zoukankan      html  css  js  c++  java
  • 如何在Windows Forms应用程序中实现可组装式(Composite)的架构以及松耦合事件机制

    越来越多人都逐渐了解了在WPF和Silverlight平台上的一个可组装式框架,它的正式名称是Prism,你可以在下面的地址找到很多学习资源

    http://compositewpf.codeplex.com/

    下面这里还有一套很不错的视频

    http://www.tudou.com/playlist/id/9143859

    是的,据我对Prism的了解,我觉得它的确是一个很不错的框架,非常好的想法,我不得不说,大家都应该或多或少地对其有所学习和了解。事实上,很多想法,我们或许也有过,或者在以前的项目中实践过,而这是微软官方提供的框架,至少我是从中也学到了很多东西。

    那么,现在有一个问题就是,既然Prism是个不错的框架,那么能不能用在Windows Forms应用程序里面呢?答案是:不可以。

    噢。。。先不要着急沮丧,也不要开始扔你桌子上的东西,这并不是什么大不了的事情,世界不会停止转动。你懂的。

    我这里实现了一套类似的框架,出于演示目的,我大大简化了有关的细节,但大家通过学习也可以了解,并不是那么难,而且这是你自己的Prism,是你通过学习转换为自己的知识。

    那么,来看看这个演示程序吧

    image

    备注:

    • Common目录中的东西是每个模块都要公用的,例如对象定义,事件定义等
    • Modules目录中的东西是可以不断添加的模块,例如客户管理,订单管理等
    • MainApplication是主程序

    我知道有人已经等不及了,那么我们就来看看到底这是一个什么效果吧

    首先,这是一个可以组装的程序,就是可以通过添加Module来丰富MainApplication的功能,例如下面这样

    image

    我是将每个模块,都定义一个工具栏按钮。

    点击之后,两个模块的显示效果如下

    image

    如果光是这样,也没有什么大不了的。虽然它也很重要,它实现了模块化开发和组装。它们不管在开发阶段,还是在使用阶段,都是没有直接依赖的。

    然后,我这个例子还实现了松耦合的事件机制,就是:虽然这些模块之间确实没有任何依赖,但是,我们可以实现类似Prism那种EventAggregator机制,也就是说,它们之间仍然可以通讯。

    例如,如果这两个模块的窗口都显示出来的情况下,我可能希望在Customer Module里面下了一个订单,能立即在Order Module里面显示出来(请注意,Customer Module里面是不可能直接访问到Order Module的控件的,严格意义上说,它根本不知道是否有Order Module),我们该如何做到呢?

     

    Good question! 哲学告诉我们,问题的答案往往就在问题本身。所以,提问题,提正确的问题,是多么重要

    答案就是:Event Aggregation。你可以通过范例代码知道这个小精灵是如何工作的。现在,还是让我们来看一下效果吧

    image

    首先,我们在右侧的界面中添加订单信息,然后点击“Create Order”按钮

    image

    我们立即就发现,在左侧的订单列表中添加了一条记录。这就是我们需要的,对吧

    所以,综上所述,我在这个范例中实现了两个主要功能

    1.动态组装模块

    2.模块之间的松耦合事件

    下面我将大致解释一下内部的原理,大家可以通过下面链接下载到源代码,并且跟我的步骤来进行学习。这些代码并不见得是最优化的,欢迎自行修改

    https://files.cnblogs.com/chenxizhang/WindowsFormsCompisitionFrameworkSample.rar

    整个架构的核心技术是:MEF,Managed Extensibility Framework

    这一篇文章并不是普及MEF的基础文章,事实上,我发现有很多这方面的文章,例如

    http://zzk.cnblogs.com/s?w=MEF

    MEF的官方站点是:

    http://mef.codeplex.com/

    顺便说一下,Prism从4.0开始,也直接支持MEF来做为组装技术,之前它仅支持Unity Container的方式。

     

    我依次来解释一下有关组件以及他们的关系

    Framework项目

    这个项目是定义了框架级别的一些接口和类型,例如事件的基类,事件聚合器及其实现。这是一个Class Library项目,需要添加一个特殊的引用:System.ComponentModel.Composition.dll

    image

    IEventAgregator,这是一个接口,因为我们是要实现聚合器,所以需要支持多个事件。这里我们公开了一个方法,GetEvent,可以根据事件类型获取事件的实例

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Framework
    {
        /// <summary>
        /// 事件聚合器的接口
        /// </summary>
        public interface IEventAggregator
        {
            T GetEvent<T>();
        }
    }
    

    EventAggregator:这是对IEventAggregator的具体实现。这里用一个列表保存了所有的事件的实例。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel.Composition;
    
    
    namespace Framework
    {
        [Export(typeof(IEventAggregator))]
        public class EventAggregator:IEventAggregator
        {
    
            private List<EventBase> events = new List<EventBase>();
    
    
            #region IEventAggregator Members
    
            public T GetEvent<T>()
            {
                //如果事件存在就返回,否则创建一个新的
                if(events.OfType<T>().FirstOrDefault() == null)
                {
                    var evt = Activator.CreateInstance<T>();
                    events.Add(evt as EventBase);
                }
    
    
                var result = events.OfType<T>().FirstOrDefault();
    
                return result;
    
            }
    
            #endregion
        }
    }
    

    EventBase和CompositePresentationEvent,这两个是定义事件的基类。我们规定,在模块中所有的事件,必须基于ComositePresentationEvent进行实现。这个类型,我们提供了两个方法,Publish是触发某个事件,而Subscribe则是订阅某个事件。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Framework
    {
        public class EventBase
        {
        }
    
    
        public class CompositePresentationEvent<T>:EventBase
            where T:new()
        {
            //这里保存所有的处理程序
            private List<Action<T>> handlers = new List<Action<T>>();
    
    
            public void Subscribe(Action<T> callback)
            {
                ///将处理程序添加到集合中
                handlers.Add(callback);
            }
    
            public void Publish(T parameter)
            {
                ///依次执行所有的处理程序
                handlers.ForEach(a => a(parameter));
            }
    
        }
    }
    
     

    Events项目

    这个项目定义了在当前应用程序,所有模块之间需要公用的一些事件定义,它需要引用两个程序集:Framework,和Models

    image

    这里只有一个类型,定义了一个事件类别,CreateOrderEvent,它的基类是CompositePresentationEvent,需要传递的数据是Order

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Framework;
    using Models;
    
    namespace Events
    {
        public class CreateOrderEvent:CompositePresentationEvent<Order>
        {
        }
    }
    

    Models项目

    这个项目定义了在所有模块之间共享的业务实体类型,例如本例中用到的Order类型,它表示一个订单信息

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Models
    {
        public class Order
        {
            public int OrderID { get; set; }
            public string CustomerID { get; set; }
    
            public override string ToString()
            {
                return string.Format("OrderID:{0}, CustomerID:{1}", OrderID, CustomerID);
            }
        }
    }
    

    接下来,我们看看模块里面应该如何实现

    本例中我已经实现了两个简单的模块,他们都是标准的Class Library项目。里面各自包含了一个控件,我让每个控件成为该模块的主界面。

    CustomerModule项目

    image

    该项目,需要有四个外部引用(换句话说,任何模块都应该需要这四个引用)

    image

    我们提供了一个用户控件做为主界面。它看起来像是上面这样。并且它拥有下面这样的后台代码

    using System;
    using System.ComponentModel.Composition;
    using System.Windows.Forms;
    using Events;
    using Framework;
    using Models;
    
    
    namespace CustomerModule
    {
        [Export(typeof(UserControl))]
        [ExportMetadata("ModuleName","Customer Module")]
        public partial class UserControl1 : UserControl
        {
            public UserControl1()
            {
                InitializeComponent();
            }
    
    
            [Import]
            public IEventAggregator EventAggregator { get; set; }
    
            private void button1_Click(object sender, EventArgs e)
            {
                if(EventAggregator != null)
                {
                    EventAggregator.GetEvent<CreateOrderEvent>().Publish(new Order()
                    {
                        OrderID = int.Parse(txtOrderID.Text),
                        CustomerID = txtCustomerID.Text
                    });
                }
            }
            
        }
    }
    

    首先,我们看到在Class上面,添加了Export和ExportMetadata两个Attribute,这是MEF的核心要素,也就是说,如果这个部件需要能够动态组合,它就必须导出(Export)。

    然后,这里比较特殊的还有那个EventAggregator的属性,我们添加了一个Import的Attribute。这是干什么的呢?我们这里也没有看到谁对它进行赋值。其实,这个属性肯定不是在Module里面赋值的,是由主程序提供的。这也就是MEF的魔力之一:

    • 某个部件需要支持动态组装,就提供Export
    • 我需要用到其他一个部件,虽然我不知道谁会给我,我只要声明Import

    仔细想想吧,很酷,不是吗?

    我们现在是在Customer 模块里,刚才说了,我希望在这个模块里面做的一个操作,能够用某种方式通知其他模块。所以,请注意,在Button1_Click事件中,我们Publish了一个事件,或者称之为触发了某个事件。松耦合在这里表现得淋漓尽致:你发布事件,你不需要知道谁会响应事件,或者用什么形式响应。

    我们再来看一下订单模块吧

    OrderModule项目

    这个项目与CustomerModule有很多相似之处,除了代码。它作为事件的消费者,在启动之后,订阅了CreateOrderEvent事件。

    using System;
    using System.ComponentModel.Composition;
    using System.Windows.Forms;
    using Events;
    using Framework;
    
    namespace OrderModule
    {
        [Export(typeof(UserControl))]
        [ExportMetadata("ModuleName","Order Module")]
        public partial class UserControl1 : UserControl
        {
            public UserControl1()
            {
                InitializeComponent();
    
    
                Load += new EventHandler(UserControl1_Load);
            }
    
            void UserControl1_Load(object sender, EventArgs e)
            {
    
                EventAggregator.GetEvent<CreateOrderEvent>().Subscribe((o) =>
                {
                    listBox1.Items.Add(o);
                });
            }
    
            [Import]
            public IEventAggregator EventAggregator { get; set; }
        }
    }
    

    MainApplication项目

    这个项目,实现了动态加载模块,并且将它们绑定在工具栏上面,请参考下面代码和注释吧

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.IO;
    using System.Windows.Forms;
    using Framework;
    
    
    namespace MainApplication
    {
        public partial class Form1 : Form,IPartImportsSatisfiedNotification
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            protected override void OnLoad(EventArgs e)
            {
    
                //这里一方面要加载那些模块,还要加载Framework,因为里面有一个默认实现好的EventAggregator
                var catalog = new AggregateCatalog(
                    new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "modules")),//所有的模块必须放在应用程序根目录下面的modules目录
                    new AssemblyCatalog(typeof(EventAggregator).Assembly));
    
                var container = new CompositionContainer(catalog);
    
                //立即组装这个EventAggregator部件,明确地定义
                container.ComposeExportedValue<IEventAggregator>(new EventAggregator());
    
                //执行导入
                container.SatisfyImportsOnce(this);
    
    
                base.OnLoad(e);
    
            }
    
            //导入多个模块,以及它们的元数据
            [ImportMany(typeof(UserControl),AllowRecomposition=true)]
            public Lazy<UserControl,Dictionary<string,object>>[] Modules { get; set; }
    
            #region IPartImportsSatisfiedNotification Members
    
            /// <summary>
            /// 当导入成功时触发该方法
            /// </summary>
            public void OnImportsSatisfied()
            {
                //循环所有模块,并且添加工具栏按钮,绑定事件
                Array.ForEach<Lazy<UserControl, Dictionary<string, object>>>(Modules, l =>
                {
                    var toolItem = new ToolStripButton(l.Metadata["ModuleName"].ToString());
                    toolItem.Click += (o, a) => {
                        var form = new Form();
                        form.Text = toolItem.Text;
                        l.Value.Dock = DockStyle.Fill;
                        form.Controls.Add(l.Value);
                        form.MdiParent = this;
                        form.Show();
                        this.LayoutMdi(MdiLayout.TileVertical);
                    };
    
                    toolStrip1.Items.Add(toolItem);
                });
            }
    
            #endregion
        }
    }
    

    这个项目介绍到这里,有兴趣的朋友,可以研究一下,并且尝试添加一些新模块。欢迎你在这个基础上进行修改,实现真正能满足你需求的框架。

    本文代码,请通过下面地址下载

    https://files.cnblogs.com/chenxizhang/WindowsFormsCompisitionFrameworkSample.rar

  • 相关阅读:
    用SQL语言操作数据
    用表组织数据
    第一个C#程序
    利用CSS3制作网页动画
    CSS3美化网页元素
    列表、表格与媒体元素
    表单
    HTML5基础
    使用Java编译思想
    Day06:方法 / 猜字母游戏
  • 原文地址:https://www.cnblogs.com/chenxizhang/p/2139317.html
Copyright © 2011-2022 走看看