文章目录
-
- [ViewModel 通讯方案](#ViewModel 通讯方案)
-
- [1. 强引用模式:依赖注入 (Dependency Injection)](#1. 强引用模式:依赖注入 (Dependency Injection))
- [2. 观察者模式:原生事件 (C# Events)](# Events))
- [3. 中转站模式:事件聚合器 (Event Aggregator)](#3. 中转站模式:事件聚合器 (Event Aggregator))
- [4. 广播模式:信使 (Messenger)](#4. 广播模式:信使 (Messenger))
- [5. 状态共享模式:单一事实源 (Single Source of Truth)](#5. 状态共享模式:单一事实源 (Single Source of Truth))
-
- [1. 业务场景](#1. 业务场景)
- [2. 方案实现](#2. 方案实现)
-
- 第一步:定义共享状态服务
- [第二步:在 ViewModel 中注入并绑定](#第二步:在 ViewModel 中注入并绑定)
- [第三步:XAML 绑定](#第三步:XAML 绑定)
- 6.实现方案对比
-
- [方案 1 & 2:直接引用与事件](#方案 1 & 2:直接引用与事件)
- [方案 3 & 4:消息中间件 (以现代 Toolkit 为例)](#方案 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)的服务,内部包含
ObservableCollection<T>或INotifyPropertyChanged属性。 - 适用场景:多视图同步。例如:右侧侧边栏修改了用户信息,主界面的头像和列表中的名字需要同步更新。
- 优点:消除了"同步消息"的负担。VM 不再需要互相喊话("我改了!""收到!"),而是直接绑定到同一个数据源。
在 WPF 开发中,单一事实源 (Single Source of Truth, SSoT) 的核心在于:状态不属于任何一个 ViewModel,而是属于一个独立的数据层。
这种模式通过依赖注入 (DI) 将同一个服务实例注入到多个 ViewModel 中,从而实现数据自动同步。
1. 业务场景
假设你正在开发一个社交软件。你有两个界面:
- 主界面 (MainViewModel):显示当前用户的头像和昵称。
- 设置侧边栏 (SettingsViewModel):允许用户修改昵称。
如果你使用"信使"模式,设置页改完后要发广播,主界面要接广播。但使用 SSoT,你只需要改动共享的服务,主界面会自动感应。
2. 方案实现
第一步:定义共享状态服务
这个服务本质上是一个"数据容器",必须实现 INotifyPropertyChanged。
csharp
// 用户状态服务接口
public interface IUserSessionService
{
string UserName { get; set; }
}
// 具体实现
public class UserSessionService : ObservableObject, IUserSessionService
{
private string _userName = "初始用户";
public string UserName
{
get => _userName;
set => SetProperty(ref _userName, value); // 触发属性变更通知
}
}
第二步:在 ViewModel 中注入并绑定
主界面 ViewModel:
csharp
public class MainViewModel : ObservableObject
{
// 直接暴露服务实例,或包装一层属性
public IUserSessionService UserSession { get; }
public MainViewModel(IUserSessionService userSession)
{
UserSession = userSession;
}
}
设置界面 ViewModel:
csharp
public class SettingsViewModel : ObservableObject
{
public IUserSessionService UserSession { get; }
public SettingsViewModel(IUserSessionService userSession)
{
UserSession = userSession;
}
public void UpdateName(string newName)
{
// 这里的修改会直接改变内存中唯一的那个 UserSession 实例
UserSession.UserName = newName;
}
}
第三步:XAML 绑定
在 View 中,你只需要绑定到同一个服务属性即可:
csharp
<TextBlock Text="{Binding UserSession.UserName}" />
<TextBox Text="{Binding UserSession.UserName, UpdateSourceTrigger=PropertyChanged}" />
6.实现方案对比
方案 1 & 2:直接引用与事件
csharp
// 方案 1: DI
public class ChildViewModel { }
public class ParentViewModel
{
public ChildViewModel Child { get; }
public ParentViewModel(ChildViewModel child) => Child = child;
}
// 方案 2: Event
public class ChildViewModel
{
public event Action<string> SomethingHappened;
public void DoWork() => SomethingHappened?.Invoke("Done");
}
方案 3 & 4:消息中间件 (以现代 Toolkit 为例)
在 2026 年,我们更推荐使用 CommunityToolkit.Mvvm(它是 MVVMLight 的官方继承者)。
csharp
// 定义消息类型
public record UserChangedMessage(string UserName);
// 订阅方 (Receiver)
public class ReceiverViewModel : IRecipient<UserChangedMessage>
{
public ReceiverViewModel() => WeakReferenceMessenger.Default.Register(this);
public void Receive(UserChangedMessage message)
{
var name = message.UserName; // 处理逻辑
}
}
// 发送方 (Sender)
public class SenderViewModel
{
public void Notify()
{
WeakReferenceMessenger.Default.Send(new UserChangedMessage("MvvmExpert"));
}
}
