CommunityToolkit.Mvvm Messenger实战:在.NET 8 WPF中实现一个实时数据同步的简易聊天室Demo

张开发
2026/4/9 2:48:11 15 分钟阅读

分享文章

CommunityToolkit.Mvvm Messenger实战:在.NET 8 WPF中实现一个实时数据同步的简易聊天室Demo
CommunityToolkit.Mvvm Messenger实战构建.NET 8 WPF多窗口聊天系统在WPF开发中ViewModel之间的通信一直是架构设计的核心挑战。传统的事件或直接引用方式往往导致代码高度耦合而CommunityToolkit.Mvvm提供的Messenger组件通过消息总线模式完美解决了这一痛点。本文将带你用.NET 8和WPF构建一个支持群聊、私聊、用户状态同步的完整聊天系统深度解析WeakReferenceMessenger的四种典型应用场景。1. 项目架构设计与环境搭建1.1 解决方案结构规划我们采用多窗口设计模拟真实聊天软件每个功能模块严格遵循MVVM模式ChatApp/ ├── Models/ # 数据模型 │ ├── User.cs │ └── ChatMessage.cs ├── ViewModels/ # 视图模型 │ ├── LoginViewModel.cs │ ├── MainViewModel.cs │ ├── UserListViewModel.cs │ └── PrivateChatViewModel.cs ├── Views/ # 视图层 │ ├── LoginWindow.xaml │ ├── MainWindow.xaml │ ├── UserListWindow.xaml │ └── PrivateChatWindow.xaml └── App.xaml # 应用入口1.2 必要环境配置确保开发环境满足以下要求SDK.NET 8.0 SDKIDEVisual Studio 2022社区版即可NuGet包PackageReference IncludeCommunityToolkit.Mvvm Version8.2.1 / PackageReference IncludeMicrosoft.Extensions.DependencyInjection Version8.0.0 /提示建议使用依赖注入容器管理ViewModel生命周期避免手动处理Messenger的注册/注销。2. 核心消息模式实战解析2.1 公共聊天室实现ValueChangedMessage在MainViewModel中实现消息广播功能// 发送群聊消息 public void SendPublicMessage() { var message new ChatMessage(CurrentUser, _messageText); WeakReferenceMessenger.Default.Send(new ValueChangedMessageChatMessage(message)); MessageText string.Empty; } // 接收端注册 WeakReferenceMessenger.Default.RegisterValueChangedMessageChatMessage( this, (r, m) { Messages.Add(m.Value); ScrollToBottom(); });关键设计点使用ValueChangedMessageT包装消息实体发送方无需知道接收方存在多个窗口可同时订阅同一消息类型2.2 私聊功能实现Token路由通过消息令牌实现定向通信// 发起私聊 public void StartPrivateChat(User targetUser) { var token $Private_{CurrentUser.Id}_{targetUser.Id}; WeakReferenceMessenger.Default.Send( new PrivateChatRequest(targetUser), token); } // 接收方注册在PrivateChatViewModel中 WeakReferenceMessenger.Default.RegisterPrivateChatRequest, string( this, Private_*_CurrentUserId, // 使用通配符匹配Token (r, m) ShowPrivateWindow(m.Sender));Token设计规范Token类型格式示例用途说明用户状态更新Status_UserId在线状态广播私聊通道Private_SenderId_RecvId点对点通信系统消息System_All全局通知2.3 用户状态同步PropertyChangedMessage实时更新用户在线状态// 在User模型中 [ObservableProperty] [NotifyPropertyChangedRecipients] private bool _isOnline; // 任意ViewModel中可订阅状态变化 WeakReferenceMessenger.Default.RegisterPropertyChangedMessagebool( this, (r, m) { if (m.PropertyName nameof(User.IsOnline)) UpdateUserStatus(m.Sender as User); });2.4 消息回执机制RequestMessage实现重要消息的确认接收// 发送带响应的请求 var response WeakReferenceMessenger.Default.Send( new MessageReadRequest(CurrentUser, messageId)); if (response.HasReceivedResponse) { StatusText $已读于 {response.Response}; } // 接收方处理 public void Receive(MessageReadRequest message) { message.Reply(DateTime.Now); }3. 高级技巧与性能优化3.1 消息订阅生命周期管理推荐两种安全的注册方式方式1手动注销适合瞬态对象public class MyViewModel : IDisposable { public MyViewModel() { WeakReferenceMessenger.Default.RegisterMyMessage(this, Handle); } public void Dispose() { WeakReferenceMessenger.Default.UnregisterMyMessage(this); } }方式2自动注销继承ObservableRecipientpublic class MyViewModel : ObservableRecipient { protected override void OnActivated() { Messenger.RegisterMyMessage(this, Handle); // 当IsActivefalse时自动取消注册 } }3.2 消息过滤与优先级控制通过自定义MessageHandler实现高级路由WeakReferenceMessenger.Default.RegisterChatMessage( this, (r, m) m.Value.Type MessageType.Urgent, (r, m) FlashWindow());3.3 性能敏感场景优化对于高频消息如打字状态通知// 使用值类型消息减少GC压力 public readonly struct TypingStatus { public int UserId { get; } public bool IsTyping { get; } } // 合并连续消息 private DebounceDispatcher _debouncer new(TimeSpan.FromMilliseconds(300)); public void OnTextChanged() { _debouncer.Debounce(() WeakReferenceMessenger.Default.Send(new TypingStatus(CurrentUser.Id, true))); }4. 调试与异常处理实战4.1 常见问题排查表现象可能原因解决方案消息未接收1. 注册时机过晚2. Token不匹配1. 在构造函数注册2. 检查Token内存泄漏未及时取消注册实现IDisposable或使用WeakReference重复接收多次注册同一处理器检查注册代码是否重复执行4.2 消息日志诊断工具开发阶段可注入消息监视器public class MessageLogger { public MessageLogger(IMessenger messenger) { messenger.RegisterMessageLogger, object, string( this, *, (r, m) Debug.WriteLine($[MSG] {m.GetType().Name})); } }4.3 单元测试策略测试消息交互的正确性[Test] public void Should_ReceiveStatusUpdate_When_UserGoesOffline() { var vm new UserListViewModel(); var testUser new User(); WeakReferenceMessenger.Default.Send( new PropertyChangedMessagebool( testUser, nameof(User.IsOnline), true, false)); Assert.IsFalse(vm.OnlineUsers.Contains(testUser)); }在实现过程中我发现正确处理消息订阅的生命周期是避免内存泄漏的关键。特别是在多窗口应用中每个窗口关闭时务必调用Unregister或设置IsActivefalse。对于需要强类型检查的场景可以继承ValueChangedMessageT创建特定消息类型这比直接使用object类型更安全可靠。

更多文章