【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类 ...
随机推荐
- C++连接Mysql数据库操作
参考文章 http://www.cnblogs.com/justinzhang/archive/2011/09/23/2185963.html 写的很详细 http://blog.csdn.net/j ...
- kali Rolling 安装QQ和虚拟机
环境: kali Rolling 64位 WPS 官网linux版下载链接:http://community.wps.cn/download/ 使用dpkg命令安装,遇到依赖问题,用apt-get ...
- CSS超过指定的宽度加省略号
/*table-layout:fixed 会使表格均等分*/ #TreeView1 table { width:290px; table-layout: fixed; } #TreeView1 td: ...
- Virtual DOM 虚拟DOM的理解(转)
作者:戴嘉华 转载请注明出处并保留原文链接( #13 )和作者信息. 目录: 1 前言 2 对前端应用状态管理思考 3 Virtual DOM 算法 4 算法实现 4.1 步骤一:用JS对象模拟DOM ...
- 【Unity Shader】六、使用法线贴图(Normal Map)的Shader
学习资料: http://www.sikiedu.com/course/37/task/456/show# http://www.sikiedu.com/course/37/task/458/show ...
- WPF DataTomplate中Command无效
问题:在DataTomplate中添加一个Button,Button添加Command,但是Command生效. 原因:ItemTemplate的DataContext指代不明,需要改为父类的Data ...
- JavaScript JSON 数据处理
在JavaScript 也自带了 JSON 格式的处理 <!doctype html> <html> <script> var test_json_str = { ...
- Java编程的逻辑 (54) - 剖析Collections - 设计模式
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- JDBC-批处理操作
javaweb学习总结(三十六)——使用JDBC进行批处理 在实际的项目开发中,有时候需要向数据库发送一批SQL语句执行,这时应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效 ...
- Javascript Notes
Get started with Programming Javascript中的保留字,但是大部分却没在这个语言中使用.undefined,NaN,Infinity也应该算保留字. abstract ...