Textual OptionList 组件完全指南:可导航选项列表的构建与交互
Textual OptionList 组件完全指南可导航选项列表的构建与交互【免费下载链接】textualThe lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser.项目地址: https://gitcode.com/gh_mirrors/te/textual本文面向使用 Python 终端 UI 框架 Textual 的开发者系统讲解OptionList组件的完整用法从最基础的字符串选项列表、带 ID 与禁用状态的Option实例到以任意 Rich renderable如表格作为选项条目的高级用法并深入解析其响应式属性、事件消息、按键绑定、组件类与常用 API同时结合仓库源码与测试用例说明底层实现原理。读完本文你将能独立构建一个可键盘导航、可鼠标点选、可动态增删改的垂直选项列表。OptionList是 Textual 在0.17.0版本引入的组件用于展示一个垂直排列的、可导航的选项列表。它属于可聚焦Focusable组件文档标记为[x] Focusable但不是容器Container——它继承自ScrollView见 源码专门用于单选场景例如菜单、命令选择、列表选择等。与同为列表类组件的ListView相比OptionList的显著特点是每个选项的提示内容prompt可以是任意 Rich renderableRich 渲染对象因此选项的高度可以任意——这为构建富文本菜单提供了极大的灵活性。三种构建选项的方式1. 简单字符串选项构造OptionList时最简单的做法是直接传入一串字符串每个字符串会自动成为一个选项from textual.app import App, ComposeResult from textual.widgets import Footer, Header, OptionList class OptionListApp(App[None]): CSS_PATH option_list.tcss def compose(self) - ComposeResult: yield Header() yield OptionList( Aerilon, Aquaria, Canceron, Caprica, Gemenon, Leonis, Libran, Picon, Sagittaron, Scorpia, Tauron, Virgon, ) yield Footer() if __name__ __main__: OptionListApp().run()完整示例见 docs/examples/widgets/option_list_strings.py。对应的样式文件 option_list.tcss 将选项列表居中放置Screen { align: center middle; } OptionList { width: 70%; height: 80%; }2. 使用Option实例与分隔线当需要更精细的控制——例如为选项设置 ID、设置初始禁用状态——应使用Option类。此外在选项序列中插入None即可在前后选项之间绘制一条分隔线from textual.app import App, ComposeResult from textual.widgets import Footer, Header, OptionList from textual.widgets.option_list import Option class OptionListApp(App[None]): CSS_PATH option_list.tcss def compose(self) - ComposeResult: yield Header() yield OptionList( Option(Aerilon, idaer), Option(Aquaria, idaqu), None, Option(Canceron, idcan), Option(Caprica, idcap, disabledTrue), None, Option(Gemenon, idgem), None, Option(Leonis, idleo), Option(Libran, idlib), None, Option(Picon, idpic), None, Option(Sagittaron, idsag), Option(Scorpia, idsco), None, Option(Tauron, idtau), None, Option(Virgon, idvir), ) yield Footer() if __name__ __main__: OptionListApp().run()完整示例见 docs/examples/widgets/option_list_options.py。Option的构造签名见 源码为Option(prompt: VisualType, id: str | None None, disabled: bool False)参数类型默认值说明promptVisualType必填选项显示的提示内容文本或 Rich renderableidstr \| NoneNone选项的 ID用于之后通过 ID 查询、修改、删除选项disabledboolFalse是否禁用该选项。被禁用的选项会以灰色显示且不可被选中、不可被导航高亮注意ID 在同一列表中必须唯一。若尝试添加重复 ID 的选项会抛出DuplicateID异常见 源码 与测试 tests/option_list/test_option_list_create.py。ID 在OptionList挂载后即可通过get_option查询回归测试见test_options_are_available_soon对应 issue #3903。3. 以 Rich renderable 作为选项由于Option的prompt可以是任意 Rich renderable选项的高度可以任意。下面的例子用 Rich 的Table作为每个选项的内容每个选项都是一个独立的表格from __future__ import annotations from rich.table import Table from textual.app import App, ComposeResult from textual.widgets import Footer, Header, OptionList COLONIES: tuple[tuple[str, str, str, str], ...] ( (Aerilon, Demeter, 1.2 Billion, Gaoth), (Aquaria, Hermes, 75,000, None), (Canceron, Hephaestus, 6.7 Billion, Hades), (Caprica, Apollo, 4.9 Billion, Caprica City), (Gemenon, Hera, 2.8 Billion, Oranu), (Leonis, Artemis, 2.6 Billion, Luminere), (Libran, Athena, 2.1 Billion, None), (Picon, Poseidon, 1.4 Billion, Queenstown), (Sagittaron, Zeus, 1.7 Billion, Tawa), (Scorpia, Dionysus, 450 Million, Celeste), (Tauron, Ares, 2.5 Billion, Hypatia), (Virgon, Hestia, 4.3 Billion, Boskirk), ) class OptionListApp(App[None]): CSS_PATH option_list.tcss staticmethod def colony(name: str, god: str, population: str, capital: str) - Table: table Table(titlefData for {name}, expandTrue) table.add_column(Patron God) table.add_column(Population) table.add_column(Capital City) table.add_row(god, population, capital) return table def compose(self) - ComposeResult: yield Header() yield OptionList(*[self.colony(*row) for row in COLONIES]) yield Footer() if __name__ __main__: OptionListApp().run()完整示例见 docs/examples/widgets/option_list_tables.py。从源码结构看get_content_height与_update_lines每个选项的高度由渲染出的视觉内容高度决定多行选项会被当作多条终端行参与滚动与分页计算这正是选项高度任意的实现基础。响应式属性Reactive AttributesOptionList对外暴露的核心响应式属性如下见 源码名称类型默认值说明highlightedint \| NoneNone当前高亮选项的索引None表示没有任何选项被高亮compactboolFalse是否启用紧凑显示模式对应 CSS 类-textual-compacthighlighted的值在写入时会经过校验validate_highlighted小于 0 会收敛为 0超过列表末尾会收敛为len(options) - 1。当高亮变化且目标选项未被禁用时组件会自动滚动到该选项并发布OptionHighlighted消息watch_highlighted。通过highlighted_option属性可以直接获取当前高亮对应的Option对象源码option: Option | None option_list.highlighted_option消息MessagesOptionList会发布两类消息OptionList.OptionHighlighted当某个选项被高亮时发布。OptionList.OptionSelected当某个选项被选中时发布。两者都继承自共同的基类OptionList.OptionMessage因此都具备以下属性见 源码属性类型说明option_listOptionList发送该消息的 OptionList 实例optionOption消息所涉及的选项对象option_idstr \| None该选项的 IDoption.id的别名option_indexint该选项在列表中的索引controlOptionListoption_list的别名供on装饰器使用处理方式与 Textual 其他消息一致——在 App 或父级组件中定义on_option_list_option_highlighted/on_option_list_option_selected方法即可。测试 tests/option_list/test_option_messages.py 演示了这两个处理器的签名写法例如def on_option_list_option_selected(self, event: OptionList.OptionSelected) - None: self.selected_message fSelected {event.option.prompt}绑定键位BindingsOptionList定义了以下默认按键绑定见 源码按键动作说明downcursor_down高亮向下移动upcursor_up高亮向上移动homefirst高亮移动到第一个选项endlast高亮移动到最后一个选项pagedownpage_down高亮向下翻一页pageuppage_up高亮向上翻一页enterselect选中当前高亮选项所有绑定在 Footer 中默认隐藏showFalse。这些动作对应的实现方法action_cursor_up、action_cursor_down、action_first、action_last、action_page_up、action_page_down、action_select位于 源码上下移动通过_widget_navigation.find_next_enabled实现会跳过被禁用的选项因此高亮始终停留在可交互的选项上分页移动通过_move_page按可视区域高度估算行距并使用find_next_enabled_no_wrap在目标附近收敛到可用的选项action_select在存在高亮且高亮选项未禁用时发布OptionSelected消息。鼠标交互同样受支持点击未禁用选项会将其高亮并立即选中_on_click鼠标悬停会触发option-list--option-hover样式_on_mouse_move。组件类Component ClassesOptionList提供了以下组件类可用于在 CSS 中精细化定制各状态的外观见 源码类名说明option-list--option默认状态未禁用、未高亮、鼠标未悬停的选项option-list--option-disabled被禁用的选项option-list--option-highlighted被高亮的选项option-list--option-hover鼠标悬停的选项option-list--separator分隔线其默认 CSSDEFAULT_CSS展示了这些类的典型用法与默认外观OptionList { height: auto; max-height: 100%; color: $foreground; overflow-x: hidden; border: tall $border-blurred; padding: 0 1; background: $surface; } OptionList:focus { border: tall $border; background-tint: $foreground 5%; }聚焦时高亮选项会采用$block-cursor-*主题色未聚焦时采用对应的$block-cursor-blurred-*模糊色。你可以通过覆盖这些组件类来定制自己的配色OptionList .option-list--option-highlighted { background: $success; color: $text; text-style: bold; }常用 API 一览除构造参数*content选项内容、name、id、classes、disabled、markup、compact外OptionList还提供了丰富的增删改查方法均支持链式调用并返回self方法说明add_option(option)/add_options(options)向列表末尾添加选项传None表示添加分隔线set_options(options)清空现有选项后整体替换见 tests/option_list/test_option_list_create.pyclear_options()清空全部选项并重置高亮与滚动位置get_option(option_id)按 ID 获取Option不存在则抛OptionDoesNotExistget_option_index(option_id)按 ID 获取选项索引get_option_at_index(index)按索引获取Option越界抛OptionDoesNotExistenable_option(option_id)/disable_option(option_id)按 ID 启用 / 禁用选项另有_at_index版本remove_option(option_id)/remove_option_at_index(index)删除指定选项replace_option_prompt(option_id, prompt)/replace_option_prompt_at_index(index, prompt)替换选项的提示内容scroll_to_highlight(topFalse)滚动到当前高亮选项topTrue时将其置于组件顶部相关异常见 源码OptionListError选项列表错误的基类DuplicateID添加了重复 ID 的选项时抛出OptionDoesNotExist按不存在的 ID 或越界索引查询时抛出。Option同样支持子类化以携带额外数据测试 tests/option_list/test_option_list_option_subclass.py 展示了自定义OptionWithExtras并添加 100 个实例的用法。底层实现要点从源码结构看OptionList在渲染层面做了如下优化渲染缓存使用LRUCache容量 2048按(option, style, padding)缓存已渲染的行_get_option_render选项内容变化或组件尺寸变化_on_resize时清空缓存行缓存通过_LineCache记录选项索引 → 终端行的映射支持任意高度选项的滚动定位_update_lines分隔线渲染None添加的分隔线通过将前一个选项标记_divider True实现add_options渲染时在选项下方追加一条─组成的横线行高计算与虚拟尺寸计算都会把这条线纳入考虑。小结OptionList是 Textual 中构建单选式菜单与选择界面的高效组件字符串构造开箱即用Option实例带来 ID 与禁用状态控制Rich renderable 支持让选项可以承载表格、富文本等任意高度的内容配合highlighted响应式属性、OptionHighlighted/OptionSelected消息、完整的键盘导航绑定与细粒度的组件类样式足以覆盖从简单命令菜单到复杂数据浏览面板的各类场景。【免费下载链接】textualThe lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser.项目地址: https://gitcode.com/gh_mirrors/te/textual创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考