# Dialog 对话框 ## 概述 对话框(Dialog)是一个从界面中弹出的模态或非模态面板,用于承载需要用户关注或交互的内容。典型使用场景包括:确认操作、表单填写、详情展示、流程引导等。对话框在出现时会在视觉上「聚焦」用户注意力,阻止(模态)或不阻止(非模态)用户与底层界面的交互。 AtomUI 的 `Dialog` 控件对应 Ant Design 的 [Modal 组件](https://ant.design/components/modal-cn)。在 AtomUI 中采用 `Dialog` 命名(而非 `Modal`),是因为 `Dialog` 更准确地描述了桌面端的语义——桌面应用中「对话框」是长期沿用的标准术语,且 AtomUI 的实现不仅支持模态(Modal),还支持非模态(Modeless)模式。 --- ## 设计原理 ### Ant Design 的对话框设计哲学 Ant Design 对 Modal 的定位是:**在当前页面打开一个浮层,承载相关操作或信息。用户不需要离开当前页面就能完成任务或获取信息**。 **典型使用场景:** | 场景 | 说明 | |---|---| | 🔔 **信息确认** | 操作前的二次确认(如删除、提交),避免误操作 | | 📝 **表单填写** | 在不离开当前页面的情况下完成新建/编辑等操作 | | 📄 **详情展示** | 展示详细信息或预览内容 | | ⚠️ **消息提示** | 成功、错误、警告、确认等快捷提示(MessageBox) | ### AtomUI 的扩展设计 AtomUI `Dialog` 在 Ant Design Modal 基础上做了大量桌面端增强,核心设计决策包括: **双宿主架构(Dual Host):** | 宿主类型 | 说明 | 适用场景 | |---|---|---| | `Overlay` | 基于 Overlay 层渲染,在当前窗口内覆盖显示 | 大多数场景(默认);对话框内容与主窗口共享渲染上下文 | | `Window` | 创建独立的原生窗口作为宿主 | 需要独立标题栏、系统窗口管理器交互、最小化/最大化等 | **标准按钮系统(Standard Buttons):** Dialog 提供了一套完整的标准按钮系统,基于 `DialogStandardButton` 枚举(Flags),支持 `Ok`、`Cancel`、`Save`、`Yes`、`No`、`Apply`、`Reset` 等 18 种标准按钮。每个标准按钮都有预定义的 `DialogButtonRole`(AcceptRole / RejectRole / DestructiveRole 等),Dialog 根据角色自动决定点击后的行为(Accept / Reject / Done)。 **定位系统(Positioning):** Dialog 提供了灵活的定位能力: - `HorizontalStartupLocation` / `VerticalStartupLocation`:锚点定位(Left / Center / Right / Top / Bottom / Custom) - `HorizontalOffset` / `VerticalOffset`:偏移量,支持像素和百分比(`Dimension` 类型) - `PlacementTarget`:定位参考控件 - `PlacementCallback`:完全自定义定位回调 **MVVM 集成:** 通过 `IDialogAwareDataContext` 接口,ViewModel 可以感知自身被关联到哪个 Dialog 实例,从而在 ViewModel 层主动关闭对话框、获取结果等。 --- ## 功能详解 ### 宿主类型(DialogHostType) 通过 `DialogHostType` 属性选择宿主: - **Overlay(默认)**:对话框渲染在当前窗口的 Overlay 层上,通过 `OverlayDialogHost` 实现。支持拖拽移动、调整大小、遮罩层。 - **Window**:对话框在独立的 `DialogHost`(继承自 `Window`)中渲染。支持系统窗口标题栏、最小化、最大化、原生拖拽。 ### 模态与非模态 - **模态(IsModal = true,默认)**:打开后阻止用户与底层界面交互。`OpenAsync()` 返回的 Task 在对话框关闭后才完成,可以直接 `await` 获取 `Result`。 - **非模态(IsModal = false)**:打开后不阻止用户操作底层界面。`OpenAsync()` 在对话框显示后立即完成。 ### 标准按钮与自定义按钮 - **标准按钮**:通过 `StandardButtons` 属性配置,例如 `StandardButtons="Ok, Cancel"`。 - **默认按钮**:通过 `DefaultStandardButton` 指定哪个标准按钮为默认确认按钮(视觉上高亮显示)。 - **自定义按钮**:通过 `CustomButtons` 集合添加 `DialogButton` 实例,可自定义文本、角色和样式。 - **按钮属性配置回调**:`ButtonsConfigure` 回调可在按钮创建后统一修改属性(如禁用所有按钮)。 ### 加载状态 - **IsLoading**:整体加载状态,对话框内容区域显示加载遮罩。 - **IsConfirmLoading**:确认按钮加载状态。当 `IsConfirmLoading = true` 时,对话框不会响应关闭请求,防止用户在异步操作完成前关闭对话框。 ### 拖拽与调整大小 - **IsDragMovable**:启用标题栏拖拽移动。 - **IsResizable**:启用对话框边缘拖拽调整大小。 - **IsMaximizable**:启用最大化按钮。 ### 关闭流程 Dialog 提供三种关闭方式: 1. **Accept()**:设置 `Result = DialogCode.Accepted`,触发 `Accepted` 事件后关闭。 2. **Reject()**:设置 `Result = DialogCode.Rejected`,触发 `Rejected` 事件后关闭。 3. **Done(result?)**:设置自定义 Result 后关闭,触发 `Finished` 事件。 关闭前会触发 `Closing` 事件(`CancelEventArgs`),可以通过设置 `Cancel = true` 阻止关闭。当 `IsConfirmLoading = true` 时,所有关闭请求均被忽略。 **按钮角色到关闭行为的映射:** 当底部按钮被点击时,Dialog 首先触发 `ButtonClicked` 事件。若未被 `Handled`,则根据按钮角色自动执行: | 按钮角色 | 关闭行为 | |---|---| | `AcceptRole` / `YesRole` / `ApplyRole` / `ResetRole` | 调用 `Accept()`(Result = Accepted) | | `RejectRole` / `NoRole` | 调用 `Reject()`(Result = Rejected) | | `ActionRole` / `HelpRole` / `CustomRole` / `DestructiveRole` | 不自动关闭,需在 `ButtonClicked` 事件中自行处理 | ### 静态 API Dialog 提供了一组静态方法,用于在代码中快速创建和显示对话框,无需在 AXAML 中声明: - `Dialog.ShowDialog(...)` / `Dialog.ShowDialogModal(...)` — 同步打开 - `Dialog.ShowDialogAsync(...)` / `Dialog.ShowDialogModalAsync(...)` — 异步打开 - 泛型版本 `Dialog.ShowDialogModalAsync(...)` — 用于自定义视图 + ViewModel --- ## 与 Ant Design 规范的对齐程度 | 特性 | Ant Design (React) | AtomUI (Avalonia) | 对齐情况 | |---|---|---|---| | 模态弹窗 | ✅ `Modal` | ✅ `Dialog` | ✅ 完全对齐(命名不同) | | 标题 | ✅ `title` | ✅ `Title` 属性 | ✅ 完全对齐 | | 内容 | ✅ `children` | ✅ `Content` 属性 | ✅ 完全对齐 | | 页脚按钮 | ✅ `footer` / `okText` / `cancelText` | ✅ `StandardButtons` + `CustomButtons` | ✅ 增强:支持 18 种标准按钮 | | 确认加载 | ✅ `confirmLoading` | ✅ `IsConfirmLoading` | ✅ 完全对齐 | | 关闭按钮 | ✅ `closable` | ✅ `IsClosable` | ✅ 完全对齐 | | 遮罩层 | ✅ `mask` | ✅ Overlay 模式自带遮罩 | ✅ 完全对齐 | | 点击遮罩关闭 | ✅ `maskClosable` | ✅ `IsLightDismissEnabled` | ✅ 完全对齐 | | 居中显示 | ✅ `centered` | ✅ `HorizontalStartupLocation="Center"` | ✅ 完全对齐(更灵活) | | 宽度 | ✅ `width` | ✅ `Width` / `MinWidth` / `MaxWidth` | ✅ 完全对齐 | | 加载中 | ✅ `loading` | ✅ `IsLoading` | ✅ 完全对齐 | | 拖拽移动 | ❌ 不支持 | ✅ `IsDragMovable` | ⭐ AtomUI 增强 | | 调整大小 | ❌ 不支持 | ✅ `IsResizable` | ⭐ AtomUI 增强 | | 窗口宿主 | ❌ 不适用(Web) | ✅ `DialogHostType.Window` | ⭐ AtomUI 桌面端增强 | | 最大化/最小化 | ❌ 不适用 | ✅ `IsMaximizable` / `IsMinimizable` | ⭐ AtomUI 桌面端增强 | | 标准按钮系统 | ❌ 仅 Ok/Cancel | ✅ 18 种标准按钮 + 角色系统 | ⭐ AtomUI 增强 | | MVVM 集成 | ❌ 不适用 | ✅ `IDialogAwareDataContext` | ⭐ AtomUI 增强 | | `Modal.confirm()` | ✅ 静态方法 | ✅ `Dialog.ShowDialogModalAsync()` | ✅ 完全对齐 | --- ## 继承关系 ``` Avalonia.Controls.Primitives.TemplatedControl └── AtomUI.Desktop.Controls.Dialog ├── implements IDialogHostProvider ├── implements IMotionAwareControl └── implements IDialog ``` `Dialog` 直接继承自 Avalonia 的 `TemplatedControl`,未使用 `ContentControl` 或 `Window`。内容通过 `Content` 属性传递给宿主(`OverlayDialogHost` 或 `DialogHost`)进行渲染。Dialog 本身不参与可视化树的测量和排列(`MeasureCore` 返回空尺寸),其唯一职责是管理对话框的生命周期和状态。 **各层级职责划分:** | 层级 | 提供的能力 | |---|---| | `TemplatedControl` | 模板化控件基础设施 | | `Dialog` | 对话框生命周期管理(打开/关闭/结果)、双宿主架构、标准按钮系统、定位系统、加载状态、拖拽/调整大小、MVVM 集成 | **实现的接口:** | 接口 | 定义位置 | 作用 | |---|---|---| | `IDialog` | `AtomUI.Desktop.Controls` | 对话框核心协议:Title、Result、生命周期事件、Accept/Reject/Done | | `IDialogHostProvider` | `AtomUI.Desktop.Controls` | 提供对宿主(DialogHost)的访问 | | `IMotionAwareControl` | `AtomUI.Controls.Shared` | 支持 `IsMotionEnabled` 动画开关 | --- ## 源码位置 | 层级 | 文件路径 | 说明 | |---|---|---| | 控件类 | `src/AtomUI.Desktop.Controls/Dialog/Dialog.cs` | Dialog 核心实现 | | 静态 API | `src/AtomUI.Desktop.Controls/Dialog/Dialog.StaticAPI.cs` | 静态方法 | | 配置选项 | `src/AtomUI.Desktop.Controls/Dialog/DialogOptions.cs` | 静态 API 的配置参数 | | 接口 | `src/AtomUI.Desktop.Controls/Dialog/IDialog.cs` | IDialog 接口 | | MVVM 接口 | `src/AtomUI.Desktop.Controls/Dialog/IDialogAwareDataContext.cs` | ViewModel 感知接口 | | 宿主类型 | `src/AtomUI.Desktop.Controls/Dialog/DialogHostType.cs` | Overlay / Window 枚举 | | 定位枚举 | `src/AtomUI.Desktop.Controls/Dialog/DialogPlacement.cs` | 水平/垂直锚点枚举 | | 按钮系统 | `src/AtomUI.Desktop.Controls/Dialog/ButtonBox/` | DialogButton、DialogButtonRole、DialogStandardButton 等 | | Token 定义 | `src/AtomUI.Desktop.Controls/Dialog/DialogToken.cs` | 组件级 Design Token | | 主题 | `src/AtomUI.Desktop.Controls/Dialog/Themes/` | ControlTheme 及 code-behind | | Gallery 示例 | `controlgallery/AtomUIGallery/ShowCases/Views/Feedback/ModalShowCase.axaml` | 使用范例 | --- ## 模板结构 Dialog 本身不直接渲染可视内容,而是将属性代理给宿主。根据 `DialogHostType` 的不同,使用不同的宿主模板: ### Overlay 宿主模板结构 ``` OverlayDialogHost ├── OverlayDialogMask ← 遮罩层(模态时显示) └── OverlayDialogResizer (PART_Resizer) ← 调整大小控件 └── StackPanel ├── OverlayDialogHeader (PART_Header) ← 标题栏(标题、图标、关闭/最大化按钮) ├── ContentPresenter ← 内容区域 └── DialogButtonBox (PART_ButtonBox) ← 底部按钮区域 ``` ### Window 宿主模板结构 ``` DialogHost (extends Window) ├── 系统窗口标题栏 ← 原生标题栏(标题、最小化/最大化/关闭) └── StackPanel ├── ContentPresenter ← 内容区域 └── DialogButtonBox (PART_ButtonBox) ← 底部按钮区域 ``` ### 模板部件常量 | 常量名 | 值 | 所属类 | 说明 | |---|---|---|---| | `OverlayDialogThemeConstants.ResizerPart` | `"PART_Resizer"` | OverlayDialogHost | 调整大小控件 | | `OverlayDialogThemeConstants.HeaderPart` | `"PART_Header"` | OverlayDialogHost | 标题栏 | | `OverlayDialogHeaderThemeConstants.CloseButtonPart` | `"PART_CloseButton"` | OverlayDialogHeader | 关闭按钮 | | `OverlayDialogHeaderThemeConstants.MaximizeButtonPart` | `"PART_MaximizeButton"` | OverlayDialogHeader | 最大化按钮 | | `DialogThemeConstants.ButtonBoxPart` | `"PART_ButtonBox"` | Dialog/DialogHost | 按钮区域 |