Textual Python TUI 框架¶
为什么要学 Textual¶
Textual 是 Python 中功能最强大的 TUI(终端用户界面)框架,由 Rich 库的作者开发。它使用类似 Web 的 CSS 布局系统和组件化架构来构建终端应用,支持按钮、输入框、表格、树形视图、Markdown 渲染、语法高亮等丰富的组件。对于需要在终端中构建复杂交互界面的 Python 开发者来说,Textual 是无可替代的选择。
核心概念¶
| 概念 | 白话解释 | 用途 |
|---|---|---|
| App | 应用 | TUI 应用的根容器 |
| Widget | 组件 | UI 的构建块(按钮、输入框等) |
| Screen | 屏幕 | 全屏视图(可切换) |
| CSS | 样式 | 类似 Web CSS 的样式系统 |
| Binding | 键绑定 | 快捷键到操作的映射 |
| Message | 消息 | 组件间的事件通信 |
安装配置¶
快速上手¶
最简应用¶
from textual.app import App, ComposeResult
from textual.widgets import Header, Footer, Static
class MyApp(App):
CSS = """
Screen {
align: center middle;
}
#hello {
width: 40;
height: 5;
border: solid green;
content-align: center middle;
}
"""
BINDINGS = [("q", "quit", "退出")]
def compose(self) -> ComposeResult:
yield Header()
yield Static("Hello, Textual!", id="hello")
yield Footer()
if __name__ == "__main__":
app = MyApp()
app.run()
使用组件¶
from textual.app import App, ComposeResult
from textual.widgets import Button, Input, Label, Header, Footer
from textual.containers import Horizontal, Vertical
class TodoApp(App):
CSS = """
#input-area { height: 3; }
#todo-list { height: 1fr; border: solid blue; padding: 1; }
Button { margin: 0 1; }
"""
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
with Horizontal(id="input-area"):
yield Input(placeholder="输入待办事项...", id="todo-input")
yield Button("添加", variant="primary", id="add-btn")
yield Vertical(id="todo-list")
yield Footer()
def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "add-btn":
input_widget = self.query_one("#todo-input", Input)
if input_widget.value:
self.query_one("#todo-list").mount(
Label(f"• {input_widget.value}")
)
input_widget.value = ""
def on_input_submitted(self, event: Input.Submitted) -> None:
self.on_button_pressed(Button.Pressed(self.query_one("#add-btn")))
if __name__ == "__main__":
TodoApp().run()
进阶用法¶
CSS 文件分离¶
# app.py
class MyApp(App):
CSS_PATH = "styles.tcss"
# styles.tcss
Screen {
layout: grid;
grid-size: 2 3;
grid-gutter: 1;
}
.panel {
border: solid $accent;
padding: 1;
height: 100%;
}
Button.primary {
background: $primary;
color: $text;
}
Button:hover {
background: $primary-lighten-1;
}
DataTable 数据表格¶
from textual.widgets import DataTable
class TableApp(App):
def compose(self) -> ComposeResult:
yield DataTable()
def on_mount(self) -> None:
table = self.query_one(DataTable)
table.add_columns("名称", "语言", "星数")
table.add_rows([
("Textual", "Python", "25k"),
("Bubble Tea", "Go", "23k"),
("Ratatui", "Rust", "8k"),
])
table.cursor_type = "row"
多屏幕应用¶
from textual.screen import Screen
class MenuScreen(Screen):
def compose(self) -> ComposeResult:
yield Button("开始", id="start")
yield Button("设置", id="settings")
def on_button_pressed(self, event: Button.Pressed):
if event.button.id == "start":
self.app.push_screen(GameScreen())
class GameScreen(Screen):
BINDINGS = [("escape", "app.pop_screen", "返回")]
def compose(self) -> ComposeResult:
yield Static("游戏画面")
class MyApp(App):
def on_mount(self):
self.push_screen(MenuScreen())
异步操作¶
from textual.app import App
from textual.widgets import Static
import httpx
class AsyncApp(App):
def compose(self) -> ComposeResult:
yield Static("加载中...", id="result")
async def on_mount(self) -> None:
self.run_worker(self.fetch_data())
async def fetch_data(self) -> None:
async with httpx.AsyncClient() as client:
response = await client.get("https://api.github.com")
data = response.json()
self.query_one("#result").update(f"GitHub API: {data.get('current_user_url')}")
开发者工具¶
# 实时 CSS 调试
textual run --dev app.py
# 控制台(类似浏览器 DevTools)
# 在另一个终端运行
textual console
# 截图
textual run app.py --screenshot output.svg
常见问题¶
Q: 与 Rich 的关系?¶
Textual 建立在 Rich 之上。Rich 是输出库(美化打印),Textual 是交互式 TUI 框架。
Q: 支持鼠标吗?¶
支持。按钮点击、滚动、拖拽等鼠标操作都支持。
Q: 性能如何?¶
异步架构,可以处理大量数据。DataTable 组件支持虚拟化,百万行也不卡。
Q: 能否发布为独立应用?¶
可以用 PyInstaller 打包为二进制,或使用 pipx 安装。
参考资源¶
- GitHub:https://github.com/Textualize/textual
- 文档:https://textual.textualize.io/
- 组件库:https://textual.textualize.io/widget_gallery/
- CSS 参考:https://textual.textualize.io/css_types/
- 教程:https://textual.textualize.io/tutorial/