pythontkintermultiple-inheritancetoplevel

Subclass of tkinter's Toplevel class doesn't seem to inherit "tk" attribute


I'm writing a Python application using tkinter GUI. I created the TimestampWindow class which should be one of my Windows and made it extend the Toplevel class of tkinter's library and Window, a custom class where I put common attributes/methods for all my Windows. Unfortunately when I try to call self.title(...) on Window class (from a TimestampWindow instance) I get the following error:

File "/home/ela/elaPythonVirtualENV/PythonScripts/pgnclocker/pgnClocker/gui/windows/Window.py", line 54, in setUp
  self.title(CommonStringsEnum.APP_NAME.value)     
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/tkinter/__init__.py", line 2301, in wm_title
  return self.tk.call('wm', 'title', self._w, string)
       ^^^^^^^
AttributeError: 'TimestampWindow' object has no attribute 'tk'

Things doesn't change if I move the invocation of title method directly in TimestampWindow.

I leave two snippets with a "sketch" of TimestampWindow and Window classes below to clarify the whole thing:

Timestamp Window

import tkinter as tk
from pgnClocker.gui.windows.Window import *
... more imports ...

class TimestampWindow(tk.Toplevel, Window):

    ... code ...

Window

class Window:

    ... code ...

    def setUp(self):
        self.title(CommonStringsEnum.APP_NAME.value)     

Could you please help me understand what's going on here? Shouldn't I be able to use TimestampWindow as a tkinter window since it inherits all methods from tk.Toplevel? Why it isn't inheriting tk attribute?


Solution

  • tk is an attribute of the root window. To access it, you need to pass the master to your class.

    import tkinter as tk
    
    
    class Window:
    
        def setUp(self):
            self.title("Title")
    
    
    class TimestampWindow(tk.Toplevel, Window):
        
        def __init__(self, master): # <-
            super().__init__(master) # <-
            print("master:", master, master.call)
            print("self.tk:", self.tk, self.tk.call)
            #master.call('wm', 'title', self, "New title")
            self.tk.call('wm', 'title', self, "New title")
            self.transient(master)
            #self.setUp()
    
    root = tk.Tk()
    print("root.tk:", root.tk, root.tk.call)
    root.title("Root")
    t = TimestampWindow(root) # <-
    root.mainloop()
    

    Example with more classes

    Classes:

    import tkinter as tk
    import tkinter.ttk as ttk
    
    
    print(tk._default_root)
    
    
    class Window:
    
        # if you need it
        def __init__(self, attr):
            self.attr = attr
            print("Window:", locals())
    
        def setUp(self):
            self.title("Title")
            
    
    class TimestampWindow(tk.Toplevel, Window):
    
        def __init__(self, root_window, attr):
            tk.Toplevel.__init__(self, root_window) # super().__init__(root_window)
            # if you need it
            Window.__init__(self, attr)
            # explicit master - self
            b1 = tk.Button(self, text="Button2")
            b1.pack()
            print("TimestampWindow:", locals())
            # attr, setUp, tk, and others should be there
            # print(dir(self))
            self.setUp()
    
          
    class RootWindow(tk.Tk, Window):
        def __init__(self):
            tk.Tk.__init__(self) # super().__init__()
            # if you need it
            Window.__init__(self, "Root")
            print("RootWindow:", locals())
            self.setUp()
            
    
    class GUI:
        def __init__(self):
            root = RootWindow()
            root.config(bg="white")
            print("root.tk:", root.tk)
            # explicit master - root
            t = TimestampWindow(root, "Toplevel")
            t.config(bg="white")
            # the button will be associated with the first root created
            b = tk.Button(text="Button1")
            b.pack()
            print("GUI:", locals())
            print("t.master is root:", t.master is root)
            print("b.master is root:", b.master is root)
            print("root is tk._default_root:", root is tk._default_root)
            root.mainloop()
    
    
    def main():
        # try with blue master, see where Button1 is
        #root1 = RootWindow()
        #root1.config(bg="blue")
        #print(root1.tk)
        #print(root1 is tk._default_root)
    
        # If there is no call to tk.Tk(), Toplevel and Style initiate the creation of the root behind the scene.
    
        # or try with red master, see where Button1 is
        #top = tk.Toplevel()
        #top.master.config(bg="red")
        #top.config(bg="red")
        #print(top.master.tk)
        #print(top.master is tk._default_root)
    
        # or try with yellow master, see where Button1 is
        #s = ttk.Style()
        #s.master.config(bg="yellow")
        #print(s.master.tk)
        #print(s.master is tk._default_root)
        gui = GUI()
    
    
    if __name__ == "__main__":
        main()
    

    Master in tkinter: