# ListBox 列表框 ## 概述 列表框(ListBox)是一种用于展示可选项集合的容器控件,用户可以从中选择一个或多个选项。AtomUI 的 ListBox 在 Avalonia 原生 `ListBox` 的基础上,增强了 Ant Design 风格的视觉体验,并扩展了项目过滤、高亮搜索、空状态指示器、选中指示器等实用功能。 AtomUI ListBox 控件族包含: | 控件 | 说明 | |---|---| | `ListBox` | 列表框容器,管理列表项集合与选择行为 | | `ListBoxItem` | 列表框中的单个选项项 | > ListBox 不直接对标某个特定的 Ant Design 组件,而是作为 AtomUI 体系中 Select、AutoComplete、Transfer 等复合控件的底层选项容器使用。其设计兼顾独立使用和作为子组件嵌入的两种场景。 --- ## 设计原理 ### 定位与设计动机 在 Ant Design 体系中,`List` 组件用于展示数据列表,而 `Select` 的下拉面板中也包含类似列表的选项容器。AtomUI 的 ListBox 定位为: 1. **独立列表控件** — 可直接使用,展示可选项列表。 2. **复合控件的内部选项容器** — 作为 Select、AutoComplete、Cascader、Transfer 等控件的底层下拉选项列表。 因此,ListBox 在设计上注重: - **灵活的选择模式** — 支持单选、多选,以及可选的"不可选"模式(纯展示)。 - **内置过滤与高亮** — 支持按关键字过滤列表项并高亮匹配文本,为搜索场景提供原生支持。 - **空状态指示器** — 当列表为空或过滤无结果时,自动显示空状态提示。 - **选中指示器** — 可选显示勾选图标标记已选项。 - **虚拟化上下文管理** — 支持 UI 虚拟化场景下的状态保存与恢复。 ### Avalonia ListBox 基础能力 AtomUI ListBox 继承自 `Avalonia.Controls.ListBox`,其继承链为: ``` Control → TemplatedControl → ItemsControl → SelectingItemsControl → ListBox ``` **Avalonia ListBox 提供的核心能力:** | 能力 | 说明 | |---|---| | 项目管理 | `Items` 集合、`ItemsSource` 数据绑定、`ItemTemplate` 数据模板 | | 选择模型 | `SelectionMode`(Single / Multiple / Toggle / AlwaysSelected)、`SelectedItem`、`SelectedIndex`、`SelectedItems` | | 虚拟化 | `VirtualizationMode` 支持项目虚拟化以提升大数据量性能 | | 滚动 | 内置 `ScrollViewer` 支持 | | 容器化 | 自动将数据项包装为 `ListBoxItem` 容器 | ### AtomUI 的扩展设计 | 扩展能力 | 实现方式 | 设计动机 | |---|---|---| | **可选性控制** | `IsSelectable` 属性 | 允许禁用选择行为,用于纯展示场景 | | **三种尺寸** | `ISizeTypeAware` 接口 | 全局统一的 Large / Middle / Small 尺寸系统 | | **无边框模式** | `IsBorderless` 属性 | 嵌入其他控件时去除边框 | | **自定义悬浮/选中背景** | `ItemHoverBg` / `ItemSelectedBg` | 允许精细控制列表项的交互态颜色 | | **选中指示器** | `IsShowSelectedIndicator` + `SelectedIndicator` | 可选显示勾选图标 | | **项目过滤** | `ItemFilter` + `ItemFilterValue` | 内置过滤机制,支持自定义过滤器 | | **过滤高亮** | `ItemFilterHighlightStrategy` + `FilterHighlightForeground` | 过滤时高亮匹配文本 | | **空状态指示器** | `EmptyIndicator` + `IsShowEmptyIndicator` | 列表为空时自动显示空状态 | | **项目点击事件** | `ItemClicked` 事件 | 除选择外还提供独立的点击事件 | | **虚拟化上下文** | `IListVirtualizingContextAware` | 虚拟化场景下保存/恢复项目状态 | | **Design Token** | `ListBoxToken` | 所有视觉值从全局 Token 派生 | --- ## 控件层级关系 ListBox 控件族完全定义在桌面端控件层(`AtomUI.Desktop.Controls`),没有对应的设备无关基类。 ``` Avalonia.Controls.ListBox └── AtomUI.Desktop.Controls.ListBox (ISizeTypeAware, IMotionAwareControl, IListVirtualizingContextAware) Avalonia.Controls.ListBoxItem └── AtomUI.Desktop.Controls.ListBoxItem (IListItemVirtualizingContextAware) ``` ### 各层级职责划分 | 层级 | 提供的能力 | |---|---| | `Avalonia.Controls.ListBox` | 项目集合管理、选择模型(Single/Multiple/Toggle)、虚拟化、滚动、容器化 | | `AtomUI.Desktop.Controls.ListBox` | 可选性控制、尺寸系统、无边框模式、自定义交互态颜色、选中指示器、项目过滤与高亮、空状态指示器、项目点击事件、虚拟化上下文管理、Design Token 注册 | | `Avalonia.Controls.ListBoxItem` | 基础列表项容器、选中态管理 | | `AtomUI.Desktop.Controls.ListBoxItem` | 点击路由事件、选中指示器显示逻辑、过滤高亮文本渲染、背景色过渡动画、虚拟化上下文感知 | ### 实现的接口 | 接口 | 控件 | 说明 | |---|---|---| | `ISizeTypeAware` | ListBox | 支持 `SizeType`(Small / Middle / Large)尺寸切换 | | `IMotionAwareControl` | ListBox | 支持动画开关 | | `IListVirtualizingContextAware` | ListBox | 虚拟化场景下保存/恢复项目状态 | | `IListItemVirtualizingContextAware` | ListBoxItem | 虚拟化场景下的项目级上下文感知 | --- ## 功能详解 ### 项目过滤机制 ListBox 内置了完整的项目过滤系统: 1. **`IListBoxItemFilter` 接口**:定义过滤逻辑的抽象接口。 ```csharp public interface IListBoxItemFilter { bool Filter(ListBox listBox, ListBoxItem listBoxItem, object? filterValue); } ``` 2. **`DefaultListBoxItemFilter`**:默认过滤器实现,基于字符串包含匹配。支持 `ValueFilterMode.Contains`(默认)等过滤模式。 3. **过滤流程**: - 设置 `ItemFilterValue` 时触发过滤。 - 对每个列表项调用 `ItemFilter.Filter()`。 - 根据 `ItemFilterHighlightStrategy` 控制匹配行为: - `HighlightedMatch`:高亮匹配文本。 - `BoldedMatch`:加粗匹配文本。 - `HideUnMatched`:隐藏未匹配的项。 - `All`:以上全部。 - 过滤结果数通过 `FilterResultCount` 只读属性暴露。 4. **过滤高亮**:过滤时,`ListBoxItem` 内部切换为 `HighlightableTextBlock` 渲染,自动高亮匹配的关键字。 ### 选中指示器 通过 `IsShowSelectedIndicator` 启用后,已选中的列表项右侧会显示一个勾选图标。默认使用 `CheckOutlined` 图标,可通过 `SelectedIndicator` 属性自定义。 ### 空状态指示器 当 `IsShowEmptyIndicator == true`(默认)时: - 列表为空(`ItemCount == 0`)时自动显示空状态。 - 过滤无结果(`IsFiltering && FilterResultCount == 0`)时也显示空状态。 - 默认显示 AtomUI 的 `Empty` 控件(Simple 预设图片)。 - 可通过 `EmptyIndicator` / `EmptyIndicatorTemplate` 自定义空状态内容。 ### 虚拟化上下文管理 ListBox 实现了 `IListVirtualizingContextAware` 接口,在 UI 虚拟化场景下: - 当列表项被回收时,通过 `SaveVirtualizingContext()` 保存其状态(如 `IsEnabled`)。 - 当列表项被重用时,通过 `RestoreVirtualizingContext()` 恢复其状态。 - 子类可通过重写 `NotifySaveVirtualizingContext()` / `NotifyRestoreVirtualizingContext()` 扩展保存/恢复的上下文。 ### 项目点击事件 ListBoxItem 定义了 `Clicked` 路由事件(Bubble 策略),ListBox 监听该事件并通过 `ItemClicked` CLR 事件暴露给外部。这允许在不依赖选择变化的情况下响应项目点击。 --- ## 与 Ant Design 规范的对齐程度 | 特性 | Ant Design | AtomUI | 对齐情况 | |---|---|---|---| | 列表展示 | ✅ `List` | ✅ `ListBox` | ✅ 功能对齐 | | 多选 | ✅ `Select` multiple | ✅ `SelectionMode="Multiple"` | ✅ 完全对齐 | | 空状态 | ✅ `Empty` | ✅ `EmptyIndicator` 内置 `Empty` 控件 | ✅ 完全对齐 | | 搜索过滤 | ✅ `Select` filterOption | ✅ `ItemFilter` + `ItemFilterValue` | ✅ 完全对齐 | | 高亮匹配 | ✅ | ✅ `HighlightableTextBlock` | ✅ 完全对齐 | | 勾选指示器 | ✅ Select 下拉勾选 | ✅ `IsShowSelectedIndicator` | ✅ 完全对齐 | | 禁用 | ✅ `disabled` | ✅ `IsEnabled` | ✅ 完全对齐 | | 虚拟滚动 | ✅ `virtual` | ✅ Avalonia 虚拟化 | ✅ 完全对齐 | --- ## 源码位置 | 层级 | 文件路径 | 说明 | |---|---|---| | ListBox 控件 | `src/AtomUI.Desktop.Controls/ListBox/ListBox.cs` | 列表框实现 | | ListBoxItem 控件 | `src/AtomUI.Desktop.Controls/ListBox/ListBoxItem.cs` | 列表项实现 | | ListBox Token | `src/AtomUI.Desktop.Controls/ListBox/ListBoxToken.cs` | 组件 Token | | 过滤器接口 | `src/AtomUI.Desktop.Controls/ListBox/IListBoxItemFilter.cs` | 过滤器抽象接口 | | 默认过滤器 | `src/AtomUI.Desktop.Controls/ListBox/DefaultListBoxItemFilter.cs` | 默认过滤器实现 | | 点击事件参数 | `src/AtomUI.Desktop.Controls/ListBox/ListBoxItemClickedEventArgs.cs` | 点击事件参数类 | | ListBox 主题 | `src/AtomUI.Desktop.Controls/ListBox/Themes/ListBoxTheme.axaml` | ListBox ControlTheme | | ListBoxItem 主题 | `src/AtomUI.Desktop.Controls/ListBox/Themes/ListBoxItemTheme.axaml` | ListBoxItem ControlTheme | --- ## 模板结构 ### ListBox 模板 ``` Border#Frame └── Panel ├── ScrollViewer (PART_ScrollViewer) ← 列表内容(非空时可见) │ └── ItemsPresenter#ItemsPresenter └── ContentPresenter#EmptyIndicator ← 空状态指示器(空时可见) ``` ### ListBoxItem 模板 ``` Border#Frame └── DockPanel ├── IconTemplatePresenter#SelectedIndicator ← 选中指示器(右侧对齐) └── Panel ├── ContentPresenter#ContentPresenter ← 常规内容(非过滤时可见) └── HighlightableTextBlock ← 高亮文本(过滤时可见) ```