1

Say I have a collection of classes using Tkinter:

class MAIN(tk.Tk):
    def __init__(self, *args):
    ...
class Page1(tk.Frame): #Login page Frame
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        ...
        def Method(self):
        ...
class Page2(tk.Frame): 
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        ...

Say Page2 needs to use Method(self) from Page1, how would it be designed? Assuming that we have Tkinter frames, each frame is written in a class format with all the frames initiated in the MAIN class.

What I am trying to do, for a while now with no success, is to get my main menu page to update it's List Box which takes and displays arguments from a database. For example, When I insert another row in the db using Page2 it has to bring me back to Page1, However, Page1 is static and does not update. I'm learning UI, and stuck on this for days now. I have a function that deletes the list box arguments, and a function to display arguments again, But, they are in Page1 and once I raise Page1 back up from Page2, Page1 won't update arguments.

EDIT: This is the Class with the method I need to use elsewhere (Continue below)

class mainMenuPage(tk.Frame): #Main Menu Frame, consists of the main navigation page, List box
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        self.controller = controller

        self.listBox()


        #self.listBox_delete(numrow)

        self.label = tk.Label(self, text="Search employee by typing his/her last name:")
        self.label.grid(row=0, column=1)

        self.entry_username = tk.Entry(self)
        self.entry_username.grid(row=0, column=3)


        button = tk.Button(self, text='Add Employee', width=15,
                            command=lambda: (controller.clear_shared_data(), controller.raise_frame(addEmpFrame)))
        button.grid(row=2, column=3)
        button = tk.Button(self, text='Remove Employee', width=15, command=lambda: controller.raise_frame(removeEmpFrame))
        button.grid(row=3, column=3)
        button = tk.Button(self, text='Employee Lookup', width=15, command=lambda: controller.raise_frame(EmpLookupFrame))
        button.grid(row=4, column=3)
        button = tk.Button(self, text='Calculate Pay Roll', width=15, command=lambda: controller.raise_frame(calcPayRollFrame))
        button.grid(row=5, column=3)

    def listBox(self):
        self.row = []

        self.scrollbar = tk.Scrollbar(self)
        self.scrollbar.grid(row=2, column=2)

        connection: Connection = pymysql.connect(host='localhost',
                                                 port=3306,
                                                 user='root',
                                                 password='root',
                                                 db='hrdb')
        cursorObject = connection.cursor()
        cursorObject.execute('SELECT `firstName`,`lastName` from `employeeprofile`')
        numrows = int(cursorObject.rowcount)
        for x in range(0, numrows):
            self.row.append(cursorObject.fetchone())

        self.list1 = tk.Listbox(self, height=10, width=35)
        for x in self.row:
            self.list1.insert(END, x)
        self.list1.grid(row=2, column=0, columnspan=2)

        self.scrollbar.config(command=self.list1.yview)

        return numrows

    def listBox_delete(self, numrow):
        self.list1 = tk.Listbox(self, height=10, width=35)
        for x in range(0, numrow):
            self.list1.delete('0', 'end')
        self.list1.grid(row=2, column=0, columnspan=2)
        self.list1 = tk.Listbox(self, height=10, width=35)

The class, which is a Frame of my UI, is basically a main menu navigator with a list box of values from mySQL. The problem here, is after using any other Frame or operation within the program which leads me back to the main menu, the program wont update the List Box list1, i.e when I add an employee into the system, the new employee shows in the list box only after I restart the app. (The methods work with the db). I know the reason is the frame is static, and my list box is created once and nothing is manipulated since, but, I can't find a way to basically generate the list box every time the frame is called, and in turn get all the values from the db again to update the list box.

Alex Kulinkovich
  • 4,408
  • 15
  • 46
  • 50
Yurius
  • 25
  • 5

1 Answers1

2

The only way to reference one frame from the other is through the controller. Consider this example:

import tkinter as tk

class MAIN(tk.Tk):
    def __init__(self, *args):
        tk.Tk.__init__(self)

        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)

        self.frames = {}
        for F in (Page1, Page2):
            page_name = F.__name__
            frame = F(parent=container, controller=self)
            self.frames[page_name] = frame

            frame.grid(row=0, column=0, sticky="nsew")

    def show_frame(self, page_name):
        frame = self.frames[page_name]
        frame.tkraise()

    def get_frame(self, page_name):
        frame = self.frames[page_name]
        return frame


class Page1(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        tk.Label(self, text='Page 1').pack()

    def Method(self):
        print('Page 1 method')


class Page2(tk.Frame): 
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        tk.Label(self, text='Page 2').pack()
        tk.Button(self, text='Raise Page 1 and execute function', command=self.exit).pack()

    def exit(self):
        self.controller.show_frame("Page1")
        self.controller.get_frame("Page1").Method()


app = MAIN()
app.mainloop()

Page1 has no reference to Page2 but it does have controller which is a reference to the instance of MAIN. Because MAIN is the controller, it "knows" all the frames. I gave MAIN the get_frame method that returns a reference to the frame by its name. Therefore, to reference Page1 from any other frame, you can do self.controller.get_frame("Page1"). To then call method Method(), simply add that to make self.controller.get_frame("Page1").Method()

Because you haven't shown your actual main class, I'm not sure on how you save references to your frames, but I think you used this as a start. From my example I think the concept should be clear.


Another way to accomplish what you want would be to make sure a method is called every time Page1 is made visible, to do this follow the explanation in the answer to How would I make a method which is run every time a frame is shown in tkinter.

fhdrsdg
  • 10,297
  • 2
  • 41
  • 62
  • I am using a controller, yes, but since the generation of the list box happens in one of the frame classes, controller can't really access it (because I have an object of main class, just like you described). That's basically where my complication comes from. However, the second way I had no idea about and I will try implementing that shortly. Thank you very much, the way it is described, is exactly what I need (Basically `listBox()` to be run every time the frame is shown. – Yurius Oct 30 '18 at 11:37
  • 1
    a slightly better solution would be to give the controller a method that returns the instance of a page (eg: `self.controller.get_page('Page1')`. This way the individual pages don't need to know about the internal implementation details of the controller. – Bryan Oakley Oct 30 '18 at 11:41
  • @BryanOakley mind elaborating please? Say I have a main `GUI` class with a controller, and `mainMenuPage` is the one i need to constantly be refreshed when raised by `def raise_frame:... tkraise()`, there is a way to use controller to constantly re-generate the frame? – Yurius Oct 30 '18 at 14:21
  • @Yurius I believe what @BryanOakley means is that I directly accessed an attribute of the controller (`controller.frames`) from `Page2`. This means that the frame has some knowledge over the inner workings of the controller, which shouldn't be necessary. If frames should be able to get a reference to another frame from the controller, the controller should have a method which does this. This makes that the frame only needs to communicate to the controller through methods. I changed my answer to incorporate what I believe Bryan meant. – fhdrsdg Oct 30 '18 at 14:44
  • @fhdrsdg Thank you very much for your response and for your time. I just got it working. Appreciate you both – Yurius Oct 30 '18 at 15:20