Prism for WPF再探(基於Prism事件的模塊間通信)
上篇博文鏈接
一、簡單介紹:
在上一篇博文中初步搭建了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的入門,實現簡單的模塊間通信。真正復雜的架構設計要看個人水平了。
作者水平有限,如有不足之處還請賜教。
源碼在這裏!!!
Prism for WPF再探(基於Prism事件的模塊間通信)