Event handling in depth
You have been using evt_* methods since Module 2, but there is more to the event system than registering a handler and reading a value. This lesson covers how events propagate through the widget hierarchy, when and why to use skip, how to bind events in different ways, and how to define your own custom events for communication between parts of your app.
Start fresh
Create event_demo.rb:
|
|
Step 1 — Event propagation
When a widget receives an event, wxRuby3 looks for a handler at each level of the container hierarchy — starting at the widget itself, then moving up through its parent panel, then the frame. Each level gets a chance to respond. This is event propagation.
Replace the skeleton with this complete version, which registers handlers for the same button click at two levels:
|
|
Run it and click the button. Both messages appear — the widget-level handler fires first, calls event.skip to pass the event up, and the container-level handler in bind_events fires next.
Now remove event.skip from the widget-level handler and click again. Only the first message appears — the event is consumed at the widget level and never reaches the container.
Notice that log sets @modified = true and clear_log resets it to false. Any action that produces output is treated as unsaved state — clearing the log resets that state. This sets up Step 2.
In a well-structured app with separate panel classes, the panel is often the right place to handle events from its own widgets. The frame only needs to handle events that cross panel boundaries or affect the whole window. The propagation chain is: widget → containing panel → frame.
Step 2 — event.skip and the close event
wxRuby3 has built-in default behaviour for many events. Close a window and it closes — no handler needed. You only write an evt_close handler when you want to intercept the close and do something first.
event.skip tells wxRuby3 to carry out the default behaviour after your handler runs. Without it, your handler consumes the event and the default never happens.
The on_close handler is already in the app above. Try it:
With unsaved changes: Click the test button (which logs a message and sets @modified = true), then try to close the window. The confirm dialog appears. Choose No — the window stays open. Choose Yes — event.skip is called and the window closes.
Without unsaved changes: Click Clear log (which resets @modified = false), then close. The window closes immediately with no dialog — the else branch calls event.skip directly.
Without any event.skip: Temporarily remove both event.skip calls from on_close and try to close. The window refuses — the close event is consumed by your handler and the default close behaviour never happens. You have to force-quit the app. Restore event.skip before continuing.
event.skip is rarely needed outside evt_close and keyboard handlers. For most events — button clicks, checkbox toggles, menu selections — you handle the event and that is the end of it.
Step 3 — Inline vs method handlers
There are two ways to write a handler — inline in the block, or delegated to a named method.
Handled inline — the response logic lives directly in the block:
|
|
Handled by a method — the block delegates to a named method:
|
|
Both styles are equally valid. The choice comes down to complexity and readability:
- Inline works well for short, self-contained responses — toggling a flag, updating a label, calling one other method. It keeps everything visible at the binding site.
- A named method is better when the handler is longer, when the same method is called from multiple places, or when you want
bind_eventsto read as a clean index of what responds to what without implementation details mixed in.
The method reference symbol style (evt_button(id, :on_click)) is particularly useful when several widgets share the same handler:
|
|
Step 4 — The event object
Every handler receives an event object containing information about what happened. The event type determines what information is available:
|
|
You will not always need the event object — many handlers ignore it entirely and just call a method. But it is available whenever you need it.
Step 5 — Custom events
Custom events let one part of your app signal another without tight coupling. Rather than calling methods directly across class boundaries, you post an event and let the event system deliver it.
Define a custom event type and post it from one place, handle it in another:
|
|
A simpler and more common pattern in practice is to use Wx.call_later or post a standard CommandEvent with a custom ID:
|
|
In practice, the most common use of custom events in this series is for background thread communication — posting an event from a worker thread to notify the UI that work is complete. That pattern is covered in detail in lesson 3.5.
Step 6 — Keyboard events
wxRuby3 provides two keyboard event handlers and the distinction between them matters:
evt_key_down fires when a physical key is pressed. The event gives you the raw key code — Wx::K_ESCAPE, Wx::K_RETURN, Wx::K_F5, or an ASCII code for letter and number keys. It fires for every key press regardless of whether a printable character is produced. Use it for keyboard shortcuts, function keys, Escape, Enter, and arrow keys — anything where you care about which key was pressed.
evt_char fires after evt_key_down, but only for keys that produce a printable character. It gives you the translated character after modifier keys have been applied — pressing Shift+A gives you A (65) in evt_char but a (97) as the raw key code in evt_key_down. It handles platform differences in character input automatically, including non-ASCII input methods. Use it when you care about the character being entered, not the physical key — for example, filtering a list as the user types, or building a custom text input widget.
For most wxRuby3 apps you will use evt_key_down almost exclusively. TextCtrl already handles character input correctly on its own, so evt_char only becomes relevant when you are processing typed characters yourself.
Use evt_key_down for shortcuts and special keys:
|
|
Modifier keys are available as methods on the event object:
|
|
Use evt_char when processing typed characters:
|
|
Always call event.skip for keys your handler does not specifically handle — otherwise you break normal keyboard navigation and text input across the whole window.
Here’s a simple example to demonstrate (keypress_demo.rb):
|
|
What to take forward
The patterns that matter most for the rest of this series:
evt_*on the frame is the default binding location- Always
event.skipinevt_close, and for keyboard events you do not handle - The event object carries the information you need —
event.int,event.string,event.checked? - Custom events (via pending events) are the right way to communicate from background threads to the UI
Previous: Module 2 — Building Blocks | Next: Application structure