mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-18 22:46:38 +08:00
14 KiB
14 KiB
自定义控件接入 StyleVariant 与 Status 开发指南
1. 概述
本文档面向控件开发者,详细说明如何让自定义控件支持 AtomUI 的样式变体(InputControlStyleVariant)和验证状态(InputControlStatus)系统,使其与 AtomUI 内置输入控件表现一致,并能在 Form 中自动级联。
2. 快速判断:你的控件需要实现哪些接口?
你的控件是输入类控件吗?(用户通过它输入/选择/切换值)
│
├── 是 → 建议同时实现 IInputControlStyleVariantAware 和 IInputControlStatusAware
│ │
│ ├── 控件需要 Form 验证? → 必须同时实现 IFormItemAware
│ ├── 控件需要跟随 Form 尺寸? → 实现 ISizeTypeAware
│ └── 控件需要跟随 Form 动画开关? → 实现 IMotionAwareControl
│
└── 否 → 不需要实现这两个接口
3. 基础实现模板
3.1 最小实现
以下展示了一个自定义输入控件同时支持 StyleVariant 和 Status 的最小实现:
using AtomUI.Controls;
using Avalonia;
using Avalonia.Controls.Primitives;
namespace MyApp.Controls;
public class MyInput : TemplatedControl,
IInputControlStyleVariantAware,
IInputControlStatusAware
{
// ===== 1. 使用 AddOwner 注册共享属性 =====
public static readonly StyledProperty<InputControlStyleVariant> StyleVariantProperty =
InputControlStyleVariantProperty.StyleVariantProperty.AddOwner<MyInput>();
public static readonly StyledProperty<InputControlStatus> StatusProperty =
InputControlStatusProperty.StatusProperty.AddOwner<MyInput>();
// ===== 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 完整集成的自定义输入控件模板:
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<InputControlStyleVariant> StyleVariantProperty =
InputControlStyleVariantProperty.StyleVariantProperty.AddOwner<MyInput>();
public static readonly StyledProperty<InputControlStatus> StatusProperty =
InputControlStatusProperty.StatusProperty.AddOwner<MyInput>();
public static readonly StyledProperty<SizeType> SizeTypeProperty =
SizeTypeControlProperty.SizeTypeProperty.AddOwner<MyInput>();
public static readonly StyledProperty<bool> IsMotionEnabledProperty =
MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner<MyInput>();
// ===== 控件自有属性 =====
public static readonly StyledProperty<string?> ValueProperty =
AvaloniaProperty.Register<MyInput, string?>(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<MyInput>(
(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 模板示例
<ControlTheme x:Key="{x:Type local:MyInput}" TargetType="local:MyInput">
<Setter Property="Template">
<ControlTemplate>
<StackPanel Orientation="Horizontal" Spacing="10">
<!-- 将 StyleVariant、Status、SizeType 全部传递给内部 LineEdit -->
<atom:LineEdit SizeType="{TemplateBinding SizeType}"
IsMotionEnabled="{TemplateBinding IsMotionEnabled}"
Status="{TemplateBinding Status}"
StyleVariant="{TemplateBinding StyleVariant}"
Width="200"
Text="{TemplateBinding Value, Mode=TwoWay}" />
<!-- 非输入控件不需要传递 StyleVariant 和 Status -->
<atom:Button SizeType="{TemplateBinding SizeType}"
IsMotionEnabled="{TemplateBinding IsMotionEnabled}">
Submit
</atom:Button>
</StackPanel>
</ControlTemplate>
</Setter>
</ControlTheme>
4.2 实际示例
验证码控件(Captcha)— 组合 LineEdit + Button:
<ControlTheme x:Key="{x:Type gallery:Captcha}" TargetType="gallery:Captcha">
<Setter Property="Template">
<ControlTemplate>
<StackPanel Orientation="Horizontal" Spacing="10">
<atom:LineEdit SizeType="{TemplateBinding SizeType}"
IsMotionEnabled="{TemplateBinding IsMotionEnabled}"
Status="{TemplateBinding Status}"
StyleVariant="{TemplateBinding StyleVariant}"
Width="200"
Text="{TemplateBinding Value, Mode=TwoWay}"/>
<atom:Button SizeType="{TemplateBinding SizeType}"
IsMotionEnabled="{TemplateBinding IsMotionEnabled}">
Get captcha
</atom:Button>
</StackPanel>
</ControlTemplate>
</Setter>
</ControlTheme>
价格输入控件(PriceInput)— 组合 NumericUpDown + Select:
<ControlTheme x:Key="{x:Type gallery:PriceInput}" TargetType="gallery:PriceInput">
<Setter Property="Template">
<ControlTemplate>
<StackPanel Orientation="Horizontal" Spacing="10">
<atom:NumericUpDown SizeType="{TemplateBinding SizeType}"
IsMotionEnabled="{TemplateBinding IsMotionEnabled}"
Status="{TemplateBinding Status}"
StyleVariant="{TemplateBinding StyleVariant}"
Width="120"
Value="0"
Name="NumberInput"
Minimum="0"/>
<atom:Select SizeType="{TemplateBinding SizeType}"
IsMotionEnabled="{TemplateBinding IsMotionEnabled}"
Status="{TemplateBinding Status}"
StyleVariant="{TemplateBinding StyleVariant}"
DefaultValues="RMB"
Name="UnitInput">
<atom:SelectOption Header="RMB" Content="RMB"/>
<atom:SelectOption Header="Dollar" Content="Dollar"/>
</atom:Select>
</StackPanel>
</ControlTemplate>
</Setter>
</ControlTheme>
要点:每个内部的 AtomUI 输入控件都绑定了
SizeType、IsMotionEnabled、Status、StyleVariant四个属性。这样当 Form 级联传播这些属性到外层自定义控件时,内部子控件会自动跟随变化。
5. 在 AXAML 中独立使用
5.1 设置样式变体
<!-- 四种变体 -->
<atom:LineEdit PlaceholderText="Outlined" StyleVariant="Outline" />
<atom:LineEdit PlaceholderText="Filled" StyleVariant="Filled" />
<atom:LineEdit PlaceholderText="Borderless" StyleVariant="Borderless" />
<atom:LineEdit PlaceholderText="Underlined" StyleVariant="Underlined" />
5.2 设置验证状态
<!-- 手动设置 Status(不在 Form 中时) -->
<atom:LineEdit PlaceholderText="Error" Status="Error" />
<atom:LineEdit PlaceholderText="Warning" Status="Warning" />
5.3 组合使用
<!-- StyleVariant + Status 自由组合 -->
<atom:LineEdit PlaceholderText="Filled + Error"
StyleVariant="Filled"
Status="Error" />
<atom:LineEdit PlaceholderText="Underlined + Warning"
StyleVariant="Underlined"
Status="Warning" />
5.4 在 Form 中自动级联
<!-- Form 级别设置 StyleVariant,所有子控件自动跟随 -->
<atom:Form StyleVariant="Filled" SizeType="Large">
<atom:FormItem LabelText="Username" FieldName="username">
<!-- 此 LineEdit 自动采用 Filled 变体 + Large 尺寸 -->
<atom:LineEdit PlaceholderText="Enter username" />
</atom:FormItem>
<atom:FormItem LabelText="Email" FieldName="email">
<!-- 此 LineEdit 也自动采用 Filled 变体 + Large 尺寸 -->
<atom:LineEdit PlaceholderText="Enter email" />
</atom:FormItem>
</atom:Form>
6. NotifyValidateStatus 映射规则
当控件接入 Form 时,NotifyValidateStatus 是将 Form 验证结果映射到控件视觉反馈的关键方法。标准映射规则如下:
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<T>()注册属性(不要 自行Register) - 模板中的内部 AtomUI 输入控件通过
{TemplateBinding StyleVariant}绑定 - 默认值为
Outline(由共享属性自动提供)
Status 接入
- 实现
IInputControlStatusAware接口 - 使用
InputControlStatusProperty.StatusProperty.AddOwner<T>()注册属性(不要 自行Register) - 模板中的内部 AtomUI 输入控件通过
{TemplateBinding Status}绑定 - 如果实现了
IFormItemAware,在NotifyValidateStatus中映射FormValidateStatus→InputControlStatus - 默认值为
Default(由共享属性自动提供)
Form 集成
- 同时实现
IFormItemAware(参见 Form 集成指南) - 同时实现
ISizeTypeAware(使 Form 的 SizeType 能级联) - 同时实现
IMotionAwareControl(使 Form 的动画开关能级联) - 所有
IFormItemAware成员使用显式接口实现
常见错误
| 错误 | 后果 | 解决方案 |
|---|---|---|
使用 Register 而非 AddOwner |
Form 级联绑定静默失败 | 使用 AddOwner<T>() |
模板中遗漏 {TemplateBinding StyleVariant} |
内部控件不跟随变体变化 | 为所有内部输入控件添加绑定 |
模板中遗漏 {TemplateBinding Status} |
内部控件不显示验证状态 | 为所有内部输入控件添加绑定 |
NotifyValidateStatus 中使用 SetValue |
可能覆盖外部绑定 | 使用 SetCurrentValue |
未实现 IInputControlStyleVariantAware 但 Form 中使用 |
Form 不会级联 StyleVariant 到该控件 | 实现接口 |