ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

从零搭建工业控制系统(二十):MVVM消息传递——跨ViewModel通信

从零搭建工业控制系统(二十):MVVM消息传递——跨ViewModel通信 MVVM消息传递跨ViewModel通信这是「从零搭建工业控制系统」系列第20篇。前面讲了图表和UI这篇讲一个MVVM架构里的关键问题——ViewModel之间怎么通信。问题ViewModel不能互相引用MVVM架构里每个ViewModel管自己的事。但有些事件需要跨ViewModel通知配置改了 → 所有相关ViewModel要刷新报警触发了 → 状态栏ViewModel要显示报警用户登录了 → 导航ViewModel要更新菜单工艺开始了 → 日志ViewModel要记录这些ViewModel之间没有直接引用关系。如果在ViewModel A里持有ViewModel B的引用耦合就产生了——A必须知道B的存在B变了A可能也要改。消息机制解决的就是这个问题A发消息不关心谁收B收消息不关心谁发。中间由Messenger做转发。WeakReferenceMessengerCommunityToolkit.Mvvm 提供了WeakReferenceMessengerusingCommunityToolkit.Mvvm.Messaging;// 发送WeakReferenceMessenger.Default.Send(newConfigChangedMessage());// 接收WeakReferenceMessenger.Default.RegisterConfigChangedMessage(this,(r,m){// 处理配置变更});为什么用WeakReference而不是StrongReference避免内存泄漏。强引用会让Messenger持有接收者引用接收者即使从UI移除了也不会被GC回收。弱引用不会阻止GC。定义消息类消息类要继承ValueChangedEventArgs或MessageBase// 无参消息publicclassConfigChangedMessage{}// 带值的消息publicclassAlarmTriggeredMessage{publicstringAlarmCode{get;}publicstringDescription{get;}publicDateTimeTimestamp{get;}publicAlarmTriggeredMessage(stringcode,stringdesc){AlarmCodecode;Descriptiondesc;TimestampDateTime.Now;}}// 带发送者的消息publicclassNavigationMessage{publicstringTargetView{get;}publicobjectParameter{get;}publicNavigationMessage(stringtarget,objectparamnull){TargetViewtarget;Parameterparam;}}消息类越简单越好。只装数据不放逻辑。发送和接收发送publicpartialclassSettingsViewModel:ObservableObject{privatevoidSaveConfig(){// 保存配置...WeakReferenceMessenger.Default.Send(newConfigChangedMessage());}}发送方不需要知道谁会接收。就算没人接收也不报错。接收接收方需要实现IRecipientT接口或者用Lambda注册// 方式A实现接口推荐publicpartialclassStatusViewModel:ObservableObject,IRecipientAlarmTriggeredMessage{publicStatusViewModel(){WeakReferenceMessenger.Default.Register(this);}publicvoidReceive(AlarmTriggeredMessagemessage){// 更新状态栏显示CurrentAlarmmessage.Description;}}// 方式BLambda注册WeakReferenceMessenger.Default.RegisterConfigChangedMessage(this,(r,m){RefreshData();});方式A更安全——编译器检查消息类型不会漏写。方式B更灵活但容易忘记Unregister。Unregister别忘了接收者销毁时必须Unregister否则消息会发给一个已经不存在的对象publicpartialclassLogViewModel:ObservableObject,IRecipientAlarmTriggeredMessage,IDisposable{publicLogViewModel(){WeakReferenceMessenger.Default.Register(this);}publicvoidDispose(){WeakReferenceMessenger.Default.UnregisterAlarmTriggeredMessage(this);}}虽然用的是WeakReference理论上不Unregister也不会泄漏。但不Unregister的消息会尝试调用已销毁对象的方法可能抛异常。养成Unregister的习惯。实际场景场景发送方消息接收方配置变更SettingsViewModelConfigChangedMessage所有需要配置的VM报警触发AlarmServiceAlarmTriggeredMessageStatusVM, LogVM用户登录LoginViewModelUserLoggedInMessageMainWindowVM导航任意VMNavigationMessageMainWindowVM工艺开始WorkflowControllerProcessStartedMessageStatusVM, LogVM踩坑记录坑1消息收不到注册时机太晚——发送消息时接收者还没注册。比如在ViewModel的LoadData方法里注册但LoadData是异步的消息在注册前就发了。解决办法在构造函数里注册不要在异步方法里注册。坑2重复接收同一个消息被处理两次。原因是注册了两次——构造函数注册一次某个方法里又注册了一次。加个标志位防止重复注册。坑3内存泄漏用StrongReferenceMessenger时没Unregister页面切换后旧ViewModel没被GC回收。换成WeakReferenceMessenger后解决但最好还是显式Unregister。本篇小结概念关键做法WeakReferenceMessenger弱引用避免泄漏消息类只装数据不放逻辑IRecipient编译器检查消息类型Register构造函数里注册UnregisterDispose时取消注册消息机制的核心发送方不关心谁收接收方不关心谁发。松耦合改一方不影响另一方。下期预告第21篇状态管理系统消息讲完了下篇讲状态管理——所有设备状态怎么集中管理怎么跟Modbus寄存器映射怎么绑定到UI。
返回列表