mcpField
A
avalonia-api

avalonia-api

github.com/sharex开发34248 次安装今天更新
代码仓库

关于 avalonia-api

面向使用 .NET 构建 Avalonia UI 的开发者,提供 AXAML 实用参考,涵盖控件、布局、绑定、MVVM 和样式,并包含 XerahS 专用建议。

主要功能

  1. 01AXAML 控件与布局指南
  2. 02编译绑定和 MVVM 模式
  3. 03样式、主题与资源
  4. 04自定义属性和附加属性
  5. 05响应式与可滚动布局实践

适用场景

  1. 01开发视图时查找 Avalonia UI 模式
  2. 02为响应式界面选择布局
  3. 03应用绑定和 MVVM 约定
  4. 04排查滚动和上下文菜单问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/sharex/xerahs --skill avalonia-api

安装后在 Claude Code 中输入/avalonia-api确认已加载。

详细介绍

Avalonia UI 框架——完整 API 与最佳实践指南

目标框架:.NET 10.0+
文件扩展名:.axaml(Avalonia XAML)
官方文档:https://docs.avaloniaui.net/


目录

  1. AXAML 基础知识
  2. 控件与 UI 元素
  3. 布局系统
  4. 数据绑定
  5. 使用 CommunityToolkit.Mvvm 的 MVVM 模式
  6. 样式与主题
  7. 依赖属性与附加属性
  8. 自定义控件
  9. 控件模板
  10. 资源与转换器
  11. 事件与命令
  12. 导航
  13. 跨平台模式
  14. 性能与最佳实践
  15. 开发者工具
  16. 应避免的常见错误
  17. XerahS 中的常见模式

AXAML 基础知识

文件结构

每个 .axaml 文件都遵循以下标准结构:

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:YourApp.ViewModels"
        x:Class="YourApp.Views.MainWindow"
        x:DataType="vm:MainViewModel"
        x:CompileBindings="True">
    
    
    
</Window>
必需的命名空间声明
命名空间用途必需
xmlns="https://github.com/avaloniaui"Avalonia 核心控件✅ 始终需要
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"XAML 语言功能✅ 始终需要
xmlns:vm="using:YourNamespace.ViewModels"ViewModel 引用⚠️ 用于 MVVM
xmlns:local="using:YourNamespace"本地类型/控件🔹 按需使用
自定义命名空间语法

<xmlns:myAlias1="using:AppNameSpace.MyNamespace">


<xmlns:myAlias2="clr-namespace:OtherAssembly.MyNameSpace;assembly=OtherAssembly">


<xmlns:controls="using:XerahS.UI.Controls">
控件内容与属性

<Button>Hello World!</Button>


<Button Content="Hello World!" />


<Button>
    <Button.Content>
        <StackPanel>
            <TextBlock Text="Complex" />
            <TextBlock Text="Content" />
        </StackPanel>
    </Button.Content>
</Button>

控件与 UI 元素

常见内置控件
输入控件
  • TextBox:单行/多行文本输入
  • PasswordBox:密码掩码输入
  • NumericUpDown:可递增/递减的数字输入
  • CheckBox:布尔值切换
  • RadioButton:互斥选择
  • Slider:连续范围选择
  • ComboBox:下拉选择
  • AutoCompleteBox:带建议的文本输入
  • DatePicker:日期选择
  • TimePicker:时间选择
  • ColorPicker:颜色选择
显示控件
  • TextBlock:只读文本显示
  • Label:支持访问键的文本
  • Image:显示图像
  • Border:内容周围的可视边框
  • ContentControl:单内容容器
布局面板
  • Panel:基本容器(填充可用空间)
  • StackPanel:垂直/水平堆叠
  • Grid:行/列网格布局
  • DockPanel:边缘停靠布局
  • Canvas:绝对定位
  • WrapPanel:自动换行流式布局
  • RelativePanel:相对定位
  • UniformGrid:大小相同的单元格
列表与集合
  • ListBox:可选择列表
  • ListView:支持自定义视图的列表
  • TreeView:分层树
  • DataGrid:带列的表格数据
  • ItemsControl:集合显示基类
  • ItemsRepeater:虚拟化集合
容器
  • Window:顶级窗口
  • UserControl:可复用 UI 组件
  • ScrollViewer:可滚动内容
  • Expander:可折叠内容
  • TabControl:选项卡界面
  • SplitView:汉堡菜单模式
按钮
  • Button:标准按钮
  • ToggleButton:双状态按钮
  • RepeatButton:自动重复按钮
  • RadioButton:互斥按钮
  • SplitButton:带下拉菜单的按钮
  • DropDownButton:下拉菜单按钮
高级
  • Carousel:循环播放内容
  • MenuFlyout:基于现代浮出菜单的上下文菜单(⚠️ 与 FluentAvalonia 一起使用)
  • ContextFlyout:右键菜单容器(⚠️ 优先于 ContextMenu 使用)
  • ContextMenu:旧版右键菜单(⚠️ 避免与 FluentAvalonia 主题一起使用)
  • Menu:菜单栏
  • ToolTip:悬停时显示的信息
  • Flyout:弹出式浮层
  • Calendar:日历显示

布局系统

ScrollViewer 激活要求

只有当 ScrollViewer 在 Measure 阶段从父级接收到有限(有界)的高度约束时,才会激活(显示并启用滚动条)。如果任一祖先沿布局链向下传递 ∞(无穷大),ScrollViewer 就永远无法滚动。

XerahS 布局中常见的无限高度来源及其修复方法:

根本原因滚动失效的原因修复方法
SplitView 作为双列外壳继承自 ContentControl;默认 VerticalContentAlignment=Top → ContentPresenter 会传递 ∞ 高度替换为 Grid ColumnDefinitions="auto,*"
TransitioningContentControl 作为页面承载控件内部动画 Panel 在测量期间会传递 ∞ 高度替换为 ContentControl HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"
TabControl 未设置 VerticalContentAlignment="Stretch"内部 ContentPresenter 模板绑定到 {TemplateBinding VerticalContentAlignment};默认值为 Top → 向选项卡内容传递 ∞为 TabControl 添加 VerticalContentAlignment="Stretch"
ScrollViewer Padding="N"会缩小视口,但不会将 N 加入滚动范围——内容底部的 Npx 永远无法访问从 ScrollViewer 移除 Padding;改为在内部的 StackPanel/Grid 上添加 Margin="N"

可滚动设置页面的标准模式:


<TabControl VerticalContentAlignment="Stretch">
    <TabItem Header="General">
        <ScrollViewer>
            <StackPanel Spacing="24" Margin="24">
                
            </StackPanel>
        </ScrollViewer>
    </TabItem>
</TabControl>


<TabControl>  
    <TabItem Header="General">
        <ScrollViewer Padding="24">  
            <StackPanel Spacing="24">
            </StackPanel>
        </ScrollViewer>
    </TabItem>
</TabControl>
布局流程

Avalonia 使用两阶段布局系统:

  1. 测量阶段:确定每个控件的期望大小
  2. 排列阶段:在可用空间内放置控件
Control → Measure → MeasureOverride → DesiredSize
       → Arrange → ArrangeOverride → FinalSize
面板对比
面板使用场景性能复杂度
Panel填充可用空间⚡ 最佳简单
StackPanel线性堆叠⚡ 良好简单
Canvas绝对定位⚡ 良好简单
DockPanel边缘停靠✅ 良好中等
Grid复杂布局⚠️ 一般复杂
RelativePanel相对约束⚠️ 一般复杂

建议:若不需要行列,请使用 Panel 代替 Grid,以获得更好的性能。

常见布局属性
<Control Width="100"                    
         Height="50"                     
         MinWidth="50"                   
         MaxWidth="200"                  
         Margin="10,5,10,5"              
         Padding="5"                     
         HorizontalAlignment="Stretch"   
         VerticalAlignment="Center"      
         HorizontalContentAlignment="Center"  
         VerticalContentAlignment="Center" />
Grid 布局
<Grid RowDefinitions="Auto,*,50"           
      ColumnDefinitions="200,*,Auto">      
    
    <TextBlock Grid.Row="0" Grid.Column="0" Text="Header" />
    <Border Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" />
    
    
    <Grid ColumnDefinitions="*,2*,*">  
        
    </Grid>
</Grid>
DockPanel 布局
<DockPanel LastChildFill="True">
    <Menu DockPanel.Dock="Top" />
    <StatusBar DockPanel.Dock="Bottom" />
    <TreeView DockPanel.Dock="Left" Width="200" />
    
    
    <ContentControl Content="{Binding CurrentView}" />
</DockPanel>
StackPanel 布局
<StackPanel Orientation="Vertical"    
            Spacing="10">              
    <TextBlock Text="Item 1" />
    <TextBlock Text="Item 2" />
    <TextBlock Text="Item 3" />
</StackPanel>
GridSplitter(可调整大小的窗格)

当 Grid 包含不同的内容区域(侧边栏 + 主区域、上下分区)时,添加 GridSplitter,以便用户调整窗格大小。为分隔条预留一个较窄的列或行(3–6 px)。


<Grid ColumnDefinitions="250, 4, *">
    <TreeView Grid.Column="0" />
    <GridSplitter Grid.Column="1" ResizeDirection="Columns" />
    <ContentControl Grid.Column="2" Content="{Binding Detail}" />
</Grid>

关键规则:

  • ResizeDirection 必须与轴向匹配:列分隔条使用 Columns,行分隔条使用 Rows。
  • 在相邻单元格上设置 MinWidth/MaxWidth(或 MinHeight/MaxHeight),防止其缩小到零。
  • 只要 Grid 中有两个或更多可调整大小的内容区域,就使用 GridSplitter。
响应式布局

Avalonia 提供了四种实现方式。优先使用这些方式,而不是固定像素布局。

容器查询(可复用组件首选)

根据祖先控件的大小做出响应,而不是根据窗口大小。控件调整大小时会实时生效。

<Border Container.Name="main" Container.Sizing="Width">
    <Panel.Styles>
        <ContainerQuery Name="main" Query="max-width:600">
            <Style Selector="StackPanel#sidebar">
                <Setter Property="IsVisible" Value="False" />
            </Style>
        </ContainerQuery>
    </Panel.Styles>
    
</Border>
  • 组合条件:Query="min-width:400 and max-width:800"
  • Container.Sizing:Width、Height 或 Width Height
OnFormFactor(静态平台检测)

在启动时解析一次。用于桌面与移动设备之间不会随窗口大小变化而改变的差异。

<Grid ColumnDefinitions="{OnFormFactor Desktop='250,*', Mobile='*'}">
    <Border IsVisible="{OnFormFactor Desktop=True, Mobile=False}" />
</Grid>
自适应重排面板(自我适应)
<ItemsRepeater ItemsSource="{Binding Items}">
    <ItemsRepeater.Layout>
        <UniformGridLayout MinItemWidth="200" MinItemHeight="150" />
    </ItemsRepeater.Layout>
</ItemsRepeater>
何时使用哪种方式
场景方式
可复用组件根据自身大小进行适配容器查询
桌面与移动端布局(静态)OnFormFactor
可换行排列的卡片/图块WrapPanel 或 UniformGridLayout
在断点处进行复杂的多属性更改断点 ViewModel(观察窗口大小,公开布尔属性)

关键规则:

  • 优先使用容器查询,而不是手动处理大小变化事件。
  • Grid 定义中始终使用星号尺寸(*)和 Auto——避免为内容区域指定固定像素宽度。
  • Visibility 枚举已由 bool IsVisible 取代;如需隐藏但保留占用空间,请使用 Opacity="0"。
  • 不要使用 VisualStateManager——改用伪类选择器或容器查询。

数据绑定

绑定语法

<TextBlock Text="{Binding PropertyName}" />


<TextBlock Text="{Binding Person.Name}" />


<TextBox Text="{Binding Name, Mode=TwoWay}" />



<TextBlock x:Name="MyText" Text="Hello" />
<TextBox Text="{Binding #MyText.Text}" />


<TextBlock Text="{Binding $parent[Window].DataContext.Title}" />


<TextBlock Text="{Binding Name, FallbackValue='Unknown'}" />


<TextBlock Text="{Binding Price, StringFormat='${0:F2}'}" />


<TextBlock Text="{Binding IsEnabled, Converter={StaticResource BoolToStringConverter}}" />

重要提示:

  • #ElementName.Property 是 Avalonia 绑定路径扩展,应与 Avalonia Binding / 编译绑定搭配使用。
  • 不要写 {ReflectionBinding #SomeElement.SomeCommand}。ReflectionBinding 会将 #... 标记视为普通路径段,因此运行时可能无法查找命令/属性。
编译绑定(推荐)

编译绑定提供编译时安全性和更好的性能。


<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>


<Window x:DataType="vm:MainViewModel"
        x:CompileBindings="True">
    
    
    <TextBox Text="{Binding FirstName}" />
    <TextBox Text="{Binding LastName}" />
    
    
    <Button Command="{ReflectionBinding DynamicCommandName}" />
</Window>


<TextBox Text="{CompiledBinding FirstName}" />

最佳实践:始终使用编译绑定以确保类型安全并提升性能。仅当路径确实是动态的、无法进行类型化时,才使用 ReflectionBinding;绝不要与 #ElementName 语法搭配使用。

DataContext 类型推断(v11.3+)
<Window x:Name="MyWindow"
        x:DataType="vm:TestDataContext">
    
    
    <TextBlock Text="{Binding #MyWindow.DataContext.StringProperty}" />
    <TextBlock Text="{Binding $parent[Window].DataContext.StringProperty}" />
    
    
</Window>
多重绑定
<TextBlock>
    <TextBlock.Text>
        <MultiBinding StringFormat="{}{0} {1}">
            <Binding Path="FirstName" />
            <Binding Path="LastName" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>
元素绑定

<Slider x:Name="volumeSlider" Minimum="0" Maximum="100" Value="50" />
<TextBlock Text="{Binding #volumeSlider.Value}" />


<Border BorderThickness="{Binding $parent.IsMouseOver, 
                                  Converter={StaticResource BoolToThicknessConverter}}" />

使用 CommunityToolkit.Mvvm 的 MVVM 模式

⚠️ XerahS 使用 CommunityToolkit.Mvvm,而非 ReactiveUI。 不要添加或引用 ReactiveUI 或 Avalonia.ReactiveUI 包。

安装包
dotnet add package CommunityToolkit.Mvvm
ViewModel 基础模式
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

public partial class MainViewModel : ObservableObject
{
    [ObservableProperty]
    private string _firstName = string.Empty;

    [ObservableProperty]
    private bool _isLoading;

    [RelayCommand(CanExecute = nameof(CanSave))]
    private async Task SaveAsync()
    {
        IsLoading = true;
        try
        {
            await Task.Delay(1000); // Simulate save
        }
        finally
        {
            IsLoading = false;
        }
    }

    private bool CanSave() => !string.IsNullOrWhiteSpace(FirstName);
}

源代码生成器说明:

  • 在 private 字段上使用 [ObservableProperty] 会生成一个公开的 PascalCase 属性,并发出 INotifyPropertyChanged 通知(_firstName → FirstName)。
  • [RelayCommand] 会自动生成 SaveAsyncCommand(一个 IAsyncRelayCommand)。
  • 为使源代码生成器正常工作,类必须为 partial。
  • [RelayCommand(CanExecute = nameof(...))] 会自动绑定 CanExecute;条件发生变化时,调用 SaveAsyncCommand.NotifyCanExecuteChanged()。
架构分层
  • 视图(AXAML):仅负责视觉组合。除 InitializeComponent() 外,代码隐藏中不包含业务逻辑。
  • ViewModel:负责状态、命令和编排。与 UI 框架无关,且可进行单元测试。通过 DI 接入服务。
  • 服务 / 领域层:业务逻辑和数据访问——不引用 Avalonia 类型。
视图设置(代码隐藏)
public partial class MainView : UserControl
{
    public MainView()
    {
        InitializeComponent();
        DataContext = new MainViewModel(); // Or resolve via ViewLocator / DI
    }
}
XAML 中的命令绑定

<Button Content="Save" Command="{Binding SaveAsyncCommand}" />


<Button Content="Delete"
        Command="{Binding DeleteCommand}"
        CommandParameter="{Binding SelectedItem}" />
属性更改回调
[ObservableProperty]
private string _name = string.Empty;

// Source-generated partial method — called automatically when Name changes
partial void OnNameChanged(string value)
{
    // React to change
}
手动属性通知(源代码生成器不可用时)
public partial class MyViewModel : ObservableObject
{
    private string _title = string.Empty;

    public string Title
    {
        get => _title;
        set => SetProperty(ref _title, value);
    }
}

样式与主题

样式类型

Avalonia 有三种样式机制:

  1. 样式:类似 CSS,按类型或类定位控件
  2. 控件主题:完整的视觉模板(类似 WPF 样式)
  3. 容器查询:根据容器大小应用响应式样式
基本样式
<Window.Styles>
    
    <Style Selector="TextBlock">
        <Setter Property="Foreground" Value="White" />
        <Setter Property="FontSize" Value="14" />
    </Style>

    
    <Style Selector="TextBlock.header">
        <Setter Property="FontSize" Value="24" />
        <Setter Property="FontWeight" Value="Bold" />
    </Style>

    
    <Style Selector="Button:pointerover">
        <Setter Property="Background" Value="LightBlue" />
    </Style>

    
    <Style Selector="StackPanel > Button">
        <Setter Property="Margin" Value="5" />
    </Style>
</Window.Styles>


<TextBlock Classes="header" Text="Title" />
伪类
<Style Selector="Button:pointerover">         
<Style Selector="Button:pressed">             
<Style Selector="Button:disabled">            
<Style Selector="ListBoxItem:selected">       
<Style Selector="TextBox:focus">              
<Style Selector="CheckBox:checked">           
<Style Selector="ToggleButton:unchecked">     
样式选择器

<Style Selector="StackPanel TextBlock">


<Style Selector="StackPanel > TextBlock">


<Style Selector="Button.primary:pointerover">


<Style Selector="Button, ToggleButton">


<Style Selector="Button:not(.primary)">


<Style Selector="Button /template/ ContentPresenter">
资源
<Window.Resources>
    
    <SolidColorBrush x:Key="PrimaryBrush" Color="#007ACC" />
    
    
    <x:Double x:Key="StandardSpacing">10</x:Double>
    
    
    <LinearGradientBrush x:Key="GradientBrush" StartPoint="0%,0%" EndPoint="0%,100%">
        <GradientStop Color="#FF0000" Offset="0" />
        <GradientStop Color="#00FF00" Offset="1" />
    </LinearGradientBrush>
</Window.Resources>


<Button Background="{StaticResource PrimaryBrush}" 
        Margin="{StaticResource StandardSpacing}" />


<Button Background="{DynamicResource PrimaryBrush}" />
主题

<Application.Styles>
    
    <FluentTheme />
    
    
    <SimpleTheme />
    
    
    <StyleInclude Source="/Styles/CustomStyles.axaml" />
</Application.Styles>

依赖属性与附加属性

StyledProperty(依赖属性)
public class MyControl : ContentControl
{
    // Define the property
    public static readonly StyledProperty<string> TitleProperty =
        AvaloniaProperty.Register<MyControl, string>(
            nameof(Title), 
            defaultValue: string.Empty);

    // CLR wrapper
    public string Title
    {
        get => GetValue(TitleProperty);
        set => SetValue(TitleProperty, value);
    }

    // React to property changes
    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
    {
        base.OnPropertyChanged(change);

        if (change.Property == TitleProperty)
        {
            // Handle change
            var oldValue = (string?)change.OldValue;
            var newValue = (string?)change.NewValue;
        }
    }
}
附加属性
public class MyPanel : Panel
{
    // Define attached property
    public static readonly AttachedProperty<int> ColumnProperty =
        AvaloniaProperty.RegisterAttached<MyPanel, Control, int>(
            "Column",
            defaultValue: 0);

    // Getters/Setters
    public static int GetColumn(Control element)
        => element.GetValue(ColumnProperty);

    public static void SetColumn(Control element, int value)
        => element.SetValue(ColumnProperty, value);
}

<local:MyPanel>
    <Button local:MyPanel.Column="0" Content="First" />
    <Button local:MyPanel.Column="1" Content="Second" />
</local:MyPanel>
常见附加属性

<Button Grid.Row="0" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="3" />


<Menu DockPanel.Dock="Top" />


<Rectangle Canvas.Left="50" Canvas.Top="100" />


<Button ToolTip.Tip="Click me!" />


<Border>
    <Border.ContextFlyout>
        <MenuFlyout>
            <MenuItem Header="Copy" />
            <MenuItem Header="Paste" />
        </MenuFlyout>
    </Border.ContextFlyout>
</Border>

⚠️ XerahS 专属:ContextMenu 与 ContextFlyout

FluentAvalonia 主题的关键问题

问题:标准 ContextMenu 控件无法与 FluentAvaloniaTheme 正常呈现。它们使用旧版 Popup 窗口,样式不完整,可能显示为无样式或不可见。

解决方案:✅ 始终使用 ContextFlyout 与 MenuFlyout,而不是 ContextMenu。


<Border.ContextMenu>
    <ContextMenu>
        <MenuItem Header="Action" Command="{Binding MyCommand}"/>
    </ContextMenu>
</Border.ContextMenu>


<Border.ContextFlyout>
    <MenuFlyout>
        <MenuItem Header="Action" Command="{Binding MyCommand}"/>
    </MenuFlyout>
</Border.ContextFlyout>
DataTemplate 中使用 Flyout 绑定

问题:在 DataTemplate 中使用 ContextFlyout 或 ContextMenu 时,绑定到父级 ViewModel 会失败,因为 Popup/Flyout 位于独立的可视化树中,与 DataTemplate 的层级分离。

解决方案:使用 $parent[UserControl].DataContext 访问主视图的 DataContext。

<DataTemplate x:DataType="local:MyItem">
    <Border>
        <Border.ContextFlyout>
            <MenuFlyout>
                
                <MenuItem Header="Edit" 
                          Command="{Binding $parent[UserControl].DataContext.EditCommand}"
                          CommandParameter="{Binding}"/>
            </MenuFlyout>
        </Border.ContextFlyout>
        
        <TextBlock Text="{Binding Name}" />
    </Border>
</DataTemplate>

要点:

  • 使用 $parent[UserControl].DataContext 在 flyout 中访问视图的 ViewModel
  • CommandParameter="{Binding}" 会传递当前数据项(DataTemplate 的 DataContext)
  • 对于共享 flyout,在 UserControl.Resources 中定义,并通过 {StaticResource} 引用

自定义控件

自绘制的自定义控件
public class CircleControl : Control
{
    public static readonly StyledProperty<IBrush?> FillProperty =
        AvaloniaProperty.Register<CircleControl, IBrush?>(nameof(Fill));

    public IBrush? Fill
    {
        get => GetValue(FillProperty);
        set => SetValue(FillProperty, value);
    }

    public override void Render(DrawingContext context)
    {
        var renderSize = Bounds.Size;
        var center = new Point(renderSize.Width / 2, renderSize.Height / 2);
        var radius = Math.Min(renderSize.Width, renderSize.Height) / 2;

        context.DrawEllipse(Fill, null, center, radius, radius);
    }
}
模板化控件(无外观)
public class MyButton : TemplatedControl
{
    public static readonly StyledProperty<string> TextProperty =
        AvaloniaProperty.Register<MyButton, string>(nameof(Text));

    public string Text
    {
        get => GetValue(TextProperty);
        set => SetValue(TextProperty, value);
    }

    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
    {
        base.OnApplyTemplate(e);
        
        // Find template parts
        var presenter = e.NameScope.Find<ContentPresenter>("PART_ContentPresenter");
    }
}
UserControl(组合控件)

<UserControl xmlns="https://github.com/avaloniaui"
             x:Class="MyApp.Controls.MyUserControl">
    <StackPanel>
        <TextBlock Text="{Binding Title}" />
        <Button Content="Click Me" />
    </StackPanel>
</UserControl>
// MyUserControl.axaml.cs
public partial class MyUserControl : UserControl
{
    public MyUserControl()
    {
        InitializeComponent();
    }
}

控件模板

定义 ControlTheme
<ControlTheme x:Key="CustomButtonTheme" TargetType="Button">
    <Setter Property="Background" Value="Blue" />
    <Setter Property="Foreground" Value="White" />
    <Setter Property="Padding" Value="10,5" />
    <Setter Property="Template">
        <ControlTemplate>
            <Border Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    CornerRadius="5">
                <ContentPresenter Name="PART_ContentPresenter"
                                  Content="{TemplateBinding Content}"
                                  Padding="{TemplateBinding Padding}"
                                  HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                                  VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
            </Border>
        </ControlTemplate>
    </Setter>
    
    
    <Style Selector="^:pointerover /template/ Border">
        <Setter Property="Background" Value="LightBlue" />
    </Style>
    
    <Style Selector="^:pressed /template/ Border">
        <Setter Property="Background" Value="DarkBlue" />
    </Style>
</ControlTheme>


<Button Theme="{StaticResource CustomButtonTheme}" Content="Custom" />
模板部件
[TemplatePart("PART_ContentPresenter", typeof(ContentPresenter))]
public class MyTemplatedControl : TemplatedControl
{
    private ContentPresenter? _presenter;

    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
    {
        base.OnApplyTemplate(e);
        _presenter = e.NameScope.Find<ContentPresenter>("PART_ContentPresenter");
    }
}

资源与转换器

值转换器
public class BoolToVisibilityConverter : IValueConverter
{
    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        if (value is bool boolValue)
            return boolValue ? true : false; // Or specific logic
        return false;
    }

    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        if (value is bool visible)
            return visible;
        return false;
    }
}
<Window.Resources>
    <local:BoolToVisibilityConverter x:Key="BoolToVisConverter" />
</Window.Resources>

<Border IsVisible="{Binding IsActive, Converter={StaticResource BoolToVisConverter}}" />
内置转换器

<Button IsEnabled="{Binding !IsLoading}" />


<TextBlock IsVisible="{Binding MyObject, Converter={x:Static ObjectConverters.IsNotNull}}" />


<TextBlock Text="{Binding Count, StringFormat='Items: {0}'}" />

事件与命令

事件处理程序
<Button Click="OnButtonClick" Content="Click" />
private void OnButtonClick(object? sender, RoutedEventArgs e)
{
    // Handle event
}
命令(MVVM)
<Button Command="{Binding SaveCommand}" 
        CommandParameter="{Binding CurrentItem}"
        Content="Save" />
// Using CommunityToolkit.Mvvm
[RelayCommand]
private void Save(object? parameter)
{
    // Execute command
}

// Async command
[RelayCommand]
private async Task SaveAsync()
{
    await Task.Delay(100);
}

// Command with can-execute
[RelayCommand(CanExecute = nameof(CanDelete))]
private void Delete() { }

private bool CanDelete() => SelectedItem is not null;
路由事件
public static readonly RoutedEvent<RoutedEventArgs> MyEvent =
    RoutedEvent.Register<MyControl, RoutedEventArgs>(
        nameof(MyEvent), 
        RoutingStrategies.Bubble);

public event EventHandler<RoutedEventArgs> MyEvent
{
    add => AddHandler(MyEvent, value);
    remove => RemoveHandler(MyEvent, value);
}

// Raise event
RaiseEvent(new RoutedEventArgs(MyEvent));

跨平台模式

平台检测
if (OperatingSystem.IsWindows())
{
    // Windows-specific code
}
else if (OperatingSystem.IsMacOS())
{
    // macOS-specific code
}
else if (OperatingSystem.IsLinux())
{
    // Linux-specific code
}
特定于平台的资源
<Application.Styles>
    <StyleInclude Source="/Styles/Common.axaml" />
    
    
    <OnPlatform>
        <On Options="Windows">
            <StyleInclude Source="/Styles/Windows.axaml" />
        </On>
        <On Options="macOS">
            <StyleInclude Source="/Styles/macOS.axaml" />
        </On>
    </OnPlatform>
</Application.Styles>
设计原则
  1. 使用 .NET Standard:在 .NET Standard 库中编写业务逻辑
  2. MVVM 模式:将 UI 与逻辑分离
  3. Avalonia 绘制:利用 Avalonia 绘制的 UI(而非原生控件)
  4. 平台抽象:使用接口实现特定于平台的功能
  5. 响应式设计:使用容器查询和自适应布局

性能与最佳实践

性能提示
  1. 无需行或列时使用 Panel 而非 Grid
  2. 全局启用编译绑定
  3. 为大型列表使用虚拟化(ItemsRepeater、VirtualizingStackPanel)
  4. 避免可视化树嵌套过深
  5. 动画中使用 RenderTransform 而非 Margin
  6. 使用 ItemsRepeater 回收 DataTemplate
  7. 通过批量更改属性来尽量减少布局遍历次数
内存管理

CommunityToolkit.Mvvm 源生成的属性会自动引发 PropertyChanged — 无需为 [ObservableProperty] 字段手动取消订阅。订阅外部可观察对象或事件时,请实现 IDisposable:

public partial class MyViewModel : ObservableObject, IDisposable
{
    private readonly IDisposable _subscription;

    public MyViewModel(IMyService service)
    {
        _subscription = service.ValueStream.Subscribe(OnValue);
    }

    private void OnValue(string v) { }

    public void Dispose() => _subscription.Dispose();
}
空值安全

XerahS 使用严格的可空引用类型。请始终:

// Enable in .csproj
<Nullable>enable</Nullable>

// Handle nullability properly
public string? Title { get; set; }  // Nullable
public string Name { get; set; } = string.Empty;  // Non-nullable with default
AOT 与裁剪注意事项

对于面向移动平台(iOS/Android)、WebAssembly 或 NativeAOT 的应用:

  • 优先使用编译绑定(已是默认设置)— 它们可避免反射。
  • 避免使用 Activator.CreateInstance 解析 ViewModel/服务;应使用支持 AOT 的 DI 容器(例如,使用源生成注册的 Microsoft.Extensions.DependencyInjection)。
  • 避免在热点路径中使用 Type.GetType()、PropertyInfo.SetValue() 或运行时反射。
  • 无法避免反射时,使用 [DynamicallyAccessedMembers] 注解。
  • 谨慎使用 {ReflectionBinding} — 它不具备裁剪安全性,且不要将其与 Avalonia #ElementName 路径组合使用。

开发者工具

⚠️ 旧版 Avalonia.Diagnostics 包已弃用。切勿使用。

使用 migrate_diagnostics MCP 工具在任何项目中设置或迁移开发者工具。它会处理以下事项:

  • 移除 Avalonia.Diagnostics
  • 安装 AvaloniaUI.DiagnosticsSupport
  • 配置 Program.cs 和 App.axaml.cs
  • 替换旧 API 调用
# Install the global developer tools CLI
dotnet tool install --global AvaloniaUI.DeveloperTools

规则:始终使用 AvaloniaUI.DiagnosticsSupport 和 AvaloniaUI.DeveloperTools .NET 全局工具。


常见错误及避免方法

#错误正确做法
1使用 .xaml 扩展名使用 .axaml
2使用 WPF 命名空间使用 https://github.com/avaloniaui
3使用 Style.Triggers / DataTrigger / EventTrigger使用伪类选择器
4使用 DependencyProperty使用 StyledProperty 或 DirectProperty
5使用 Style x:Key="..."使用样式类和选择器
6使用 HierarchicalDataTemplate使用 TreeDataTemplate
7使用 pack://application:,,,/ URI使用 avares://AssemblyName/path
8使用 Visibility 枚举使用 bool IsVisible(隐藏但保留空间时使用 Opacity="0")
9缺少 x:DataType编译绑定时始终设置它
10默认使用 {ReflectionBinding}全局启用编译绑定
11使用 {ReflectionBinding #MyRoot.SomeCommand}使用 {Binding #MyRoot.SomeCommand}(或编译绑定作用域)
12使用 Avalonia.Diagnostics使用 AvaloniaUI.DiagnosticsSupport + AvaloniaUI.DeveloperTools
13使用 ReactiveUI / Avalonia.ReactiveUI使用 CommunityToolkit.Mvvm
14手动交换 ContentControl.Content 页面使用 NavigationPage / TabbedPage / DrawerPage
15使用 VisualStateManager使用伪类选择器或容器查询
16使用 LayoutTransform使用 LayoutTransformControl 包裹
17使用 Dispatcher.Invoke()使用 Dispatcher.UIThread.InvokeAsync()
18在 FluentAvalonia 中使用 ContextMenu使用 ContextFlyout + MenuFlyout
19在 StackPanel 外包裹 ScrollViewer Padding="N"将内边距移到内部元素:<StackPanel Margin="N"> — ScrollViewer.Padding 只会缩小视口,不会缩小滚动范围;底部内容会始终无法访问
20将 SplitView 用作无窗格的双列外壳使用 Grid ColumnDefinitions="auto,*" — SplitView 继承自 ContentControl,其默认 VerticalContentAlignment=Top 会将 ∞ 高度传递给子元素,导致任何嵌套的 ScrollViewer 无法正常工作
21将包含 ScrollViewer 的 TransitioningContentControl 用作页面宿主使用普通的 ContentControl HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" — TransitioningContentControl 的动画面板在测量期间会传递 ∞ 高度
22使用默认 VerticalContentAlignment 的 TabControl 包裹可滚动选项卡在 TabControl 上设置 VerticalContentAlignment="Stretch" — 内部 ContentPresenter 模板使用 {TemplateBinding VerticalContentAlignment},默认值为 Top,会将 ∞ 高度传递给选项卡主体

XerahS 中的常见模式

StyledProperty 模式
public static readonly StyledProperty<object?> SelectedObjectProperty =
    AvaloniaProperty.Register<PropertyGrid, object?>(nameof(SelectedObject));

public object? SelectedObject
{
    get => GetValue(SelectedObjectProperty);
    set => SetValue(SelectedObjectProperty, value);
}
附加属性模式(审计)
public static readonly AttachedProperty<bool> IsUnwiredProperty =
    AvaloniaProperty.RegisterAttached<UiAudit, Control, bool>("IsUnwired");

public static bool GetIsUnwired(Control control)
    => control.GetValue(IsUnwiredProperty);

public static void SetIsUnwired(Control control, bool value)
    => control.SetValue(IsUnwiredProperty, value);
窗口结构
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:XerahS.ViewModels"
        x:Class="XerahS.UI.Views.MainWindow"
        x:DataType="vm:MainViewModel"
        x:CompileBindings="True"
        Title="{Binding Title}"
        Width="1000" Height="700">
    
    <Window.Styles>
        
    </Window.Styles>
    
    <DockPanel>
        
    </DockPanel>
</Window>

快速参考表

对齐值
属性值默认值
HorizontalAlignmentLeft、Center、Right、StretchStretch
VerticalAlignmentTop、Center、Bottom、StretchStretch
绑定模式
模式方向更新时机
OneWay源 → 目标源发生变化时
TwoWay源 ↔ 目标双方发生变化时
OneTime源 → 目标初始化时一次
OneWayToSource源 ← 目标目标发生变化时
网格尺寸
类型语法行为
AutoAuto根据内容调整大小
Pixel100固定大小
Star* 或 2*按比例填充

其他资源


新控件/视图检查清单

  • 使用 .axaml 文件扩展名
  • 设置 x:Class 属性
  • 为编译绑定设置 x:DataType
  • 设置 x:CompileBindings="True"(或在 .csproj 中全局启用)
  • 定义正确的命名空间
  • 可设置样式的自定义属性使用 StyledProperty;不可设置样式或性能关键的属性使用 DirectProperty
  • 遵循可空引用类型规则
  • 使用 CommunityToolkit.Mvvm(ObservableObject、[ObservableProperty]、[RelayCommand])实现 MVVM——不要使用 ReactiveUI
  • 将 ViewModel 类标记为 partial,以支持源生成器
  • 使用一致的样式/主题
  • ⚠️ 使用 ContextFlyout + MenuFlyout,不要使用 ContextMenu(兼容 FluentAvalonia)
  • 在 DataTemplates 中为弹出菜单绑定使用 $parent[UserControl].DataContext
  • 对 #ElementName 路径使用 Avalonia Binding;切勿使用 {ReflectionBinding #...}
  • 多页面应用使用 NavigationPage/TabbedPage/DrawerPage,不要手动切换 ContentControl
  • AOT/裁剪:优先使用编译绑定;避免运行时反射
  • 使用 AvaloniaUI.DiagnosticsSupport——切勿使用 Avalonia.Diagnostics
  • 处理无障碍访问(Tab 顺序、可访问名称)
  • 在所有目标平台上测试

最后更新:2026 年 3 月 17 日
版本:1.3.1
维护者:XerahS 开发团队

评论

支持 Markdown