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
}

MainViewModel

  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
}

Page1ViewModel

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();
} public void PublishString()
{
//发送一个string消息
eventAggregator.PublishOnUIThread("来自Page2的消息");
}
}

Page2ViewModel

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

  

  

【WP8】自定义EventAggregator的更多相关文章

  1. wp8 自定义相机+nokia滤镜+录制amr音频

    demo截图:      代码量有点多,就不贴出来了. 备注: 1.自定义相机主要横竖屏时,对相机进行旋转. 2.播放amr格式可以在页面中直接添加MediaElement控件进行播放,或者使用Bac ...

  2. [WP8.1UI控件编程]Windows Phone自定义布局规则

    3.2 自定义布局规则 上一节介绍了Windows Phone的系统布局面板和布局系统的相关原理,那么系统的布局面板并不一定会满足所有的你想要实现的布局规律,如果有一些特殊的布局规律,系统的布局面板是 ...

  3. 【WP8】自定义配置存储类

    之前在WP7升级到WP8的时候遇到配置不兼容的问题 情景:之前只有一个WP7版本,现在需要发布WP8版本,让用户可以从原来的WP7版本升级到WP8版本 一般情况下从WP7升级到WP8没什么问题 但是在 ...

  4. Cocos2d-x项目移植到WP8系列之九:使用自定义shader

    本文原链接:http://www.cnblogs.com/zouzf/p/3995132.html 有时候想得到一些例如灰度图等特殊的渲染效果,就得用到自定义shader,关于shader的一些背景知 ...

  5. 【WP8.1】WebView笔记

    之前在WP8的时候做过WebBrowser相关的笔记,在WP8.1的WebView和WebBrowser有些不一样,在这里做一些笔记 下面分为几个部分 1.禁止缩放 2.JS通知后台C#代码(noti ...

  6. 【WP8】WebBrowser相关

    2014年09月02日更新 今天用了一下WebBrowser,在使用过程中也遇到了一些问题,在这里做一下记录 虽然WebBrowser比较重,会比较影响性能(除非一定要用到它,否则尽量少用),但有时候 ...

  7. 使用WP8最新的AudioVideoCaptureDevice类制作录像应用

    WP8出来好一段时间了,新出的AudioVideoCaptureDevice类自定义功能比WP7的CaptureSource强大的多,但网上比较全面的中文实例还比较少,分享一个最近做的小实例给大家参考 ...

  8. JSON/XML序列化与反序列化(非构造自定义类)

    隔了很长时间再重看自己的代码,觉得好陌生..以后要养成多注释的好习惯..直接贴代码..对不起( ▼-▼ ) 保存保存:进行序列化后存入应用设置里 ApplicationDataContainer _a ...

  9. [深入浅出WP8.1(Runtime)]网络编程之HttpClient类

    12.2 网络编程之HttpClient类 除了可以使用HttpWebRequest类来实现HTTP网络请求之外,我们还可以使用HttpClient类来实现.对于基本的请求操作,HttpClient类 ...

随机推荐

  1. C#学习笔记(8)——委托应用(显示,写入时间)

    说明(2017-5-30 09:08:10): 1. 定义一个委托,public delegate void MyDel();无参数,无返回值. 2. 委托作为DoSth的参数,DoSth里面调用委托 ...

  2. curl Array to string conversion 错误

    0x00 故障 由于GuzzleHttp在iis上使用错误,于是开始替换其为Unirest,没想到发送了一个curl Array to string conversion 错误 0x01 原因 跟踪调 ...

  3. Objective的宏到swift中该怎么办?

    ReadMehtml, body {overflow-x: initial !important;}.CodeMirror { height: auto; } .CodeMirror-scroll { ...

  4. Oracle锁表查询和解锁方法

    数据库操作语句的分类 DDL:数据库模式定义语言,关键字:create DML:数据操纵语言,关键字:Insert.delete.update DCL:数据库控制语言 ,关键字:grant.remov ...

  5. Android——MeasureSpec学习 - 解决ScrollView嵌套ListView和GridView冲突的方法

      原文地址:http://blog.csdn.net/yuhailong626/article/details/20639217   在自定义View和ViewGroup的时候,我们经常会遇到int ...

  6. PCL中点云数据格式之间的转化

    (1) 关于pcl::PCLPointCloud2::Ptr和pcl::PointCloud<pcl::PointXYZ>两中数据结构的区别 pcl::PointXYZ::PointXYZ ...

  7. PCL点云曲面重建(1)

    在测量较小的数据时会产生一些误差,这些误差所造成的不规则数据如果直接拿来曲面重建的话,会使得重建的曲面不光滑或者有漏洞,可以采用对数据重采样来解决这样问题,通过对周围的数据点进行高阶多项式插值来重建表 ...

  8. windows10激活工具,绿色无毒,不改浏览器主页

    最近发现一个很好用的Windows10 永久激活的工具,比KMS什么的管用,而且无毒无公害.几乎支持所有的win10版本.感兴趣的朋友可以试试.之前win10没洗白的同学,也试试吧,说不定就洗白了呢. ...

  9. CREATESTRUCT cs 结构体

    PreCreateWindow(CREATESTRUCT& cs) typedef struct tagCREATESTRUCT { LPVOID lpCreateParams; // 创建窗 ...

  10. Java设计模式(8)组合模式(Composite模式)

    Composite定义:将对象以树形结构组织起来,以达成“部分-整体” 的层次结构,使得客户端对单个对象和组合对象的使用具有一致性. Composite比较容易理解,想到Composite就应该想到树 ...