在Tkinter的 Treeview中检测列宽调整

编程语言 2026-07-11

我尝试在树形视图上,通过在画布(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,当发生移动事件时,如果目标是分隔符,就对分隔符对象进行相对移动。

修改如下:

  1. 在表头区域再添加几列

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()
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章