16.21. Low-Level Event Processing¶
In the previous lessons you learned how to associate simple user actions with the most common user events, such as clicking on a button. For simple GUI programs this is typically sufficient. But for more complex programs you might need to process “lower level” events, such as recognizing when the user’s cursor is over a widget, or when a widget becomes the focus of user input. To handle these types of events you need to implement event binding. This lesson provides an overview of how to process any event that happens on a computer. We will not use these techniques in most of the simple GUI programs we discuss, but you should be aware of what is possible.
16.22. Focus¶
Remember from our previous discussion that the operating system is in control
of your computer’s screen, keyboard, mouse, and other input devices. When
the user interacts with these input devices the operating system generates
event objects
that capture specific event information. Your computer is running
multiple processes at any given time, so how does
the computer know which process to send an event to? All GUI operating systems
have the concept of an “active window.” Only the “active window” receives events.
We say that the “active window” has the focus of the user. Typically the
application whose window is in front of all other windows on the computer
screen has the operating system’s focus. Only the process with the focus
receives user events; the other running applications do not receive
user events.
The concept of focus goes further than the applications running on a
computer. Inside an application, only one widget at a time can have the
focus of the user. The widget with the focus is the widget that
receives keyboard events. For example, suppose you had a GUI interface
that contained two different Entry
widgets. If the user hits an “a”
key, which widget has an “a” added to their existing text? It is the widget
that currently has the focus.
16.23. Event Binding¶
When your program has the focus it can potentially receive many more
events than it desires to process. For example, you may not care that the
pointing device just moved one pixel to the right. All “low level” user
events are ignored by your program unless you specifically request that
a particular type of event be sent to a specific function in your program.
The task of associating a specific event handler
function with a specific
“low level” event is called binding.
An event is always bound to a specific widget. For example, if you wanted
to know when the cursor of a pointing device has moved over a widget, you would
bind an "<Enter>"
event to the widget and specify a function to call when the
event happens. This is done with the .bind(event_description, event_handler)
method of a widget. Once this bind operation is complete and the
application’s main event loop has started, every time a pointing device is
moved over the widget, the event handler will be called.
Here is an example of binding an "<Enter>"
event to a Button.
def process_event(event):
print("The process_event function was called.")
my_button = tk.Button(application_window, text="Example")
my_button.bind("<Enter>", process_event)
It is best practice to “stub out” event handlers and verify that events are
being processed correctly before you start developing an application’s
actual processing code. Here is an example of a “stubbed” out event handler
:
def my_function():
print("my_function was called.")
16.24. Event Descriptors¶
An event represents an action that needs to be processed. Events are caused by a user when they click in a window or type on the keyboard. Events can also be generated by software, such as a request to redraw the application’s window after changes have been made. In a GUI program, all processing is done by responding to events.
In Tkinter, events are defined as strings using a pre-defined syntax. The general format
of an event description is <modifier-type-detail>
, where the modifier
and detail
portions are optional. For example, a <Button>
event is
generated by any change of state of any mouse button, while a <Shift-Button>
event will only be generated if the mouse state changes while the SHIFT key
on the keyboard is down. And a <Shift-Button-1>
event will only be generated
if the left mouse button (associated with the number 1) changes state.
Below is a list of the most widely used events along with a brief description
of each one. You can add or remove modifier
and detail
values to make
events more or less specific.
Pointer Related Events | Description |
---|---|
"<ButtonPress-1>" |
The left mouse button was pressed. |
"<ButtonPress-2>" |
The middle mouse button was pressed. |
"<ButtonPress-3>" |
The right mouse button was pressed. |
"<B1-Motion>" |
With the left mouse button held down, the mouse changed location. |
"<B2-Motion>" |
With the middle mouse button held down, the mouse changed location. |
"<B3-Motion>" |
With the right mouse button held down, the mouse changed location. |
"<ButtonRelease-1>" |
The left mouse button was released. |
"<Double-Button-1>" |
The left mouse button was double-clicked. |
"<Enter>" |
The mouse pointer just moved over a particular widget. |
"<Leave>" |
The mouse pointer is no longer over a particular widget. |
"<FocusIn>" |
A widget just received the keyboard focus. |
"<FocusOut>" |
A widget just lost the keyboard focus. |
"<Configure>" |
A widget just changed its size or position. |
Keyboard Related Events | Description |
---|---|
"<Key>" |
The user pressed any key on the keyboard. |
"<Return>" |
The user pressed the Enter key. |
"<Backspace>" |
The user pressed the Backspace key. |
"<Tab>" |
The user pressed the Tab key. |
"<Escape>" |
The user pressed the Escape key. |
"<Prior>" |
The user pressed the Page-up key. |
"<Next>" |
The user pressed the Page-down key. |
"<Up>" |
The user pressed the up arrow key. |
"<Down>" |
The user pressed the down arrow key. |
"<Left>" |
The user pressed the left arrow key. |
"<Right>" |
The user pressed the right arrow key. |
"<F1>" |
The user pressed the F1 key. |
"<F2>" |
The user pressed the F2 key. |
"<a>" |
The user pressed the “a” key. |
"<b>" |
The user pressed the “b” key. |
"<c>" |
The user pressed the “c” key. |
"<Shift-Up>" |
The user pressed the up arrow key while the shift key was down. |
"<Alt-Up>" |
The user pressed the up arrow key while the alt key was down. |
"<Control-Up>" |
The user pressed the up arrow key while the control key was down |
etc… |
16.25. Event Objects¶
When a user generates an event, or the software generates an event, an event
object
is created. This object is automatically passed to the function that
is registered to handle the event. Every event handler function that is bound
to an event using the .bind(event_description, function_handler)
function
must be defined to receive one parameter, an event object
.
An event object
contains the following attributes.
Event Object Attribute | Description |
---|---|
.widget |
The widget this event was bound to. This is a reference to a
Tkinter widget instance; it is not a string name. |
.x , .y |
The current mouse position, relative to the application’s window, in pixels. |
.x_root , .y_root |
The current mouse position relative to the upper left corner of the screen, in pixels. |
.char |
For keyboard events only, this is the character code of the key pressed or released as a string. |
.keysym |
For keyboard events only, the key symbol. |
.keycode |
For keyboard events only, the key code (i.e., the key’s Unicode decimal value). |
.num |
For mouse button events only, the button number. |
.width , .height |
For configure events only, the new size of the widget, in pixels. |
A typical event handler will use the values in the event object
it receives
to perform an appropriate action related to the event. For example,
def process_event(event):
if event.x > 10 and event.y > 20:
# do something
16.26. Event Processing¶
The operating system generates events in the order the user or the program creates them. An application’s GUI event loop receives the events in this same order and then calls the appropriate event handler. Therefore, events are processed in the same order they are created.
Events can’t be processed unless the application’s GUI event-loop is running. If an individual event handler takes a long time to process an event, other events will get “queued up” waiting for a chance to be processed. It is considered bad GUI programming for any event handler to take up too much processing time. An event handler should do as little processing as possible to accomplish its intended task and then quit. This returns control of the application back to the event loop.
Events are always associated with a widget. If you want to know every event that happens inside an application’s window, then bind events to the widget that is your application’s window. If you only want to know about button click events on a particular button, then bind an event handler to that specific button. In general, events should be associated with the most specific widget possible.
Some widgets, such as a Notebook that implements a tabbed set of frames, have predefined events that are used to manipulate them. These are called “bind_class” event bindings and they bind certain events to all instances of a particular widget type. In general you should not modify or change these types of event bindings because a user expects a certain behaviour from a particular type of widget and changing that behaviour can make the entire user interface confusing to a user.