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