Python tkinter 当 Toplevel 对象被销毁时,对于 <Destroy> 的顶层绑定,绑定命令执行多次

Python tkinter When Toplevel object is destroyed, With toplevel binding for <Destroy> the bound command executes multiple times

在下面的代码中,当顶层window被销毁时, bind 语句被执行多次。顶层中的每个子部件可能一次。当我将顶层更改为框架时,绑定命令只执行一次。 在这个例子中, quit() 或 raise SystemExit 被推迟到命令完成它的循环。为什么会这样?

import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showinfo

class PlainFrame(tk.Frame):
    def __init__(self,parent):
        super().__init__(parent)
        self.butts = []
        for i in range(20) :
            # button
            self.butts.append(ttk.Button(self, text=f'Click Me {i}'))
            self.butts[i]['command'] = self.button_clicked
            self.butts[i].grid(column=0,row=i)
        self.pack()

    def button_clicked(self):
        showinfo(title='Information',
                 message='Hello, Tkinter!')

class MainFrame(tk.Toplevel):
#class MainFrame(tk.Frame):
    def __init__(self, container,*args,**kwargs):
        super().__init__(container,kwargs)

        options = {'padx': 5, 'pady': 5}

        # label
        self.label = ttk.Label(self, text='Hello, Tkinter!')
        self.label.pack(**options)
        self.quit_button = ttk.Button(self,text='Quit',
                                      command = self._quit)
        self.quit_button.pack()
        self.frame = PlainFrame(self)
        # add when frame
        #self.pack()

    def button_clicked(self):
        showinfo(title='Information',
                 message='Hello, Tkinter!')

    def _quit(self):
        self.destroy()
    

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        # configure the root window
        self.title('My Awesome App')
        self.geometry('600x100')
    
        def quitting(self,event):
            print ('just passing through')
            quit()
            raise SystemExit

if __name__ == "__main__":
    app = App()
    frame = MainFrame(app)
    # app.withdraw()
    frame.bind('<Destroy>',app.quitting)
    app.mainloop()

With toplevel binding for the bound command executes multiple times

是的,这就是 tkinter 设计的工作方式。

绑定到某物时,您不会绑定到小部件。相反,您绑定到 绑定标签 。每个小部件都有一组 绑定标签 。当一个小部件接收到一个事件时,tkinter 将检查它的每个绑定标签,以查看是否有针对给定事件绑定到它的函数。

那么,什么是widget绑定标签呢?每个小部件都有绑定标签“all”。每个小部件还获得一个以小部件本身命名的绑定标记。它获得第三个绑定标签,即小部件的名称 class(例如:“Button”、“Label”等)。第四个标记——给您带来麻烦的标记——是包含小部件的 window 的名称。顺序从最具体到最不具体:小部件、小部件 class、window、“全部”。

您可以通过打印出小部件的绑定标签来查看这一点。考虑以下代码:

import tkinter as tk

root = tk.Tk()
toplevel = tk.Toplevel(root)
label = tk.Label(toplevel)
print(f"binding tags for label: {label.bindtags()}")

当 运行 时,上面的代码产生这个输出:

binding tags for label: ('.!toplevel.!label', 'Label', '.!toplevel', 'all')

第一个字符串,.!toplevel.!label是标签小部件的内部名称。 Label 是小部件 class,.!toplevel 是顶级小部件的名称,然后是字符串 all.

当您点击标签时会发生什么?首先,tkinter 将检查标签 .!toplevel.!label 上的按钮是否有绑定。如果您绑定到小部件本身,它将有一个。接下来,它将检查小部件 class 上是否存在绑定。滚动条和按钮等小部件将绑定到小部件 class,但标签不会。接下来,tkinter 将查看事件的 window 本身是否有绑定。最后,它将查看特殊标签 all.

上是否有绑定

您可以通过将绑定标签列表传递给 bindtags 命令来更改小部件的绑定标签。例如,如果您希望每个小部件都有一个框架的绑定标签,您可以将绑定标签设置为包含框架,然后每个小部件都会响应绑定到框架的事件。

您也可以使用相同的技术来删除绑定。例如,如果您想从 Text 小部件中删除所有默认绑定,您可以从其绑定标签列表中删除 Text。执行此操作后,小部件将不会响应任何按键或按键释放。

关于绑定标签如何工作的规范描述在 tcl/tk 的 bindtags man page 中。