Textual OptionList 组件完全指南:可导航选项列表的构建与交互
2026/9/19 7:35:19 网站建设 项目流程

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 renderable(Rich 渲染对象),因此选项的高度可以任意——这为构建富文本菜单提供了极大的灵活性。

三种构建选项的方式

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", id="aer"), Option("Aquaria", id="aqu"), None, Option("Canceron", id="can"), Option("Caprica", id="cap", disabled=True), None, Option("Gemenon", id="gem"), None, Option("Leonis", id="leo"), Option("Libran", id="lib"), None, Option("Picon", id="pic"), None, Option("Sagittaron", id="sag"), Option("Scorpia", id="sco"), None, Option("Tauron", id="tau"), None, Option("Virgon", id="vir"), ) 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 renderable)
idstr \| 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 作为选项

由于Optionprompt可以是任意 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(title=f"Data for {name}", expand=True) 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 Attributes)

OptionList对外暴露的核心响应式属性如下(见 源码):

名称类型默认值说明
highlightedint \| NoneNone当前高亮选项的索引;None表示没有任何选项被高亮
compactboolFalse是否启用紧凑显示模式(对应 CSS 类-textual-compact

highlighted的值在写入时会经过校验(validate_highlighted):小于 0 会收敛为 0,超过列表末尾会收敛为len(options) - 1。当高亮变化且目标选项未被禁用时,组件会自动滚动到该选项并发布OptionHighlighted消息(watch_highlighted)。

通过highlighted_option属性可以直接获取当前高亮对应的Option对象(源码):

option: Option | None = option_list.highlighted_option

消息(Messages)

OptionList会发布两类消息:

  • OptionList.OptionHighlighted:当某个选项被高亮时发布。
  • OptionList.OptionSelected:当某个选项被选中时发布。

两者都继承自共同的基类OptionList.OptionMessage,因此都具备以下属性(见 源码):

属性类型说明
option_listOptionList发送该消息的 OptionList 实例
optionOption消息所涉及的选项对象
option_idstr \| None该选项的 ID(option.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 = f"Selected {event.option.prompt}"

绑定键位(Bindings)

OptionList定义了以下默认按键绑定(见 源码):

按键动作说明
downcursor_down高亮向下移动
upcursor_up高亮向上移动
homefirst高亮移动到第一个选项
endlast高亮移动到最后一个选项
pagedownpage_down高亮向下翻一页
pageuppage_up高亮向上翻一页
enterselect选中当前高亮选项

所有绑定在 Footer 中默认隐藏(show=False)。这些动作对应的实现方法(action_cursor_upaction_cursor_downaction_firstaction_lastaction_page_upaction_page_downaction_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 Classes)

OptionList提供了以下组件类,可用于在 CSS 中精细化定制各状态的外观(见 源码):

类名说明
option-list--option默认状态(未禁用、未高亮、鼠标未悬停)的选项
option-list--option-disabled被禁用的选项
option-list--option-highlighted被高亮的选项
option-list--option-hover鼠标悬停的选项
option-list--separator分隔线

其默认 CSS(DEFAULT_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选项内容、nameidclassesdisabledmarkupcompact)外,OptionList还提供了丰富的增删改查方法,均支持链式调用并返回self

方法说明
add_option(option)/add_options(options)向列表末尾添加选项;传None表示添加分隔线
set_options(options)清空现有选项后整体替换(见 tests/option_list/test_option_list_create.py)
clear_options()清空全部选项并重置高亮与滚动位置
get_option(option_id)按 ID 获取Option,不存在则抛OptionDoesNotExist
get_option_index(option_id)按 ID 获取选项索引
get_option_at_index(index)按索引获取Option,越界抛OptionDoesNotExist
enable_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(top=False)滚动到当前高亮选项,top=True时将其置于组件顶部

相关异常(见 源码):

  • 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询