• 转载 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,希望大家在工作中要仔细斟酌使用,虽然用起来很灵活,但是如果事件太多的话,也会让人有无从下手的感觉,增加维护的难度。

  • 相关阅读:
    python运行错误---TabError: Inconsistent use of tabs and spaces in indentation
    python运行错误------Non-UTF-8 code
    opencv错误(Unhandled expection at at 0x0007EEE...)
    fatal error LNK1112: 模块计算机类型“X86”与目标计算机类型“x64”冲突——我的解决方案
    基础术语
    opencv
    图像归一化
    人脸相关数据库
    堆排序
    abp学习(二)
  • 原文地址:https://www.cnblogs.com/superxiaohuihui/p/7010128.html
Copyright © 2020-2023  润新知