如何准确提取ttk.Treeview中所有可见顶层项的iid?
提取ttk.Treeview中可见顶层项iid的正确方法
我在根窗口中创建了带有垂直和水平ttk.Scrollbar的ttk.Treeview组件,希望实现点击「Show Visible Top-Level Items iid」按钮时,提取所有可见顶层项的iid(即Restaurant 0、1、2这类项的iid)。
之前的尝试与问题
使用@JRiggles的解决方案后,返回结果不准确。例如调整窗口仅显示特定内容后,self._show()方法返回:
total_items=10 treeview_top_level_iids=('0', '1', '2', '3', '4', '5', '6', '7', '8', '9') y_top=0.3333333333333333 y_bottom=0.6 first_visible=3 last_visible=6 visible_tl_idds=['G3', 'G4', 'G5', 'G6']
但正确的visible_tl_idds应为['G4', 'G5']。
我尝试修改self._show()的计算逻辑:
if y_top == 0.0: first_visible = 0 else: first_visible = int(y_top * total_items) + 1 if y_bottom != 1.0: last_visible = int(y_bottom * total_items) else: last_visible = total_items - 1
替代原有代码:
first_visible = int(y_top * total_items) last_visible = int(y_bottom * total_items)
结果更接近实际,但仍不准确,G6不应出现在结果中:
total_items=10 treeview_top_level_iids=('0', '1', '2', '3', '4', '5', '6', '7', '8', '9') y_top=0.3333333333333333 y_bottom=0.6 first_visible=4 last_visible=6 visible_tl_idds=['G4', 'G5', 'G6']
正确解决方案
问题根源在于直接用yview()返回的比例计算可见项,没有考虑顶层项展开后包含子项,实际占据高度不均。正确做法是通过bbox()方法获取每个顶层项的边界框,判断其是否在Treeview的可视区域内。
修改后的_show()方法:
def _show(self): self.update_idletasks() # 获取Treeview组件的整体边界框(可视区域坐标) tree_bbox = self.tree.bbox("") if not tree_bbox: print("visible_tl_idds=[]") return tree_x, tree_y, tree_width, tree_height = tree_bbox visible_tl_idds = [] # 遍历所有顶层项 for iid in self.tree.get_children(): # 获取当前顶层项的边界框 item_bbox = self.tree.bbox(iid) if not item_bbox: continue # 项完全不可见时bbox返回空 item_x, item_y, item_w, item_h = item_bbox # 判断项是否与可视区域有重叠:项的底部大于可视区域顶部,且项的顶部小于可视区域底部 if (item_y + item_h > tree_y) and (item_y < tree_y + tree_height): visible_tl_idds.append(iid) print(f"{visible_tl_idds=}")
完整测试脚本
#!/usr/bin/python3 # -*- coding: utf-8 -*- try: import tkinter as tk import tkinter.ttk as ttk except: import Tkinter as tk import 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') for i in range(10): self.tree.tag_configure(i) self.tree.insert("", "end", iid=f"G{i}", open=True, tags=[i, 'Parent'], text=f"Restaurant {i}") self.tree.insert(f"G{i}", "end", iid=f"C-{i}", text=f"Cookie", tags=[f"{i}-a", 'Child', "Not Selected"], values=(f"C-{i}-a", f"{i}-Ca", f"{i}-Cb", False)) self.tree.insert(f"G{i}", "end", iid=f"P-{i}", text=f"Pudding", tags=[f"P-{i}", 'Child', "Not Selected"], values=(f"P-{i}", f"{i}-Pa", f"{i}-Pb", False)) # Button self.showbutton = ttk.Button(self, text="Show Visible Top-Level Items iid", command=self._show) self.showbutton.grid(row=2, column=0, sticky='nsew') def _show(self): self.update_idletasks() # 获取Treeview组件的整体边界框(可视区域坐标) tree_bbox = self.tree.bbox("") if not tree_bbox: print("visible_tl_idds=[]") return tree_x, tree_y, tree_width, tree_height = tree_bbox visible_tl_idds = [] # 遍历所有顶层项 for iid in self.tree.get_children(): # 获取当前顶层项的边界框 item_bbox = self.tree.bbox(iid) if not item_bbox: continue # 项完全不可见时bbox返回空 item_x, item_y, item_w, item_h = item_bbox # 判断项是否与可视区域有重叠:项的底部大于可视区域顶部,且项的顶部小于可视区域底部 if (item_y + item_h > tree_y) and (item_y < tree_y + tree_height): visible_tl_idds.append(iid) print(f"{visible_tl_idds=}") 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()
内容的提问来源于stack exchange,提问作者Sun Bear
相关产品推荐
相关产品推荐

