14.20. 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.

14.21. 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.

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

14.23. 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…  

14.24. 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

14.25. 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.

Next Section - 14.26. The Design of GUI Programs