天天看點

從PRISM開始學WPF(七)MVVM(三)事件聚合器EventAggregator?

原文: 從PRISM開始學WPF(七)MVVM(三)事件聚合器EventAggregator? 從PRISM開始學WPF(一)WPF? 從PRISM開始學WPF(二)Prism? 從PRISM開始學WPF(三)Prism-Region? 從PRISM開始學WPF(四)Prism-Module? 從PRISM開始學WPF(五)MVVM(一)ViewModel? 從PRISM開始學WPF(六)MVVM(二)Command?

事件聚合器EventAggregator

  • Event aggregation. For communication across view models, presenters, or controllers when there is not a direct action-reaction expectation.

(⊙﹏⊙),Google一下:

事件聚合。在沒有直接的行動反應期望的情況下,跨視圖模型,示範者或控制者進行通信。           

1是沒有直接行動反應期望,2跨視圖通信

在具體了解這個概念之前,先看一個例子:

通過簡介,很容易想到聊天視窗,當你在一個視圖A中輸入文字點選發送之後,另外一個視圖B會接收到這個消息,并将文字輸出到螢幕上,而這個時候,視圖A并不關心誰将收到資訊,隻管送出,視圖B也不管是誰發來的消息,隻管接收,并顯示。

關門,放代碼:

Setp1 在Shell視窗中,定義兩個Region,分别來展示發送視圖和接收視圖

<Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <ContentControl prism:RegionManager.RegionName="LeftRegion" />
        <ContentControl Grid.Column="1" prism:RegionManager.RegionName="RightRegion" />
    </Grid>           
今天Typora更新了,代碼塊支援

xaml

格式,以前都是用

xml-dtd

,下面統一使用

xaml

XAML

這應該是教程中出現的比較複雜的xaml,比較詳細的使用了Grid,這裡的Grid很像一個表格,在使用他布局之前,需要定義好列數,下面的代碼為Grid設定了兩個列

<Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>           
那麼能加行嗎?那是當然的了,下面是為Grid添加兩行,Height的Auto表示這個行的高度會根據内容高度進行适應:
<Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>           
在使用Grid的時候,需要為控件指定Grid的位置

Grid.Column="1"

,1就是下标,都是從0開始的,1就代表第二列,當你不指定具體位置的時候,預設将控件插入Grid的0行0列,上面的

LeftRegion

就是在首行首列的位置。

Setp2 建立兩個Module,分别為ModuleA 和 ModuleB,ModuleA中的視圖用來發送資訊,ModuleB中的視圖用來接收顯示資訊。

Module的建立在第四節已經說明了

先看ModuleA的發送視圖:

MessageView.xaml

<StackPanel>
        <TextBox Text="{Binding Message}" Margin="5"/>
        <Button Command="{Binding SendMessageCommand}" Content="Send Message" Margin="5"/>
    </StackPanel>           

MessageView中定義了一個文本框,進行了資料綁定,然後是一個按鈕,綁定了一個SendMessageCommand指令。在我們點選Send Message按鈕的時候,就會将Message顯示到接收視圖裡去。

再看ModuleB的顯示視圖:

MessageList.xaml

<Grid>
        <ListBox ItemsSource="{Binding Messages}" />
    </Grid>           

就定義了一個ListBox來顯示Message。ItemsSource綁定的應該是一個集合,不然怎麼叫Source呢?

接下來,看下Prism怎麼實作跨視圖模型通訊:

首先,定義一個

MessageSentEvent

類,繼承

PubSubEvent<string>

,string是因為這個事件接收的payload是字元串類型,

PubSubEvent<T>

類負責連接配接釋出者和訂閱者,他負責維護訂閱者清單并處理事件派發給訂閱者。

using Prism.Events;
namespace UsingEventAggregator.Core
{
    public class MessageSentEvent : PubSubEvent<string>
    {
    }
}           

然後我們看下MessageViewModel:

using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using UsingEventAggregator.Core;

namespace ModuleA.ViewModels
{
    public class MessageViewModel : BindableBase
    {
        IEventAggregator _ea;

        private string _message = "Message to Send";
        public string Message
        {
            get { return _message; }
            set { SetProperty(ref _message, value); }
        }

        public DelegateCommand SendMessageCommand { get; private set; }

        public MessageViewModel(IEventAggregator ea)
        {
            _ea = ea;
            SendMessageCommand = new DelegateCommand(SendMessage);
        }

        private void SendMessage()
        {
            _ea.GetEvent<MessageSentEvent>().Publish(Message);
        }
    }
}
           

先看我們熟悉的部分:

private string _message = "Message to Send";
        public string Message
        {
            get { return _message; }
            set { SetProperty(ref _message, value); }
        }           

這是

<TextBox Text="{Binding Message}" Margin="5"/>

中的Message

然後定義了一個DelegateCommand

public DelegateCommand SendMessageCommand { get; private set; }           

接下來就是EventAggregator部分了:

首先定義一個IEventAggregator:

IEventAggregator _ea;           

構造函數:

ea是依賴注入容器提供的EventAggregator執行個體,還定義了指令SendMessageCommand的回調函數

SendMessage

public MessageViewModel(IEventAggregator ea)
        {
            _ea = ea;
            SendMessageCommand = new DelegateCommand(SendMessage);
        }           

SendMessge中通過MessageSentEvent釋出Payload,這裡Payload一定要比對MessageSentEvent的Payload類型,上面我們繼承

PubSubEvent<string>

時使用的

String

,不然的話,這在編譯的時候就會抛出異常。

private void SendMessage()
        {
            _ea.GetEvent<MessageSentEvent>().Publish(Message);
        }           

接下來,我們讓ModuleB中的

MessageListViewModel

擷取這個Payload,并進行一些操作:

using Prism.Events;
using Prism.Mvvm;
using System.Collections.ObjectModel;
using UsingEventAggregator.Core;

namespace ModuleB.ViewModels
{
    public class MessageListViewModel : BindableBase
    {
        IEventAggregator _ea;

        private ObservableCollection<string> _messages;
        public ObservableCollection<string> Messages
        {
            get { return _messages; }
            set { SetProperty(ref _messages, value); }
        }

        public MessageListViewModel(IEventAggregator ea)
        {
            _ea = ea;
            Messages = new ObservableCollection<string>();

            _ea.GetEvent<MessageSentEvent>().Subscribe(MessageReceived);
        }

        private void MessageReceived(string message)
        {
            Messages.Add(message);
        }
    }
}
           

代碼閱讀:

private ObservableCollection<string> _messages;
        public ObservableCollection<string> Messages
        {
            get { return _messages; }
            set { SetProperty(ref _messages, value); }
        }           

<ListBox ItemsSource="{Binding Messages}" />

中的Messages,他的類型是

ObservableCollection

,具體為什麼是 ObservableCollection而不是List!後面再說。

public MessageListViewModel(IEventAggregator ea)
        {
            _ea = ea;
            Messages = new ObservableCollection<string>();

            _ea.GetEvent<MessageSentEvent>().Subscribe(MessageReceived);
        }           

這裡訂閱了MessageSentEvent,并且處理Payload,處理Payload的方法是MessageReceived,這個方法在Messages新增一條記錄。

事件聚合器可以有多個釋出者和多個訂閱者。

作為訂閱者,我想訂閱特定類型的Payload,上個例子中我隻想訂閱message裡含有Brian的事件,怎麼處理呢?Prism為Subscribe方法實作了一個過濾器:

_ea.GetEvent<MessageSentEvent>().Subscribe(MessageReceived, ThreadOption.PublisherThread, false, (filter) => filter.Contains("Brian"));
           

過濾器是一個

Predicate<TPayload> filter

,參考

Predicate