如何让ttk.Treeview.bbox在实例化阶段正常工作?
问题:ttk.Treeview的.bbox方法在实例化阶段无法获取可见项IID
我编写了一个类,其中_show_visible_toplevel_iids方法用于获取ttk.Treeview组件的可见顶层项IID,但该方法在类实例化阶段调用时,.bbox方法无法正常工作;而后续手动点击触发该方法的ttk.Button组件时,功能却完全正常。
现象对比
实例化阶段打印输出
all_toplevel_iids=('G0', 'G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'G9') visible_toplevel_iids=[] children_iids=[] all_toplevel_iids=('G0', 'G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'G9') visible_toplevel_iids=[] children_iids=[]
手动点击按钮时打印输出
all_toplevel_iids=('G0', 'G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'G9') visible_toplevel_iids=['G0', 'G1', 'G2', 'G3', 'G4'] children_iids=[('F0', 'F1'), ('F2', 'F3'), ('F4', 'F5'), ('F6', 'F7'), ('F8', 'F9')]
测试脚本
#!/usr/bin/python3 # -*- coding: utf-8 -*- import tkinter as tk import tkinter.ttk as ttk class App(ttk.Frame): def __init__(self, parent, *args, **kwargs): BG0 = '#aabfe0' #Blue scheme BG1 = '#4e88e5' #Blue scheme ttk.Frame.__init__(self, parent=None, style='App.TFrame', borderwidth=0, relief='raised', width=390, height=390) self.parent = parent self.parent.title('Treeview') self.parent.geometry('470x350') self.setStyle() self.createWidgets(BG0, BG1) self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) def setStyle(self): style = ttk.Style() style.configure('App.TFrame', background='pink') def createWidgets(self, BG0, BG1): # Treeview with scroll bars columns = [f'Column {i}' for i in range(10)] self.tree = ttk.Treeview( self, height=20, selectmode='extended', takefocus=True, columns=("type", "property A", "property B", "selected"), displaycolumns=["property A", "property B", "selected"]) self.ysb = ttk.Scrollbar(self, orient=tk.VERTICAL) self.xsb = ttk.Scrollbar(self, orient=tk.HORIZONTAL) self.tree.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set) self.tree.grid(row=0, column=0, columnspan=4, sticky='nsew') self.ysb.grid(row=0, column=5, sticky='ns') self.xsb.grid(row=1, column=0, columnspan=4, sticky='ew') self.tree.column('#0', stretch=True, anchor='w', width=100) self.tree.column('property A', stretch=True, anchor='n', width=100) self.tree.column('property B', stretch=True, anchor='n', width=100) self.tree.column('selected', stretch=True, anchor='n', width=100) self.tree.heading('#0', text="Type", anchor='w') self.tree.heading('property A', text='Property A', anchor='center') self.tree.heading('property B', text='Property B', anchor='center') self.tree.heading('selected', text='Selected', anchor='center') counter = 0 for i in range(10): self.tree.tag_configure(i) tliid = f"G{i}" self.tree.insert("", "end", iid=tliid, open=True, tags=[tliid, 'Parent'], text=f"Restaurant {i}") ciid = f"F{counter}" self.tree.insert(tliid, "end", iid=ciid, text=f"Cookie", tags=[tliid, ciid, 'Child', "Not Selected"], values=(tliid, f"{i}-Ca", f"{i}-Cb", False)) counter += 1 ciid = f"F{counter}" self.tree.insert(tliid, "end", iid=ciid, text=f"Pudding", tags=[tliid, ciid, 'Child', "Not Selected"], values=(tliid, f"{i}-Pa", f"{i}-Pb", False)) counter += 1 # Button self.showbutton = ttk.Button(self, text="Show Visible Top-Level Items iid", command=self._show_visible_toplevel_iids) self.showbutton.grid(row=2, column=0, sticky='nsew') self._show_visible_toplevel_iids() # .bbox doesn't work correctly self.showbutton.invoke() # .bbox doesn't work correctly def _show_visible_toplevel_iids(self): tree = self.tree tree.update_idletasks() all_toplevel_iids = tree.get_children() visible_toplevel_iids = [i for i in all_toplevel_iids if isinstance(tree.bbox(i), tuple)] children_iids = [tree.get_children([i]) for i in visible_toplevel_iids] print() print(f"{all_toplevel_iids=}") print(f"{visible_toplevel_iids=}") print(f"{children_iids=}") if __name__ == '__main__': root = tk.Tk() app = App(root) app.grid(row=0, column=0, sticky='nsew') root.rowconfigure(0, weight=1) root.columnconfigure(0, weight=1) root.mainloop()
原因与解决方法
这不是tkinter的bug,问题出在组件渲染时机上:
- Treeview的
.bbox方法依赖组件实际绘制到屏幕后的几何布局信息。在类实例化阶段,mainloop()还未启动,窗口和Treeview组件还未完成最终的布局计算,此时即使调用update_idletasks(),也无法获取到完整的渲染后尺寸数据,导致.bbox返回空值。 - 手动点击按钮时,窗口已经完成渲染,布局信息全部确定,
.bbox能正常返回可见项的边界框。
修复方案
将实例化阶段直接调用_show_visible_toplevel_iids的代码,替换为使用after_idle()延迟执行,它会等待tkinter处理完所有初始化和布局任务后再执行目标函数:
修改createWidgets方法末尾的代码:
# 替换原来的直接调用和invoke self.parent.after_idle(self._show_visible_toplevel_iids)
修改后,程序会在窗口第一次渲染完成后自动调用方法,此时.bbox就能正确获取到可见项的IID。
内容的提问来源于stack exchange,提问作者Sun Bear
相关产品推荐
相关产品推荐

