0

Inspired by this 300+ vote closed Q&A: Best way to structure a tkinter application?, I'm looking to avoid explicitly using root in a function within a class. I think it should be implicitly declared through self or parent or something like that. Here is the code in question:

I have this code...

        self.label_this = tk.StringVar()
        self.label_last = tk.StringVar()
        self.label_total = tk.StringVar()

        tk.Label(count_frame, textvariable=self.label_this, \
                 font=(None, MON_FONTSIZE)).pack(anchor=tk.W)
        tk.Label(count_frame, textvariable=self.label_last, \
                 font=(None, MON_FONTSIZE)).pack(anchor=tk.W)
        tk.Label(count_frame, textvariable=self.label_total, \
                 font=(None, MON_FONTSIZE)).pack(anchor=tk.W)
        self.update_cnt_labels()

Then later on...

        ''' Get list of Window ID's on monitor now '''
        new_windows = self.windows_on_monitor(new_windows)
        new_windows_cnt = len(new_windows) / WIN_CNT
        if self.old_windows_cnt == new_windows_cnt :
            FlashMessage (self.label_this, "No new windows to remove...", \
                          3, 750, 250)
            self.update_cnt_labels()
            return

Then later on...

class FlashMessage:
    def __init__(self, widget, message, count=5, on=500, off=300):

        self.delay_show (1, widget, message)
        for i in range(count):
            self.delay_show (on, widget, "")
            self.delay_show (off, widget, message)

    def delay_show(self, ms, widget, message):
        root.after(ms, widget.set(message))
        root.update_idletasks()

I want to avoid using root in the last two lines and use self or something similar.

My program call chain is something like:

  • the traditional: root = tk.Tk()
  • bunch of mainline initialization stuff.
  • the class: ResizingCanvas(mycanvas)
  • mainline function: popup(event) which is bound to <ButtonPress-1>
  • Dynamically formatted menu.tk_popup(event.x_root, event.y_root)
  • the class: RemoveNewWindows()
  • the function: remove()
  • the class: FlashMessage() (show above)
  • the function: self.delay_show() (shown above)

Each class and function has haphazard self, positional parameters, *args and **kwargs which mostly serve no purpose. Indeed even the __init__ above might be unnecessary. This is a result of copying code all over stack overflow.

Every second word in the program seems to be self but the word parent is only used in the class ResizingCanvas(). Do I have to propagate parent down the call list and use it somehow?

WinEunuuchs2Unix
  • 1,801
  • 1
  • 17
  • 34
  • I would say that the Tkinter root window is a perfectly valid use of a global variable; after all, you can't have more than one of them (without all sorts of problems). On the other had, neither of the uses of `root` in the code you posted actually need the root window, those are methods that could be invoked on any widget you happen to have handy, without any difference in effect. – jasonharper Jan 07 '20 at 03:03
  • `explicitly is better then implicitly` - I would send `root` to every class which will need it later and keep as `self.root` or `self.parent` or `self.master`. Next time you can send other widget as parent and use this class in other window (Toplevel) or inside some widget (not directly in main window). – furas Jan 07 '20 at 03:08
  • @furas Yes I'm extensively using `Toplevel` within `Toplevel` throughout the program. However `self.topevel.update_idletasks()` is not working as it has no attribute or whatever the error message is. – WinEunuuchs2Unix Jan 07 '20 at 03:40
  • then show full error message in question. – furas Jan 07 '20 at 03:44
  • @furas I've added more code to question. – WinEunuuchs2Unix Jan 07 '20 at 03:47

1 Answers1

1

You can call after and update_idletasks on any widget. There are many such functions that can be called on any widget but which have a global effect.

In your case, you'll need to pass some widget into the FlashMessage constructor and save the reference. You can then use the reference to call the functions.

You're passing something called widget that doesn't actually contain a widget. You need to rename it to something more appropriate (eg: var), and then pass in an actual widget.

(Note: you also are calling after incorrectly, which I've fixed in the following example)

For example:

class FlashMessage:
    def __init__(self, widget, var, message, count=5, on=500, off=300):
        self.widget = widget
        ...

def delay_show(self, ...):
    self.widget.after(ms, var.set, message)
    self.widget.update_idletasks()

Then, whenever you create an instance of FlashMessage you need to add a widget as the first parameter.

For example, assuming that count_frame is defined in the context where you create an instance of FlashMessage and it is an actual widget, it might look something like this:

if self.old_windows_cnt == new_windows_cnt :
    FlashMessage (count_frame, self.label_this, "No new windows to remove...", \
                  3, 750, 250)
    self.update_cnt_labels()
    return
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Sorry a little too advanced for me today. After using `widget...` I get the error: `AttributeError: StringVar instance has no attribute 'after'` In this case `widget` is a `tk.Label` variable text string. – WinEunuuchs2Unix Jan 07 '20 at 03:38
  • @WinEunuuchs2Unix: that tells me that `widget` doesn't actually contain a widget. If `widget` really was a `tk.Label` you definitely would not get that error. – Bryan Oakley Jan 07 '20 at 03:39
  • Sorry I got used to "widget" as meaning "thing". It is really tk.StringVar which is used to update variable text in a Label field in this instance. – WinEunuuchs2Unix Jan 07 '20 at 03:42
  • @WinEunuuchs2Unix: the term `widget` in the context of a tkinter application has a very specific meaning that specifically refers to a subclass of `tk.Widget` -- Buttons, Labels, etc. and not variables. – Bryan Oakley Jan 07 '20 at 03:44
  • I've updated question with code that hopefully illustrates everything you need. I'll rename the field widget to object or variable or something like that. – WinEunuuchs2Unix Jan 07 '20 at 03:49
  • Could I also assign the label to a name and pass that name as a widget instead of the frame? It's easier passing the frame which has a name already but I'm just curious if a named Label qualifies as a widget just as a named Frame would. – WinEunuuchs2Unix Jan 07 '20 at 04:09
  • @WinEunuuchs2Unix: yes, a label is a widget. – Bryan Oakley Jan 07 '20 at 04:13
  • I went with the `self.count_frame` widget because it was already named and encompasses all three variable texts should they be needed later. Also instead of generic `var` I used the same name as tikinter which is `textvariable`. On a side note Python is a wonderful coding experience and I'm looking forward to the next couple / few years. TYVM for your help. The new code works perfectly and feels much better than using `root.update_idlteasks()`. – WinEunuuchs2Unix Jan 07 '20 at 04:23