0

Ive been trying out OOP for use with Tkinter - Im getting there (I think) slowly...

I wanted to build a structure where each frame is handled by its own class, including all of its widgets and functions. Perhaps I am coming from the wrong angle but that is what makes most logical sense to me. - Feel free to tell me if you agree / disagree!

I know why the problem is happening - when im calling each class my __init__ runs everytime and builds the relevant widgets regardless of whether they are already present in the frame. However, the only way I can think of getting round this would be to build each frame in the __init__ of my primary class GUI_Start. - Although this seems like a messy and un-organised soloution to the problem.

Is there a way I can achieve a structure where each class takes care of its own functions and widgets but doesn't build the frame each time?

See below for minimal example of the issue:

from Tkinter import *

class GUI_Start:

    def __init__(self, master):
        self.master = master
        self.master.geometry('300x300')
        self.master.grid_rowconfigure(0, weight=1)
        self.master.grid_columnconfigure(0, weight=1)
        self.win_colour = '#D2B48C'
        self.frames = {}

        for window in ['win1', 'win2']:
            frame = Frame(self.master, bg=self.win_colour, bd=10, relief=GROOVE)
            frame.grid(row=0, column=0, sticky='news')
            setattr(self, window, frame)
            self.frames[window] = frame

        Page_1(self.frames)

    def Next_Page(self, frames, controller):
        controller(frames)


class Page_1(GUI_Start):

    def __init__(self, master):
        self.master = master
        self.master['win1'].tkraise()

        page1_label = Label(self.master['win1'], text='PAGE 1')
        page1_label.pack(fill=X)

        page1_button = Button(self.master['win1'], text='Visit Page 2...', command=lambda: self.Next_Page(self.master, Page_2))
        page1_button.pack(fill=X, side=BOTTOM)


class Page_2(GUI_Start):

    def __init__(self, master):
        self.master = master
        self.master['win2'].tkraise()

        page2_label = Label(self.master['win2'], text='PAGE 2')
        page2_label.pack(fill=X)

        page2_button = Button(self.master['win2'], text='Back to Page 1...', command=lambda: self.Next_Page(self.master, Page_1))
        page2_button.pack(fill=X, side=BOTTOM)


root = Tk()
gui = GUI_Start(root)
root.mainloop()

Feel free to critique the structure as I may be trying to approach this from the wrong angle!

Any feedback would be much appreciated! Luke

Luke.py
  • 965
  • 8
  • 17
  • The code you originally copied does it the right way. Each "page" should inherit from `Frame`, and all the widgets in the page go inside that frame. What are you trying to accomplish by doing it differently? See http://stackoverflow.com/a/7557028/7432 – Bryan Oakley Oct 19 '16 at 11:33
  • Not sure what 'code you originally copied' means!?? A few questions from your example - It looks like you create a frame to contain the other frames, where as I'm creating frames and gridding them in the root. Is there a reason you do this? or a reason I should? Also, could you expand on why you have double __init__ in each class? – Luke.py Oct 19 '16 at 12:11
  • For what its worth, I didn't copy any code :) – Luke.py Oct 19 '16 at 14:40
  • 1
    My mistake: I thought it looked fairly similar to the link I provided in my comment. There are many simularities, and the code shows up in a lot of questions. – Bryan Oakley Oct 19 '16 at 15:03

2 Answers2

4

The point of using classes is to encapsulate a bunch of behavior as a single unit. An object shouldn't modify anything outside of itself. At least, not by simply creating the object -- you can have methods that can have side effects.

In my opinion, the proper way to create "pages" is to inherit from Frame. All of the widgets that belong to the "page" must have the object itself as its parent. For example:

class PageOne(tk.Frame):
    def __init__(self, parent):
        # use the __init__ of the superclass to create the actual frame
        tk.Frame.__init__(self, parent)

        # all other widgets use self (or some descendant of self)
        # as their parent

        self.label = tk.Label(self, ...)
        self.button = tk.Button(self, ...)
        ...

Once done, you can treat instances of this class as if they were a single widget:

root = tk.Tk()
page1 = PageOne(root)
page1.pack(fill="both", expand=True)

You can also create a base Page class, and have your actual pages inherit from it, if all of your pages have something in common (for example, a header or footer)

class Page(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        <code common to all pages goes here>

class PageOne(Page):
    def __init__(self, parent):
        # initialize the parent class
        Page.__init__(self, parent)

        <code unique to page one goes here>
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • With this structure how would I configure for example the base size (geometry) of the window? Thanks for your answer – Luke.py Oct 19 '16 at 15:13
  • 2
    You can create a separate class for the main window, and inherit from `Tk`. Or, inherit from `object` and simply have the root window be one of its attributes. The point being, objects shouldn't directly be affecting anything outside of itself. – Bryan Oakley Oct 19 '16 at 15:22
  • Is there maybe a canonical reference which outlines how to correctly extend tkinter in this way? – user32882 Jan 31 '22 at 21:06
  • @user32882: as far as I know, no, there is not. – Bryan Oakley Jan 31 '22 at 21:08
  • @BryanOakley I apologize in advance if this seems a bit forward, but in that case how did you learn it yourself? And how can you claim this is the "proper" way to do it? – user32882 Jan 31 '22 at 21:11
  • 1
    @user32882: it's just applying basic knowledge of how tkinter works (ie: that you can put widgets inside of frames) combined with basic knowledge about python classes (eg: classes can inherit from other classes). There's nothing here that is particularly unique or interesting. I would say it's the proper way simply because it's using features of tkinter and python in the way they were designed to be used. – Bryan Oakley Jan 31 '22 at 21:16
0

Your use of OOP is not very logical here. Your main program is in the class GUI_start. If your pages inherit from GUI_start, basically you create a whole new program with every page instance you create. You should instead inherit from Frame as Bryan Oakley has pointed our in the comments. Here is a somewhat repaired version of what you have posted. The original one by Bryan is still much better.

from Tkinter import *

class GUI_Start:

    def __init__(self, master):
        self.master = master
        self.master.geometry('300x300')
        self.master.grid_rowconfigure(0, weight=1)
        self.master.grid_columnconfigure(0, weight=1)
        self.win_colour = '#D2B48C'
        self.current_page=0

        self.pages = []
        for i in range(5):
            page = Page(self.master,i+1)
            page.grid(row=0,column=0,sticky='nsew')
            self.pages.append(page)

        for i in range(2):
            page = Page_diff(self.master,i+1)
            page.grid(row=0,column=0,sticky='nsew')
            self.pages.append(page)

        self.pages[0].tkraise()

        def Next_Page():
            next_page_index = self.current_page+1
            if next_page_index >= len(self.pages):
                next_page_index = 0
            print(next_page_index)
            self.pages[next_page_index].tkraise()
            self.current_page = next_page_index

        page1_button = Button(self.master, text='Visit next Page',command = Next_Page)
        page1_button.grid(row=1,column=0)



class Page(Frame):

    def __init__(self,master,number):
        super().__init__(master,bg='#D2B48C')
        self.master = master
        self.master.tkraise()

        page1_label = Label(self, text='PAGE '+str(number))
        page1_label.pack(fill=X,expand=True)



class Page_diff(Frame):

    def __init__(self,master,number):
        super().__init__(master)
        self.master = master
        self.master.tkraise()

        page1_label = Label(self, text='I am different PAGE '+str(number))
        page1_label.pack(fill=X)



root = Tk()
gui = GUI_Start(root)
root.mainloop()
Jannick
  • 1,036
  • 7
  • 16