【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类 ...
随机推荐
- css按钮口诀 - CSS BUG顺口溜
在进行CSS网页布局时遇到BUG,请认真阅读以下内容,非常容易记忆的,不知道哪位高人把CSS BUG编成了顺口溜了!看看好不好记住呢? 一.IE边框若显若无,须注意,定是高度设置已忘记: 二.浮动产生 ...
- Creating a Physical Standby Database 11g
1.Environment Item Primary database standby database Platform Redhat 5.4 Redhat 5.4 Hostname gc1 gc2 ...
- C语言 · 周期字串
算法提高 周期字串 时间限制:1.0s 内存限制:256.0MB 问题描述 右右喜欢听故事,但是右右的妈妈总是讲一些“从前有座山,山里有座庙,庙里有个老和尚给小和尚讲故事,讲的什么呢 ...
- Android——自定义视图(一)转
首先,建立一个Android工程,命名MyView. 然后,自定义一个类myView,继承View类.代码如下 package com.android.randy.viewDemo; import a ...
- centos7添加bridge-nf-call-ip6tables出现No such file or directory
在/etc/sysctl.conf中添加: net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 ...
- 【驱动】——seq_file使用指南
seq_file只是在普通的文件read中加入了内核缓冲的功能,从而实现顺序多次遍历,读取大数据量的简单接口.seq_file一般只提供只读接口,在使用seq_file操作时,主要靠下述四个操作来完成 ...
- 【C】——可变参数
写代码之前要先介绍一下可变参数的备用知识: C函数要在程序中用到以下这些宏: void va_start( va_list arg_ptr, prev_param ); type va_arg( va ...
- Python 匿名参数
#-*- coding:utf-8 -*- #匿名函数 #匿名函数语法格式 ''' 变量 = lambda 参数列表:表达式 ''' func = lambda x,y:x+y a = func(2, ...
- 自然语言交流系统 phxnet团队 创新实训 项目博客 (五)
3DMax方面所涉及的专业知识: (1)一下的关于3DMax中对于人物的设计和操作均需要在对3DMax基础知识熟练掌握的情况下进行的. (2)骨骼架设:首先 ...
- Android---如何返回上一Activity
Android 代码用来返回上一个activity 调用onKeyDown()时发生java.lang.NullPointerException. 来自:CSDN博客推荐文章 | 时间:2012 ...