上篇博文链接
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的入门,实现简单的模块间通信。真正复杂的架构设计要看个人水平了。
作者水平有限,如有不足之处还请赐教。
源码在这里!!!