Yes, you can set things up so that events that happen on any widget will trigger a binding on a higher level widget. Unfortunately, that means you have to configure every child widget in one way or another. There's no way to automatically get this behavior.
The mechanism for making this work is called a binding tag (or bindtag). When you bind to a widget you aren't actually binding to the widget. Instead, you are binding to a tag that is the name of a widget. When an event happens on a widget, tkinter will search through all of the bindtags for that widget to see if there is a binding for the event. When a match is found, the bound function is executed.
As an example, let's create a frame with a canvas, and with labels in the canvas.
frame = tk.Frame(root, bg="black")
canvas = tk.Canvas(frame, width=400, height=400, bg="bisque")
canvas.pack(padx=10, pady=10)
for i in range(10):
label = tk.Label(canvas, text=f"Label #{i+1}")
label.pack(side="top", padx=20, pady=4)
As mentioned earlier, each widget has a set of binding tags associated with it, and one of those tags is the name of the widget. Thus, the frame has a binding tag with the name of the frame. You can see these binding tags by printing the results of the bindtag
method:
print(f"bindtags for {frame}: {frame.bindtags()}")
The above will print this:
bindtags for .!frame: ('.!frame', 'Frame', '.', 'all')
These values represent, in order:
- the actual widget, so you can create bindings for the specific widget,
- the widget class, so you can create bindings for all widgets of the same ty pe,
- the toplevel (or root) window for the widget, so you can bind to all widgets in a window,
- the special string "all" so you can bind to literally all widgets at once for application-wide hot keys.
If we add the binding tag of the frame to the other widgets, then any bindings on the frame will also be picked up by the other widgets widgets. Again, this is because bindings to widgets aren't actually on the widget but rather on the widget's binding tag (which is set to the name of the widget)
canvas.bindtags((canvas, "Canvas", frame, ".", "all"))
for label in canvas.winfo_children():
label.bindtags((label, "Label", frame, ".", "all"))
If you set a binding on the frame, the binding will trigger even if you click on the canvas or one of the labels.
For more insights into binding tags, see the following answers which also mention binding tags:
Here is a complete example that has a working version of the above code:
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root, bg="black")
status_label = tk.Label(root, width=50)
print(f"bindtags for {frame}: {frame.bindtags()}")
status_label.pack(side="top", fill="x")
frame.pack(side="top", fill="both", expand=True)
canvas = tk.Canvas(frame, width=400, height=400, bg="bisque")
canvas.pack(padx=10, pady=10)
for i in range(10):
label = tk.Label(canvas, text=f"Label #{i+1}")
label.pack(side="top", padx=20, pady=4)
canvas.bindtags((canvas, "Canvas", frame, ".", "all"))
for label in canvas.winfo_children():
label.bindtags((label, "Label", frame, ".", "all"))
def handle_click(event):
status_label.configure(text=f"you clicked on a {event.widget.winfo_class()}")
frame.bind("<1>", handle_click)
root.mainloop()