0

I'm trying to create an app in which i could move between windows. While trying to put together a tkinter app, I came up with an idea to create new windows as the class App: methods.

Questions: is this a good idea? Maybe such approach has some drawbacks that I do not see?

Sample code of the idea:

import tkinter as tk
import tkinter.ttk as ttk


class App:

    def __init__(self, master=None):
        self.master = master

        # Window 1 widgets
        self.frame1 = ttk.Frame(master, width=300, height=150, relief='groove')
        self.frame1.pack_propagate(False)
        self.label1 = ttk.Label(self.frame1, text='This is window 1')
        self.button1 = ttk.Button(self.frame1, text='Go to window 2', command=self.window2)
        self.button2 = ttk.Button(self.frame1, text='Go to window 3', command=self.window3)

        # Window 2 widgets
        self.frame2 = ttk.Frame(master, width=300, height=150, relief='groove')
        self.frame2.pack_propagate(False)
        self.label2 = ttk.Label(self.frame2, text='This is window 2')
        self.button3 = ttk.Button(self.frame2, text='Go to window 1', command=self.window1)
        self.button4 = ttk.Button(self.frame2, text='Go to window 3', command=self.window3)

        # Window 3 widgets
        self.frame3 = ttk.Frame(master, width=300, height=150, relief='groove')
        self.frame3.pack_propagate(False)
        self.label3 = ttk.Label(self.frame3, text='This is window 3')
        self.button5 = ttk.Button(self.frame3, text='Go to window 1', command=self.window1)
        self.button6 = ttk.Button(self.frame3, text='Go to window 2', command=self.window2)

        self.window1()

    def window1(self):
        self.forget_widgets()
        self.frame1.pack(side='top', pady=(25, 0))
        self.label1.pack(side='top', pady=(25, 25))
        self.button1.pack(side='top', pady=(0, 5))
        self.button2.pack(side='top')

    def window2(self):
        self.forget_widgets()
        self.frame2.pack(side='top', pady=(25, 0))
        self.label2.pack(side='top', pady=(25, 25))
        self.button3.pack(side='top', pady=(0, 5))
        self.button4.pack(side='top')

    def window3(self):
        self.forget_widgets()
        self.frame3.pack(side='top', pady=(25, 0))
        self.label3.pack(side='top', pady=(25, 25))
        self.button5.pack(side='top', pady=(0, 5))
        self.button6.pack(side='top')

    def forget_widgets(self):
        for widget in self.master.winfo_children():
            widget.pack_forget()


if __name__ == '__main__':
    root = tk.Tk()
    root.geometry('350x200')
    App(master=root)
    root.mainloop()

While looking for other possible methods I have found that it is either best to use the toplevel widget, or create each window as a new class. The first method is not what i am looking for though, because I am trying to replicate a game-like GUI right now. As for the second method, I am not really sure. While surfing in the net I found out that creating a class with only __init__ method is overuse of classes and in general a bad practice. This is done in an answer by Bryan Oakley here on stackoverflow though: Switch between two frames in tkinter

Could anyone elaborate on this and try to explain me which method is the best and the one I should stick to while learning tkinter? Thanks!

  • ***While surfing in the net I found out that creating a class with only __init__ method is overuse of classes and in general a bad practice***. I am not sure what you mean by this. `__init__` is used when building a GUI in a inherited class for good reason. Can you elaborate on the issue you see with `__init__`? – Mike - SMT Jan 30 '19 at 16:11
  • Actually, I am very new to programming and thus I can't really give my own arguments. The point there was that anything, that can be written in with class that holds only __init__ method or an __init__ method and one another method probably can just be simplified to a few lines of code without any classes, methods or functions at all. – Aivaras Kazakevičius Jan 30 '19 at 16:16
  • 1
    Well I would argue at lest for Tkinter it is better to use an OOP approach and take advantage of inheriting from a Tkinter class. – Mike - SMT Jan 30 '19 at 16:21

1 Answers1

1

Your code works fine. It is a different approach I would use but still works. One of the downsides to using a class and building all your code inside that class in different methods is a maintainability issue. The larger the code gets the harder it is to make changes and fix issues that come up.

That said for smaller programs I do not see any harm in using a class in this way.

While surfing in the net I found out that creating a class with only init method is overuse of classes and in general a bad practice.

I am note 100% sure what you mean by this but it sounds like you are saying the articles you have found do not want you to use the __init__ method of the class. Honestly I have no idea why one would say that. There might be a reason but for the 2 years I have spent building GUI's with Tkinter I have always used the __init__ method to build the basics of the GUI and then class methods or other class objects to work out the rest of my GUI and back end.

As for the linked post that was answered by Bryan I would say that Bryan is very knowledgeable in tkinter and python in general. If it were a bad idea to use multiple classes he would say so. I think you can build things in multiple ways but I have found using separate classes for different portions of your GUI is good for maintainability IE (class for menu, class for saving files, class for controlling themes). By separating them you can keep them in separate files allowing you to read through just the part of the code you need to look at and not worry about reading through a wall of text to figure out where things are going wrong.

All that said unless your code is going to be large or extremely resource intensive I would say build your class or classes the way you want.

I have taken your code and rebuilt it the way I would have written this to give you some idea of how you can approach this in another way. I have inherited form Tk() and used only one class attribute to build my windows.

import tkinter as tk
import tkinter.ttk as ttk


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.geometry('350x200')
        self.working_frame = ttk.Frame(self)
        self.working_frame.grid(row=0, column=0, sticky='nsew')
        self.window1()

    def window1(self):
        self.working_frame.destroy()
        self.working_frame = ttk.Frame(self, width=300, height=150, relief='groove')
        self.working_frame.pack_propagate(False)
        ttk.Label(self.working_frame, text='This is window 1').pack(side='top', pady=(25, 25))
        ttk.Button(self.working_frame, text='Go to window 2', command=self.window2).pack(side='top', pady=(0, 5))
        ttk.Button(self.working_frame, text='Go to window 3', command=self.window3).pack(side='top')
        self.working_frame.pack(side='top', pady=(25, 0))

    def window2(self):
        self.working_frame.destroy()
        self.working_frame = ttk.Frame(self, width=300, height=150, relief='groove')
        self.working_frame.pack_propagate(False)
        ttk.Label(self.working_frame, text='This is window 2').pack(side='top', pady=(25, 25))
        ttk.Button(self.working_frame, text='Go to window 1', command=self.window1).pack(side='top', pady=(0, 5))
        ttk.Button(self.working_frame, text='Go to window 3', command=self.window3).pack(side='top')
        self.working_frame.pack(side='top', pady=(25, 0))

    def window3(self):
        self.working_frame.destroy()
        self.working_frame = ttk.Frame(self, width=300, height=150, relief='groove')
        self.working_frame.pack_propagate(False)
        ttk.Label(self.working_frame, text='This is window 3').pack(side='top', pady=(25, 25))
        ttk.Button(self.working_frame, text='Go to window 1', command=self.window1).pack(side='top', pady=(0, 5))
        ttk.Button(self.working_frame, text='Go to window 2', command=self.window2).pack(side='top')
        self.working_frame.pack(side='top', pady=(25, 0))


if __name__ == '__main__':
    App().mainloop()
Mike - SMT
  • 14,784
  • 4
  • 35
  • 79
  • That is a very good answer and something I was looking for Mike. Thank you. As for the reference about the use of classes, I cannot find it anymore unfortunately. Anyway, it was a youtube video and the guy that was talking wasn't saying that you should not use the __init__ method. He was saying, that using classes with only one or two methods is an overkill and should be rewritten as plain code. I am still not sure i am expressing myself correctly though. Anyway, your answer gives me clarity on how to build tkinter apps and that was what I was looking for. – Aivaras Kazakevičius Jan 31 '19 at 09:13
  • 1
    I do not agree with the person who stated it is over use of a class. I would say unless your program is going to be relatively small you should use classes/methods most if not all of the time. It is not overkill IMO. – Mike - SMT Jan 31 '19 at 13:33