Files

374 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TreeView 树形控件
## 概述
树形控件(TreeView)用于展示具有层级关系的数据结构。它支持节点的展开/折叠、复选框勾选、拖拽排序、搜索过滤、异步加载等功能,是文件浏览器、组织架构、分类目录等场景中最常见的数据展示控件之一。
AtomUI 的 `TreeView` 控件对齐了 [Ant Design 5.0 Tree](https://ant.design/components/tree-cn) 的设计规范,在 .NET / Avalonia 平台上提供与 Ant Design React 版一致的视觉体验和交互行为。
---
## 设计原理
### Ant Design 的树形控件设计哲学
Ant Design 对树形控件的定位是:**「展示具有层级关系的信息,可支持展开收起、复选等交互」**。树形控件承载两类核心需求:
1. **数据展示**:将具有父子关系的数据以可视化的层级结构呈现,帮助用户理解数据的组织方式。
2. **交互操作**:支持节点选中(单选/多选)、勾选(Checkbox)、展开/折叠、拖拽重排、搜索过滤等丰富的交互模式。
**核心功能特性**:
| 功能 | 设计意图 | 典型场景 |
|---|---|---|
| 📁 **展开/折叠** | 按需展示层级数据,减少视觉负担 | 文件目录、组织架构 |
| ☑️ **复选框(Checkable)** | 对树节点进行批量勾选,支持父子联动 | 权限配置、分类筛选 |
| 🔘 **单选按钮(Radio)** | 对同一层级节点进行单选 | 分组选择 |
| 🖱️ **拖拽排序(Draggable)** | 通过拖放调整节点位置和层级关系 | 菜单编排、目录管理 |
| 🔍 **搜索过滤** | 快速定位匹配节点并高亮显示 | 大量节点中快速查找 |
| ⏳ **异步加载** | 展开节点时按需加载子节点数据 | 大数据量、远程数据源 |
| ➖ **连接线(ShowLine)** | 用连接线展示节点间的层级关系 | 增强视觉层级感 |
| 🎨 **自定义图标** | 为每个节点设置自定义图标 | 文件类型区分、状态标识 |
**三种悬浮高亮模式**:
| 模式 | 设计意图 |
|---|---|
| `Default` | 仅高亮节点文字区域(默认模式) |
| `Block` | 高亮整个节点行(包含缩进区域),增强视觉反馈 |
| `WholeLine` | 高亮整行(从容器左边缘到右边缘),适合目录风格 |
### Avalonia TreeView 基础能力
AtomUI 的 `TreeView` 继承自 Avalonia 框架的 `Avalonia.Controls.TreeView`。理解 Avalonia TreeView 的基础能力有助于理解 AtomUI 在其之上做了哪些扩展。
**Avalonia TreeView 的核心职责:**
Avalonia 的 `TreeView` 是一个 `ItemsControl` 的子类,用于以树形结构展示层级数据。其继承链为:
```
Control → TemplatedControl → ItemsControl → SelectingItemsControl → TreeView
```
作为 `ItemsControl`,TreeView 可以通过 `Items` 集合直接添加 `TreeViewItem`,也可以通过 `ItemsSource` + `ItemTemplate` / `TreeDataTemplate` 进行数据绑定。Avalonia TreeView 提供了基础的选择模型(`SelectedItem` / `SelectedItems` / `SelectionMode`)以及节点容器化管理。
**Avalonia TreeView 提供的基础属性:**
| 属性 | 说明 |
|---|---|
| `Items` | 直接添加 `TreeViewItem` 子节点 |
| `ItemsSource` | 数据绑定源 |
| `ItemTemplate` | 项模板(`TreeDataTemplate` 支持层级绑定) |
| `SelectedItem` | 当前选中项 |
| `SelectedItems` | 选中项集合(多选模式) |
| `SelectionMode` | 选择模式(`Single` / `Multiple` / `Toggle`) |
**Avalonia TreeViewItem 提供的基础属性:**
| 属性 | 说明 |
|---|---|
| `Header` | 节点标题内容 |
| `HeaderTemplate` | 标题模板 |
| `IsExpanded` | 节点是否展开 |
| `IsSelected` | 节点是否选中 |
| `Level` | 节点层级(只读,自动计算) |
| `Items` | 子节点集合 |
### AtomUI 的扩展设计
AtomUI `TreeView` 在 Avalonia TreeView 的基础上,遵循 Ant Design 5.0 规范做了以下扩展:
| 扩展能力 | 实现方式 | 设计动机 |
|---|---|---|
| **复选框勾选** | `ToggleType` 枚举 + `IsChecked` 三态属性 | 对齐 Ant Design 的 `checkable`,支持 CheckBox 和 Radio 两种模式 |
| **父子联动** | `IsCheckStrictly` 属性 + 递归勾选算法 | 勾选父节点自动勾选所有子节点,取消时反之 |
| **拖拽排序** | `IsDraggable` 属性 + 拖拽指示线渲染 | 对齐 Ant Design 的 `draggable`,支持拖放重排节点 |
| **搜索过滤** | `Filter` + `FilterValue` + `FilterHighlightStrategy` | 对齐 Ant Design 的搜索功能,支持高亮匹配、隐藏未匹配等策略 |
| **异步加载** | `DataLoader` + `ITreeItemNodeLoader` 接口 | 对齐 Ant Design 的 `loadData`,展开时按需加载子节点 |
| **连接线** | `IsShowLine` 属性 + `OnRender` 自绘 | 对齐 Ant Design 的 `showLine`,绘制节点间连接线 |
| **悬浮模式** | `NodeHoverMode` 三种模式 | 不同场景下的悬浮高亮效果 |
| **节点图标** | `IsShowIcon` / `IsShowLeafIcon` + `PathIcon` | 对齐 Ant Design 的 `showIcon`,支持自定义节点图标 |
| **自定义开关图标** | `SwitcherExpandIcon` / `SwitcherCollapseIcon` 等 | 对齐 Ant Design 的 `switcherIcon`,支持替换展开/折叠图标 |
| **展开/折叠动画** | `IsMotionEnabled` + `ExpandMotion` / `CollapseMotion` | 展开/折叠带缓动动画,提升交互体验 |
| **默认路径** | `DefaultExpandedPaths` / `DefaultSelectedPaths` / `DefaultCheckedPaths` | 对齐 Ant Design 的 `defaultExpandedKeys` 等,通过路径指定初始状态 |
| **空数据提示** | `EmptyIndicator` / `IsShowEmptyIndicator` | 当无数据时展示空状态指示器 |
| **表单集成** | `IFormItemAware` 接口 | 可参与 FormItem 验证流程 |
| **Design Token** | `TreeViewToken` + `RegisterTokenResourceScope` | 所有视觉值从全局/组件级 Token 派生 |
---
## 功能详解
### 节点展开与折叠
TreeView 支持多种方式控制节点展开状态:
- **手动展开**:点击节点前的展开/折叠图标(Switcher)
- **全部展开**:设置 `IsDefaultExpandAll="True"` 或调用 `ExpandAll()` 方法
- **全部折叠**:调用 `CollapseAll()` 方法
- **路径展开**:通过 `DefaultExpandedPaths` 指定初始展开路径
- **自动展开父节点**:设置 `IsAutoExpandParent="True"`(默认值),选中子节点时自动展开其父节点
展开/折叠带有缓动动画(`ExpandMotion` / `CollapseMotion`),可通过 `IsMotionEnabled` 属性控制是否启用。
### 复选框勾选(ToggleType)
通过 `ToggleType` 属性启用节点勾选功能:
| ToggleType | 行为 |
|---|---|
| `None` | 不显示勾选控件(默认) |
| `CheckBox` | 显示 CheckBox,支持三态(选中/半选/未选) |
| `Radio` | 显示 Radio,同组内单选 |
**父子联动机制**(`IsCheckStrictly == false` 时,默认行为):
- 勾选父节点 → 递归勾选所有可勾选子节点
- 取消父节点 → 递归取消所有子节点
- 子节点全部勾选 → 父节点自动变为「选中」状态
- 子节点部分勾选 → 父节点自动变为「半选」(Indeterminate)状态
- 子节点全部取消 → 父节点自动变为「未选」状态
设置 `IsCheckStrictly="True"` 可关闭父子联动,每个节点独立勾选。
### 拖拽排序(IsDraggable)
设置 `IsDraggable="True"` 启用拖拽功能:
- 拖动时显示拖拽预览浮层(`DragPreviewAdorner`)
- 拖拽过程中渲染指示线,指示插入位置(上方/下方/内部)
- 不允许将父节点拖入自身子节点中(防止循环引用)
- 拖拽完成后自动调整节点位置
拖拽提供完整的事件回调:`ItemDragStarted`、`ItemDragEnter`、`ItemDragLeave`、`ItemDragOver`、`ItemDragCompleted`、`ItemDropped`。
### 搜索过滤(Filter)
TreeView 内置搜索过滤功能,通过设置 `FilterValue` 触发过滤:
1. 设置 `Filter` 属性(默认使用 `DefaultTreeItemFilter`,按 `Header` 文本匹配)
2. 设置 `FilterValue` 为搜索关键词
3. 通过 `FilterHighlightStrategy` 控制过滤行为
**过滤高亮策略**(`TreeFilterHighlightStrategy`,可组合):
| 策略 | 说明 |
|---|---|
| `HighlightedMatch` | 高亮匹配文本 |
| `HighlightedWhole` | 高亮整个匹配节点 |
| `BoldedMatch` | 加粗匹配文本 |
| `ExpandPath` | 自动展开匹配节点的祖先路径 |
| `HideUnMatched` | 隐藏未匹配的节点 |
| `All` | 以上全部(默认) |
### 异步加载(DataLoader)
通过 `DataLoader` 属性设置异步数据加载器:
- 必须使用 `ItemsSource` 绑定数据(不支持手动添加 `TreeViewItem` 模式)
- 展开非叶子节点时自动调用 `ITreeItemNodeLoader.LoadAsync()`
- 加载过程中节点显示旋转 Loading 图标
- 设置 `TreeItemNode.IsLeaf = true` 的节点不会触发加载
### 连接线(ShowLine)
设置 `IsShowLine="True"` 启用节点间连接线:
- 连接线通过 `TreeViewItem.Render()` 自绘,使用 `BorderBrush` 颜色
- `IsSwitcherRotation` 控制展开/折叠图标是旋转动画还是替换图标
- 当 `IsShowLine="True"` 时建议设置 `IsSwitcherRotation="False"`,使用展开/折叠两个不同图标
### 空数据指示器
当 TreeView 无数据(`Items` 为空或过滤无结果)时:
- `IsShowEmptyIndicator="True"`(默认启用)自动显示空状态指示器
- 可通过 `EmptyIndicator` / `EmptyIndicatorTemplate` 自定义空状态内容
---
## 与 Ant Design 规范的对齐程度
| 特性 | Ant Design (React) | AtomUI (Avalonia) | 对齐情况 |
|---|---|---|---|
| 展开/折叠 | ✅ 基础能力 | ✅ `IsExpanded` + 动画 | ✅ 完全对齐 |
| 复选框 `checkable` | ✅ `checkable` 属性 | ✅ `ToggleType=CheckBox` | ✅ 完全对齐 |
| 父子联动 `checkStrictly` | ✅ `checkStrictly` | ✅ `IsCheckStrictly` | ✅ 完全对齐 |
| 拖拽 `draggable` | ✅ `draggable` | ✅ `IsDraggable` | ✅ 完全对齐 |
| 连接线 `showLine` | ✅ `showLine` | ✅ `IsShowLine` | ✅ 完全对齐 |
| 节点图标 `showIcon` | ✅ `showIcon` | ✅ `IsShowIcon` | ✅ 完全对齐 |
| 默认展开 `defaultExpandAll` | ✅ `defaultExpandAll` | ✅ `IsDefaultExpandAll` | ✅ 完全对齐 |
| 默认展开键 `defaultExpandedKeys` | ✅ 按 key 指定 | ✅ `DefaultExpandedPaths` | ✅ 对齐(使用路径而非 key) |
| 默认选中键 `defaultSelectedKeys` | ✅ 按 key 指定 | ✅ `DefaultSelectedPaths` | ✅ 对齐(使用路径而非 key) |
| 默认勾选键 `defaultCheckedKeys` | ✅ 按 key 指定 | ✅ `DefaultCheckedPaths` | ✅ 对齐(使用路径而非 key) |
| 异步加载 `loadData` | ✅ 回调函数 | ✅ `DataLoader` + `ITreeItemNodeLoader` | ✅ 对齐(接口模式) |
| 搜索过滤 | ✅ `filterTreeNode` | ✅ `Filter` + `FilterValue` | ✅ 完全对齐 |
| 自定义开关图标 `switcherIcon` | ✅ ReactNode | ✅ `SwitcherExpandIcon` 等 | ✅ 对齐 |
| Block 节点 `blockNode` | ✅ `blockNode` | ✅ `NodeHoverMode=Block` | ✅ 对齐 |
| 虚拟滚动 `virtual` | ✅ 虚拟列表 | ❌ 暂未支持 | ⚠️ 待支持 |
| 目录模式 `Tree.DirectoryTree` | ✅ 独立组件 | ❌ 暂未提供 | ⚠️ 可通过样式模拟 |
---
## 继承关系
```
Avalonia.Controls.Primitives.TemplatedControl
└── Avalonia.Controls.ItemsControl
└── Avalonia.Controls.SelectingItemsControl
└── Avalonia.Controls.TreeView
└── AtomUI.Desktop.Controls.TreeView
├── implements IMotionAwareControl
└── implements IFormItemAware
```
`TreeView` 通过 `using AvaloniaTreeView = Avalonia.Controls.TreeView;` 别名引用 Avalonia 原生 TreeView,避免类名冲突。
**各层级职责划分:**
| 层级 | 提供的能力 |
|---|---|
| `ItemsControl` | Items 集合管理、ItemsSource 数据绑定、ItemTemplate 模板化 |
| `SelectingItemsControl` | 选择模型(SelectedItem / SelectedItems / SelectionMode)、选择事件 |
| `Avalonia.Controls.TreeView` | 树形容器化管理、`TreeViewItem` 容器创建、展开/折叠基础逻辑 |
| `AtomUI.Desktop.Controls.TreeView` | Ant Design 视觉体系(连接线/图标/悬浮模式)、CheckBox/Radio 勾选、拖拽排序、搜索过滤、异步加载、默认路径、Design Token 集成、展开折叠动画、空数据指示器、表单集成 |
**实现的共享接口:**
| 接口 | 定义位置 | 作用 |
|---|---|---|
| `IMotionAwareControl` | `AtomUI.Controls.Shared` | 支持 `IsMotionEnabled` 动画开关 |
| `IFormItemAware` | `AtomUI.Controls.Shared` | 可作为 `FormItem` 的子控件参与表单验证 |
---
## 关联类型
### TreeViewItem
`TreeViewItem` 是 TreeView 的节点容器控件,继承自 `Avalonia.Controls.TreeViewItem`,实现了 `IRadioButton` 和 `ITreeItemNode` 接口。
```
Avalonia.Controls.Primitives.TemplatedControl
└── Avalonia.Controls.ContentControl (HeaderedItemsControl)
└── Avalonia.Controls.TreeViewItem
└── AtomUI.Desktop.Controls.TreeViewItem
├── implements IRadioButton
└── implements ITreeItemNode
```
每个 TreeViewItem 代表树中的一个节点,支持:
- 标题内容(`Header`)
- 图标(`Icon`)
- 三态勾选(`IsChecked`)
- 叶子节点标识(`IsLeaf`)
- 加载状态(`IsLoading`)
- 节点值(`Value`)
- 指示器启用(`IsIndicatorEnabled`)
- 节点唯一标识(`ItemKey`)
### ITreeItemNode
树节点数据接口,用于数据绑定模式下描述节点数据。继承自 `ITreeNode<ITreeItemNode>`。
```csharp
public interface ITreeItemNode : ITreeNode<ITreeItemNode>
{
bool? IsChecked { get; set; }
bool IsSelected { get; set; }
bool IsExpanded { get; set; }
bool IsIndicatorEnabled { get; set; }
string? GroupName { get; }
bool IsLeaf { get; }
object? Value { get; set; }
bool IsEnabled { get; set; }
void UpdateParentNode(ITreeItemNode? parentNode);
}
```
### TreeItemNode
`ITreeItemNode` 的默认实现(`record` 类型),支持声明式初始化和父子关系自动维护。当向 `Children` 集合添加子节点时,会自动设置 `ParentNode`。
### ITreeItemNodeLoader
异步数据加载器接口,用于展开节点时按需加载子节点数据。
```csharp
public interface ITreeItemNodeLoader
{
Task<TreeItemLoadResult> LoadAsync(ITreeItemNode targetTreeItem, CancellationToken token);
}
```
### ITreeItemFilter
自定义过滤器接口,用于控制树节点的搜索匹配逻辑。
```csharp
public interface ITreeItemFilter
{
bool Filter(TreeView treeView, TreeViewItem treeViewItem, object? filterValue);
}
```
### FloatableTreeView
`TreeView` 的子类,额外支持 `TreeViewFlyout` 弹出层集成,用于下拉树选择器(如 `TreeSelect`)等场景。提供 `IsOpen`、`Opened`、`Closed` 等弹出状态管理。
---
## 源码位置
| 层级 | 文件路径 | 说明 |
|---|---|---|
| 控件类(主文件) | `src/AtomUI.Desktop.Controls/TreeView/TreeView.cs` | TreeView 主类 |
| 控件类(过滤) | `src/AtomUI.Desktop.Controls/TreeView/TreeView.Filter.cs` | 搜索过滤相关逻辑 |
| 控件类(异步加载) | `src/AtomUI.Desktop.Controls/TreeView/TreeView.AsyncItemDataLoad.cs` | 异步加载逻辑 |
| 控件类(拖拽) | `src/AtomUI.Desktop.Controls/TreeView/TreeView.DragAndDrop.cs` | 拖拽排序逻辑 |
| 节点控件 | `src/AtomUI.Desktop.Controls/TreeView/TreeViewItem.cs` | 树节点容器控件 |
| 节点数据接口 | `src/AtomUI.Desktop.Controls/TreeView/ITreeItemNode.cs` | 节点数据接口定义 |
| 节点数据实现 | `src/AtomUI.Desktop.Controls/TreeView/TreeItemNode.cs` | 节点数据默认实现 |
| 浮动树 | `src/AtomUI.Desktop.Controls/TreeView/FloatableTreeView.cs` | 弹出层集成 |
| 过滤接口 | `src/AtomUI.Desktop.Controls/TreeView/ITreeItemFilter.cs` | 自定义过滤器接口 |
| 伪类常量 | `src/AtomUI.Desktop.Controls/TreeView/TreeViewPseudoClass.cs` | 伪类定义 |
| Token 定义 | `src/AtomUI.Desktop.Controls/TreeView/TreeViewToken.cs` | 组件级 Design Token |
| 异步加载接口 | `src/AtomUI.Desktop.Controls/TreeView/DataLoad/ITreeItemNodeLoader.cs` | 数据加载器接口 |
| 加载结果 | `src/AtomUI.Desktop.Controls/TreeView/DataLoad/TreeItemLoadResult.cs` | 加载结果模型 |
| 事件参数 | `src/AtomUI.Desktop.Controls/TreeView/EventArgs/` | 各类事件参数定义 |
| 主题模板 | `src/AtomUI.Desktop.Controls/TreeView/Themes/TreeViewTheme.axaml` | ControlTheme AXAML |
| 主题代码 | `src/AtomUI.Desktop.Controls/TreeView/Themes/TreeViewTheme.cs` | 主题 code-behind |
| 模板常量 | `src/AtomUI.Desktop.Controls/TreeView/Themes/TreeViewThemeConstants.cs` | 模板部件名称常量 |
| Gallery 示例 | `controlgallery/AtomUIGallery/ShowCases/Views/DataDisplay/TreeViewShowCase.axaml` | 使用范例 |
---
## 模板结构
TreeView 的模板由三层组成:
### TreeView 模板
```
Border#Frame ← 主框架(承载背景、边框)
└── ScrollViewer ← 滚动容器
└── ItemsPresenter ← 节点列表
```
### TreeViewItem 模板
```
Panel ← 节点根面板
├── TreeViewItemHeader (Header) ← 节点标题区域
│ ├── NodeSwitcherButton (PART_NodeSwitcherButton) ← 展开/折叠按钮
│ ├── CheckBox / RadioButton ← 勾选控件(由 ToggleType 决定)
│ ├── IconPresenter (PART_IconPresenter) ← 节点图标
│ └── ContentPresenter ← 标题内容
└── BaseMotionActor (PART_ItemsPresenterMotionActor) ← 子节点容器(带动画)
└── ItemsPresenter ← 子节点列表
```
### 模板部件常量
| 常量名 | 值 | 说明 |
|---|---|---|
| `TreeViewItemThemeConstants.ItemsPresenterMotionActorPart` | `"PART_ItemsPresenterMotionActor"` | 子节点动画容器 |
| `TreeViewItemHeaderThemeConstants.IconPresenterPart` | `"PART_IconPresenter"` | 图标展示器 |
| `TreeViewItemHeaderThemeConstants.NodeSwitcherButtonPart` | `"PART_NodeSwitcherButton"` | 展开/折叠按钮 |
| `TreeViewItemHeaderThemeConstants.HeaderContentFramePart` | `"PART_HeaderContentFrame"` | 标题内容框架 |