From e7e71d9422ffb6f2bd01c6eea8b7d8f50c19237a Mon Sep 17 00:00:00 2001 From: chinboy Date: Sat, 11 Apr 2026 21:14:07 +0800 Subject: [PATCH] docs(Controls): add InputControl StyleVariant and Status design specification and developer guide --- docs/Controls/FormIntegrationGuide.md | 2 +- .../InputControlStyleAndStatusDesign.md | 508 ++++++++++++++++++ .../InputControlStyleAndStatusGuide.md | 391 ++++++++++++++ 3 files changed, 900 insertions(+), 1 deletion(-) create mode 100644 docs/Controls/InputControlStyleAndStatusDesign.md create mode 100644 docs/Controls/InputControlStyleAndStatusGuide.md diff --git a/docs/Controls/FormIntegrationGuide.md b/docs/Controls/FormIntegrationGuide.md index e3887a857..e2c675110 100644 --- a/docs/Controls/FormIntegrationGuide.md +++ b/docs/Controls/FormIntegrationGuide.md @@ -366,7 +366,7 @@ public class SimpleInput : TemplatedControl, IFormItemAware ### 4.2 完整功能的复合输入控件 -一个包含完整 Form 集成的复合控件(参考 Gallery 中的 PriceInput): +一个包含完整 Form 集成的复合输入控件: ```csharp using AtomUI; diff --git a/docs/Controls/InputControlStyleAndStatusDesign.md b/docs/Controls/InputControlStyleAndStatusDesign.md new file mode 100644 index 000000000..1f4f508bb --- /dev/null +++ b/docs/Controls/InputControlStyleAndStatusDesign.md @@ -0,0 +1,508 @@ +# AtomUI 输入控件样式变体与验证状态设计原理 + +## 1. 概述 + +AtomUI 的输入控件提供两个正交的外观维度:**样式变体**(StyleVariant)控制控件的视觉风格,**验证状态**(Status)反馈数据校验结果。两者可自由组合,通过 Design Token 系统驱动全部视觉表现,并能在 Form 中自动级联传播。 + +这一设计对应 Ant Design 5.0 中 Input 组件的 [`variant`](https://ant.design/components/input#api) 和 [`status`](https://ant.design/components/input#api) 属性,AtomUI 将其抽象为两组独立的接口 + 共享属性 + Token 映射体系,确保所有输入类控件(包括自定义控件)能够表现一致。 + +### 核心设计理念 + +1. **正交性**:StyleVariant 和 Status 是两个独立维度,4 种变体 × 3 种状态 = 12 种组合,每种组合在主题中有完整的视觉定义。 +2. **接口驱动**:通过 `IInputControlStyleVariantAware` 和 `IInputControlStatusAware` 接口标记控件支持相应能力,Form 据此实现自动级联。 +3. **共享属性模式**:所有控件使用 `AddOwner()` 注册同名 `StyledProperty`,确保属性名一致,级联绑定正确生效。 +4. **Token 驱动视觉**:每种 Variant × Status × State(hover / focus / pressed / disabled)组合的颜色均由 Design Token 定义,无硬编码值。 +5. **Form 自动级联**:Form → FormItem → 内容控件的 StyleVariant 自动向下传播;验证管线自动将 `FormValidateStatus` 映射为 `InputControlStatus`。 + +--- + +## 2. 类型定义 + +### 2.1 InputControlStyleVariant 枚举 + +定义在 `AtomUI.Controls.Shared/InputControlStyleVariant.cs`: + +```csharp +public enum InputControlStyleVariant +{ + Outline, // 带边框(默认) + Filled, // 填充背景 + Borderless, // 无边框 + Underlined // 仅下划线 +} +``` + +| 值 | 对应 Ant Design | 视觉特征 | +|---|---|---| +| `Outline` | `outlined`(默认) | 完整边框 + 透明背景,hover 时边框变主色,focus 时边框高亮 + 阴影 | +| `Filled` | `filled` | 灰色填充背景 + 同色边框,hover 时背景加深,focus 时切换为透明背景 + 主色边框 | +| `Borderless` | `borderless` | 完全无边框无背景,最小化视觉干扰 | +| `Underlined` | — | 仅底部下划线(AtomUI 扩展),适用于极简风格表单 | + +### 2.2 InputControlStatus 枚举 + +定义在 `AtomUI.Controls.Shared/InputControlStatus.cs`: + +```csharp +public enum InputControlStatus +{ + Default, // 正常状态 + Warning, // 警告(黄色系) + Error // 错误(红色系) +} +``` + +| 值 | 对应 Ant Design | 视觉特征 | +|---|---|---| +| `Default` | 无 status 属性 | 正常边框/背景颜色 | +| `Warning` | `status="warning"` | 边框/背景切换为 Warning 色系(`ColorWarning` 系列 Token) | +| `Error` | `status="error"` | 边框/背景切换为 Error 色系(`ColorError` 系列 Token) | + +### 2.3 两者的正交组合 + +StyleVariant × Status 构成一个 4×3 的视觉矩阵,每种组合都有完整的交互状态定义: + +``` + Default Warning Error + ┌────────────────┬────────────────┬────────────────┐ + Outline │ 灰边框 │ 黄边框 │ 红边框 │ + │ hover→主色 │ hover→黄hover │ hover→红hover │ + │ focus→主色+影 │ focus→黄+黄影 │ focus→红+红影 │ + ├────────────────┼────────────────┼────────────────┤ + Filled │ 灰底 │ 黄底 │ 红底 │ + │ hover→深灰 │ hover→深黄 │ hover→深红 │ + │ focus→白底+主框│ focus→白底+黄框│ focus→白底+红框│ + ├────────────────┼────────────────┼────────────────┤ + Borderless │ 透明 │ 透明 │ 透明 │ + │ (无边框变化) │ (无边框变化) │ (无边框变化) │ + ├────────────────┼────────────────┼────────────────┤ + Underlined │ 灰下划线 │ 黄下划线 │ 红下划线 │ + │ hover→主色 │ hover→黄hover │ hover→红hover │ + │ focus→主色 │ focus→黄色 │ focus→红色 │ + └────────────────┴────────────────┴────────────────┘ +``` + +--- + +## 3. 接口与共享属性 + +### 3.1 IInputControlStyleVariantAware + +```csharp +public interface IInputControlStyleVariantAware +{ + InputControlStyleVariant StyleVariant { get; set; } +} +``` + +标记控件支持**样式变体切换**。Form 系统通过 `is IInputControlStyleVariantAware` 类型检查来决定是否向子控件级联 `StyleVariant`。 + +### 3.2 InputControlStyleVariantProperty(共享属性持有者) + +```csharp +public abstract class InputControlStyleVariantProperty +{ + public const string StyleVariantPropertyName = "StyleVariant"; + public static readonly StyledProperty StyleVariantProperty = + AvaloniaProperty.Register( + StyleVariantPropertyName, InputControlStyleVariant.Outline); +} +``` + +所有控件 **必须** 使用 `AddOwner()` 注册此属性,**禁止** 自行 `Register` 同名属性: + +```csharp +// ✅ 正确 +public static readonly StyledProperty StyleVariantProperty = + InputControlStyleVariantProperty.StyleVariantProperty.AddOwner(); + +// ❌ 错误:自行 Register 会导致属性名相同但实例不同,级联绑定失败 +public static readonly StyledProperty StyleVariantProperty = + AvaloniaProperty.Register("StyleVariant"); +``` + +### 3.3 IInputControlStatusAware + +```csharp +public interface IInputControlStatusAware +{ + InputControlStatus Status { get; set; } +} +``` + +标记控件支持**验证状态反馈**。当 Form 验证完成后,通过 `IFormItemAware.NotifyValidateStatus()` 通知控件更新状态。 + +### 3.4 InputControlStatusProperty(共享属性持有者) + +```csharp +public abstract class InputControlStatusProperty +{ + public const string StatusPropertyName = "Status"; + public static readonly StyledProperty StatusProperty = + AvaloniaProperty.Register( + StatusPropertyName, InputControlStatus.Default); +} +``` + +同理,**必须** 使用 `AddOwner()`: + +```csharp +public static readonly StyledProperty StatusProperty = + InputControlStatusProperty.StatusProperty.AddOwner(); +``` + +### 3.5 为什么必须使用 AddOwner? + +Avalonia 的属性级联绑定(`BindUtils.RelayBind`)要求父控件和子控件的 `StyledProperty` 实例来自同一个 `Register` 调用链。如果子控件自行 `Register` 了同名属性,虽然属性名相同,但 Avalonia 内部的属性标识符不同,导致: + +- Form → FormItem → 子控件 的 `RelayBind` 绑定静默失败 +- 在 CompactSpace 中属性传播失效 +- 在 AXAML 中使用 `{TemplateBinding StyleVariant}` 时无法正确匹配 + +--- + +## 4. Token 集成 + +样式变体和验证状态的视觉表现完全由 Design Token 驱动。以 `AddOnDecoratedBox`(所有输入控件的通用装饰框)为例: + +### 4.1 组件 Token 定义 + +```csharp +[ControlDesignToken] +internal class AddOnDecoratedBoxToken : AbstractControlDesignToken +{ + /// + /// 悬浮态边框色 + /// + public Color HoverBorderColor { get; set; } + + /// + /// 激活态边框色 + /// + public Color ActiveBorderColor { get; set; } + + /// + /// 激活态阴影 + /// + public BoxShadow ActiveShadow { get; set; } + + /// + /// 错误状态时激活态阴影 + /// + public BoxShadow ErrorActiveShadow { get; set; } + + /// + /// 警告状态时激活态阴影 + /// + public BoxShadow WarningActiveShadow { get; set; } + + public override void CalculateTokenValues(bool isDarkMode) + { + base.CalculateTokenValues(isDarkMode); + ActiveBorderColor = SharedToken.ColorPrimary; + HoverBorderColor = SharedToken.ColorPrimaryHover; + ActiveShadow = new BoxShadow + { + Spread = SharedToken.ControlOutlineWidth, + Color = SharedToken.ColorControlOutline + }; + ErrorActiveShadow = new BoxShadow + { + Spread = SharedToken.ControlOutlineWidth, + Color = SharedToken.ColorErrorOutline // ← Error 色系 + }; + WarningActiveShadow = new BoxShadow + { + Spread = SharedToken.ControlOutlineWidth, + Color = SharedToken.ColorWarningOutline // ← Warning 色系 + }; + } +} +``` + +### 4.2 全局共享 Token 映射表 + +各种 Status × State 组合使用的 SharedToken: + +| 场景 | Token | 说明 | +|---|---|---| +| Outline 默认边框 | `ColorBorder` | 灰色边框 | +| Outline hover | `HoverBorderColor`(组件 Token,= `ColorPrimaryHover`) | 主色悬浮 | +| Outline focus/pressed | `ActiveBorderColor`(组件 Token,= `ColorPrimary`) | 主色激活 | +| Error 边框 | `ColorError` | 红色 | +| Error hover | `ColorErrorBorderHover` | 红色悬浮 | +| Error focus 阴影 | `ErrorActiveShadow`(= `ColorErrorOutline`) | 红色光晕 | +| Warning 边框 | `ColorWarning` | 黄色 | +| Warning hover | `ColorWarningBorderHover` | 黄色悬浮 | +| Warning focus 阴影 | `WarningActiveShadow`(= `ColorWarningOutline`) | 黄色光晕 | +| Filled 默认背景 | `ColorFillTertiary` | 浅灰填充 | +| Filled hover 背景 | `ColorFillSecondary` | 中灰填充 | +| Filled + Error 背景 | `ColorErrorBg` | 浅红填充 | +| Filled + Warning 背景 | `ColorWarningBg` | 浅黄填充 | +| Disabled 背景 | `ColorBgContainerDisabled` | 禁用灰色 | + +--- + +## 5. 主题选择器模式 + +### 5.1 AXAML 中 StyleVariant 选择器 + +AtomUI 同时使用两种选择器策略: + +**属性选择器**(用于主题 AXAML): + +```xml + + + + + + + + + + + +``` + +### 5.2 Status 选择器(嵌套在 StyleVariant 内) + +Status 选择器 **嵌套** 在 StyleVariant 选择器内部,因为不同变体下 Status 的视觉表达方式不同: + +```xml + + + + + + + + + +``` + +### 5.3 伪类策略 + +部分控件同时使用属性选择器和伪类来处理 StyleVariant/Status 变化: + +```csharp +// AddOnDecoratedBox 中的伪类更新 +protected virtual void UpdatePseudoClasses() +{ + PseudoClasses.Set(AddOnDecoratedBoxPseudoClass.Outline, StyleVariant == InputControlStyleVariant.Outline); + PseudoClasses.Set(AddOnDecoratedBoxPseudoClass.Filled, StyleVariant == InputControlStyleVariant.Filled); + PseudoClasses.Set(AddOnDecoratedBoxPseudoClass.Borderless, StyleVariant == InputControlStyleVariant.Borderless); +} + +// LineEdit 中的 Status 伪类更新 +PseudoClasses.Set(StdPseudoClass.Error, Status == InputControlStatus.Error); +PseudoClasses.Set(StdPseudoClass.Warning, Status == InputControlStatus.Warning); +``` + +> **设计选择**:主题 AXAML 中优先使用 `[StyleVariant=Outline]`、`[Status=Error]` 等属性选择器(语义更清晰);伪类(`:outline`、`:error`)主要用于控件内部需要向下游子控件传递状态的场景。 + +--- + +## 6. Form 级联传播 + +### 6.1 StyleVariant 级联链 + +Form 中的 StyleVariant 通过 `BindUtils.RelayBind` 自动向下传播: + +``` +Form ─── StyleVariant ──→ FormItem ─── StyleVariant ──→ Content (子控件) + └── StyleVariant ──→ FormItemDecorator ──→ Child +``` + +**FormItem 中的级联代码**: + +```csharp +// FormItem.cs — 当 Content 变化时设置级联绑定 +if (Content is IInputControlStyleVariantAware) +{ + _disposables.Add(BindUtils.RelayBind(this, StyleVariantProperty, Content, StyleVariantProperty)); +} +``` + +**FormItemDecorator 中的级联代码**: + +```csharp +// FormItemDecorator.cs — 当 Child 变化时设置级联绑定 +if (newChild is IInputControlStyleVariantAware) +{ + _disposables.Add(BindUtils.RelayBind(this, StyleVariantProperty, newChild, StyleVariantProperty)); +} +``` + +这意味着开发者只需在 `` 上设置一次 `StyleVariant`,所有子输入控件会自动采用相同的样式变体。 + +### 6.2 Status 从验证管线自动设置 + +Status 不通过级联绑定传播,而是由 Form 验证管线通过 `IFormItemAware.NotifyValidateStatus()` 设置: + +``` +Form 验证管线 + ↓ ValidateValueAsync() +FormItem + ↓ NotifyValidateStatus(FormValidateStatus.Error) +IFormItemAware.NotifyValidateStatus() + ↓ 控件内部映射 +SetCurrentValue(StatusProperty, InputControlStatus.Error) +``` + +**控件中的映射代码**(以 LineEdit 为例): + +```csharp +protected override void NotifyValidateStatus(FormValidateStatus status) +{ + if (status == FormValidateStatus.Error) + { + SetCurrentValue(StatusProperty, InputControlStatus.Error); + } + else if (status == FormValidateStatus.Warning) + { + SetCurrentValue(StatusProperty, InputControlStatus.Warning); + } + else + { + SetCurrentValue(StatusProperty, InputControlStatus.Default); + } +} +``` + +### 6.3 级联传播的设计优势 + +| 特性 | 说明 | +|---|---| +| **单点配置** | 在 Form 级别设置 `StyleVariant="Filled"`,所有子控件统一切换 | +| **逐级覆盖** | 单个控件可覆盖 Form 级别的 StyleVariant | +| **类型安全** | 只有实现了 `IInputControlStyleVariantAware` 的控件才会参与级联 | +| **自动清理** | 当控件从 Form 移除时,通过 `CompositeDisposable` 自动解除绑定 | +| **验证自动化** | Status 由验证管线自动设置,开发者无需手动管理 | + +--- + +## 7. 已实现控件清单 + +### 7.1 同时实现 StyleVariant + Status 的控件 + +这些控件同时支持样式变体切换和验证状态反馈,是完整的输入控件: + +| 控件 | 项目 | 说明 | +|---|---|---| +| `LineEdit` | Desktop.Controls | 单行文本输入 | +| `TextArea` | Desktop.Controls | 多行文本输入 | +| `SearchButton` | Desktop.Controls | 搜索按钮(SearchEdit 内部) | +| `ButtonSpinner` | Desktop.Controls | 带按钮的微调器 | +| `NumericUpDown` | Desktop.Controls | 数值输入 | +| `ComboBox` | Desktop.Controls | 下拉选择 | +| `AbstractSelect` | Desktop.Controls | Select 基类 | +| `Mentions` | Desktop.Controls | @提及输入 | +| `AbstractAutoComplete` | Desktop.Controls | AutoComplete 基类 | +| `InfoPickerInput` | Desktop.Controls | 日期/时间选择器输入框 | +| `AddOnDecoratedBox` | Desktop.Controls | 输入装饰框(内部组件) | +| `CompactSpaceAddOn` | Desktop.Controls | 紧凑空间装饰(内部组件) | +| `FormItemDecorator` | Desktop.Controls | 表单项装饰器 | +| `AbstractColorPicker` | Desktop.Controls.ColorPicker | 颜色选择器 | + +### 7.2 仅实现 Status 的控件 + +| 控件 | 项目 | 说明 | +|---|---|---| +| `AbstractTransfer` | Desktop.Controls | 穿梭框基类 | +| `TransferItemDecorator` | Desktop.Controls | 穿梭框项装饰器 | + +### 7.3 仅实现 StyleVariant 的控件 + +| 控件 | 项目 | 说明 | +|---|---|---| +| `Form` | Desktop.Controls | 表单容器(仅负责向下级联) | + +--- + +## 8. 设计优势分析 + +### 8.1 与 Ant Design 的对齐 + +Ant Design 5.0 引入了 `variant` 属性(替代旧版的 `bordered` 布尔值),AtomUI 将其映射为类型安全的枚举,并增加了 `Underlined` 变体以适应更多场景。`status` 属性与 Ant Design 完全一致。 + +### 8.2 与尺寸系统的解耦 + +StyleVariant 和 SizeType 是完全独立的维度: + +- `SizeType` 控制高度、内边距、字号 +- `StyleVariant` 控制边框、背景、视觉风格 +- `Status` 控制色彩反馈 + +三者可自由组合,例如 `SizeType="Small" StyleVariant="Filled" Status="Error"` 产生一个小号填充式红色错误输入框。 + +### 8.3 统一的视觉语义 + +所有输入控件共享同一套颜色语义: + +- Error 永远使用 `ColorError` 系列 Token +- Warning 永远使用 `ColorWarning` 系列 Token +- 默认状态永远使用 `ColorPrimary` 系列 Token 作为交互色 + +这保证了不同控件在相同 Status 下视觉表现一致。 + +### 8.4 Icon 自动染色 + +`AddOnDecoratedBox` 在 Status 变化时自动将内部 Icon 的颜色切换为对应的状态色: + +```csharp +// Warning 状态下自动将前后缀图标染为黄色 +var warningStyle = new Style(x => + x.PropertyEquals(StatusProperty, InputControlStatus.Warning)); +iconStyle.Add(Icon.FillBrushProperty, SharedTokenKind.ColorWarning); +iconStyle.Add(Icon.ForegroundProperty, SharedTokenKind.ColorWarning); +``` + +开发者可通过 `skip-status` CSS 类排除特定图标不参与自动染色。 + +--- + +## 9. 设计限制与注意事项 + +### 9.1 Borderless 变体下 Status 视觉有限 + +Borderless 变体没有边框和背景,因此 Error/Warning 状态的视觉反馈仅体现在: +- 内部 Icon 的颜色变化 +- 如果结合 Form 使用,由 FormItem 显示验证消息 + +### 9.2 Status 不影响 Disabled 样式 + +当控件处于 `:disabled` 状态时,所有 Status 的视觉反馈被覆盖为统一的禁用样式(灰色背景、灰色边框、灰色文字),Status 设置仍保留但不可见。 + +### 9.3 Status 与 Form 的关系 + +- **独立使用**:可以在 Form 外部直接设置 `Status="Error"` 手动显示错误状态。 +- **Form 内使用**:验证管线自动管理 Status 值,开发者通常不需要手动设置。 +- **两者冲突**:如果在 Form 内手动设置了 Status,下次验证触发时会被覆盖。 + diff --git a/docs/Controls/InputControlStyleAndStatusGuide.md b/docs/Controls/InputControlStyleAndStatusGuide.md new file mode 100644 index 000000000..7a2edbdb2 --- /dev/null +++ b/docs/Controls/InputControlStyleAndStatusGuide.md @@ -0,0 +1,391 @@ +# 自定义控件接入 StyleVariant 与 Status 开发指南 + +## 1. 概述 + +本文档面向控件开发者,详细说明如何让自定义控件支持 AtomUI 的样式变体(`InputControlStyleVariant`)和验证状态(`InputControlStatus`)系统,使其与 AtomUI 内置输入控件表现一致,并能在 Form 中自动级联。 + +> 前置阅读:[输入控件样式变体与验证状态设计原理](./InputControlStyleAndStatusDesign.md)、[Form 集成指南](./FormIntegrationGuide.md) + +--- + +## 2. 快速判断:你的控件需要实现哪些接口? + +``` +你的控件是输入类控件吗?(用户通过它输入/选择/切换值) +│ +├── 是 → 建议同时实现 IInputControlStyleVariantAware 和 IInputControlStatusAware +│ │ +│ ├── 控件需要 Form 验证? → 必须同时实现 IFormItemAware +│ ├── 控件需要跟随 Form 尺寸? → 实现 ISizeTypeAware +│ └── 控件需要跟随 Form 动画开关? → 实现 IMotionAwareControl +│ +└── 否 → 不需要实现这两个接口 +``` + +--- + +## 3. 基础实现模板 + +### 3.1 最小实现 + +以下展示了一个自定义输入控件同时支持 StyleVariant 和 Status 的最小实现: + +```csharp +using AtomUI.Controls; +using Avalonia; +using Avalonia.Controls.Primitives; + +namespace MyApp.Controls; + +public class MyInput : TemplatedControl, + IInputControlStyleVariantAware, + IInputControlStatusAware +{ + // ===== 1. 使用 AddOwner 注册共享属性 ===== + public static readonly StyledProperty StyleVariantProperty = + InputControlStyleVariantProperty.StyleVariantProperty.AddOwner(); + + public static readonly StyledProperty StatusProperty = + InputControlStatusProperty.StatusProperty.AddOwner(); + + // ===== 2. 属性包装器 ===== + public InputControlStyleVariant StyleVariant + { + get => GetValue(StyleVariantProperty); + set => SetValue(StyleVariantProperty, value); + } + + public InputControlStatus Status + { + get => GetValue(StatusProperty); + set => SetValue(StatusProperty, value); + } +} +``` + +### 3.2 完整实现(含 Form 集成) + +以下是一个支持 Form 完整集成的自定义输入控件模板: + +```csharp +using AtomUI; +using AtomUI.Controls; +using Avalonia; +using Avalonia.Controls.Primitives; + +namespace MyApp.Controls; + +public class MyInput : TemplatedControl, + IInputControlStyleVariantAware, + IInputControlStatusAware, + ISizeTypeAware, + IMotionAwareControl, + IFormItemAware +{ + #region 公共属性定义 + + // ===== 共享属性:必须使用 AddOwner ===== + public static readonly StyledProperty StyleVariantProperty = + InputControlStyleVariantProperty.StyleVariantProperty.AddOwner(); + + public static readonly StyledProperty StatusProperty = + InputControlStatusProperty.StatusProperty.AddOwner(); + + public static readonly StyledProperty SizeTypeProperty = + SizeTypeControlProperty.SizeTypeProperty.AddOwner(); + + public static readonly StyledProperty IsMotionEnabledProperty = + MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner(); + + // ===== 控件自有属性 ===== + public static readonly StyledProperty ValueProperty = + AvaloniaProperty.Register(nameof(Value)); + + public InputControlStyleVariant StyleVariant + { + get => GetValue(StyleVariantProperty); + set => SetValue(StyleVariantProperty, value); + } + + public InputControlStatus Status + { + get => GetValue(StatusProperty); + set => SetValue(StatusProperty, value); + } + + public SizeType SizeType + { + get => GetValue(SizeTypeProperty); + set => SetValue(SizeTypeProperty, value); + } + + public bool IsMotionEnabled + { + get => GetValue(IsMotionEnabledProperty); + set => SetValue(IsMotionEnabledProperty, value); + } + + public string? Value + { + get => GetValue(ValueProperty); + set => SetValue(ValueProperty, value); + } + + #endregion + + static MyInput() + { + ValueProperty.Changed.AddClassHandler( + (control, _) => control.HandleValueChanged()); + } + + #region IFormItemAware 实现 + + private EventHandler? _formValueChanged; + event EventHandler? IFormItemAware.ValueChanged + { + add => _formValueChanged += value; + remove => _formValueChanged -= value; + } + + void IFormItemAware.SetFormValue(object? value) => NotifySetFormValue(value?.ToString()); + object? IFormItemAware.GetFormValue() => NotifyGetFormValue(); + void IFormItemAware.ClearFormValue() => NotifyClearFormValue(); + void IFormItemAware.NotifyValidateStatus(FormValidateStatus status) => NotifyValidateStatus(status); + + private void HandleValueChanged() + { + _formValueChanged?.Invoke(this, EventArgs.Empty); + } + + protected virtual void NotifySetFormValue(string? value) + { + SetCurrentValue(ValueProperty, value); + } + + protected virtual string? NotifyGetFormValue() => Value; + + protected virtual void NotifyClearFormValue() + { + SetCurrentValue(ValueProperty, null); + } + + // ===== 关键:将 FormValidateStatus 映射为 InputControlStatus ===== + protected virtual void NotifyValidateStatus(FormValidateStatus status) + { + var inputStatus = status switch + { + FormValidateStatus.Error => InputControlStatus.Error, + FormValidateStatus.Warning => InputControlStatus.Warning, + _ => InputControlStatus.Default + }; + SetCurrentValue(StatusProperty, inputStatus); + } + + #endregion +} +``` + +--- + +## 4. 控件模板中传递 StyleVariant 和 Status + +当自定义控件的模板中包含 AtomUI 内置输入控件时,**必须** 将 StyleVariant 和 Status 通过 `{TemplateBinding}` 向下传递。 + +### 4.1 AXAML 模板示例 + +```xml + + + + + + + + + Submit + + + + + +``` + +### 4.2 实际示例 + +**验证码控件**(`Captcha`)— 组合 LineEdit + Button: + +```xml + + + + + + + Get captcha + + + + + +``` + +**价格输入控件**(`PriceInput`)— 组合 NumericUpDown + Select: + +```xml + + + + + + + + + + + + + +``` + +> **要点**:每个内部的 AtomUI 输入控件都绑定了 `SizeType`、`IsMotionEnabled`、`Status`、`StyleVariant` 四个属性。这样当 Form 级联传播这些属性到外层自定义控件时,内部子控件会自动跟随变化。 + +--- + +## 5. 在 AXAML 中独立使用 + +### 5.1 设置样式变体 + +```xml + + + + + +``` + +### 5.2 设置验证状态 + +```xml + + + +``` + +### 5.3 组合使用 + +```xml + + + +``` + +### 5.4 在 Form 中自动级联 + +```xml + + + + + + + + + + + +``` + +--- + +## 6. NotifyValidateStatus 映射规则 + +当控件接入 Form 时,`NotifyValidateStatus` 是将 Form 验证结果映射到控件视觉反馈的关键方法。标准映射规则如下: + +```csharp +protected virtual void NotifyValidateStatus(FormValidateStatus status) +{ + var inputStatus = status switch + { + FormValidateStatus.Error => InputControlStatus.Error, + FormValidateStatus.Warning => InputControlStatus.Warning, + _ => InputControlStatus.Default + }; + SetCurrentValue(StatusProperty, inputStatus); +} +``` + +| FormValidateStatus | InputControlStatus | 说明 | +|---|---|---| +| `Error` | `Error` | 验证失败,显示红色反馈 | +| `Warning` | `Warning` | 验证警告,显示黄色反馈 | +| `Success` | `Default` | 验证通过,恢复正常样式 | +| `Validating` | `Default` | 验证中,保持正常样式 | +| `Default` | `Default` | 初始状态 | + +> **注意**:使用 `SetCurrentValue` 而非 `SetValue`,以避免覆盖用户的外部绑定。 + +--- + +## 7. 实现检查清单 + +### StyleVariant 接入 + +- [ ] 实现 `IInputControlStyleVariantAware` 接口 +- [ ] 使用 `InputControlStyleVariantProperty.StyleVariantProperty.AddOwner()` 注册属性(**不要** 自行 `Register`) +- [ ] 模板中的内部 AtomUI 输入控件通过 `{TemplateBinding StyleVariant}` 绑定 +- [ ] 默认值为 `Outline`(由共享属性自动提供) + +### Status 接入 + +- [ ] 实现 `IInputControlStatusAware` 接口 +- [ ] 使用 `InputControlStatusProperty.StatusProperty.AddOwner()` 注册属性(**不要** 自行 `Register`) +- [ ] 模板中的内部 AtomUI 输入控件通过 `{TemplateBinding Status}` 绑定 +- [ ] 如果实现了 `IFormItemAware`,在 `NotifyValidateStatus` 中映射 `FormValidateStatus` → `InputControlStatus` +- [ ] 默认值为 `Default`(由共享属性自动提供) + +### Form 集成 + +- [ ] 同时实现 `IFormItemAware`(参见 [Form 集成指南](./FormIntegrationGuide.md)) +- [ ] 同时实现 `ISizeTypeAware`(使 Form 的 SizeType 能级联) +- [ ] 同时实现 `IMotionAwareControl`(使 Form 的动画开关能级联) +- [ ] 所有 `IFormItemAware` 成员使用显式接口实现 + +### 常见错误 + +| 错误 | 后果 | 解决方案 | +|---|---|---| +| 使用 `Register` 而非 `AddOwner` | Form 级联绑定静默失败 | 使用 `AddOwner()` | +| 模板中遗漏 `{TemplateBinding StyleVariant}` | 内部控件不跟随变体变化 | 为所有内部输入控件添加绑定 | +| 模板中遗漏 `{TemplateBinding Status}` | 内部控件不显示验证状态 | 为所有内部输入控件添加绑定 | +| `NotifyValidateStatus` 中使用 `SetValue` | 可能覆盖外部绑定 | 使用 `SetCurrentValue` | +| 未实现 `IInputControlStyleVariantAware` 但 Form 中使用 | Form 不会级联 StyleVariant 到该控件 | 实现接口 | +