Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to capture events on tkinter child widgets?

In the following block, clicking on a_frame triggers the event handler on_frame_click, but clicking on a_label which is a child of a_frame does not. Is there a way to force a_frame to trap and handle events which originated on it's children (preferably with out having to add handlers to the children directly)? I am using Python 3.2.3.

import tkinter

def on_frame_click(e):
    print("frame clicked")

tk = tkinter.Tk()
a_frame = tkinter.Frame(tk, bg="red", padx=20, pady=20)
a_label = tkinter.Label(a_frame, text="A Label")
a_frame.pack()
a_label.pack()
tk.protocol("WM_DELETE_WINDOW", tk.destroy)
a_frame.bind("<Button>", on_frame_click)
tk.mainloop()
like image 894
Monkeyer Avatar asked Jul 12 '12 16:07

Monkeyer


People also ask

How do you bind an event in Python?

Binding Mouse click events In the below example we call the events to display the left-button double click, right button click and scroll-button click to display the position in the tkinter canvas where the buttons were clicked.

What is tkinter explain how the events are processed in tkinter?

Tkinter provides a mechanism to let the programmer deal with events. For each widget, it's possible to bind Python functions and methods to an event. widget.bind(event, handler) If the defined event occurs in the widget, the "handler" function is called with an event object. describing the event.

What is bind () in Python?

The bind() method of Python's socket class assigns an IP address and a port number to a socket instance. The bind() method is used when a socket needs to be made a server socket. As server programs listen on published ports, it is required that a port and the IP address to be assigned explicitly to a server socket.

What is Winfo_children tkinter?

TkinterServer Side ProgrammingProgramming. In order to get the information about the widgets, tkinter provides a set of methods that can be used to test the widgets in an application. In order to get the list of all the child widgets, we can use the winfo_children() method.


2 Answers

Yes, you can do what you want, but it requires a bit of work. It's not that it's not supported, it's just that it's actually quite rare to need something like this so it's not the default behavior.

TL;DR - research "tkinter bind tags"

The Tkinter event model includes the notion of "bind tags". This is a list of tags associated with each widget. When an event is received on a widget, each bind tag is checked to see if it has a binding for the event. If so, the handler is called. If not, it continues on. If a handler returns "break", the chain is broken and no more tags are considered.

By default, the bind tags for a widget are the widget itself, the widget class, the tag for the toplevel window the widget is in, and finally the special tag "all". However, you can put any tags you want in there, and you can change the order.

The practical upshot of all this? You can add your own unique tag to every widget, then add a single binding to that tag that will be processed by all widgets. Here's an example, using your code as a starting point (I added a button widget, to show this isn't something special just for frames and labels):

import Tkinter as tkinter

def on_frame_click(e):
    print("frame clicked")

def retag(tag, *args):
    '''Add the given tag as the first bindtag for every widget passed in'''
    for widget in args:
        widget.bindtags((tag,) + widget.bindtags())

tk = tkinter.Tk()
a_frame = tkinter.Frame(tk, bg="red", padx=20, pady=20)
a_label = tkinter.Label(a_frame, text="A Label")
a_button = tkinter.Button(a_frame, text="click me!")
a_frame.pack()
a_label.pack()
a_button.pack()
tk.protocol("WM_DELETE_WINDOW", tk.destroy)
retag("special", a_frame, a_label, a_button)
tk.bind_class("special", "<Button>", on_frame_click)
tk.mainloop()

For more on bindtags, you might be interested in my answer to the question How to bind self events in Tkinter Text widget after it will binded by Text widget?. The answer addresses a different question than the one here, but it shows another example of using bind tags to solve real world problems.

like image 155
Bryan Oakley Avatar answered Oct 16 '22 23:10

Bryan Oakley


I can't seem to find a direct method of automatically binding to child widgets (though there are methods of binding to an entire class of widgets and to all widgets in an application), but something like this would be easy enough.

def bind_tree(widget, event, callback, add=''):
    "Binds an event to a widget and all its descendants."

    widget.bind(event, callback, add)

    for child in widget.children.values():
        bind_tree(child, event, callback, replace_callback)

Just thought of this, but you could also put a transparent widget the size of a_frame on top of everything as a child of a_frame and bind the <Button> event to that, and then you could refer to a_frame as e.widget.master in the callback in order to make it reusable if necessary. That'd likely do what you want.

like image 36
JAB Avatar answered Oct 17 '22 00:10

JAB