textual-event-messages
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTextual Event and Message Handling
Textual 事件与消息处理
Purpose
用途
Implement robust event handling and inter-widget communication in Textual using messages, keyboard bindings, and action dispatch. Messages enable loose coupling between components.
使用消息、键盘绑定和动作分发在Textual中实现可靠的事件处理与组件间通信。消息可实现组件间的松耦合。
Quick Start
快速开始
python
from textual.message import Message
from textual.app import App, ComposeResult
from textual.widgets import Button, Static
from textual import on
class ItemWidget(Static):
"""Widget that emits custom messages."""
class ItemSelected(Message):
"""Posted when item is selected."""
def __init__(self, item_id: str) -> None:
super().__init__()
self.item_id = item_id
class MyApp(App):
def compose(self) -> ComposeResult:
yield ItemWidget()
@on(ItemWidget.ItemSelected)
def on_item_selected(self, message: ItemWidget.ItemSelected) -> None:
"""Handle item selection."""
self.notify(f"Selected: {message.item_id}")python
from textual.message import Message
from textual.app import App, ComposeResult
from textual.widgets import Button, Static
from textual import on
class ItemWidget(Static):
"""Widget that emits custom messages."""
class ItemSelected(Message):
"""Posted when item is selected."""
def __init__(self, item_id: str) -> None:
super().__init__()
self.item_id = item_id
class MyApp(App):
def compose(self) -> ComposeResult:
yield ItemWidget()
@on(ItemWidget.ItemSelected)
def on_item_selected(self, message: ItemWidget.ItemSelected) -> None:
"""Handle item selection."""
self.notify(f"Selected: {message.item_id}")Instructions
操作步骤
Step 1: Define Custom Messages
步骤1:定义自定义消息
Create message classes to communicate between widgets:
python
from textual.message import Message
from dataclasses import dataclass创建消息类以实现组件间通信:
python
from textual.message import Message
from dataclasses import dataclassSimple message without data
Simple message without data
class DataRefreshed(Message):
"""Posted when data is refreshed."""
pass
class DataRefreshed(Message):
"""Posted when data is refreshed."""
pass
Message with data
Message with data
class ItemSelected(Message):
"""Posted when item is selected."""
def __init__(self, item_id: str, index: int) -> None:
"""Initialize message.
Args:
item_id: Selected item ID.
index: Index in list.
"""
super().__init__()
self.item_id = item_id
self.index = indexclass ItemSelected(Message):
"""Posted when item is selected."""
def __init__(self, item_id: str, index: int) -> None:
"""Initialize message.
Args:
item_id: Selected item ID.
index: Index in list.
"""
super().__init__()
self.item_id = item_id
self.index = indexMessage with complex data
Message with complex data
@dataclass(frozen=True)
class SearchResult:
"""Result of search operation."""
query: str
items: list[str]
total_count: int
class SearchCompleted(Message):
"""Posted when search completes."""
def __init__(self, result: SearchResult) -> None:
"""Initialize with search result."""
super().__init__()
self.result = result
**Message Conventions:**
- Subclass `Message`
- Define immutable data in `__init__`
- Use descriptive PascalCase names
- Document the message purpose in docstring
- Use `frozen=True` for dataclasses to ensure immutability@dataclass(frozen=True)
class SearchResult:
"""Result of search operation."""
query: str
items: list[str]
total_count: int
class SearchCompleted(Message):
"""Posted when search completes."""
def __init__(self, result: SearchResult) -> None:
"""Initialize with search result."""
super().__init__()
self.result = result
**消息约定:**
- 继承自`Message`
- 在`__init__`中定义不可变数据
- 使用描述性的大驼峰命名
- 在文档字符串中说明消息用途
- 为数据类使用`frozen=True`以确保不可变性Step 2: Post and Handle Messages
步骤2:发送与处理消息
Post messages from widgets and handle in parents:
python
from textual import on
from textual.widgets import Static, ListView, ListItem
from textual.app import ComposeResult
class ItemWidget(Static):
"""Widget that posts custom messages."""
class ItemClicked(Message):
"""Posted when item is clicked."""
def __init__(self, item_id: str) -> None:
super().__init__()
self.item_id = item_id
def __init__(self, item_id: str, label: str, **kwargs: object) -> None:
super().__init__(**kwargs)
self._item_id = item_id
self._label = label
def render(self) -> str:
return f"[{self._item_id}] {self._label}"
def on_click(self) -> None:
"""Post message when clicked."""
self.post_message(self.ItemClicked(self._item_id))
class ItemListWidget(Static):
"""Parent widget that handles item messages."""
def compose(self) -> ComposeResult:
yield ListView(id="item-list")
async def on_mount(self) -> None:
"""Create items."""
list_view = self.query_one("#item-list", ListView)
for i, item_id in enumerate(["a", "b", "c"]):
await list_view.append(ListItem(
ItemWidget(item_id, f"Item {item_id}")
))
@on(ItemWidget.ItemClicked)
async def on_item_clicked(self, message: ItemWidget.ItemClicked) -> None:
"""Handle item click - called automatically."""
self.notify(f"Clicked: {message.item_id}")从组件发送消息并在父组件中处理:
python
from textual import on
from textual.widgets import Static, ListView, ListItem
from textual.app import ComposeResult
class ItemWidget(Static):
"""Widget that posts custom messages."""
class ItemClicked(Message):
"""Posted when item is clicked."""
def __init__(self, item_id: str) -> None:
super().__init__()
self.item_id = item_id
def __init__(self, item_id: str, label: str, **kwargs: object) -> None:
super().__init__(**kwargs)
self._item_id = item_id
self._label = label
def render(self) -> str:
return f"[{self._item_id}] {self._label}"
def on_click(self) -> None:
"""Post message when clicked."""
self.post_message(self.ItemClicked(self._item_id))
class ItemListWidget(Static):
"""Parent widget that handles item messages."""
def compose(self) -> ComposeResult:
yield ListView(id="item-list")
async def on_mount(self) -> None:
"""Create items."""
list_view = self.query_one("#item-list", ListView)
for i, item_id in enumerate(["a", "b", "c"]):
await list_view.append(ListItem(
ItemWidget(item_id, f"Item {item_id}")
))
@on(ItemWidget.ItemClicked)
async def on_item_clicked(self, message: ItemWidget.ItemClicked) -> None:
"""Handle item click - called automatically."""
self.notify(f"Clicked: {message.item_id}")Alternative handler without @on decorator
Alternative handler without @on decorator
class AltListWidget(Static):
"""Using on_* method naming convention."""
def on_item_widget_item_clicked(self, message: ItemWidget.ItemClicked) -> None:
"""Auto-routed handler.
Convention: on_{widget_class_snake_case}_{message_class_snake_case}
"""
self.notify(f"Clicked: {message.item_id}")
**Message Routing:**
1. Widget posts message: `self.post_message(MyMessage())`
2. Message bubbles up to parent widgets
3. Parent handles with `@on(MessageType)` or `on_*` method
4. First handler to handle the message stops propagation (can call `event.stop()`)class AltListWidget(Static):
"""Using on_* method naming convention."""
def on_item_widget_item_clicked(self, message: ItemWidget.ItemClicked) -> None:
"""Auto-routed handler.
Convention: on_{widget_class_snake_case}_{message_class_snake_case}
"""
self.notify(f"Clicked: {message.item_id}")
**消息路由:**
1. 组件发送消息:`self.post_message(MyMessage())`
2. 消息向上冒泡至父组件
3. 父组件通过`@on(MessageType)`或`on_*`方法处理
4. 第一个处理消息的处理器会停止消息传播(可调用`event.stop()`)Step 3: Implement Keyboard Event Handlers
步骤3:实现键盘事件处理器
Handle keyboard input directly:
python
from textual.events import Key, Paste
from textual.widgets import Static
class KeyboardWidget(Static):
"""Widget handling keyboard events."""
def on_key(self, event: Key) -> None:
"""Called when any key is pressed.
Args:
event: Key event with key name.
"""
key_name = event.key
# Common keys: "up", "down", "left", "right", "enter", "escape"
# Letters: "a", "b", "ctrl+a", "shift+a"
if key_name == "enter":
self.handle_enter()
elif key_name == "escape":
self.handle_escape()
elif key_name == "ctrl+c":
self.app.exit()
def handle_enter(self) -> None:
"""Handle Enter key."""
self.update("Enter pressed")
def handle_escape(self) -> None:
"""Handle Escape key."""
self.update("Escape pressed")
def on_paste(self, event: Paste) -> None:
"""Called when text is pasted.
Args:
event: Paste event with text.
"""
self.update(f"Pasted: {event.text}")Key Event Handling:
- called for keyboard events
on_key() - Common keys: arrow keys, enter, escape, tab, delete
- Modifier keys: "ctrl+key", "shift+key", "alt+key"
- Special: "home", "end", "page_up", "page_down", "F1"-"F12"
直接处理键盘输入:
python
from textual.events import Key, Paste
from textual.widgets import Static
class KeyboardWidget(Static):
"""Widget handling keyboard events."""
def on_key(self, event: Key) -> None:
"""Called when any key is pressed.
Args:
event: Key event with key name.
"""
key_name = event.key
# Common keys: "up", "down", "left", "right", "enter", "escape"
# Letters: "a", "b", "ctrl+a", "shift+a"
if key_name == "enter":
self.handle_enter()
elif key_name == "escape":
self.handle_escape()
elif key_name == "ctrl+c":
self.app.exit()
def handle_enter(self) -> None:
"""Handle Enter key."""
self.update("Enter pressed")
def handle_escape(self) -> None:
"""Handle Escape key."""
self.update("Escape pressed")
def on_paste(self, event: Paste) -> None:
"""Called when text is pasted.
Args:
event: Paste event with text.
"""
self.update(f"Pasted: {event.text}")键盘事件处理:
- 会响应所有键盘事件
on_key() - 常见按键:方向键、回车键、ESC键、Tab键、删除键
- 修饰键组合:"ctrl+key"、"shift+key"、"alt+key"
- 特殊按键:"home"、"end"、"page_up"、"page_down"、"F1"至"F12"
Step 4: Add Keyboard Bindings and Actions
步骤4:添加键盘绑定与动作
Bindings provide discoverable keyboard shortcuts tied to actions:
python
from textual.app import App, ComposeResult
from textual.binding import Binding
from typing import ClassVar
class MyApp(App):
"""App with keyboard bindings."""
BINDINGS: ClassVar[list[Binding]] = [
# (key, action, description, show, priority)
Binding("q", "quit", "Quit", show=True, priority=True),
Binding("ctrl+c", "quit", "Quit", show=False, priority=True),
Binding("r", "refresh", "Refresh"),
Binding("n", "new_item", "New"),
Binding("?", "show_help", "Help"),
]
def compose(self) -> ComposeResult:
yield Header()
yield Static("Content")
yield Footer() # Shows bindings
def action_refresh(self) -> None:
"""Action handler for 'refresh' binding."""
self.notify("Refreshed")
def action_new_item(self) -> None:
"""Action handler for 'new_item' binding."""
self.notify("Creating new item")
async def action_show_help(self) -> None:
"""Action handlers can be async."""
# Show help dialog
passBinding Conventions:
- Binding key to action method name
- Action method:
action_{action_name} - Action methods can be sync or async
- Bindings shown in Footer if
show=True - for high-priority bindings (quit)
priority=True - Bindings override key event handlers when matched
绑定功能可将可发现的键盘快捷键与动作关联:
python
from textual.app import App, ComposeResult
from textual.binding import Binding
from typing import ClassVar
class MyApp(App):
"""App with keyboard bindings."""
BINDINGS: ClassVar[list[Binding]] = [
# (key, action, description, show, priority)
Binding("q", "quit", "Quit", show=True, priority=True),
Binding("ctrl+c", "quit", "Quit", show=False, priority=True),
Binding("r", "refresh", "Refresh"),
Binding("n", "new_item", "New"),
Binding("?", "show_help", "Help"),
]
def compose(self) -> ComposeResult:
yield Header()
yield Static("Content")
yield Footer() # Shows bindings
def action_refresh(self) -> None:
"""Action handler for 'refresh' binding."""
self.notify("Refreshed")
def action_new_item(self) -> None:
"""Action handler for 'new_item' binding."""
self.notify("Creating new item")
async def action_show_help(self) -> None:
"""Action handlers can be async."""
# Show help dialog
pass绑定约定:
- 将按键与动作方法名绑定
- 动作方法命名格式:
action_{action_name} - 动作方法可以是同步或异步的
- 如果,绑定会在Footer中显示
show=True - 为高优先级绑定(如退出)设置
priority=True - 匹配到绑定时,绑定会覆盖键盘事件处理器
Step 5: Handle Mouse Events
步骤5:处理鼠标事件
React to mouse clicks and movements:
python
from textual.events import MouseDown, MouseUp, MouseMove
from textual.widgets import Static
class MouseWidget(Static):
"""Widget handling mouse events."""
def on_mouse_down(self, event: MouseDown) -> None:
"""Called when mouse button pressed.
Args:
event: MouseDown event with position and button.
"""
x, y = event.x, event.y
button = event.button # 1: left, 2: middle, 3: right
if button == 1: # Left click
self.handle_click(x, y)
def on_mouse_up(self, event: MouseUp) -> None:
"""Called when mouse button released."""
pass
def on_mouse_move(self, event: MouseMove) -> None:
"""Called when mouse moves over widget."""
x, y = event.x, event.y
# Update display or state based on position
def handle_click(self, x: int, y: int) -> None:
"""Handle click at position."""
self.update(f"Clicked at ({x}, {y})")Mouse Events:
- - Button pressed
on_mouse_down() - - Button released
on_mouse_up() - - Mouse moved
on_mouse_move() - - Single click (widget-specific)
on_click() - - Double click (widget-specific)
on_double_click()
响应鼠标点击与移动:
python
from textual.events import MouseDown, MouseUp, MouseMove
from textual.widgets import Static
class MouseWidget(Static):
"""Widget handling mouse events."""
def on_mouse_down(self, event: MouseDown) -> None:
"""Called when mouse button pressed.
Args:
event: MouseDown event with position and button.
"""
x, y = event.x, event.y
button = event.button # 1: left, 2: middle, 3: right
if button == 1: # Left click
self.handle_click(x, y)
def on_mouse_up(self, event: MouseUp) -> None:
"""Called when mouse button released."""
pass
def on_mouse_move(self, event: MouseMove) -> None:
"""Called when mouse moves over widget."""
x, y = event.x, event.y
# Update display or state based on position
def handle_click(self, x: int, y: int) -> None:
"""Handle click at position."""
self.update(f"Clicked at ({x}, {y})")鼠标事件:
- - 鼠标按键按下
on_mouse_down() - - 鼠标按键释放
on_mouse_up() - - 鼠标移动
on_mouse_move() - - 单击(组件专属)
on_click() - - 双击(组件专属)
on_double_click()
Step 6: Control Event Flow
步骤6:控制事件流
Stop event propagation and bubble events:
python
from textual.events import Key
from textual.app import ComposeResult
from textual.widgets import Static, Container
class StopPropagationWidget(Static):
"""Widget that stops event propagation."""
def on_key(self, event: Key) -> None:
"""Handle key and stop propagation."""
if event.key == "enter":
self.handle_enter()
event.stop() # Stop parent from handling
# If not handled, event propagates to parent
def handle_enter(self) -> None:
"""Handle enter key."""
self.update("Handled locally")
class EventBubblingExample(Container):
"""Demonstrate event bubbling."""
def compose(self) -> ComposeResult:
yield StopPropagationWidget(id="inner")
def on_key(self, event: Key) -> None:
"""Parent key handler."""
# Called if child doesn't call event.stop()
passEvent Control:
- - Stop propagation to parent
event.stop() - - Prevent default behavior
event.prevent_default() - Messages bubble up automatically
- Key events can be stopped
停止事件传播与事件冒泡:
python
from textual.events import Key
from textual.app import ComposeResult
from textual.widgets import Static, Container
class StopPropagationWidget(Static):
"""Widget that stops event propagation."""
def on_key(self, event: Key) -> None:
"""Handle key and stop propagation."""
if event.key == "enter":
self.handle_enter()
event.stop() # Stop parent from handling
# If not handled, event propagates to parent
def handle_enter(self) -> None:
"""Handle enter key."""
self.update("Handled locally")
class EventBubblingExample(Container):
"""Demonstrate event bubbling."""
def compose(self) -> ComposeResult:
yield StopPropagationWidget(id="inner")
def on_key(self, event: Key) -> None:
"""Parent key handler."""
# Called if child doesn't call event.stop()
pass事件控制:
- - 停止向父组件传播事件
event.stop() - - 阻止默认行为
event.prevent_default() - 消息会自动向上冒泡
- 键盘事件可被停止传播
Examples
示例
Example 1: Agent Command Widget with Custom Messages
示例1:带自定义消息的Agent命令组件
python
from textual.message import Message
from textual.widgets import Static, Input, Button
from textual.containers import Container, Horizontal
from textual.app import ComposeResult
from textual import on
class CommandWidget(Container):
"""Widget for entering agent commands."""
class CommandSubmitted(Message):
"""Posted when command is submitted."""
def __init__(self, agent_id: str, command: str) -> None:
super().__init__()
self.agent_id = agent_id
self.command = command
class CommandCancelled(Message):
"""Posted when command is cancelled."""
def __init__(self, agent_id: str) -> None:
super().__init__()
self.agent_id = agent_id
def __init__(self, agent_id: str, **kwargs: object) -> None:
super().__init__(**kwargs)
self._agent_id = agent_id
def compose(self) -> ComposeResult:
"""Compose command input widget."""
with Horizontal():
yield Input(
placeholder="Enter command...",
id="command-input",
)
yield Button("Send", id="btn-send", variant="primary")
yield Button("Clear", id="btn-clear")
async def on_button_pressed(self, event: Button.Pressed) -> None:
"""Handle button press."""
if event.button.id == "btn-send":
await self._submit_command()
elif event.button.id == "btn-clear":
await self._cancel_command()
async def _submit_command(self) -> None:
"""Submit command to agent."""
input_field = self.query_one("#command-input", Input)
command = input_field.value.strip()
if command:
input_field.value = ""
self.post_message(self.CommandSubmitted(self._agent_id, command))
async def _cancel_command(self) -> None:
"""Cancel command entry."""
input_field = self.query_one("#command-input", Input)
input_field.value = ""
self.post_message(self.CommandCancelled(self._agent_id))python
from textual.message import Message
from textual.widgets import Static, Input, Button
from textual.containers import Container, Horizontal
from textual.app import ComposeResult
from textual import on
class CommandWidget(Container):
"""Widget for entering agent commands."""
class CommandSubmitted(Message):
"""Posted when command is submitted."""
def __init__(self, agent_id: str, command: str) -> None:
super().__init__()
self.agent_id = agent_id
self.command = command
class CommandCancelled(Message):
"""Posted when command is cancelled."""
def __init__(self, agent_id: str) -> None:
super().__init__()
self.agent_id = agent_id
def __init__(self, agent_id: str, **kwargs: object) -> None:
super().__init__(**kwargs)
self._agent_id = agent_id
def compose(self) -> ComposeResult:
"""Compose command input widget."""
with Horizontal():
yield Input(
placeholder="Enter command...",
id="command-input",
)
yield Button("Send", id="btn-send", variant="primary")
yield Button("Clear", id="btn-clear")
async def on_button_pressed(self, event: Button.Pressed) -> None:
"""Handle button press."""
if event.button.id == "btn-send":
await self._submit_command()
elif event.button.id == "btn-clear":
await self._cancel_command()
async def _submit_command(self) -> None:
"""Submit command to agent."""
input_field = self.query_one("#command-input", Input)
command = input_field.value.strip()
if command:
input_field.value = ""
self.post_message(self.CommandSubmitted(self._agent_id, command))
async def _cancel_command(self) -> None:
"""Cancel command entry."""
input_field = self.query_one("#command-input", Input)
input_field.value = ""
self.post_message(self.CommandCancelled(self._agent_id))Parent handling messages
Parent handling messages
class AgentConsoleWidget(Static):
"""Console displaying agent commands and responses."""
def compose(self) -> ComposeResult:
yield Static("Agent Console", classes="header")
yield Static("Ready", id="console-output")
yield CommandWidget("agent-1", id="command-widget")
@on(CommandWidget.CommandSubmitted)
async def on_command_submitted(self, message: CommandWidget.CommandSubmitted) -> None:
"""Handle command submission."""
output = self.query_one("#console-output", Static)
output.update(f"Executing: {message.command}\n")
self.notify(f"Command sent to {message.agent_id}")
@on(CommandWidget.CommandCancelled)
async def on_command_cancelled(self, message: CommandWidget.CommandCancelled) -> None:
"""Handle command cancellation."""
output = self.query_one("#console-output", Static)
output.update("Command cancelled\n")undefinedclass AgentConsoleWidget(Static):
"""Console displaying agent commands and responses."""
def compose(self) -> ComposeResult:
yield Static("Agent Console", classes="header")
yield Static("Ready", id="console-output")
yield CommandWidget("agent-1", id="command-widget")
@on(CommandWidget.CommandSubmitted)
async def on_command_submitted(self, message: CommandWidget.CommandSubmitted) -> None:
"""Handle command submission."""
output = self.query_one("#console-output", Static)
output.update(f"Executing: {message.command}\n")
self.notify(f"Command sent to {message.agent_id}")
@on(CommandWidget.CommandCancelled)
async def on_command_cancelled(self, message: CommandWidget.CommandCancelled) -> None:
"""Handle command cancellation."""
output = self.query_one("#console-output", Static)
output.update("Command cancelled\n")undefinedExample 2: Keyboard Navigation in List
示例2:列表中的键盘导航
python
from textual.widgets import Static, ListView, ListItem
from textual.events import Key
from textual.app import ComposeResult
class NavigableListWidget(Static):
"""List with keyboard navigation."""
DEFAULT_CSS = """
NavigableListWidget {
height: 100%;
}
NavigableListWidget ListView {
height: 1fr;
}
NavigableListWidget .highlight {
background: $boost;
}
"""
def compose(self) -> ComposeResult:
yield ListView(id="item-list")
async def on_mount(self) -> None:
"""Populate list."""
list_view = self.query_one("#item-list", ListView)
for i in range(10):
await list_view.append(
ListItem(Static(f"Item {i}"))
)
def on_key(self, event: Key) -> None:
"""Handle keyboard navigation."""
list_view = self.query_one("#item-list", ListView)
if event.key == "up":
if list_view.index is not None and list_view.index > 0:
list_view.index -= 1
event.stop()
elif event.key == "down":
if list_view.index is not None and list_view.index < len(list_view.children) - 1:
list_view.index += 1
event.stop()
elif event.key == "home":
list_view.index = 0
event.stop()
elif event.key == "end":
list_view.index = len(list_view.children) - 1
event.stop()python
from textual.widgets import Static, ListView, ListItem
from textual.events import Key
from textual.app import ComposeResult
class NavigableListWidget(Static):
"""List with keyboard navigation."""
DEFAULT_CSS = """
NavigableListWidget {
height: 100%;
}
NavigableListWidget ListView {
height: 1fr;
}
NavigableListWidget .highlight {
background: $boost;
}
"""
def compose(self) -> ComposeResult:
yield ListView(id="item-list")
async def on_mount(self) -> None:
"""Populate list."""
list_view = self.query_one("#item-list", ListView)
for i in range(10):
await list_view.append(
ListItem(Static(f"Item {i}"))
)
def on_key(self, event: Key) -> None:
"""Handle keyboard navigation."""
list_view = self.query_one("#item-list", ListView)
if event.key == "up":
if list_view.index is not None and list_view.index > 0:
list_view.index -= 1
event.stop()
elif event.key == "down":
if list_view.index is not None and list_view.index < len(list_view.children) - 1:
list_view.index += 1
event.stop()
elif event.key == "home":
list_view.index = 0
event.stop()
elif event.key == "end":
list_view.index = len(list_view.children) - 1
event.stop()Requirements
依赖要求
- Textual >= 0.45.0
- Python 3.9+
- Textual >= 0.45.0
- Python 3.9+
Common Patterns
常见模式
Modal Message Handling
模态消息处理
python
class ConfirmDialog(Screen):
"""Modal dialog that returns a choice."""
def __init__(self, prompt: str, **kwargs: object) -> None:
super().__init__(**kwargs)
self._prompt = prompt
def action_confirm(self) -> None:
"""Confirm and return True."""
self.app.pop_screen(result=True)
def action_cancel(self) -> None:
"""Cancel and return False."""
self.app.pop_screen(result=False)python
class ConfirmDialog(Screen):
"""Modal dialog that returns a choice."""
def __init__(self, prompt: str, **kwargs: object) -> None:
super().__init__(**kwargs)
self._prompt = prompt
def action_confirm(self) -> None:
"""Confirm and return True."""
self.app.pop_screen(result=True)
def action_cancel(self) -> None:
"""Cancel and return False."""
self.app.pop_screen(result=False)Usage
Usage
async def show_confirm() -> bool:
"""Show confirmation dialog."""
result = await self.app.push_screen_wait(
ConfirmDialog("Are you sure?")
)
return result
undefinedasync def show_confirm() -> bool:
"""Show confirmation dialog."""
result = await self.app.push_screen_wait(
ConfirmDialog("Are you sure?")
)
return result
undefinedEvent Debouncing
事件防抖
python
import asyncio
from textual.widgets import Input
class DebouncedInput(Input):
"""Input with debounced on_change events."""
class ValueChanged(Message):
def __init__(self, value: str) -> None:
super().__init__()
self.value = value
def __init__(self, **kwargs: object) -> None:
super().__init__(**kwargs)
self._debounce_timer: asyncio.Task | None = None
async def on_input_changed(self, event: Input.Changed) -> None:
"""Handle input change with debounce."""
# Cancel previous timer
if self._debounce_timer:
self._debounce_timer.cancel()
# Schedule new timer
self._debounce_timer = asyncio.create_task(self._emit_change(event.value))
async def _emit_change(self, value: str) -> None:
"""Emit change after delay."""
await asyncio.sleep(0.5) # 500ms debounce
self.post_message(self.ValueChanged(value))python
import asyncio
from textual.widgets import Input
class DebouncedInput(Input):
"""Input with debounced on_change events."""
class ValueChanged(Message):
def __init__(self, value: str) -> None:
super().__init__()
self.value = value
def __init__(self, **kwargs: object) -> None:
super().__init__(**kwargs)
self._debounce_timer: asyncio.Task | None = None
async def on_input_changed(self, event: Input.Changed) -> None:
"""Handle input change with debounce."""
# Cancel previous timer
if self._debounce_timer:
self._debounce_timer.cancel()
# Schedule new timer
self._debounce_timer = asyncio.create_task(self._emit_change(event.value))
async def _emit_change(self, value: str) -> None:
"""Emit change after delay."""
await asyncio.sleep(0.5) # 500ms debounce
self.post_message(self.ValueChanged(value))See Also
相关链接
- textual-app-lifecycle.md - Keyboard bindings and actions
- textual-widget-development.md - Widget lifecycle
- textual-app-lifecycle.md - 键盘绑定与动作
- textual-widget-development.md - 组件生命周期