11 KiB
DropdownButton 下拉按钮
概述
下拉按钮(DropdownButton)是一个附带下拉菜单的按钮控件。点击或悬浮按钮后,会弹出一个菜单列表供用户选择操作。它将按钮的触发交互和菜单的选项展示结合在一起,是 Ant Design Dropdown 组件的 AtomUI 实现。
DropdownButton 继承自 Button(AtomUI 的桌面端 Button 控件),因此完整具备 Button 的所有能力(按钮类型、尺寸、危险样式、图标、加载状态、波纹效果等),同时额外提供了弹出菜单的管理能力。
设计原理
Ant Design 的下拉菜单设计哲学
Ant Design 中 Dropdown 是一个独立的行为组件,包裹在任意触发元素外层,为其附加下拉菜单弹出功能。Dropdown.Button 则是其带按钮外观的变体——一个将「按钮」与「下拉菜单」融合的复合控件,用户既能直接点击按钮执行主操作,又能通过下拉箭头访问更多操作。
核心设计思想:
| 设计维度 | 设计意图 |
|---|---|
| 操作聚合 | 将多个相关操作收纳到一个下拉菜单中,减少界面上的按钮数量 |
| 触发灵活 | 支持点击(Click)和悬浮(Hover)两种触发方式,适应不同场景 |
| 位置可控 | 弹出菜单支持多种方位(上下 × 左中右),自动适应可用空间 |
| 视觉一致 | 按钮部分与 Button 组件保持一致的视觉体系 |
Avalonia Button 基础能力
DropdownButton 的继承链经过 AtomUI Button → Avalonia Button,因此天然具备 Avalonia Button 的全部基础能力:
Content/ContentTemplate— 任意内容承载Command/CommandParameter— MVVM 命令绑定HotKey— 键盘快捷键Click事件 — 按钮点击语义事件IsEnabled— 启用/禁用
同时也继承了 AtomUI Button 的全部扩展能力:五种按钮类型、三种形状、三种尺寸、危险/幽灵样式、加载状态、波纹效果、Design Token 集成等。
AtomUI 的扩展设计
AtomUI 将 Ant Design 的 Dropdown 和 Dropdown.Button 统一为 DropdownButton:它本身就是一个 Button,同时内置了 Flyout 弹出管理机制。弹出的菜单通过 DropdownFlyout 属性(类型为 MenuFlyout)配置。
| 扩展能力 | 实现方式 | 设计动机 |
|---|---|---|
| 弹出菜单 | DropdownFlyout 属性(MenuFlyout) |
声明式配置下拉菜单内容 |
| 触发方式 | TriggerType 枚举(Click / Hover) |
对齐 Ant Design 的 trigger 属性 |
| 弹出位置 | Placement / PlacementAnchor / PlacementGravity |
精细控制弹出方位 |
| 箭头指示 | IsShowArrow / IsPointAtCenter |
弹出菜单上显示指向触发元素的箭头 |
| 延迟策略 | MouseEnterDelay / MouseLeaveDelay |
控制悬浮触发的进入/离开延迟 |
| 下拉指示器 | OpenIndicator / IsShowOpenIndicator |
按钮右侧的下拉箭头图标(默认 DownOutlined) |
| 菜单项事件 | MenuItemClicked 路由事件 |
统一监听弹出菜单中任意菜单项的点击 |
功能详解
弹出机制
DropdownButton 内部使用 FlyoutStateHelper 来管理弹出状态。FlyoutStateHelper 是 AtomUI 封装的 Flyout 生命周期管理器,负责:
- 触发方式管理:根据
TriggerType决定是 Click 还是 Hover 触发弹出 - 延迟控制:
MouseEnterDelay/MouseLeaveDelay控制 Hover 模式下的弹出/关闭延迟 - 生命周期:在控件 Attach/Detach 到可视化树时自动管理弹出层的注册与销毁
- 属性中继:DropdownButton 上设置的
Placement、IsShowArrow、IsPointAtCenter、MarginToAnchor、IsMotionEnabled等属性自动中继(Relay Bind)到MenuFlyout
下拉指示器
DropdownButton 默认在按钮内容右侧显示一个下拉箭头图标(DownOutlined),初始化时自动设置。通过 OpenIndicator 属性可自定义图标,通过 IsShowOpenIndicator 可隐藏。
当按钮没有 Content、ContentTemplate、Icon 且不处于 Loading 状态时,内容区域自动隐藏,仅显示下拉指示器。这由内部的 IsContentVisible 属性智能控制。
菜单项点击事件
DropdownButton 提供了 MenuItemClicked 路由事件(RoutingStrategies.Bubble),当用户点击弹出菜单中的某个菜单项时触发。事件参数 FlyoutMenuItemClickedEventArgs 包含被点击的 MenuItem 引用,避免了手动为每个 MenuItem 单独注册 Click 事件。
弹出位置
支持 6 种标准弹出位置(对齐 Ant Design 的 placement 属性),默认为 BottomEdgeAlignedLeft:
| Placement 值 | 说明 | 对应 Ant Design |
|---|---|---|
BottomEdgeAlignedLeft |
底部左对齐(默认) | bottomLeft |
Bottom |
底部居中 | bottom |
BottomEdgeAlignedRight |
底部右对齐 | bottomRight |
TopEdgeAlignedLeft |
顶部左对齐 | topLeft |
Top |
顶部居中 | top |
TopEdgeAlignedRight |
顶部右对齐 | topRight |
点击隐藏策略
当 TriggerType 为 Click 时,点击弹出层外部即关闭菜单。当 TriggerType 为 Hover 时,仅当点击位置不在按钮区域内才关闭菜单。这确保了 Hover 模式下不会因误触按钮区域而关闭菜单。
与 Ant Design 规范的对齐程度
| 特性 | Ant Design (React) | AtomUI (Avalonia) | 对齐情况 |
|---|---|---|---|
触发方式 trigger |
✅ click / hover / contextMenu |
✅ TriggerType(Click / Hover) |
⚠️ 不支持右键触发 |
菜单弹出位置 placement |
✅ 6 个方向 | ✅ Placement(6 个方向 + 更多) |
✅ 完全对齐 |
箭头指示 arrow |
✅ 布尔/对象 | ✅ IsShowArrow + IsPointAtCenter |
✅ 完全对齐 |
按钮类型 type |
✅ type prop |
✅ ButtonType(继承 Button) |
✅ 完全对齐 |
菜单项点击 onMenuClick |
✅ 回调函数 | ✅ MenuItemClicked 路由事件 |
✅ 完全对齐 |
| 悬浮延迟 | ✅ mouseEnterDelay / mouseLeaveDelay |
✅ MouseEnterDelay / MouseLeaveDelay |
✅ 完全对齐 |
禁用 disabled |
✅ 布尔 | ✅ IsEnabled(继承 Button) |
✅ 完全对齐 |
按钮尺寸 size |
✅ large / middle / small |
✅ SizeType(继承 Button) |
✅ 完全对齐 |
按钮危险样式 danger |
✅ 布尔 | ✅ IsDanger(继承 Button) |
✅ 完全对齐 |
加载状态 loading |
✅ 布尔 | ✅ IsLoading(继承 Button) |
✅ 完全对齐 |
受控弹出 open / onOpenChange |
✅ 受控模式 | ❌ 暂未提供 | ⚠️ 待支持 |
继承关系
Avalonia.Controls.Primitives.TemplatedControl
└── Avalonia.Controls.ContentControl
└── Avalonia.Controls.Button (ICommandSource, IClickableControl)
└── AtomUI.Desktop.Controls.Button
├── implements ISizeTypeAware
├── implements IWaveSpiritAwareControl
├── implements ICompactSpaceAware
└── implements IFormItemAware
└── AtomUI.Desktop.Controls.DropdownButton
└── + DropdownFlyout / TriggerType / Placement / 箭头 / 菜单项事件
各层级职责划分:
| 层级 | 提供的能力 |
|---|---|
Avalonia.Controls.Button |
指针交互 → Click 事件、ICommand 绑定、IsPressed 状态、HotKey 快捷键、Flyout 弹出 |
AtomUI.Desktop.Controls.Button |
Ant Design 视觉体系(五种类型/三种形状/三种尺寸)、Design Token 集成、WaveSpirit 波纹、加载状态、危险/幽灵样式、紧凑空间适配、表单集成 |
AtomUI.Desktop.Controls.DropdownButton |
DropdownFlyout 管理、TriggerType、Placement、箭头指示、菜单项事件、下拉指示器 |
源码位置
| 层级 | 文件路径 | 说明 |
|---|---|---|
| 控件类 | src/AtomUI.Desktop.Controls/Buttons/DropdownButton.cs |
桌面端下拉按钮实现 |
| 主题模板 | src/AtomUI.Desktop.Controls/Buttons/Themes/DropdownButtonTheme.axaml |
ControlTheme AXAML |
| 主题代码 | src/AtomUI.Desktop.Controls/Buttons/Themes/DropdownButtonTheme.cs |
主题 code-behind |
| 模板常量 | src/AtomUI.Desktop.Controls/Buttons/Themes/DropdownButtonThemeConstants.cs |
模板部件名称常量 |
| Gallery 示例 | controlgallery/AtomUIGallery/ShowCases/Views/Navigation/DropdownButtonShowCase.axaml |
使用范例 |
模板结构
DropdownButton 的模板继承自 Button 模板(通过 ControlTheme.BasedOn),为每种 ButtonType(Default / Primary / Link / Text)分别定义了独立的模板变体。核心差异在于:在 Button 模板的内容区域右侧增加了一个下拉指示器 IconPresenter,使用 DockPanel 布局使指示器固定在右侧。
Panel
├── WaveSpiritDecorator (PART_WaveSpirit) ← 点击波纹效果层(仅 Default / Primary 类型)
├── Border#ShadowsFrame ← 阴影层(仅 Default 类型)
├── Border#Frame (主框架) ← 背景、边框、圆角
│ └── DockPanel (PART_RootLayout) ← 内容布局容器
│ ├── IconPresenter (PART_DropdownIndicator) ← 下拉指示器(DockPanel.Dock="Right")
│ └── StackPanel (PART_MainInfoLayout) ← 内容区域(填充剩余空间)
│ ├── LoadingOutlined (PART_LoadingIcon) ← 加载旋转图标
│ ├── IconPresenter (PART_ButtonIcon) ← 按钮图标
│ └── ContentPresenter (PART_ContentPresenter) ← 文本/自定义内容
模板变体差异:
| ButtonType | 波纹效果 | 阴影层 | 说明 |
|---|---|---|---|
Default |
✅ WaveSpiritDecorator | ✅ Border#ShadowsFrame | 白色背景 + 边框 + 阴影 |
Primary |
✅ WaveSpiritDecorator | ❌ | 主色调填充 |
Link |
❌ | ❌ | 无边框无背景 |
Text |
❌ | ❌ | 无边框无背景 |
模板部件常量
| 常量名 | 值 | 说明 |
|---|---|---|
DropdownButtonThemeConstants.DropdownIndicatorPart |
"PART_DropdownIndicator" |
下拉指示器图标 |
ButtonThemeConstants.WaveSpiritPart |
"PART_WaveSpirit" |
波纹动画装饰器 |
ButtonThemeConstants.RootLayoutPart |
"PART_RootLayout" |
根布局面板 |
ButtonThemeConstants.MainInfoLayoutPart |
"PART_MainInfoLayout" |
主信息布局面板 |
ButtonThemeConstants.LoadingIconPart |
"PART_LoadingIcon" |
加载旋转图标 |
ButtonThemeConstants.ButtonIconPart |
"PART_ButtonIcon" |
按钮图标展示器 |
ButtonThemeConstants.ContentPresenterPart |
"PART_ContentPresenter" |
内容展示器 |