Avalonia ViewModels 实战指南:用 Zafiro 与 ReactiveUI 构建响应式 MVVM 架构
2026/9/21 14:40:04 网站建设 项目流程
  • AI 技能
  • AI 插件

【免费下载链接】agentic-awesome-skills

AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and planning, backed by 2,400+ agentic skills. Includes CLI, local MCP, catalog, plugins, and Workbench.

项目地址:https://gitcode.com/gh_mirrors/an/agentic-awesome-skills
点击查看免费下载

本篇技术指南围绕 AAS(Agentic Awesome Skills)仓库中的avalonia-viewmodels-zafiro技能展开,系统讲解如何在 Avalonia 应用中基于ReactiveUIZafiro工具包编写 ViewModel、增强命令、多步骤向导(Wizard)、应用导航与 UI 分区,以及 View-ViewModel 的映射与依赖注入组合。读完本篇,你将掌握一套可直接落地的函数式响应式 MVVM 编码规范,包括IEnhancedCommandSlimWizard[Section]自动注册与DataTypeViewLocator的完整用法。

核心原则:函数式响应式 MVVM

在 Zafiro 体系中,ViewModel 的设计遵循五条核心原则(源自 SKILL.md):

  1. 函数式响应式(Functional-Reactive):使用 ReactiveUI 的ReactiveObjectWhenAnyValue等原语承载状态与逻辑,让数据流显式、可组合、可预测。
  2. 增强命令(Enhanced Commands):统一使用IEnhancedCommand管理命令,获得进度上报、Name/Text元数据等更强能力。
  3. 向导模式(Wizard Pattern):复杂多步流程用SlimWizard+WizardBuilder以声明式方式组织,取代手写状态机。
  4. 自动分区发现(Automatic Section Discovery):通过[Section]特性自动注册和发现 UI 分区(如侧边栏、标签页),避免手工维护注册表。
  5. 清晰组合(Clean Composition):用DataTypeViewLocator完成 ViewModel→View 的映射,并在统一的CompositionRoot中集中管理依赖。

这五条原则共同指向一个目标:让 ViewModel 纯粹、可测试、与 Avalonia 平台解耦。同级技能 avalonia-zafiro-development 进一步补充了四条强制规范:纯 MVVM(用 DynamicData 与 ReactiveUI)、显式错误处理(用Result类型而非异常控制流)、跨平台卓越(ViewModel 严格不引用 Avalonia 类型、组合优于继承)、以及“Zafiro First”(优先复用现有抽象与助手,避免重复造轮子)。

响应式 ViewModel:从ReactiveObject到自动属性

[Reactive]简化属性定义

ViewModel 应以ReactiveObject为基类,并使用ReactiveUI.SourceGenerators提供的[Reactive]特性标注字段,编译器会自动生成可观察的属性包装。这种做法把传统手写RaiseAndSetIfChanged的样板代码压缩到最小:

public partial class MyViewModel : ReactiveObject { [Reactive] private string name; [Reactive] private bool isBusy; }

注意:类必须声明为partial[Reactive]修饰的是私有字段(nameisBusy),生成的公开属性即为NameIsBusy

WhenAnyValue做观察与变换

属性间的派生状态应通过WhenAnyValue组合订阅并做变换,配合ToPropertyEx输出为只读可观察属性(viewmodels.md):

this.WhenAnyValue(x => x.Name) .Select(name => !string.IsNullOrEmpty(name)) .ToPropertyEx(this, x => x.CanSubmit);

ToPropertyEx会生成CanSubmit这个ObservableAsPropertyHelper驱动的属性,供 XAML 双向/单向绑定直接使用。由于生成器产出的属性名取自表达式,整个链路类型安全,重构友好。

增强命令:用IEnhancedCommand管理交互

为什么用增强命令

Zafiro 的IEnhancedCommand同时扩展了ICommandIReactiveCommand,在此之上附加了NameText等元数据。这让命令不仅是一个可执行动作,还能携带面向 UI 的展示信息(按钮文案、命令标识),便于日志、遥测与自动化测试。

创建并增强命令

先用ReactiveCommand.Create(同步)或ReactiveCommand.CreateFromTask(异步)创建基础命令,再调用.Enhance(...)注入元数据:

public IEnhancedCommand Submit { get; } public MyViewModel() { Submit = ReactiveCommand.CreateFromTask(OnSubmit, canSubmit) .Enhance(text: "Submit Data", name: "SubmitCommand"); }

canSubmit可以是IObservable<bool>(通常由WhenAnyValue变换而来,如前述CanSubmit),实现“输入合法才可提交”的声明式交互约束;OnSubmit返回Task表示异步操作。第二个可选参数也可以直接传入可观察对象来驱动命令的可执行状态。

统一错误处理:HandleErrorsWith

命令执行失败时,推荐用HandleErrorsWith把异常自动投递到NotificationService,避免在每个命令体内手写 try/catch(viewmodels.md):

Submit.HandleErrorsWith(uiServices.NotificationService, "Submission Failed") .DisposeWith(disposable);

同源的最佳实践在 patterns.md 中再次强调:不要在手写Subscribe里堆业务逻辑,而是让错误沿响应式管道流到统一的展示层——例如LoadProjects.HandleErrorsWith(uiServices.NotificationService, "Could not load projects")。错误处理管道化之后,ViewModel 的每个命令都具备一致的失败反馈行为。

生命周期管理:CompositeDisposableDisposeWith

响应式编程中订阅与命令均有生命周期,忘记释放会造成内存泄漏与幽灵回调。规范做法是让 ViewModel 实现IDisposable,用CompositeDisposable汇集所有订阅,并在Dispose时统一释放:

public class MyViewModel : ReactiveObject, IDisposable { private readonly CompositeDisposable disposables = new(); public void Dispose() => disposables.Dispose(); }

凡是用到的 observable 订阅、命令增强链、验证规则,一律追加.DisposeWith(disposables),确保组件销毁时订阅同步解除。同级技能 avalonia-reactive-rules.md 也把DisposeWith列为生命周期管理的强制要求。

向导模式:用SlimWizard声明式编排多步流程

WizardBuilder定义步骤

复杂多步流程(注册、创建项目、部署向导等)应使用SlimWizard。每个步骤对应一个 ViewModel,步骤间通过上一个步骤的结果(prevResult)流转,链式声明让流程结构一目了然(wizards.md):

SlimWizard<string> wizard = WizardBuilder .StartWith(() => new Step1ViewModel(data)) .NextUnit() .WhenValid() .Then(prevResult => new Step2ViewModel(prevResult)) .NextCommand(vm => vm.CustomNextCommand) .Then(result => new SuccessViewModel("Done!")) .Next((_, s) => s, "Finish") .WithCompletionFinalStep();

上面示例展示了三种不同的推进方式:第一步等待内部信号(NextUnit)且需通过验证(WhenValid);第二步等待 ViewModel 中某个特定命令成功执行(NextCommand(vm => vm.CustomNextCommand));最后一步直接以自定义转换函数产出结果,并给出按钮文案"Finish"

导航规则

  • NextUnit():当步骤 ViewModel 发出一个简单信号时推进到下一步。
  • NextCommand():当指定命令成功执行后推进,适用于“点击按钮并完成操作才进入下一步”的场景。
  • WhenValid():等待当前 ViewModel 的验证通过后才允许导航,与 Zafiro 的验证扩展协同工作。
  • Always():任何情况下都允许导航。

向导收尾配置

  • WithCompletionFinalStep():最后一个步骤完成即标记向导结束(普通完成型)。
  • WithCommitFinalStep():适用于最后一步执行“保存 / 部署”等提交动作的向导。

INavigator集成

向导本身不直接驱动 UI,而是通过INavigator导航执行:

public async Task CreateSomething() { var wizard = BuildWizard(); var result = await wizard.Navigate(navigator); // Handle result }

SlimWizard会自动处理“返回(Back)”命令,因此在所有流程中用户都能获得一致的返回体验,无需每个向导单独实现回退逻辑。

导航与 UI 分区:INavigator[Section]

页面级导航

INavigator负责在视图/ViewModel 之间切换:

public class MyViewModel(INavigator navigator) { public async Task GoToDetails() { await navigator.Navigate(() => new DetailsViewModel()); } }

主构造函数注入是 Zafiro 项目的常见风格,与 DI 容器配合时由容器解析INavigator实例。

[Section]标记模块化分区

分区(Section)是 UI 中的模块化部件,例如侧边栏项或标签页。需要作为分区的 ViewModel 用[Section]特性标注,并携带显示名与图标:

[Section("Wallet", icon: "fa-wallet")] public class WalletSectionViewModel : IWalletSectionViewModel { // ... }

icon参数支持 FontAwesome 图标(如fa-home),前提是在应用中配置了ProjektankerIconControlProvider图标提供器。

自动注册与切换

CompositionRoot中可以通过扩展方法扫描并自动注册所有带[Section]特性的分区,无需手工逐一添加:

services.AddAnnotatedSections(logger); services.AddSectionsFromAttributes(logger);

运行时切换当前激活分区,通过IShellViewModel完成:

shellViewModel.SetSection("Browse");

这套机制让“新增一个侧边栏页面”的成本降为两步:标记[Section]+ 在组合根自动注册,其余注册与切换逻辑全部由框架接管。

组合与映射:DataTypeViewLocatorCompositionRoot

View-ViewModel 自动映射

Zafiro 用DataTypeViewLocator依据 ViewModel 的数据类型自动解析对应 View。在App.axaml的数据模板中注册它,并引入 Zafiro 内置模板:

<Application.DataTemplates> <DataTypeViewLocator /> <DataTemplateInclude Source="avares://Zafiro.Avalonia/DataTemplates.axaml" /> </Application.DataTemplates>

映射既可以全局注册,也可以采用命名约定或由源生成器显式生成。DataTypeViewLocator保证“ViewModel 类型 → View”的解析与 XAML 数据模板无缝衔接。

集中式 CompositionRoot

所有服务注册应集中在CompositionRoot,把 View 层(topLevelView)需要的 UI 服务注入进来,最终对外暴露IShellViewModel

public static class CompositionRoot { public static IShellViewModel CreateMainViewModel(Control topLevelView) { var services = new ServiceCollection(); services .AddViewModels() .AddUIServices(topLevelView); var serviceProvider = services.BuildServiceProvider(); return serviceProvider.GetRequiredService<IShellViewModel>(); } }

按作用域注册 ViewModel

ViewModel 的作用域需依据生命周期谨慎选择:临时页面用Transient,全局唯一的 Shell 用Singleton

public static IServiceCollection AddViewModels(this IServiceCollection services) { return services .AddTransient<IHomeSectionViewModel, HomeSectionSectionViewModel>() .AddSingleton<IShellViewModel, ShellViewModel>(); }

应用启动时的 View 注入

OnFrameworkInitializationCompleted中,用Connect帮助方法把 ShellView、主 ViewModel 与主窗口三者接线:

public override void OnFrameworkInitializationCompleted() { this.Connect( () => new ShellView(), view => CompositionRoot.CreateMainViewModel(view), () => new MainWindow()); base.OnFrameworkInitializationCompleted(); }

需要手工实例化某个类但又要从IServiceProvider解析其依赖时,使用ActivatorUtilities.CreateInstance(composition.md),它能在不显式传参的情况下完成构造函数依赖注入。

进阶:集合、验证与响应式管道的强制规范

围绕 Zafiro ViewModel 体系,同级技能还沉淀了几条高频模式,直接提升真实项目的代码质量:

动态集合验证(Mandatory Validation Pattern):对动态集合的验证统一走 Zafiro 验证扩展,用 DynamicData 管道组合出验证流(avalonia-reactive-rules.md):

this.ValidationRule( StagesSource .Connect() .FilterOnObservable(stage => stage.IsValid) .IsEmpty(), b => !b, _ => "Stages are not valid") .DisposeWith(Disposables);

RefreshableCollection 模式:管理可刷新列表时,RefreshableCollection.Create内部维护SourceCache/SourceList,对外暴露ReadOnlyObservableCollection,刷新时用EditDiff做增量更新而非清空重建(patterns.md):

var refresher = RefreshableCollection.Create( () => GetDataTask(), model => model.Id) .DisposeWith(disposable); LoadData = refresher.Refresh; Items = refresher.Items;

DynamicData 操作符优先:操作集合时优先使用 DynamicData 的ConnectFilterTransformSortBindDisposeMany,而不是普通 Rx 操作符;禁止为局部问题随手新建SourceList/SourceCache;禁止把业务逻辑写进Subscribe。这些规则保证了数据管道可读、集中、可释放。

何时使用与注意事项

  • 适用场景:任务与上述范围明确匹配时启用——包括新建或重构 Avalonia ViewModel、实现多步向导、搭建 Shell 导航与分区 UI、以及配置 View-ViewModel 组合与映射。
  • 不适用场景:任务超出该范围(如纯后端逻辑、非 Avalonia 平台代码)时不应套用本技能。
  • 必要提醒:本技能输出不能替代针对具体环境的验证、测试与专家评审;当所需输入、权限、安全边界或成功判据缺失时,应停下并澄清后再继续。

上述规范的参考实现可对照社区项目AngorCreateProjectFlowV2.cs是复杂 Wizard 构建的范例,HomeViewModel.cs则是使用函数式响应式命令的简单分区 ViewModel 范例(SKILL.md)。

总结

avalonia-viewmodels-zafiro提供了一整套可复制的 Avalonia MVVM 工程范式:以ReactiveObject+[Reactive]+WhenAnyValue搭建响应式状态层,以IEnhancedCommand+HandleErrorsWith统一命令与错误流,以SlimWizard声明式编排多步流程,以[Section]+IShellViewModel管理模块化分区,最终通过DataTypeViewLocatorCompositionRoot完成 View-ViewModel 的解耦接线。配合 DynamicData 集合管道与 Zafiro 验证扩展,这套方法能让跨平台 Avalonia 应用在可维护性、可测试性与一致性上显著受益。若需进一步了解配套规范,可继续阅读仓库内 avalonia-zafiro-development 技能集中的 naming-standards.md、zafiro-shortcuts.md 等文档。

  • AI 技能
  • AI 插件

【免费下载链接】agentic-awesome-skills

AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and planning, backed by 2,400+ agentic skills. Includes CLI, local MCP, catalog, plugins, and Workbench.

项目地址:https://gitcode.com/gh_mirrors/an/agentic-awesome-skills
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询