I have a treeview in a frame. The tree is managed by grid geometry manager. On button press I want to:
The tree should stick to each end of its grid and the columns should stretch to both sides of the tree. I attempt to achieve this by remapping tree with new columnspan, deleting children of tree, and adding the new columns. See toggle_span().
import tkinter as tk
from tkinter import ttk
def setup_tree():
columns = ["col1"]
tree['columns'] = columns
tree.column('#0', width=20, minwidth=20, stretch=tk.YES)
tree.heading('#0', text='id')
for i, column in enumerate(columns):
tree.column(i, width=20, minwidth=20, stretch=tk.YES)
tree.heading(i, text=column)
tree.insert('', 'end', iid='1', text='1', values=("item1",))
tree.insert('', 'end', iid='2', text='2', values=("item2",))
# Toggle treeview
def toggle_span():
global span
# Extend tree
if span == 1:
span = 2
columns = ["col1", "col2"]
row1 = ("item1", "item2")
row2 = ("item3", "item4")
# Retract tree
else:
span = 1
columns = ["col1"]
row1 = ("item1",)
row2 = ("item2",)
# Refresh columnspan of old tree
tree.grid_forget()
tree.grid(column=1, row=0, sticky="nsew", columnspan=span)
# Clear tree
for item in tree.get_children():
tree.delete(item)
# Rebuild tree
tree['columns'] = columns
tree.column('#0', width=20, minwidth=20, stretch=tk.YES)
tree.heading('#0', text='id')
for i, column in enumerate(columns):
tree.column(i, width=20, minwidth=20, stretch=tk.YES)
tree.heading(i, text=column)
tree.insert('', 'end', iid='1', text='1', values=row1)
tree.insert('', 'end', iid='2', text='2', values=row2)
span = 1
# Root
root = tk.Tk()
root.state("zoomed")
# Frame
frame = tk.Frame(root, bg="cyan")
frame.pack(fill='both', expand=True)
frame.grid_rowconfigure(0, weight=1)
for i in range(3):
frame.grid_columnconfigure(i, weight=1)
# Button
button = tk.Button(frame, text="Toggle Span", command=toggle_span)
button.grid(column=0, row=0, sticky="nsew")
# Tree
tree = ttk.Treeview(frame)
setup_tree()
tree.grid(column=1, row=0, sticky="nsew", columnspan=span)
# Manual toggle works as expected!
toggle_span()
toggle_span()
root.mainloop()
When toggle_span() is called manually, columns are stretched as expected. Howewer when toggle_span() is called by button press, only the initial retracted state and the extended state immediately following that is shown correctly. After that, the columns do not stretch initially. They do stretch though, when the edge of a column is clicked. Column states
How do I make sure the columns are always stretched?
Call grid_forget after modifying the first treeview column (but before the rest).
...
tree.column('#0', width=20, minwidth=20, stretch=tk.YES)
tree.heading('#0', text='id')
tree.grid_forget()
tree.grid(column=1, row=0, sticky="nsew", columnspan=span)
for i, column in enumerate(columns):
tree.column(i, width=20, minwidth=20, stretch=tk.YES)
tree.heading(i, text=column)
...
After this the columns will stretch as expected. I don't know why it works this way.
Its hardly visible with this few columns, but the button and the treeview are not always the same width, they wobble a bit. This can be solved by configuring the frame columns to be uniform.
frame.grid_rowconfigure(0, weight=1)
for i in range(3):
frame.grid_columnconfigure(i, weight=1, uniform="colgroup")
Create a distinct frame for the treeview to fit in. The tree can be resized by resizing this frame. With this, the columns stretch as expected.
The inner frame's columnspan can be configured directly with grid_configure(columnspan=...) instead of grid_forget() and grid().
However, now the inner frame changes based on the treeview, causing even wilder "wobbling". To stop this, we can call grid_propagate(False) on the inner frame.
The code for the question's example:
import tkinter as tk
from tkinter import ttk
def setup_tree():
columns = ["col1"]
tree['columns'] = columns
tree.column('#0', width=20, minwidth=20, stretch=tk.YES)
tree.heading('#0', text='id')
for i, column in enumerate(columns):
tree.column(i, width=20, minwidth=20, stretch=tk.YES)
tree.heading(i, text=column)
tree.insert('', 'end', iid='1', text='1', values=("item1",))
tree.insert('', 'end', iid='2', text='2', values=("item2",))
# Toggle treeview
def toggle_span():
global span
# Extend tree
if span == 1:
span = 2
columns = ["col1", "col2"]
row1 = ("item1", "item2")
row2 = ("item3", "item4")
# Retract tree
else:
span = 1
columns = ["col1"]
row1 = ("item1",)
row2 = ("item2",)
# Clear tree
for item in tree.get_children():
tree.delete(item)
# Rebuild tree
tree['columns'] = columns
tree.column('#0', width=20, minwidth=20, stretch=tk.YES)
tree.heading('#0', text='id')
for i, column in enumerate(columns):
tree.column(i, width=20, minwidth=20, stretch=tk.YES)
tree.heading(i, text=column)
tree.insert('', 'end', iid='1', text='1', values=row1)
tree.insert('', 'end', iid='2', text='2', values=row2)
tree_frame.grid_configure(columnspan=span)
span = 1
# Root
root = tk.Tk()
root.state("zoomed")
# Frame
frame = tk.Frame(root, bg="cyan")
frame.pack(fill='both', expand=True)
frame.grid_rowconfigure(0, weight=1)
for i in range(3):
frame.grid_columnconfigure(i, weight=1)
# Tree frame
tree_frame = tk.Frame(frame)
tree_frame.grid(row=0, column=1, sticky="nsew", columnspan=span)
tree_frame.grid_propagate(False)
tree_frame.grid_rowconfigure(0, weight=1)
tree_frame.grid_columnconfigure(0, weight=1)
# Button
button = tk.Button(frame, text="Toggle Span", command=toggle_span)
button.grid(column=0, row=0, sticky="nsew")
# Tree
tree = ttk.Treeview(tree_frame)
setup_tree()
tree.grid(row=0, column=0, sticky="nsew")
root.mainloop()
I don't quite understand what is happening, but here is what I see.
The columns stretch nicely because the treeview is toggled before the mainloop. They still shrink during the mainloop, even if the toggle is not called by button press:
# Produces unexpected state
def toggle():
print("toggling")
toggle_span()
root.after(2000, toggle)
root.after(2000, toggle)
root.mainloop()