利刃 MVVMLight 10:Messenger 深入
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、基本的命名方法注册
// 使用命名方法进行注册
Messenger.Default.Register<String>(this, HandleMessage); //卸载当前(this)对象注册的所有MVVMLight消息
this.Unloaded += (sender, e) => Messenger.Default.Unregister(this); private void HandleMessage(String msg)
{
//Todo
}
2.2、使用 Lambda 注册
Messenger.Default.Register<String>(this, message => {
// Todo
});
//卸载当前(this)对象注册的所有MVVMLight消息
this.Unloaded += (sender, e) => Messenger.Default.Unregister(this);
3、跨线程访问
之前在第8篇《利刃 MVVMLight 8:DispatchHelper在多线程和调度中的使用》中,
我们有讨论过在异步线程中使用事件来执行和获取相关的执行步骤。但是如果异步线程中的某个方法需要操作主线程(UI线程的时候)的UI是不允许的。
Windows 中的控件被绑定到特定的UI线程(主线程)中,其他线程是不允许访问的,因为不具备线程安全性和规范性。所以后来MVVM Light才有了调度帮助类(DispatchHelper)来处理不用线程中的调度方案。
从这边可以衍生到异步线程下,对UI线程(主线程)的信息发送和接收。所以之前的代码 DispatchHelper 可以改装如下:
注册模块(ViewModel中):
public class MessengerForDispatchViewModel:ViewModelBase
{
/// <summary>
/// 构造函数
/// </summary>
public MessengerForDispatchViewModel()
{
InitData();
DispatcherHelper.Initialize(); ///Messenger:信使
///Recipient:收件人
Messenger.Default.Register<TopUserInfo>(this, "UserMessenger", FeedBack);
}
}
发送模块(异步线程中代码):
private void Start()
{
TopUserInfo ui = new TopUserInfo(); //ToDo:编写创建用户的DataAccess代码
for (Int32 idx = ; idx <= ; idx++)
{
Thread.Sleep();
ui = new TopUserInfo() {
isFinish = false,
process = idx*,
userInfo =null
};
DispatcherHelper.CheckBeginInvokeOnUI(() =>
15 {
16 Messenger.Default.Send<TopUserInfo>(ui, "UserMessenger");
17 });
}
Thread.Sleep();
ui = new TopUserInfo()
{
isFinish = true,
process = ,
userInfo = up
};
DispatcherHelper.CheckBeginInvokeOnUI(() =>
27 {
28 Messenger.Default.Send<TopUserInfo>(ui, "UserMessenger");
29 });
}
结果:

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)被释放,引用被清空,该注册信息也会在一定时间内被释放。
下面一个表格来源于 Laurent Bugnion 对 MVVM 的说明文档:
未取消注册时的内存泄漏风险:
| 可见性 | WPF | Silverlight | Windows Phone 8 | Windows 运行时 |
| 静态 | 无风险 | 无风险 | 无风险 | 无风险 |
| 公共 | 无风险 | 无风险 | 无风险 | 无风险 |
| 内部 | 无风险 | 风险 | 风险 | 无风险 |
| 专用 | 无风险 | 风险 | 风险 | 无风险 |
| 匿名Lambda | 无风险 | 风险 | 风险 | 无风险 |
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,可以区分不同的信道,并提高复用性。
Messenger中包含一个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 const string PropertyName = "UserName"; //注册为该属性,该属性变化时进行消息发送
public PropertyChangedViewModel() { } #region 全局变量
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);//输出旧值到新值的内容
}
});
结果:

转载请注明出处,谢谢
利刃 MVVMLight 10:Messenger 深入的更多相关文章
- 利刃 MVVMLight
已经很久没有写系列文章了,上一次是2012年写的HTLM5系列,想想我们应该是较早一批使用HTML5做项目的人. 相比我当时动不动100+的粉丝增长和两天3000+的阅读量,MVVM Light只能算 ...
- 利刃 MVVMLight 9:Messenger
MVVM的目标之一就是为了解耦View和ViewModel.View负责视图展示,ViewModel负责业务逻辑处理,尽量保证 View.xaml.cs中的简洁,不包含复杂的业务逻辑代码. 但是在实际 ...
- 利刃 MVVMLight 3:双向数据绑定
上篇我们已经了解了MVVM的框架结构和运行原理.这里我们来看一下伟大的双向数据绑定. 说到双向绑定,大家比较熟悉的应该就是AngularJS了,几乎所有的AngularJS 系列教程的开篇 ...
- 利刃 MVVMLight 5:绑定在表单验证上的应用
表单验证是MVVM体系中的重要一块.而绑定除了推动 Model-View-ViewModel (MVVM) 模式松散耦合 逻辑.数据 和 UI定义 的关系之外,还为业务数据验证方案提供强大而灵活的支持 ...
- 利刃 MVVMLight 6:命令基础
在MVVM Light框架中,事件是WPF应用程序中UI与后台代码进行交互的最主要方式,与传统方式不同,mvvm中主要通过绑定到命令来进行事件的处理, 因此要了解mvvm中处理事件的方式,就必须先熟悉 ...
- 利刃 MVVMLight 7:命令深入
上面一篇我们大致了解了命令的基本使用方法和基础原理,但是实际在运用命令的时候会复杂的多,并且有各种各样的情况. 一.命令带参数的情况: 如果视图控件所绑定的命令想要传输参数,需要配置 CommandP ...
- 利刃 MVVMLight 1:MVVMLight介绍以及在项目中的使用
一.MVVM 和 MVVMLight介绍 MVVM是Model-View-ViewModel的简写.类似于目前比较流行的MVC.MVP设计模式,主要目的是为了分离视图(View)和模型(Model)的 ...
- 利刃 MVVMLight 2:Model、View、ViewModel结构以及全局视图模型注入器的说明
上一篇我们已经介绍了如何使用NuGet把MVVMLight应用到我们的WPF项目中.这篇我们来了解下一个基本的MVVMLight框架所必须的结构和运行模式. MVVMLight安装之后,我们 ...
- 利刃 MVVMLight 4:绑定和绑定的各种使用场景
一.绑定: 主要包含元素绑定和非元素绑定两种. 1.元素绑定,是绑定的最简单形式,源对象是WPF的元素,并且源对象的属性是依赖项属性. 根据我们之前的知识 ,依赖项属性具有内置的更改通知支持.所以当我 ...
随机推荐
- Android使用本地广播
Android本地广播学习中一直被忽略,今天用到了,Mark一下 1.本地广播的定义和普通广播一样 例如 public class WakeTimesBroadcastReceiver extends ...
- 插入排序的优化非希尔【不靠谱地讲可以优化到O(nlogn)】 USACO 丑数
首先我们先介绍一下普通的插排,就是我们现在一般写的那种,效率是O(n^2)的. 普通的插排基于的思想就是找位置,然后插入进去,其他在它后面的元素全部后移,下面是普通插排的代码: #include< ...
- 重新认识JavaScript里的数据类型
一.序 数据类型,平时天天在用,今日闲暇便重新阅读了JavaScript数据类型这块,才发现平时用的时候有许些错误和不足,且对此深有感悟,便写下这篇文章加以巩固基础知识并有空翻出来温故而知新. 二.概 ...
- C#调用WebService接口实现天气预报在web前端显示
本文使用web (C#)调用互联网上公开的WebServices接口: (http://www.webxml.com.cn/WebServices/WeatherWebService.asmx)来实现 ...
- 微信端解决a标签链接 失效的问题
最近常碰到这个问题就是 在微信端点击a标签链接的时候,第一次正常界面跳转.但是,界面重新跳转回来再次点击a标签的话 .出现 界面不跳转,但是进度条加载完毕,点击多次页面无法跳转. 解决办法 在链接后边 ...
- 做一个完整的纯react-naitve安卓应用【从环境安装到应用发布】
前提:从来没有写过android 跟 ios 应用,是一个小前端.前一段时间玩了一下 react-native 感觉还不错,应用代码还未开源. 环境: win7 成果: ...
- 深入理解MVC
首先我们来看看MVC架构的示意图: 和访问者交互的是控制层(Controller层),控制器(controller)是同类交互的集合,每一个交互的操作,都对应了一个动作(act ...
- Unity 脚本中各种[XXX]的用法
1.[SerializeField]在Inspector中显示非public属性,并且序列化:若写在public修饰的字段前,相当于没写,Unity会自动为Public变量做序列化,序列化的意思是说再 ...
- Extjs6组件——Form大家族成员介绍
本文基于ext-6.0.0 一.xtype form一共有12种xtype,下面来一一举例说一下. 1.textfield 这个是用的最多的form之一. { xtype: 'textfield', ...
- html渲染过程
用户输入url地址,浏览器依据域名寻觅IP地址浏览器向服务器发送http恳求,假如服务器段回来以301之类的重定向,浏览器依据相应头中的location再次发送恳求服务器端承受恳求,处理恳求生成htm ...