I'm having trouble laying out two same ttk.Treeview
widgets using ttk.Notebook
and grid
layout manager. When I'm working with only one treeview (placed in a labelframe, that is placed in a frame (section), that is placed in yet another frame (main), that is placed in root), then everything works and I get the desired results:
But as soon as I try putting two such treeviews in a notebook, everything goes haywire, even though I don't change anything in the code, apart from adding a notebook:
Here's the code I prepared according to the SO guidelines to better illustrate the problem (I tried to make it as short as I could while retaining vital characteristics (like the columns layout and scrollbars) of the app it was taken out of) :
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.title("App")
mainframe = ttk.Frame(root)
mainframe.grid()
section = ttk.Frame(mainframe, padding=(0, 0, 10, 60))
section.grid(sticky="nsew")
labelframe = ttk.Labelframe(section, text="Title", padding=(5, 7, 5, 13))
labelframe.grid(sticky="nsew")
# styles
style = ttk.Style()
style.configure(
"App.Treeview",
font=("TkDefaultFont", 8),
rowheight=15
)
style.configure(
"App.Treeview.Heading",
font=("TkDefaultFont", 8)
)
def build_tree(parent):
# treeview
trv = ttk.Treeview(parent)
trv.configure(
style="App.Treeview",
height=19,
columns=("#1", "#2", "#3", "#4"),
displaycolumns="#all",
selectmode="browse"
)
trv.grid(column=0, row=0, sticky="nsew")
# treeview's scrollbars
sbar_vertical = ttk.Scrollbar(parent, orient=tk.VERTICAL, command=trv.yview)
sbar_vertical.grid(column=1, row=0, sticky="ns")
sbar_horizontal = ttk.Scrollbar(parent, orient=tk.HORIZONTAL, command=trv.xview)
sbar_horizontal.grid(column=0, row=1, sticky="we")
trv.configure(yscrollcommand=sbar_vertical.set, xscrollcommand=sbar_horizontal.set)
# treeview's columns
trv.heading("#1", text="Name_1")
trv.heading("#2", text="Pos")
trv.heading("#3", text="NT/R")
trv.heading("#4", text="★")
trv.column("#0", stretch=True, minwidth=70, width=49)
trv.column("#1", stretch=True, minwidth=97, width=49)
trv.column("#2", stretch=True, minwidth=35, width=49)
trv.column("#3", stretch=True, minwidth=40, width=49, anchor=tk.E)
trv.column("#4", stretch=True, minwidth=12, width=49)
return trv
# tree = build_tree(labelframe)
# notebook
notebook = ttk.Notebook(labelframe)
tree1 = build_tree(notebook)
tree2 = build_tree(notebook)
notebook.add(tree1, text="One")
notebook.add(tree2, text="Two")
notebook.grid(sticky="nsew")
root.resizable(False, False)
root.mainloop()
To switch between the first result and the second, one has to only uncomment the
# tree = build_tree(labelframe)
line and comment out the # notebook
section.
I tried giving the notebook height
and weight
option arguments in a constructor but nothing changed and also tried with adding
rowconfigure(0, weight=1)
columnconfigure(0, weight=1)
to a container on each level (mainframe
, section
and labelframe
) and even all down to the notebook and the treeviews (even though AFAIK this should only be necessary if the window was to be resizable) - obtaining exactly the same result.
My understanding was if widgets are placed in a grid
without any options passed to a grid()
call, then a widget placed decides how much space it needs and the grid
manager accommodates a given column's width (or a height in case of a row) to the highest value requested by the widget (of all managed in a given column or row). Then if such decided space is too big for some other widget that uses the same row/column, then it gets stretched (or no - according mainly to the sticky
option). If that understanding is correct, then I don't quite understand the notebook's behavior, especially as the alone treeview seems to perfectly adhere to it.
My environment: Ubuntu 17.10, Python 3.6.3, Tkinter 8.6. In case the treeview columns' setup looks somewhat peculiar - it was derived from a comment to this question and has to be like that to make use of the horizontal scrollbar.
The problem is that you are trying to add the scrollbars and treeviews to the notebook with grid
, and then also adding them with the add
method of the notebook. You cannot mix the two. When you create a notebook, you should not directly add widgets to it via pack
, place
, or grid
.
Instead, each notebook tab should be a single frame. You can then put any other widgets inside that frame with pack
, place
, or grid
. You then add this frame to the notebook with add
.
So, your first step is to modify build_tree
to create and return a frame:
def build_tree(parent):
frame = ttk.Frame(frame)
...
return frame
Next, you need to make sure that the tree and scrollbars are children of this frame rather than children of the notebook:
trv = ttk.Treeview(frame)
sbar_vertical = ttk.Scrollbar(frame, ...)
sbar_horizontal = ttk.Scrollbar(frame, ...)
As a rule of thumb, whenever you use grid
you should always give at least one row and one column a non-zero weight so that they grow and shrink to fill any extra space. So, make sure that the weights of the rows and columns of this frame are set.
For example:
def build_tree(parent):
...
frame.grid_rowconfigure(0, weight=1)
frame.grid_columnconfigure(0, weight=1)
...
That will now make the notebook look and behave properly with respect to the notebook.
You similar row/column weight issues with all of the other frames, so you'll want to apply the weights to every widget which has children widgets managed by grid
.