【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类 ...
随机推荐
- bootstrapTable刷新当前页码不变和从第一页开始查询
function searchAgencyProject(){ $("#statisticalProjectListTable").bootstrapTable('refresh' ...
- C# 中的await
今天看代码遇到了很多await,梳理一下await的用法. await只针对awaitable对象,经常和Task一起使用,主要用法如下: var task = RunAsync(); //开始执行异 ...
- C语言 · 最长公共子序列 · 最长字符序列
算法提高篇有两个此类题目: 算法提高 最长字符序列 时间限制:1.0s 内存限制:256.0MB 最长字符序列 问题描述 设x(i), y(i), z(i)表示单个字符,则X={x( ...
- Ansible 进阶技巧
原文 http://www.ibm.com/developerworks/cn/linux/1608_lih_ansible/index.html?ca=drs- 简介 Ansible 是一个系 ...
- hbase 学习(十二)非mapreduce生成Hfile,然后导入hbase当中
最近一个群友的boss让研究hbase,让hbase的入库速度达到5w+/s,这可愁死了,4台个人电脑组成的集群,多线程入库调了好久,速度也才1w左右,都没有达到理想的那种速度,然后就想到了这种方式, ...
- mysql lower_case_table_names 区分表名大小写设置
Command-Line Format --lower-case-table-names[=#] System Variable Name lower_case_table_names Variabl ...
- Git 执行更改
Jerry 克隆库,他决定实现基本字符串操作.于是,他创建文件string.c,在添加内容到 string.c 会这个样子. #include <stdio.h> int my_strle ...
- iOS应用跳转到App Store评分
iOS应用跳转到App Store评分 1.跳转到应用评价页 NSString *urlStr = [NSString stringWithFormat:@"itms-apps://itun ...
- Casual Note of OS
20170104 冯诺依曼计算机(遵循冯诺依曼结构设计的计算机:存储器.运算器.控制器.输入设备.输出设备)之前也有计算机,不过在那之前的计算机是专用的,不可编程,只能干特定的事情没法干其他事.与之前 ...
- Android 扩大view点击范围
Android4.0设计规定的有效可触摸的UI元素标准是48dp,转化为一个物理尺寸约为9毫米.7~10毫米,这是一个用户手指能准确并且舒适触摸的区域. 如下图所示,你的UI元素可能小于48dp,图标 ...