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

文章目录

    • [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. 业务场景

假设你正在开发一个社交软件。你有两个界面:

  1. 主界面 (MainViewModel):显示当前用户的头像和昵称。
  2. 设置侧边栏 (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"));
    }
}
相关推荐
XiYang-DING2 小时前
【Java】堆
java·开发语言
Lyyaoo.2 小时前
【Java基础面经】Java 反射机制
java·开发语言·python
雨浓YN2 小时前
OPC UA 通讯开发笔记 - 基于Opc.Ua.Client
笔记·c#
霍理迪2 小时前
Vue路由——route
前端
whuhewei2 小时前
js事件循环
前端·javascript
TheRouter2 小时前
构建一个支持多模型的 AI 聊天应用:React + TheRouter API 全栈教程
前端·人工智能·react.js
xiaofan11062 小时前
Pretext:无 DOM 的多行文本测量与排版库
前端·javascript
m0_694845572 小时前
UVdesk部署教程:企业级帮助台系统实践
服务器·开发语言·后端·golang·github
泉飒2 小时前
C2001: 常量中有换行符-QT解决办法-逆向思路
开发语言·qt