5
\$\begingroup\$

This is code that produces a checkered flag-like GUI. I wanted to demonstrate, also learn in detail, how tkinter's geometry managers can be used modularly, so I decided that I could write code for a checkered flag that has frames inside frames, that which have their geometries managed independent of each other.

The code produces the following:

  1. The main window(root) has a frame that has 2 vertical frames as partitions (class TwoFrames).
  2. Each one of those frames has 2 horizontal labels as partitions (class TwoLabels).

I wanted to show that you can manage a frame's geometry which manages its own widgets' geometry.

import tkinter as tk

#a class that has 2 columns of frames inside
class TwoFrames(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)

        #creates 2 frame objects and passes self as parent, which means object created using TwoFrames class is the parent
        self.frame1 = tk.Frame(self)
        self.frame2 = tk.Frame(self)

        #manages 2 frames geometry
        self.frame1.grid(row=0, column=0, sticky="nsew")
        self.frame2.grid(row=0, column=1, sticky="nsew")

        #enables resizing for 0th row, and 1st and 2nd columns of an object of this class
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=1)

class TwoLabels(tk.Frame):
    def __init__(self, master=None, color=True):
        super().__init__(master)

        #creates 2 Label objects with TwoLabels object as parent
        self.label1 = tk.Label(self)
        self.label2 = tk.Label(self)

        #configures the background color of labels for demonstrative purposes
        if color:
            #label1 will have black color
            self.label1.configure(bg="black")
            #label2 will have white color
            self.label2.configure(bg="white")
        else:
            #label1 will have white color
            self.label1.configure(bg="white")
            #label2 will have black color
            self.label2.configure(bg="black")


        #manages the geometry
        self.label1.grid(column=0, row=0, sticky="nsew")
        self.label2.grid(column=0, row=1, sticky="nsew")

        #enables resizing like above, but this time for 2 rows and 1 column
        self.rowconfigure(0, weight=1)
        self.rowconfigure(1, weight=1)
        self.columnconfigure(0, weight=1)

#creates the mainWindow
mainWindow = tk.Tk()

#creates a mainFrame that has 2 frames in it
mainFrame = TwoFrames(mainWindow)

#manages geometry of mainFrame and display it
mainFrame.pack(fill="both", expand=True)

#creates row_labels1 and row_labels2, both has 2 colored labels in them
row_labels1 = TwoLabels(mainFrame.frame1, True)
row_labels2 = TwoLabels(mainFrame.frame2, False)

#manages geometry of labels inside frames and displays them
row_labels1.pack(fill="both", expand=True)
row_labels2.pack(fill="both", expand=True)

#run the application
mainWindow.mainloop()

Review Concerns:

  • My main concern is to have this code well written in the structure of object oriented programming. Tiniest of issue is important to me.
  • I'd also like to know in what ways it can be more concise for better understanding tkinter's geometry managers.
\$\endgroup\$
3
\$\begingroup\$
  1. OOP is not merely creating classes: it is also about a thoughtful design. This becomes even more true when it comes to tkiner. I mean you created 2 separate classes to refer to tk.Label() and tk.Frame() classes. This is not the best option for your context as that design is misleading in that it makes the reader of your code think you are trying to create custom Label() and Frame() widgets (which is not what the application actually does).

  2. Also, from the OOP perspective, the lines of code spanning from mainWindow = tk.Tk() to the end do not comply with the OOP philosophy (they are mere sequential instructions not even wrapped within a function)

  3. You say the goal of your application is to play with the tkinter geometry managers. Actually there are 3 of them: place(), pack() and grid(). You are using only one of them. The ideal is to use the 3 of them, but in my solution below I will use only 2 of them as the context is not suitable for a smart use of place().

  4. You can use pack() for the frames and grid() to manage the labels.

  5. It is better that you attach your widgets to a parent widget instead of the instance of the class you use. Example: instead of coding something like self.frame1 = tk.Frame(self), it would be more appropriate to write self.frame1 = tk.Frame(self.some_more_specific_parent)

  6. There is an easier way to play with the flag (color) you are using as a background color for the label widgets (see my solution below)

  7. It is hard, when launching your application, to see where is the main window and stretch it to make it visible. So for the sake of a better user experience, you should set default dimension to it and keep it resizable (see my solution below)

  8. No need to use the pack() geometry manager the way you did.

Based on the elements stated above, and based on some of Tkinter best practices, I would like to suggest you the solution below which requires, logically, only one class, and refactors your code in a cleaner way:

import tkinter as tk


class SimpleGeometryManagersDemo(tk.Frame):

    def __init__(self, master):
        self.master = master
        super().__init__(self.master)
        self.configure_gui()
        self.create_widgets()

    def configure_gui(self):
        self.master.title('Simple Geometry Managers Demo')
        self.master.geometry('400x200')

    def create_widgets(self):
        self.create_two_frames()
        self.create_two_labels_per_frame()

    def create_two_frames(self):
        self.frame1 = tk.Frame(self.master)
        self.frame2 = tk.Frame(self.master)        
        self.frame1.pack(fill=tk.BOTH, expand=1)
        self.frame2.pack(fill=tk.BOTH, expand=1)        

    def create_two_labels_per_frame(self):
        self.create_two_labels(self.frame1, 'black')
        self.configure_columns_and_rows(self.frame1)
        self.create_two_labels(self.frame2, 'white')
        self.configure_columns_and_rows(self.frame2)

    def create_two_labels(self, parent, color):
        self.label1 = tk.Label(parent, bg=color) 
        bg_color = 'white' if color == 'black' else 'black'
        self.label2 = tk.Label(parent, bg=bg_color)        
        self.label1.grid(row=0, column=0, sticky='nswe')
        self.label2.grid(row=0, column=1, sticky='nswe')

    def configure_columns_and_rows(self, parent):
        parent.rowconfigure(0, weight=1)
        parent.columnconfigure(0, weight=1)
        parent.columnconfigure(1, weight=1)

def main():
   root = tk.Tk()
   geometry_managers_demo = SimpleGeometryManagersDemo(root)
   root.mainloop()


if __name__ == '__main__':
   main()   
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.