传播事件比在Widget中传播更远?

问题描述

  • 是否可以将事件发送给其他创建它的小部件?

我试图找到,但是我发现的所有教程和文档都没有产生任何效果。因此,我进行了以下实验:有一个简单的GUI树,如下所示:

                    root Tk()
                      |
          +---- Frame a------+------------+
          |                  |            |
   +---Frame b1--+        Frame b2     Label al
   |             |           |
Frame c       Label b1l   Label b2l
   |        
Label cl
           

然后,我将b1设置为在<<MyEvent>>上检测到<Button-1>调用b1l。此外,我将<<MyEvent>>绑定到所有其他根节点。当运行程序并单击所述标签时,我只能看到b1本身和root对事件有反应,但亲戚都没有反应。因此看来,此事件只能由调用它的小部件和在根小部件中捕获。

  • 但是有没有办法让其他任何小部件接收此事件?

  • 还有谁能推荐涵盖此类内容的更全面的教程/文档?

我的实验代码如下

import tkinter
root = tkinter.Tk()
a    = tkinter.Frame(master=root); a.pack()
al   = tkinter.Label(master=a,text="label a  "); al .pack()
b1   = tkinter.Frame(master=a); b1.pack()
b1l  = tkinter.Label(master=b1,text="label b1"); b1l.pack()
c    = tkinter.Frame(master=b1); c.pack()
cl   = tkinter.Label(master=c,text="label c1 "); cl .pack()
b2   = tkinter.Frame(master=a); b2.pack()
b2l  = tkinter.Label(master=b2,text="label b2"); b2l.pack()
root.bind('<<MyEvent>>',lambda *_: print("MyEvent caught in root"))
a.   bind('<<MyEvent>>',lambda *_: print("MyEvent caught in a   "))
al.  bind('<<MyEvent>>',lambda *_: print("MyEvent caught in al  "))
b1.  bind('<<MyEvent>>',lambda *_: print("MyEvent caught in b1  "))
b1l. bind('<<MyEvent>>',lambda *_: print("MyEvent caught in b1l "))
b2.  bind('<<MyEvent>>',lambda *_: print("MyEvent caught in b2  "))
b2l. bind('<<MyEvent>>',lambda *_: print("MyEvent caught in b2l "))
c.   bind('<<MyEvent>>',lambda *_: print("MyEvent caught in c   "))
cl.  bind('<<MyEvent>>',lambda *_: print("MyEvent caught in cl  "))
def cb(*x):
    b1.event_generate('<<MyEvent>>')
b1l.bind('<Button-1>',cb)
root.mainloop()

解决方法

简而言之,事件不会传播。这根本不是事件在Tkinter中的工作方式。它们仅由接收事件的小部件处理。如果您希望每个小部件都收到<<MyEvent>>事件,则必须在每个小部件上调用event_generate

即使您的代码使root也会收到事件,但事实并非如此。这就是为什么这样做的原因:

通过绑定调用函数时,将传递代表事件的对象。该对象的属性之一是widget,它代表捕获事件的小部件。如果将其与已经打印过的字符串一起打印出来,则会看到它仅报告您单击过的标签。

例如,将您的绑定语句更改为如下形式:

...
root.bind('<<MyEvent>>',lambda event: print(f"MyEvent caught in root ({event.widget})"))
b1.  bind('<<MyEvent>>',lambda event: print(f"MyEvent caught in b1   ({event.widget})"))
...

为便于阅读,请为您的每个小部件起一个这样的名称:

...
a    = tkinter.Frame(master=root,name="a"); a.pack()
b1   = tkinter.Frame(master=a,name="b1"); b1.pack()
...

根窗口的名称为“。”。由于b1.a.b1的子级,而b1是根窗口的子级,因此a的全名是a

当我这样做时,我单击b1l,这是输出:

MyEvent caught in b1   (.a.b1)
MyEvent caught in root (.a.b1)

请注意,即使在“ root”正在处理事件的情况下,在两个打印语句中也显示.a.b1。即使看起来像root一样正在获取事件,实际上它是b1小部件来获取事件,但同时由b1root上的绑定来处理。

原因是事件在tkinter中如何工作的基础。函数实际上并不绑定到小部件,而是绑定到绑定标签。每个小部件都有一组与之关联的绑定标签。默认情况下,这组绑定标签为:

  1. 窗口小部件的全名(例如:“。a.b1”)
  2. 窗口小部件的类别(“框架”)
  3. 根窗口的名称(“。”)
  4. 特殊标签“ all”

b1之类的小部件接收到一个事件时,tkinter将遍历绑定标签的列表并为该事件运行与每个标签关联的任何功能。因此,它是这样的:

  1. b1(“。a.b1”)上有一个绑定,因此该函数被调用
  2. “框架”没有绑定,所以什么也没叫
  3. 根窗口(“。”)上有一个绑定,因此该函数被调用
  4. 特殊标签“ all”上没有绑定。

在任何时候,如果被调用函数之一返回字符串"break",则该链将断开,并且将不再处理绑定标签。