1. 程式人生 > >Prism for WPF再探(基於Prism事件的模塊間通信)

Prism for WPF再探(基於Prism事件的模塊間通信)

模塊化 mod ane red chan lazy eat markup pub

上篇博文鏈接

一、簡單介紹: 

 在上一篇博文中初步搭建了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事件的模塊間通信)