Search code examples
pythonuser-interfacetkinter

Independently set Label width of Frames in multi-frame tkinter App


I want to create a GUI, that has two labels at the bottom of the GUI for status (Status 0 and Status 1) and the the upper part of the GUI, that has all the GUI stuff of the main application.

Basically the GUI layout should look like this:

┏━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━┓
┃ @ ┃ hello world                ┃ ╳ ┃
┣━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━┫
┃                                    ┃   <- Frame 0
┃                MAIN                ┃   <- Frame 0
┃                                    ┃   <- Frame 0
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃                OTHER               ┃   <- Frame 1
┣━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┫   <- Frame 1
┃ Status 0            ┃ Status 1     ┃   <- Frame 1
┗━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━┛

However, my code produces something like this

example

or even worse, on resize something like this

resized example

My question therefore is, how can I set the Status 0 and Status 1 widths independently to something like

  • Status 0 shall be 2/3 of the current window width
  • Status 1 shall be 1/3 of the current window width
import tkinter as tk

class MyApp(tk.Tk):
    def __init__(self):
        super().__init__()

        self.frame0 = tk.Frame(self, background="green")
        self.title('hello world')
        self.frame0.pack(fill=tk.X)
        self.frame0_label0 = tk.Label(self.frame0, text='MAIN', border=10)
        self.frame0_label0.grid(row=0, column=0)

        self.frame1 = tk.Frame(self, background="blue")
        self.frame1.pack(fill=tk.X)
        self.frame1_label0 = tk.Label(self.frame1, text='OTHER', border=10)
        self.frame1_label0.grid(row=0, column=0)
        self.var0 = tk.StringVar()
        self.var0.set("S0")
        self.var1 = tk.StringVar()
        self.var1.set("S1")
        sbar0 = tk.Label(self.frame1, textvariable=self.var0, relief=tk.SUNKEN, anchor="w")
        sbar1 = tk.Label(self.frame1, textvariable=self.var1, relief=tk.SUNKEN, anchor="w")
        sbar0.grid(row=1, column=0)
        sbar1.grid(row=1, column=1)

def main():
    MyApp().mainloop()

if __name__ == "__main__":
    main()

Solution

  • In my opinion, the simplest solution is to put the status labels in a separate frame using grid. With that, you can control their layout more easily without affecting any other part of the UI.

    So, start by adding the statusbar frame and make sure it is below the other frames.

    self.sbar = tk.Frame(self)
    self.sbar.pack(side="bottom", fill="x")
    

    Next, place the labels in this frame and configure appropriately:

    sbar0 = tk.Label(self.sbar, textvariable=self.var0, relief=tk.SUNKEN, anchor="w")
    sbar1 = tk.Label(self.sbar, textvariable=self.var1, relief=tk.SUNKEN, anchor="w")
    
    self.sbar.grid_columnconfigure(0, weight=2)
    self.sbar.grid_columnconfigure(1, weight=1)
    sbar0.grid(row=1, column=0, sticky="ew")
    sbar1.grid(row=1, column=1, sticky="ew")
    

    You'll also want to make sure that your "main" frame is properly packed to fill the majority of the window:

    self.frame0.pack(fill=tk.BOTH, expand=True)
    

    screenshot

    Personally I find layout problems much easier to solve when you group all of the layout code together for children of a widget, instead of interspersing it with the code that creates the widget.

    For example, this makes the outermost layout much easier to visualize in the code.

    self.frame0 = tk.Frame(self, background="green")
    self.frame1 = tk.Frame(self, background="blue")
    self.sbar = tk.Frame(self)
    
    self.frame0.pack(side="top", fill="both", expand=True)
    self.frame1.pack(side="top", fill="x")
    self.sbar.pack(side="bottom", fill="x")