Files
AtomUI/docs/Controls/FormDesign.md
T

529 lines
20 KiB
Markdown
Raw 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.
# AtomUI Form 系统设计原理
## 1. 概述
AtomUI 的 Form 系统是对 [Ant Design 5.0 Form 组件](https://ant.design/components/form) 的完整 C# / Avalonia 实现。它提供了一套完整的表单数据收集、验证与提交机制,并通过接口契约系统使任意控件(包括自定义控件和第三方控件)都能无缝接入 Form 工作流。
### 核心设计理念
1. **接口驱动的松耦合**:Form 不直接依赖任何具体输入控件,而是通过 `IFormItemAware` 接口与内容控件交互。任何实现了该接口的控件都可以放入 Form 中参与数据收集和验证。
2. **属性级联传播**:Form 自动将 `SizeType`、`StyleVariant`、`IsMotionEnabled` 等配置通过 FormItem 中继传播到内容控件,实现统一的外观和行为控制。
3. **异步验证管线**:内置异步验证管线,支持多种验证触发时机、防抖、短路策略和 Warning-only 非阻塞验证。
4. **事件冒泡架构**:`SubmitButton` 和 `ResetButton` 通过 Avalonia 路由事件冒泡到 Form,实现声明式的提交/重置,无需手动编码事件绑定。
---
## 2. 架构总览
```
┌─────────────────────────────────────────────────────────────┐
│ Form (IForm) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ ISizeTypeAware, IMotionAwareControl, │ │
│ │ IInputControlStyleVariantAware │ │
│ │ │ │
│ │ 职责: │ │
│ │ - 管理 FormItem 集合 │ │
│ │ - 级联配置(SizeType, StyleVariant, Layout 等) │ │
│ │ - 编排验证、提交、重置流程 │ │
│ │ - 收集/分发表单值(FormValues) │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ FormItem (IFormItem) │ │
│ │ │ │
│ │ 职责: │ │
│ │ - 包裹一个内容控件 │ │
│ │ - 渲染 Label、必填标记、冒号、Tooltip │ │
│ │ - 级联配置到内容控件 │ │
│ │ - 编排单项验证逻辑 │ │
│ │ - 显示验证错误/警告消息 │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────┐ │ │
│ │ │ Content (IFormItemAware) │ │ │
│ │ │ │ │ │
│ │ │ - 提供/接收表单值 │ │ │
│ │ │ - 通知值变更 │ │ │
│ │ │ - 响应验证状态 │ │ │
│ │ └───────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ FormActionsItem │ │
│ │ SubmitButton / ResetButton / 自定义按钮 │ │
│ │ (通过路由事件冒泡到 Form) │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 代码分层
| 层级 | 项目 | 内容 |
|---|---|---|
| 接口层 | `AtomUI.Controls` | `IForm`, `IFormItem`, `IFormItemAware`, `IFormItemFeedbackAware`, `IFormValidator`, `IFormValues`, `IFormValidateFeedback`, 枚举, 验证器基类, 事件参数 |
| 实现层 | `AtomUI.Desktop.Controls` | `Form`, `FormItem`, `FormActionsItem`, `FormItemDecorator`, `SubmitButton`, `ResetButton`, `FormToken`, 主题 |
---
## 3. 核心接口
### 3.1 IFormItemAware — 表单值感知(必须实现)
```csharp
public interface IFormItemAware
{
event EventHandler ValueChanged; // 值变更事件
void SetFormValue(object? value); // 外部设值(如 InitialValues、SetFormValues)
object? GetFormValue(); // 获取当前值
void ClearFormValue(); // 重置为默认值
void NotifyValidateStatus(FormValidateStatus status); // 接收验证状态
}
```
**这是控件接入 Form 系统的唯一必要接口。** FormItem 在设置 `Content` 时会检查内容控件是否实现了此接口:
```csharp
// FormItem.NotifyContentAdded()
if (change.NewValue is IFormItemAware newFormItemAware)
{
newFormItemAware.ValueChanged += HandleContentValueChanged;
}
else if (IsValidateContentType)
{
throw new Exception($"Form item content: {change.NewValue?.GetType().FullName} not implement IFormItemAware interface");
}
```
如果 `IsValidateContentType` 为 `true`(默认值),不实现 `IFormItemAware` 会抛出异常。
### 3.2 IFormItemFeedbackAware — 验证反馈感知(可选)
```csharp
public interface IFormItemFeedbackAware
{
void SetFeedbackControl(FormValidateFeedback? value);
}
```
当 `FormItem.IsValidateFeedbackEnabled = true` 时,FormItem 会构建 `FormValidateFeedback` 控件并通过此接口传递给内容控件。内容控件可以在自己的 UI 中显示验证状态图标(✓、✗、⚠、⏳)。
### 3.3 ISizeTypeAware — 尺寸感知(推荐实现)
```csharp
public interface ISizeTypeAware
{
SizeType SizeType { get; set; }
}
```
FormItem 检测到内容控件实现此接口后,会自动将 Form 的 `SizeType` 级联绑定下去:
```csharp
if (Content is ISizeTypeAware)
{
_disposables.Add(BindUtils.RelayBind(this, SizeTypeProperty, Content, SizeTypeProperty));
}
```
### 3.4 IInputControlStyleVariantAware — 样式变体感知(推荐实现)
```csharp
public interface IInputControlStyleVariantAware
{
InputControlStyleVariant StyleVariant { get; set; }
}
```
Form 的 `StyleVariant`(Outlined / Filled / Borderless / Underlined)会自动级联到实现此接口的内容控件。
### 3.5 IMotionAwareControl — 动画感知(推荐实现)
```csharp
public interface IMotionAwareControl
{
bool IsMotionEnabled { get; }
}
```
Form 的 `IsMotionEnabled` 会自动级联到实现此接口的内容控件。
### 3.6 IInputControlStatusAware — 输入状态感知(推荐实现)
```csharp
public interface IInputControlStatusAware
{
InputControlStatus Status { get; set; }
}
```
虽然 FormItem 不会自动级联此属性,但它在 `NotifyValidateStatus` 中非常有用——控件可以根据验证状态设置自身的视觉状态(Error 红框、Warning 橙框等)。
---
## 4. 接口关系矩阵
| 接口 | 是否必须 | 作用 | 级联方式 |
|---|---|---|---|
| `IFormItemAware` | **必须** | 值的读写、变更通知、验证状态回调 | FormItem 直接调用接口方法 |
| `IFormItemFeedbackAware` | 可选 | 接收验证反馈 UI 控件 | FormItem 调用 `SetFeedbackControl()` |
| `ISizeTypeAware` | 推荐 | 尺寸级联 | `BindUtils.RelayBind` 自动绑定 |
| `IInputControlStyleVariantAware` | 推荐 | 样式变体级联 | `BindUtils.RelayBind` 自动绑定 |
| `IMotionAwareControl` | 推荐 | 动画开关级联 | `BindUtils.RelayBind` 自动绑定 |
| `IInputControlStatusAware` | 推荐 | 验证状态视觉反馈 | 控件在 `NotifyValidateStatus` 中自行设置 |
---
## 5. 数据流
### 5.1 值变更流
```
用户输入
│
▼
Content 控件值变化
│
├─► Content 触发 IFormItemAware.ValueChanged 事件
│ │
│ ▼
│ FormItem.HandleContentValueChanged()
│ │
│ ├─► 根据 ValidateTrigger 决定是否触发验证
│ │ OnChanged → 立即验证(可配 Debounce)
│ │ OnBlur → 失焦时验证
│ │ OnSubmit → 仅提交时验证
│ │
│ └─► 冒泡 ValueChangedEvent → Form.HandleFormItemValueChanged()
│ │
│ ├─► 触发 Form.ItemValueChanged 事件
│ └─► 通知其他 FormItem(NotifyFormItemChanged)
│
▼
其他 FormItem 收到通知(可实现联动逻辑)
```
### 5.2 验证流
```
触发验证(OnChanged / OnBlur / Submit / 手动调用 Validate)
│
▼
FormItem.ValidateValueAsync()
│
├─► 从 Content 获取值: IFormItemAware.GetFormValue()
│
├─► 遍历 Validators 执行验证(支持三种策略)
│ StopWhenFirstFailed → 遇到第一个失败即停止
│ Sequential → 逐个执行,收集全部结果
│ Parallel → 并行执行,收集全部结果
│
├─► 设置 ValidateStatus (Default / Success / Warning / Error / Validating)
│
├─► 回调 Content: IFormItemAware.NotifyValidateStatus(status)
│
├─► 构建错误/警告消息 Inlines
│
└─► 冒泡 ValidateChangedEvent → Form.HandleFormItemValidateChanged()
│
└─► 更新 Form.IsFormValid
```
### 5.3 提交流
```
SubmitButton.Click
│
├─► SubmitButton 冒泡 SubmitEvent
│
▼
Form.HandleSubmitButtonClick()
│
├─► Form.Submit()
│ │
│ ├─► 验证全部 FormItem
│ │
│ ├─► 如果全部通过 → 收集 FormValues → 触发 Submitted 事件
│ │
│ └─► 如果有失败 → 触发 Validated 事件(包含错误信息)
```
### 5.4 重置流
```
ResetButton.Click
│
├─► ResetButton 冒泡 ResetEvent
│
▼
Form.HandleResetButtonClick()
│
├─► Form.Reset()
│ │
│ ├─► 遍历 FormItem 调用 ResetItemValue()
│ │ │
│ │ └─► IFormItemAware.ClearFormValue()
│ │ └─► IFormItemAware.NotifyValidateStatus(Default)
│ │
│ └─► 触发 ResetCompleted 事件
```
---
## 6. 属性级联机制
Form 通过两种方式将配置传播给 FormItem 和内容控件:
### 6.1 Form → FormItem(Avalonia 属性绑定)
在 `PrepareContainerForItemOverride` 中通过 `[!Property]` 语法创建单向绑定:
```csharp
formItem[!FormItem.SizeTypeProperty] = this[!SizeTypeProperty];
formItem[!FormItem.IsMotionEnabledProperty] = this[!IsMotionEnabledProperty];
formItem[!FormItem.StyleVariantProperty] = this[!StyleVariantProperty];
formItem[!FormItem.RequiredMarkProperty] = this[!RequiredMarkProperty];
formItem[!FormItem.LabelWrappingProperty] = this[!LabelWrappingProperty];
// ... 以及更多属性
```
### 6.2 FormItem → Content(接口检测 + RelayBind)
在 `NotifyContentAdded` 中通过接口检测决定是否绑定:
```csharp
if (Content is ISizeTypeAware)
{
_disposables.Add(BindUtils.RelayBind(this, SizeTypeProperty, Content, SizeTypeProperty));
}
if (Content is IMotionAwareControl)
{
_disposables.Add(BindUtils.RelayBind(this, IsMotionEnabledProperty, Content, IsMotionEnabledProperty));
}
if (Content is IInputControlStyleVariantAware)
{
_disposables.Add(BindUtils.RelayBind(this, StyleVariantProperty, Content, StyleVariantProperty));
}
```
### 级联链路图
```
Form.SizeType ──► FormItem.SizeType ──► Content.SizeType (if ISizeTypeAware)
Form.StyleVariant ──► FormItem.StyleVariant ──► Content.StyleVariant (if IInputControlStyleVariantAware)
Form.IsMotionEnabled ──► FormItem.IsMotionEnabled ──► Content.IsMotionEnabled (if IMotionAwareControl)
Form.LabelAlign ──► FormItem.LabelAlign
Form.RequiredMark ──► FormItem.RequiredMark
Form.ValidateTrigger ──► FormItem.ValidateTrigger
Form.FormLayout ──► FormItem.Layout (Horizontal→Horizontal, Vertical→Vertical)
```
---
## 7. 验证系统
### 7.1 验证器接口
```csharp
public interface IFormValidator
{
string? Message { get; } // 验证失败时的提示消息
bool WarningOnly { get; } // 是否仅警告(不阻塞提交)
Task<FormValidateResult> ValidateAsync(string fieldName, object? value, CancellationToken cancellationToken);
}
```
### 7.2 内置验证器
| 验证器 | 用途 |
|---|---|
| `FormNotNullValidator` | 非空验证(值不为 null) |
| `FormStringNotEmptyValidator` | 字符串非空验证 |
| `FormStringLengthValidator` | 字符串长度范围验证 |
| `FormUrlValidator` | URL 格式验证 |
| `FormAssertValidator` | 断言验证(用于调试/演示) |
### 7.3 验证配置
```xml
<atom:FormItem LabelText="Username"
FieldName="username"
IsRequired="True"
ValidateTrigger="OnChanged"
ValidateDebounce="00:00:01"
ValidateStrategy="StopWhenFirstFailed">
<atom:FormItem.Validators>
<atom:FormValidatorProvider>
<atom:FormStringNotEmptyValidator Message="Please input your username!" />
<atom:FormStringLengthValidator Message="Username must be 3-20 characters!"
MinimumLength="3" MaximumLength="20" />
</atom:FormValidatorProvider>
</atom:FormItem.Validators>
<atom:LineEdit />
</atom:FormItem>
```
### 7.4 验证触发时机
| `FormValidateTrigger` | 时机 |
|---|---|
| `OnChanged` | 内容控件值变更时立即触发(默认) |
| `OnBlur` | 内容控件失焦时触发 |
| `OnSubmit` | 仅在 Form.Submit() 时触发 |
### 7.5 验证策略
| `FormValidateStrategy` | 行为 |
|---|---|
| `StopWhenFirstFailed` | 遇到第一个失败的验证器即停止(默认) |
| `Sequential` | 逐个执行所有验证器,收集全部结果 |
| `Parallel` | 并行执行所有验证器,收集全部结果 |
### 7.6 验证状态
| `FormValidateStatus` | 含义 | UI 表现 |
|---|---|---|
| `Default` | 初始状态 | 无特殊样式 |
| `Success` | 验证通过 | 绿色(配合 Feedback 显示 ✓) |
| `Warning` | 警告(不阻塞提交) | 橙色 |
| `Error` | 错误(阻塞提交) | 红色 |
| `Validating` | 验证进行中 | 加载动画(配合 Feedback 显示 ⏳) |
### 7.7 WarningOnly 非阻塞验证
验证器设置 `WarningOnly="True"` 时,验证失败只会产生 Warning 状态,不会阻塞表单提交:
```xml
<atom:FormUrlValidator Message="URL is not a valid url!" WarningOnly="True" />
```
---
## 8. 特殊控件
### 8.1 FormActionsItem
`FormActionsItem` 是 `FormItem` 的特殊子类,用于放置表单操作按钮:
```csharp
public class FormActionsItem : FormItem
{
static FormActionsItem()
{
IsValidateContentTypeProperty.OverrideDefaultValue<FormActionsItem>(false);
IsValueItemProperty.OverrideDefaultValue<FormActionsItem>(false);
}
}
```
- `IsValidateContentType = false`:不要求内容实现 `IFormItemAware`
- `IsValueItem = false`:不参与值收集和验证
### 8.2 SubmitButton
继承自 `Button`,点击时冒泡 `SubmitEvent` 到 Form:
```csharp
protected override void OnClick()
{
base.OnClick();
RaiseEvent(new RoutedEventArgs(SubmitEvent));
}
```
支持 `IsWatchValidateResult` 属性,自动根据表单验证状态启用/禁用按钮。
### 8.3 ResetButton
继承自 `Button`,点击时冒泡 `ResetEvent` 到 Form:
```csharp
protected override void OnClick()
{
base.OnClick();
RaiseEvent(new RoutedEventArgs(ResetEvent));
}
```
### 8.4 FormItemDecorator
一个中间层控件,实现了 `IFormItemAware` 和 `IFormItemFeedbackAware`,用于包裹已实现 `IFormItemAware` 的子控件,并添加额外的 UI 元素(如后缀文字):
```xml
<atom:FormItem LabelText="InputNumber" FieldName="inputNumber">
<atom:FormItemDecorator>
<atom:NumericUpDown Minimum="1" Maximum="10" Width="120"/>
<atom:FormItemDecorator.Extra>
machines
</atom:FormItemDecorator.Extra>
</atom:FormItemDecorator>
</atom:FormItem>
```
`FormItemDecorator` 透传所有 `IFormItemAware` 调用到内部子控件,同时级联 `SizeType`、`StyleVariant` 等属性。
---
## 9. 表单布局
### 9.1 三种布局模式
| `FormLayout` | 说明 |
|---|---|
| `Horizontal` | Label 和控件在同一行(默认),Label 在左侧 |
| `Vertical` | Label 在控件上方 |
| `Inline` | 所有 FormItem 水平排列 |
### 9.2 响应式栅格
通过 `LabelColInfo` 和 `WrapperColInfo` 控制 Label 和内容区域的宽度比例:
```xml
<atom:Form LabelColInfo="8*" WrapperColInfo="16*">
<!-- Label 占 8 份,内容占 16 份 = 1:2 比例 -->
</atom:Form>
<atom:Form LabelColInfo="200" WrapperColInfo="*">
<!-- Label 固定 200px,内容占剩余空间 -->
</atom:Form>
```
支持 `MediaBreakGridLength` 响应式断点。
---
## 10. 已实现 IFormItemAware 的内置控件
| 控件 | 值类型 | 是否支持 Feedback |
|---|---|---|
| `TextBox` (LineEdit) | `string?` | ✅ `IFormItemFeedbackAware` |
| `TextArea` | `string?` | ✅ `IFormItemFeedbackAware` |
| `NumericUpDown` | `decimal?` | ✅ `IFormItemFeedbackAware` |
| `AbstractSelect` (Select, Cascader, TreeSelect) | `ISelectOption?` / `IList<ISelectOption>?` | ✅ `IFormItemFeedbackAware` |
| `ComboBox` | `string?` | ✅ `IFormItemFeedbackAware` |
| `Mentions` | `string?` | ✅ `IFormItemFeedbackAware` |
| `InfoPickerInput` (DatePicker, TimePicker, RangeDatePicker) | 日期/时间值 | ✅ `IFormItemFeedbackAware` |
| `AbstractAutoComplete` | `string?` | ✅ |
| `AbstractColorPicker` (ColorPicker) | `Color?` | ✅ |
| `FormItemDecorator` | 透传子控件值 | ✅ `IFormItemFeedbackAware` |
### 无需 IFormItemAware 即可放入 Form 的控件
以下控件可以放入 `FormItem`,但因为它们不是典型的"输入控件",不实现 `IFormItemAware`。需要将 `IsValidateContentType="False"` 或使用 `FormActionsItem`:
| 控件 | 场景 |
|---|---|
| `CheckBox` | 简单勾选项(如"记住我") |
| `RadioButtonGroup` | 单选组 |
| `OptionButtonGroup` | 选项按钮组 |
| `CheckBoxGroup` | 多选组 |
| `ToggleSwitch` | 开关 |
| `Slider` | 滑块 |
| `Rate` | 评分 |
| `Upload` | 文件上传 |
| `TreeView` | 树视图 |
| `Button` | 普通按钮 |
| `TextBlock` | 纯文本 |
> **注意**:这些控件放入 FormItem 后可以正常显示,但不参与 Form 的验证和值收集。如果需要参与验证,需要实现 `IFormItemAware` 或用 `FormItemDecorator` 包裹。