I am trying to create a tkinter app of 5 columns and 2 rows, containing a ttk.Treeview
in the top row (spanning all 5 cols). The bottom row should contain a ttk.Frame
in each column. The frames' widths should correspond to the width of the treeview's columns. I have achieved the behavior with a custom empty frame class MyFrameResizable
. However, once I fill the custom frame (MyFrame
) with other widgets, the resizing behavior breaks down.
import tkinter as tk
from tkinter import ttk
class MyFrame(ttk.Frame):
def __init__(self, master = None, **kwargs):
super().__init__(master, **kwargs)
self.columnconfigure(0, weight=0)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
# adding other widgets will destroy resizing behavior
self.button = tk.Button(self)
self.button.grid(column=0, row=0, sticky="nesw")
self.entry = tk.Entry(self)
self.entry.grid(column=1, row=0, sticky="nesw")
class MyFrameResizable(ttk.Frame):
def __init__(self, master = None, **kwargs):
super().__init__(master, **kwargs)
self.columnconfigure(0, weight=0)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
class ResizableApp:
def __init__(self, root):
self.root = root
self.root.title("Resizable Grid Example")
# Treeview with 5 columns
self.treeview = ttk.Treeview(root, columns=("Col1", "Col2", "Col3", "Col4", "Col5"), show="headings")
for i, col in enumerate(("Col1", "Col2", "Col3", "Col4", "Col5"), start=1):
self.treeview.heading(f"# {i}", text=col)
self.treeview.column(f"# {i}", width=100, anchor="center") # Initial column widths
self.treeview.grid(row=0, column=0, columnspan=5, sticky="nsew")
# Create bottom row frames with different colors
self.style = ttk.Style()
self.frames = []
colors = ["red", "green", "blue", "yellow", "cyan"]
for i, color in enumerate(colors):
self.style.configure(style_name:=f"Custom{i}.TFrame", background=color)
# change to MyFrame breaks resizing behavior
frame = MyFrameResizable(root, height=10, style=style_name)
frame.grid(row=1, column=i, sticky="nsew")
self.frames.append(frame)
# Configure grid weights for resizing
root.grid_rowconfigure(0, weight=1) # Treeview row resizes with window
root.grid_rowconfigure(1, weight=1) # Frame row stays fixed
for i in range(5):
root.grid_columnconfigure(i, weight=1)
# Bind resizing events to update frame widths
self.treeview.bind("<Configure>", self.update_frame_widths)
self.treeview.bind("<<TreeviewColumnMoved>>", self.update_frame_widths)
self.treeview.bind("<ButtonRelease-1>", self.update_frame_widths)
def update_frame_widths(self, event=None):
"""Update the widths of frames to match Treeview column widths."""
print("update")
for i, frame in enumerate(self.frames):
col_width = self.treeview.column(f"# {i + 1}", option="width") # Get current column width
frame.config(width=col_width)
# Run the app
if __name__ == "__main__":
root = tk.Tk()
app = ResizableApp(root)
root.mainloop()
I expected the grid layout manager to assign the width to the frames, no matter the content. Once the frame width is determined, I thought the child widgets of the frame will then be gridded according to the available space.
It seems however, that including child widgets overrides the columnar width set via columnconfigure
. Why is that?
For both pack
and grid
, they are designed to grow or shrink to fit the size of their children. That is the behavior you're seeing. This feature is called geometry propagation. You can turn this off by calling self.grid_propagate(False)
in your MyFrame
class.
class MyFrame(ttk.Frame):
def __init__(self, master = None, **kwargs):
super().__init__(master, **kwargs)
self.grid_propagate(False)
...