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

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

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

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

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

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

1
2
3
public class ReceiveNewEmailEvent : CompositePresentationEvent<MailViewModel>
{
}

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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事件的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MailBox
{
    public MailBox(IEventAggregator eventAggregator)
    {
        eventAggregator.GetEvent<ReceiveNewEmailEvent>()
            .Subscribe(OnReceivedNewEmail);
    }
  
    //  该方法必须为public
    public void OnReceivedNewEmail(MailViewModel mail)
    {
        //  do something
    }
}

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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组件会订阅这个事件,并对邮件列表进行更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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来完成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//  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显示出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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就会过滤掉黑名单中已经存在的发件人的邮件:

1
2
3
4
5
6
7
public void OnRefusedSendersAdded(string sender)
{
    if (!_refusedSenders.Contains(sender))
    {
        _refusedSenders.Add(sender);
    }
}

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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,希望大家在工作中要仔细斟酌使用,虽然用起来很灵活,但是如果事件太多的话,也会让人有无从下手的感觉,增加维护的难度。

示例代码可以在这里下载

引文原址:http://www.cnblogs.com/li-xiao/archive/2011/04/20/2022962.html

Prism之使用EventAggregation进行模块间通信的更多相关文章

  1. Prism for WPF再探(基于Prism事件的模块间通信)

    上篇博文链接 Prism for WPF初探(构建简单的模块化开发框架) 一.简单介绍: 在上一篇博文中初步搭建了Prism框架的各个模块,但那只是搭建了一个空壳,里面的内容基本是空的,在这一篇我将实 ...

  2. OpenCV MFC 模块间通信

    1. 新建MFC项目 点击完成. 2. 添加按钮 在"工具箱"中找到"Button"控件,添加至界面:  2. 配置opencv, 添加colordetecto ...

  3. 如何使用Prism框架的EventAggregator在模块间进行通信

    目的 本文主要介绍如何使用Prism类库提供的事件机制在松耦合组件之间相互通信,Prism类库的事件机制建立在事件聚合服务之上,允许发布者和订阅者通过事件进行通信,不需要彼此之间引用. 事件聚合 Ev ...

  4. 在Prism 框架中,实现主程序与模块间 UI 的通信

    背景: 在模块的UI中包含 TreeView 控件,在该树形控件的每一节点前面定义了一个复选框,如图 需求: 在两个不同的应用程序中使用该控件,而它在不同应用程序中的外观则并不一致,按照本例,即一个显 ...

  5. 系统间通信(10)——RPC的基本概念

    1.概述 经过了详细的信息格式.网络IO模型的讲解,并且通过JAVA RMI的讲解进行了预热.从这篇文章开始我们将进入这个系列博文的另一个重点知识体系的讲解:RPC.在后续的几篇文章中,我们首先讲解R ...

  6. Linux下多任务间通信和同步-信号

    Linux下多任务间通信和同步-信号 嵌入式开发交流群280352802,欢迎加入! 1.概述 信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式.信号可以直接进行用户空间进程和内核进程之间的 ...

  7. React 组件间通信介绍

    React 组件间通信方式简介 React 组件间通信主要分为以下四种情况: 父组件向子组件通信 子组件向父组件通信 跨级组件之间通信 非嵌套组件间通信 下面对这四种情况分别进行介绍:   父组件向子 ...

  8. Vue 根组件,局部,全局组件 | 组件间通信,案例组件化

    一 组件 <div id="app"> <h1>{{ msg }}</h1> </div> <script src=" ...

  9. python 全栈开发,Day91(Vue实例的生命周期,组件间通信之中央事件总线bus,Vue Router,vue-cli 工具)

    昨日内容回顾 0. 组件注意事项!!! data属性必须是一个函数! 1. 注册全局组件 Vue.component('组件名',{ template: `` }) var app = new Vue ...

随机推荐

  1. 将json转化为model

    /// <summary> /// 获取Json的Model /// </summary> /// <typeparam name="T">&l ...

  2. zf-关于调用页面提示找不到className的原因

    多亏了蒋杰 还好他上次告诉我 关于节点的问题 我一看到这个函数就想到了他以前教我的    我这里一开始就调用js函数了 所以没获取到节点    后来把方法换到这里就OK了    

  3. Html 中表单提交的一些知识总结——防止表单自动提交,以及submit和button提交表单的区别

    转自:http://jackaudrey.blog.163.com/blog/static/1314217882010590041833/ 在页面中有多个input type="text&q ...

  4. Android----drawable state各个属性详解----ListView几个比较特别的属性:

    android:drawable 放一个drawable资源android:state_pressed 是否按下,如一个按钮触摸或者点击.android:state_focused 是否取得焦点,比如 ...

  5. Go语言实现列出排列组合

    今天,隔壁坐的小朋友给我一串数字: 1 6 21 55 让我观察规律,然后帮他推导公式. 尼玛,当我是神呢?!! 想了半天没看出个原委, 于是看了他那边具体需要才发现他那边是对N个数字进行5个数字的组 ...

  6. python之路:进阶 二

        c = collections.Counter(  Counter({ b = collections.Counter(  b.update(c)   Counter({ Counter({  ...

  7. POJ 3186 Treats for the Cows

    简单DP dp[i][j]表示的是i到j这段区间获得的a[i]*(j-i)+... ...+a[j-1]*(n-1)+a[j]*n最大值 那么[i,j]这个区间的最大值肯定是由[i+1,j]与[i,j ...

  8. [ERROR] Fatal error: Can't open and lock privilege tables: Table 'mysql.user' doesn't exist 160913 02:11:21 mysqld_safe mysqld from pid file /tmp/mysql.pid ended

    -- :: [Note] InnoDB: Renaming log file ./ib_logfile101 to ./ib_logfile0 -- :: [Warning] InnoDB: New ...

  9. Lazy Load, 延迟加载图片的 jQuery 插件 - NeoEase

    body { font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI ...

  10. html5绘图

    html5绘图 这是我在绘图过程中遇到的问题,求助高手帮忙啊... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ...