如何在实例化期间正确使用ttk.Treeview.bbox?

huangapple go评论89阅读模式
英文:

How to get ttk.Treeview.bbox to work correctly during instantiating?

问题

以下是翻译好的部分:

这个脚本的目的是暴露出一个 ttk.Treeview 小部件的可见顶级项目的 id(即 idd)。我在类中创建了一个名为 _show_visible_toplevel_iids 的方法来实现这一目标。我注意到这个方法(特别是 ttk.Treeview 小部件的 .bbox 方法)在类的实例化阶段不起作用。然而,在随后的阶段,也就是手动点击鼠标运行 _show_visible_toplevel_iids 方法的 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=[]

当点击 ttk.Button 时,以下消息被打印出来:

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')]

你能帮我理解我做错了什么吗?tkinter 本身是否存在 bug?

测试脚本如下:

#!/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'  # 蓝色主题
        BG1 = '#4e88e5'  # 蓝色主题

        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
        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="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="Pudding",
                             tags=[tliid, ciid, 'Child', "Not Selected"],
                             values=(tliid, f"{i}-Pa", f"{i}-Pb", False))
            counter += 1

        # 按钮
        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 不起作用
        self.showbutton.invoke()            # .bbox 不起作用

    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()
英文:

The script below is meant to expose the visible top-level items id (i.e idd) of a ttk.Treeview widget. I created a method called ._show_visible_toplevel_iids in the class to achieve this. I notice that this method (specifically the .bbox method of the ttk.Treeview widget) does not work correctly during the instantiation stage of the class. However, this method works thereafter, i.e. whenever the ttk.Button widget which runs the ._show_visible_toplevel_iids method is manually clicked on by the mouse.

The following messages are printed out during instantiation:

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=[]

The following messages are printed out when the ttk.Button is clicked:

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')]

Can you help me understand what I am doing wrong? Is there a bug in tkinter itself?

Test Script:

#!/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()

答案1

得分: 2

当你创建一个小部件时,它实际上不会立即显示出来。相反,小部件(或者至少它的底层图形实体)直到被映射之前并没有真正被创建,而且要在应用程序事件循环在小部件的脚本级部分创建后进入空闲状态时才发出请求(假设你也已经将小部件放入了一个适当的容器中,直到映射为止;toplevel 直接与窗口管理器和根窗口交互)。之后,发生了一系列事件和回调的复杂交互,工具包会决定小部件的实际大小和位置;在所有这些都完成之前,你不能从任何边界框查询中获取有用的信息。处理速度很快,但不是瞬时的。

所有这些延迟处理就是为什么 Tk(因此 Tkinter)看起来如此快,并且已经保持了 30 年;复杂的部分在后台运行。

更正式地说,一旦你收到一个<Configure>事件传递给小部件,你应该能够获取边界框信息。最好等待事件循环在这一点上进入空闲状态,以防其他排队的事件也会导致调整大小;你可能看不到延迟,但它确保在开始查询最终布局之前排空了所有排队的事件。

使用这个来进行初始调用,而不是直接调用

self.tree.bind("", self.__show_first_visible_toplevel_iids)

然后使用这个方法

def __show_first_visible_toplevel_iids(self, e):
self.tree.bind("", None)
# 我 认为 这是正确的调用方式
self.tree.after_idle(self._show_visible_toplevel_iids)

英文:

When you create a widget, it doesn't actually get displayed immediately. Instead, the widget (or at least its underlying graphical entities) isn't really created until it is mapped, and the request to do that happens when the application event loop becomes idle after the script-level part of the widget is created (assuming you've also put the widget in a suitable container up as far as a toplevel that is mapped; toplevels directly interact with the window manager and the root window). After that, there is a fairly complex interaction of events and callbacks while the toolkit decides what actual size and position to give the widget; until those are all done, you don't get useful information out of any bounding box queries. The processing is fast, but not instantaneous.

All this delayed processing is why Tk (and hence Tkinter) seems so fast and has done for 30 years; the complex bits run in the background.

More formally, once you get a &lt;Configure&gt; event delivered to the widget, you should be able to get bounding box info. It's probably best to wait for the event loop to go idle at that point as well in case some other event queued causes a resize as well; you won't be able to see the delay but it ensures that all queued events are drained before you start querying the finalized layout.

# Use this to do the initial call, instead of directly
self.tree.bind(&quot;&lt;Configure&gt;&quot;, self.__show_first_visible_toplevel_iids)
# and then this method
def __show_first_visible_toplevel_iids(self, e):
self.tree.bind(&quot;&lt;Configure&gt;&quot;, None)
# I *think* this is the right way to call after_idle
self.tree.after_idle(self._show_visible_toplevel_iids)

答案2

得分: 1

以下是您提供的代码的翻译部分:

"Thanks @acw1668.

I learned from you that during the instantiation of my App class, the geometry manager methods (i.e. grid, place or pack) of self (which is a ttk.Frame widget) typically is not yet activated. Consequently, the self and the ttk.Treeview widgets are not visible yet. Therefore the .bbox method of the ttk.Treeview widget did not detect any visible ttk.Treeview items.

To overcome this issue, I believe a best practice for customizing a widget class that consists of a ttk.Treeview widget is to not insert any items into the ttk.Treeview widget or use the .bbox method of ttk.Treeview during the class initialization stage. Rather, the insertion of items into the ttk.Treeview widget and any use of its .bbox method should be done after the customized widget class has been instantiated and positioned into the Tk window.

Test Script:

#!/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):
        super().__init__(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()

        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):
        # 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')
            
        # 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')
        

    def insert_treeview_items(self):
        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="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="Pudding", 
                             tags=[tliid, ciid, 'Child', "Not Selected"],
                             values=(tliid, f"{i}-Pa", f"{i}-Pb", False))
            counter += 1
            
    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()
    root.rowconfigure(0, weight=1)
    root.columnconfigure(0, weight=1)

    app = App(root)
    app.grid(row=0, column=0, sticky='nsew')
    app.insert_treeview_items()  # Do this after app has been gridded
    app.showbutton.invoke()      # Do this after app has been gridded

    root.mainloop()"


<details>
<summary>英文:</summary>

Thanks @acw1668. 

I learned from you that during the instantiation of my App class, the geometry manager methods (i.e. `grid`, `place` or `pack`) of `self` (which is a `ttk.Frame` widget) typically is not yet activated. Consequently, the `self` and the `ttk.Treeview` widgets are not visible yet. Therefore the `.bbox` method of the `ttk.Treeview` widget did not detect any visible `ttk.Treeview` items.

To overcome this issue, I believe a **best practice** for customizing a widget class that consists of a `ttk.Treeview` widget is to not insert any items into the `ttk.Treeview` widget or use the `.bbox` method of `ttk.Treeview` during the class initialization stage. Rather, the insertion of items into the `ttk.Treeview` widget and any use of its `.bbox` method should be done after the customized widget class has been instantiated and positioned into the `Tk` window.



Test Script:

    #!/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):
            super().__init__(parent=None, style=&#39;App.TFrame&#39;, borderwidth=0,
                             relief=&#39;raised&#39;, width=390, height=390)
            self.parent = parent
            self.parent.title(&#39;Treeview&#39;)
            self.parent.geometry(&#39;470x350&#39;)
            
            self.setStyle()
            self.createWidgets()
    
            self.rowconfigure(0, weight=1)
            self.columnconfigure(0, weight=1)
        
    
        def setStyle(self):
            style = ttk.Style()
            style.configure(&#39;App.TFrame&#39;, background=&#39;pink&#39;)
    
    
        def createWidgets(self):
            # Treeview with scroll bars
            columns = [f&#39;Column {i}&#39; for i in range(10)]
            self.tree = ttk.Treeview(
                self, height=20, selectmode=&#39;extended&#39;, takefocus=True,
                columns=(&quot;type&quot;, &quot;property A&quot;, &quot;property B&quot;, &quot;selected&quot;),
                displaycolumns=[&quot;property A&quot;, &quot;property B&quot;, &quot;selected&quot;])
            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=&#39;nsew&#39;)
            self.ysb.grid(row=0, column=5, sticky=&#39;ns&#39;)
            self.xsb.grid(row=1, column=0, columnspan=4, sticky=&#39;ew&#39;)
    
            self.tree.column(&#39;#0&#39;, stretch=True, anchor=&#39;w&#39;, width=100)
            self.tree.column(&#39;property A&#39;, stretch=True, anchor=&#39;n&#39;, width=100)
            self.tree.column(&#39;property B&#39;, stretch=True, anchor=&#39;n&#39;, width=100)
            self.tree.column(&#39;selected&#39;, stretch=True, anchor=&#39;n&#39;, width=100)
    
            self.tree.heading(&#39;#0&#39;, text=&quot;Type&quot;, anchor=&#39;w&#39;)
            self.tree.heading(&#39;property A&#39;, text=&#39;Property A&#39;, anchor=&#39;center&#39;)
            self.tree.heading(&#39;property B&#39;, text=&#39;Property B&#39;, anchor=&#39;center&#39;)
            self.tree.heading(&#39;selected&#39;, text=&#39;Selected&#39;, anchor=&#39;center&#39;)
                
            # Button
            self.showbutton = ttk.Button(self, text=&quot;Show Visible Top-Level Items iid&quot;,
                                         command=self.show_visible_toplevel_iids)
            self.showbutton.grid(row=2, column=0, sticky=&#39;nsew&#39;)
            
    
        def insert_treeview_items(self):
            counter = 0
            for i in range(10):
                self.tree.tag_configure(i)
                tliid = f&quot;G{i}&quot;
                self.tree.insert(&quot;&quot;, &quot;end&quot;, iid=tliid, open=True,
                                 tags=[tliid, &#39;Parent&#39;], text=f&quot;Restaurant {i}&quot;)
                ciid = f&quot;F{counter}&quot;
                self.tree.insert(tliid, &quot;end&quot;, iid=ciid, text=f&quot;Cookie&quot;, 
                                 tags=[tliid, ciid, &#39;Child&#39;, &quot;Not Selected&quot;],
                                 values=(tliid, f&quot;{i}-Ca&quot;, f&quot;{i}-Cb&quot;, False))
                counter += 1
                ciid = f&quot;F{counter}&quot;
                self.tree.insert(tliid, &quot;end&quot;, iid=ciid, text=f&quot;Pudding&quot;, 
                                 tags=[tliid, ciid, &#39;Child&#39;, &quot;Not Selected&quot;],
                                 values=(tliid, f&quot;{i}-Pa&quot;, f&quot;{i}-Pb&quot;, False))
                counter += 1
                
        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&quot;{all_toplevel_iids=}&quot;)
            print(f&quot;{visible_toplevel_iids=}&quot;)
            print(f&quot;{children_iids=}&quot;)
                   
    
    if __name__ == &#39;__main__&#39;:
        root = tk.Tk()
        root.rowconfigure(0, weight=1)
        root.columnconfigure(0, weight=1)
    
        app = App(root)
        app.grid(row=0, column=0, sticky=&#39;nsew&#39;)
        app.insert_treeview_items()  # Do this after app has been gridded
        app.showbutton.invoke()      # Do this after app has been gridded
    
        root.mainloop()

</details>



huangapple
  • 本文由 发表于 2023年6月29日 10:36:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/76577745.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定