文章目录
- 核心差异
- [Avalonia 避坑指南 (Avalonia for WPF Developers)](#Avalonia 避坑指南 (Avalonia for WPF Developers))
-
- [1. 布局与测量系统](#1. 布局与测量系统)
-
- [1.1.Margin 和 Padding](#1.1.Margin 和 Padding)
- [1.2.Width/Height = "Auto"](#1.2.Width/Height = "Auto")
- 1.3.StackPanel
- 1.4.Bounds
- [2. 数据绑定 (Data Binding)](#2. 数据绑定 (Data Binding))
- [3. 样式与控件模板 (Styles & Control Templates)](#3. 样式与控件模板 (Styles & Control Templates))
-
- 3.1.样式
- [1. 类型选择器 (Type Selector)](#1. 类型选择器 (Type Selector))
- [2. 类选择器 (Class Selector)](#2. 类选择器 (Class Selector))
- [3. 子选择器 (Child Selector)](#3. 子选择器 (Child Selector))
- [4. 后代选择器 (Descendant Selector)](#4. 后代选择器 (Descendant Selector))
- [5. 属性选择器 (Property Selector)](#5. 属性选择器 (Property Selector))
- [6. 伪类选择器 (Pseudo-class Selector)](#6. 伪类选择器 (Pseudo-class Selector))
- [7. ID 选择器 (ID Selector)](#7. ID 选择器 (ID Selector))
- [8. 复合选择器 (Compound Selector)](#8. 复合选择器 (Compound Selector))
- [9. 通配符选择器 (Universal Selector)](#9. 通配符选择器 (Universal Selector))
- [10. 否定选择器 (Not Selector)](#10. 否定选择器 (Not Selector))
- [11. 相邻兄弟选择器 (Adjacent Sibling Selector)](#11. 相邻兄弟选择器 (Adjacent Sibling Selector))
- [12. 通用兄弟选择器 (General Sibling Selector)](#12. 通用兄弟选择器 (General Sibling Selector))
- [13. 属性包含选择器 (Attribute Contains Selector)](#13. 属性包含选择器 (Attribute Contains Selector))
- [14. 属性开头选择器 (Attribute Starts With Selector)](#14. 属性开头选择器 (Attribute Starts With Selector))
- [15. 属性结尾选择器 (Attribute Ends With Selector)](#15. 属性结尾选择器 (Attribute Ends With Selector))
- [16. 嵌套样式选择器](#16. 嵌套样式选择器)
- [17. 多样式选择器](#17. 多样式选择器)
- [18. 模板选择器](#18. 模板选择器)
- [19. 条件样式选择器](#19. 条件样式选择器)
- [20. 派生类选择器](#20. 派生类选择器)
- [21. 子元素位置 ControlType:nth-child(An+B)](#21. 子元素位置 ControlType:nth-child(An+B))
- 功能符号
- 其他
- 3.2.控件模板
- [4. 线程上的差异(Async)](#4. 线程上的差异(Async))
- [5. 平台交互与依赖服务](#5. 平台交互与依赖服务)
- [6. 内存泄漏 (Memory Leaks)](#6. 内存泄漏 (Memory Leaks))
- [7. UI 虚拟化 (UI Virtualization)](#7. UI 虚拟化 (UI Virtualization))
- [8. 命令 (Commands) 的使用](#8. 命令 (Commands) 的使用)
- [9. 事件](#9. 事件)
- 10.其他
-
- [10.1. 调试模式](#10.1. 调试模式)
- [10.2. 加载图像](#10.2. 加载图像)
- [10.3. Prism容器变化](#10.3. Prism容器变化)
- [10.4 axaml定义,后台设定值](#10.4 axaml定义,后台设定值)
- 10.5.初始化相关设置
- 10.6.IsVisible属性
- 10.7.URI
- 10.8.控件相关
核心差异
Avalonia 受 WPF 启发,API 设计非常相似,但它是为了跨平台而从头构建的。因此:
- WPF: 深度集成于 Windows,依赖 DirectX、Windows 主题和 Win32。
- Avalonia: 使用自绘引擎,抽象了平台细节,以实现对 Windows, macOS, Linux, 甚至 Web (WASM) 和移动端的支持。
类结构上的区别
- Control -> TemplatedControl
- WPF中的UIElement和FrameworkElement是非模板控件的基类,大致对应于Avalonia中的Control类。
- WPF中的Control类则是模板控件,Avalonia中相应的类是TemplatedControl。
- FrameworkElement -> Control
- 在WPF/UWP中,从FrameworkElement类继承来创建新的自定义绘制控件,
- 而在Avalonia中,该从Control继承。

类结构图上的区别

Avalonia架构
Avalonia 避坑指南 (Avalonia for WPF Developers)
从 WPF 转向 Avalonia,需要特别注意以下差异:
1. 布局与测量系统
1.1.Margin 和 Padding
Margin 和 Padding的行为在复杂布局中可能与 WPF 有细微差别,可能导致控件"消失"或布局错乱。
1.2.Width/Height = "Auto"
axaml里面控件Width/Height默认值为Auto,如果强制设置为Auto,会报错
Width="Auto"
Height="Auto"
Inner Exception 1:
Exception: Auto is not a valid value for Double.
Inner Exception 2:
FormatException: Input string was not in a correct format.
1.3.StackPanel
StackPanel并不会为每一个控件设置宽度或高度,而是优先控件 (WPF中不是这样):
Eg.
例如在StackPanel中放置若干个TextBlock,你会看到TextBlock延伸到了StackPanel外部,而并没有按照stackpanel的宽度来适配,会超过控件,可以
1.设置绑定: Width="{Binding Width , ElementName=MyStackPanel(是控件x:Name)}"
2.在后台遍历设置宽度:
1.4.Bounds
原WPF属性都可以在这里面Bounds找到:
如元素的真实高宽:Bounds.Height/Width
元素相对于父控件的位置:Bounds.Position.X/Y
2. 数据绑定 (Data Binding)
- 坑: 绑定表达式和转换器的语法高度相似,但背后的机制和部分特性不同。
- 避坑:
- 绑定模式: Avalonia 的默认绑定模式是
Default,其行为取决于属性。对于可写属性,Default通常意味着TwoWay。显式指定绑定模式(如{Binding Name, Mode=OneWay})是好习惯,可以避免意外双向更新。 - 更新触发器: WPF 的
UpdateSourceTrigger=PropertyChanged很常用。在 Avalonia 中,你需要为TextBox等控件显式设置Text="{Binding Name, Mode=TwoWay}"才能实现输入时实时更新。或者使用TextBox.TextInput事件。 - ElementName 绑定: Avalonia 11 之前不支持直接的
ElementName绑定。常用替代方案是使用{Binding #elementName}语法或通过 RelativeSource 查找。
- 绑定模式: Avalonia 的默认绑定模式是
3. 样式与控件模板 (Styles & Control Templates)
3.1.样式
WPF中的触发器Trigger、DataTrigger、EventTrigger 在 Avalonia 中被 Selectors 取代。
Avalonia没有像WPF中定义Style的Key值,然后引用Style这种方式,替换为Selectors
选择器,其语法更接近 CSS,与 WPF 的 Style.Triggers 差异很大。
<!-- WPF -->
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
<!-- Avalonia -->
<Style Selector="Button:pointerover">
<Setter Property="Background" Value="Red"/>
</Style>
在 Avalonia 中,样式选择器用于定义如何将样式应用于特定的控件。Avalonia 提供了多种样式选择器,类似于 WPF 和 CSS 中的选择器。以下是 Avalonia 中常用的样式选择器类型:
1. 类型选择器 (Type Selector)
根据控件的类型来选择应用样式。
<Style Selector="Button">
<Setter Property="Background" Value="Red"/>
</Style>
这将应用于所有的 Button 控件。
2. 类选择器 (Class Selector)
根据控件的 Classes 属性来选择应用样式。
<Style Selector="Button.myClass">
<Setter Property="Background" Value="Blue"/>
</Style>
<Button Classes="myClasses"/>
这将应用于所有具有 myClass 类的 Button 控件。
3. 子选择器 (Child Selector)
选择某个控件的子控件。
<Style Selector="StackPanel > Button">
<Setter Property="Background" Value="Green"/>
</Style>
这将应用于所有作为 StackPanel 直接子元素的 Button 控件。
4. 后代选择器 (Descendant Selector)
选择某个控件的后代控件。
<Style Selector="StackPanel Button">
<Setter Property="Background" Value="Yellow"/>
</Style>
这将应用于所有在 StackPanel 内的 Button 控件,无论它们嵌套多深。
5. 属性选择器 (Property Selector)
根据控件的属性值来选择应用样式。
<Style Selector="Button[IsDefault=true]">
<Setter Property="Background" Value="Orange"/>
</Style>
这将应用于所有 IsDefault 属性为 true 的 Button 控件。
6. 伪类选择器 (Pseudo-class Selector)
根据控件的状态来选择应用样式。
<Style Selector="Button:pointerover">
<Setter Property="Background" Value="Purple"/>
</Style>
这将应用于所有鼠标悬停时的 Button 控件。

7. ID 选择器 (ID Selector)
根据控件的 Name 属性来选择应用样式。
<Style Selector="#myButton">
<Setter Property="Background" Value="Pink"/>
</Style>
这将应用于 Name 为 myButton 的控件。
8. 复合选择器 (Compound Selector)
组合多个选择器来选择应用样式。
<Style Selector="Button.myClass:pointerover">
<Setter Property="Background" Value="Brown"/>
</Style>
这将应用于所有具有 myClass 类并且鼠标悬停时的 Button 控件。
9. 通配符选择器 (Universal Selector)
选择所有控件。
<Style Selector="*">
<Setter Property="Margin" Value="5"/>
</Style>
这将应用于所有控件。
10. 否定选择器 (Not Selector)
排除某些控件。
<Style Selector="Button:not(.myClass)">
<Setter Property="Background" Value="Gray"/>
</Style>
<Button Classes="myClass"/>
<Button Width="120" Height="40"/>
这将应用于所有不具有 myClass 类的 Button 控件。
11. 相邻兄弟选择器 (Adjacent Sibling Selector)
选择紧跟在某个控件后面的兄弟控件。
<Style Selector="Button + TextBlock">
<Setter Property="Foreground" Value="Red"/>
</Style>
这将应用于紧跟在 Button 后面的 TextBlock 控件。
12. 通用兄弟选择器 (General Sibling Selector)
选择某个控件后面的所有兄弟控件。
<Style Selector="Button ~ TextBlock">
<Setter Property="Foreground" Value="Blue"/>
</Style>
这将应用于所有在 Button 后面的 TextBlock 控件。
13. 属性包含选择器 (Attribute Contains Selector)
根据属性值是否包含某个字符串来选择应用样式。
<Style Selector="Button[Content*= 'Save']">
<Setter Property="Background" Value="Green"/>
</Style>
这将应用于 Content 属性包含 "Save" 的 Button 控件。
14. 属性开头选择器 (Attribute Starts With Selector)
根据属性值是否以某个字符串开头来选择应用样式。
<Style Selector="Button[Content^= 'Save']">
<Setter Property="Background" Value="Yellow"/>
</Style>
这将应用于 Content 属性以 "Save" 开头的 Button 控件。
15. 属性结尾选择器 (Attribute Ends With Selector)
根据属性值是否以某个字符串结尾来选择应用样式。
<Style Selector="Button[Content$= 'Cancel']">
<Setter Property="Background" Value="Red"/>
</Style>
16. 嵌套样式选择器
样式可以嵌套在其他样式中。要嵌套样式,只需将子样式作为父 <Style> 元素的子元素包含,并在子选择器的开头加上嵌套选择器 ^ (可以理解为外部样式的缩写)
<Style Selector="TextBlock.h1">
<Setter Property="FontSize" Value="24"/>
<Setter Property="FontWeight" Value="Bold"/>
<Style Selector="^:pointerover">
<Setter Property="Foreground" Value="Red"/>
</Style>
</Style>
等价于:
<Style Selector="TextBlock.h1">
<Setter Property="FontSize" Value="24"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
<Style Selector="TextBlock.h1:pointerover">
<Setter Property="FontSize" Value="24"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Red"/>
</Style>
17. 多样式选择器
如果你想样式应用于多个不同控件,可以用逗号分隔:ControlType1,ControlType2
<Style Selector="TextBlock, Button">
<Setter Property="Background" Value="Yellow"/>
</Style>
18. 模板选择器
<StackPanel>
<StackPanel.Styles>
<Style Selector="Button:pressed /template/ ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="Red"/>
</Style>
</StackPanel.Styles>
<Button>I will have red text when pressed.</Button>
</StackPanel>
19. 条件样式选择器
<Style Selector="Button">
<Setter Property="Foreground" Value="Blue"/>
</Style>
<Style Selector="Button.accent">
<Setter Property="Foreground" Value="Red"/>
</Style>
<Button Classes.accent="{Binding IsSpecial}" />
// 如果我们想要取反,直接加!即可 !IsSpecial
20. 派生类选择器
is(ControlBase)
<Style Selector=":is(Button)">
<Style Selector=":is(local|Button)">
// 派生自Control,且Classes = "margin2"
<Style Selector=":is(Control).margin2">
<Style Selector=":is(local|Control.margin2)">
21. 子元素位置 ControlType:nth-child(An+B)
从前往后,第B个开始,步长为A,子元素位置与An+B结果相等时应用样式,计算结果小于1时不起作用,n=0,1,2,3...
<Style Selector="TextBlock:nth-child(2n+3)">
与此相反,从末尾开始往前计算的也有:ControlType:nth-last-child(An+B)
<Style Selector="TextBlock:nth-last-child(2n+3)">
直接指定位置:
<Style Selector="TextBlock:nth-child(3)">
奇偶数:odd / even
<Style Selector="TextBlock:nth-child(odd)">
<Style Selector="TextBlock:nth-child(even)">
功能符号
An+B 表示列表中的元素,其索引有An+B定义的自定义数字模式中的索引匹配,其中:
A 是整数步长
B 是整数偏移量
n 是所有非负整数,从0开始。
可以理解为从B开始的每个A th元素。

这里有一个在线测试网站,可以方便的观察CSS样式
在线测试::nth Tester
其他
如果控件不是Avalonia自带的,需要带上程序集名(xmlns名称) ,通过竖线 | 分开
<Style Selector="controls|ImageRadioButton">
<Setter Property="Foreground" Value="{DynamicResource B700}" />
<Setter Property="Background" Value="{DynamicResource N950}" />
<Setter Property="FontSize" Value="{DynamicResource BaseFontSize60}" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
3.2.控件模板
- 模板绑定: 在控件模板中,始终使用
{TemplateBinding}而不是普通绑定来连接模板化父控件的属性。这是性能和安全性的最佳实践。 - 主题与资源: 资源字典(ResourceDictionary)的概念类似。
4. 线程上的差异(Async)
在 UI 线程外操作 UI 控件会导致跨线程访问异常。
-
使用
Dispatcher.UIThread.InvokeAsync: 在任何后台线程或异步方法中更新 UI 时,必须通过 Dispatcher 派回 UI 线程。await Task.Run(async () =>
{
var result = await HeavyWorkAsync();
await Dispatcher.UIThread.InvokeAsync(() =>
{
MyTextBox.Text = result;
});
});
访问UI线程与WPF类似也是使用Dispatcher:
4.1.异步执行不等待Post
Dispatcher.UIThread.Post(()=>LongRunningTask(), DispatcherPriority.Background);
Post 方法是 Avalonia.Threading.Dispatcher.UIThread.Post 的一个方法。它将一个操作(通常是一个 Action)添加到 UI 线程的任务队列中,然后立即返回,不返回任何 Task 或等待机制。
特点:
- "即发即忘": Post 不会返回 Task,因此你无法用 await 来等待它完成。它只是把任务扔给了 UI 线程,然后就"不管了"。
- 非阻塞: 它永远不会阻塞调用线程。
- 返回值: 它不返回任何东西。
使用场景:
当你只想在 UI 线程上执行一个操作,并且 不需要知道它什么时候完成,也不需要获取它的结果 时,Post 非常适合。例如,一个后台任务运行完毕后,只想更新一下 UI 上的状态文本,但并不关心这个更新动作本身是否成功或何时完成。
4.2.异步执行可等待InvokeAsync
await Dispatcher.UIThread.InvokeAsync(()=>LongRunningTask(), DispatcherPriority.Background);
InvokeAsync 方法是 Avalonia.Threading.Dispatcher.UIThread.InvokeAsync 的一个方法。它将一个操作添加到 UI 线程的任务队列中,并返回一个 Task。
特点:
- 可等待: 因为它返回一个 Task,你可以使用 await 来等待 UI 线程上的操作完成,这使得它非常适合于异步编程模型。
- 非阻塞 :
await使得调用线程在等待时不会被阻塞。 - 可返回值: 如果你调用的操作返回一个值,InvokeAsync 可以返回一个 Task,让你获取到 UI 线程上的操作结果。
使用场景:
当你需要 等待 UI 线程上的操作完成,或者 需要获取 UI 线程上操作的返回值 时,InvokeAsync 是更好的选择。这在许多异步流程中非常常见。
5. 平台交互与依赖服务
- 无法直接使用
Microsoft.Win32等 Windows 特有的命名空间来打开文件对话框或访问注册表。 - 避坑:
- 使用 Avalonia 的抽象: 使用
TopLevel.GetTopLevel(this).StorageProvider来异步打开文件对话框(这是跨平台的)。 - 依赖注入: 对于平台特定的功能(如通知、系统信息),创建接口,并在平台相关的项目中实现它们,然后在 App 启动时通过 DI 容器注册和解析。
- 使用 Avalonia 的抽象: 使用
6. 内存泄漏 (Memory Leaks)
- 坑: 忘记注销事件订阅、长时间存在的静态对象持有视图的引用等,会导致视图和ViewModel无法被垃圾回收。
- 避坑:
- 事件订阅: 如果某个对象生命周期比事件发布者长,记得在适当时候(如
Unloaded事件中)使用=取消订阅。 - 静态事件: 谨慎订阅静态事件,如果不注销,一定会泄漏。
- 事件订阅: 如果某个对象生命周期比事件发布者长,记得在适当时候(如
7. UI 虚拟化 (UI Virtualization)
- 坑: 在
StackPanel或Canvas中放置大量数据项,会导致所有项一次性渲染,造成UI卡顿和内存激增。Avalonia 的ItemsControl默认是虚拟化的
8. 命令 (Commands) 的使用
- 坑:
ICommand的CanExecute状态不会自动通知更新。 - 避坑:
- 手动通知: 在使用
RelayCommand或DelegateCommand时,在条件改变后手动调用RaiseCanExecuteChanged()方法。 - 使用响应式编程: 可以使用
ReactiveCommand,它能够自动根据IObservable<bool>来管理CanExecute状态。
- 手动通知: 在使用
**8.1.**直接绑定到方法
Avalonia可以直接绑定到方法上而无需创建ICommand:
<Window xmlns="https:///avaloniaui">
...
<StackPanel Margin="20">
<Button Command="{Binding DoTest}"
CommandParameter="test">
Run the example</Button>
</StackPanel>
</Window>
namespace AvaloniaDemo.ViewModels
{
public class MainWindowViewModel
{
public void DoTest(object msg)
{
Debug.WriteLine($"The action was called. {msg}");
}
//表示是否可执行
public bool CanDoTest(object msg)
{
if (msg!=null) return !string.IsNullOrWhiteSpace( msg.ToString() );
return false;
}
}
}
Avalonia默认会在绑定的方法上加上Can,用来查找是否定义了可执行方法。
9. 事件
9.1.无法自动生成
1.直接在axaml中定义事件有时候不会成功,可以在后台中定义,例如:
<Button x:Name="btn" Click="Btn_Click">Click Me</Button>
private void Btn_Click(object sender, RoutedEventArgs args)
{
//...
}
若不成功可以:
this.Get<Button>("btn").Click+=Btn_Click;
9.2.事件的更替
WPF中的MouseDown/Up事件和Preview事件
Avalonia中将Preview替换成了 Tapped 事件
PointerPressed ---> MouseDown
PointerReleased ---> MouseUp
为控件注册一个Preview事件:
你的控件名.AddHandler(PointerReleasedEvent,事件名称, RoutingStrategies.Tunnel);
10.其他
10.1. 调试模式
Avalonia不支持热重载,如需热重载需要添加相应Nuget包,但对于大型项目不是很友好。
在主窗口对应axaml.cs里添加如下代码,在Debug模式下运行调试,进入程序后,F12进入调试模式
#if DEBUG
this.AttachDevTools();
#endif
开启Avalonia自带开发者工具Avalonia DevTools调试

10.2. 加载图像
在加载图像资源的时候,Avalonia没有WPF中处理图像的BitmapImage,Avalonia 并未内置可以直接添加到 ResourceDictionary 中的 Bitmap 对象。而且如果你尝试直接将 Avalonia 资源路径(以 avares:// 开头的路径)绑定给 Image 控件的 Source 属性,会发现图片无法正常显示,故原先加载图像资源的方式有所改变
public class AssetBitmap(string url) : Bitmap(AssetLoader.Open(new Uri(url)));
自定义类,该类型派生自 Bitmap,并向外暴露一个构造函数。接收一个 Avalonia 资源路径用于初始化位图。
P.S.
对于Avalonia的样式资源,这里生成操作统一变为AvaloniaResource
10.3. Prism容器变化
由于原先采用的Unity容器,底层基于Prism.WPF,Avalonia无法使用,所以采用DryIoc容器。
基于Prism为Avalonia版本定制的框架(这里采用9.0.537.11130版本)


10.4. Prism区域加载方式
Prism 区域发现(Region Discovery)的机制在 Avalonia 中没有自动生效。
在 WPF 中,能够自动扫描 XAML 树中的 prism:RegionManager.RegionName 属性,并创建相应的区域。但在 Avalonia 中,Prism 无法利用 Avalonia 依赖属性系统的特性来实现自动发现。
所以原先区域导航这个方法无法使用
_regionManager.RequestNavigate(RegionName, ViewUri);
更改为
_regionManager.RegisterViewWithRegion(RegionName, Type.GetType(ViewUri));
10.4 axaml定义,后台设定值
在WPF中,当你在xaml文件中定义完UI并设置x:Name就可以在后台中直接使用对象名称进行操作.
那是因为vs在你设计时自动生成了.g.i.cs文件(你可以在/obj中看到)
而avalonia中不同,你需要在后台中自己Get到这个UI对象(与Android类似): 例如:
axaml中定义一个名称为 TB_Title的TextBlock文本标签:
<TextBlock x:Name="tb_test"
HorizontalAlignment="Center"
Foreground="White"
FontSize="14" />
在cs中定义并更改标签内容:
TextBlock tb_test= this.Get<TextBlock>("tb_test");
TB_Title.Text = "test";
10.5.初始化相关设置
由于Avalonia使用Prism容器为DryIoc,所以在App继承的类更改为PrismApplication,对应引用命名空间为
using Prism.DryIoc;
需要重写CreateContainerExtension方法进行初始化容器
protected override IContainerExtension CreateContainerExtension()
{
var extension = new DryIocContainerExtension();
ServiceLocator.SetLocatorProvider(() => new DryIocServiceLocator(extension.Instance));
return extension;
}
同样对于Prism加载模块可以使用原先读取配置文件的形式,目前工程里采用的为AddModule的形式
moduleCatalog.AddModule
10.6.IsVisible属性
Avalonia 并没有直接的 Visibility 枚举,而是使用 bool 类型的 IsVisible 属性
10.7.URI
这是最核心的区别。WPF 使用 pack:// 方案,而 Avalonia 引入了自己的一套方案。
- WPF:
- 使用 pack://application:,/ 方案来访问应用程序内部的资源。
- Avalonia :
- 主要使用 avares:// 方案来访问嵌入式资源。
10.8.控件相关
Avalonia中CalendarDatePicker代替WPF中的DatePicker
DatePicker ---> CalendarDatePicker
Avalonia中没有MessageBox控件,需自己实现或者引用第三方开源组件