Files

17 KiB
Raw Permalink Blame History

TreeSelect 树选择

概述

树选择(TreeSelect)是一种将树形数据与下拉选择结合的复合控件,用于从层级结构的数据中选择一个或多个值。它本质上是将 TreeView 嵌入到下拉弹窗中,让用户在不占用大量页面空间的前提下,浏览和选择树形数据。

TreeSelect 常见于:组织架构选择、分类目录选择、地区层级选择、权限树选择等场景——凡是数据具有父子嵌套关系、且需要通过下拉方式呈现的,都适合使用 TreeSelect。

AtomUI 的 TreeSelect 控件对齐了 Ant Design 5.0 TreeSelect 的设计规范,在 .NET / Avalonia 平台上提供与 Ant Design React 版一致的视觉体验和交互行为。


设计原理

Ant Design 的 TreeSelect 设计哲学

Ant Design 对 TreeSelect 的定位是:「类似 Select 的选择控件,可选择的数据结构是一个树形结构时,可以使用 TreeSelect」。与普通的 Select 相比,TreeSelect 的核心差异在于:

  • 数据源是树形的:每个选项可以有子选项,形成多层嵌套。
  • 支持展开/折叠:用户可以通过展开/折叠来逐层浏览。
  • 支持勾选模式:除了单选/多选之外,还支持 Checkbox 勾选方式,并提供 ShowCheckedStrategy 控制显示策略。

三种选择模式:

模式 设计意图 典型用途
🔘 单选 默认模式,点击节点即选中,弹窗自动关闭 选择一个分类、一个部门
✅ 多选(Multiple) 允许选择多个节点,选中项以 Tag 形式展示 选择多个标签、多个地区
☑️ 勾选(Checkable) 使用 Checkbox 进行勾选,支持父子联动 权限树、批量分类选择

勾选策略(ShowCheckedStrategy):

策略 行为
All 展示所有勾选的节点(默认)
ShowParent 当所有子节点都勾选时,仅展示父节点
ShowChild 仅展示叶子节点

AbstractSelect 基类能力

AtomUI 的 TreeSelect 继承自 AbstractSelect,这是一个桌面端选择类控件的公共基类(定义在 AtomUI.Desktop.Controls 中),它提供了所有选择类控件共享的基础设施。

AbstractSelect 的核心职责:

AbstractSelect 继承自 Avalonia 的 TemplatedControl,并实现了 7 个共享接口。其核心行为是:管理下拉弹窗的打开/关闭、提供统一的输入框装饰(AddOnDecoratedBox)、处理筛选/清除/加载/状态反馈等通用逻辑。继承链为:

Control → TemplatedControl → AbstractSelect

AbstractSelect 提供的基础能力:

能力 说明
下拉弹窗管理 IsDropDownOpen 控制弹窗开关,自动处理 Opening / Opened / Closing / Closed 生命周期
占位文本 PlaceholderText + PlaceholderForeground 提供空状态提示
筛选支持 IsFilterEnabled + FilterValue 提供搜索筛选基础
清除按钮 IsAllowClear 支持一键清除选中项
AddOn 装饰 LeftAddOn / RightAddOn / ContentLeftAddOn / ContentRightAddOn 提供前后缀附加内容
弹窗位置 Placement 控制弹窗相对于控件的弹出方向
样式变体 StyleVariant(Outline / Filled / Borderless / Underlined)
状态反馈 Status(Default / Error / Warning)配合表单验证
尺寸系统 SizeType(Small / Middle / Large)统一尺寸
多选限制 MaxCount 限制最大可选数量,MaxTagCount 限制 Tag 最大显示数量
空状态指示 EmptyIndicator 在无数据时展示空提示
加载状态 IsLoading 显示旋转加载图标
动画控制 IsMotionEnabled 控制弹窗展开/收起动画
紧凑空间 ICompactSpaceAware 支持在 Space.Compact 中使用
表单集成 IFormItemAware 参与 Form 验证流程

AtomUI TreeSelect 的扩展设计

TreeSelect 在 AbstractSelect 的基础上,增加了树形数据特有的能力:

扩展能力 实现方式 设计动机
树形数据源 ItemsSource(IEnumerable<ITreeItemNode>)+ Items 集合 支持树形数据绑定和直接操作
自定义模板 ItemTemplate(IDataTemplate) 自定义节点渲染方式
单选/多选/勾选 IsMultiple + IsTreeCheckable 属性 三种选择模式灵活切换
勾选策略 ShowCheckedStrategy 枚举 控制勾选模式下 Tag 的显示策略
搜索筛选 Filter(ITreeItemFilter)+ FilterHighlightStrategy 支持关键字搜索并高亮匹配项
异步加载 DataLoader(ITreeItemNodeLoader) 支持树节点按需异步加载
树线/图标 IsShowTreeLine / IsShowIcon / IsShowLeafIcon / IsShowLine 控制树形结构的视觉展示
展开控制 IsDefaultExpandAll / TreeDefaultExpandedPaths 控制初始展开状态
严格勾选 IsTreeCheckStrictly 父子节点勾选互不关联
选中项 SelectedItem(单选)/ SelectedItems(多选) 双向绑定选中值
弹窗匹配宽度 IsPopupMatchSelectWidth(继承自 AbstractSelect) 控制弹窗是否匹配控件宽度
最大选择数量 MaxCount(继承自 AbstractSelect) 达到上限后禁止继续选择

功能详解

选择模式

TreeSelect 根据 IsMultiple 和 IsTreeCheckable 的组合,提供三种选择模式:

IsMultiple IsTreeCheckable 效果
false false 单选模式:点击节点即选中,下拉自动关闭
true false 多选模式:可选择多个节点,选中项以 Tag 展示
true* true 勾选模式:显示 Checkbox,支持父子联动勾选

*注:设置 IsTreeCheckable = true 会自动将 IsMultiple 设为 true。

勾选策略(ShowCheckedStrategy)

在勾选模式下,ShowCheckedStrategy 控制选中项在选择框中的展示方式:

策略值 行为 典型效果
All 展示所有被勾选的节点 勾选了父节点和所有子节点,则全部显示为 Tag
ShowParent 当一个父节点的所有子节点都被勾选时,仅显示父节点 简化展示,减少 Tag 数量
ShowChild 仅展示被勾选的叶子节点 只关注最终数据,忽略中间层级

搜索筛选

当 IsFilterEnabled = true 时,TreeSelect 在输入框中提供搜索功能:

  • 单选模式:输入框变为搜索框,打开弹窗后可直接输入关键字。
  • 多选模式:在 Tag 区域末尾提供内嵌搜索输入框。
  • 筛选逻辑由 Filter(ITreeItemFilter 接口)控制,默认使用 DefaultTreeItemFilter。
  • FilterHighlightStrategy 控制匹配高亮方式(高亮整行、加粗匹配文字、展开匹配路径、隐藏不匹配项等)。
  • FilterHighlightForeground 控制高亮颜色,默认使用 ColorPrimary。

异步加载

通过 DataLoader(ITreeItemNodeLoader)属性,TreeSelect 支持树节点按需异步加载:

  • 展开一个非叶子节点时,自动调用 DataLoader 加载其子节点。
  • 加载过程中节点显示旋转加载图标。
  • 适用于数据量大、不适合一次性加载全部树形数据的场景。

最大选择数量(MaxCount)

在多选/勾选模式下,可通过 MaxCount 限制最大可选数量:

  • 当选择数量达到 MaxCount 时,未选中的节点变为禁用状态。
  • 配合 IsShowMaxCountIndicator 可在右侧显示已选/最大数量指示器。

树线与图标

属性 效果
IsShowTreeLine 在节点之间显示连接线
IsShowLine 控制 TreeView 中的连接线显示
IsShowIcon 显示节点图标
IsShowLeafIcon 显示叶子节点图标
IsSwitcherRotation 展开/折叠按钮使用旋转动画(而非切换图标)

与 Ant Design 规范的对齐程度

特性 Ant Design (React) AtomUI (Avalonia) 对齐情况
树形数据源 treeData ✅ 数组对象 ✅ ItemsSource(IEnumerable<ITreeItemNode>) ✅ 完全对齐
单选 ✅ 默认模式 ✅ 默认模式 ✅ 完全对齐
多选 multiple ✅ 布尔属性 ✅ IsMultiple 属性 ✅ 完全对齐
勾选 treeCheckable ✅ 布尔属性 ✅ IsTreeCheckable 属性 ✅ 完全对齐
勾选策略 showCheckedStrategy ✅ SHOW_ALL / SHOW_PARENT / SHOW_CHILD ✅ TreeSelectCheckedStrategy 枚举 ✅ 完全对齐
搜索筛选 showSearch / filterTreeNode ✅ 属性 + 回调 ✅ IsFilterEnabled + Filter 接口 ✅ 完全对齐
异步加载 loadData ✅ 回调函数 ✅ DataLoader(ITreeItemNodeLoader) ✅ 完全对齐
默认展开所有 treeDefaultExpandAll ✅ 布尔属性 ✅ IsDefaultExpandAll ✅ 完全对齐
默认展开指定节点 treeDefaultExpandedKeys ✅ 数组 ✅ TreeDefaultExpandedPaths ✅ 完全对齐
显示树线 treeLine ✅ 布尔/对象 ✅ IsShowTreeLine / IsShowLine ✅ 完全对齐
显示图标 treeIcon ✅ 布尔属性 ✅ IsShowIcon ✅ 完全对齐
严格勾选 treeCheckStrictly ✅ 布尔属性 ✅ IsTreeCheckStrictly ✅ 完全对齐
清除按钮 allowClear ✅ 布尔属性 ✅ IsAllowClear ✅ 完全对齐
占位文本 placeholder ✅ 字符串 ✅ PlaceholderText ✅ 完全对齐
弹窗匹配宽度 popupMatchSelectWidth ✅ 布尔/数值 ✅ IsPopupMatchSelectWidth ✅ 完全对齐
弹窗位置 placement ✅ 字符串 ✅ Placement 枚举 ✅ 完全对齐
最大选择数 maxCount ✅ 数值 ✅ MaxCount ✅ 完全对齐
样式变体 variant ✅ outlined / filled / borderless ✅ StyleVariant(Outline / Filled / Borderless / Underlined) ✅ 超越对齐(额外支持 Underlined)
状态 status ✅ error / warning ✅ Status 枚举 ✅ 完全对齐
尺寸 size ✅ large / middle / small ✅ SizeType 属性 ✅ 完全对齐
前后缀 ✅ suffixIcon ✅ LeftAddOn / RightAddOn / ContentLeftAddOn / SuffixIcon ✅ 超越对齐
虚拟滚动 virtual ✅ 支持 ⚠️ 暂未支持 ⚠️ 待支持
下拉菜单自定义渲染 dropdownRender ✅ 回调函数 ❌ 暂不支持 ⚠️ 待支持
treeTitleRender ✅ 回调函数 ✅ ItemTemplate ✅ 完全对齐(方式不同)

继承关系

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.TreeSelect

注意:AbstractSelect 和 TreeSelect 都位于 AtomUI.Desktop.Controls。当前 TreeSelect 没有跨平台的抽象基类(即没有 AtomUI.Controls.AbstractTreeSelect),这意味着它目前是纯桌面端控件。未来如需支持移动端,可能需要提取共享基类。

各层级职责划分:

层级 提供的能力
TemplatedControl Avalonia 模板化控件基础设施
AbstractSelect 下拉弹窗管理、AddOn 装饰框、筛选/清除/加载、样式变体、状态反馈、尺寸系统、紧凑空间适配、表单集成
TreeSelect 树形数据源、单选/多选/勾选模式、勾选策略、树线/图标控制、异步加载、搜索筛选(树专用)、选中项管理

实现的共享接口(通过 AbstractSelect 继承):

接口 定义位置 作用
IMotionAwareControl AtomUI.Controls.Shared 支持动画开关控制
ISizeTypeAware AtomUI.Controls.Shared 支持 SizeType(Small / Middle / Large)三种尺寸
ICompactSpaceAware AtomUI.Controls.Shared 在 Space.Compact 中使用时自动调整圆角和边框
IInputControlStatusAware AtomUI.Controls.Shared 支持 Status(Default / Error / Warning)状态切换
IInputControlStyleVariantAware AtomUI.Controls.Shared 支持 StyleVariant(Outline / Filled / Borderless / Underlined)
IFormItemAware AtomUI.Controls.Shared 可作为 FormItem 的子控件参与表单验证
IFormItemFeedbackAware AtomUI.Controls.Shared 支持表单验证反馈控件展示

源码位置

层级 文件路径 说明
基类 src/AtomUI.Desktop.Controls/Select/AbstractSelect.cs 选择类控件公共基类
控件类 src/AtomUI.Desktop.Controls/TreeSelect/TreeSelect.cs TreeSelect 主控件
辅助控件 src/AtomUI.Desktop.Controls/TreeSelect/TreeSelectAddOnDecoratedBox.cs 内部装饰框(处理多选内边距)
辅助控件 src/AtomUI.Desktop.Controls/TreeSelect/TreeSelectTreeView.cs 内部 TreeView(处理 CheckBox 切换和最大选择数)
辅助控件 src/AtomUI.Desktop.Controls/TreeSelect/TreeViewSelectTreeViewItem.cs 内部 TreeViewItem(传递最大选择数状态)
枚举类型 src/AtomUI.Desktop.Controls/TreeSelect/TreeSelectCheckedStrategy.cs 勾选策略枚举
Token 定义 src/AtomUI.Desktop.Controls/TreeSelect/TreeSelectToken.cs 组件级 Design Token
转换器 src/AtomUI.Desktop.Controls/TreeSelect/Converters/TreeItemNodeConverter.cs ITreeItemNode → Header 转换器
伪类常量 src/AtomUI.Desktop.Controls/Select/SelectPseudoClass.cs 共享伪类定义
主题模板 src/AtomUI.Desktop.Controls/TreeSelect/Themes/TreeSelectTheme.axaml ControlTheme AXAML
主题模板 src/AtomUI.Desktop.Controls/TreeSelect/Themes/TreeSelectAddOnDecoratedBoxTheme.axaml 装饰框 ControlTheme
主题模板 src/AtomUI.Desktop.Controls/TreeSelect/Themes/TreeSelectTreeViewItemTheme.axaml TreeViewItem ControlTheme
主题注册 src/AtomUI.Desktop.Controls/TreeSelect/Themes/TreeSelectThemes.axaml ResourceDictionary 聚合
模板常量 src/AtomUI.Desktop.Controls/TreeSelect/Themes/TreeSelectThemeConstants.cs 模板部件名称常量
Gallery 示例 controlgallery/AtomUIGallery/ShowCases/Views/DataEntry/TreeSelectShowCase.axaml 使用范例
Gallery 代码 controlgallery/AtomUIGallery/ShowCases/Views/DataEntry/TreeSelectShowCase.axaml.cs 示例 code-behind

模板结构

TreeSelect 的 ControlTemplate 由两个核心部分组成:装饰框(选择框外观)和弹窗(下拉树)。

Panel
├── TreeSelectAddOnDecoratedBox (PART_AddOnDecoratedBox)  ← 选择框外观层
│   ├── ContentRightAddOn                                  ← 右侧附加内容
│   │   ├── SelectMaxCountIndicator                        ← 最大数量指示器(可选)
│   │   ├── ContentPresenter (ContentRightAddOn)           ← 自定义右侧内容(可选)
│   │   └── SelectHandle                                   ← 下拉箭头 / 清除按钮 / 加载图标
│   └── Panel                                              ← 内容区域
│       ├── TextBlock#PlaceholderText                      ← 占位文本
│       ├── ContentPresenter#SingleSelectResultPresenter   ← 单选模式:显示选中项文本
│       ├── SelectFilterTextBox (PART_SingleFilterInput)   ← 单选模式:搜索输入框
│       └── SelectTagAwareTextBox#SelectedItemsBox         ← 多选模式:Tag 展示 + 搜索输入
└── Popup (PART_Popup)                                     ← 下拉弹窗
    └── Border#PopupFrame                                  ← 弹窗容器(背景、圆角、阴影)
        └── TreeSelectTreeView (PART_TreeView)             ← 内嵌 TreeView

模板设计要点:

  • 单选 vs 多选:通过 IsMultiple 属性切换 SingleSelectResultPresenter/SelectFilterTextBox(单选)和 SelectTagAwareTextBox(多选)的可见性。
  • 搜索输入:单选搜索时,SelectFilterTextBox 覆盖在选中文本之上,选中文本降为 Placeholder 色。
  • 弹窗 TreeView:使用自定义的 TreeSelectTreeView,继承自标准 TreeView,增加了 IsMaxSelectReached 状态传递能力。

模板部件常量

常量名 值 说明
TreeSelectThemeConstants.PopupPart "PART_Popup" 下拉弹窗
TreeSelectThemeConstants.SingleFilterInputPart "PART_SingleFilterInput" 单选搜索输入框
TreeSelectThemeConstants.TreeViewPart "PART_TreeView" 内嵌 TreeView