20 KiB
AtomUI Form 系统设计原理
1. 概述
AtomUI 的 Form 系统是对 Ant Design 5.0 Form 组件 的完整 C# / Avalonia 实现。它提供了一套完整的表单数据收集、验证与提交机制,并通过接口契约系统使任意控件(包括自定义控件和第三方控件)都能无缝接入 Form 工作流。
核心设计理念
- 接口驱动的松耦合:Form 不直接依赖任何具体输入控件,而是通过
IFormItemAware接口与内容控件交互。任何实现了该接口的控件都可以放入 Form 中参与数据收集和验证。 - 属性级联传播:Form 自动将
SizeType、StyleVariant、IsMotionEnabled等配置通过 FormItem 中继传播到内容控件,实现统一的外观和行为控制。 - 异步验证管线:内置异步验证管线,支持多种验证触发时机、防抖、短路策略和 Warning-only 非阻塞验证。
- 事件冒泡架构:
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 — 表单值感知(必须实现)
public interface IFormItemAware
{
event EventHandler ValueChanged; // 值变更事件
void SetFormValue(object? value); // 外部设值(如 InitialValues、SetFormValues)
object? GetFormValue(); // 获取当前值
void ClearFormValue(); // 重置为默认值
void NotifyValidateStatus(FormValidateStatus status); // 接收验证状态
}
这是控件接入 Form 系统的唯一必要接口。 FormItem 在设置 Content 时会检查内容控件是否实现了此接口:
// 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 — 验证反馈感知(可选)
public interface IFormItemFeedbackAware
{
void SetFeedbackControl(FormValidateFeedback? value);
}
当 FormItem.IsValidateFeedbackEnabled = true 时,FormItem 会构建 FormValidateFeedback 控件并通过此接口传递给内容控件。内容控件可以在自己的 UI 中显示验证状态图标(✓、✗、⚠、⏳)。
3.3 ISizeTypeAware — 尺寸感知(推荐实现)
public interface ISizeTypeAware
{
SizeType SizeType { get; set; }
}
FormItem 检测到内容控件实现此接口后,会自动将 Form 的 SizeType 级联绑定下去:
if (Content is ISizeTypeAware)
{
_disposables.Add(BindUtils.RelayBind(this, SizeTypeProperty, Content, SizeTypeProperty));
}
3.4 IInputControlStyleVariantAware — 样式变体感知(推荐实现)
public interface IInputControlStyleVariantAware
{
InputControlStyleVariant StyleVariant { get; set; }
}
Form 的 StyleVariant(Outlined / Filled / Borderless / Underlined)会自动级联到实现此接口的内容控件。
3.5 IMotionAwareControl — 动画感知(推荐实现)
public interface IMotionAwareControl
{
bool IsMotionEnabled { get; }
}
Form 的 IsMotionEnabled 会自动级联到实现此接口的内容控件。
3.6 IInputControlStatusAware — 输入状态感知(推荐实现)
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] 语法创建单向绑定:
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 中通过接口检测决定是否绑定:
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 验证器接口
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 验证配置
<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 状态,不会阻塞表单提交:
<atom:FormUrlValidator Message="URL is not a valid url!" WarningOnly="True" />
8. 特殊控件
8.1 FormActionsItem
FormActionsItem 是 FormItem 的特殊子类,用于放置表单操作按钮:
public class FormActionsItem : FormItem
{
static FormActionsItem()
{
IsValidateContentTypeProperty.OverrideDefaultValue<FormActionsItem>(false);
IsValueItemProperty.OverrideDefaultValue<FormActionsItem>(false);
}
}
IsValidateContentType = false:不要求内容实现IFormItemAwareIsValueItem = false:不参与值收集和验证
8.2 SubmitButton
继承自 Button,点击时冒泡 SubmitEvent 到 Form:
protected override void OnClick()
{
base.OnClick();
RaiseEvent(new RoutedEventArgs(SubmitEvent));
}
支持 IsWatchValidateResult 属性,自动根据表单验证状态启用/禁用按钮。
8.3 ResetButton
继承自 Button,点击时冒泡 ResetEvent 到 Form:
protected override void OnClick()
{
base.OnClick();
RaiseEvent(new RoutedEventArgs(ResetEvent));
}
8.4 FormItemDecorator
一个中间层控件,实现了 IFormItemAware 和 IFormItemFeedbackAware,用于包裹已实现 IFormItemAware 的子控件,并添加额外的 UI 元素(如后缀文字):
<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 和内容区域的宽度比例:
<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包裹。