WPF中ViewModel之间的5种通讯方式

张开发
2026/5/5 3:16:57 15 分钟阅读
WPF中ViewModel之间的5种通讯方式
文章目录ViewModel 通讯方案1. 强引用模式依赖注入 (Dependency Injection)2. 观察者模式原生事件 (C# Events)3. 中转站模式事件聚合器 (Event Aggregator)4. 广播模式信使 (Messenger)5. 状态共享模式单一事实源 (Single Source of Truth)1. 业务场景2. 方案实现第一步定义共享状态服务第二步在 ViewModel 中注入并绑定第三步XAML 绑定6.实现方案对比方案 1 2直接引用与事件方案 3 4消息中间件 (以现代 Toolkit 为例)在 WPF 开发中ViewModel下文简称 VM之间的通讯是架构设计的核心。按照第一性原理通讯的本质只有两件事谁持有谁的引用以及数据如何流动。ViewModel 通讯方案其核心目的在于解决解耦 (De coupling)问题即如何在不让两个类互相强引用的情况下实现数据的传递或方法的调用。1. 强引用模式依赖注入 (Dependency Injection)核心思想通过构造函数将一个 VM 实例直接传递给另一个 VM。适用场景父子关系极其紧密且子 VM 无法脱离父 VM 独立存在。优点代码直观可以直接调用方法支持编译时检查。缺点耦合度最高。如果 VM 层次很深传递过程会非常痛苦。2. 观察者模式原生事件 (C# Events)核心思想底层 VM 定义event上层 VM 订阅该event。适用场景简单的组件化开发父组件监听子组件的状态变化。优点不需要引入任何第三方框架执行效率最高。缺点内存泄漏隐患。如果订阅者不手动注销即使 View 关闭了VM 依然留在内存中。3. 中转站模式事件聚合器 (Event Aggregator)核心思想通过 Prism 框架的IEventAggregator。它要求你先定义一个“事件类”。适用场景大型模块化项目不同模块Module之间甚至互不知道对方的存在。优点类型安全 (Type Safety)。发送的消息必须匹配预定义的类型。缺点需要预定义大量的 Event 类略显繁琐。4. 广播模式信使 (Messenger)核心思想通过 MVVMLight 或 CommunityToolkit 的Messenger。它更像是一个电台广播。适用场景中小型项目快速实现跨页面、跨层级的数据传递。优点极其灵活。可以使用Token频道名来区分不同的消息。缺点由于过于灵活代码维护时很难一眼看出某个消息到底被谁接收了。方案核心手段耦合度典型场景方案 1依赖注入 (DI)构造函数直接传递对象引用高强相关的业务逻辑如主窗体直接控制唯一的子功能模块。方案 2普通事件 (Events)C# 原生 event 关键字中简单的父子通讯且能确保手动销毁订阅防止内存泄漏。方案 3事件聚合 (EventAggregator)Prism 框架提供的中转站低大型项目多个不相关的模块Module之间广播消息。方案 4信使 (Messenger)MVVMLight 框架提供的中转站低轻量级项目支持通过 Token标识符进行精准的消息投递。5. 状态共享模式单一事实源 (Single Source of Truth)核心思想不通过“传递”来通讯而是让多个 VM 共同观察同一个域模型 (Domain Model)或状态服务 (State Service)。实现方式创建一个单例或作用域Scoped的服务内部包含ObservableCollectionT或INotifyPropertyChanged属性。适用场景多视图同步。例如右侧侧边栏修改了用户信息主界面的头像和列表中的名字需要同步更新。优点消除了“同步消息”的负担。VM 不再需要互相喊话“我改了”“收到”而是直接绑定到同一个数据源。在 WPF 开发中单一事实源 (Single Source of Truth, SSoT)的核心在于状态不属于任何一个 ViewModel而是属于一个独立的数据层。这种模式通过依赖注入 (DI) 将同一个服务实例注入到多个 ViewModel 中从而实现数据自动同步。1. 业务场景假设你正在开发一个社交软件。你有两个界面主界面 (MainViewModel)显示当前用户的头像和昵称。设置侧边栏 (SettingsViewModel)允许用户修改昵称。如果你使用“信使”模式设置页改完后要发广播主界面要接广播。但使用SSoT你只需要改动共享的服务主界面会自动感应。2. 方案实现第一步定义共享状态服务这个服务本质上是一个“数据容器”必须实现INotifyPropertyChanged。// 用户状态服务接口publicinterfaceIUserSessionService{stringUserName{get;set;}}// 具体实现publicclassUserSessionService:ObservableObject,IUserSessionService{privatestring_userName初始用户;publicstringUserName{get_userName;setSetProperty(ref_userName,value);// 触发属性变更通知}}第二步在 ViewModel 中注入并绑定主界面 ViewModelpublicclassMainViewModel:ObservableObject{// 直接暴露服务实例或包装一层属性publicIUserSessionServiceUserSession{get;}publicMainViewModel(IUserSessionServiceuserSession){UserSessionuserSession;}}设置界面 ViewModelpublicclassSettingsViewModel:ObservableObject{publicIUserSessionServiceUserSession{get;}publicSettingsViewModel(IUserSessionServiceuserSession){UserSessionuserSession;}publicvoidUpdateName(stringnewName){// 这里的修改会直接改变内存中唯一的那个 UserSession 实例UserSession.UserNamenewName;}}第三步XAML 绑定在 View 中你只需要绑定到同一个服务属性即可TextBlockText{Binding UserSession.UserName}/TextBoxText{Binding UserSession.UserName, UpdateSourceTriggerPropertyChanged}/6.实现方案对比方案 1 2直接引用与事件// 方案 1: DIpublicclassChildViewModel{}publicclassParentViewModel{publicChildViewModelChild{get;}publicParentViewModel(ChildViewModelchild)Childchild;}// 方案 2: EventpublicclassChildViewModel{publiceventActionstringSomethingHappened;publicvoidDoWork()SomethingHappened?.Invoke(Done);}方案 3 4消息中间件 (以现代 Toolkit 为例)在 2026 年我们更推荐使用CommunityToolkit.Mvvm它是 MVVMLight 的官方继承者。// 定义消息类型publicrecordUserChangedMessage(stringUserName);// 订阅方 (Receiver)publicclassReceiverViewModel:IRecipientUserChangedMessage{publicReceiverViewModel()WeakReferenceMessenger.Default.Register(this);publicvoidReceive(UserChangedMessagemessage){varnamemessage.UserName;// 处理逻辑}}// 发送方 (Sender)publicclassSenderViewModel{publicvoidNotify(){WeakReferenceMessenger.Default.Send(newUserChangedMessage(MvvmExpert));}}

更多文章