• 转载 Prism之使用EventAggregation进行模块间通信 (http://www.cnblogs.com/li-xiao/archive/2011/04/20/2022962.html)


    在开发Silverlight程序的时候,经常需要在不同的组件间进行通信。比如点击一个button,可能就需要改变另一个控件的内容。比较直接的办法是使用事件,当然使用MVVM的时候也可以使用command,还可以定义一些全局的变量来保存一些信息等。

    Prism提供了几种用于组件间通信的途径,可以使用RegionContext使不同的视图共享数据,也可以借助于容器的力量来使用共享的service来进行通信,或者使用command等。除此之外,Prism还提供了一种基于事件的多播发布/订阅方式的通信机制,使不同的组件之间能够以一种松散耦合的方式来进行通信。这就是本文要介绍的事件聚合(Event Aggregation)。

    事件聚合的过程有点像收听广播,首先要有个固定的频率,然后内容就会在这个频率上广播出去,至于有没有人收听,广播电台是不知道的,它只是把内容播送了出去。而其他的人想听广播也不用跑到广播电台,只要知道频率,收听这个频率就可以了。联系广播电台和听众的就是这个频率。

    在事件聚合的过程中,事件发布方(publisher)相当于广播电台,事件接收方(Subscriber)相当于听众,而事件自然就相当于频率了。

    使用Event Aggregation很简单,只需要知道一个接口和一个类基本上就足够了。接口是IEventAggregator,类是CompositePresentationEvent。

    要想发布或订阅事件,自然得先要有事件,所以第一件工作就是要定义事件。Prism提供了一个事件基类CompositePresentationEvent<TPayload>,自定义的事件只需要继承这个类就可以了,泛型代表的是事件发生过程中需要传递的参数类型。如:

    public class ReceiveNewEmailEvent : CompositePresentationEvent<MailViewModel>
    {
    }

    上面定义了一个事件,用于在收到新邮件时使用,传递的参数是一个邮件的ViewModel。

    使用的时候也很简单,使用IEventAggregator接口中的GetEvent<TEventType>方法来获取事件,然后要么发布出去,要么订阅一下就可以了。

    下面是当收到一封新的邮件的时候,发布事件的代码:

    public class EmailReceiver
    {
        private IEventAggregator _eventAggregator;
        public EmailReceiver(IEventAggregator eventAggregator)
        {
            _eventAggregator = eventAggregator;
        }
     
        public void ReceiveEmail()
        {
            if (_email != null)
            {   //  当接收到新邮件时,就发布事件,所有订阅了该事件的组件都会接到通知
                _eventAggregator.GetEvent<ReceiveNewEmailEvent>()
                    .Publish(_email);
            }
        }
    }

    可以看到我们直接在构造函数中传递了IEventAggregator类型的参数,如果使用Prism来搭建Silverlight程序的话,那么在默认的Bootstrapper中会在容器中添加IEventAggregator的实例,所以并不需要我们做其它更多的工作。如果对Prism或Bootstrapper不太了解的话,可以参考这两篇文章(Prism简介Bootstrapper)。

    下面是订阅ReceiveNewEmail事件的代码:

    public class MailBox
    {
        public MailBox(IEventAggregator eventAggregator)
        {
            eventAggregator.GetEvent<ReceiveNewEmailEvent>()
                .Subscribe(OnReceivedNewEmail);
        }
     
        //  该方法必须为public
        public void OnReceivedNewEmail(MailViewModel mail)
        {
            //  do something
        }
    }

    这样,发布出去的事件马上就可以被接收到,而且两个组件只是依赖于事件,彼此之间是松散耦合的。

    事件可以订阅,也可以退订,甚至可以有选择地接受某些特定的事件。下面以一个模拟的简单的邮箱客户端来演示一下Event Agregation的使用场景。

    如图所示,左边是邮件列表,会有一个定时器每隔两秒钟接收到一封邮件,这时邮箱客户端会更新邮件列表,点击左边的列表,会在右边显示邮件的内容。如果点击’将该发信人加入黑名单’,则不会再接受来自该发件人的邮件,如果点击断开连接,则停止接受邮件,再次点击会继续接收邮件。需求大致就是这样了。

    首先在启动程序的时候开启一个定时器,每隔两秒钟会接收一封邮件,并发布事件通知有新邮件:

    public class EmailReceiver
    {
        public void Run()
        {
            var timer = new DispatcherTimer();
            timer.Tick += (s, e) => EventAggregatorRepository.EventAggregator
                                        .GetEvent<ReceiveNewEmailEvent>()
                                        .Publish(EmailRepository.GetMail());
            timer.Interval = new TimeSpan(0, 0, 0, 2);
            timer.Start();
        }
     
    }

    MailList组件会订阅这个事件,并对邮件列表进行更新:

    public partial class MailList : UserControl
    {
        private readonly ObservableCollection<MailViewModel> _mails = 
            new ObservableCollection<MailViewModel>();
     
        //  黑名单列表
        private readonly List<string> _refusedSenders = new List<string>();
             
        public MailList()
        {
            InitializeComponent();
     
            MailItems.ItemsSource = _mails;
     
            SubscribeReceiveEmailEvent();
        }
     
        private void SubscribeReceiveEmailEvent()
        {   //  订阅事件的Subscribe方法提供了几个重载方法,除了最简单的直接订阅之外,
            //  还可以指定线程类型(比如如果直接使用System.Threading.Timer的话,
            //  就必须使用ThreadOption.UIThread,否则会报错),以及是否持有订阅者的引用,
            //  或者指定一个filter来对事件进行过滤
            //  本例中使用的filter是拒绝接受黑名单中包含的发件人发过来的邮件
            EventAggregatorRepository.EventAggregator
                .GetEvent<ReceiveNewEmailEvent>()
                .Subscribe(OnReceiveNewEmail, ThreadOption.UIThread,
                true, (mail) => !_refusedSenders.Contains(mail.From));
        }
     
        public void OnReceiveNewEmail(MailViewModel mail)
        {
            _mails.Insert(0, mail);
        }
    }

    当点击左边的邮件列表的时候,会在右边的MailContent组件中显示该邮件的信息,这个过程也是通过Event Aggregation来完成的。

    //  NotificationObject是Prism提供的对MVVM的支持的ViewModel的基类
    //  可以简化INotifyPropertyChanged接口的实现方式
    public class MailViewModel : NotificationObject
    {
        public MailViewModel()
        {   //  DelegateCommand也是Prism提供的一种Command类型
            ViewMailCommand = new DelegateCommand(OnViewMail);
        }
             
        public ICommand ViewMailCommand { get; private set; }
     
        public void OnViewMail()
        {
            this.HasRead = true;
            EventAggregatorRepository.EventAggregator
                .GetEvent<ViewEmailEvent>()
                .Publish(this);
        }
    }

    当点击时,会进入相应的Command逻辑,而MailContent则订阅了ViewEmailEvent,并将传递过来的MailViewModel显示出来:

    public partial class MailContent : UserControl
    {
        public MailContent()
        {
            InitializeComponent();
     
            EventAggregatorRepository.EventAggregator
                .GetEvent<ViewEmailEvent>()
                .Subscribe(OnViewEmail);
        }
     
        public void OnViewEmail(MailViewModel mail)
        {
            this.DataContext = mail;
        }
    }

     当点击将该发信人加入黑名单按钮时,会发布AddRefuseSenderEvent,而接收到这一事件的MailList组件则会更新黑名单,这样filter就会过滤掉黑名单中已经存在的发件人的邮件:

    public void OnRefusedSendersAdded(string sender)
    {
        if (!_refusedSenders.Contains(sender))
        {
            _refusedSenders.Add(sender);
        }
    }

    如果点击了断开连接或重新连接的话,会发布一个ConnectOrDisconnectMailServerEvent事件。Prism的事件基类并不支持不带参数的事件,也就是说没有办法创建一个不需要传参的事件。所以这里我们使用了object类型作为参数类型,在传递参数的时候直接传了个null过去。

    EventAggregatorRepository.EventAggregator
        .GetEvent<ConnectOrDisconnectMailServerEvent>()
        .Publish(null);

    而当MailList接收到该事件的时候,首先判断一下是否已经订阅了ReceiveNewEmailEvent事件,如果订阅了就退订,如果没有订阅就重新订阅。这样来达到开启或关闭接收邮件的目的:

    public partial class MailList : UserControl
    {
        private readonly ObservableCollection<MailViewModel> _mails = 
            new ObservableCollection<MailViewModel>();
     
        private readonly List<string> _refusedSenders = new List<string>();
             
        public MailList()
        {
            InitializeComponent();
     
            SubscribeReceiveEmailEvent();
     
            EventAggregatorRepository.EventAggregator
                .GetEvent<ConnectOrDisconnectMailServerEvent>()
                .Subscribe(OnConnectOrDisconnectMailServer);
        }
     
        public void OnConnectOrDisconnectMailServer(object obj)
        {
            //  判断是否已经订阅了该事件
            bool hasSubscribed = EventAggregatorRepository.EventAggregator
                .GetEvent<ReceiveNewEmailEvent>()
                .Contains(OnReceiveNewEmail);
            if (hasSubscribed)
            {
                UnsubscribeReceiveEmailEvent();
            }
            else
            {
                SubscribeReceiveEmailEvent();
            }
        }
     
        private void SubscribeReceiveEmailEvent()
        {
            EventAggregatorRepository.EventAggregator
                .GetEvent<ReceiveNewEmailEvent>()
                .Subscribe(OnReceiveNewEmail, ThreadOption.UIThread,
                true, (mail) => !_refusedSenders.Contains(mail.From));
        }
     
        private void UnsubscribeReceiveEmailEvent()
        {   //  退订事件
            EventAggregatorRepository.EventAggregator
                .GetEvent<ReceiveNewEmailEvent>()
                .Unsubscribe(OnReceiveNewEmail);
        }
     
        public void OnReceiveNewEmail(MailViewModel mail)
        {
            _mails.Insert(0, mail);
        }
    }

    由于EventAggregation并不需要建立在Prism装配的程序上,为了操作简便,所以并没有使用Prism来管理这个程序,当然也就没有使用容器。所以我用了一个static的全局变量来保存了一个IEventAggregator的实例。

    本文为了演示,所以大量地使用了Event Aggregation,希望大家在工作中要仔细斟酌使用,虽然用起来很灵活,但是如果事件太多的话,也会让人有无从下手的感觉,增加维护的难度。

  • 相关阅读:
    51 Nod 1086 多重背包问题(单调队列优化)
    51 Nod 1086 多重背包问题(二进制优化)
    51 Nod 1085 01背包问题
    poj 2559 Largest Rectangle(单调栈)
    51 Nod 1089 最长回文子串(Manacher算法)
    51 Nod N的阶乘的长度 (斯特林近似)
    51 Nod 1134 最长递增子序列(经典问题回顾)
    51 Nod 1020 逆序排列
    PCA-主成分分析(Principal components analysis)
    Python中cPickle
  • 原文地址:https://www.cnblogs.com/superxiaohuihui/p/7010128.html
Copyright © 2020-2023  润新知