- 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.
4.7 KiB
4.7 KiB
Textual Events and Messages Reference
Overview
Textual uses an event-driven architecture with two main types of communication:
- Events - User interactions (keyboard, mouse)
- 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
- Capture Phase: Event travels down from App to target
- 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
- Wrong handler name:
on_data_table_cursor_moved❌ →on_data_table_row_highlighted✅ - Missing namespace:
on_row_selected❌ →on_data_table_row_selected✅ - Wrong event class:
DataTable.CursorMoved❌ →DataTable.RowHighlighted✅ - Not converting types: Assuming
row_keyis string when it'sRowKeytype