【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

  https://files.cnblogs.com/bomo/EventAggregatorDemo.zip

 

 

  

 

 

 

  

posted @ 2014-08-20 18:44  bomo  阅读(698)  评论(0编辑  收藏  举报