原文: Prism for WPF再探(基于Prism事件的子產品間通信) 上篇博文連結
Prism for WPF初探(建構簡單的子產品化開發架構)
一、簡單介紹:
在上一篇博文中初步搭建了Prism架構的各個子產品,但那隻是搭建了一個空殼,裡面的内容基本是空的,在這一篇我将實作各個子產品間的通信,在上一篇博文的基礎上改的。
先上效果圖:初步介紹下,圖中虛線分割為四個子產品,每個子產品可向另外三個子產品發消息。這裡還是基于子產品化開發CS端程式的思路,子產品之間低耦合,如果項目做大,好處自然展現出來了。

圖中的效果已經實作了一個子產品朝其他三個子產品發送消息。這裡我使用的事Prism架構中的PubSubEvent事件,其優點是簡單易用,直接Publish和Subscribe即可。
二、基本思路
項目結構圖:
四個子產品間基礎和共用的東西我放在Desktop.Infrastructure中。A、B、C、D四個子產品都保持對Desktop.Infrastructure的引用,各自間無引用,互相獨立,以後需要添加删除子產品或者改動既有子產品,都不影響其他子產品的功能。
1、事件與接口,代碼很簡單。
接口代碼:接口定義空的就行,後面Event需要Publish的Model繼承自接口IBaseModel。
namespace Desktop.Infrastucture.Interface
{
public interface IBaseModel
{
}
}
事件代碼:自定義事件 SendMessageEvent 繼承自Prism架構的PubSubEvent。定義好Event,之後隻需要在IEventAggregator的實作中Publish和Subscribe即可。
namespace Desktop.Infrastucture.Event
{
public class SendMessageEvent : PubSubEvent<IBaseModel>
{
}
}
從下圖可以看到PubSubEvent的定義,其Subscribe支援過濾。
實作原理中其實是個子產品都訂閱了同一個事件,是以每個子產品發一次消息它本身也會接收到,而第一張的效果圖中發送消息的子產品本身并沒有顯示出接收到消息,是因為我在Subscribe的時候将本身發的消息的過濾了。
2、Model的實作。
發送的資料為ModelData,是以ModelData肯定要繼承自IBaseModel,由于WPF經常需要實作通功能,也就是必須繼承自INotifyPropertyChanged接口(這點是WPF的内容),是以我定義了一個BaseNotificationObject來繼承INotifyPropertyChanged和IBaseModel,ModelData繼承自BaseNotificationObject。
namespace Desktop.Infrastucture.Model
{
[Export(typeof(ModelData))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ModelData: BaseNotificationObject
{
/// <summary>
/// 子產品名稱
/// </summary>
private ModuleNameEnum _ModuleName;
public ModuleNameEnum ModuleName
{
get { return _ModuleName; }
set { _ModuleName = value;
}
}
/// <summary>
/// 消息内容
/// </summary>
private string _Message;
public string Message
{
get { return _Message; }
set { _Message = value;
OnPropertyChanged("Message");
}
}
}
}
3、ViewModel的實作。
每個子產品的界面都需要ViewModel,是以我把通用的功能抽象出來單獨寫成一個類BaseViewModel。代碼如下:
首先是BaseNotify,通過MEF的構造函數導入來注入IRegionManager 與IEventAggregator 的實作。其子類也就可以直接使用了。
namespace Desktop.Infrastucture.ViewModel
{
public class BaseNotify:BaseNotificationObject
{
public List<SubscriptionToken> SubscriptionTokens = new List<SubscriptionToken>();
public readonly IRegionManager regionManager;
public readonly IEventAggregator eventAggregator;
public BaseNotify()
{
}
[ImportingConstructor]
public BaseNotify(IRegionManager regionManager,IEventAggregator eventAggregator)
{
this.regionManager = regionManager;
this.eventAggregator = eventAggregator;
}
}
}
BaseViewModel是所有子產品ViewModel的父類。按鈕觸發的是BtnCommand收到消息後執行的是CallBack,這個CallBack定義成Virtual是為了子類可以重載進而執行自己特定的操作。子產品的的View中綁定的資料是Data的Message。
namespace Desktop.Infrastucture.ViewModel
{
public class BaseViewModel:BaseNotify
{
#region 屬性、字段、指令
//[Import]
private Lazy<ModelData> _Data = new Lazy<ModelData>();
public Lazy<ModelData> Data
{
get { return _Data; }
set { _Data = value; }
}
private ICommand _BtnCommand;
public ICommand BtnCommand
{
get
{
if (null == _BtnCommand)
{
_BtnCommand = new DelegateCommand<object>((obj) =>
{
eventAggregator.GetEvent<SendMessageEvent>().Publish(Data.Value);
});
}
return _BtnCommand;
}
set { _BtnCommand = value; }
}
#endregion
#region 構造
[ImportingConstructor]
public BaseViewModel(IRegionManager regionManager, IEventAggregator eventAggregator) : base(regionManager, eventAggregator)
{
eventAggregator.GetEvent<SendMessageEvent>().Unsubscribe(CallBack);
SubscriptionTokens.Add(eventAggregator.GetEvent<SendMessageEvent>().Subscribe(CallBack, ThreadOption.PublisherThread, false, x =>
{
if (x is ModelData)
{
var modelData = x as ModelData;
if (modelData.ModuleName==Data.Value.ModuleName)
return false;
}
return true;
}));
}
#endregion
#region 方法
public virtual void CallBack(IBaseModel obj)
{
if (obj is ModelData)
{
var modelData = obj as ModelData;
Data.Value.Message = "";
Data.Value.Message += "Reciced:" + modelData.Message+"\n";
}
}
#endregion
}
}
4、子產品的實作。
公共的東西都實作了,最後是子產品改怎麼來寫。每個子產品的寫法基本一緻,這裡我以其中一個為例。這些東西簡單,不多講貼代碼了。
ModelA的View
<Grid x:Class="ModuleA.View.GridA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
>
<StackPanel>
<TextBox Foreground="Red" FontSize="20" Text="{Binding Data.Value.Message}"></TextBox>
<TextBlock Foreground="Red" FontSize="20" ></TextBlock>
<Button Height="30" Width="90" Background="LightPink" Command="{Binding BtnCommand}">ClickMe</Button>
</StackPanel>
</Grid>
using System.ComponentModel.Composition;
using System.Windows.Controls;
using ModuleA.ViewModel;
namespace ModuleA.View
{
/// <summary>
/// GridA.xaml 的互動邏輯
/// </summary>
[PartCreationPolicy(CreationPolicy.NonShared)]
[Export]
public partial class GridA : Grid
{
[Import]
public GridA_ViewModel ViewModel
{
set { this.DataContext = value; }
}
public GridA()
{
InitializeComponent();
}
}
}
ModelA的ViewModel
using Desktop.Infrastucture.Interface;
using Desktop.Infrastucture.Model;
using Desktop.Infrastucture.ViewModel;
using Prism.Events;
using Prism.Regions;
using System.ComponentModel.Composition;
namespace ModuleA.ViewModel
{
[Export(typeof(GridA_ViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class GridA_ViewModel: BaseViewModel
{
//public new Lazy<ModelData> Data = new Lazy<ModelData>();
[ImportingConstructor]
public GridA_ViewModel(IRegionManager regionManager, IEventAggregator eventAggregator) : base(regionManager, eventAggregator)
{
Data.Value.ModuleName = ModuleNameEnum.ModuleA;
}
public override void CallBack(IBaseModel obj)
{
base.CallBack(obj);
}
}
}
ModuleNameEnum中定義的是ModuleA、ModuleB、ModuleC、ModuleD的枚舉。Data 的定義用了懶加載,不用也一樣的。如果要傳更多的内容,定義ModelData就行了
講的比較簡單,代碼寫的也簡單,這裡隻是作為Prism内置Event的入門,實作簡單的子產品間通信。真正複雜的架構設計要看個人水準了。
作者水準有限,如有不足之處還請賜教。
源碼在這裡!!!