从 WPF 到 Avalonia 的迁移系列实战篇4:控件模板与 TemplatedControl
从 WPF 到 Avalonia 的迁移系列实战篇4:控件模板与 TemplatedControl
我的GitHub仓库Avalonia学习项目包含完整的Avalonia实践案例与代码对比。
我的gitcode仓库是Avalonia学习项目
在前几篇中,我们聊过了依赖属性、路由事件、资源等迁移相关的基础,这一篇我们进入 自定义控件开发。
如果你在 WPF 中做过复杂控件,就一定绕不开 Control
+ ControlTemplate
的组合。
那么问题来了:
在 Avalonia 中,我们该如何实现类似的机制?
答案就是 —— TemplatedControl
。
为了直观演示,我写了一个小 Demo:自定义一个 三角形控件(TriangleControl),让它自动闪烁。我们分别用 WPF 和 Avalonia 来实现,最后对比一下两者的异同。
一、为什么要用 TemplatedControl?
在 UI 框架里,我们有两种常见的自定义控件方式:
-
UserControl
- 逻辑 + UI 写在一起
- 简单场景够用,但换皮肤、换模板比较困难
-
TemplatedControl(WPF 中就是 Control)
- 逻辑(C#)和外观(XAML 模板)解耦
- 控件类只负责属性和逻辑,不关心 UI 长什么样
- 外观完全交给
ControlTemplate
控制
例如 WPF 的
Button
,内部逻辑并不知道它有边框还是圆角,这些都由ControlTemplate
决定。
Avalonia 完全继承了这一思想,只不过对应的基类换成了TemplatedControl
。
二、WPF 版 TriangleControl
在 WPF 中,我们继承 Control
,通过 OnApplyTemplate()
获取模板里的元素,然后对它做动画。
控件类
public class TriangleControl : Control
{private Path? _path;static TriangleControl(){DefaultStyleKeyProperty.OverrideMetadata(typeof(TriangleControl),new FrameworkPropertyMetadata(typeof(TriangleControl)));}public static readonly DependencyProperty FillProperty =DependencyProperty.Register(nameof(Fill), typeof(Brush),typeof(TriangleControl), new PropertyMetadata(Brushes.Black));public Brush Fill{get => (Brush)GetValue(FillProperty);set => SetValue(FillProperty, value);}public override void OnApplyTemplate(){base.OnApplyTemplate();if (_path != null){// 避免重复应用模板导致多次动画_path.ClearValue(UIElement.OpacityProperty);}_path = GetTemplateChild("PART_Path") as Path;if (_path != null){var blinkAnimation = new DoubleAnimation{From = 1.0,To = 0.2,Duration = TimeSpan.FromSeconds(0.5),AutoReverse = true,RepeatBehavior = RepeatBehavior.Forever};_path.BeginAnimation(UIElement.OpacityProperty, blinkAnimation);}}
}
样式模板
<ResourceDictionaryxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:controls="clr-namespace:WpfDemo.controls"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"><Style TargetType="{x:Type controls:TriangleControl}"><Setter Property="Width" Value="50" /><Setter Property="Height" Value="50" /><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type controls:TriangleControl}"><Viewbox Stretch="Fill"><PathData="M 0,1 L 0.5,0 L 1,1 Z"Fill="{TemplateBinding Fill}"Stretch="Uniform"x:Name="PART_Path" /></Viewbox></ControlTemplate></Setter.Value></Setter></Style>
</ResourceDictionary>
效果:一个三角形控件,透明度循环闪烁。
三、Avalonia 版 TriangleControl
在 Avalonia 中,流程几乎一模一样:
- 继承
TemplatedControl
- 属性用
StyledProperty<T>
- 模板元素通过
e.NameScope.Find<T>()
获取 - 动画系统换成
Animation + KeyFrame
控件类
public class TriangleControl : TemplatedControl
{private Path? _path;public static readonly StyledProperty<IBrush> FillProperty =AvaloniaProperty.Register<TriangleControl, IBrush>(nameof(Fill), Brushes.Black);public IBrush Fill{get => GetValue(FillProperty);set => SetValue(FillProperty, value);}protected override void OnApplyTemplate(TemplateAppliedEventArgs e){base.OnApplyTemplate(e);_path = e.NameScope.Find<Path>("PART_Path");if (_path != null){var animation = new Animation{Duration = TimeSpan.FromSeconds(1),IterationCount = IterationCount.Infinite,Easing = new SineEaseInOut(),Children ={new KeyFrame{Cue = new Cue(0),Setters = { new Setter(Visual.OpacityProperty, 1.0) }},new KeyFrame{Cue = new Cue(0.5),Setters = { new Setter(Visual.OpacityProperty, 0.2) }},new KeyFrame{Cue = new Cue(1),Setters = { new Setter(Visual.OpacityProperty, 1.0) }}}};animation.RunAsync(_path, CancellationToken.None);}}
}
样式模板
<Stylesxmlns="https://github.com/avaloniaui"xmlns:controls="clr-namespace:AvaloniaDemo.Controls"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"><Design.PreviewWith><controls:TriangleControl /></Design.PreviewWith><Style Selector="controls|TriangleControl"><Setter Property="Width" Value="50" /><Setter Property="Height" Value="50" /><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="controls:TriangleControl"><Viewbox Stretch="Uniform"><PathData="M 0,1 L 0.5,0 L 1,1 Z"Fill="{TemplateBinding Fill}"Stretch="Uniform"x:Name="PART_Path" /></Viewbox></ControlTemplate></Setter.Value></Setter></Style>
</Styles>
效果:与 WPF 一致,三角形控件透明度闪烁。
四、WPF vs Avalonia 对比分析
下面是一个详细对比表格:
功能点 | WPF | Avalonia |
---|---|---|
基类 | Control | TemplatedControl |
依赖属性 | DependencyProperty | StyledProperty<T> |
应用模板方法 | OnApplyTemplate() | OnApplyTemplate(TemplateAppliedEventArgs) |
查找模板元素 | GetTemplateChild("PART_X") | e.NameScope.Find<T>("PART_X") |
模板绑定 | {TemplateBinding ...} | {TemplateBinding ...} (完全一致) |
动画系统 | Storyboard / DoubleAnimation | Animation / KeyFrame |
可以看到:
- 整体思想几乎完全一致,因此从 WPF 迁移过来没有学习门槛
- Avalonia 在语法上更简洁,比如属性直接用
StyledProperty
,不需要DependencyProperty.Register
那么啰嗦 - 动画系统的 API 不同,但用法也很直观
五、心得体会
通过这个 Demo,我们发现:
-
WPF 和 Avalonia 在自定义控件的核心思路上保持了高度一致性
-
只要你熟悉 WPF 的
ControlTemplate
模型,迁移到 Avalonia 基本无缝 -
不同点主要体现在:
- API 命名(
DependencyProperty
→StyledProperty
) - 模板元素查找方式
- 动画系统
- API 命名(
因此,掌握 TemplatedControl = 掌握 Avalonia 自定义控件的核心。
✍️ 结语
如果你正打算从 WPF 迁移到 Avalonia,不妨从这种简单的自定义控件开始练手。理解了 TemplatedControl
,你就掌握了 Avalonia 的控件扩展基础。
我的GitHub仓库Avalonia学习项目包含完整的Avalonia实践案例与代码对比。
我的gitcode仓库是Avalonia学习项目