docs(TreeView): add comprehensive design, API, styling, token and usage documentation

This commit is contained in:
chinboy
2026-04-14 10:31:07 +08:00
parent 73accd6426
commit 684d72c185
5 changed files with 1644 additions and 0 deletions
+373
View File
@@ -0,0 +1,373 @@
# 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"` | 标题内容框架 |
@@ -0,0 +1,332 @@
# TreeView API 参考
## 命名空间
```csharp
namespace AtomUI.Desktop.Controls;
```
AXAML 命名空间前缀:`atom`(`xmlns:atom="https://atomui.net"`)
---
## 枚举类型
### TreeItemHoverMode
节点悬浮高亮模式。
| 值 | 说明 |
|---|---|
| `Default` | 仅高亮节点文字区域(默认) |
| `Block` | 高亮整个节点行(包含缩进区域) |
| `WholeLine` | 高亮整行(从容器左到右) |
### TreeFilterHighlightStrategy(Flags 枚举)
搜索过滤高亮策略,支持组合使用。
| 值 | 说明 |
|---|---|
| `HighlightedMatch` (0x01) | 高亮匹配文本 |
| `HighlightedWhole` (0x02) | 高亮整个匹配节点 |
| `BoldedMatch` (0x04) | 加粗匹配文本 |
| `ExpandPath` (0x08) | 自动展开匹配节点的祖先路径 |
| `HideUnMatched` (0x10) | 隐藏未匹配的节点 |
| `All` | 以上全部策略组合(默认值) |
### ItemToggleType(来自 `AtomUI.Controls`)
节点勾选控件类型。
| 值 | 说明 |
|---|---|
| `None` | 不显示勾选控件(默认) |
| `CheckBox` | 显示 CheckBox 复选框 |
| `Radio` | 显示 Radio 单选按钮 |
---
## TreeView 公共属性
### 核心属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `IsAutoExpandParent` | `bool` | `true` | 是否自动展开父节点 |
| `IsDraggable` | `bool` | `false` | 是否启用拖拽排序 |
| `IsShowIcon` | `bool` | `false` | 是否显示节点图标 |
| `IsShowLine` | `bool` | `false` | 是否显示节点间连接线 |
| `IsDefaultExpandAll` | `bool` | `false` | 是否默认展开所有节点 |
| `IsShowLeafIcon` | `bool` | `false` | 是否显示叶子节点图标 |
| `IsSwitcherRotation` | `bool` | `true` | 展开/折叠图标是否使用旋转动画(`false` 时使用替换图标) |
| `IsSelectable` | `bool` | `true` | 节点是否可选中 |
| `IsCheckStrictly` | `bool` | `false` | 是否严格模式(`true` 时关闭父子联动勾选) |
| `NodeHoverMode` | `TreeItemHoverMode` | `Default` | 节点悬浮高亮模式 |
| `ToggleType` | `ItemToggleType` | `None` | 节点勾选控件类型(None/CheckBox/Radio) |
| `IsMotionEnabled` | `bool` | 跟随全局 Token | 是否启用展开/折叠动画(共享属性) |
### 自定义图标属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `SwitcherExpandIcon` | `IconTemplate?` | `null` | 自定义展开图标模板 |
| `SwitcherCollapseIcon` | `IconTemplate?` | `null` | 自定义折叠图标模板 |
| `SwitcherRotationIcon` | `IconTemplate?` | `null` | 自定义旋转图标模板(`IsSwitcherRotation=True` 时生效) |
| `SwitcherLoadingIcon` | `IconTemplate?` | `null` | 自定义加载中图标模板 |
| `SwitcherLeafIcon` | `IconTemplate?` | `null` | 自定义叶子节点图标模板 |
### 默认路径属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `DefaultExpandedPaths` | `IList<TreeNodePath>?` | `null` | 默认展开的节点路径列表 |
| `DefaultSelectedPaths` | `IList<TreeNodePath>?` | `null` | 默认选中的节点路径列表 |
| `DefaultCheckedPaths` | `IList<TreeNodePath>?` | `null` | 默认勾选的节点路径列表 |
### 异步加载属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `DataLoader` | `ITreeItemNodeLoader?` | `null` | 异步数据加载器 |
### 搜索过滤属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `Filter` | `ITreeItemFilter?` | `DefaultTreeItemFilter` | 自定义过滤器 |
| `FilterValue` | `object?` | `null` | 过滤关键词 |
| `FilterHighlightStrategy` | `TreeFilterHighlightStrategy` | `All` | 过滤高亮策略 |
| `FilterResultCount` | `int` | `0` | 过滤匹配结果数量(只读) |
| `FilterHighlightForeground` | `IBrush?` | `null` | 过滤高亮前景色(默认使用 Token 中的 `FilterHighlightColor`) |
### 动画属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `OpenMotion` | `AbstractMotion?` | `ExpandMotion` | 展开动画 |
| `CloseMotion` | `AbstractMotion?` | `CollapseMotion` | 折叠动画 |
### 空数据指示器属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `EmptyIndicator` | `object?` | `null` | 空数据指示器内容 |
| `EmptyIndicatorTemplate` | `IDataTemplate?` | `null` | 空数据指示器模板 |
| `IsShowEmptyIndicator` | `bool` | `true` | 是否在无数据时显示空状态指示器 |
| `EmptyIndicatorPadding` | `Thickness` | `default` | 空数据指示器内边距 |
### 勾选相关属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `CheckedItems` | `IList` | `AvaloniaList<object>` | 已勾选项集合(可读写) |
### 继承自 Avalonia.Controls.TreeView 的常用属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `Items` | `ItemCollection` | — | 直接添加子节点 |
| `ItemsSource` | `IEnumerable?` | `null` | 数据绑定源 |
| `ItemTemplate` | `IDataTemplate?` | `null` | 项数据模板(推荐 `TreeDataTemplate`) |
| `SelectedItem` | `object?` | `null` | 当前选中项 |
| `SelectedItems` | `IList?` | — | 选中项集合(多选) |
| `SelectionMode` | `SelectionMode` | `Single` | 选择模式 |
---
## TreeView 公共方法
| 方法名 | 签名 | 说明 |
|---|---|---|
| `ExpandAll` | `void ExpandAll(bool? motionEnabled = null)` | 展开所有节点。可选参数指定是否启用动画 |
| `CollapseAll` | `void CollapseAll(bool? motionEnabled = null)` | 折叠所有节点。可选参数指定是否启用动画 |
| `CheckedSubTree` | `void CheckedSubTree(TreeViewItem viewItem)` | 勾选指定节点及其所有子节点 |
| `UnCheckedSubTree` | `void UnCheckedSubTree(TreeViewItem viewItem)` | 取消勾选指定节点及其所有子节点 |
| `FilterTreeNode` | `void FilterTreeNode()` | 手动触发树节点过滤 |
---
## TreeView 事件
| 事件名 | 类型 | 说明 |
|---|---|---|
| `CheckedItemsChanged` | `EventHandler<TreeViewCheckedItemsChangedEventArgs>` | 勾选项发生变化时触发 |
| `TreeItemLoaded` | `EventHandler<TreeViewItemLoadedEventArgs>` | 异步加载节点完成时触发 |
| `ItemExpanded` | `EventHandler<TreeItemExpandedEventArgs>` | 节点展开时触发 |
| `ItemCollapsed` | `EventHandler<TreeItemCollapsedEventArgs>` | 节点折叠时触发 |
| `ItemClicked` | `EventHandler<TreeItemClickedEventArgs>` | 节点被点击时触发 |
| `ItemContextMenuRequest` | `EventHandler<TreeItemContextMenuEventArgs>` | 节点右键菜单请求时触发 |
| `ItemDragStarted` | `EventHandler<TreeViewDragStartedEventArgs>` | 拖拽开始时触发 |
| `ItemDragCompleted` | `EventHandler<TreeViewDragCompletedEventArgs>` | 拖拽完成时触发 |
| `ItemDragEnter` | `EventHandler<TreeViewDragEnterEventArgs>` | 拖拽进入节点时触发 |
| `ItemDragLeave` | `EventHandler<TreeViewDragLeaveEventArgs>` | 拖拽离开节点时触发 |
| `ItemDragOver` | `EventHandler<TreeViewDragOverEventArgs>` | 拖拽经过节点时触发 |
| `ItemDropped` | `EventHandler<TreeViewDroppedEventArgs>` | 拖拽放下时触发 |
### 事件参数说明
**TreeViewCheckedItemsChangedEventArgs**:
| 属性 | 类型 | 说明 |
|---|---|---|
| `AddedItems` | `IList` | 新增的勾选项 |
| `RemovedItems` | `IList` | 移除的勾选项 |
**TreeViewDragStartedEventArgs / TreeViewDragCompletedEventArgs**:
| 属性 | 类型 | 说明 |
|---|---|---|
| `ViewItem` | `TreeViewItem` | 被拖拽的节点 |
**TreeViewDragEnterEventArgs / TreeViewDragLeaveEventArgs / TreeViewDragOverEventArgs**:
| 属性 | 类型 | 说明 |
|---|---|---|
| `DraggedViewItem` | `TreeViewItem` | 被拖拽的节点 |
| `DragOverViewItem` | `TreeViewItem` | 经过的目标节点 |
**TreeViewDroppedEventArgs**:
| 属性 | 类型 | 说明 |
|---|---|---|
| `DraggedViewItem` | `TreeViewItem` | 被拖拽的节点 |
| `DroppedItem` | `TreeViewItem?` | 放下目标节点 |
| `DropIndex` | `int` | 插入位置索引 |
**TreeItemExpandedEventArgs / TreeItemCollapsedEventArgs / TreeItemClickedEventArgs**:
| 属性 | 类型 | 说明 |
|---|---|---|
| `ViewItem` | `TreeViewItem` | 相关的节点 |
---
## TreeView 伪类
| 伪类 | 常量 | 触发条件 |
|---|---|---|
| `:draggable` | `StdPseudoClass.Draggable` | `IsDraggable == true` |
### 继承的标准伪类
| 伪类 | 触发条件 |
|---|---|
| `:pointerover` | 鼠标悬浮 |
| `:disabled` | `IsEnabled == false` |
| `:focus` | 获得焦点 |
---
## TreeViewItem 公共属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `Icon` | `PathIcon?` | `null` | 节点自定义图标 |
| `IsChecked` | `bool?` | `false` | 三态勾选状态(`true` / `false` / `null` 表示半选) |
| `IsLeaf` | `bool` | 自动推断 | 是否为叶子节点(只读,由子节点数和 DataLoader 决定) |
| `IsLoading` | `bool` | `false` | 是否处于异步加载中 |
| `Value` | `object?` | `null` | 节点绑定值 |
| `GroupName` | `string?` | `null` | Radio 模式下的分组名称 |
| `IsIndicatorEnabled` | `bool` | `true` | 是否启用勾选指示器(`false` 时节点不可勾选) |
| `ItemKey` | `EntityKey?` | `null` | 节点唯一标识键 |
### 继承自 Avalonia.Controls.TreeViewItem 的常用属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `Header` | `object?` | `null` | 节点标题内容 |
| `HeaderTemplate` | `IDataTemplate?` | `null` | 标题模板 |
| `IsExpanded` | `bool` | `false` | 是否展开 |
| `IsSelected` | `bool` | `false` | 是否选中 |
| `Items` | `ItemCollection` | — | 子节点集合 |
| `Level` | `int` | 自动计算 | 节点层级(只读) |
| `IsEnabled` | `bool` | `true` | 是否启用 |
---
## TreeViewItem 事件
| 事件名 | 类型 | 说明 |
|---|---|---|
| `Click` | `EventHandler<RoutedEventArgs>` | 节点被点击时触发(路由事件,冒泡) |
| `ContextMenuRequest` | `EventHandler<RoutedEventArgs>` | 右键菜单请求事件(路由事件,冒泡+隧道) |
---
## TreeViewItem 伪类
| 伪类 | 常量 | 触发条件 |
|---|---|---|
| `:toggle` | `TreeViewPseudoClass.NodeToggleTypeCheckBox` | `ToggleType == CheckBox` |
| `:radio` | `TreeViewPseudoClass.NodeToggleTypeRadio` | `ToggleType == Radio` |
| `:checked` | `StdPseudoClass.Checked` | `IsChecked == true` |
### 继承的标准伪类
| 伪类 | 触发条件 |
|---|---|
| `:selected` | `IsSelected == true` |
| `:expanded` | `IsExpanded == true` |
| `:pointerover` | 鼠标悬浮 |
| `:disabled` | `IsEnabled == false` |
---
## 关联数据类型
### TreeItemNode(record)
`ITreeItemNode` 的默认实现,推荐用于 `ItemsSource` 数据绑定。
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `Header` | `object?` | `null` | 节点标题 |
| `ItemKey` | `EntityKey?` | `null` | 节点唯一标识 |
| `Icon` | `PathIcon?` | `null` | 节点图标 |
| `IsEnabled` | `bool` | `true` | 是否启用 |
| `IsChecked` | `bool?` | `false` | 勾选状态 |
| `IsSelected` | `bool` | `false` | 选中状态 |
| `IsExpanded` | `bool` | `false` | 展开状态 |
| `IsIndicatorEnabled` | `bool` | `true` | 是否可勾选 |
| `GroupName` | `string?` | `null` | Radio 分组名 |
| `IsLeaf` | `bool` | `false` | 是否叶子节点 |
| `Value` | `object?` | `null` | 节点值 |
| `Children` | `IList<ITreeItemNode>` | `[]` | 子节点列表 |
| `ParentNode` | `ITreeNode<ITreeItemNode>?` | 自动设置 | 父节点引用(只读) |
### TreeNodePath
节点路径,用于 `DefaultExpandedPaths` / `DefaultSelectedPaths` / `DefaultCheckedPaths`。
```csharp
// 路径格式:使用 "/" 分隔的 ItemKey 值
var path = new TreeNodePath("0-0/0-0-0/0-0-0-1");
```
### TreeItemLoadResult
异步加载结果模型。
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `IsSuccess` | `bool` | `true` | 是否加载成功 |
| `StatusCode` | `RpcStatusCode` | `Unknown` | 状态码 |
| `UserFriendlyMessage` | `string?` | `null` | 用户友好的错误消息 |
| `Data` | `IReadOnlyList<ITreeItemNode>?` | `null` | 加载的子节点数据 |
---
## 实现的接口
### IMotionAwareControl
```csharp
public bool IsMotionEnabled { get; set; }
```
控制展开/折叠动画是否启用。
### IFormItemAware
可作为 `FormItem` 的子控件参与表单验证流程。选择模式为 `Multiple` 时,表单值为 `SelectedItems`;否则为 `SelectedItem`。
@@ -0,0 +1,339 @@
# TreeView 自定义样式指南
TreeView 的视觉表现通过 `ControlTheme` + Design Token 系统控制。以下介绍几种常见的自定义方式。
---
## 1. 使用属性直接控制
最简单的方式是通过 TreeView 的公共属性来控制外观和行为:
```xml
<!-- 基本带复选框的树 -->
<atom:TreeView ToggleType="CheckBox">
<atom:TreeViewItem Header="parent 1" ItemKey="0-0">
<atom:TreeViewItem Header="child 1" ItemKey="0-0-0" />
<atom:TreeViewItem Header="child 2" ItemKey="0-0-1" />
</atom:TreeViewItem>
</atom:TreeView>
<!-- 显示连接线 + 图标 -->
<atom:TreeView IsShowLine="True"
IsShowIcon="True"
IsSwitcherRotation="False">
<atom:TreeViewItem Header="parent 1"
Icon="{antdicons:AntDesignIconProvider Kind=CarryOutOutlined}">
<atom:TreeViewItem Header="leaf"
Icon="{antdicons:AntDesignIconProvider Kind=CarryOutOutlined}" />
</atom:TreeViewItem>
</atom:TreeView>
<!-- Block 悬浮模式 -->
<atom:TreeView NodeHoverMode="Block"
IsDefaultExpandAll="True"
ToggleType="CheckBox">
<atom:TreeViewItem Header="parent">
<atom:TreeViewItem Header="child 1" IsEnabled="False" />
<atom:TreeViewItem Header="child 2" IsIndicatorEnabled="False" />
</atom:TreeViewItem>
</atom:TreeView>
<!-- 拖拽排序 -->
<atom:TreeView IsDraggable="True" NodeHoverMode="Block">
<atom:TreeViewItem Header="Item 1">
<atom:TreeViewItem Header="Sub Item 1" />
<atom:TreeViewItem Header="Sub Item 2" />
</atom:TreeViewItem>
<atom:TreeViewItem Header="Item 2" />
</atom:TreeView>
```
> 📖 完整示例参考:`controlgallery/AtomUIGallery/ShowCases/Views/DataDisplay/TreeViewShowCase.axaml`
---
## 2. 通过 Style 覆盖样式
可以在 AXAML 中使用 `Style` 对 TreeView 和 TreeViewItem 进行全局或局部样式覆盖:
### 全局设置连接线和展开行为
```xml
<Window.Styles>
<Style Selector="atom|TreeView">
<Setter Property="IsShowLine" Value="True" />
<Setter Property="IsDefaultExpandAll" Value="True" />
</Style>
</Window.Styles>
```
### 自定义选中节点样式
```xml
<Style Selector="atom|TreeViewItem:selected">
<Setter Property="FontWeight" Value="Bold" />
</Style>
```
### 自定义勾选模式下的节点
```xml
<!-- 勾选模式下禁用的节点降低透明度 -->
<Style Selector="atom|TreeViewItem:toggle:disabled">
<Setter Property="Opacity" Value="0.5" />
</Style>
```
### 根据悬浮模式定制背景
```xml
<!-- Block 模式下增加额外内边距 -->
<Style Selector="atom|TreeView[NodeHoverMode=Block]">
<Setter Property="Padding" Value="4" />
</Style>
```
---
## 3. 自定义展开/折叠图标
通过 `SwitcherRotationIcon`、`SwitcherExpandIcon`、`SwitcherCollapseIcon` 属性自定义展开/折叠图标:
### 旋转图标模式
```xml
<atom:TreeView IsSwitcherRotation="True">
<atom:TreeView.SwitcherRotationIcon>
<atom:IconTemplate>
<antdicons:RightOutlined />
</atom:IconTemplate>
</atom:TreeView.SwitcherRotationIcon>
<atom:TreeViewItem Header="parent" ItemKey="0-0">
<atom:TreeViewItem Header="child 1" ItemKey="0-0-0" />
</atom:TreeViewItem>
</atom:TreeView>
```
### 替换图标模式
```xml
<atom:TreeView IsSwitcherRotation="False">
<atom:TreeView.SwitcherExpandIcon>
<atom:IconTemplate>
<antdicons:PlusSquareOutlined />
</atom:IconTemplate>
</atom:TreeView.SwitcherExpandIcon>
<atom:TreeView.SwitcherCollapseIcon>
<atom:IconTemplate>
<antdicons:MinusSquareOutlined />
</atom:IconTemplate>
</atom:TreeView.SwitcherCollapseIcon>
<!-- 节点... -->
</atom:TreeView>
```
> 📖 参考:`controlgallery/AtomUIGallery/ShowCases/Views/DataDisplay/TreeViewShowCase.axaml` 中 "Customize collapse/expand icon" 示例。
---
## 4. 通过 TreeDataTemplate 数据绑定
使用 `ItemsSource` + `TreeDataTemplate` 实现 MVVM 数据驱动:
```xml
<atom:TreeView Name="MyTreeView" ToggleType="CheckBox">
<atom:TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}">
<atom:TextBlock Text="{Binding Header}" />
</TreeDataTemplate>
</atom:TreeView.ItemTemplate>
</atom:TreeView>
```
```csharp
// Code-behind 或 ViewModel
myTreeView.ItemsSource = new List<TreeItemNode>
{
new TreeItemNode()
{
Header = "parent 1",
ItemKey = "0-0",
Children = [
new TreeItemNode() { Header = "leaf 1", ItemKey = "0-0-0" },
new TreeItemNode() { Header = "leaf 2", ItemKey = "0-0-1" }
]
}
};
```
> 📖 参考:`controlgallery/AtomUIGallery/ShowCases/Views/DataDisplay/TreeViewShowCase.axaml` 中 "Generate by template" 示例。
---
## 5. 搜索过滤示例
通过配合 `SearchEdit` 实现树节点搜索:
```xml
<StackPanel Orientation="Vertical" Spacing="10">
<atom:SearchEdit PlaceholderText="Search"
SearchButtonClick="HandleFilterTreeClicked" />
<atom:TreeView Name="SearchTreeView">
<atom:TreeViewItem Header="0-0">
<atom:TreeViewItem Header="0-0-0">
<atom:TreeViewItem Header="0-0-0-0" />
</atom:TreeViewItem>
</atom:TreeViewItem>
</atom:TreeView>
</StackPanel>
```
```csharp
private void HandleFilterTreeClicked(object? sender, RoutedEventArgs e)
{
if (sender is SearchEdit searchEdit)
{
SearchTreeView.FilterValue = searchEdit.Text?.Trim();
}
}
```
> 📖 参考:`controlgallery/AtomUIGallery/ShowCases/Views/DataDisplay/TreeViewShowCase.axaml` 中 "Searchable" 示例。
---
## 6. 异步加载示例
通过 `DataLoader` 实现展开时异步加载子节点:
```xml
<atom:TreeView Name="AsyncLoadTree">
<atom:TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}">
<atom:TextBlock Text="{Binding Header}" />
</TreeDataTemplate>
</atom:TreeView.ItemTemplate>
</atom:TreeView>
```
```csharp
// 初始化数据
AsyncLoadTree.ItemsSource = new List<TreeItemNode>
{
new TreeItemNode() { Header = "Expand to load", ItemKey = "0" },
new TreeItemNode() { Header = "Expand to load", ItemKey = "1" },
new TreeItemNode() { Header = "Tree Node", ItemKey = "2", IsLeaf = true }
};
// 设置加载器
AsyncLoadTree.DataLoader = new TreeItemDataLoader();
```
```csharp
// 自定义加载器实现
public class TreeItemDataLoader : ITreeItemNodeLoader
{
public async Task<TreeItemLoadResult> LoadAsync(ITreeItemNode targetTreeItem, CancellationToken token)
{
await Task.Delay(1000, token); // 模拟异步加载
return new TreeItemLoadResult
{
IsSuccess = true,
Data = new List<ITreeItemNode>
{
new TreeItemNode() { Header = "Child 1", ItemKey = "child-1" },
new TreeItemNode() { Header = "Child 2", ItemKey = "child-2", IsLeaf = true }
}.AsReadOnly()
};
}
}
```
> 📖 参考:`controlgallery/AtomUIGallery/ShowCases/Views/DataDisplay/TreeViewShowCase.axaml` 中 "load data asynchronously" 示例。
---
## 7. 默认展开和选中路径
通过路径指定初始展开、选中和勾选状态:
```csharp
// 默认展开路径
myTreeView.DefaultExpandedPaths = new List<TreeNodePath>
{
new TreeNodePath("0-0/0-0-0"),
new TreeNodePath("0-0/0-0-1/0-0-1-1")
};
// 默认选中路径
myTreeView.DefaultSelectedPaths = new List<TreeNodePath>
{
new TreeNodePath("0-0/0-0-1")
};
// 默认勾选路径
myTreeView.DefaultCheckedPaths = new List<TreeNodePath>
{
new TreeNodePath("0-0/0-0-1/0-0-1-1")
};
```
> 📖 参考:`controlgallery/AtomUIGallery/ShowCases/Views/DataDisplay/TreeViewShowCase.axaml.cs` 中 `InitBasicTreeViewData` 方法。
---
## 8. 控制动画行为
```xml
<!-- 禁用展开/折叠动画 -->
<atom:TreeView IsMotionEnabled="False">
<!-- 节点... -->
</atom:TreeView>
```
```csharp
// 代码中全部展开(不带动画)
myTreeView.ExpandAll(motionEnabled: false);
// 代码中全部展开(带动画)
myTreeView.ExpandAll(motionEnabled: true);
```
---
## 样式选择器速查
> 说明:Avalonia 样式选择器中使用 `atom|TreeView` 语法引用 `atom` XML 命名空间下的类型,其中 `|` 是命名空间分隔符。
### TreeView 选择器
| 选择器 | 说明 |
|---|---|
| `atom\|TreeView` | 匹配所有 AtomUI TreeView 实例 |
| `atom\|TreeView:draggable` | 匹配启用了拖拽功能的 TreeView |
| `atom\|TreeView[NodeHoverMode=Block]` | 匹配 Block 悬浮模式的 TreeView |
| `atom\|TreeView[NodeHoverMode=WholeLine]` | 匹配 WholeLine 悬浮模式的 TreeView |
| `atom\|TreeView[IsShowLine=True]` | 匹配显示连接线的 TreeView |
| `atom\|TreeView:disabled` | 匹配禁用状态的 TreeView |
### TreeViewItem 选择器
| 选择器 | 说明 |
|---|---|
| `atom\|TreeViewItem` | 匹配所有树节点 |
| `atom\|TreeViewItem:selected` | 匹配选中的节点 |
| `atom\|TreeViewItem:expanded` | 匹配展开的节点 |
| `atom\|TreeViewItem:checked` | 匹配勾选的节点 |
| `atom\|TreeViewItem:toggle` | 匹配 CheckBox 勾选模式的节点 |
| `atom\|TreeViewItem:radio` | 匹配 Radio 单选模式的节点 |
| `atom\|TreeViewItem:disabled` | 匹配禁用的节点 |
| `atom\|TreeViewItem:pointerover` | 匹配鼠标悬浮的节点 |
### 组合选择器示例
| 选择器 | 说明 |
|---|---|
| `atom\|TreeViewItem:selected:not(:disabled)` | 非禁用状态下已选中的节点 |
| `atom\|TreeViewItem:toggle:checked` | CheckBox 模式下已勾选的节点 |
| `atom\|TreeView[IsShowLine=True] atom\|TreeViewItem:expanded` | 显示连接线的 TreeView 中展开的节点 |
| `atom\|TreeViewItem /template/ atom\|TreeViewItemHeader` | 访问节点模板内的标题区域 |
@@ -0,0 +1,133 @@
# TreeView Design Token
TreeView 控件使用 `TreeViewToken`(Token ID: `"TreeView"`)作为组件级 Design Token,所有视觉属性均从全局 `SharedToken` 派生,不硬编码任何具体值。
---
## Token 资源访问方式
在 AXAML 中使用组件级 Token:
```xml
{atom:TokenResource TreeViewHeaderHeight}
{atom:TokenResource TreeViewNodeHoverBg}
{atom:TokenResource TreeViewNodeSelectedBg}
```
在 AXAML 中使用全局共享 Token:
```xml
{atom:SharedTokenResource ColorPrimary}
{atom:SharedTokenResource ControlHeightSM}
{atom:SharedTokenResource ColorBorder}
```
---
## 组件级 Token 一览
以下是 `TreeViewToken` 定义的全部组件级 Token,按功能分组说明。
### 尺寸 Token
| Token 名 | 类型 | 来源 | 说明 |
|---|---|---|---|
| `HeaderHeight` | `double` | `SharedToken.ControlHeightSM` | 节点标题行高度 |
### 颜色 Token
| Token 名 | 类型 | 来源 | 说明 |
|---|---|---|---|
| `NodeHoverBg` | `Color` | `SharedToken.ControlItemBgHover` | 节点悬浮态背景色 |
| `NodeSelectedBg` | `Color` | `SharedToken.ControlItemBgActive` | 节点选中态背景色 |
| `DirectoryNodeSelectedColor` | `Color` | `SharedToken.ColorTextLightSolid` | 目录树节点选中文字颜色 |
| `DirectoryNodeSelectedBg` | `Color` | `SharedToken.ColorPrimary` | 目录树节点选中背景色 |
| `FilterHighlightColor` | `Color` | `SharedToken.ColorError` | 搜索过滤高亮颜色(默认使用错误色以醒目标识) |
### 间距 Token
| Token 名 | 类型 | 来源 | 说明 |
|---|---|---|---|
| `TreeItemMargin` | `Thickness` | `0, 0, 0, SharedToken.UniformlyPaddingXXS / 2` | 树节点的外边距(节点间的垂直间距) |
| `TreeItemHeaderPadding` | `Thickness` | `SharedToken.UniformlyPaddingXS, 0` | 树节点标题的内间距 |
| `TreeItemHeaderMargin` | `Thickness` | `SharedToken.UniformlyMarginXXS, 0, 0, 0` | 树节点标题的外间距 |
| `TreeNodeSwitcherMargin` | `Thickness` | `0, 0, SharedToken.UniformlyPaddingXS / 2, 0` | 展开/折叠按钮的外边距 |
| `TreeNodeIconMargin` | `Thickness` | `SharedToken.UniformlyPaddingXS / 2, 0, 0, 0` | 节点图标的外边距 |
### 拖拽 Token
| Token 名 | 类型 | 来源 | 说明 |
|---|---|---|---|
| `DragIndicatorLineWidth` | `double` | `SharedToken.LineWidthFocus` | 拖拽指示线的线宽 |
---
## 主题中使用的全局 SharedToken
除组件级 Token 外,TreeView 的 ControlTheme 还直接引用了以下全局 `SharedToken`:
| Token 资源键 | 使用场景 |
|---|---|
| `ControlHeightSM` | 节点标题行高度 |
| `ControlItemBgHover` | 节点悬浮背景色 |
| `ControlItemBgActive` | 节点选中背景色 |
| `ColorPrimary` | 目录树选中背景色、主题强调色 |
| `ColorTextLightSolid` | 目录树选中文字颜色 |
| `ColorError` | 搜索过滤高亮颜色 |
| `ColorText` | 节点文字默认颜色 |
| `ColorTextDisabled` | 禁用节点文字颜色 |
| `ColorBorder` | 连接线颜色 |
| `BorderThickness` | 连接线粗细 |
| `UniformlyPaddingXS` | 基础内间距单位 |
| `UniformlyPaddingXXS` | 微小内间距单位 |
| `UniformlyMarginXXS` | 微小外边距单位 |
| `LineWidthFocus` | 拖拽指示线宽度 |
| `EnableMotion` | 全局动画开关 |
| `FontSize` | 节点文字大小 |
| `BorderRadius` | 节点圆角半径 |
---
## Token 对外观的具体影响
### 节点状态与 Token 映射
| 节点状态 | 文字颜色 | 背景色 | 边框 |
|---|---|---|---|
| **正常** | `ColorText` | 透明 | 无 |
| **悬浮** | `ColorText` | `NodeHoverBg` | 无 |
| **选中** | `ColorText` | `NodeSelectedBg` | 无 |
| **禁用** | `ColorTextDisabled` | 透明 | 无 |
### 悬浮模式与 Token 映射
| 悬浮模式 | 高亮范围 | 使用 Token |
|---|---|---|
| `Default` | 节点文字区域 | `NodeHoverBg`(仅标题区域) |
| `Block` | 整个节点行(含缩进) | `NodeHoverBg`(扩展到完整行) |
| `WholeLine` | 从容器左到右完整行 | `NodeHoverBg`(扩展到容器宽度) |
### 连接线与 Token 映射
| 元素 | 使用 Token | 说明 |
|---|---|---|
| 纵向连接线 | `BorderBrush`(`ColorBorder`) | 父子节点间的垂直连接线 |
| 横向连接线 | `BorderBrush`(`ColorBorder`) | 叶子节点的水平连接线 |
| 线条粗细 | `BorderThickness` | 连接线的像素宽度 |
### 拖拽指示器与 Token 映射
| 元素 | 使用 Token | 说明 |
|---|---|---|
| 指示线 | `DragIndicatorLineWidth` | 拖拽时显示的位置指示线宽度 |
| 指示线颜色 | `ColorPrimary` | 拖拽指示线使用主题色 |
### 间距与 Token 映射
| 元素 | Token | 影响 |
|---|---|---|
| 节点之间的垂直间距 | `TreeItemMargin` | 控制节点列表的紧凑程度 |
| 标题文字内间距 | `TreeItemHeaderPadding` | 控制标题左右留白 |
| 标题与 Switcher 间距 | `TreeItemHeaderMargin` | 控制展开按钮和标题之间的距离 |
| 展开按钮右侧间距 | `TreeNodeSwitcherMargin` | 控制展开按钮与后续内容的间距 |
| 图标左侧间距 | `TreeNodeIconMargin` | 控制图标与前方内容的间距 |
@@ -0,0 +1,467 @@
# TreeView 使用文档
本文档介绍 AtomUI TreeView 控件的各种使用方式,示例代码摘自 Gallery 演示程序。
> 📖 Gallery 源码位置:`controlgallery/AtomUIGallery/ShowCases/Views/DataDisplay/TreeViewShowCase.axaml`
---
## 前置准备
在 AXAML 中使用 TreeView,需要引入以下命名空间:
```xml
xmlns:atom="https://atomui.net"
xmlns:antdicons="https://atomui.net/icons/antdesign"
```
在 C# 代码中引入:
```csharp
using AtomUI.Desktop.Controls; // TreeView, TreeViewItem, TreeItemNode 等
using AtomUI.Controls; // ItemToggleType, TreeNodePath, EntityKey 等共享类型
```
---
## 1. 基本用法(直接声明节点)
最简单的方式是在 AXAML 中直接声明 `TreeViewItem` 节点:
```xml
<atom:TreeView ToggleType="CheckBox">
<atom:TreeViewItem Header="parent 1" ItemKey="0-0">
<atom:TreeViewItem Header="parent 1-0" ItemKey="0-0-0">
<atom:TreeViewItem Header="leaf" ItemKey="0-0-0-0" IsEnabled="False" />
<atom:TreeViewItem Header="leaf" ItemKey="0-0-0-1" />
</atom:TreeViewItem>
<atom:TreeViewItem Header="parent 1-1" ItemKey="0-0-1">
<atom:TreeViewItem Header="sss" ItemKey="0-0-1-0">
<atom:TreeViewItem Header="ccc" ItemKey="0-0-1-0-0" />
</atom:TreeViewItem>
</atom:TreeViewItem>
</atom:TreeViewItem>
</atom:TreeView>
```
**要点**:
- `ToggleType="CheckBox"` 启用复选框勾选功能
- `ItemKey` 用于唯一标识节点,配合 `DefaultExpandedPaths` 等路径功能使用
- `IsEnabled="False"` 可禁用特定节点
> 📖 参考:Gallery 中 "Basic" 示例。
---
## 2. 通过数据模板生成(ItemsSource + TreeDataTemplate)
推荐的 MVVM 数据驱动方式,使用 `ItemsSource` 绑定数据:
```xml
<atom:TreeView Name="BasicTplTree" ToggleType="CheckBox">
<atom:TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}">
<atom:TextBlock Text="{Binding Header}" />
</TreeDataTemplate>
</atom:TreeView.ItemTemplate>
</atom:TreeView>
```
```csharp
// Code-behind
BasicTplTree.ItemsSource = new List<TreeItemNode>
{
new TreeItemNode()
{
Header = "parent 1",
ItemKey = "0-0",
Children = [
new TreeItemNode()
{
Header = "parent 1-0",
ItemKey = "0-0-0",
Children = [
new TreeItemNode() { Header = "leaf 1", ItemKey = "0-0-0-0", IsEnabled = false },
new TreeItemNode() { Header = "leaf 2", ItemKey = "0-0-0-1" }
]
},
new TreeItemNode()
{
Header = "parent 1-1",
ItemKey = "0-0-1",
Children = [
new TreeItemNode() { Header = "sss", ItemKey = "0-0-1-0" }
]
}
]
}
};
```
**要点**:
- `TreeDataTemplate` 的 `ItemsSource` 绑定到 `Children` 属性实现递归层级展示
- `TreeItemNode` 是 `ITreeItemNode` 的默认实现,支持 `Header`、`ItemKey`、`Children`、`Icon` 等属性
- 使用 ReactiveUI 绑定:`this.OneWayBind(ViewModel, vm => vm.TreeNodes, v => v.BasicTplTree.ItemsSource)`
> 📖 参考:Gallery 中 "Generate by template" 示例。
---
## 3. Block 悬浮模式
设置 `NodeHoverMode="Block"` 使节点悬浮效果扩展到整行:
```xml
<atom:TreeView ToggleType="CheckBox"
IsDefaultExpandAll="True"
NodeHoverMode="Block">
<atom:TreeViewItem Header="parent">
<atom:TreeViewItem Header="child 1" IsEnabled="False" />
<atom:TreeViewItem Header="child 2" IsIndicatorEnabled="False" />
</atom:TreeViewItem>
</atom:TreeView>
```
**要点**:
- `NodeHoverMode="Block"` 高亮整个节点行
- `IsIndicatorEnabled="False"` 禁用该节点的勾选指示器(CheckBox 不显示)
> 📖 参考:Gallery 中 "Block Node" 示例。
---
## 4. 显示连接线和图标
通过 `IsShowLine`、`IsShowIcon`、`IsShowLeafIcon`、`IsSwitcherRotation` 属性控制树形结构的视觉展示:
```xml
<atom:TreeView IsShowLine="{Binding ShowLineSwitchChecked}"
IsShowIcon="{Binding ShowIconSwitchChecked}"
IsShowLeafIcon="{Binding ShowLeafIconSwitchChecked}"
NodeHoverMode="{Binding TreeViewNodeHoverMode}"
IsSwitcherRotation="{Binding ShowLineSwitchChecked,
Converter={x:Static BoolConverters.Not}}">
<atom:TreeViewItem Header="parent 1"
Icon="{antdicons:AntDesignIconProvider Kind=CarryOutOutlined}"
IsExpanded="True">
<atom:TreeViewItem Header="parent 1-0"
Icon="{antdicons:AntDesignIconProvider Kind=CarryOutOutlined}"
IsExpanded="True">
<atom:TreeViewItem Header="leaf 1"
Icon="{antdicons:AntDesignIconProvider Kind=CarryOutOutlined}" />
</atom:TreeViewItem>
</atom:TreeViewItem>
</atom:TreeView>
```
**要点**:
- `IsShowLine="True"` 启用节点间连接线
- `IsShowIcon="True"` 显示节点图标
- `IsShowLeafIcon="True"` 显示叶子节点图标
- 当启用连接线时,建议将 `IsSwitcherRotation` 设为 `False`,使用展开/折叠两个不同图标
> 📖 参考:Gallery 中 "Tree with line" 示例。
---
## 5. 拖拽排序
设置 `IsDraggable="True"` 启用拖拽功能:
```xml
<atom:TreeView IsDraggable="True" NodeHoverMode="Block">
<atom:TreeViewItem Header="0-0">
<atom:TreeViewItem Header="0-0-0">
<atom:TreeViewItem Header="0-0-0-0" />
<atom:TreeViewItem Header="0-0-0-1" />
</atom:TreeViewItem>
<atom:TreeViewItem Header="0-0-1">
<atom:TreeViewItem Header="0-0-1-0" />
</atom:TreeViewItem>
</atom:TreeViewItem>
<atom:TreeViewItem Header="0-1">
<atom:TreeViewItem Header="0-1-0" />
</atom:TreeViewItem>
</atom:TreeView>
```
**要点**:
- 拖拽时显示预览浮层和位置指示线
- 支持拖到节点上方、下方或内部(作为子节点)
- 不允许将父节点拖入自身子节点中
> 📖 参考:Gallery 中 "draggable" 示例。
---
## 6. 自定义展开/折叠图标
通过 `SwitcherRotationIcon` 或 `SwitcherExpandIcon`/`SwitcherCollapseIcon` 属性自定义图标:
```xml
<atom:TreeView IsSwitcherRotation="True">
<atom:TreeView.SwitcherRotationIcon>
<atom:IconTemplate>
<antdicons:RightOutlined />
</atom:IconTemplate>
</atom:TreeView.SwitcherRotationIcon>
<atom:TreeViewItem Header="parent 1" ItemKey="0-0">
<atom:TreeViewItem Header="parent 1-0" ItemKey="0-0-0">
<atom:TreeViewItem Header="leaf" ItemKey="0-0-0-0" />
</atom:TreeViewItem>
</atom:TreeViewItem>
</atom:TreeView>
```
> 📖 参考:Gallery 中 "Customize collapse/expand icon" 示例。
---
## 7. 异步加载
通过 `DataLoader` 属性提供异步加载器,实现展开节点时按需加载子节点:
```xml
<atom:TreeView Name="AsyncLoadTree">
<atom:TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}">
<atom:TextBlock Text="{Binding Header}" />
</TreeDataTemplate>
</atom:TreeView.ItemTemplate>
</atom:TreeView>
```
```csharp
// 初始化根节点
AsyncLoadTree.ItemsSource = new List<TreeItemNode>
{
new TreeItemNode() { Header = "Expand to load", ItemKey = "0" },
new TreeItemNode() { Header = "Expand to load", ItemKey = "1" },
new TreeItemNode() { Header = "Tree Node", ItemKey = "2", IsLeaf = true }
};
// 设置异步加载器
AsyncLoadTree.DataLoader = new TreeItemDataLoader();
```
```csharp
// 自定义加载器实现 ITreeItemNodeLoader 接口
public class TreeItemDataLoader : ITreeItemNodeLoader
{
public async Task<TreeItemLoadResult> LoadAsync(ITreeItemNode targetTreeItem, CancellationToken token)
{
await Task.Delay(1000, token); // 模拟异步加载
return new TreeItemLoadResult
{
IsSuccess = true,
Data = new List<ITreeItemNode>
{
new TreeItemNode() { Header = "Child 1", ItemKey = "child-1" },
new TreeItemNode() { Header = "Child 2", ItemKey = "child-2", IsLeaf = true }
}.AsReadOnly()
};
}
}
```
**要点**:
- 必须使用 `ItemsSource` 绑定数据(不支持直接声明 `TreeViewItem` 模式)
- 非叶子节点展开时自动调用 `DataLoader`,加载过程中显示旋转图标
- 设置 `IsLeaf = true` 的节点不会触发加载
> 📖 参考:Gallery 中 "load data asynchronously" 示例。
---
## 8. 搜索过滤
通过设置 `FilterValue` 属性触发树节点搜索过滤:
### 对 ItemsSource 数据源搜索
```xml
<StackPanel Orientation="Vertical" Spacing="10">
<atom:SearchEdit PlaceholderText="Search"
SearchButtonClick="HandleFilterItemsSourceTreeClicked" />
<atom:TreeView Name="SearchTreeViewByItemsSource">
<atom:TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="atom:ITreeItemNode">
<atom:TextBlock Text="{Binding Header}" />
</TreeDataTemplate>
</atom:TreeView.ItemTemplate>
</atom:TreeView>
</StackPanel>
```
### 对直接声明的节点搜索
```xml
<StackPanel Orientation="Vertical" Spacing="10">
<atom:SearchEdit PlaceholderText="Search"
SearchButtonClick="HandleFilterTreeClicked" />
<atom:TreeView Name="SearchTreeView">
<atom:TreeViewItem Header="0-0">
<atom:TreeViewItem Header="0-0-0">
<atom:TreeViewItem Header="0-0-0-0" />
</atom:TreeViewItem>
</atom:TreeViewItem>
</atom:TreeView>
</StackPanel>
```
```csharp
private void HandleFilterTreeClicked(object? sender, RoutedEventArgs e)
{
if (sender is SearchEdit searchEdit)
{
SearchTreeView.FilterValue = searchEdit.Text?.Trim();
}
}
```
**要点**:
- 默认使用 `DefaultTreeItemFilter`,按节点 `Header` 文本匹配
- `FilterHighlightStrategy` 控制过滤行为(高亮匹配/隐藏未匹配/展开路径等)
- 将 `FilterValue` 设为 `null` 或空字符串可清除过滤
> 📖 参考:Gallery 中 "Searchable" 和 "Searchable by TreeDataTemplate" 示例。
---
## 9. 默认展开、选中和勾选
通过路径属性指定初始状态:
```csharp
// 默认展开路径
myTreeView.DefaultExpandedPaths = new List<TreeNodePath>
{
new TreeNodePath("0-0/0-0-0"),
new TreeNodePath("0-0/0-0-1/0-0-1-1")
};
// 默认选中路径
myTreeView.DefaultSelectedPaths = new List<TreeNodePath>
{
new TreeNodePath("0-0/0-0-1")
};
// 默认勾选路径(需配合 ToggleType="CheckBox")
myTreeView.DefaultCheckedPaths = new List<TreeNodePath>
{
new TreeNodePath("0-0/0-0-1/0-0-1-1")
};
```
**路径格式**:使用 `/` 分隔的 `ItemKey` 值,从根节点到目标节点的完整路径。
> 📖 参考:`controlgallery/AtomUIGallery/ShowCases/Views/DataDisplay/TreeViewShowCase.axaml.cs` 中 `InitBasicTreeViewData` 方法。
---
## 10. 获取选中和勾选数据
### 获取选中节点
```csharp
// 单选模式
object? selected = myTreeView.SelectedItem;
// 多选模式
IList? selectedItems = myTreeView.SelectedItems;
```
### 获取勾选节点
```csharp
// 获取所有勾选项
IList checkedItems = myTreeView.CheckedItems;
```
### 监听勾选变化
```csharp
myTreeView.CheckedItemsChanged += (sender, args) =>
{
foreach (var added in args.AddedItems)
{
Console.WriteLine($"Checked: {added}");
}
foreach (var removed in args.RemovedItems)
{
Console.WriteLine($"Unchecked: {removed}");
}
};
```
---
## 11. 程序化控制
### 展开/折叠所有节点
```csharp
// 展开全部(不带动画)
myTreeView.ExpandAll(motionEnabled: false);
// 展开全部(带动画)
myTreeView.ExpandAll(motionEnabled: true);
// 折叠全部
myTreeView.CollapseAll(motionEnabled: false);
```
### 勾选/取消勾选子树
```csharp
// 勾选指定节点及其所有子节点
if (myTreeView.ContainerFromIndex(0) is TreeViewItem item)
{
myTreeView.CheckedSubTree(item);
}
// 取消勾选
myTreeView.UnCheckedSubTree(item);
```
---
## 常见组合模式
### 文件浏览器风格
```xml
<atom:TreeView IsShowIcon="True"
IsShowLine="True"
IsSwitcherRotation="False"
NodeHoverMode="WholeLine">
<!-- 节点... -->
</atom:TreeView>
```
### 权限配置风格
```xml
<atom:TreeView ToggleType="CheckBox"
IsDefaultExpandAll="True"
NodeHoverMode="Block">
<!-- 节点... -->
</atom:TreeView>
```
### 搜索 + 异步加载
```xml
<StackPanel Orientation="Vertical" Spacing="10">
<atom:SearchEdit PlaceholderText="Search"
SearchButtonClick="HandleSearch" />
<atom:TreeView Name="MyTree"
DataLoader="{Binding MyDataLoader}">
<atom:TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}">
<atom:TextBlock Text="{Binding Header}" />
</TreeDataTemplate>
</atom:TreeView.ItemTemplate>
</atom:TreeView>
</StackPanel>
```