Avalonia+ReactiveUI+Sourcegenerators实现记录条数的自动更新
1、项目文件
csharp
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.6" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.6" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.6" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.3.6" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.6" />
<PackageReference Include="ReactiveUI.SourceGenerators" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
2、Model
csharp
using ReactiveUI;
using ReactiveUI.SourceGenerators;
namespace AvaRea.Models
{
public partial class Person:ReactiveObject
{
[Reactive]private string _name;
[Reactive] private int _age;
[Reactive] private string _email;
}
}
3、ViewModel (使用 ReactiveUI Source Generators)
csharp
using AvaRea.Models;
using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
using ReactiveUI.SourceGenerators;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reactive.Linq;
namespace AvaRea.ViewModels;
public partial class MainViewModel : ViewModelBase
{
private int index = 0;
public ObservableCollection<Person> People { get; set; } = new();
[ObservableAsProperty]
private int _peopleCount;
[Reactive]
private Person _selectedPerson;
[ReactiveCommand]
private void AddPerson()
{
People.Add(new Person { Name = $"王五{index++}", Age = 28+index, Email = $"mysoilost{index}@163.com" });
}
public MainViewModel()
{
// 初始化数据
var people = new[]
{
new Person { Name = "张三", Age = 25, Email = "zhang@example.com" },
new Person { Name = "李四", Age = 30, Email = "li@example.com" }
};
People.AddRange(people);
// 监听集合变化
_peopleCountHelper = People
.ToObservableChangeSet()
.Select(_ => People.Count) // 每次变化时获取最新的数量
.ToProperty(this, x => x.PeopleCount);
}
}
using ReactiveUI;
namespace AvaRea.ViewModels;
public class ViewModelBase : ReactiveObject
{
}
4、View
csharp
<Window
x:Class="AvaRea.Views.MainView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:model="clr-namespace:AvaRea.Models"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:AvaRea.ViewModels"
d:DesignHeight="450"
d:DesignWidth="800"
x:DataType="vm:MainViewModel"
mc:Ignorable="d">
<Window.DataTemplates>
<DataTemplate DataType="model:Person">
<StackPanel
Margin="5"
Orientation="Horizontal"
Spacing="10">
<Ellipse
Width="32"
Height="32"
Fill="LightBlue" />
<StackPanel>
<TextBlock FontWeight="Bold" Text="{Binding Name}" />
<TextBlock
FontSize="12"
Foreground="Gray"
Text="{Binding Email}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</Window.DataTemplates>
<Design.DataContext>
<!--
This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs)
-->
<vm:MainViewModel />
</Design.DataContext>
<StackPanel>
<TextBlock Text="{Binding PeopleCount}" />
<ListBox
ItemsSource="{Binding People}"
SelectedItem="{Binding SelectedPerson}" />
<Button Command="{Binding AddPersonCommand}" Content="增加" />
</StackPanel>
</Window>
using Avalonia.Controls;
namespace AvaRea.Views;
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
}
}
5、App.axaml
csharp
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AvaRea.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>
6、App.axaml.cs
csharp
<Application xmlns="https://github.com/avaloniaui"
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using AvaRea.ViewModels;
using AvaRea.Views;
namespace AvaRea;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainView
{
DataContext = new MainViewModel()
};
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
singleViewPlatform.MainView = new MainView
{
DataContext = new MainViewModel()
};
}
base.OnFrameworkInitializationCompleted();
}
}
7、Program.cs
csharp
using System;
using Avalonia;
using Avalonia.ReactiveUI;
namespace AvaRea.Desktop;
class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace()
.UseReactiveUI();
}
总结
Avalonia 给你跨平台的舞台,ReactiveUI 给你响应式的灵魂,Source Generators 给你自动生成代码;三者合体 = 用最少的代码,写出最快、最稳、最易维护的跨平台 .NET UI。