在 Avalonia 开发中,导航功能是构建多页面应用的核心需求。Avalonia 无需依赖第三方库,仅通过内置控件与 MVVM 模式即可实现灵活的页面切换。本文将以 "基础导航" 为核心,从 ViewModel 与 View 设计、导航逻辑实现,到样式美化与响应式交互,完整拆解一个多页面 Avalonia 应用的开发流程。
一、核心原理:Avalonia 导航的实现逻辑
Avalonia 实现导航的核心思路是 "控件绑定 + 视图模型切换":
- 核心控件 :使用
TransitioningContentControl
作为页面容器,该控件支持内容切换时的过渡效果,且可直接绑定到 ViewModel 中的属性。 - 状态管理 :在主视图模型(如
MainWindowViewModel
)中定义_currentPage
属性(类型为ViewModelBase
),通过更新该属性的值,触发TransitioningContentControl
加载对应的视图。 - View-ViewModel 匹配 :借助 Avalonia 的
ViewLocator
(通常在项目初始化时自动配置),框架会根据_currentPage
的具体 ViewModel 类型,自动匹配并加载对应的 View(视图)。
二、分步实现:从 ViewModel 到 View 的开发
1. 定义页面 ViewModel
首先创建两个业务页面的 ViewModel(继承自项目基础类 ViewModelBase
),分别对应 "颜色列表页" 和 "关于页"。
(1)ColorsViewModel:颜色列表视图模型
该 ViewModel 负责加载系统颜色、处理颜色数据转换(如 RGB 转 CMYK),并通过响应式命令触发数据初始化。
cs
using Avalonia.Data.Converters;
using Avalonia.Media;
using ReactiveUI.SourceGenerators;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
namespace BasicRoutingExample.ViewModels
{
public partial class ColorsViewModel : ViewModelBase
{
// 响应式属性:当前选中的颜色名称与颜色值
[Reactive]
private string? _colorName;
[Reactive]
private Color? _color;
// 颜色转换器:将 Color 类型转为 CMYK 格式字符串(静态属性,直接复用)
public static FuncValueConverter<Color, string> ToCMYK { get; } = new(color =>
{
double r = color.R / 255.0;
double g = color.G / 255.0;
double b = color.B / 255.0;
double k = 1 - Math.Max(Math.Max(r, g), b);
double C = k < 1 ? (1 - r - k) / (1 - k) : 0;
double M = k < 1 ? (1 - g - k) / (1 - k) : 0;
double Y = k < 1 ? (1 - b - k) / (1 - k) : 0;
return $"C={Math.Round(C * 100, 1)}% M={Math.Round(M * 100, 1)}% Y={Math.Round(Y * 100, 1)}% K={Math.Round(k * 100, 1)}%";
});
// 颜色列表数据源
public ObservableCollection<ColorsViewModel> Colors { get; } = [];
// 无参构造函数(用于框架实例化)
public ColorsViewModel() { }
// 响应式命令:初始化颜色列表(加载 Avalonia 内置颜色)
[ReactiveCommand]
private void Init()
{
// 反射获取 Avalonia.Media.Colors 中的所有静态颜色属性
var colorProperties = typeof(Colors).GetProperties(
BindingFlags.Static | BindingFlags.Public)
.Where(p => p.PropertyType == typeof(Color));
foreach (var property in colorProperties)
{
if (property.GetValue(null) is Color color)
{
Colors.Add(new ColorsViewModel
{
Color = color,
ColorName = property.Name // 颜色名称(如 "Red"、"Blue")
});
}
}
}
}
}
2)AboutViewModel:关于页视图模型
该 ViewModel 主要提供应用基础信息(名称、版本、描述),通过反射获取程序集信息。
cs
using System.Reflection;
namespace BasicRoutingExample.ViewModels
{
public partial class AboutViewModel : ViewModelBase
{
// 应用名称(从当前程序集获取)
public string? AppName => Assembly.GetExecutingAssembly().GetName().Name;
// 应用版本(从当前程序集获取)
public string? Version => Assembly.GetExecutingAssembly().GetName()?.Version?.ToString();
// 应用描述信息
public string Message => "这是基于 Avalonia 框架开发的应用,集成 ReactiveUI 实现响应式交互。";
}
}
2. 设计页面 View(视图)
View 采用 XAML 编写,通过数据绑定关联对应的 ViewModel,实现 "数据驱动视图"。
(1)ColorsView:颜色列表视图
通过 ItemsControl
展示颜色列表,搭配 ColorToHexConverter
和自定义的 ToCMYK
转换器,实现颜色的多格式展示。
XML
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:BasicRoutingExample.ViewModels"
xmlns:b="using:BasicRoutingExample.Behaviors"
xmlns:cv="using:Avalonia.Controls.Converters"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="560"
x:DataType="vm:ColorsViewModel"
<!-- 页面加载时执行 Init 命令,初始化颜色数据 -->
b:LoadedBehavior.ExecuteCommandOnLoaded="{Binding InitCommand}"
x:Class="BasicRoutingExample.Views.ColorsView">
<!-- 资源定义:颜色转 16 进制字符串转换器 -->
<UserControl.Resources>
<cv:ColorToHexConverter x:Key="ColorToHex"
AlphaPosition="Leading"
IsAlphaVisible="False"/>
</UserControl.Resources>
<!-- 布局:顶部标题 + 滚动列表 -->
<Grid RowDefinitions="Auto,*">
<!-- 标题:显示颜色总数 -->
<TextBlock Text="{Binding Colors.Count, StringFormat='Avalonia 系统颜色 ({0}种)'}"
FontSize="18"
Margin="5"/>
<!-- 滚动列表:展示所有颜色 -->
<ScrollViewer Grid.Row="1">
<ItemsControl ItemsSource="{Binding Colors}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Spacing="10"
Margin="5">
<!-- 颜色预览块 -->
<Rectangle Width="300"
Height="30">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding Color}"/>
</Rectangle.Fill>
</Rectangle>
<!-- 颜色名称 -->
<TextBlock Text="{Binding ColorName}"
Width="110"/>
<!-- 16 进制颜色值(使用内置转换器) -->
<TextBlock Text="{Binding Color,
Converter={StaticResource ColorToHex},
ConverterParameter={x:True}}"
Width="80"/>
<!-- CMYK 颜色值(使用 ViewModel 中的静态转换器) -->
<TextBlock Text="{Binding Color,
Converter={x:Static vm:ColorsViewModel.ToCMYK}}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</UserControl>
(2)AboutView:关于页视图
简洁展示应用名称、版本和描述,通过网格布局实现内容对齐。
XML
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:BasicRoutingExample.ViewModels"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="560"
x:DataType="vm:AboutViewModel"
x:Class="BasicRoutingExample.Views.AboutView">
<!-- 布局:两行网格,分别展示"应用信息"和"描述" -->
<Grid RowDefinitions="Auto,Auto">
<!-- 应用名称 + 版本 -->
<StackPanel Orientation="Horizontal"
Spacing="5"
Grid.Row="0">
<TextBlock Text="{Binding AppName}"
FontWeight="Bold"
FontSize="24"/>
<TextBlock Text="{Binding Version}"
FontSize="20"/>
</StackPanel>
<!-- 应用描述 -->
<TextBlock Text="{Binding Message}"
Grid.Row="1"
FontSize="18"
Margin="0,10,0,0"/>
</Grid>
</UserControl>
3. 实现主窗口导航逻辑
主窗口的 ViewModel(MainWindowViewModel
)负责管理页面切换状态,通过响应式命令触发导航,并通过属性监听实现按钮 "选中态" 同步。
MainWindowViewModel:导航核心逻辑
cs
using Microsoft.Extensions.DependencyInjection;
using ReactiveUI;
using ReactiveUI.SourceGenerators;
using System.Reactive.Linq;
namespace BasicRoutingExample.ViewModels
{
public partial class MainWindowViewModel : ViewModelBase
{
// 核心导航属性:当前显示的页面 ViewModel
[Reactive]
private ViewModelBase? _currentPage;
// 导航命令:跳转到"颜色列表页"
[ReactiveCommand]
private void GotoColors()
{
// 避免重复切换同一页面
if (CurrentPage is not ColorsViewModel)
{
// 从 IOC 容器获取 ColorsViewModel 实例
CurrentPage = App.Current.Services?.GetRequiredService<ColorsViewModel>();
}
}
// 导航命令:跳转到"关于页"
[ReactiveCommand]
private void GotoAbout()
{
// 避免重复切换同一页面
if (CurrentPage is not AboutViewModel)
{
// 从 IOC 容器获取 AboutViewModel 实例
CurrentPage = App.Current.Services?.GetRequiredService<AboutViewModel>();
}
}
// 构造函数:初始化默认页面,并监听页面切换状态
public MainWindowViewModel()
{
// 初始页面设为"颜色列表页"
CurrentPage = App.Current.Services?.GetRequiredService<ColorsViewModel>();
// 监听 CurrentPage 变化,同步"颜色页选中态"
_isColorsPage = this.WhenAnyValue(x => x.CurrentPage)
.Select(x => x?.GetType() == typeof(ColorsViewModel))
.ToProperty(this, x => x.IsColorsPage);
// 监听 CurrentPage 变化,同步"关于页选中态"
_isAboutPage = this.WhenAnyValue(x => x.CurrentPage)
.Select(x => x?.GetType() == typeof(AboutViewModel))
.ToProperty(this, x => x.IsAboutPage);
}
// 页面选中态属性(用于按钮样式绑定)
private readonly ObservableAsPropertyHelper<bool> _isColorsPage;
private readonly ObservableAsPropertyHelper<bool> _isAboutPage;
public bool IsColorsPage => _isColorsPage.Value;
public bool IsAboutPage => _isAboutPage.Value;
}
}
4. 配置 IOC 容器(依赖注入)
为了实现 ViewModel 的解耦与复用,通过 Microsoft.Extensions.DependencyInjection
配置 IOC 容器,在应用启动时注册 ViewModel 实例。
App.axaml.cs:应用启动与 IOC 配置
cs
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using BasicRoutingExample.ViewModels;
using BasicRoutingExample.Views;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace BasicRoutingExample
{
public partial class App : Application
{
// 应用初始化:加载 XAML 资源
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
// 框架初始化完成:配置 IOC 并启动主窗口
public override void OnFrameworkInitializationCompleted()
{
// 移除默认数据验证插件(避免不必要的验证逻辑)
BindingPlugins.DataValidators.RemoveAt(0);
// 配置 IOC 服务
Services = ConfigureServices();
// 启动桌面应用主窗口
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(), // 为主窗口绑定 ViewModel
};
}
base.OnFrameworkInitializationCompleted();
}
// 静态属性:获取当前应用实例
public new static App Current => (App)Application.Current!;
// IOC 服务容器
public IServiceProvider? Services { get; private set; }
// 配置 IOC 服务:注册 ViewModel(瞬态模式,每次获取新实例)
private static ServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddTransient<ColorsViewModel>();
services.AddTransient<AboutViewModel>();
return services.BuildServiceProvider();
}
}
}
三、样式美化:实现响应式按钮与全局样式
通过 Avalonia 的样式系统,为按钮添加 "默认态"" hover 态""选中态",并统一全局控件样式(如文本、边框)。
1. 全局样式配置(App.axaml)
XML
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="BasicRoutingExample.App"
xmlns:local="using:BasicRoutingExample"
RequestedThemeVariant="Default"> <!-- 跟随系统主题(浅色/深色) -->
<!-- View-ViewModel 匹配器:自动根据 ViewModel 加载 View -->
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<!-- 资源定义:颜色、渐变等可复用资源 -->
<Application.Resources>
<!-- 主色调 -->
<SolidColorBrush x:Key="PrimaryBackground">#14172D</SolidColorBrush>
<SolidColorBrush x:Key="PrimaryForeground">#cfcfcf</SolidColorBrush>
<!-- 渐变背景 -->
<LinearGradientBrush x:Key="PrimaryGradient" StartPoint="0%,0%" EndPoint="100%,0%">
<GradientStops>
<GradientStop Offset="0" Color="#111214"/>
<GradientStop Offset="1" Color="#151E3E"/>
</GradientStops>
</LinearGradientBrush>
<!-- 交互状态颜色 -->
<SolidColorBrush x:Key="PrimaryHoverBackground">#333853</SolidColorBrush>
<SolidColorBrush x:Key="PrimaryHoverForeground">White</SolidColorBrush>
<SolidColorBrush x:Key="PrimaryActiveBackground">#334488</SolidColorBrush>
<SolidColorBrush x:Key="PrimaryActiveForeground">AliceBlue</SolidColorBrush>
</Application.Resources>
<!-- 全局控件样式 -->
<Application.Styles>
<!-- 基础主题(Fluent 风格) -->
<FluentTheme />
<!-- 文本框默认样式 -->
<Style Selector="TextBlock">
<Setter Property="Foreground" Value="{StaticResource PrimaryForeground}"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<!-- 标题文本样式(用于页面标题) -->
<Style Selector="TextBlock.caption">
<Setter Property="FontSize" Value="28"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<!-- 菜单边框样式 -->
<Style Selector="Border.menu">
<Setter Property="Background" Value="{StaticResource PrimaryGradient}"/>
<Setter Property="Padding" Value="10"/>
</Style>
<!-- 内容区域边框样式 -->
<Style Selector="Border.client">
<Setter Property="Padding" Value="10"/>
<Setter Property="Background" Value="{StaticResource PrimaryBackground}"/>
</Style>
<!-- 按钮基础样式 -->
<Style Selector="Button">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Width" Value="150"/>
</Style>
<!-- 按钮默认态 -->
<Style Selector="Button /template/ContentPresenter">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryForeground}"/>
<Setter Property="FontSize" Value="18"/>
</Style>
<!-- 按钮 hover 态(鼠标悬浮) -->
<Style Selector="Button:pointerover /template/ContentPresenter">
<Setter Property="Background" Value="{StaticResource PrimaryHoverBackground}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryHoverForeground}"/>
</Style>
<!-- 按钮选中态(当前页面对应的按钮) -->
<Style Selector="Button.active /template/ContentPresenter">
<Setter Property="Background" Value="{StaticResource PrimaryActiveBackground}"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryActiveForeground}"/>
</Style>
</Application.Styles>
</Application>
2. 导航按钮实现(主窗口 XAML 片段)
通过 Classes.active
绑定 IsColorsPage
/IsAboutPage
属性,实现 "选中态" 自动切换;通过 Command
绑定导航命令,触发页面跳转。
XML
<!-- 颜色列表页按钮 -->
<Button Command="{Binding GotoColorsCommand}"
Classes.active="{Binding IsColorsPage}">
<StackPanel Orientation="Horizontal" Spacing="15">
<Image Source="/Assets/images/colors.png" Width="32"/>
<TextBlock Text="颜色列表"/>
</StackPanel>
</Button>
<!-- 关于页按钮 -->
<Button Command="{Binding GotoAboutCommand}"
Classes.active="{Binding IsAboutPage}">
<StackPanel Orientation="Horizontal" Spacing="15">
<Image Source="/Assets/images/aboutA.png" Width="32"/>
<TextBlock Text="关于应用"/>
</StackPanel>
</Button>
四、关键技巧与优化建议
1. 轻量转换器 FuncValueConverter
的优势
Avalonia 提供的 FuncValueConverter
无需创建单独的转换器类,可直接在 ViewModel 中定义为静态属性,减少代码冗余。例如本文中 RGB 转 CMYK 的转换器,直接通过 x:Static
引用即可使用:
XML
<TextBlock Text="{Binding Color, Converter={x:Static vm:ColorsViewModel.ToCMYK}}"/>
2. 页面切换的性能优化
- 避免重复实例化 :在导航命令中添加判断(如
CurrentPage is not ColorsViewModel
),防止同一页面被重复创建。 - 选择合适的 IOC 生命周期 :本文使用
AddTransient
(瞬态模式,每次获取新实例),若需保留页面状态,可改为AddSingleton
(单例模式)或AddScoped
(作用域模式)。
3. 响应式框架的选择
- ReactiveUI :适合复杂交互场景,支持属性监听、响应式命令,但需要编写较多模板代码(如
ObservableAsPropertyHelper
相关逻辑)。 - CommunityToolkit.Mvvm :语法更简洁,通过
[ObservableProperty]
[RelayCommand]
等特性减少样板代码,适合快速开发。
五、总结
本文通过一个完整的示例,展示了 Avalonia 基础导航的实现流程:从 ViewModel 设计(数据与命令)、View 布局(XAML 与数据绑定),到导航逻辑(_currentPage
属性管理)、样式美化(响应式按钮与全局样式)。核心要点包括:
- 利用
TransitioningContentControl
与_currentPage
绑定实现页面切换; - 通过 IOC 容器实现 ViewModel 的解耦与实例管理;
- 借助样式系统与属性监听,实现交互状态(如按钮选中态)的自动同步。
掌握这些基础后,可进一步扩展功能,例如添加页面切换动画、参数传递、导航历史记录等,构建更复杂的 Avalonia 多页面应用。
运行效果图
