• 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+"
    ";
                }
            }
    
    
            #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的入门,实现简单的模块间通信。真正复杂的架构设计要看个人水平了。

    作者水平有限,如有不足之处还请赐教。

    源码在这里!!!


  • 相关阅读:
    离线安装SharePoint 2010必备组件下载链接
    skrollr——兼容性超强的视差滚动js插件
    Heroku实战入门(二)简单实战
    xp中安装sybase15.7遇到的三个问题
    思科三层交换机开启路由模式的方法
    Heroku实战入门(一)初识heroku
    Heroku实战入门(三)常用命令
    Splinter——开源的轻量级前端测试工具
    Request.ServerVariables 转
    Page.MaintainScrollPositionOnPostBack 属性
  • 原文地址:https://www.cnblogs.com/lelehellow/p/8136867.html
Copyright © 2020-2023  润新知