导图社区 messenger的使用
mvvm 下 messenge 的使用总结,附带示例代码。通过该思维导图,可以帮助你更好掌握的相关知识。
编辑于2020-10-25 08:01:40Messenger使用
MVVM Light Messenger 旨在通过简单的前提来精简此场景:任何对象都可以是接收端;任何对象都可以是发送端;任何对象都可以是消息
1、Messager交互结构和消息类型
Messenger类用于应用程序的通信,接受者只能接受注册的消息类型,且接收的目标类型可以被指定,用Send<TMessage, TTarget>(TMessage message)实现, 
MessageBase
GenericMessage<T>
NotificationMessage
NotificationMessage<T>
NotificationMessageAction
NotificationMessageAction<T>
DialogMessage
PropertyChangedMessage<T>
2、注册消息的模式
2.1、基本的命名方法注册
// 使用命名方法进行注册 Messenger.Default.Register<String>(this, HandleMessage); //卸载当前(this)对象注册的所有MVVMLight消息 this.Unloaded += (sender, e) => Messenger.Default.Unregister(this); private void HandleMessage(String msg) { //Todo }
示例
public ViewViewmodel ( ) { InitializeComponent(); #region 演示view与viewModel间通信 Messenger.Default.Register<string>(this, "AnotherAlert",ShowReceiveInfo); //<String>:对回调方法ShowReceiveInfo参数类型的约束,--带有一个string型参数 //"ViewAlert",消息标志token,用于标识只阅读某个或者某些Sender发送的消息,并执行相应的处理,所以Sender那边的token要保持一致 //ShowReceiveInfo 回调方法,用来执行接收到消息后的后续工作 #endregion //为this(此处就是MainWindow)unload(卸载)事件注册一个一个方法,它执行的是:注销其上的所有MVVMLight消息 this.Unloaded += (sender, e) => Messenger.Default.Unregister(this); //+=后的内容, (sender, e) => Messenger.Default.Unregister(this) 实际是一个lamda表达式定义的方法. } /// <summary> /// 接收到消息后的后续工作:根据返回来的信息弹出消息框 /// </summary> /// <param name="msg"></param> private void ShowReceiveInfo (String msg) { MessageBox.Show(msg+ " 我是"+this.Name+",我侦听到一个token = AnotherAlert的消息"); }
2.2、使用 Lambda 注册
Messenger.Default.Register<String>(this, message => { // Todo }); //卸载当前(this)对象注册的所有MVVMLight消息 this.Unloaded += (sender, e) => Messenger.Default.Unregister(this);
Messenger的案例演示。
在实例中,有两个窗口,一个是显示产品详细信息MainWindow.xaml,另一个是EditDetailView.xaml,通过选择MainWindow窗口中的任意产品,能在EditDetailView窗口显示,如果在EiditDetailView中修改产品的数量,可以将修改的结果立即呈现到MainWindow中。 原理浅析:发布者通过Messenger.Default.Send<T>发布某个类型消息,而接受者通过 Messenger.Default.Register<T>注册相应的消息类型,从而实现接受消息
发送者,代码片段
SelectedProductCommand = new RelayCommand(() => { if (Products.CurrentItem != null) { //获取当前选中列 Product selectedProduct = (Product)Products.CurrentItem; //消息的发送 //(1)如果我们只需要接受者接受消息,用下面的方式就可以了 //发布消息 另一种写法Messenger.Default.Send<Product>(selectedProduct); Messenger.Default.Send(selectedProduct); //(2)如果我们需要接受者返回消息,还需要下面的代码 //发布消息,接受者可以通过NotificationMessageAction<Product> 的实例lastProduct的Excute()方法返回消息到接受者 Messenger.Default.Send(new NotificationMessageAction<Product>("当前选择的产品是:" + selectedProduct.Description, FromEditViewModelCallBack)); } } );
接收者代码片段
public EditDetailViewModel() { //消息的接受 //(1)如果我们只是接受消息,不返回消息,只需要下面的代码 Messenger.Default.Register<Product>(this, m => FromMainWindowProduct= m); //(2)如果我们还需要向发送者返回消息,我们需要将如下代码,获取 NotificationMessageAction<Product> lastProduct实例 Messenger.Default.Register<NotificationMessageAction<Product>> (this, m => { ShowMessage= m.Notification; lastProduct = m; } ); SaveCommand = new RelayCommand(() => { //处理来至发送者的消息,并返回消息到发送者 lastProduct.Execute(product); } ); }
3、跨线程访问
注册模块,需要做两件事
public class MessengerForDispatchViewModel:ViewModelBase { /// <summary> /// 构造函数 /// </summary> public MessengerForDispatchViewModel() { InitData(); DispatcherHelper.Initialize(); ///Messenger:信使 ///Recipient:收件人 Messenger.Default.Register<TopUserInfo>(this, "UserMessenger", FeedBack); } }
1.执行DispatcherHelper.Initialize();//初始化它后,就可以使用另一线程发送来的消息数据修改主线程中的数据了
2.注册一个消息接收器,接收另一线程发送来的数据
发送模块(异步线程中代码)
private void Start() { TopUserInfo ui = new TopUserInfo(); //ToDo:编写创建用户的DataAccess代码 for (Int32 idx = 1; idx <= 9; idx++) { Thread.Sleep(1000); ui = new TopUserInfo() { isFinish = false, process = idx*10, userInfo =null }; DispatcherHelper.CheckBeginInvokeOnUI(() => { Messenger.Default.Send<TopUserInfo>(ui, "UserMessenger"); }); } Thread.Sleep(1000); ui = new TopUserInfo() { isFinish = true, process = 100, userInfo = up }; DispatcherHelper.CheckBeginInvokeOnUI(() => { Messenger.Default.Send<TopUserInfo>(ui, "UserMessenger"); }); }
发送端代码详解

4、释放注册信息:
4.1、基于View界面内的UnRegister的释放(为当前视图页面的Unload事件 附加 释放注册信息的功能)
//卸载当前(this)对象注册的所有MVVMLight消息 this.Unloaded += (sender, e) => Messenger.Default.Unregister(this);
4.2、基于ViewModel类中的UnRegister释放(用户在关闭使用页面的时候同事调用该方法,释放注册,这个需要开发人员在关闭视图模型的时候发起)
/// <summary> /// 手动调用释放注册信息(该视图模型内的所有注册信息全部释放) /// </summary> public void ReleaseRegister() { Messenger.Default.Unregister(this); }
5、释放注册信息和内存处理
为了避免不必要的内存泄漏, .Net框架提出了比较实用的 WeakReference(弱引用)对象。该功能允许将对象的引用进行弱存储。如果对该对象的所有引用都被释放了,则垃圾回收机便可回收该对象。 类似将所有的注册信息保存在一个弱引用的存储区域,一旦注册信息所寄宿的宿主(View或者ViewModel)被释放,引用被清空,该注册信息也会在一定时间内被释放。

6、专有信道和广播信道
6.1 过滤Messenger发送端(通过判断发送端来确认是否是发送给自己的)
public class ForSourceSenderViewModel:ViewModelBase { public ForSourceSenderViewModel(){} #region 全局命令 private RelayCommand sendMsg; /// <summary> /// 发送消息 /// </summary> public RelayCommand SendMsg { get { if (sendMsg == null) sendMsg = new RelayCommand(() => ExcuteSendMsh()); return sendMsg; } set { sendMsg = value; } } #endregion #region 附属方法 private void ExcuteSendMsh() { NotificationMessage nm = new NotificationMessage(this,"发送源消息"); Messenger.Default.Send<NotificationMessage>(nm); } #endregion } Messenger.Default.Register<NotificationMessage>(this, message => { if (message.Sender is ForSourceSenderViewModel) { // 判断来源来接受消息 MsgInfo = message.Notification; } });
6.2 开设专用的Messenger通道:
private Messenger myMessenger; public MessengerForSourceViewModel() { //构造函数 myMessenger = new Messenger(); SimpleIoc.Default.Register(() => myMessenger, "MyMessenger"); //注入一个Key为MyMessenger的Messenger对象 myMessenger.Register<NotificationMessage>(this, message => //注册myMessenger,开启监听 { // 判断来源来接受消息 MsgInfo = message.Notification; }); } #region 全局命令 private RelayCommand sendMsg; /// <summary> /// 发送消息 /// </summary> public RelayCommand SendMsg { get { if (sendMsg == null) sendMsg = new RelayCommand(() => ExcuteSendMsh()); return sendMsg; } set { sendMsg = value; } } #endregion #region 附属方法 private void ExcuteSendMsh() { NotificationMessage nm = new NotificationMessage(this,String.Format("发送消息:{0}",DateTime.Now)); Messenger myMessenger = SimpleIoc.Default.GetInstance<Messenger>("MyMessenger");//获取已存在的Messenger实例 myMessenger.Send<NotificationMessage>(nm);//消息发送 } #endregion
6.3 使用令牌(Token)区分和使用信道:这是最常用的方式。使用专属Token,可以区分不同的信道,并提高复用性。
//以ViewAlert位Tokon标志,进行消息发送 Messenger.Default.Send<String>("ViewModel通知View弹出消息框", "ViewAlert"); public MessagerForView() { InitializeComponent(); //消息标志token:ViewAlert,用于标识只阅读某个或者某些Sender发送的消息,并执行相应的处理,所以Sender那边的token要保持一致 //执行方法Action:ShowReceiveInfo,用来执行接收到消息后的后续工作,注意这边是支持泛型能力的,所以传递参数很方便。 Messenger.Default.Register<String>(this, "ViewAlert", ShowReceiveInfo); this.DataContext = new MessengerRegisterForVViewModel(); //卸载当前(this)对象注册的所有MVVMLight消息 this.Unloaded += (sender, e) => Messenger.Default.Unregister(this); } /// <summary> /// 接收到消息后的后续工作:根据返回来的信息弹出消息框 /// </summary> /// <param name="msg"></param> private void ShowReceiveInfo(String msg) { MessageBox.Show(msg); }
7、使用内置消息
比如我们上面用到的 NotificationMessage<T> ,以及PropertyChangedMessage<T>。
Notification是一种消息通知机制;而PropertyChangedMessage主要指的是当属性改变的时候,执行通知操作。
public class PropertyChangedViewModel:ViewModelBase { public PropertyChangedViewModel() { } #region 全局变量 // 相应属性用快捷键mvvminpcmsg 生成,代码如下 public const string PropertyName = "UserName"; //注册为该属性,该属性变化时进行消息发送 private String userName; /// <summary> /// 用户名称 /// </summary> public string UserName { get { return userName; } set { String oldValue = userName; userName = value; RaisePropertyChanged(()=>UserName,oldValue,value,true);//这边相应配置上发送参数 } } #endregion //侦听端代码 Messenger.Default.Register<PropertyChangedMessage<String>>(this, message => { if (message.PropertyName == PropertyChangedViewModel.PropertyName) //接受特定属性值相关信道的消息 { PropertyChangedInfo = (message.OldValue + " --> " + message.NewValue);//输出旧值到新值的内容 } });
应用场景
1、View和ViewModel之间的消息交互
在View和ViewModel中进行消息器注册,相当于订阅服务。包含消息标志、消息参数和消息执行方法。
消息标志token:ViewAlert
用于标识只阅读某个或者某些Sender发送的消息,并执行相应的处理,所以Sender那边的token要保持一致
执行方法Action:ShowReceiveInfo
用来执行接收到消息后的后续工作,注意这边是支持泛型能力的,所以传递参数很方便。
2.ViewModel和ViewModel之间的消息交互