Files

241 lines
10 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.
# Menu 菜单
## 概述
菜单(Menu)用于为页面和功能提供导航菜单。AtomUI 的菜单控件家族包括三个核心组件:`Menu`(顶部菜单栏)、`MenuItem`(菜单项)和 `ContextMenu`(右键上下文菜单),以及辅助的 `MenuSeparator`(分隔线)和 `MenuFlyout`(弹出菜单)。
AtomUI 的 `Menu` 控件对应 [Ant Design 5.0 Menu](https://ant.design/components/menu-cn) 组件中的水平菜单模式,在 .NET / Avalonia 平台上提供一致的交互体验。
> **注意**:Ant Design 的 `Menu` 组件还包含 Inline(内嵌)和 Vertical(垂直弹出)模式,这些在 AtomUI 中由独立的 `NavMenu` 控件实现,详见 `docs/Desktop.Controls/NavMenu/` 文档。
---
## 设计原理
### Ant Design 的 Menu 设计哲学
Ant Design 的 Menu 定位为:**应用和页面的功能导航载体**。核心设计目标包括:
- **层级嵌套**:支持多级子菜单,通过弹出层展示下级菜单项
- **多种模式**:水平(horizontal)、垂直弹出(vertical)、内嵌展开(inline)
- **丰富的菜单项类型**:普通项、分组、分隔线、CheckBox/Radio 切换项
- **图标支持**:菜单项可包含前置图标
- **快捷键提示**:显示键盘快捷方式
### Avalonia 基础能力
AtomUI 的 `Menu`、`MenuItem`、`ContextMenu` 分别继承自 Avalonia 的同名控件,通过别名避免冲突:
```csharp
using AvaloniaMenu = Avalonia.Controls.Menu;
using AvaloniaMenuItem = Avalonia.Controls.MenuItem;
using AvaloniaContextMenu = Avalonia.Controls.ContextMenu;
```
**Avalonia Menu 体系的核心能力:**
| 能力 | 说明 |
|---|---|
| `Menu` | 顶部水平菜单栏,继承自 `MenuBase`,管理顶层菜单项 |
| `MenuItem` | 菜单项,继承自 `HeaderedSelectingItemsControl`,支持嵌套子项、图标、快捷键 |
| `ContextMenu` | 右键弹出菜单,绑定到控件的 `ContextMenu` 属性 |
| `MenuBase.IsOpen` | 菜单是否处于打开状态 |
| `MenuItem.IsSubMenuOpen` | 子菜单是否打开 |
| `MenuItem.ToggleType` | 支持 None/CheckBox/Radio 三种切换类型 |
| `MenuItem.InputGesture` | 快捷键手势绑定 |
| `MenuItem.Header` | 菜单项标题内容 |
| `MenuItem.Command` | 点击命令绑定 |
### AtomUI 的扩展设计
| 扩展能力 | 实现方式 | 设计动机 |
|---|---|---|
| **三种尺寸** | `ISizeTypeAware`(Large/Middle/Small) | 菜单栏适配不同布局密度 |
| **弹出动画** | `IMotionAwareControl` + Popup 动画 | 子菜单弹出/关闭的过渡动画 |
| **自定义图标** | `PathIcon` 类型的 `Icon` 属性(重写基类) | 使用 Ant Design 图标集 |
| **数据驱动** | `IMenuItemData` / `MenuItemData` / `MenuSeparatorData` | 支持通过数据模型和 `ItemsSource` 生成菜单结构 |
| **可滚动子菜单** | `DisplayPageSize` + `ScrollViewer` | 子菜单项过多时自动出现滚动 |
| **分隔线** | `MenuSeparator` + `MenuSeparatorData` | 自定义分隔线控件,从 Token 系统获取样式 |
| **CheckBox/Radio** | `ToggleType` + `IsCheckStateChanged` 事件 | 菜单项支持选中/取消选中交互 |
| **Design Token** | `MenuToken` + `RegisterTokenResourceScope` | 所有视觉值从 Token 系统派生 |
| **OverlayLayer** | `ShouldUseOverlayLayer` 属性 | 支持使用 OverlayLayer 作为 Popup 宿主 |
---
## 功能详解
### Menu(顶部菜单栏)
`Menu` 是水平排列的顶部菜单栏,包含多个顶层 `MenuItem`。每个顶层项可有子菜单,点击后以 Popup 弹出展示。
**关键行为:**
- 顶层菜单项使用 `TopLevelMenuItemTheme` 主题,样式与子菜单项不同
- 使用自定义 `DefaultMenuInteractionHandler` 处理菜单交互
- 关闭时支持动画(`IsMotionEnabled`),逐级关闭子菜单的 Popup
### MenuItem(菜单项)
`MenuItem` 是菜单的基本构建块。支持以下特性:
| 特性 | 说明 |
|---|---|
| 嵌套子菜单 | `MenuItem` 可包含子 `MenuItem`,子菜单以 Popup 弹出 |
| 图标 | `Icon` 属性(`PathIcon?`),与 Ant Design 图标集配合 |
| 快捷键 | `InputGesture` 属性,显示在菜单项右侧 |
| CheckBox/Radio 切换 | `ToggleType` 属性,支持 None/CheckBox/Radio |
| 分组 | `GroupName` 属性,用于 Radio 类型的互斥分组 |
| 禁用 | `IsEnabled = false`,灰色调显示 |
| 危险样式 | 通过 Token 支持危险色(`DangerItemColor`) |
### ContextMenu(右键菜单)
`ContextMenu` 通过右键触发弹出的浮动菜单。与 `Menu` 共享 `MenuToken`,使用相同的菜单项样式。
**关键行为:**
- 自动管理 Popup 生命周期
- 支持窗口失活时自动关闭
- 支持检测外部点击关闭
- 支持动画开关
### MenuSeparator(分隔线)
自定义分隔线控件,继承自 Avalonia `Separator`。在菜单中水平显示,用于分组菜单项。
### 数据驱动模式
通过 `IMenuItemData` 接口和 `MenuItemData` 类,支持使用数据模型 + `ItemTemplate` 生成菜单结构:
```csharp
public interface IMenuItemData : ITreeNode<IMenuItemData>
{
KeyGesture? InputGesture { get; }
}
```
`MenuSeparatorData` 继承自 `MenuItemData`,当数据项为 `MenuSeparatorData` 类型时,自动创建 `MenuSeparator` 容器。
---
## 与 Ant Design 规范的对齐程度
| 特性 | Ant Design (React) | AtomUI (Avalonia) | 对齐情况 |
|---|---|---|---|
| 水平顶部菜单 | ✅ `mode="horizontal"` | ✅ `Menu`(水平菜单栏) | ✅ 对齐 |
| 垂直弹出菜单 | ✅ `mode="vertical"` | ✅ `NavMenu Mode="Vertical"` | ✅ 对齐(独立控件) |
| 内嵌展开菜单 | ✅ `mode="inline"` | ✅ `NavMenu Mode="Inline"` | ✅ 对齐(独立控件) |
| 子菜单嵌套 | ✅ `children` 属性 | ✅ `MenuItem` 嵌套子项 | ✅ 对齐 |
| 图标 | ✅ `icon` 属性 | ✅ `Icon`(`PathIcon`) | ✅ 对齐 |
| 禁用 | ✅ `disabled` | ✅ `IsEnabled` | ✅ 对齐 |
| 分隔线 | ✅ `type: "divider"` | ✅ `MenuSeparator` / `MenuSeparatorData` | ✅ 对齐 |
| 选中状态 | ✅ `selectedKeys` | ✅ `ToggleType` + `IsChecked` | ✅ 对齐 |
| 快捷键提示 | ❌ 无原生支持 | ✅ `InputGesture` | ✅ 超集 |
| CheckBox/Radio | ❌ 无原生支持 | ✅ `ToggleType` | ✅ 超集 |
| 数据驱动 | ✅ `items` 属性 | ✅ `ItemsSource` + `IMenuItemData` | ✅ 对齐 |
| 右键菜单 | ❌(使用 Dropdown) | ✅ `ContextMenu` | ✅ 扩展 |
| 弹出动画 | ✅ 内置动画 | ✅ `IsMotionEnabled` | ✅ 对齐 |
| 危险菜单项 | ✅ `danger` 属性 | ✅ `DangerItemColor` Token | ✅ 对齐 |
---
## 继承关系
### Menu
```
Avalonia.Controls.MenuBase
└── Avalonia.Controls.Menu
└── AtomUI.Desktop.Controls.Menu
├── implements ISizeTypeAware
└── implements IMotionAwareControl
```
### MenuItem
```
Avalonia.Controls.HeaderedSelectingItemsControl
└── Avalonia.Controls.MenuItem
└── AtomUI.Desktop.Controls.MenuItem
└── implements IMenuItemData
```
### ContextMenu
```
Avalonia.Controls.MenuBase
└── Avalonia.Controls.ContextMenu
└── AtomUI.Desktop.Controls.ContextMenu
├── implements ISizeTypeAware
└── implements IMotionAwareControl
```
### MenuSeparator
```
Avalonia.Controls.Separator
└── AtomUI.Desktop.Controls.MenuSeparator
```
---
## 源码位置
| 层级 | 文件路径 | 说明 |
|---|---|---|
| Menu | `src/AtomUI.Desktop.Controls/Menu/Menu.cs` | 顶部菜单栏 |
| MenuItem | `src/AtomUI.Desktop.Controls/Menu/MenuItem.cs` | 菜单项 |
| ContextMenu | `src/AtomUI.Desktop.Controls/Menu/ContextMenu.cs` | 右键上下文菜单 |
| MenuSeparator | `src/AtomUI.Desktop.Controls/Menu/MenuSeparator.cs` | 菜单分隔线 |
| MenuItemData | `src/AtomUI.Desktop.Controls/Menu/MenuItemData.cs` | 数据模型接口和类 |
| MenuSeparatorData | `src/AtomUI.Desktop.Controls/Menu/MenuSeparatorData.cs` | 分隔线数据模型 |
| MenuItemPseudoClass | `src/AtomUI.Desktop.Controls/Menu/MenuItemPseudoClass.cs` | 伪类常量 |
| Token 定义 | `src/AtomUI.Desktop.Controls/Menu/MenuToken.cs` | 组件级 Design Token(40+ 属性) |
| Menu 主题 | `src/AtomUI.Desktop.Controls/Menu/Themes/MenuTheme.axaml` | Menu ControlTheme |
| MenuItem 主题 | `src/AtomUI.Desktop.Controls/Menu/Themes/MenuItemTheme.axaml` | MenuItem ControlTheme |
| ContextMenu 主题 | `src/AtomUI.Desktop.Controls/Menu/Themes/ContextMenuTheme.axaml` | ContextMenu ControlTheme |
| TopLevelMenuItem 主题 | `src/AtomUI.Desktop.Controls/Menu/Themes/TopLevelMenuItemTheme.axaml` | 顶层菜单项 ControlTheme |
| MenuSeparator 主题 | `src/AtomUI.Desktop.Controls/Menu/Themes/MenuSeparatorTheme.axaml` | 分隔线 ControlTheme |
| 模板常量 | `src/AtomUI.Desktop.Controls/Menu/Themes/MenuThemeConstants.cs` | 模板部件常量 |
| Gallery 示例 | `controlgallery/AtomUIGallery/ShowCases/Views/Navigation/MenuShowCase.axaml` | 使用范例 |
---
## 模板结构
### Menu 模板
```
Border (背景、边框、圆角)
└── ItemsPresenter (PART_ItemsPresenter)
└── StackPanel (Horizontal) ← 水平排列顶层菜单项
```
### MenuItem 模板
```
Panel
├── Border#Frame (背景、圆角、内间距)
│ └── Grid (5 列)
│ ├── Panel#ToggleItemsLayout ← CheckBox/Radio 切换控件
│ │ ├── CheckBox (PART_ToggleCheckbox)
│ │ └── RadioButton (PART_ToggleRadio)
│ ├── IconPresenter#ItemIconPresenter ← 图标
│ ├── ContentPresenter#ItemTextPresenter ← 标题文本
│ ├── TextBlock#InputGestureText ← 快捷键提示
│ └── RightOutlined#MenuIndicatorIcon ← 子菜单箭头
└── Popup (PART_Popup) ← 子菜单弹窗
└── Border#PopupFrame
└── ScrollViewer
└── ItemsPresenter (PART_ItemsPresenter)
```
### 模板部件常量
| 类 | 常量名 | 值 | 说明 |
|---|---|---|---|
| `MenuThemeConstants` | `ItemsPresenterPart` | `"PART_ItemsPresenter"` | Menu 项列表 |
| `MenuItemThemeConstants` | `ToggleCheckboxPart` | `"PART_ToggleCheckbox"` | CheckBox 切换 |
| `MenuItemThemeConstants` | `ToggleRadioPart` | `"PART_ToggleRadio"` | Radio 切换 |
| `MenuItemThemeConstants` | `ItemsPresenterPart` | `"PART_ItemsPresenter"` | 子菜单项列表 |
| `ContextMenuThemeConstants` | `ItemsPresenterPart` | `"PART_ItemsPresenter"` | ContextMenu 项列表 |
| `TopLevelMenuItemThemeConstants` | `ItemsPresenterPart` | `"PART_ItemsPresenter"` | 顶层菜单项子菜单 |