【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(120);
    }

    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

时间: 2025-01-15 02:34:05

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

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

3.2 自定义布局规则 上一节介绍了Windows Phone的系统布局面板和布局系统的相关原理,那么系统的布局面板并不一定会满足所有的你想要实现的布局规律,如果有一些特殊的布局规律,系统的布局面板是不支持,这时候就需要去自定义实现一个布局面板,在自定义的布局面板里面封装布局规律的逻辑.那么我们这一节从一个实际的需求出发,来实现一个自定义规律的布局面板.我们这一小节要实现的布局规律是把布局面板里面的子元素,按照圆形的排列规则进行排列,下面我们来看下这个例子的详细实现过程. 3.2.1 创建布局类

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

有时候想得到一些例如灰度图等特殊的渲染效果,就得用到自定义shader,关于shader的一些背景知识,自行谷歌,列出两篇cocos2dx里介绍shader的相关文章 http://blog.csdn.net/while0/article/details/9666829     http://blog.sina.com.cn/s/blog_aa01f7030101mdom.html cocos2dx在wp8平台不知道是不是在渲染时OpenGL要转成D3D的原因还是其他原因,不能在运行时编译链接s

【WP8】自定义配置存储类

之前在WP7升级到WP8的时候遇到配置不兼容的问题 情景:之前只有一个WP7版本,现在需要发布WP8版本,让用户可以从原来的WP7版本升级到WP8版本 一般情况下从WP7升级到WP8没什么问题 但是在项目中升级到WP8的时候,原先在WP7下保存在IsolatedStorageSettings的数据都不出来,经过调试发现 1.IsolatedStorageSettings存放数据的隔离存储控件的"__ApplicationSettings"文件中,存放的格式如下 {"test&

cordova3.X 运用grunt生成plugin自定义插件骨架

Cordova提供了一组设备相关的API,通过这组API,移动应用能够以JavaScript访问原生的设备功能,如摄像头.麦克风等.Cordova还提供了一组统一的JavaScript类库,以及为这些类库所用的设备相关的原生后台代码. 一.安装cordova npm install -g cordova 二.创建项目 cordova create hello com.blue.sky.hybrid.app.hello HelloWorld 三.添加平台支持 cd hello cordova pl

[WP8.1UI控件编程]Windows Phone理解和运用ItemTemplate、ContentTemplate和DataTemplate

2.2.5 ItemTemplate.ContentTemplate和DataTemplate 在理解ItemTemplate.ContentTemplate和DataTemplate的关系的之前,我们先来看看ContentControl类和ItemsControl类.ContentControl类是内容控件的基类,如Button, CheckBox,最明显的特征就是这个控件有Content属性,有Content属性的系统控件都是ContentControl的子类.ItemsControl类是列

[WP8.1UI控件编程]Windows Phone动画方案的选择

8.1 动画方案的选择 Windows Phone的动画实现方式有线性插值动画(3种类型).关键祯动画(4种类型)和基于帧动画,甚至还有定时器动画,然后动画所改变的UI元素属性可以是普通的UI元素属性,变换特效属性和三维特效属性,面对着这么多的选择,我们要实现一个动画效果该怎么去思考动画实现的思路以及怎么选择实现的技术呢?那么我们这小节会先讲解与动画性能相关的知识,然后再讲解怎么去选择动画的实现方案. 8.1.1 帧速率 帧速率是用于测量显示帧数的量度,测量单位为"每秒显示帧数"(Fr

[WP8.1UI控件编程]Windows Phone VirtualizingStackPanel、ItemsStackPanel和ItemsWrapGrid虚拟化排列布局控件

11.2.2 VirtualizingStackPanel.ItemsStackPanel和ItemsWrapGrid虚拟化排列布局控件 VirtualizingStackPanel.ItemsStackPanel和ItemsWrapGrid都是虚拟化布局控件,一般情况下在界面的布局上很少会用到这些虚拟化排列的控件,大部分都是封装在列表的布局面板上使用,主要的目的就是为了实现列表上大数据量的虚拟化,从而极大地提高列表的效率.那么其实这3个虚拟化布局控件都是列表控件的默认布局排列的方式,其中Vir

WP8.1学习系列(第一章)——添加应用栏

做过android开发的同学们应该都知道有个ActionBar的头部操作栏,而wp也有类似的一个固定在app页面里通常拥有的内部属性,就是应用栏.以前叫做ApplicationBar,现在wp和win统一称AppBar,以后Win10一统手机和桌面相信Api将会高度统一. 废话不多说了,从wp8.1开始,系统提供了AppBar和CommandBar两种控件,CommandBar集成了很多功能,但是是系统指定的模板,如果要高度自定义(如显示进度条,搜索框等等)应用栏就需要使用AppBar了.其中A

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

隔了很长时间再重看自己的代码,觉得好陌生..以后要养成多注释的好习惯..直接贴代码..对不起( ▼-▼ ) 保存保存:进行序列化后存入应用设置里 ApplicationDataContainer _appSettings = ApplicationData.Current.LocalSettings; //这个是保存一些页面输入信息 private async void Save_Click(object sender, RoutedEventArgs e) { if (userName.Tex