MVVM模式实现了ViewModel和View的分离,但是有很多时候我们需要进行页面间通信
比如,我们在设置界面修改了某个项的值,需要同步到主页面,让主页面做相关的逻辑,由于每个页面对应一个ViewModel,ViewModel之间又是独立的,很多MVVM实现都提供了EventAggregator来实现ViewModel之间的通信,其原理就是订阅与广播
EventAggregator原理
1、消息订阅者向EventAggregator订阅消息
2、消息发布者向EventAggregator发布消息
3、EventAggregator想所有订阅该消息的订阅者发送
4、订阅者接受到消息,进行相关的逻辑处理
EventAggregator可以保证ViewModel相互独立的情况下,实现ViewModel之间的交互
CM(Caliburn.Micro)也提供了对EventAggregator的支持
消息以类型区分,比如两个ViewModel都订阅了string类型的消息,EventAggregator发送了一个字符串消息的时候,这两个ViewModel都会接收到,如果是不同的消息,需要进行区分
下面简单演示一下CM中EventAggregator的使用
1、ViewModel订阅消息
MainViewModel订阅string 类型消息
public class MainViewModel : PropertyChangedBase, IHandle<string> { private readonly INavigationService navigationService; public string Message { get; set; } public MainViewModel(INavigationService navigationService, IEventAggregator eventAggregator) { this.navigationService = navigationService; eventAggregator.Subscribe(this); } public void Nav2Page1() { navigationService.UriFor<Page1ViewModel>().Navigate(); } #region 接受消息函数 //接受string类型的消息 public void Handle(string message) { Message = message; NotifyOfPropertyChange(() => Message); } #endregion }
Page1ViewModel订阅int类型消息
public class Page1ViewModel : PropertyChangedBase, IHandle<int> { private readonly INavigationService navigationService; public string Message { get; set; } public Page1ViewModel(INavigationService navigationService, IEventAggregator eventAggregator) { this.navigationService = navigationService; eventAggregator.Subscribe(this); } public void Nav2Page2() { navigationService.UriFor<Page2ViewModel>().Navigate(); } #region 接受消息函数 //接受int类型的消息 public void Handle(int message) { Message = message.ToString(CultureInfo.InvariantCulture); NotifyOfPropertyChange(() => Message); } #endregion }
2、发布消息
在Page2ViewModel发布消息
public class Page2ViewModel : PropertyChangedBase { private readonly IEventAggregator eventAggregator; public string Message { get; set; } public Page2ViewModel(IEventAggregator eventAggregator) { this.eventAggregator = eventAggregator; } public void PublishInt() { //发送一个int消息 eventAggregator.PublishOnUIThread(120); } public void PublishString() { //发送一个string消息 eventAggregator.PublishOnUIThread("来自Page2的消息"); } }
IEventAggregator.Publish之后,订阅的MainViewModel和Page1ViewModel都能接受到消息
问题:
CM只提供了基本的功能,并不能满足一起特殊的需求
1、CM的EventAggregator底层保存了一个列表,用弱类型保存Subscriber,而CM中ViewModel的生命周期是跟随View的,当View被GC回收的时候,ViewModel也会被回收
但是有一个问题,就是GC的回收时间是不确定的,比如我们进入了一个页面,给该页面的ViewModel订阅消息,然后离开页面,这个时候,如果这个时候GC还没有回收该ViewModel的内存时,消息订阅器EventAggregator还是可以接受到消息去触发ViewModel执行相应的消息处理函数的,也就是说,ViewModel可能还没有被回收,还可以接受消息,所以,当我们离开页面的时候需要取消ViewModel对消息的订阅以保证页面的ViewModel不能再接受消息了
场景:我们在MainView订阅了一个消息,然后注销登陆,到了登陆页面,然后再进入MainView,如果GC还没有对之前的ViewModel进行回收的话,这个时候就会有两个MainViewModel可以接受消息,可能会导致消息函数被执行多次,
解决:我们需要对离开的页面注销消息的订阅
在View离开的时候取消对消息的注册
protected override void OnNavigatedFrom(NavigationEventArgs e) { if (e.NavigationMode == NavigationMode.Back) { //取消ViewModel消息注册 var model = ViewModelLocator.LocateForView(this); eventsAggregator.Unsubscribe(model); } }
扩展(自定义EventAggregator)
1、需求:我们需要在消息处理完后进行回掉,消息发送者可以得到消息订阅者处理结果
场景:A页面需要一个学校列表,但是学校列表保存在B页面中,完美需要A页面发送一个广播说:我要一个学校列表,然后B页面收到广播,然后把消息列表返回给A页面,A页面就可以得到学校列表了
由于CM内部定义的EventAggregator暴露的属性有限,很难在不改动CM源码的前提下进行扩展,下面通过自定义一个EventAggregator已满足上面需求
public interface IEventAggregator { //订阅消息 void Subscribe<T>(object subscriber, Action<T> handler); //发送消息 void Publish<TSent>(TSent data); //订阅消息(带回掉) void Subscribe<TSent, TBack>(object subscriber, Func<TSent, TBack> handler); //发送消息(带回掉) void Publish<TSent, TBack>(TSent data, Action<TBack> callback); //注销订阅者 void Unsubscribe(object subscriber); //注销消息订阅 void Unsubscribe<T>(object subscriber); //清除被回收的弱类型 void Cleanup(); //清除所有订阅者 void Clear(); }
实现
/// <summary> /// 自定义消息聚合器 /// </summary> public class EventAggregator : IEventAggregator { /// <summary> /// 订阅者信息(弱类型保存) /// </summary> private class Handler { public object Action { get; set; } /// <summary> /// 消息订阅者(sender) /// </summary> public WeakReference Sender { get; set; } /// <summary> /// 消息类型 /// </summary> public Type Type { get; set; } /// <summary> /// 回掉的类型 /// </summary> public Type BackType { get; set; } } /// <summary> /// 线程锁 /// </summary> private readonly object locker = new object(); /// <summary> /// 订阅者列表 /// </summary> private readonly List<Handler> handlers = new List<Handler>(); /// <summary> /// 发布消息 /// </summary> /// <typeparam name="TSent">发送的消息类型</typeparam> public void Publish<TSent>(TSent data) { lock (locker) { Cleanup(); foreach (var l in handlers.Where(a => a.Type.IsAssignableFrom(typeof(TSent))).ToList()) { var action = l.Action as Action<TSent>; if (action != null) action(data); } } } /// <summary> /// 发布消息,在执行完成后调用回掉函数(订阅函数有返回值) /// </summary> /// <typeparam name="TSent">发送的消息类型</typeparam> /// <typeparam name="TBack">返回的消息类型</typeparam> /// <param name="data">发送的消息</param> /// <param name="callback">回掉函数</param> public void Publish<TSent, TBack>(TSent data, Action<TBack> callback) { lock (locker) { Cleanup(); foreach (var l in handlers.Where(a => a.Type.IsAssignableFrom(typeof (TSent)) && a.BackType.IsAssignableFrom(typeof (TBack))).ToList()) { var action = l.Action as Func<TSent, TBack>; if (action != null) { var re = action(data); callback(re); } } } } /// <summary> /// 订阅消息(带返回值) /// </summary> /// <typeparam name="TSent">发送的消息类型</typeparam> /// <typeparam name="TBack">返回的消息类型</typeparam> /// <param name="subscriber">消息订阅者</param> /// <param name="handler">订阅函数(带返回值)</param> public void Subscribe<TSent, TBack>(object subscriber, Func<TSent, TBack> handler) { lock (locker) { handlers.Add(new Handler { Action = handler, Sender = new WeakReference(subscriber), Type = typeof (TSent), BackType = typeof (TBack) }); } } /// <summary> /// 订阅消息(带返回值) /// </summary> /// <typeparam name="TSent">发送的消息类型</typeparam> /// <param name="subscriber">消息订阅者</param> /// <param name="handler">订阅函数</param> public void Subscribe<TSent>(object subscriber, Action<TSent> handler) { lock (locker) { handlers.Add(new Handler { Action = handler, Sender = new WeakReference(subscriber), Type = typeof(TSent) }); } } /// <summary> /// 取消消息订阅 /// </summary> /// <param name="subscriber"></param> public void Unsubscribe(object subscriber) { lock (locker) { Cleanup(); var query = handlers.Where(a => a.Sender.Target.Equals(subscriber)); foreach (var h in query.ToList()) { handlers.Remove(h); } } } /// <summary> /// 取消指定消息类型的消息订阅 /// </summary> public void Unsubscribe<T>(object subscriber) { lock (locker) { Cleanup(); var query = handlers.Where(a => a.Sender.Target.Equals(subscriber) && a.Type == typeof(T)); foreach (var h in query.ToList()) { handlers.Remove(h); } } } /// <summary> /// 清理被回收的订阅者 /// </summary> public void Cleanup() { foreach (var l in handlers.Where(a => !a.Sender.IsAlive).ToList()) { handlers.Remove(l); } } /// <summary> /// 清空所有订阅者 /// </summary> public void Clear() { handlers.Clear(); } }
Demo
http://files.cnblogs.com/bomo/EventAggregatorDemo.zip