ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Textual ListView 指南:用 Python 构建可键盘导航的垂直列表界面

Textual ListView 指南:用 Python 构建可键盘导航的垂直列表界面 Textual ListView 指南用 Python 构建可键盘导航的垂直列表界面【免费下载链接】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导读ListView是 Textual 内置的垂直列表容器组件用于展示一组ListItem子项支持鼠标高亮与键盘导航是构建菜单、设置页、选择器、日志浏览等终端交互界面的基础构件。本文以官方文档 docs/widgets/list_view.md 为骨架结合 ListView 源码 与 ListView 测试用例完整讲解其特性、响应式属性、消息、按键绑定、动态增删项 API 与源码级工作原理读完即可在自己的 Textual 应用中落地使用。ListView 是什么ListView是一个可聚焦Focusable的容器组件以垂直方式展示若干个ListItem用户可以通过鼠标或键盘在其中移动高亮并选中条目。它自 0.6.0 版本起加入 Textual。从源码可以看出ListView 继承自VerticalScroll垂直滚动容器并以can_focusTrue, can_focus_childrenFalse声明列表本身可聚焦但其子项ListItem不可单独聚焦高亮操作统一由列表容器接管——这正是整表键盘导航体验的来源。可聚焦FocusableTab 键可以将焦点移到列表上从而启用键盘操作容器Container作为容器组件它可以承载多个ListItem子组件。它的默认 CSS见 源码 DEFAULT_CSS定义了完整的视觉状态列表背景使用$surface主题色直接子项ListItem高度自动、宽度占满width: 1fr、溢出隐藏鼠标悬停.-hovered与高亮.-highlight分别使用主题中的块级光标配色列表聚焦时叠加background-tint并切换高亮项为聚焦态光标配色$block-cursor-*系列。这些样式意味着你无需写任何 CSS 就能获得可用的默认外观同时也可以通过 CSS 覆盖。快速上手最小可运行示例官方文档提供了开箱即用的示例代码见 docs/examples/widgets/list_view.py样式表见 docs/examples/widgets/list_view.tcss。from textual.app import App, ComposeResult from textual.widgets import Footer, Label, ListItem, ListView class ListViewExample(App): CSS_PATH list_view.tcss def compose(self) - ComposeResult: yield ListView( ListItem(Label(One)), ListItem(Label(Two)), ListItem(Label(Three)), ) yield Footer() if __name__ __main__: app ListViewExample() app.run()配套的样式表list_view.tcssScreen { align: center middle; } ListView { width: 30; height: auto; margin: 2 2; } Label { padding: 1 2; }运行后你会看到一个居中的列表包含 One / Two / Three 三个条目底部是Footer会动态显示当前可用按键。用方向键移动高亮按 Enter 选中点击条目也可直接选中。要点拆解每个ListItem内部通常包裹一个Label作为文本内容但ListItem是普通Widget内部可以是任意内容图片、其他组件组合均可ListView作为容器子项通过compose或后续的append/extend等 API 添加样式表中height: auto让列表高度随子项数量自然伸缩。响应式属性indexListView只有一个核心响应式reactive属性NameTypeDefaultDescriptionindexint0当前高亮条目的索引。在源码中它被声明为index reactive[Optional[int]](None, initFalse)几点源码级细节值得注意默认值语义虽然文档表中标默认0但源码实现中index的初始值是None真正的高亮位置由构造函数参数initial_index默认0在挂载_on_mount时确定见 源码 _on_mount。当initial_index越界时会被重置为0若initial_index指定的项被禁用disabled则从该位置起向后循环查找第一个可用项。校验validate_index对index的赋值会经过 validate_index 夹取到合法范围小于 0 归 0大于等于子项数量归到最后一个索引列表为空时置为None。监听watch_indexwatch_index 在索引变化时负责三件事将新高亮项滚动到可视区域scroll_to_widget、清除旧项的-highlight、为新项设置-highlight并广播Highlighted消息。若新索引无效或指向被禁用项则广播的item为None。因此运行时可以这样读取/修改高亮# 读取当前高亮索引 current my_list_view.index # 直接跳到第 3 项会经过校验与监听自动触发滚动和消息 my_list_view.index 2消息Highlighted 与 SelectedListView通过两类消息与外部通信处理方式遵循 Textual 惯例——在父组件或App中定义on_list_view_highlighted/on_list_view_selected方法即可ListView.Highlighted当高亮项发生变化时发出例如按下上/下键移动光标见 源码 Highlighted。属性list_view所属列表、item新高亮项可为Nonecontrol属性是list_view的别名供on装饰器使用额外的消息匹配属性item已通过ALLOW_SELECTOR_MATCH {item}注册可以配合on(ListView.Highlighted, item...)做精细匹配。典型用法def on_list_view_highlighted(self, event: ListView.Highlighted) - None: if event.item is not None: label event.item.query_one(Label) self.status_bar.update(f当前高亮{label.renderable})ListView.Selected当用户选中条目时发出例如按下 Enter 或鼠标点击见 源码 Selected。属性list_view、item被选中的项、index选中项的索引同样支持on(ListView.Selected)装饰器匹配item也可作为匹配键。典型用法def on_list_view_selected(self, event: ListView.Selected) - None: self.log(f选中了第 {event.index} 项: {event.item})注意Highlighted在每次高亮移动时都会触发频率较高Selected只在确认选中时触发频率低两者分工明确适合分别驱动预览/详情与确认操作两类 UI 逻辑。按键绑定键盘导航ListView定义了三个按键绑定见 源码 BINDINGS| Key(s) | Description | | :- | :- | | enter | 选中当前条目。 | | up | 上移光标。 | | down | 下移光标。 |这三个绑定都声明为showFalse因此不会出现在Footer的默认按键提示中但ListView示例里Footer仍能显示 enter/up/down 相关提示因为 Footer 默认展示全局绑定如需让用户看到这些操作可以自行在应用层添加带描述的绑定。绑定背后的动作实现源码 actionsaction_cursor_down从当前索引向后查找下一个未禁用的项并高亮若当前无高亮则从第 0 项开始action_cursor_up对称地向前查找上一个未禁用项无高亮时从末尾项开始action_select_cursor取出当前高亮项并广播Selected消息若没有高亮项则直接返回。这里的关键细节是导航会跳过被禁用disabledTrue的ListItem见 tests/listview/test_listview_navigation.py 中的回归测试在 0、2、3、6、8 被禁用的列表中连续按 5 次 down 再 5 次 up高亮依次为1 → 4 → 5 → 7 → 5 → 4 → 1验证了跳过逻辑的确定性。鼠标交互虽然键盘是主要输入方式ListView同样支持完整的鼠标操作。其机制是ListItem捕获点击后向上抛出内部消息_ChildClickedListView通过_on_list_item__child_clicked接收并处理见 源码停止消息继续冒泡event.stop()将焦点转移到ListView本身self.focus()把index设置为被点击项的索引触发高亮更新与Highlighted广播Selected消息。相应地ListItem自己维护两个视觉状态见 ListItem 源码highlighted响应式属性由ListView写入通过watch_highlighted切换-highlightCSS 类鼠标悬停通过on(events.Enter)/on(events.Leave)设置-hovered类。动态增删项append / extend / insert / pop / clear / remove_itemsListView内置了完整的运行时增删API全部返回可等待对象配合await或App.run_async使用这在构建动态数据驱动的列表如文件列表、任务队列、日志流时非常关键方法作用返回类型append(item)在末尾追加一个ListItemAwaitMountextend(items)批量追加多个ListItemAwaitMountinsert(index, items)在指定索引处插入一个或多个ListItemAwaitMountpop(indexNone)移除最后一个或指定索引处的ListItemAwaitCompleteremove_items(indices)按索引批量移除多个ListItemAwaitCompleteclear()清空所有ListItemAwaitRemove实现要点见 源码 _list_view.py 动态 API 区段append/extend/insert都委托给容器的mount机制返回AwaitMount调用方可以用await list_view.append(...)等待 DOM 更新完成clear使用self.query(ListView ListItem).remove()移除全部直接子项并把index置为Nonepop在列表为空时会抛出IndexError(pop from empty list)该行为由 tests/listview/test_listview_remove_items.py 中的test_listview_pop_empty_raises_index_error测试锁定pop与remove_items在删除项后会自动校正高亮索引被删项位于高亮之前则索引前移删除的正是高亮项则触发索引重校验并手动调用watch_index确保-highlight与消息状态同步remove_items支持负索引归一化后批量删除并一次性地计算删除项位于高亮之前的数量来平移索引该行为同样有test_listview_remove_items回归测试覆盖。动态使用示例async def on_button_pressed(self) - None: # 动态追加 await self.list_view.append(ListItem(Label(新条目))) # 批量插入到开头 await self.list_view.insert( 0, [ListItem(Label(A)), ListItem(Label(B))] ) # 删除第 2 项0 起始 await self.list_view.pop(2)关于 initial_index 的边界行为构造函数的initial_index参数决定了列表首次挂载时的高亮位置默认0传None表示不预高亮任何项。源码_on_mount的边界处理src/textual/widgets/_list_view.py#L159-L170索引越界 子项数量时回退为0指向被禁用项时从该位置向后循环查找第一个可用的未禁用项因此initial_index指向禁用项时最终落点可能不是传入值而是向后找到的第一个可用项详见参数化测试 tests/listview/test_listview_initial_index.py例如 9 个条目中 0、2、3、6、8 禁用initial_index2时最终高亮4initial_index8时最终高亮1循环回绕。组件类Component ClassesListView没有定义任何组件类这是官方文档明确的结论。所有视觉定制都通过常规 CSS 选择器完成例如覆盖默认高亮配色ListView ListItem.-highlight { background: $accent; color: $text; }若需要在自定义场景中调整悬停/聚焦样式直接对.-hovered、.-highlight以及ListView:focus ListItem.-highlight写规则即可。与相近组件的选型对比如果你正在搭建选择类界面Textual 还提供若干与ListView定位相近的组件可参考官方 widgets 文档目录 按需选用OptionList面向纯文本选项 可选元数据的轻量列表API 更简单适合配置菜单、命令列表等无需复杂子内容结构的场景SelectionList带复选框的多选列表适合批量选择Select单行下拉选择器适合空间受限的表单场景Tree/DirectoryTree层级树形结构适合目录浏览等有父子关系的场景ListView的优势在于它是通用容器ListItem内部可以承载任意组件组合图片、进度条、按钮、嵌套布局等并自带完整的键盘导航、滚动跟随与动态增删 API是自由度最高的通用列表方案。小结ListView是一个开箱即用的垂直列表容器官方文档docs/widgets/list_view.md提供了最小示例与 API 总览而源码src/textual/widgets/_list_view.py与测试tests/listview/则进一步揭示了索引校验、禁用项跳过、滚动跟随、消息广播与动态增删的完整实现。掌握以下要点即可在生产代码中熟练使用用ListItem(Label(...))组合条目交给ListView统一管理焦点与高亮通过index响应式属性读写高亮通过Highlighted/Selected消息响应交互上下方向键导航会自动跳过disabled条目Enter 或点击触发选中用append/extend/insert/pop/remove_items/clear动态维护列表内容注意它们返回可等待对象视觉定制通过 CSS 对.-highlight、.-hovered与ListView:focus规则完成无需组件类。现在就可以参照 docs/examples/widgets/list_view.py 在终端里跑起你的第一个可键盘导航的 Textual 列表应用了。【免费下载链接】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),仅供参考
返回列表