Search code examples
pythonclasstkinterclass-attributes

How can I modify a variable being used in one class from another class by using tkinter?


What I'm trying to accomplish is a way of changing a variable in another class instance. I've been scratching my head trying to understand how (or if) this is possible.

How can one update the value of self.lblvar that is in the class MainWindow from the class SecondWindow?

This is what I'm working with to test my theory:

from tkinter import *

class MainWindow:
    def __init__(self, rootWin):
        self.rootWin = rootWin
        self.rootWin.geometry('400x200')

        self.mainMenu = Menu(self.rootWin)
        self.mainMenu.add_command(label = 'Open Second Window', command = self.openSecondWindow)

        self.lblvar = StringVar()
        self.lblvar.set('Change Me!')
        self.lbl = Label(rootWin, textvariable = self.lblvar)
        self.lbl.pack()

        self.rootWin.config(menu = self.mainMenu)

    def openSecondWindow(self):
        self.secondWin = Tk()
        self.secWin = SecondWindow(self)
        self.secondWin.mainloop()

class SecondWindow:
    def __init__(self, parent):
        self.parent = parent

        self.btn = Button(self, label = 'Change Label?', command = self.changeOther)
        self.btn.pack()

    def changeOther(self):
        self.parent.lblvar.set('Changed it!')

def main():
    root = Tk()
    mainWin = MainWindow(root)
    root.mainloop()

if __name__ == "__main__":
    main()

I'm a bit of a novice when it comes to Python classes, so any guidance and/or explanation regarding this would be appreciated!

Edit: Changed original question to a clearer question to aid further searches on the topic


Solution

  • So the minimal changes that are needed to get your code to work as exspected would be to change your class SecondWindow to accept 2 arguements, one for the top level window and one for the main window class as well as changing the method openSecondWindow to work with a Toplevel window instead of a 2nd instance of Tk as you should never use more than one instance of Tk() within a tkinter GUI. One minor issue is you use label = inside of your button and this should actually be text=. All so we can work with the class attribute.

    Something like this:

    def openSecondWindow(self):
        self.secondWin = Toplevel(self.rootWin)
        self.secWin = SecondWindow(self.secondWin, self)
    
    class SecondWindow:
        def __init__(self, top, master):
            self.master = master
    
            self.btn = Button(top, text='Change Label?', command=self.changeOther)
            self.btn.pack()
    
        def changeOther(self):
            self.master.lblvar.set('Changed it!')
    

    That said I would change a few things overall to improve readability and reduce the lines of code.

    1. import tkinter as tk instead of using *. This will help prevent overwriting of built in methods/other imports as well as make it clear what library you are working with in the code.

    2. Inherit from Tk() and Toplevel() in your class to make things easier to work with.

    3. Only use self. where it is needed. Some places in your code this is not needed.

    See below reworked example and let me know if you have any questions:

    import tkinter as tk
    
    
    class MainWindow(tk.Tk):
        def __init__(self):
            # super is used to avoid referring to the base class explicitly.
            # to read more on this check out the link below.
            super().__init__()
            self.geometry('400x200')
            main_menu = tk.Menu(self)
            self.config(menu=main_menu)
            main_menu.add_command(label='Open Second Window', command=SecondWindow)
            self.lblvar = tk.StringVar(value='Change Me!')
            # No need to use `self.` on this label as we are working with the lblvar in the 2nd window and not the label.
            tk.Label(self, textvariable=self.lblvar).pack()
    
    
    class SecondWindow(tk.Toplevel):
        def __init__(self):
            super().__init__()
            # No need to use `self.` on this button unless down the road you want to change this button in some way.
            tk.Button(self, text='Change Label?', command=self.change_other).pack()
    
        def change_other(self):
            # with inheriting like this in tkinter you do not need to define self.master as it is automatic here.
            # Other classes or methods may need it defined however. Just wanted to make sure you are aware of this.
            self.master.lblvar.set('Changed it!')
    
    
    if __name__ == "__main__":
        MainWindow().mainloop()
    

    Results:

    enter image description here

    More details on super().