1. 为什么你的WPF项目需要Messenger?
如果你写过WPF或者类似的桌面应用,肯定遇到过这样的头疼事:一个页面上的按钮点击了,需要更新另一个页面的数据;或者一个模块里的数据变了,得通知好几个不相关的组件跟着刷新。最直接的办法是什么?没错,就是让它们互相“认识”,然后直接调用对方的方法或者修改对方的属性。
比如,你的 MainViewModel 里有个用户列表,UserDetailViewModel 负责显示用户详情。当在列表里选中一个用户时,你得想办法告诉详情页:“嘿,换人了,显示这个!” 新手最常见的做法,就是在 MainViewModel 里持有一个 UserDetailViewModel 的引用,然后直接给它赋值。代码可能长这样:
// MainViewModel.cs
public class MainViewModel
{
public UserDetailViewModel DetailVM { get; set; } // 直接持有引用
private User _selectedUser;
public User SelectedUser
{
get => _selectedUser;
set
{
_selectedUser = value;
OnPropertyChanged();
// 直接操作另一个ViewModel
DetailVM.CurrentUser = value;
}
}
}
看起来挺简单,对吧?但问题很快就来了。随着功能增加,SettingsViewModel 也需要知道用户切换了,NotificationViewModel 也想发个欢迎提示。你的 MainViewModel 会变成这样:
public class MainViewModel
{
public UserDetailViewModel DetailVM { get; set; }
public SettingsViewModel SettingsVM { get; set; }
public NotificationViewModel NotifyVM { get; set; }
// ... 更多引用
public User SelectedUser
{
set
{
// 更新自己
_selectedUser = value;
OnPropertyChanged();
// 挨个通知所有相关方
DetailVM.CurrentUser = value;
SettingsVM.OnUserChanged(value);
NotifyVM.ShowWelcome(value);
// ... 每加一个功能,这里就要多一行代码
}
}
}
这下麻烦了。MainViewModel 变成了一个“上帝类”,它必须知道整个应用里谁关心用户切换这件事。各个模块之间紧紧耦合在一起,牵一发而动全身。想单独测试 MainViewModel?你得把所有依赖的ViewModel都构造出来。想复用 UserDetailViewModel 到另一个项目?得把 MainViewModel 也一起搬过去。这种代码维护起来简直就是噩梦,添加新功能时你永远不知道会不小心“碰坏”哪里。
CommunityToolkit.Mvvm 里的 Messenger 就是来解决这个问题的。 它的核心思想是“发布-订阅”(Pub/Sub)。想象一下微信群:MainViewModel 不用知道谁在群里,它只管往群里扔一条消息:“用户切换成张三了”。而关心这件事的 DetailVM、SettingsVM 早就订阅了这个群。它们收到消息后,自己决定要做什么。MainViewModel 彻底解放了,它和接收者们完全解耦,互不认识。这就是我们追求的“松耦合”架构,让代码更清晰、更易测试、也更易扩展。接下来,我们就看看怎么把这个“利器”用起来。
2. 初识Messenger:核心概念与两种信使
在 CommunityToolkit.Mvvm (简称 MVVM Toolkit) 中,Messenger 是消息系统的核心接口。我们主要打交道的是它的默认实现:WeakReferenceMessenger.Default。这个“默认信使”是一个全局单例,在整个应用程序域内都可用,非常方便。
但为什么叫“弱引用”(WeakReference)信使呢?这是它设计上的一大亮点,专门用来解决内存泄漏这个老大难问题。在传统的的事件订阅里,如果订阅者(比如一个ViewModel)没有取消订阅,发布者(事件源)就会一直持有对订阅者的引用,导致订阅者即使不再使用也无法被垃圾回收。而 WeakReferenceMessenger 使用弱引用来持有接收者对象。这意味着,垃圾回收器(GC)在判断对象是否存活时,会忽略这些弱引用。一旦你的ViewModel在其他地方没有强引用了,即使它还在Messenger里“注册”着,GC也能正常回收它,从而有效避免了因疏忽而造成的内存泄漏。对于桌面应用这种可能长时间运行的程序来说,这个特性至关重要。
除了 WeakReferenceMessenger,工具箱里其实还有一个 StrongReferenceMessenger。从名字就能猜出来,它使用的是强引用。那什么时候会用强引用呢?通常是在对象的生命周期非常明确、短暂,并且你希望确保在消息发送时接收者绝对存活(比如在一个临时的工作流程中)的场景下。不过,在绝大多数WPF的MVVM场景中,尤其是在ViewModel这种生命周期可能比较模糊的对象间通信,WeakReferenceMessenger 是更安全、更推荐的选择。所以,我们后续的讨论和示例都将围绕 WeakReferenceMessenger.Default 展开。
Messenger 的工作流程可以概括为三步:
- 订阅(Register):接收者(Recipient)告诉Messenger:“我对某某类型的消息感兴趣,如果收到了,请调用我的这个处理方法。”
- 发布(Send):发送者(Sender)将一条消息实例交给Messenger:“帮我把这个消息广播出去。”
- 派发与处理:Messenger 找到所有订阅了该消息类型的接收者,并调用它们预先注册的处理方法。
整个过程,发送者和接收者不需要知道彼此的存在,它们只和Messenger这个中间人打交道。下面,我们先从一个最简单的消息类型开始实战。
3. 从最简单的消息开始:自定义消息与ValueChangedMessage
让我们先抛开复杂的框架概念,写点能立刻跑起来的代码。假设我们有一个简单的需求:在一个窗口点击按钮,在另一个窗口的文本框中显示一段文字。两个窗口的ViewModel互不知情。
首先,我们可以定义自己的消息类型。任何类都可以作为消息,但最佳实践是将其定义为 record 类型,因为它具有不可变性(Immutable)和值比较的语义,非常适合作为数据传输对象。
// 自定义一个简单的文本消息
public record TextMessage(string Content);
现在,在接收方(比如一个显示日志的ViewModel),我们进行订阅:
// LogViewModel.cs
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.ComponentModel;
public partial class LogViewModel : ObservableObject
{
[ObservableProperty]
private string _logText = "就绪";
public LogViewModel()
{
// 订阅 TextMessage 类型的消息
WeakReferenceMessenger.Default.Register<TextMessage>(this, (recipient, message) =>
{
// 当收到消息时,更新日志文本
LogText = $"[{DateTime.Now:HH:


4697

被折叠的 条评论
为什么被折叠?



