Events and Bindings
As was mentioned earlier, a Tkinter application spends most of
its time inside an event loop (entered via the mainloop
method). Events can come from various sources, including key
presses and mouse operations by the user, and redraw events from
the window manager (indirectly caused by the user, in many
cases).
Tkinter provides a powerful mechanism to let you deal with events yourself. For each widget, you can bind Python functions and methods to events.
Here’s a simple example:
Keyboard events are sent to the widget that currently owns the keyboard focus. You can use the focus_set method to move focus to a widget:
Instead of spending a few pages on discussing all the syntactic shortcuts, let’s take a look on the most common event formats:
But Tkinter also allows you to create bindings on the class and application level; in fact, you can create bindings on four different levels:
First, on each of these four levels, Tkinter chooses the “closest match” of the available bindings. For example, if you create instance bindings for the <Key> and <Return> events, only the second binding will be called if you press the Enter key.
However, if you add a <Return> binding to the toplevel widget, both bindings will be called. Tkinter first calls the best binding on the instance level, then the best binding on the toplevel window level, then the best binding on the class level (which is often a standard binding), and finally the best available binding on the application level. So in an extreme case, a single event may call four event handlers.
A common cause of confusion is when you try to use bindings to override the default behavior of a standard widget. For example, assume you wish to disable the Enter key in the text widget, so that the users cannot insert newlines into the text. Maybe the following will do the trick?
Unfortunately, the newline is still inserted, since the above binding applies to the instance level only, and the standard behavior is provided by a class level bindings.
You could use the bind_class method to modify the bindings on the class level, but that would change the behavior of all text widgets in the application. An easier solution is to prevent Tkinter from propagating the event to other handlers; just return the string “break” from your event handler:
You can use the protocol method to install a handler for this protocol (the widget must be a root or Toplevel widget):
Tkinter provides a powerful mechanism to let you deal with events yourself. For each widget, you can bind Python functions and methods to events.
widget.bind(event, handler)If an event matching the event description occurs in the widget, the given handler is called with an object describing the event.
Here’s a simple example:
Capturing clicks in a window
In this example, we use the bind method of the frame widget
to bind a callback function to an event called
<Button-1>. Run this program and click in the window that
appears. Each time you click, a message like “clicked at 44
63” is printed to the console window.from Tkinter import * root = Tk() def callback(event): print "clicked at", event.x, event.y frame = Frame(root, width=100, height=100) frame.bind("<Button-1>", callback) frame.pack() root.mainloop()
Keyboard events are sent to the widget that currently owns the keyboard focus. You can use the focus_set method to move focus to a widget:
Capturing keyboard events
If you run this script, you’ll find that you have to click in the
frame before it starts receiving any keyboard events.from Tkinter import * root = Tk() def key(event): print "pressed", repr(event.char) def callback(event): frame.focus_set() print "clicked at", event.x, event.y frame = Frame(root, width=100, height=100) frame.bind("<Key>", key) frame.bind("<Button-1>", callback) frame.pack() root.mainloop()
Events #
Events are given as strings, using a special event syntax:<modifier-type-detail>The type field is the most important part of an event specifier. It specifies the kind of event that we wish to bind, and can be user actions like Button, and Key, or window manager events like Enter, Configure, and others. The modifier and detail fields are used to give additional information, and can in many cases be left out. There are also various ways to simplify the event string; for example, to match a keyboard key, you can leave out the angle brackets and just use the key as is. Unless it is a space or an angle bracket, of course.
Instead of spending a few pages on discussing all the syntactic shortcuts, let’s take a look on the most common event formats:
Event Formats
- <Button-1>
-
A mouse button is pressed over the widget. Button 1 is the
leftmost button, button 2 is the middle button (where available),
and button 3 the rightmost button. When you press down a mouse
button over a widget, Tkinter will automatically “grab” the mouse
pointer, and subsequent mouse events (e.g. Motion and Release events)
will then be sent to the current widget as long as the mouse button
is held down, even if the mouse is moved outside the current widget.
The current position of the mouse pointer (relative to the widget)
is provided in the x and y members of the event object
passed to the callback.
You can use ButtonPress instead of Button, or even leave it out completely: <Button-1>, <ButtonPress-1>, and <1> are all synonyms. For clarity, I prefer the <Button-1> syntax. - <B1-Motion>
- The mouse is moved, with mouse button 1 being held down (use B2 for the middle button, B3 for the right button). The current position of the mouse pointer is provided in the x and y members of the event object passed to the callback.
- <ButtonRelease-1>
- Button 1 was released. The current position of the mouse pointer is provided in the x and y members of the event object passed to the callback.
- <Double-Button-1>
- Button 1 was double clicked. You can use Double or Triple as prefixes. Note that if you bind to both a single click (<Button-1>) and a double click, both bindings will be called.
- <Enter>
- The mouse pointer entered the widget (this event doesn’t mean that the user pressed the Enter key!).
- <Leave>
- The mouse pointer left the widget.
- <FocusIn>
- Keyboard focus was moved to this widget, or to a child of this widget.
- <FocusOut>
- Keyboard focus was moved from this widget to another widget.
- <Return>
- The user pressed the Enter key. You can bind to virtually all keys on the keyboard. For an ordinary 102-key PC-style keyboard, the special keys are Cancel (the Break key), BackSpace, Tab, Return(the Enter key), Shift_L (any Shift key), Control_L (any Control key), Alt_L (any Alt key), Pause, Caps_Lock, Escape, Prior (Page Up), Next (Page Down), End, Home, Left, Up, Right, Down, Print, Insert, Delete, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, Num_Lock, and Scroll_Lock.
- <Key>
- The user pressed any key. The key is provided in the char member of the event object passed to the callback (this is an empty string for special keys).
- a
- The user typed an “a”. Most printable characters can be used as is. The exceptions are space (<space>) and less than (<less>). Note that 1 is a keyboard binding, while <1> is a button binding.
- <Shift-Up>
- The user pressed the Up arrow, while holding the Shift key pressed. You can use prefixes like Alt, Shift, and Control.
- <Configure>
- The widget changed size (or location, on some platforms). The new size is provided in the width and height attributes of the event object passed to the callback.
The Event Object
The event object is a standard Python object instance, with a number of attributes describing the event.Event Attributes
- widget
- The widget which generated this event. This is a valid Tkinter widget instance, not a name. This attribute is set for all events.
- x, y
- The current mouse position, in pixels.
- x_root, y_root
- The current mouse position relative to the upper left corner of the screen, in pixels.
- char
- The character code (keyboard events only), as a string.
- keysym
- The key symbol (keyboard events only).
- keycode
- The key code (keyboard events only).
- num
- The button number (mouse button events only).
- width, height
- The new size of the widget, in pixels (Configure events only).
- type
- The event type.
Instance and Class Bindings
The bind method we used in the above example creates an instance binding. This means that the binding applies to a single widget only; if you create new frames, they will not inherit the bindings.But Tkinter also allows you to create bindings on the class and application level; in fact, you can create bindings on four different levels:
- the widget instance, using bind.
- the widget’s toplevel window (Toplevel or root), also using bind.
- the widget class, using bind_class (this is used by Tkinter to provide standard bindings).
- the whole application, using bind_all.
First, on each of these four levels, Tkinter chooses the “closest match” of the available bindings. For example, if you create instance bindings for the <Key> and <Return> events, only the second binding will be called if you press the Enter key.
However, if you add a <Return> binding to the toplevel widget, both bindings will be called. Tkinter first calls the best binding on the instance level, then the best binding on the toplevel window level, then the best binding on the class level (which is often a standard binding), and finally the best available binding on the application level. So in an extreme case, a single event may call four event handlers.
A common cause of confusion is when you try to use bindings to override the default behavior of a standard widget. For example, assume you wish to disable the Enter key in the text widget, so that the users cannot insert newlines into the text. Maybe the following will do the trick?
def ignore(event): pass text.bind("<Return>", ignore)or, if you prefer one-liners:
text.bind("<Return>", lambda e: None)(the lambda function used here takes one argument, and returns None)
Unfortunately, the newline is still inserted, since the above binding applies to the instance level only, and the standard behavior is provided by a class level bindings.
You could use the bind_class method to modify the bindings on the class level, but that would change the behavior of all text widgets in the application. An easier solution is to prevent Tkinter from propagating the event to other handlers; just return the string “break” from your event handler:
def ignore(event): return "break" text.bind("<Return>", ignore)or
text.bind("<Return>", lambda e: "break")By the way, if you really want to change the behavior of all text widgets in your application, here’s how to use the bind_class method:
top.bind_class("Text", "<Return>", lambda e: None)But there are a lot of reasons why you shouldn’t do this. For example, it messes things up completely the day you wish to extend your application with some cool little UI component you downloaded from the net. Better use your own Text widget specialization, and keep Tkinter’s default bindings intact:
class MyText(Text): def __init__(self, master, **kw): apply(Text.__init__, (self, master), kw) self.bind("<Return>", lambda e: "break")
Protocols #
In addition to event bindings, Tkinter also supports a mechanism called protocol handlers. Here, the term protocol refers to the interaction between the application and the window manager. The most commonly used protocol is called WM_DELETE_WINDOW, and is used to define what happens when the user explicitly closes a window using the window manager.You can use the protocol method to install a handler for this protocol (the widget must be a root or Toplevel widget):
widget.protocol("WM_DELETE_WINDOW", handler)
Once you have installed your own handler, Tkinter will no longer
automatically close the window. Instead, you could for example
display a message box asking the user if the current data should be
saved, or in some cases, simply ignore the request. To close the
window from this handler, simply call the destroy method
of the window:
Capturing destroy events
from Tkinter import * import tkMessageBox def callback(): if tkMessageBox.askokcancel("Quit", "Do you really wish to quit?"): root.destroy() root = Tk() root.protocol("WM_DELETE_WINDOW", callback) root.mainloop()
Note that even you don’t register an handler for
WM_DELETE_WINDOW on a toplevel window, the window itself will
be destroyed as usual (in a controlled fashion, unlike X). However, as
of Python 1.5.2, Tkinter will not destroy the corresponding widget
instance hierarchy, so it is a good idea to always register a handler
yourself:
top = Toplevel(...) # make sure widget instances are deleted top.protocol("WM_DELETE_WINDOW", top.destroy)Future versions of Tkinter will most likely do this by default.
No hay comentarios:
Publicar un comentario