Tkinter Class structure (class per frame) issue wi

2019-09-06 06:55发布

问题:

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

回答1:

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>


回答2:

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()