Files
StreamLens/Documentation/textual/events.md
noisedestroyers 36a576dc2c Enhanced Textual TUI with proper API usage and documentation
- Fixed DataTable row selection and event handling
- Added explicit column keys to prevent auto-generated keys
- Implemented row-to-flow mapping for reliable selection tracking
- Converted left metrics panel to horizontal top bar
- Fixed all missing FlowStats/EnhancedAnalysisData attributes
- Created comprehensive Textual API documentation in Documentation/textual/
- Added validation checklist to prevent future API mismatches
- Preserved cursor position during data refreshes
- Fixed RowKey type handling and event names

The TUI now properly handles flow selection, displays metrics in a compact top bar,
and correctly correlates selected rows with the details pane.
2025-07-27 18:37:55 -04:00

4.7 KiB

Textual Events and Messages Reference

Overview

Textual uses an event-driven architecture with two main types of communication:

  1. Events - User interactions (keyboard, mouse)
  2. Messages - Widget-to-widget communication

Event Handler Patterns

Basic Pattern

def on_<namespace>_<event_name>(self, event: EventType) -> None:
    """Handle event"""

Examples

# Widget events
def on_button_pressed(self, event: Button.Pressed) -> None:
    pass

# DataTable events
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
    pass

# Key events
def on_key(self, event: events.Key) -> None:
    if event.key == "escape":
        self.exit()

Common Widget Events

DataTable Events

Event Handler Description
RowHighlighted on_data_table_row_highlighted Cursor moves to row
RowSelected on_data_table_row_selected Row selected (Enter)
CellHighlighted on_data_table_cell_highlighted Cursor moves to cell
CellSelected on_data_table_cell_selected Cell selected
ColumnHighlighted on_data_table_column_highlighted Column highlighted
HeaderSelected on_data_table_header_selected Header clicked

Button Events

Event Handler Description
Pressed on_button_pressed Button clicked/activated

Input Events

Event Handler Description
Changed on_input_changed Text changed
Submitted on_input_submitted Enter pressed

Custom Messages

Creating a Custom Message

from textual.message import Message

class MyWidget(Widget):
    class DataUpdated(Message):
        """Custom message when data updates"""
        def __init__(self, data: dict) -> None:
            self.data = data
            super().__init__()
    
    def update_data(self, new_data: dict) -> None:
        # Post custom message
        self.post_message(self.DataUpdated(new_data))

Handling Custom Messages

class MyApp(App):
    def on_my_widget_data_updated(self, event: MyWidget.DataUpdated) -> None:
        """Handle custom message"""
        self.process_data(event.data)

Event Properties

Common Event Attributes

# All events have:
event.stop()  # Stop propagation
event.prevent_default()  # Prevent default behavior

# Keyboard events
event.key  # Key name ("a", "ctrl+c", "escape")
event.character  # Unicode character
event.aliases  # List of key aliases

# Mouse events  
event.x, event.y  # Coordinates
event.button  # Mouse button number
event.shift, event.ctrl, event.alt  # Modifier keys

# DataTable events
event.row_key  # Row identifier
event.row_index  # Row number
event.coordinate  # (row, column) tuple

Event Flow

  1. Capture Phase: Event travels down from App to target
  2. Bubble Phase: Event travels up from target to App
# Stop event propagation
def on_key(self, event: events.Key) -> None:
    if event.key == "q":
        event.stop()  # Don't let parent widgets see this

Message Posting

Post to Self

self.post_message(MyMessage())

Post to Parent

self.post_message_no_wait(MyMessage())  # Don't wait for processing

Post to Specific Widget

target_widget.post_message(MyMessage())

Event Debugging

Log All Events

def on_event(self, event: events.Event) -> None:
    """Log all events for debugging"""
    self.log(f"Event: {event.__class__.__name__}")

Check Handler Names

# List all event handlers
handlers = [m for m in dir(self) if m.startswith('on_')]

Common Patterns

Debouncing Events

from textual.timer import Timer

class MyWidget(Widget):
    def __init__(self):
        self._update_timer: Timer | None = None
    
    def on_input_changed(self, event: Input.Changed) -> None:
        # Cancel previous timer
        if self._update_timer:
            self._update_timer.cancel()
        
        # Set new timer
        self._update_timer = self.set_timer(0.3, self.perform_update)

Event Validation

def on_data_table_row_highlighted(self, event: DataTable.RowHighlighted) -> None:
    # Validate event data
    if not event.row_key:
        return
    
    # Convert types if needed
    row_key_str = str(event.row_key)

Common Mistakes

  1. Wrong handler name: on_data_table_cursor_moved on_data_table_row_highlighted
  2. Missing namespace: on_row_selected on_data_table_row_selected
  3. Wrong event class: DataTable.CursorMoved DataTable.RowHighlighted
  4. Not converting types: Assuming row_key is string when it's RowKey type