1、必要的包引用
csharp
<PackageReference Include="Avalonia" Version="11.3.6" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.3.6" />
<PackageReference Include="ReactiveUI.SourceGenerators" Version="2.4.1">
2、ViewModel (使用 ReactiveUI Source Generators)
csharp
using ReactiveUI;
using ReactiveUI.SourceGenerators;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AvaRea.ViewModels
{
public partial class TestCmdViewModel: ViewModelBase
{
private IObservable<bool> _canExecute;
private IObservable<bool> _canCancel;
private CancellationTokenSource _cancellationTokenSource = new();
[Reactive] private string _status = "就绪";
[Reactive] private bool _isExecuting = false;
[ObservableAsProperty] private bool _isCancelEnabled =>!_isExecuting;
[ReactiveCommand(CanExecute = nameof(_canExecute))]
private async void ExecuteAsync(CancellationToken cancellationToken)
{
IsExecuting = true;
Status = "任务执行中...";
try
{
// 创建新的 CancellationTokenSource 并链接命令的cancellationToken
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
// 模拟异步工作
for (int i = 0; i <= 100; i++)
{
// 检查取消请求
_cancellationTokenSource.Token.ThrowIfCancellationRequested();
Status = $"进度: {i}%";
await Task.Delay(100, cancellationToken); // 模拟工作
}
Status = "任务完成!";
}
catch (OperationCanceledException)
{
Status = "任务已取消";
}
finally
{
IsExecuting = false;
IsExecuting = false;
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
}
}
[ReactiveCommand(CanExecute =nameof(_canCancel))]
private void CancelExecution()
{
_cancellationTokenSource?.Cancel();
Status = "正在取消...";
}
public TestCmdViewModel()
{
Status = "初始化状态";
_canExecute= this.WhenAnyValue(x => x.IsExecuting, x =>!x);
_canCancel = this.WhenAnyValue(x => x.IsExecuting);
}
}
}
3、View
csharp
<Window
x:Class="AvaRea.TestCmdView"
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="clr-namespace:AvaRea.ViewModels"
Title="测试异步命令"
Width="300"
Height="200"
d:DesignHeight="200"
d:DesignWidth="300"
x:DataType="vm:TestCmdViewModel"
mc:Ignorable="d">
<StackPanel Margin="20" Spacing="10">
<TextBlock FontSize="16" Text="{Binding Status}" />
<StackPanel Orientation="Horizontal" Spacing="10">
<Button
Command="{Binding ExecuteAsyncCommand}"
Content="开始执行" />
<Button
Background="Red"
Command="{Binding CancelExecutionCommand}"
Content="取消"
Foreground="White" />
</StackPanel>
</StackPanel>
</Window>
4、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>
5、App.axaml.cs
csharp
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using AvaRea.ViewModels;
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 TestCmdView
{
DataContext = new TestCmdViewModel()
};
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
singleViewPlatform.MainView = new TestCmdView
{
DataContext = new TestCmdViewModel()
};
}
base.OnFrameworkInitializationCompleted();
}
}
6、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();
}
7、主要特点
- CancellationToken 传递: ReactiveCommand 会自动传递 CancellationToken;
- 取消检查: 在异步方法中定期检查 cancellationToken.ThrowIfCancellationRequested();
- 状态管理: 使用 IsExecuting 属性并利用ReactiveUI响应式编程特点和Sourcegenerators简化代码的功能控制 UI 状态和命令可用性,无需再axaml代码中进行绑定;
- 资源清理: 使用 using 语句或 finally 块确保 CancellationTokenSource 被正确清理。
总结
Avalonia 给你跨平台的舞台,ReactiveUI 给你响应式的灵魂,Source Generators 给你自动生成代码;三者合体 = 用最少的代码,写出最快、最稳、最易维护的跨平台 .NET UI。