23 KiB
Select 桌面版架构设计
本文档定义 AtomUI.Desktop.Controls.Select 的最新设计定位、公共契约、状态模型、视觉主题关系和兼容边界。共享输入分层见 输入控件共享架构设计,通用控件研发约束见 控件研发标准,内部实现原理见 Select 桌面版实现原理,鼠标与键盘的统一候选状态见 Select 候选交互设计,Select Token 的专项设计见 Select Token 设计,设计和契约变化记录见 Select Changelog。
该控件的 Popup 钉住打开属于共享弹层契约,详见 Popup 钉住打开设计。本控件的语义 owner 为 AbstractSelect,其 internal IsPopupPinnedOpen 只供测试和内部诊断使用;设置为 true 时保持 IsDropDownOpen 并 relay 到 PART_Popup,设置为 false 时只解除关闭拦截。控件卸载、锚点失效、TopLevel 改变和模板重建仍按共享生命周期规则清理。
1. 控件定位
| 项 | 值 |
|---|---|
| NuGet 包 | AtomUI.Desktop.Controls |
| .NET 命名空间 | AtomUI.Desktop.Controls |
| AXAML 命名空间 | https://atomui.net |
| Gallery 页面 | controlgallery/AtomUIGallery/ShowCases/DataEntry/Select |
| 控件状态 | Stable |
Select 是 AtomUI 桌面数据录入体系中的选项选择控件,用于在受控选项集合中完成单选、多选和标签化选择。它由输入壳体、已选结果区域、过滤输入、候选弹层、候选列表、异步加载和 Form / CompactSpace 集成组成,面向表单、筛选器、配置项和可搜索选项输入场景。
Select 的职责是选择一个或多个 ISelectOption,或在 Tags 模式下基于用户输入临时创建动态选项。它不负责远程服务协议、权限过滤、业务对象持久化、复杂树形选择、级联选择或富文本标签编辑;这些能力应由业务层或 TreeSelect / Cascader 等专用控件承担。
2. 设计语言
Select 的设计语言来自输入框、已选结果和候选弹层的组合。
| 维度 | 含义 | 典型表达 |
|---|---|---|
| 选择模式 | 控件选择单个选项、多个选项或允许用户创建标签。 | Mode=Single/Multiple/Tags。 |
| 选项数据 | 候选项可来自本地集合、内容子项或异步 loader。 | OptionsSource、Options、OptionsLoader。 |
| 选择结果 | 单选结果显示为文本,多选和 tags 显示为可关闭标签。 | SelectedOption、SelectedOptions、MaxTagCount。 |
| 搜索过滤 | 输入搜索文本后过滤候选项。 | IsFilterEnabled、FilterValue、Filter、FilterValueSelector。 |
| 输入表面 | 控件边框、背景、尺寸、状态和附加内容。 | StyleVariant、Status、SizeType、Addon。 |
| 弹层语义 | 候选列表按 placement 打开,并可匹配控件宽度。 | IsDropDownOpen、Placement、IsPopupMatchSelectWidth。 |
| 异步加载 | 打开控件时按上下文加载候选数据。 | OptionsLoader、OptionsAsyncLoadContext、IsLoading。 |
Custom 尺寸不是 Select 的第四套专属 Token。主题层把 SizeType=Custom 归入 Middle 的默认字体和 padding 分支;用户显式设置尺寸属性时由 Avalonia 属性优先级决定最终布局。
3. API 与契约模型
Select 的公共 API 分布在 AbstractSelect 和 Select 两层。AbstractSelect 提供输入壳体、弹层、清除、状态、尺寸、Addon、Form 和 CompactSpace 契约;Select 提供选项、选择、过滤、分组、异步加载和模式契约。
选项与选择 API:
| API | 类型 | 语义 |
|---|---|---|
Mode |
SelectMode |
选择模式,默认 Single。 |
OptionsSource |
IEnumerable<ISelectOption>? |
外部候选项集合。Select 只读取该集合,不把 Tags 运行时动态选项写回该集合。 |
Options |
ItemCollection |
XAML 内容子项入口。它表达用户声明的静态候选项,不承载 Tags 模式运行时动态选项。 |
OptionTemplate |
IDataTemplate? |
候选项显示模板,默认显示 ISelectOption.Header。 |
SelectedOption |
ISelectOption? |
单选模式当前选项,默认 TwoWay 绑定并启用 Avalonia data validation。 |
SelectedOptions |
IList<ISelectOption>? |
多选和 Tags 模式当前选项集合,默认 TwoWay 绑定并启用 Avalonia data validation。 |
DefaultValues |
IList<object>? |
加载后按值匹配默认选中项。 |
DefaultValueCompareFn |
Func<object, ISelectOption, bool>? |
默认值匹配自定义比较函数。 |
SelectionChanged |
event | SelectedOption 或 SelectedOptions 改变时触发。 |
过滤与列表 API:
| API | 类型 | 语义 |
|---|---|---|
IsFilterEnabled |
bool |
是否启用搜索过滤。Tags 模式始终形成有效过滤能力。 |
FilterValue |
object? |
当前过滤输入值。 |
Filter |
IValueFilter? |
候选项过滤器。为空时使用 ListView 默认过滤行为。 |
FilterValueSelector |
DefaultFilterValueSelector? |
从候选项中提取过滤文本。默认使用 HeaderFilterPropertySelector。 |
IsGroupEnabled |
bool |
是否启用候选项分组。 |
GroupPropertySelector |
DefaultFilterValueSelector? |
分组值 selector。 |
IsDefaultActiveFirstOption |
bool |
当前 public surface 的一部分;维护时需要先确认候选激活路径再改变其语义。 |
IsHideSelectedOptions |
bool |
多选候选列表中隐藏已选项。 |
AutoScrollToSelectedOptions |
bool |
候选列表打开或选择同步时滚动到已选项。 |
DisplayPageSize |
int |
候选弹层可视行数,用于计算最大高度,默认 10。 |
MaxCount |
int |
多选最大可选数量,默认 int.MaxValue。 |
选择绑定语义:
SelectedOption是Mode=Single的唯一选择状态入口;用户选择、清除、Form 写值和 ViewModel 写值都通过该属性同步。SelectedOptions是Mode=Multiple/Tags的唯一选择状态入口;用户选择变化会回写绑定源,绑定源替换集合或对INotifyCollectionChanged集合执行Add/Remove/Reset时,Select 会同步标签、候选列表和计数状态。OptionsSource始终只是候选项来源。即使SelectedOptions绑定到可变集合,Tags 模式运行时动态选项也只进入选择集合和内部有效候选集合,不写回OptionsSource。
弹层与异步 API:
| API | 类型 | 语义 |
|---|---|---|
IsDefaultOpen |
bool |
当前 public surface 的一部分;默认打开行为需与 IsDropDownOpen 状态保持兼容。 |
IsDropDownOpen |
bool |
候选弹层打开状态。 |
Placement |
SelectPopupPlacement |
候选弹层展开位置,默认 BottomEdgeAlignedLeft。 |
IsPopupMatchSelectWidth |
bool |
弹层最小宽度是否匹配 Select 宽度,默认 true。 |
ShouldUseOverlayPopup |
bool |
是否使用 overlay popup 宿主,默认 true。 |
OptionsLoader |
ISelectOptionsAsyncLoader? |
异步候选加载器。 |
OptionsAsyncLoadContext |
object? |
传给异步 loader 的上下文。 |
AsyncLoadTimeout |
TimeSpan |
异步加载超时时间,默认 10 秒。 |
IsLoading |
bool |
异步加载或 loading 状态,只读公开。 |
OptionsLoading / OptionsLoaded |
event | 开始加载和加载完成通知。 |
DropDownOpening / DropDownClosing |
event | 弹层打开/关闭前通知,可取消。 |
DropDownOpened / DropDownClosed |
event | 弹层打开/关闭后通知。 |
输入表面 API:
| API | 类型 | 语义 |
|---|---|---|
SizeType |
CustomizableSizeType |
输入尺寸密度,支持 Large/Middle/Small/Custom。 |
StyleVariant |
InputControlStyleVariant |
输入表面样式。 |
Status |
InputControlStatus |
显式输入反馈状态;最终视觉由 InputControlFrame.EffectiveStatus 计算,native validation error 以 DataValidationErrors 为唯一真源。 |
PlaceholderText / PlaceholderForeground |
string? / IBrush? |
空选择时的占位文本和颜色。 |
IsAllowClear / ClearIcon |
bool / PathIcon? |
清除入口和图标。 |
SuffixIcon / SuffixLoadingIcon |
PathIcon? |
普通展开指示和 loading 指示。 |
LeftAddOn / RightAddOn |
object? |
外部左右 AddOn。 |
ContentLeftAddOn / ContentRightAddOn |
object? |
内部左右内容。 |
IsMotionEnabled |
bool |
输入壳体、handle、候选列表和 popup 动效开关。 |
IsShowOverflowTip |
bool |
选中结果文本或多选 tag 视觉溢出时是否显示完整内容 tooltip,默认 true。 |
OverflowTipDelay |
int |
溢出 tooltip 打开前的延迟时间,单位毫秒,默认 1200。 |
OverflowTipPlacement |
PlacementMode |
溢出 tooltip 相对选中结果文本或多选 tag 的位置,默认 TopEdgeAlignedLeft。 |
多选标签 API:
| API | 类型 | 语义 |
|---|---|---|
MaxTagCount |
int? |
最大直接展示标签数量。 |
IsResponsiveTagMode |
bool |
使用响应式标签布局。 |
MaxTagPlaceholder |
string? |
当前 public surface 的一部分;默认模板使用剩余数量标签表达折叠信息。 |
IsShowMaxCountIndicator |
bool |
是否展示最大数量指示。 |
候选项契约:
| 类型 | 语义 |
|---|---|
ISelectOption.Header |
候选项显示内容。 |
ListItemData.Content |
候选项值和默认值匹配的主要输入。 |
ListItemData.ItemKey |
候选项稳定标识,优先用于选项替换后的选择映射。 |
ListItemData.IsEnabled |
候选项启用状态。 |
ISelectOption.IsDynamicAdded |
Tags 模式下用户输入生成的运行时动态选项标记。 |
稳定 template part:
| Template Part | 类型 | 职责 |
|---|---|---|
PART_InputControlFrame |
SelectAddOnDecoratedBox |
InputControlFrame / AddOnDecoratedBox 组合、Addon、EffectiveStatus、CompactSpace 和 hover/pressed 状态承载;选择专用节点只扩展内容布局。 |
PART_SingleFilterInput |
SelectFilterTextBox |
单选模式结果显示和搜索输入。 |
SelectedOptionsBox |
SelectResultOptionsBox |
多选和 Tags 模式已选标签与搜索输入。 |
PART_SelectMaxCountIndicator |
SelectMaxCountIndicator |
最大选择数量提示。 |
PART_ContentRightAddOnPresenter |
ContentPresenter |
用户内部右侧内容承载。 |
PART_SelectHandle |
SelectHandle |
展开、loading、清除和 Form feedback 图标入口。 |
PART_Popup |
Popup |
候选弹层宿主。 |
PopupFrame |
Border |
懒创建的候选弹层外壳。 |
PART_CandidateList |
SelectCandidateList |
懒创建的候选项列表、键盘导航和提交/取消。 |
Select 的稳定伪类包括 :dropdownopen;输入表面通过 InputControlFrame 统一表达 StyleVariant、EffectiveStatus、:pressed、:disabled 和 CompactSpace,选择专用伪类只表达 popup、候选和结果状态。
4. 行为与状态模型
Select 的核心状态流:
OptionsSource / Options / OptionsLoader
↓
用户选项源
↓
Effective options = 用户选项源 + Tags 运行时动态选项
↓
SelectCandidateList
↓
SelectedOption / SelectedOptions
↓
SingleFilterInput / SelectedOptionsBox
↓
Form value + SelectionChanged
选择模式语义:
Single使用SelectedOption作为唯一表单值,内部单行过滤输入负责展示当前Header。Multiple使用SelectedOptions作为表单值,已选项以SelectTag展示。Tags以Multiple为基础,始终启用有效过滤,并在过滤结果为空且输入非空时创建IsDynamicAdded=true的运行时动态选项。
弹层交互优先级:
Disabled / invisible / window deactivated
> DropDownClosing cancellation
> DropDownOpening cancellation
> IsDropDownOpen
> pointer / keyboard open request
键盘行为:
- 弹层打开时按键优先交给
SelectCandidateList.HandleKeyDown()。 Enter在单选模式提交候选项,在多选模式切换候选项选中状态。Escape取消候选并关闭弹层。F4或Alt+Up/Down切换弹层。- 弹层关闭时,
Up/Down、Enter、Space可打开弹层。 - 多选和 Tags 模式下,过滤输入为空时
Backspace/Delete删除最后一个已选项。
候选交互:
SelectCandidateList维护唯一 active candidate,鼠标移动和键盘导航必须更新同一个候选状态。- 鼠标在可用候选项上实际移动时,该项成为 active candidate;鼠标路径不滚动候选列表,也不提交公共选择。
Up/Down在当前有效候选视图中移动 active candidate,并可以把键盘候选滚动到可见区域。Enter只提交或切换当前 active candidate,候选视觉目标和提交目标必须一致。- active candidate 与已确认选择相互独立;已选项继续保持 selected 视觉和公共选择语义。
:pointerover只表达指针命中事实,不能与IsCandidateSelected分别绘制两个候选高亮。
完整状态、Theme、虚拟化、性能和验证边界见 Select 候选交互设计。
清除行为通过 SelectHandle.ClearRequestedEvent 冒泡到 Select,并调用 ClearValue() 清空 SelectedOption 和 SelectedOptions。
5. 视觉与主题模型
Select 的默认视觉由 Select 专属主题、InputControlFrame / AddOnDecoratedBox、ListView 和 PopupHost 协作完成。
| 主题或资源 | 职责 |
|---|---|
SelectTheme.axaml |
根模板、输入壳体、单选输入、多选结果区域、handle、popup 和基础 selector。 |
InputControlFrameTheme.axaml |
输入框 variant、effective status、hover、pressed、disabled、CompactSpace 和 motion。 |
SelectAddOnDecoratedBoxTheme.axaml |
dropdown open、多选 padding、handle 和选择结果布局;不重复实现 frame 状态 selector。 |
SelectResultOptionsBoxTheme.axaml |
多选标签布局、响应式标签布局和搜索输入承载。 |
SelectCandidateListTheme.axaml |
候选列表基础 ListView 主题和默认候选模板。 |
SelectCandidateListItemTheme.axaml |
候选项 active、selected、disabled 和隐藏已选项视觉;active 只由统一候选状态驱动。 |
SelectTagTheme.axaml |
多选标签高度、背景、关闭按钮和禁用态。 |
SelectHandleTheme.axaml |
展开、loading、清除、过滤指示和 Form feedback 图标。 |
PopupHostToken |
popup 阴影、圆角和 anchor margin。 |
SelectToken |
多选标签、候选项、popup padding 和输入 padding。 |
选中结果的完整内容提示复用 OverflowTip attached behavior。模板只在单选文本和多选 tag 上声明 IsShowOverflowTip / OverflowTipDelay / OverflowTipPlacement,实际 tooltip 只在文本视觉宽度不足时写入 ToolTip.Tip,且不会覆盖用户手动设置的 tooltip。
候选弹层内容采用懒创建模型。PART_Popup 属于模板稳定 part;PopupFrame 和 PART_CandidateList 在打开前由 C# 创建并设置 TemplatedParent,关闭或重新套用模板时释放引用和事件订阅。
6. 控件家族或集成关系
Select 属于 Data Entry 选择控件家族,与 LineEdit、NumericUpDown、DatePicker、TimePicker、TreeSelect 等共享输入尺寸、variant、status、Addon、Form 和 CompactSpace 语义。
集成关系:
AbstractSelect:Select 家族选择、弹层、Form、CompactSpace、Motion 和业务状态契约,并把输入状态投射到 shared frame。SelectCandidateList:基于ListView的候选列表,负责过滤、分组、虚拟化、候选导航和提交取消。SelectResultOptionsBox:多选和 Tags 已选结果展示。SelectHandle:右侧操作入口,负责展开指示、loading、清除和 Form feedback。ISelectOptionsAsyncLoader:异步候选加载边界。IFormItemAware/IFormItemFeedbackAware:将选择值、Form 扩展状态和 feedback 接入 Form;error 由DataValidationErrors投射到InputControlFrame。ICustomizableSizeTypeAware:接入支持Custom的输入尺寸模型。
7. 兼容性不变量
维护 Select 时必须保持以下不变量:
Mode=Single使用SelectedOption,Mode=Multiple/Tags使用SelectedOptions。SelectedOption与SelectedOptions必须保持默认TwoWay绑定;SelectedOptions绑定到INotifyCollectionChanged集合时,集合原地变化也必须刷新内部选择投影。SelectionChanged必须在选择属性变化时继续触发,并包含模式、旧值和新值。OptionsSource、Options和异步加载结果表达用户选项源;Tags 模式运行时动态选项不得写入这些用户选项源。- 候选列表必须使用用户选项源和 Tags 运行时动态选项合成后的有效选项源。
OptionsSource变化必须按ItemKey优先、Content兜底映射已有选择;已选 Tags 动态选项在没有正式选项可映射时必须保留。DefaultValues只在当前选择为空时应用。Tags模式必须保持有效过滤能力,并只在该模式下创建动态选项。MaxCount达到上限时,未选候选项不可继续选择,已选候选项仍可取消。IsHideSelectedOptions不能隐藏分组标题导致空状态判断错误。- 鼠标和键盘必须共享唯一 active candidate,任意时刻最多显示一个未确认候选高亮。
:pointerover不能独立成为 Select 候选视觉或提交状态 owner。- active candidate 变化不能提前修改
SelectedOption或SelectedOptions,Enter必须提交当前视觉候选。 - 弹层打开、关闭事件的取消语义不能被绕过。
- 窗口失活、控件不可见或祖先不可见时必须关闭弹层。
- 重新套用模板或 detach 时必须释放旧 popup 内容、候选列表事件订阅、opened 期间订阅和 TopLevel deactivation 订阅。
SizeType=Custom必须继续以Middle作为未显式覆盖时的默认视觉基线。- Template part、Token 名称、伪类和主题 selector 不得在未授权情况下重命名或删除。
如果实现某项能力时无法保持这些不变量,应先停止实现,说明原因、影响范围、替代方案和迁移方式,并获得授权。
8. 专项模型
8.1 选择模式模型
SelectMode 定义三种模式:
Single:候选列表为单选,提交后关闭弹层,结果显示在PART_SingleFilterInput。Multiple:候选列表为多选,结果显示为SelectTag集合,提交和关闭相互独立。Tags:多选基础上允许把过滤输入创建为运行时动态选项。动态选项属于 Select 内部状态,不属于用户Options/OptionsSource。
8.2 过滤与 Tags 动态选项模型
FilterValue 来自单选过滤输入或多选结果区域内的搜索输入。Tags 模式在候选总数为 0 且过滤文本非空时创建运行时动态选项,该选项的 Header 和 Content 都等于输入文本,并标记为 IsDynamicAdded=true。
Select 必须保持三层选项模型:
用户选项源 = Options / OptionsSource / OptionsLoader result
运行时动态选项 = Tags 模式由输入创建的 IsDynamicAdded 选项
有效候选选项 = 用户选项源 + 运行时动态选项
候选列表只消费有效候选选项。动态选项的创建、选择、删除和清理只能影响运行时动态选项集合及有效候选选项,不能修改用户传入的 OptionsSource 或 XAML 内容子项 Options。当新的用户选项源中出现与已选动态选项相同 identity 的正式选项时,Select 可以把选择映射到正式选项,并清理对应动态选项,避免候选项重复。
8.3 异步加载模型
打开下拉时,如果存在 OptionsLoader 且当前实例还没有成功加载过候选,Select 调用 loader。成功结果写入 OptionsSource 并刷新弹层;超时、取消和未知错误通过 OptionsLoaded 返回状态码。
8.4 Custom SizeType 模型
SizeType=Custom 共享 Middle 字体和 padding 默认分支。该语义必须同时传递给 SelectAddOnDecoratedBox、SelectFilterTextBox、SelectResultOptionsBox 和 SelectTag,保证单选、多选和 Tags 模式尺寸基线一致。
9. 文档导航、LLMS 导出与验证策略
关联文档:
LLMS 语义区域:
| Part | AtomUI 节点 | 职责 | 相关 API | 相关 Token | 稳定性 |
|---|---|---|---|---|---|
root |
Select |
数据录入控件根语义区域,承载 public API、值状态、验证状态和主题入口。 | 见 API 与契约模型 | 见视觉与主题模型 | stable |
input |
输入或编辑区域 |
承载用户输入、当前值、占位、格式化或只读状态。 | 见 API 与契约模型 | 见视觉与主题模型 | stable |
trigger |
触发区域 |
承载清除、展开、提交、步进、上传或辅助操作。 | 见 API 与契约模型 | 见视觉与主题模型 | stable |
popup |
弹层或候选区域 |
承载下拉、候选项、日历、颜色面板或异步内容。 | 见 API 与契约模型 | 见视觉与主题模型 | stable |
validation |
校验反馈区域 |
承载 Form、status、错误、警告、help 或 loading 状态。 | 见 API 与契约模型 | 见视觉与主题模型 | stable |
LLMS 导出来源:
| LLMS 内容 | 来源 | 说明 |
|---|---|---|
| 单控件完整文档 | overview.md + implementation.md + token.md + Gallery ShowCase |
专题设计通过 overview / implementation 摘要进入 controls/select/index-cn.md |
| 单控件语义文档 | overview.md + implementation.md + theme/template 信息 |
专题设计通过 overview / implementation 摘要进入 controls/select/semantic-cn.md |
| API 表 | overview.md 语义摘要 + 源码 public surface | 不在 overview.md 中复制完整 API 表 |
| Design Token 表 | token.md、Token 类型或第 5 节主题模型 | 不在生成产物中手工维护第二份 Token 表 |
| 示例 | Gallery ShowCase + source snippet catalog | 只引用稳定示例 |
| 源码索引 | implementation.md |
用于定位控件源码、主题和测试 |
验证策略:
| 层次 | 验证内容 |
|---|---|
| Public API | Mode、选项源、选择值、过滤、异步加载、弹层事件、Form 和 CompactSpace 语义。 |
| 状态行为 | 单选、多选、Tags、清除、统一 active candidate、鼠标/键盘切换、最大选择数、隐藏已选项、默认值映射和动态选项清理。 |
| AXAML / Template | 稳定 template part、:dropdownopen、AddOnDecoratedBox selector、PopupFrame 和懒创建候选列表。 |
| Token | 多选标签、候选项、popup padding、输入 padding 和 Custom 尺寸默认分支。 |
| Gallery / Docs | Select API 摘要、Token 语义、ShowCase 示例和本目录文档链接。 |