1

I'm trying to write a simple tkinter app and would like to follow OOP best practices glimpsed from here: Best way to structure a tkinter application?

and here: https://www.begueradj.com/tkinter-best-practices/

and here:

The app should be a simple game for children to learn the alphabet, showing one big letter and then three buttons with animal pictures to chose from.

Now I have started with this code:

import tkinter as tk

class MainApplication(tk.Frame):
    def __init__(self, master):
        self.master = master
        tk.Frame.__init__(self, self.master)  # or super().__init__(self.master)? 

        self.configure_gui()
        self.create_widgets()

    def configure_gui(self):
        self.master.title('Example')
        self.master.geometry('500x500')
        self.master.minsize(100, 100)
        self.master.columnconfigure([0, 1], minsize=50, weight=1)
        self.master.rowconfigure([0, 1], minsize=50, weight=1)
        # self.master.resizable(False, False)

    def create_widgets(self):
        # Frame that will contain the big letter
        self.letterbox = tk.Frame(self.master, bg='red', width=200, height=200)
        self.letterbox.grid(row=0, column=0, sticky='nsew')
        ...

        # Frame that contains the 3 animal pictures
        self.animalbox = tk.Frame(self.master, bg='yellow')
        self.animalbox.rowconfigure(0, weight=1)
        self.animalbox.columnconfigure([0, 1, 2], weight=1)
        self.animalbox.grid(row=1, column=0, sticky='nsew')
        self.animalbox_left = tk.Button(self.animalbox, text='left animal')
        self.animalbox_left.grid(row=0, column=0, padx=10, pady=10, ipady=10)
        self.animalbox_middle = tk.Button(self.animalbox, text='middle animal')
        self.animalbox_middle.grid(row=0, column=1, padx=10, pady=10, ipady=10)
        self.animalbox_right = tk.Button(self.animalbox, text='right animal')
        self.animalbox_right.grid(row=0, column=2, padx=10, pady=10, ipady=10)


        # Frame that contains the score and options
        self.scorebox = tk.Frame(self.master, bg='blue')
        self.scorebox.grid(row=0, column=1, rowspan=2, sticky='nsew')

def main():
    root = tk.Tk()
    MainApplication(root)
    root.mainloop()

if __name__ == '__main__':
    main()

I thought about splitting the questions but since they all adress the same short piece of code and are maybe rather basic, I chose to keep them in one post.

Questions:

  1. Why does it matter, whether I pass two arguments to tk.Frame.init(self, self.master) but only one to argument (self.master) to the super().__init__self(self.master)? If I pass two arguments to super()... it doesn't work.

Edit: I've found the answer here: What does 'super' do in Python? - difference between super().__init__() and explicit superclass __init__() In the end it's just an implementation detail of super, if I understand correctly.

  1. In the code examples from Brian Oakley and for example here https://www.pythontutorial.net/tkinter/tkinter-object-oriented-frame/ only "self" is passed as master to the widget, while my code needs "self.master" to work. Why?

Edit2: In the meantime I have found out, that if you initialize the MainApplication inheriting from tk.Frame, then it's possible to use "self.pack() in the MainApplication init-function:

class MainApplication(tk.Frame):
    def __init__(self, master):
        self.master = master
        tk.Frame.__init__(self, self.master)
        self.pack()

This will pack the just initialized frame into the tk.Tk() root window and then allows other widgets to pack themselves into this frame, thus allowing to reference them like:

self.letterbox = tk.Frame(self, bg='red', width=200, height=200)

...instead of:

self.letterbox = tk.Frame(self.master, bg='red', width=200, height=200)

...which directplay packs the widget into the root window.

  1. Does it matter, whether I assign MainApplication(root) to a variable or not in the main() function definition?

    def main(): root = tk.Tk() MainApplication(root) root.mainloop() Edit3: I don't think it matters.

Thanks a lot for your insight. This is my first question on stackoverflow and I tried to make it nice. Best regards!

Ranudar
  • 35
  • 7
  • I wanted to [answer](https://stackoverflow.com/a/74058017/13629335) you, but it ended up in a different question. I feel like I started from the wrong point, but it may helps anyway. – Thingamabobs Oct 13 '22 at 15:24
  • If you can confirm my findings, or have other insight, I'd appreciate your answer, upvote or comment greatly (and would also accept it, for what it's worth). Best regards – Ranudar Oct 14 '22 at 05:25

1 Answers1

2

Why does it matter, whether I pass two arguments to tk.Frame.init(self, self.master) but only one to argument (self.master) to the super().__init__self(self.master)? If I pass two arguments to super()... it doesn't work.

In fact it doesn't really matter if you are using super() or calling the parent or sibling directly in your example, in another environment it could. It's more than an implementation detail that should be said. See Reymond Hettinger's answer the author of super() considered super! for a quick overview. In python 3 there was a change to new super, syntactic sugar that let you forget about that self parameter.

I think, I have explained self well here. To boiler it down, self is the pointer to the entity of you identified type. To make another example, you can refer to a dog as an undefined individual but a defined type "normally a dog has 4 legs" and you can refer to doggo the dog has 4 legs.

class Dog:
    @classmethod
    def has_four_legs(cls):
        return True
print(Dog.has_four_legs())
doggo = Dog()
print(doggo.has_four_legs())

To come to tkinter, tkinter is conceptual hierarchically and everything ends up in the root, the instance of tkinter.Tk. The master parameter, the only positional argument and the first one you SHOULD define, is the default value for in_ in the chosen geometry method. So every constructor (e.g. tk.Label(master,..) except for the root window MUST have a master, this becomes crucial when it comes to scrollable frames, but that is a different story.

Enough context so far. You other two question can be answered together. Look at your code here:

def main():
    root = tk.Tk()
    MainApplication(root)
    root.mainloop()

if __name__ == '__main__':
    main()

You should bind your instances/entities to a variable. Because if you won't address your instance, it does usually not make any sense to initialize it in the first place. For you, you need root as the master parameter, that is why you have it bound to a variable. You should do the same for MainApplication. mainframe = MainApplication(root) then you can do mainframe.pack(). Using self.pack() is effectively the same, but it is a bad practice and you will notice that if your layout becomes more complex and you are searching through all your classes to find the methods that messing up your layout.

So I hope all questions are answered with enough context and further reading. Happy coding !

Thingamabobs
  • 7,274
  • 5
  • 21
  • 54
  • 1
    Also, you could be interested in [How do I organize my tkinter App?](https://stackoverflow.com/questions/63536505/how-do-i-organize-my-tkinter-appllication/63536506#63536506) as you seem a beginner and this answer could guide you to some point. – Thingamabobs Oct 14 '22 at 07:32
  • Thank you. I'll (re)read through the links. I'm starting to get a grasp on things but it can sometimes be difficult, when otherwise great answers leave out parts in theit code so that it is not clear for example where the main container is packed. Would you mind upvoting my question, so that I can better participate here on SO? – Ranudar Oct 14 '22 at 21:12
  • 1
    @Ranudar the problem is there is a lot to learn and [so] is basically an addition to tutorials and the documentation. It's more for things that aren't clear, though, I have learned here a lot. Pointer to other sources or techniques, or how different approaches fit into a different context. – Thingamabobs Oct 14 '22 at 21:20