在Tkinter的 Treeview中检测列宽调整
我尝试在树形视图上,通过在画布(canvas)中使用createwindow来添加网格线,到目前为止,在Linux和 Windows上都取得了成功。
问题在于,当列宽被改变时,代码无法检测到:
抱歉这个最小示例这么大。我把它完整保留下来,作为希望在树形视图中添加网格线的人的示例。
# Grid lines using horizontal separators
import tkinter as tk
import tkinter.ttk as ttk
HDR_TAG = 0; HDR_NAME = 1; HDR_WIDTH = 2 ; HDR_ANCHOR = 3
class TreeviewTest(ttk.Frame):
def __init__(self, parent, rows_visible):
ttk.Frame.__init__(self, parent)
self.parent = parent
self.initCanvas()
self.initUI(rows_visible)
self.initOverlay(rows_visible)
def createBranch(self, tree, parent, family):
''' Create tree from dictionary '''
for child, nextgen in family.items():
chval = (nextgen[0], nextgen[1])
tree.insert(parent, "end", child, text=child, values=chval)
if len(nextgen) > 2:
self.createBranch(tree, child, nextgen[2])
def initCanvas(self):
''' Set up the canvas. Set bg to yellow so we can see what we have not drawn on'''
self.canvas = tk.Canvas(self.parent, bg='yellow')
self.canvas.grid(row=0, column=0, sticky="news")
def initUI(self, rows_visible):
''' Populate the tree '''
# Column widths and headings
self.headers = [
('#0', 'Name', 100, tk.W),
('upper', 'Upper', 60, tk.E),
('lower', 'Lower', 60, tk.CENTER)
]
col_list = []
for col in self.headers[1:]:
col_list.append(col[HDR_TAG])
# Set up the Treeview
# The header for the first column is not counted
tree = ttk.Treeview(self.parent, columns=col_list, height=rows_visible)
treesb = ttk.Scrollbar(self.parent, orient=tk.VERTICAL, command=tree.yview)
tree.configure(yscrollcommand=treesb.set)
for col in self.headers:
tree.column(col[HDR_TAG], width=col[HDR_WIDTH], anchor=col[HDR_ANCHOR])
tree.heading(col[HDR_TAG], text=col[HDR_NAME])
# The data
# Simple tree
numbers = "numbers"
tree.insert("", "end", numbers, text="Numbers", tags=("numbertag",))
# Nothing in the name column
tree.insert(numbers, "end", "one", values=("One", "1"))
tree.insert(numbers, "end", "two", values=("Two", "2"))
tree.insert(numbers, "end", "three", values=("Three", "3"))
# Another simple tree
letters = "letters";
tree.insert("", "end", letters, text="Letters", tags=("lettertag",))
# text= appears in the name column
tree.insert(letters, "end", text="Alpha", values=('A', 'a'))
tree.insert(letters, "end", text="Beta", values=("B", 'b'))
tree.insert(letters, "end", text="Gamma", values=("G", 'g'))
# Tree created from dictionary
familytree = {
"GGF": ("1", "1",
{
"GF1": ("11", "11",
{
"F11": ("111", "111"),
"F12": ("112", "112"),
"F13": ("113", "113")
}),
"GF2": ("12", "12"),
"GF3": ("30", "30",
{
"F31": ("131", "131"),
"F32": ("132", "132",
{
"S321": ("1321", "1321")
}),
"F33": ("133", "133")
})
})
}
self.createBranch(tree, "", familytree)
# Set the colours
tree.tag_configure('numbertag', background='green')
tree.tag_configure('lettertag', foreground='yellow', background='red')
#tree.tag_bind('ttk', '<1>', self.clicked)
self.tree = tree
self.treesb = treesb
# Now force all the children to open
for child in self.tree.get_children():
self.tree_open(child)
# Position the widgets
self.canvas.create_window((0,0), window=self.tree, anchor='nw')
self.parent.update()
wid = self.tree.winfo_width()
hgt = self.tree.winfo_height()
print(f'Set posn wid:{wid} hgt:{hgt}')
self.canvas.create_window((wid,0), height=hgt, window=self.treesb, anchor='nw')
def initOverlay(self, rows):
''' Add the gridlines '''
self.parent.update()
wid = self.tree.winfo_width()
hgt = self.tree.winfo_height()
# Do the horizontal lines. Offset by 2 because the header is slightly bigger
y = 2.0
hstep = (hgt - y) / (rows + 1)
print(f'hstep {hstep}')
for ii in range(rows):
y += hstep
yint = int(y)
sep = ttk.Separator(self.parent, orient=tk.HORIZONTAL)
self.canvas.create_window((0, yint), width=wid, window=sep, anchor='nw')
# Now do the verticals
x = 0
ybeg = int(hstep)
yhgt = hgt - ybeg
for col in self.headers:
x += col[HDR_WIDTH]
sep = ttk.Separator(self.parent, orient=tk.VERTICAL)
self.canvas.create_window((x, ybeg), height=yhgt, window=sep, anchor='nw')
def clicked(self):
print('ouch')
def resize(self):
''' Resize the dialog'''
self.parent.update()
wid = self.tree.winfo_width() + self.treesb.winfo_width()
hgt = self.tree.winfo_height()
print(f'Resize to {wid}x{hgt}')
if wid < 10 or hgt < 10:
print('Not resizing')
else:
self.parent.geometry(f'{wid}x{hgt}')
def tree_open(self, parent):
''' Open all the children'''
self.tree.item(parent, open=True)
for child in self.tree.get_children(parent):
self.tree_open(child)
def main():
root = tk.Tk()
root.title("Tree")
ex = TreeviewTest(root, 12)
ex.resize()
root.mainloop()
if __name__ == '__main__':
main()
有没有办法在树形视图中检测列宽的变化?
解决方案
感谢 @Thingamabobs。基本上,我需要在画布上保存创建的小部件的ID,当发生移动事件时,如果目标是分隔符,就对分隔符对象进行相对移动。
修改如下:
- 在表头区域再添加几列
HDR_TAG = 0; HDR_NAME = 1; HDR_WIDTH = 2 ; HDR_ANCHOR = 3; HDR_SEP = 4; HDR_X = 5; HDR_Y = 6
2.在 initUI中,新增了额外的列。元组必须改成列表,以便进行修改
self.headers = [
['#0', 'Name', 100, tk.W, None, 0, 0],
['upper', 'Upper', 60, tk.E, None, 0, 0],
['lower', 'Lower', 60, tk.CENTER, None, 0, 0]
]
3.在 initOverlay中,保存了垂直分隔线的ID
# Now do the verticals
x = 0
ybeg = int(hstep)
yhgt = hgt - ybeg
for col in self.headers:
x += col[HDR_WIDTH]
sep = ttk.Separator(self.parent, orient=tk.VERTICAL)
col[HDR_X] = x
col[HDR_Y] = ybeg
col[HDR_SEP] = self.canvas.create_window((x, ybeg), height=yhgt, window=sep, anchor='nw')
4.在 initUI中,新增了两个绑定
# Set up resize events
self.tree.bind('<B1-Motion>', self.resize_check)
self.tree.bind('<ButtonRelease-1>', self.resize_end)
5.调整大小的代码
``` def resize_check(self, event): tree = event.widget if tree.identify_region(event.x, event.y) == 'separator': self.resize_column() self.resizing = True elif self.resizing: self.resize_column()
def resize_end(self, event):
self.resizing = False
def resize_column(self):
# Get the new sizes
x = 0
for col in self.headers:
col[HDR_WIDTH] = self.tree.column(col[HDR_TAG], 'width')
x += col[HDR_WIDTH]
if col[HDR_SEP] != None and col[HDR_X] != x:
self.canvas.move(col[HDR_SEP], x - col[HDR_X], 0)
col[HDR_X] = x
```
就这样——它现在就能工作了。
我得用place替代canvas来试试看。
编辑
这是使用place而不是canvas的代码
# Grid lines using horizontal separators
import tkinter as tk
import tkinter.ttk as ttk
HDR_TAG = 0; HDR_NAME = 1; HDR_WIDTH = 2 ; HDR_ANCHOR = 3; HDR_SEP = 4; HDR_X = 5; HDR_Y = 6
class TreeviewTest(ttk.Frame):
def __init__(self, parent, rows_visible):
ttk.Frame.__init__(self, parent)
self.parent = parent
self.initUI(rows_visible)
self.initOverlay(rows_visible)
def createBranch(self, tree, parent, family):
''' Create tree from dictionary '''
for child, nextgen in family.items():
chval = (nextgen[0], nextgen[1])
tree.insert(parent, "end", child, text=child, values=chval)
if len(nextgen) > 2:
self.createBranch(tree, child, nextgen[2])
def initUI(self, rows_visible):
''' Populate the tree '''
# Column widths and headings
self.headers = [
['#0', 'Name', 100, tk.W, None, 0, 0],
['upper', 'Upper', 60, tk.E, None, 0, 0],
['lower', 'Lower', 60, tk.CENTER, None, 0, 0]
]
col_list = []
for col in self.headers[1:]:
col_list.append(col[HDR_TAG])
# Set up the Treeview
# The header for the first column is not counted
tree = ttk.Treeview(self.parent, columns=col_list, height=rows_visible)
treesb = ttk.Scrollbar(self.parent, orient=tk.VERTICAL, command=tree.yview)
tree.configure(yscrollcommand=treesb.set)
for col in self.headers:
tree.column(col[HDR_TAG], width=col[HDR_WIDTH], anchor=col[HDR_ANCHOR])
tree.heading(col[HDR_TAG], text=col[HDR_NAME])
# The data
# Simple tree
numbers = "numbers"
tree.insert("", "end", numbers, text="Numbers", tags=("numbertag",))
# Nothing in the name column
tree.insert(numbers, "end", "one", values=("One", "1"))
tree.insert(numbers, "end", "two", values=("Two", "2"))
tree.insert(numbers, "end", "three", values=("Three", "3"))
# Another simple tree
letters = "letters";
tree.insert("", "end", letters, text="Letters", tags=("lettertag",))
# text= appears in the name column
tree.insert(letters, "end", text="Alpha", values=('A', 'a'))
tree.insert(letters, "end", text="Beta", values=("B", 'b'))
tree.insert(letters, "end", text="Gamma", values=("G", 'g'))
# Tree created from dictionary
familytree = {
"GGF": ("1", "1",
{
"GF1": ("11", "11",
{
"F11": ("111", "111"),
"F12": ("112", "112"),
"F13": ("113", "113")
}),
"GF2": ("12", "12"),
"GF3": ("30", "30",
{
"F31": ("131", "131"),
"F32": ("132", "132",
{
"S321": ("1321", "1321")
}),
"F33": ("133", "133")
})
})
}
self.createBranch(tree, "", familytree)
# Set the colours
tree.tag_configure('numbertag', background='green')
tree.tag_configure('lettertag', foreground='yellow', background='red')
#tree.tag_bind('ttk', '<1>', self.clicked)
self.tree = tree
self.treesb = treesb
# Now force all the children to open
for child in self.tree.get_children():
self.tree_open(child)
# Position the widgets
self.tree.place(x=0, y=0, anchor='nw')
self.parent.update()
wid = self.tree.winfo_width()
hgt = self.tree.winfo_height()
print(f'Set posn wid:{wid} hgt:{hgt}')
self.treesb.place(x=wid, y=0, anchor='nw', height=hgt)
# Set up resize events
self.resizing = False
self.tree.bind('<B1-Motion>', self.resize_check)
self.tree.bind('<ButtonRelease-1>', self.resize_end)
def initOverlay(self, rows):
''' Add the gridlines '''
self.parent.update()
wid = self.tree.winfo_width()
hgt = self.tree.winfo_height()
# Do the horizontal lines. Offset by 2 because the header is slightly bigger
y = 2.0
hstep = (hgt - y) / (rows + 1)
print(f'hstep {hstep}')
for ii in range(rows):
y += hstep
yint = int(y)
sep = ttk.Separator(self.parent, orient=tk.HORIZONTAL)
sep.place(x=0, y=yint, width=wid, anchor='nw')
# Now do the verticals
x = 0
ybeg = int(hstep)
yhgt = hgt - ybeg
for col in self.headers:
x += col[HDR_WIDTH]
col[HDR_SEP] = ttk.Separator(self.parent, orient=tk.VERTICAL)
col[HDR_X] = x
col[HDR_Y] = ybeg
col[HDR_SEP].place(x=x, y=ybeg, height=yhgt, anchor='nw')
def clicked(self):
print('ouch')
def resize(self):
''' Resize the dialog'''
self.parent.update()
wid = self.tree.winfo_width() + self.treesb.winfo_width()
hgt = self.tree.winfo_height()
print(f'Resize to {wid}x{hgt}')
if wid < 10 or hgt < 10:
print('Not resizing')
else:
self.parent.geometry(f'{wid}x{hgt}')
def tree_open(self, parent):
''' Open all the children'''
self.tree.item(parent, open=True)
for child in self.tree.get_children(parent):
self.tree_open(child)
def resize_check(self, event):
tree = event.widget
if tree.identify_region(event.x, event.y) == 'separator':
self.resize_column()
self.resizing = True
elif self.resizing:
self.resize_column()
def resize_end(self, event):
self.resizing = False
def resize_column(self):
# Get the new sizes
x = 0
for col in self.headers:
col[HDR_WIDTH] = self.tree.column(col[HDR_TAG], 'width')
x += col[HDR_WIDTH]
if col[HDR_SEP] != None and col[HDR_X] != x:
col[HDR_SEP].place(x=x)
col[HDR_X] = x
def main():
root = tk.Tk()
root.title("Tree")
ex = TreeviewTest(root, 12)
ex.resize()
root.mainloop()
if __name__ == '__main__':
main()

