Prism之使用EventAggregation進行模組間通訊

來源:互聯網
上載者:User

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,希望大家在工作中要仔細斟酌使用,雖然用起來很靈活,但是如果事件太多的話,也會讓人有無從下手的感覺,增加維護的難度。

範例程式碼可以在這裡下載

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,希望大家在工作中要仔細斟酌使用,雖然用起來很靈活,但是如果事件太多的話,也會讓人有無從下手的感覺,增加維護的難度。

範例程式碼可以在這裡下載

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.