1、Messager交互结构和消息类型
衔接上篇,Messeger是信使的意思,顾名思义,他的目是用于View和ViewModel 以及 ViewModel和ViewModel 之间的消息通知和接收。
Messenger类用于应用程序的通信,接受者只能接受注册的消息类型,另外目标类型可以被指定,用Send<TMessage, TTarget>(TMessage message)实现,在这种情况下信息只能被传递如果接受者类型和目标参数类型匹配,
message可以是任何简单或者复杂的对象,你可以用特定的消息类型或者创建你自己的类型继承自他们。
交互结构如下所示:
消息类型如下表所示:
message消息对象类型 | 说明 |
MessageBase | 简单的消息类,携带可选的信息关于消息发布者的 |
GenericMessage<T> | 泛型消息 |
NotificationMessage | 用于发送一个string类型通知给接受者 |
NotificationMessage<T> |
和上面一样是一个,且具有泛型功能 |
NotificationMessage | 向接受者发送一个通知,允许接受者向发送者回传消息 |
NotificationMessageAction<T> | NotificationMessage的泛型方式 |
DialogMessage | 发送者(通常是View)显示对话,并且传递调用者得回传结果(用于回调),接受者可以选择怎样显示对话框,可以使是标准的MessageBox也可也是自定义弹出窗口 |
PropertyChangedMessage<T> | 用于广播一个属性的改变在发送者里,和PropertyChanged事件有完全箱体内各的目的,但是是一种弱联系方式 |
2、注册消息的模式
上篇给出了注册的方法,但是注册可以有很多种方式,最常见的就是命名方法调用和Lambda表达式调用的方式:
2.1、基本的命名方法注册
1 // 使用命名方法进行注册 2 Messenger.Default.Register<String>(this, HandleMessage); 3 4 //卸载当前(this)对象注册的所有MVVMLight消息 5 this.Unloaded += (sender, e) => Messenger.Default.Unregister(this); 6 7 8 private void HandleMessage(String msg) 9 { 10 //Todo 11 }
2.2、使用 Lambda 注册
1 Messenger.Default.Register<String>(this, message => { 2 // Todo 3 4 }); 5 //卸载当前(this)对象注册的所有MVVMLight消息 6 this.Unloaded += (sender, e) => Messenger.Default.Unregister(this);
3、跨线程访问
之前在第8篇《利刃 MVVMLight 8:DispatchHelper在多线程和调度中的使用》中,
我们有讨论过在异步线程中使用事件来执行和获取相关的执行步骤。但是如果异步线程中的某个方法需要操作主线程(UI线程的时候)的UI是不允许的。
Windows 中的控件被绑定到特定的UI线程(主线程)中,其他线程是不允许访问的,因为不具备线程安全性和规范性。所以后来MVVM Light才有了调度帮助类(DispatchHelper)来处理不用线程中的调度方案。
从这边可以衍生到异步线程下,对UI线程(主线程)的信息发送和接收。所以之前的代码 DispatchHelper 可以改装如下:
注册模块(ViewModel中):
1 public class MessengerForDispatchViewModel:ViewModelBase 2 { 3 /// <summary> 4 /// 构造函数 5 /// </summary> 6 public MessengerForDispatchViewModel() 7 { 8 InitData(); 9 DispatcherHelper.Initialize(); 10 11 ///Messenger:信使 12 ///Recipient:收件人 13 Messenger.Default.Register<TopUserInfo>(this, "UserMessenger", FeedBack); 14 } 15 }
发送模块(异步线程中代码):
1 private void Start() 2 { 3 TopUserInfo ui = new TopUserInfo(); 4 5 //ToDo:编写创建用户的DataAccess代码 6 for (Int32 idx = 1; idx <= 9; idx++) 7 { 8 Thread.Sleep(1000); 9 ui = new TopUserInfo() { 10 isFinish = false, 11 process = idx*10, 12 userInfo =null 13 }; 14 DispatcherHelper.CheckBeginInvokeOnUI(() => 15 { 16 Messenger.Default.Send<TopUserInfo>(ui, "UserMessenger"); 17 }); 18 } 19 Thread.Sleep(1000); 20 ui = new TopUserInfo() 21 { 22 isFinish = true, 23 process = 100, 24 userInfo = up 25 }; 26 DispatcherHelper.CheckBeginInvokeOnUI(() => 27 { 28 Messenger.Default.Send<TopUserInfo>(ui, "UserMessenger"); 29 }); 30 }
结果:
4、释放注册信息:
4.1、基于View界面内的UnRegister的释放(为当前视图页面的Unload事件 附加 释放注册信息的功能):
1 //卸载当前(this)对象注册的所有MVVMLight消息 2 this.Unloaded += (sender, e) => Messenger.Default.Unregister(this);
4.2、基于ViewModel类中的UnRegister释放(用户在关闭使用页面的时候同事调用该方法,释放注册,这个需要开发人员在关闭视图模型的时候发起):
1 /// <summary> 2 /// 手动调用释放注册信息(该视图模型内的所有注册信息全部释放) 3 /// </summary> 4 public void ReleaseRegister() 5 { 6 Messenger.Default.Unregister(this); 7 }
5、释放注册信息和内存处理
为了避免不必要的内存泄漏, .Net框架提出了比较实用的 WeakReference(弱引用)对象。该功能允许将对象的引用进行弱存储。如果对该对象的所有引用都被释放了,则垃圾回收机便可回收该对象。
类似将所有的注册信息保存在一个弱引用的存储区域,一旦注册信息所寄宿的宿主(View或者ViewModel)被释放,引用被清空,该注册信息也会在一定时间内被释放。
下面一个表格来源于 Laurent Bugnion 对 MVVM 的说明文档:
未取消注册时的内存泄漏风险:
可见性 | WPF | Silverlight | Windows Phone 8 | Windows 运行时 |
静态 | 无风险 | 无风险 | 无风险 | 无风险 |
公共 | 无风险 | 无风险 | 无风险 | 无风险 |
内部 | 无风险 | 风险 | 风险 | 无风险 |
专用 | 无风险 | 风险 | 风险 | 无风险 |
匿名Lambda | 无风险 | 风险 | 风险 | 无风险 |
6、专有信道和广播信道
6.1 过滤Messenger发送端(通过判断发送端来确认是否是发送给自己的):
1 public class ForSourceSenderViewModel:ViewModelBase 2 { 3 public ForSourceSenderViewModel(){} 4 5 #region 全局命令 6 private RelayCommand sendMsg; 7 /// <summary> 8 /// 发送消息 9 /// </summary> 10 public RelayCommand SendMsg 11 { 12 get 13 { 14 if (sendMsg == null) sendMsg = new RelayCommand(() => ExcuteSendMsh()); 15 return sendMsg; 16 } 17 18 set 19 { 20 sendMsg = value; 21 } 22 } 23 24 #endregion 25 26 #region 附属方法 27 private void ExcuteSendMsh() 28 { 29 NotificationMessage nm = new NotificationMessage(this,"发送源消息"); 30 Messenger.Default.Send<NotificationMessage>(nm); 31 } 32 #endregion 33 34 }
1 Messenger.Default.Register<NotificationMessage>(this, message => 2 { 3 if (message.Sender is ForSourceSenderViewModel) 4 { 5 // 判断来源来接受消息 6 MsgInfo = message.Notification; 7 } 8 });
6.2 开设专用的Messenger通道:
1 private Messenger myMessenger; 2 public MessengerForSourceViewModel() 3 { 4 //构造函数 5 myMessenger = new Messenger(); 6 SimpleIoc.Default.Register(() => myMessenger, "MyMessenger"); //注入一个Key为MyMessenger的Messenger对象 7 8 myMessenger.Register<NotificationMessage>(this, message => //注册myMessenger,开启监听 9 { 10 // 判断来源来接受消息 11 MsgInfo = message.Notification; 12 }); 13 }
1 #region 全局命令 2 private RelayCommand sendMsg; 3 /// <summary> 4 /// 发送消息 5 /// </summary> 6 public RelayCommand SendMsg 7 { 8 get 9 { 10 if (sendMsg == null) sendMsg = new RelayCommand(() => ExcuteSendMsh()); 11 return sendMsg; 12 } 13 set 14 { 15 sendMsg = value; 16 } 17 } 18 #endregion 19 20 #region 附属方法 21 private void ExcuteSendMsh() 22 { 23 NotificationMessage nm = new NotificationMessage(this,String.Format("发送消息:{0}",DateTime.Now)); 24 Messenger myMessenger = SimpleIoc.Default.GetInstance<Messenger>("MyMessenger");//获取已存在的Messenger实例 25 myMessenger.Send<NotificationMessage>(nm);//消息发送 26 } 27 #endregion
6.3 使用令牌(Token)区分和使用信道:这是最常用的方式。使用专属Token,可以区分不同的信道,并提高复用性。
Messenger中包含一个token参数,发送方和注册方使用同一个token,便可保证数据在该专有信道中流通,所以令牌是筛选和隔离消息的最好办法。
1 //以ViewAlert位Tokon标志,进行消息发送 2 Messenger.Default.Send<String>("ViewModel通知View弹出消息框", "ViewAlert");
1 public MessagerForView() 2 { 3 InitializeComponent(); 4 5 //消息标志token:ViewAlert,用于标识只阅读某个或者某些Sender发送的消息,并执行相应的处理,所以Sender那边的token要保持一致 6 //执行方法Action:ShowReceiveInfo,用来执行接收到消息后的后续工作,注意这边是支持泛型能力的,所以传递参数很方便。 7 Messenger.Default.Register<String>(this, "ViewAlert", ShowReceiveInfo); 8 this.DataContext = new MessengerRegisterForVViewModel(); 9 //卸载当前(this)对象注册的所有MVVMLight消息 10 this.Unloaded += (sender, e) => Messenger.Default.Unregister(this); 11 } 12 13 /// <summary> 14 /// 接收到消息后的后续工作:根据返回来的信息弹出消息框 15 /// </summary> 16 /// <param name="msg"></param> 17 private void ShowReceiveInfo(String msg) 18 { 19 MessageBox.Show(msg); 20 }
7、使用内置消息
比如我们上面用到的 NotificationMessage<T> ,以及PropertyChanged-Message<T>。
Notification是一种消息通知机制;而PropertyChanged-Message主要指的是当属性改变的时候,执行通知操作。
1 public class PropertyChangedViewModel:ViewModelBase 2 { 3 public const string PropertyName = "UserName"; //注册为该属性,该属性变化时进行消息发送 4 public PropertyChangedViewModel() { } 5 6 #region 全局变量 7 private String userName; 8 /// <summary> 9 /// 用户名称 10 /// </summary> 11 public string UserName 12 { 13 get 14 { 15 return userName; 16 } 17 18 set 19 { 20 String oldValue = userName; 21 userName = value; 22 RaisePropertyChanged(()=>UserName,oldValue,value,true);//这边相应配置上发送参数 23 } 24 } 25 #endregion
1 Messenger.Default.Register<PropertyChangedMessage<String>>(this, message => 2 { 3 if (message.PropertyName == PropertyChangedViewModel.PropertyName) //接受特定属性值相关信道的消息 4 { 5 PropertyChangedInfo = (message.OldValue + " --> " + message.NewValue);//输出旧值到新值的内容 6 } 7 });
结果: