0

I'm trying to replicate an answer to my own accord from this answer/question here: Switch between two frames in tkinter

As you can see from the answer above, tehy instantiate multiple sub-classes of the Frame widget, and when we want to switch pages, we click a button and that class a method from our base class.

However, wouldn't creating multiple 'pages' methods under a 'Pages' class, be a lot cleaner and make sense? I'm not sure what to believe, and I would love clarification as to how I should be tackling this project, and why using classes or instanced methods would be better?

I've added my comments into the code below for lines I don't quite understand and I'm hoping I can gain some knowledge from the world of StackOverflow.

import Tkinter as tk

LARGE_FONT = ("Verdana", 12)

class Main(tk.Tk):

    def __init__(self, *args, **kwargs):
    ########### are we calling the Tk class from tkinter and passing in our 'Main' class?
        tk.Tk.__init__(self, *args, **kwargs)
    # why not self.container?
        container = tk.Frame(self)
    # again, should I be using self.page here instead?
        page = Pages(parent=container, controller=self)

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

        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = {}
        for F in ('page.page_one()', 'page.page_two()'):
        # what exactly does __name__ do? And how can I replicate this with my derived classes instanced methods?
            page_name = F#F.__name__
            frame = page#(parent=container, controller=self)
            self.frames[page_name] = frame
            frame.grid(row=0, column=0, sticky='nsew')

        self.show_frame("page.page_one()")

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

class Pages(tk.Frame):

    # i could just use *args, **kwargs here couldn't I?
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.page_one(controller)

    # here I have my instance methods inside my derived 'Pages' class
    # isn't this much cleaner, having multiple instance methods inside a class?
    # or I should be separating each page into it's own instanced class?
    def page_one(self, controller):
        label = tk.Label(self, text='show_firmware_page', font=LARGE_FONT)
        label.pack(pady=10, padx=10)

        # how can I switch pages using the next button?
        next_btn = tk.Button(self, text='Next', command=lambda: controller.show_frame(self.page_two(controller)))
        quit_btn = tk.Button(self, text='Quit', command=lambda: controller.show_frame())

        quit_btn.pack()
        next_btn.pack()

    def page_two(self, controller):
        label = tk.Label(self, text='second_page', font=LARGE_FONT)
        label.pack(pady=10, padx=10)

        # how can I switch pages using the next button?
        next_btn = tk.Button(self, text='Next', command=lambda: Pages.show_frame("page_one"))
        quit_btn = tk.Button(self, text='Quit', command=lambda: Pages.show_frame())

        quit_btn.pack()
        next_btn.pack()


app = Main()
app.mainloop()

Basically, my current push is to try and use methods within my class in order to define my pages and switch between them. I'm currently having some trouble, and upon taking a look at other's answers, a lot of them have the idea that each class instantiates a Frame, in which we call a method to switch between those instances.

Let me know your thoughts on this process to get me up to speed about how I should be tackling this project.

Many thanks in advance for your help - I really want to get this OOP stuff down.

martineau
  • 119,623
  • 25
  • 170
  • 301
juiceb0xk
  • 949
  • 3
  • 19
  • 46
  • 3
    IMO trying to learn OOP by using tkinter probably isn't a good way to do it because the objects in it frequently don't behave like they would regular Python classes and instances of them. It's because it's nothing more than an interface to the Tk GUI toolkit, and while it has classes and instance, they often don't behave the same way they would if they were written in pure Python (or C++, or Java, or etc). – martineau Aug 31 '17 at 01:31
  • @martineau Thanks for that. I guess what I should be doing with this problem is to just suck it up, go with the multiple classes approach and revisit this project at a later time once I've fully grasped OOP and try and implement page methods instead of classes. I just wasn't sure which is the more 'pythonic' way, if you will, to implement classes instead of methods and vice versa for this project. – juiceb0xk Aug 31 '17 at 01:54

1 Answers1

1

You could theoretically make methods in a single class reconfigure the UI the way you want, but it's probably not going to be easy.

Your current code can't work because there's no simple way for one of your methods to clean up the work done by another previous method (e.g. by getting rid of the label and buttons it created).

The original answer you linked to avoided that issue by having all the widgets created at the start of the program (not only when they're about to be displayed). Only one page is displayed at a time though, since they're all Frames that have been configured to display in the same location. The Frame that is on top hides the others and prevents their widgets from doing anything. By moving a different frame on top, you can switch between the pages.

If we ignore those widget display issues, you could make your main class call the methods you've written:

class Main(tk.Tk):

    def __init__(self, *args, **kwargs):
        container = tk.Frame(self) # I'm not sure a separate container is necessary
        container.pack(side="top", fill="both", expand=True)  # since we're only putting 
        container.grid_rowconfigure(0, weight=1)              # one other frame inside it
        container.grid_columnconfigure(0, weight=1)

        page = Pages(parent=container, controller=self)
        page.grid(row=0, column=0, sticky='nsew') # moved up from below

        self.frames = {}
        for F in (page.page_one, page.page_two): # no quotes or parentheses
            page_name = F.__name__ # the names are the strings "page_one" and "page_two"
            self.frames[page_name] = F

        self.show_frame("page_one")

    def show_frame(self, page_name):
        method = self.frames[page_name]
        method() # call the method we are switching to

class Pages(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller

    def page_one(self):
        # do something to clean up previous pages here?
        label = tk.Label(self, text='show_firmware_page', font=LARGE_FONT)
        label.pack(pady=10, padx=10)

        next_btn = tk.Button(self, text='Next',
                             command=lambda: self.controller.show_frame("page_two")
        next_btn.pack()

    def page_two(self):
        label = tk.Label(self, text='second_page', font=LARGE_FONT)
        label.pack(pady=10, padx=10)

        next_btn = tk.Button(self, text='Next',
                             command=lambda: self.controller.show_frame("page_one"))
        next_btn.pack()

This will work (for some definition of "work"). I removed the quit buttons because I'm not sure exactly what the best way to handle them is (you probably don't want them to be calling show_frame).

Note that I'm by no means an expert at TKinter, so it's entirely possible that there's some easy way to remove the widgets from the previous page when you've moved on to the next one. I just don't know how.

Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • Thanks for that, this is very helpful. My main idea when switching frames/pages is to keep them stacked on top of each other. The way that my code is currently working makes it so that it appends each page onto each other and creates a larger windows, appending the pages vertically. I need them stacked up onto each other, so I don't need to clean up any widgets. How can I take the idea from the original answer that I've linked and make it so a page is raised over the other? – juiceb0xk Aug 31 '17 at 11:30
  • Um, there's nothing to stack in this version, since there's only the one `Pages` instance. If you want to be stacking frames, you probably want several instances of different classes, like the other code had. – Blckknght Aug 31 '17 at 17:29
  • Yes, that is what I was trying to achieve from the very beginning. I wasn't sure whether or not I should be using instances of classes or instanced methods. I was trying to instatiate a frame with my methods but I'm having trouble doing that. Would it ever be possible to stack frames on top of each other using methods under a `Pages` class, like I currently have laid out, or is that not the Pythonic way to go and rather I should be doing what the other answer had with multiple classes? – juiceb0xk Sep 01 '17 at 03:39
  • The question is, which frames are you going to be stacking? If you only create one instance, there isn't more than one to stack or switch between. It's not at all clear to me why you want to go with methods instead of classes. – Blckknght Sep 01 '17 at 04:53
  • The reason as to why I'm wanting to use methods is that it makes sense that I should be creating different objects under my `Pages` class, essentially each method represents a page. Am I able to create multiple instances of a frame using those methods, or am I using methods and classes wrong? This is where I'm stuck at with OOP. – juiceb0xk Sep 01 '17 at 05:34