1

I am trying to figure out what the best way to communicate between different widgets is, where the widgets are custom classes inheriting from tkinter widgets and I have several frames present (to help with layout management). Consider for example the following simple gui (written for python 3, change tkinter to Tkinter for python 2):

from tkinter import Frame,Button,Tk

class GUI(Frame):
    def __init__(self, root):

        Frame.__init__(self,root)

        self.upper_frame=Frame(root)
        self.upper_frame.pack()

        self.lower_frame=Frame(root)
        self.lower_frame.pack()

        self.upper_btn1 = Button(self.upper_frame, text="upper button 1")
        self.upper_btn2 = Button(self.upper_frame, text="upper button 2")
        self.upper_btn1.grid(row=0,column=0)
        self.upper_btn2.grid(row=0,column=1)

        self.lower_btn = CustomButton(self.lower_frame, "lower button 3")
        self.lower_btn.pack()


class CustomButton(Button):
    def __init__(self,master,text):
        Button.__init__(self,master,text=text)

        self.configure(command=self.onClick)

    def onClick(self):
        print("here I want to change the text of upper button 1")  


root = Tk()
my_gui = GUI(root)
root.mainloop()

The reason I put them in different frames is because I want to use different layout managers in the two different frames to create a more complicated layout. However, I want the command in lower_btn to change properties of eg upper_btn1.

However, I can not immediately access upper_btn1 from the instance lower_btn of the customized class CustomButton. The parent of lower_btn is frame2, and the frame2 parent is root (not the GUI instance). If I change the parent of lower_btn to the GUI instance, ie

self.lower_btn = CustomButton(self, "lower button")

it will not be placed correctly when using pack(). If I change the parent of frame1/frame2 to the GUI instance, ie

self.upper_frame=Frame(self)
self.upper_frame.pack()

self.lower_frame=Frame(self)
self.lower_frame.pack()

I could in principle access the upper_btn1 from lower_btn by self.master.master.upper_btn1, BUT then the frame1/frame2 are not placed correctly using pack() (this I don't understand why). I can of course pass the GUI instance as separate variable, on top of master, to CustomButton, ie something like

class CustomButton(Button):
    def __init__(self,master,window,text):
        Button.__init__(self,master,text=text)
        self.window=window
        self.configure(command=self.onClick)

    def onClick(self):
        self.window.upper_btn1.configure(text="new text")

and then change the construction of lower_btn to

self.lower_btn = CustomButton(self.lower_frame,self, "lower button 3")

but is that the "correct" way of doing it?

So, what is the best way to reorganize this gui? Should I pass GUI as a separate variable on top of the master/parent argument? Should I change the master/parent of the buttons and/or the frames (and somehow fix the issues with the layout management)? Or should I make the commands of the buttons as methods of the GUI instance so they can communicate with the widgets in that GUI, although this does not really feel like object oriented programming? I can't seem to find a working object oriented design that feels "correct". Also, an explanation why pack() does not work for frame1/frame2 if I make the GUI instance (self) the master, would also be appreciated, as that would have been my intuitive, most object oriented, approach.

Edit: Maybe the best way is to not use frames inside frames at all, and use only grid() and then use colspan/rowspan to give the layout management more flexibility.

Jonathan Lindgren
  • 1,192
  • 3
  • 14
  • 31

1 Answers1

0

A year late, but: One way to communicate between widgets is to instantiate some kind of control center object that receives knowledge about the state of your widgets and then compels other widgets to act on that information. This 'manager' functionality will be independent of the layout of your widgets.

Here's an implementation that's customized to your example. The idea is to add a .manager attribute to the lower button, and to notify this manager when clicked. The GUI class remains unchanged.

from tkinter import Frame,Button,Tk

class Manager(object):
    def __init__(self, gui):
        self.gui = gui
        self.gui.lower_btn.manager = self

    def onClick(self):
        self.gui.upper_btn2.configure(text="changed text")

class GUI(Frame):
    def __init__(self, root):

        Frame.__init__(self,root)

        self.upper_frame=Frame(root)
        self.upper_frame.pack()

        self.lower_frame=Frame(root)
        self.lower_frame.pack()

        self.upper_btn1 = Button(self.upper_frame, text="upper button 1")
        self.upper_btn2 = Button(self.upper_frame, text="upper button 2")
        self.upper_btn1.grid(row=0,column=0)
        self.upper_btn2.grid(row=0,column=1)

        self.lower_btn = CustomButton(self.lower_frame, "lower button 3")
        self.lower_btn.pack()

class CustomButton(Button):
    def __init__(self,master,text):
        Button.__init__(self,master,text=text)

        self.configure(command=self.onClick)
        self.manager = None

    def onClick(self):
        if self.manager:
            self.manager.onClick()
        else:
            print("here I want to change the text of upper button 1")  

root = Tk()
my_gui = GUI(root)
Manager(my_gui)
root.mainloop()
grand_chat
  • 431
  • 3
  • 3