将事件传播到比 Widget 更远的地方?

Propagate Events farther than within Widget?

我试图找到,但我找到的所有教程和文档都没有产生任何结果。所以我做了以下实验:有一个简单的 gui 树,看起来像这样:

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

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

我的实验代码如下

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()
...

根 window 的名称为“.”。因此,b1 的全名是 .a.b1,因为 b1a 的子节点,而 a 是根 window 的子节点。

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

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

请注意,在两个打印语句中它都显示 .a.b1,即使“root”正在处理该事件。尽管看起来 root 正在获取事件,但实际上是 b1 小部件获取了事件,但它是由 b1root.[=29 上的绑定处理的=]

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

  1. 小部件的全名(例如:“.a.b1”)
  2. 小部件的 class(“框架”)
  3. 根的名称window (".")
  4. 特殊标签“全部”

当像 b1 这样的小部件接收到事件时,tkinter 将遍历绑定标签列表和 运行 与该事件的每个标签关联的任何函数。所以,它是这样的:

  1. b1 (".a.b1") 上有一个绑定,因此调用了该函数
  2. “框架”没有绑定,所以没有调用任何东西
  3. 在根 window (".") 上有一个绑定,因此调用了该函数
  4. 特殊标签“all”没有绑定。

在任何时候,如果被调用函数之一 returns 字符串 "break",则链被破坏,将不再处理绑定标签。