Event Handling

Tk, as with most other user interface toolkits, runs an event loop that receives events from the operating system. These are things like button presses, keystrokes, mouse movement, window resizing, and so on.

Generally, Tk takes care of managing this event loop for you. It will figure out what widget the event applies to (did a user click on this button? if a key was pressed, which textbox had the focus?), and dispatch it accordingly. Individual widgets know how to respond to events; for example, a button might change color when the mouse moves over it, and revert back when the mouse leaves.

It's critical in event-driven applications that the event loop not be blocked. The event loop should run continuously, normally executing dozens of steps per second. At every step, it processes an event. If your program is performing a long operation, it can potentially block the event loop. In that case, no events would be processed, no drawing would be done, and it would appear as if your application is frozen. There are many ways to avoid this happening, mostly related to the structure of your application. We'll discuss this in more detail in a later chapter.

Command Callbacks

You often want your program to handle some event in a particular way, e.g., do something when a button is pushed. For those events that are most frequently customized (what good is a button without something happening when you press it?), the widget will allow you to specify a callback as a widget configuration option. We saw this in the example with the command option of the button.


#![allow(unused)]
fn main() {
#[proc] fn calculate() { /* omitted */ }

content.add_ttk_button( ".c.calc" -text("Calculate") -command("calculate") )?;
}

Binding to Events

For events that don't have a widget-specific command callback associated with them, you can use Tk's bind to capture any event, and then (like with callbacks) execute an arbitrary piece of code.

Here's a (silly) example showing a label responding to different events. When an event occurs, a description of the event is displayed in the label.

// cargo run --example binding_to_events

use tcl::*;
use tk::*;
use tk::cmd::*;

fn main() -> TkResult<()> {
    let tk = make_tk!()?;

    let l = tk.root().add_ttk_label( "l" -text("Starting...") )?.grid(())?;

    l.bind( event::enter(), tclosure!( tk, || l.configure( -text("Moved mouse inside") )))?;

    l.bind( event::leave(), tclosure!( tk, || l.configure( -text("Moved mouse outside") )))?;

    l.bind( event::button_press_1(), tclosure!( tk, || l.configure( -text("Clicked left mouse button") )))?;

    l.bind( event::button_press_3(), tclosure!( tk, || l.configure( -text("Clicked right mouse button") )))?;

    l.bind( event::double().button_press_1(), tclosure!( tk, || l.configure( -text("Double clicked") )))?;

    l.bind( event::button_3().motion(), tclosure!( tk, |evt_rootx, evt_rooty| -> TkResult<()> {
        Ok( l.configure( -text( format!( "right button drag to {evt_rootx} {evt_rooty}" )))? )
    }))?;

    Ok( main_loop() )
}

The first two bindings are pretty straightforward, just watching for simple events. An event::enter() event means the mouse has moved over top the widget, while the event::Leave() event is generated when the mouse moves outside the widget to a different one.

The next binding looks for a mouse click, specifically a event::button_press_1 event. Here, the button_press is the actual event, but the _1 is an event detail specifying the left (main) mouse button on the mouse. The binding will only trigger when a button_press event is generated involving the main mouse button. If another mouse button was clicked, this binding would ignore it.

This next binding looks for a event::button_press_3 event. It will respond to events generated when the right mouse button is clicked. The next binding, event::double().button_press_1() adds another modifier, Double, and so will respond to the left mouse button being double clicked.

The last binding also uses a modifier: capture mouse movement (Motion), but only when the right mouse button button_3 is held down. This binding also shows an example of how to use event parameters. Many events, such as mouse clicks or movement carry additional information like the current position of the mouse. Tk provides access to these parameters in Tcl callback scripts through the use of percent substitutions. These percent substitutions let you capture them so they can be used in your script.

Multiple Bindings for an Event

We've just seen how event bindings can be set up for an individual widget. When a matching event is received by that widget, the binding will trigger. But that's not all you can do.

Your binding can capture not just a single event, but a short sequence of events. The event::double().button_press_1() binding triggers when two mouse clicks occur in a short time. You can do the same thing to capture two keys pressed in a row, e.g., key_press( TkKey::A ).key_press( TkKey::B ).

You can also set up an event binding on a toplevel window. When a matching event occurs anywhere in that window, the binding will be triggered. In our example, we set up a binding for the Return key on the main application toplevel window. If the Return key was pressed when any widget in the toplevel window had the focus, that binding would fire.

Less commonly, you can create event bindings that are triggered when a matching event occurs anywhere in the application, or even for events received by any widget of a given class, e.g., all buttons.

More than one binding can fire for an event. This keeps event handlers concise and limited in scope, meaning more modular code. For example, the behavior of each widget class in Tk is itself defined with script-level event bindings. These stay separate from event bindings in your application. Event bindings can also be changed or deleted. They can be modified to alter event handling for widgets of a certain class or parts of your application. You can reorder, extend, or change the sequence of event bindings that will be triggered for each widget; see the bindtags command reference if you're curious.

Available Events

The most commonly used events are described below, along with the circumstances when they are generated. Some are generated on some platforms and not others. For a complete description of all the different event names, modifiers, and the different event parameters that are available with each, the best place to look is the bind command reference.

event namedescription
activateWindow has become active.
deactivateWindow has been deactivated.
mouse_wheelScroll wheel on mouse has been moved.
key_pressKey on keyboard has been pressed down.
key_releaseKey has been released.
button_pressA mouse button has been pressed.
button_releaseA mouse button has been released.
motionMouse has been moved.
configureWidget has changed size or position.
destroyWidget is being destroyed.
focus_inWidget has been given keyboard focus.
focus_outWidget has lost keyboard focus.
enterMouse pointer enters widget.
leaveMouse pointer leaves widget.

Event detail for mouse events are the button that was pressed, e.g. 1, 2, or 3. For keyboard events, it's the specific key, e.g. A, 9, space, plus, comma, equal. A complete list can be found in the keysyms command reference.

Event modifiers for include, e.g. button_1 to signify the main mouse button being held down, double or triple for sequences of the same event. Key modifiers for when keys on the keyboard are held down inline control, shift, alt, option, and command.

Virtual Events

The events we've seen so far are low-level operating system events like mouse clicks and window resizes. Many widgets also generate higher level or semantic events called virtual events. These are indicated by event::virtual_event(), e.g., event::virtual_event( "foo" ).

For example, a listbox widget will generate a event::listbox_select() virtual event whenever its selection changes. The same virtual event is generated whether a user clicked on an item, moved to it using the arrow keys, or some other way. Virtual events avoid the problem of setting up multiple, possibly platform-specific event bindings to capture common changes. The available virtual events for a widget will be listed in the documentation for the widget class.

Tk also defines virtual events for common operations that are triggered in different ways for different platforms. These include event::cut(), event::copy() and event::paste().

You can define your own virtual events, which can be specific to your application. This can be a useful way to keep platform-specific details isolated in a single module, while you use the virtual event throughout your application. Your own code can generate virtual events that work in exactly the same way that virtual events generated by Tk do.


#![allow(unused)]
fn main() {
root.event_generate( event::virtual_event( "MyOwnEvent" ))?;
}