flawr
flawr

Reputation: 11628

Propagate Events farther than within Widget?

I tried to find but all tutorials and documentations I found didn't yield anything. So I made following experiment: There is a simple gui tree that looks like so:

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

Then I set up b1 to invoke <<MyEvent>> whenever a <Button-1> was detected on b1l. Furthermore I bound <<MyEvent>> to all other root nodes. when running the program and clicking on said label, I could only see that b1 itself and root reacted to the event, but none of the relatives reacted. So it seems that this event can only be caught by the widget where itw as invoked and in the root widget.

The code of my experiment is following

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

Upvotes: 0

Views: 147

Answers (1)

Bryan Oakley
Bryan Oakley

Reputation: 385970

In short, events are not propagated. That's simply not how events work in Tkinter. They are only handled by the widget that receives the event. If you want every widget to receive the <<MyEvent>> event, you'll have to call event_generate on every widget.

Even though your code makes it appear that root also gets the event, it does not. Here's why it appears that it does:

When a function is called via a binding, it is passed an object representing the event. One of the attributes of this object is widget, which represents the widget that caught the event. If you print that out along with the string you're already printing, you'll see that it only ever reports the label you clicked on.

For example, change your bind statements to look like this:

...
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})"))
...

For the sake of readability, give each of your widgets a name like so:

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

The root window has the name ".". The full name of b1 is thus .a.b1 since b1 is a child of a and a is a child of the root window.

When I do that and I click on b1l, this is the output:

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

Note that in both print statements it shows .a.b1, even when "root" is handling the event. Even though it looks like root is getting the event, it's actually the b1 widget that gets the event but it is being handled by both the binding on b1 and on root.

The reason for that is fundamental to how events work in tkinter. Functions aren't actually bound to widgets, but instead are bound to a binding tag. Every widget has a set of binding tags associated with it. By default this set of binding tags is:

  1. the full name of the widget (eg: ".a.b1")
  2. the class of the widget ("Frame")
  3. the name of the root window (".")
  4. the special tag "all"

When a widget like b1 receives an event, tkinter will traverse the list of binding tags and run any function associated with each tag for that event. So, it goes like this:

  1. there is a binding on b1 (".a.b1"), so that function is called
  2. there is no binding for "Frame" so nothing is called
  3. there is a binding on the root window (".") so that function is called
  4. there is no binding on the special tag "all".

At any point, if one of the called function returns the string "break", the chain is broken and no more binding tags will be processed.

Upvotes: 1

Related Questions