Files

16 KiB
Raw Permalink Blame History

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 全新实现。这一设计决策的原因:

  1. Ant Design Select 的功能远超传统 ComboBox(多选、标签、搜索过滤、异步加载等)
  2. 需要对 Popup、选项列表、标签面板等进行完全定制化的控制
  3. 与 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 实现:

  1. 首次点击打开时触发 LoadAsync
  2. 加载期间显示 Loading 图标(SuffixLoadingIcon)
  3. 加载完成后自动填充选项并展开下拉
  4. 支持通过 OptionsAsyncLoadContext 传递加载上下文
  5. 提供 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" 清除按钮