Search code examples
python-3.xtkintertkinter-layout

How can I align elements across different frames in tkinter?


I am reorganizing the structure of a GUI, and I am having some alignment issues. I have a LabelFrame on the left containing a matplotlib canvas, and I have a Notebook on the right containing several LabelFrames in tabs, each containing another matplotlib canvas. I want to visually align the internal LabelFrames in the Notebook to the leftmost LabelFrame, so that in the image below, the words "Left Frame" and the lower "Tab 1" are vertically aligned. How can that be done? A (mostly) minimal working example is included below.

enter image description here

import tkinter as tk
from tkinter import ttk
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk

class App(tk.Frame):
    def __init__(self,parent):
        tk.Frame.__init__(self, parent)
        parent.deiconify()
        self.parent = parent

        #define and position the main panel on the left
        self.left_frame = tk.LabelFrame(self.parent, text='Left Panel')
        self.left_frame.grid(row=0,column=0)

        #define and position the tab-enabled secondary panel on the right
        self.ntbk = ttk.Notebook(self.parent)
        self.ntbk.grid(row=0,column=1)

        #define internal frames for the tabs
        self.tab1_frame = tk.LabelFrame(self.ntbk, text='Tab 1')
        self.tab2_frame = tk.LabelFrame(self.ntbk, text='Tab 2')

        self.ntbk.add(self.tab1_frame, text='Tab 1')
        self.ntbk.add(self.tab2_frame, text='Tab 2')


        #Left panel children
        #define a figure canvas to go in the left panel
        self.left_f = Figure(figsize=(7,5), dpi=100)
        self.left_canvas = FigureCanvasTkAgg(self.left_f, master=self.left_frame)
        self.left_toolbar_frame = tk.Frame(self.left_frame)
        self.left_toolbar = NavigationToolbar2Tk(self.left_canvas, self.left_toolbar_frame)
        self.left_toolbar.update()
        self.left_canvas.get_tk_widget().grid(row=0,column=0)
        self.left_toolbar_frame.grid(row=1,column=0)

        #define a control panel for the left panel to go below the figure
        self.left_control_frame = tk.LabelFrame(self.left_frame, text='Left Control Panel')
        self.left_control_frame.grid(row=2,column=0, sticky=tk.E+tk.W)

        self.left_button = tk.Button(self.left_control_frame, text='Button')
        self.left_button.grid(row=0,column=0, sticky=tk.E+tk.W)


        #notebook children
        #define a figure canvas to go in tab 1
        self.tab1_f = Figure(figsize=(7,5), dpi=100)
        self.tab1_canvas = FigureCanvasTkAgg(self.tab1_f, master=self.tab1_frame)
        self.tab1_toolbar_frame = tk.Frame(self.tab1_frame)
        self.tab1_toolbar = NavigationToolbar2Tk(self.tab1_canvas, self.tab1_toolbar_frame)
        self.tab1_toolbar.update()
        self.tab1_canvas.get_tk_widget().grid(row=0,column=0)
        self.tab1_toolbar_frame.grid(row=1,column=0)

        self.tab1_control_frame = tk.LabelFrame(self.tab1_frame, text='Tab 1 Control Panel')
        self.tab1_control_frame.grid(row=2,column=0, sticky=tk.E+tk.W)

        self.tab1_button = tk.Button(self.tab1_control_frame, text='Button')
        self.tab1_button.grid(row=0,column=0, sticky=tk.E+tk.W)

        #define a figure canvas to go in tab 2
        self.tab2_f = Figure(figsize=(7,5), dpi=100)
        self.tab2_canvas = FigureCanvasTkAgg(self.tab2_f, master=self.tab2_frame)
        self.tab2_toolbar_frame = tk.Frame(self.tab2_frame)
        self.tab2_toolbar = NavigationToolbar2Tk(self.tab2_canvas, self.tab2_toolbar_frame)
        self.tab2_toolbar.update()
        self.tab2_canvas.get_tk_widget().grid(row=0,column=0)
        self.tab2_toolbar_frame.grid(row=1,column=0)

        self.tab2_control_frame = tk.LabelFrame(self.tab2_frame, text='Tab 2 Control Panel')
        self.tab2_control_frame.grid(row=2,column=0, sticky=tk.E+tk.W)

        self.tab2_button = tk.Button(self.tab2_control_frame, text='Button')
        self.tab2_button.grid(row=0,column=0, sticky=tk.E+tk.W)



def main():
    root=tk.Tk()
    root.withdraw()
    App(root).grid(row=0,column=0)
    root.mainloop()

if __name__=="__main__":
    main()

Solution

  • If only want it to visually align,Just add some paddings in your code.

    In your LeftFrame .grid(),add pady=xx and see the appearance manually.

    self.left_frame.grid(row=0,column=0,pady=(22,0)) # This will be bad when you use DPI awareness.
    

    enter image description here

    PS:I try to use self.ntbk.winfo_height()-self.tab1_frame.winfo_height() to get the height of tab,but it actually not accurate.(It get 28,but it looked not very nice.)

    Edit:I find a way to directly set the y of Tab1,Now your code maybe can be:

    import tkinter as tk
    from tkinter import ttk
    import matplotlib
    matplotlib.use('TkAgg')
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
    # import ctypes,sys 
    #
    # if sys.getwindowsversion().major == 10:
    #     ctypes.windll.shcore.SetProcessDpiAwareness(2) # DPI AWARENESS
    
    
    class App(tk.Frame):
        def getTabHeight(self):
            self.left_frame.grid(row=0,column=0,pady=(self.tab1_frame.winfo_y()-2,0))  # Get its height,I don't know why it need to minus 2,But in this way,no matter you use DPI awareness,It looked good.
    
        def __init__(self,parent):
            tk.Frame.__init__(self, parent)
            parent.deiconify()
            self.parent = parent
    
            #define and position the main panel on the left
            self.left_frame = tk.LabelFrame(self.parent, text='Left Panel')
    
            #define and position the tab-enabled secondary panel on the right
            self.ntbk = ttk.Notebook(self.parent)
            self.ntbk.grid(row=0,column=1)
    
            #define internal frames for the tabs
            self.tab1_frame = tk.LabelFrame(self.ntbk, text='Tab 1')
            self.tab2_frame = tk.LabelFrame(self.ntbk, text='Tab 2')
    
            self.ntbk.add(self.tab1_frame, text='Tab 1')
            self.ntbk.add(self.tab2_frame, text='Tab 2')
    
    
            #Left panel children
            #define a figure canvas to go in the left panel
            self.left_f = Figure(figsize=(7,5), dpi=100)
            self.left_canvas = FigureCanvasTkAgg(self.left_f, master=self.left_frame)
            self.left_toolbar_frame = tk.Frame(self.left_frame)
            self.left_toolbar = NavigationToolbar2Tk(self.left_canvas, self.left_toolbar_frame)
            self.left_toolbar.update()
            self.left_canvas.get_tk_widget().grid(row=0,column=0)
            self.left_toolbar_frame.grid(row=1,column=0)
    
            #define a control panel for the left panel to go below the figure
            self.left_control_frame = tk.LabelFrame(self.left_frame, text='Left Control Panel')
            self.left_control_frame.grid(row=2,column=0, sticky=tk.E+tk.W)
    
            self.left_button = tk.Button(self.left_control_frame, text='Button')
            self.left_button.grid(row=0,column=0, sticky=tk.E+tk.W)
    
    
            #notebook children
            #define a figure canvas to go in tab 1
            self.tab1_f = Figure(figsize=(7,5), dpi=100)
            self.tab1_canvas = FigureCanvasTkAgg(self.tab1_f, master=self.tab1_frame)
            self.tab1_toolbar_frame = tk.Frame(self.tab1_frame)
            self.tab1_toolbar = NavigationToolbar2Tk(self.tab1_canvas, self.tab1_toolbar_frame)
            self.tab1_toolbar.update()
            self.tab1_canvas.get_tk_widget().grid(row=0,column=0)
            self.tab1_toolbar_frame.grid(row=1,column=0)
    
            self.tab1_control_frame = tk.LabelFrame(self.tab1_frame, text='Tab 1 Control Panel')
            self.tab1_control_frame.grid(row=2,column=0, sticky=tk.E+tk.W)
    
            self.tab1_button = tk.Button(self.tab1_control_frame, text='Button')
            self.tab1_button.grid(row=0,column=0, sticky=tk.E+tk.W)
    
            #define a figure canvas to go in tab 2
            self.tab2_f = Figure(figsize=(7,5), dpi=100)
            self.tab2_canvas = FigureCanvasTkAgg(self.tab2_f, master=self.tab2_frame)
            self.tab2_toolbar_frame = tk.Frame(self.tab2_frame)
            self.tab2_toolbar = NavigationToolbar2Tk(self.tab2_canvas, self.tab2_toolbar_frame)
            self.tab2_toolbar.update()
            self.tab2_canvas.get_tk_widget().grid(row=0,column=0)
            self.tab2_toolbar_frame.grid(row=1,column=0)
    
            self.tab2_control_frame = tk.LabelFrame(self.tab2_frame, text='Tab 2 Control Panel')
            self.tab2_control_frame.grid(row=2,column=0, sticky=tk.E+tk.W)
    
            self.tab2_button = tk.Button(self.tab2_control_frame, text='Button')
            self.tab2_button.grid(row=0,column=0, sticky=tk.E+tk.W)
    
            self.after(100,self.getTabHeight)
    
    
    
    def main():
        root=tk.Tk()
        root.withdraw()
        App(root).grid(row=0,column=0)
        root.mainloop()
    
    if __name__=="__main__":
        main()
    

    In DPI awareness: enter image description here