20 KiB
AtomUI 输入控件样式变体与验证状态设计原理
1. 概述
AtomUI 的输入控件提供两个正交的外观维度:样式变体(StyleVariant)控制控件的视觉风格,验证状态(Status)反馈数据校验结果。两者可自由组合,通过 Design Token 系统驱动全部视觉表现,并能在 Form 中自动级联传播。
这一设计对应 Ant Design 5.0 中 Input 组件的 variant 和 status 属性,AtomUI 将其抽象为两组独立的接口 + 共享属性 + Token 映射体系,确保所有输入类控件(包括自定义控件)能够表现一致。
核心设计理念
- 正交性:StyleVariant 和 Status 是两个独立维度,4 种变体 × 3 种状态 = 12 种组合,每种组合在主题中有完整的视觉定义。
- 接口驱动:通过
IInputControlStyleVariantAware和IInputControlStatusAware接口标记控件支持相应能力,Form 据此实现自动级联。 - 共享属性模式:所有控件使用
AddOwner<T>()注册同名StyledProperty,确保属性名一致,级联绑定正确生效。 - Token 驱动视觉:每种 Variant × Status × State(hover / focus / pressed / disabled)组合的颜色均由 Design Token 定义,无硬编码值。
- Form 自动级联:Form → FormItem → 内容控件的 StyleVariant 自动向下传播;验证管线自动将
FormValidateStatus映射为InputControlStatus。
2. 类型定义
2.1 InputControlStyleVariant 枚举
定义在 AtomUI.Controls.Shared/InputControlStyleVariant.cs:
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:
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
public interface IInputControlStyleVariantAware
{
InputControlStyleVariant StyleVariant { get; set; }
}
标记控件支持样式变体切换。Form 系统通过 is IInputControlStyleVariantAware 类型检查来决定是否向子控件级联 StyleVariant。
3.2 InputControlStyleVariantProperty(共享属性持有者)
public abstract class InputControlStyleVariantProperty
{
public const string StyleVariantPropertyName = "StyleVariant";
public static readonly StyledProperty<InputControlStyleVariant> StyleVariantProperty =
AvaloniaProperty.Register<StyledElement, InputControlStyleVariant>(
StyleVariantPropertyName, InputControlStyleVariant.Outline);
}
所有控件 必须 使用 AddOwner<T>() 注册此属性,禁止 自行 Register 同名属性:
// ✅ 正确
public static readonly StyledProperty<InputControlStyleVariant> StyleVariantProperty =
InputControlStyleVariantProperty.StyleVariantProperty.AddOwner<MyControl>();
// ❌ 错误:自行 Register 会导致属性名相同但实例不同,级联绑定失败
public static readonly StyledProperty<InputControlStyleVariant> StyleVariantProperty =
AvaloniaProperty.Register<MyControl, InputControlStyleVariant>("StyleVariant");
3.3 IInputControlStatusAware
public interface IInputControlStatusAware
{
InputControlStatus Status { get; set; }
}
标记控件支持验证状态反馈。当 Form 验证完成后,通过 IFormItemAware.NotifyValidateStatus() 通知控件更新状态。
3.4 InputControlStatusProperty(共享属性持有者)
public abstract class InputControlStatusProperty
{
public const string StatusPropertyName = "Status";
public static readonly StyledProperty<InputControlStatus> StatusProperty =
AvaloniaProperty.Register<StyledElement, InputControlStatus>(
StatusPropertyName, InputControlStatus.Default);
}
同理,必须 使用 AddOwner<T>():
public static readonly StyledProperty<InputControlStatus> StatusProperty =
InputControlStatusProperty.StatusProperty.AddOwner<MyControl>();
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 定义
[ControlDesignToken]
internal class AddOnDecoratedBoxToken : AbstractControlDesignToken
{
/// <summary>
/// 悬浮态边框色
/// </summary>
public Color HoverBorderColor { get; set; }
/// <summary>
/// 激活态边框色
/// </summary>
public Color ActiveBorderColor { get; set; }
/// <summary>
/// 激活态阴影
/// </summary>
public BoxShadow ActiveShadow { get; set; }
/// <summary>
/// 错误状态时激活态阴影
/// </summary>
public BoxShadow ErrorActiveShadow { get; set; }
/// <summary>
/// 警告状态时激活态阴影
/// </summary>
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):
<!-- Outline 变体 -->
<Style Selector="^[StyleVariant=Outline]">
<Setter Property="BorderBrush" Value="{atom:SharedTokenResource ColorBorder}" />
</Style>
<!-- Filled 变体 -->
<Style Selector="^[StyleVariant=Filled]">
<Setter Property="Background" Value="{atom:SharedTokenResource ColorFillTertiary}" />
<Setter Property="BorderBrush" Value="{atom:SharedTokenResource ColorFillTertiary}" />
</Style>
<!-- Borderless 变体 -->
<Style Selector="^[StyleVariant=Borderless]">
<!-- 无边框、无背景 -->
</Style>
<!-- Underlined 变体 -->
<Style Selector="^[StyleVariant=Underlined]">
<Setter Property="BorderBrush" Value="{atom:SharedTokenResource ColorBorder}" />
</Style>
5.2 Status 选择器(嵌套在 StyleVariant 内)
Status 选择器 嵌套 在 StyleVariant 选择器内部,因为不同变体下 Status 的视觉表达方式不同:
<Style Selector="^[StyleVariant=Outline]">
<!-- Outline 默认样式 -->
<Setter Property="BorderBrush" Value="{atom:SharedTokenResource ColorBorder}" />
<!-- Outline + Error -->
<Style Selector="^[Status=Error]">
<Setter Property="BorderBrush" Value="{atom:SharedTokenResource ColorError}" />
<Style Selector="^[IsInnerBoxHover=True]">
<Setter Property="BorderBrush" Value="{atom:SharedTokenResource ColorErrorBorderHover}" />
</Style>
<Style Selector="^:focus-within">
<Setter Property="BorderBrush" Value="{atom:SharedTokenResource ColorError}" />
</Style>
</Style>
<!-- Outline + Warning -->
<Style Selector="^[Status=Warning]">
<Setter Property="BorderBrush" Value="{atom:SharedTokenResource ColorWarning}" />
<Style Selector="^[IsInnerBoxHover=True]">
<Setter Property="BorderBrush" Value="{atom:SharedTokenResource ColorWarningBorderHover}" />
</Style>
<Style Selector="^:focus-within">
<Setter Property="BorderBrush" Value="{atom:SharedTokenResource ColorWarning}" />
</Style>
</Style>
</Style>
5.3 伪类策略
部分控件同时使用属性选择器和伪类来处理 StyleVariant/Status 变化:
// 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 中的级联代码:
// FormItem.cs — 当 Content 变化时设置级联绑定
if (Content is IInputControlStyleVariantAware)
{
_disposables.Add(BindUtils.RelayBind(this, StyleVariantProperty, Content, StyleVariantProperty));
}
FormItemDecorator 中的级联代码:
// FormItemDecorator.cs — 当 Child 变化时设置级联绑定
if (newChild is IInputControlStyleVariantAware)
{
_disposables.Add(BindUtils.RelayBind(this, StyleVariantProperty, newChild, StyleVariantProperty));
}
这意味着开发者只需在 <atom:Form> 上设置一次 StyleVariant,所有子输入控件会自动采用相同的样式变体。
6.2 Status 从验证管线自动设置
Status 不通过级联绑定传播,而是由 Form 验证管线通过 IFormItemAware.NotifyValidateStatus() 设置:
Form 验证管线
↓ ValidateValueAsync()
FormItem
↓ NotifyValidateStatus(FormValidateStatus.Error)
IFormItemAware.NotifyValidateStatus()
↓ 控件内部映射
SetCurrentValue(StatusProperty, InputControlStatus.Error)
控件中的映射代码(以 LineEdit 为例):
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 的颜色切换为对应的状态色:
// 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,下次验证触发时会被覆盖。