16 KiB
Select 选择器
概述
下拉选择器(Select)是数据录入场景中最常见的表单控件之一,用于在大量选项中选择一个或多个值。它通过弹出下拉面板呈现可选项列表,配合搜索过滤、远程加载、分组等高级功能,满足从简单枚举到复杂数据源的各种选择需求。
AtomUI 的 Select 控件完整复刻了 Ant Design 5.0 Select 的设计规范,在 .NET / Avalonia 平台上提供与 Ant Design React 版一致的视觉体验和交互行为。
设计原理
Ant Design 的 Select 设计哲学
Ant Design 对 Select 的定位是:「弹出一个下拉菜单给用户选择操作,用于代替原生的选择器,或者需要一个更优雅的多选器时」。其核心设计准则:
三种选择模式(满足不同业务场景):
| 模式 | 设计意图 | 典型用途 |
|---|---|---|
| 🔘 单选(Single) | 从选项列表中选择唯一值,选中后下拉关闭 | 城市选择、状态筛选、分类选择 |
| ☑️ 多选(Multiple) | 从选项列表中选择多个值,以标签(Tag)形式展示已选项 | 标签选择、权限分配、多条件筛选 |
| 🏷️ 标签(Tags) | 在多选基础上允许用户自由输入创建新标签 | 文章标签、自定义分类 |
三种样式变体(对齐输入控件家族):
| 变体 | 设计意图 |
|---|---|
| Outlined | 默认外观,有明确的边框轮廓,适合大多数表单场景 |
| Filled | 填充背景,适合需要视觉层次感的场景 |
| Borderless | 无边框,适合嵌入表格或其他容器中的轻量选择 |
核心交互特性:
- 搜索过滤:展开时可通过输入关键字过滤选项
- 清空操作:可选的清除按钮一键清空已选值
- 异步加载:支持从远程数据源动态加载选项
- 分组展示:选项支持按分类分组,配合分组标题
- 响应式标签:多选模式下标签自动折叠,超出显示计数
- 自定义渲染:通过模板自定义下拉选项的展示方式
Avalonia 基础能力
AtomUI 的 Select 并非继承自 Avalonia 内置的 ComboBox,而是基于 TemplatedControl 全新实现。这一设计决策的原因:
- Ant Design Select 的功能远超传统 ComboBox(多选、标签、搜索过滤、异步加载等)
- 需要对 Popup、选项列表、标签面板等进行完全定制化的控制
- 与 AtomUI 的 Design Token 系统和 AddOnDecoratedBox 装饰器体系深度集成
继承链:
Control → TemplatedControl → AbstractSelect → Select
AtomUI 的扩展设计
AtomUI Select 基于 TemplatedControl 从零构建,实现了以下能力:
| 扩展能力 | 实现方式 | 设计动机 |
|---|---|---|
| 三种选择模式 | SelectMode 枚举(Single / Multiple / Tags) |
对齐 Ant Design 的 mode 属性 |
| 三种样式变体 | IInputControlStyleVariantAware 接口 + StyleVariant |
对齐 Ant Design 的 variant,支持 Outlined / Filled / Borderless |
| 三种尺寸 | ISizeTypeAware 接口 + SizeType 共享属性 |
全局统一的 Large / Middle / Small 尺寸系统 |
| 搜索过滤 | IsFilterEnabled + Filter + FilterValueSelector |
展开时输入关键字实时过滤选项 |
| 异步加载 | OptionsLoader + ISelectOptionsAsyncLoader 接口 |
支持从远程数据源异步加载选项 |
| 分组展示 | IsGroupEnabled + GroupPropertySelector |
选项按分组标题聚合展示 |
| 清空操作 | IsAllowClear + SelectHandle 清除按钮 |
一键清空已选值 |
| 响应式标签 | IsResponsiveTagMode + MaxTagCount |
多选标签自动折叠,超出显示 +N 计数 |
| 最大选择数 | MaxCount + IsShowMaxCountIndicator |
限制最大可选数量,超出后选项禁用 |
| 下拉弹出 | 内置 Popup + 动画支持 |
下拉面板支持动画打开/关闭 |
| AddOn 装饰 | 继承 AddOnDecoratedBox 属性 |
支持前/后附加内容区域 |
| 表单集成 | IFormItemAware + IFormItemFeedbackAware 接口 |
参与 FormItem 验证流程,支持 Error/Warning 状态 |
| 紧凑空间 | ICompactSpaceAware 接口 |
在 Space.Compact 容器中自动调整圆角和边框 |
| Design Token | SelectToken + RegisterTokenResourceScope |
所有视觉值从全局/组件级 Token 派生,支持主题切换 |
功能详解
选择模式(SelectMode)
选择模式通过 Mode 属性设置:
| 模式 | 行为 | 已选展示 |
|---|---|---|
Single |
单选,选中后自动关闭下拉。支持搜索输入框替代已选文本 | 直接显示已选项的 Header 文本 |
Multiple |
多选,选中后不关闭下拉。每个已选项以 Tag 形式展示 | SelectTag 标签列表 + 可选的搜索输入框 |
Tags |
在多选基础上,输入内容无匹配时自动创建新选项 | 同 Multiple,但支持动态添加选项 |
Tags 模式的特殊行为:
- 搜索功能强制启用(
IsEffectiveFilterEnabled = true) - 当搜索无匹配结果时,自动创建临时
SelectOption(IsDynamicAdded = true) - 选中后动态选项保留;取消选中时自动从选项列表移除
搜索过滤
当 IsFilterEnabled = true(或 Mode = Tags)时启用搜索过滤:
- Single 模式:展开时显示输入框,输入关键字过滤选项
- Multiple/Tags 模式:标签区域内嵌输入框,支持边选边搜
过滤机制:
- 默认按
Header属性匹配(HeaderFilterPropertySelector) - 可通过
FilterValueSelector自定义匹配字段 - 可通过
Filter属性提供自定义匹配逻辑(IValueFilter实现)
异步加载
通过 OptionsLoader 属性绑定 ISelectOptionsAsyncLoader 实现:
- 首次点击打开时触发
LoadAsync - 加载期间显示 Loading 图标(
SuffixLoadingIcon) - 加载完成后自动填充选项并展开下拉
- 支持通过
OptionsAsyncLoadContext传递加载上下文 - 提供
OptionsLoading/OptionsLoaded事件监听加载过程
分组展示
当 IsGroupEnabled = true 时启用分组:
- 选项通过
Group属性(IGroupListItemData)进行分组 - 可通过
GroupPropertySelector自定义分组属性提取逻辑 - 每个分组显示分组标题
响应式标签
多选模式下处理大量已选项的展示:
| 属性 | 功能 |
|---|---|
MaxTagCount |
固定最多显示 N 个标签,超出部分折叠为 +N 计数 |
IsResponsiveTagMode |
根据控件实际宽度自动计算可见标签数量 |
MaxTagPlaceholder |
自定义折叠后的显示文本 |
下拉弹出控制
| 属性 | 功能 |
|---|---|
IsDropDownOpen |
控制下拉是否打开 |
IsDefaultOpen |
初始时是否默认打开 |
Placement |
下拉面板弹出位置(BottomEdgeAlignedLeft / TopEdgeAlignedLeft 等) |
IsPopupMatchSelectWidth |
下拉面板是否与 Select 控件等宽(默认 true) |
DisplayPageSize |
下拉面板最多显示的选项条数(默认 10,控制最大高度) |
键盘交互
| 按键 | 行为 |
|---|---|
F4 / Alt+Down / Alt+Up |
切换下拉展开/关闭 |
Down / Up(下拉关闭时) |
打开下拉 |
Escape(下拉打开时) |
关闭下拉 |
Enter / Space(下拉关闭时) |
打开下拉 |
Backspace / Delete(多选模式) |
删除最后一个已选标签 |
与 Ant Design 规范的对齐程度
| 特性 | Ant Design (React) | AtomUI (Avalonia) | 对齐情况 |
|---|---|---|---|
三种模式 mode |
✅ / multiple / tags |
✅ SelectMode 枚举 |
✅ 完全对齐 |
三种变体 variant |
✅ outlined / filled / borderless |
✅ StyleVariant 属性 |
✅ 完全对齐 |
三种尺寸 size |
✅ large / middle / small |
✅ SizeType 属性 |
✅ 完全对齐 |
搜索过滤 showSearch |
✅ 布尔属性 | ✅ IsFilterEnabled |
✅ 完全对齐 |
自定义过滤 filterOption |
✅ 函数 | ✅ Filter(IValueFilter) |
✅ 对齐(类型不同,语义一致) |
允许清空 allowClear |
✅ 布尔/对象 | ✅ IsAllowClear 布尔 |
✅ 完全对齐 |
| 远程加载 | ✅ filterOption={false} |
✅ OptionsLoader + ISelectOptionsAsyncLoader |
✅ 对齐(API 不同,能力一致) |
最大选择数 maxCount |
✅ 数值 | ✅ MaxCount 属性 |
✅ 完全对齐 |
响应式标签 maxTagCount |
✅ responsive / 数值 |
✅ IsResponsiveTagMode / MaxTagCount |
✅ 完全对齐 |
分组 OptGroup |
✅ 组件 | ✅ IsGroupEnabled + Group 属性 |
✅ 对齐(声明式 → 属性驱动) |
自定义渲染 optionRender |
✅ 函数 | ✅ OptionTemplate(IDataTemplate) |
✅ 对齐(模板化方式) |
弹出位置 placement |
✅ bottomLeft 等 |
✅ Placement 枚举 |
✅ 完全对齐 |
占位文本 placeholder |
✅ 字符串 | ✅ PlaceholderText |
✅ 完全对齐 |
默认值 defaultValue |
✅ 任意类型 | ✅ DefaultValues 属性 |
✅ 完全对齐 |
前/后附加 prefix / suffixIcon |
✅ 自定义内容 | ✅ ContentLeftAddOn / SuffixIcon |
✅ 完全对齐 |
| 隐藏已选 | ✅ 通过 filterOption 实现 |
✅ IsHideSelectedOptions |
✅ 完全对齐 |
状态 status |
✅ error / warning |
✅ Status 属性 |
✅ 完全对齐 |
下拉宽度匹配 popupMatchSelectWidth |
✅ 布尔/数值 | ✅ IsPopupMatchSelectWidth |
⚠️ 仅支持布尔,不支持自定义宽度值 |
labelInValue |
✅ 返回 label 和 value | ❌ 暂未支持 | — 通过 ISelectOption 可获取全部信息 |
virtual 虚拟滚动 |
✅ 默认开启 | ✅ 通过 VirtualizingStackPanel 实现 |
✅ 完全对齐 |
空状态 notFoundContent |
✅ 自定义内容 | ✅ EmptyIndicator / EmptyIndicatorTemplate |
✅ 完全对齐 |
继承关系
Avalonia.Controls.Primitives.TemplatedControl
└── AtomUI.Desktop.Controls.AbstractSelect
├── implements IMotionAwareControl
├── implements ISizeTypeAware
├── implements ICompactSpaceAware
├── implements IInputControlStatusAware
├── implements IInputControlStyleVariantAware
├── implements IFormItemAware
└── implements IFormItemFeedbackAware
└── AtomUI.Desktop.Controls.Select
└── 注册 SelectToken.ScopeProvider
注意:
AbstractSelect和Select均定义在AtomUI.Desktop.Controls命名空间下(位于src/AtomUI.Desktop.Controls/Select/目录)。当前 Select 不存在跨平台基类层(AtomUI.Controls中无 AbstractSelect),所有逻辑均在桌面控件层实现。
各层级职责划分:
| 层级 | 提供的能力 |
|---|---|
TemplatedControl |
模板化控件基础设施、OnApplyTemplate、样式系统 |
AbstractSelect |
下拉选择器通用基础设施:Popup 管理(打开/关闭/动画)、下拉展开/折叠事件、占位文本、输入框样式变体(Outlined/Filled/Borderless)、验证状态(Error/Warning)、AddOn 装饰器属性、紧凑空间支持、表单集成、清除按钮逻辑、弹出位置管理 |
Select |
具体选择行为实现:三种选择模式(Single/Multiple/Tags)、选项数据管理(Options / OptionsSource)、选中值管理(SelectedOption / SelectedOptions)、搜索过滤、异步加载、分组展示、标签管理、默认值处理、键盘交互 |
实现的共享接口:
| 接口 | 定义位置 | 作用 |
|---|---|---|
ISizeTypeAware |
AtomUI.Controls.Shared |
支持 SizeType(Small / Middle / Large)三种尺寸切换 |
IMotionAwareControl |
AtomUI.Controls.Shared |
支持 IsMotionEnabled 动画开关 |
ICompactSpaceAware |
AtomUI.Controls.Shared |
在 Space.Compact 中使用时自动调整圆角和边框 |
IInputControlStatusAware |
AtomUI.Controls.Shared |
支持 Status(Error / Warning)验证状态 |
IInputControlStyleVariantAware |
AtomUI.Controls.Shared |
支持 StyleVariant(Outlined / Filled / Borderless)样式变体 |
IFormItemAware |
AtomUI.Controls.Shared |
可作为 FormItem 的子控件参与表单验证 |
IFormItemFeedbackAware |
AtomUI.Controls.Shared |
接收表单验证反馈控件 |
源码位置
| 层级 | 文件路径 | 说明 |
|---|---|---|
| 基类 | src/AtomUI.Desktop.Controls/Select/AbstractSelect.cs |
下拉选择器通用基础设施 |
| 控件类 | src/AtomUI.Desktop.Controls/Select/Select.cs |
Select 主控件类 |
| 异步加载 | src/AtomUI.Desktop.Controls/Select/Select.AsyncOptionsLoad.cs |
异步选项加载逻辑(partial class) |
| 选项接口 | src/AtomUI.Desktop.Controls/Select/SelectOption.cs |
ISelectOption 接口 + SelectOption 记录类 |
| 伪类常量 | src/AtomUI.Desktop.Controls/Select/SelectPseudoClass.cs |
伪类定义 |
| 弹出位置枚举 | src/AtomUI.Desktop.Controls/Select/SelectPopupPlacement.cs |
弹出位置枚举 |
| 选择变更事件 | src/AtomUI.Desktop.Controls/Select/SelectSelectionChangedEventArgs.cs |
选择变更事件参数 |
| 异步加载接口 | src/AtomUI.Desktop.Controls/Select/DataLoad/ISelectOptionsAsyncLoader.cs |
异步加载器接口 |
| 加载结果 | src/AtomUI.Desktop.Controls/Select/DataLoad/SelectOptionsLoadResult.cs |
加载结果类 |
| Token 定义 | src/AtomUI.Desktop.Controls/Select/SelectToken.cs |
组件级 Design Token |
| 主题模板 | src/AtomUI.Desktop.Controls/Select/Themes/SelectTheme.axaml |
ControlTheme AXAML |
| 模板常量 | src/AtomUI.Desktop.Controls/Select/Themes/SelectThemeConstants.cs |
模板部件名称常量 |
| Gallery 示例 | controlgallery/AtomUIGallery/ShowCases/Views/DataEntry/SelectShowCase.axaml |
使用范例 |
模板结构
Select 的 ControlTemplate 采用 Panel 布局,将选择器区域和弹出下拉面板分离:
Panel
├── SelectAddOnDecoratedBox (PART_AddOnDecoratedBox) ← 输入框装饰容器(边框、AddOn 区域)
│ ├── [ContentRightAddOn] ← 右侧附加区域
│ │ ├── SelectMaxCountIndicator ← 最大选择数指示器(可选显示)
│ │ ├── ContentPresenter (ContentRightAddOn) ← 用户自定义右侧内容
│ │ └── SelectHandle ← 下拉箭头/清除/加载图标区域
│ └── Panel ← 内容主区域
│ ├── TextBlock#PlaceholderText ← 占位文本
│ ├── ContentPresenter#SingleSelectResultPresenter ← 单选结果展示(仅 Single 模式可见)
│ ├── SelectFilterTextBox (PART_SingleFilterInput) ← 单选搜索输入框(仅 Single+Filter 可见)
│ └── SelectResultOptionsBox#SelectedOptionsBox ← 多选标签+搜索区域(仅 Multiple/Tags 可见)
└── Popup (PART_Popup) ← 下拉弹出面板
└── Border#PopupFrame ← 弹出框架(背景、圆角、阴影)
└── SelectCandidateList (PART_CandidateList) ← 候选选项列表(虚拟滚动)
分层设计理由:
- AddOnDecoratedBox 装饰:统一输入控件的边框、AddOn 和样式变体系统,与 Input、AutoComplete 等控件保持一致的外观风格
- SelectHandle 独立:集中管理下拉箭头、清除按钮、加载图标和表单反馈控件的切换逻辑
- 弹出面板独立:Popup 使用 OverlayLayer,不影响主控件布局
模板部件常量
| 常量名 | 值 | 说明 |
|---|---|---|
SelectThemeConstants.CandidateListPart |
"PART_CandidateList" |
候选选项列表 |
SelectThemeConstants.PopupPart |
"PART_Popup" |
下拉弹出面板 |
SelectThemeConstants.SingleFilterInputPart |
"PART_SingleFilterInput" |
单选搜索输入框 |
SelectHandleThemeConstants.ClearButtonPart |
"PART_ClearButton" |
清除按钮 |