【WP8】自定义EventAggregator
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的更多相关文章
- wp8 自定义相机+nokia滤镜+录制amr音频
demo截图: 代码量有点多,就不贴出来了. 备注: 1.自定义相机主要横竖屏时,对相机进行旋转. 2.播放amr格式可以在页面中直接添加MediaElement控件进行播放,或者使用Bac ...
- [WP8.1UI控件编程]Windows Phone自定义布局规则
3.2 自定义布局规则 上一节介绍了Windows Phone的系统布局面板和布局系统的相关原理,那么系统的布局面板并不一定会满足所有的你想要实现的布局规律,如果有一些特殊的布局规律,系统的布局面板是 ...
- 【WP8】自定义配置存储类
之前在WP7升级到WP8的时候遇到配置不兼容的问题 情景:之前只有一个WP7版本,现在需要发布WP8版本,让用户可以从原来的WP7版本升级到WP8版本 一般情况下从WP7升级到WP8没什么问题 但是在 ...
- Cocos2d-x项目移植到WP8系列之九:使用自定义shader
本文原链接:http://www.cnblogs.com/zouzf/p/3995132.html 有时候想得到一些例如灰度图等特殊的渲染效果,就得用到自定义shader,关于shader的一些背景知 ...
- 【WP8.1】WebView笔记
之前在WP8的时候做过WebBrowser相关的笔记,在WP8.1的WebView和WebBrowser有些不一样,在这里做一些笔记 下面分为几个部分 1.禁止缩放 2.JS通知后台C#代码(noti ...
- 【WP8】WebBrowser相关
2014年09月02日更新 今天用了一下WebBrowser,在使用过程中也遇到了一些问题,在这里做一下记录 虽然WebBrowser比较重,会比较影响性能(除非一定要用到它,否则尽量少用),但有时候 ...
- 使用WP8最新的AudioVideoCaptureDevice类制作录像应用
WP8出来好一段时间了,新出的AudioVideoCaptureDevice类自定义功能比WP7的CaptureSource强大的多,但网上比较全面的中文实例还比较少,分享一个最近做的小实例给大家参考 ...
- JSON/XML序列化与反序列化(非构造自定义类)
隔了很长时间再重看自己的代码,觉得好陌生..以后要养成多注释的好习惯..直接贴代码..对不起( ▼-▼ ) 保存保存:进行序列化后存入应用设置里 ApplicationDataContainer _a ...
- [深入浅出WP8.1(Runtime)]网络编程之HttpClient类
12.2 网络编程之HttpClient类 除了可以使用HttpWebRequest类来实现HTTP网络请求之外,我们还可以使用HttpClient类来实现.对于基本的请求操作,HttpClient类 ...
随机推荐
- [转]PLSQL Developer软件使用大全
原文地址:https://www.cnblogs.com/lhrbest/p/6493218.html 第一章 PLSQL Developer特性 PL/SQL Developer是一个集成开发环境, ...
- android手机安全性測试手段
android手机安全性測试手段 罗列一下自己经常使用的android手机安全性測试攻击手段: 1. fiddler和tcpdump+wireshark抓包分析,模拟改动http请求參数,检验漏洞 2 ...
- android中LayoutParams设置参数的理解
1. 有这样的一个应用场景:有一个linearLayout控件在其中布局一个textView控件 首先,创建线性布局对象 LinearLayout layout = new LinearLayout( ...
- Instant Django 1.5 Application Development Starter
Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic d ...
- 简单防范SYN_RECV攻击
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' 这条语句返回结果如下 TIME_WAIT FIN_WAIT1 ...
- 放弃winform的窗体吧,改用html作界面,桌面应用程序UI的新的开发方式。
做过很多winform项目,都为winform控件头疼不已.想实现一些漂亮的样子总是很难.我这里列举几个缺点: 1.winform控件大多是 绝对布局 ,你需要给出准确的坐标.那么在实现居中效果就会很 ...
- Django添加防跨站请求伪造中间件
第一步: 在全局设置中打开此中间件: MIDDLEWARE_CLASSES = [ ... 'django.middleware.csrf.CsrfViewMiddleware', ... ] ...
- 从PCD文件写入和读取点云数据
(1)学习向PCD文件写入点云数据 建立工程文件ch2,然后新建write_pcd.cpp CMakeLists.txt两个文件 write_pcd.cpp : #include <iostr ...
- oracle分页查询原理浅析
原因一 oracle默认为每个表生成rowmun,rowid字段,这些字段我们称之为伪列 1 创建测试表 CREATE TABLE TEST( ID NUMBER, NAME VARCHAR2(20) ...
- Java线程状态及 wait、sleep、join、interrupt、yield等的区别
Java中的线程状态(详见Java线程状态及转换-MarchOn): wait:Object类的实例方法,释放CPU执行权,进入等待状态,直到 被中断.被拥有该对象锁的线程唤醒(notify或not ...