• 【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

      

      

  • 相关阅读:
    MVVM架构~knockoutjs实现简单的购物车
    Thrift架构~目录
    WebApi系列~在WebApi中实现Cors访问
    WebApi系列~基于RESTful标准的Web Api
    MVVM架构~目录
    IOS设计模式学习(20)命令
    MySQL保留关键字
    Eclipse with C++: "Launch failed. Binary not found."
    HTML5 Canvas鼠标与键盘事件
    通过openssh远程登录时的延迟问题解决
  • 原文地址:https://www.cnblogs.com/bomo/p/3925293.html
Copyright © 2020-2023  润新知