如何在Windows Forms应用程序中实现可组装式(Composite)的架构以及松耦合事件机制
越来越多人都逐渐了解了在WPF和Silverlight平台上的一个可组装式框架,它的正式名称是Prism,你可以在下面的地址找到很多学习资源
http://compositewpf.codeplex.com/
下面这里还有一套很不错的视频
http://www.tudou.com/playlist/id/9143859
是的,据我对Prism的了解,我觉得它的确是一个很不错的框架,非常好的想法,我不得不说,大家都应该或多或少地对其有所学习和了解。事实上,很多想法,我们或许也有过,或者在以前的项目中实践过,而这是微软官方提供的框架,至少我是从中也学到了很多东西。
那么,现在有一个问题就是,既然Prism是个不错的框架,那么能不能用在Windows Forms应用程序里面呢?答案是:不可以。
噢。。。先不要着急沮丧,也不要开始扔你桌子上的东西,这并不是什么大不了的事情,世界不会停止转动。你懂的。
我这里实现了一套类似的框架,出于演示目的,我大大简化了有关的细节,但大家通过学习也可以了解,并不是那么难,而且这是你自己的Prism,是你通过学习转换为自己的知识。
那么,来看看这个演示程序吧
备注:
- Common目录中的东西是每个模块都要公用的,例如对象定义,事件定义等
- Modules目录中的东西是可以不断添加的模块,例如客户管理,订单管理等
- MainApplication是主程序
我知道有人已经等不及了,那么我们就来看看到底这是一个什么效果吧
首先,这是一个可以组装的程序,就是可以通过添加Module来丰富MainApplication的功能,例如下面这样
我是将每个模块,都定义一个工具栏按钮。
点击之后,两个模块的显示效果如下
如果光是这样,也没有什么大不了的。虽然它也很重要,它实现了模块化开发和组装。它们不管在开发阶段,还是在使用阶段,都是没有直接依赖的。
然后,我这个例子还实现了松耦合的事件机制,就是:虽然这些模块之间确实没有任何依赖,但是,我们可以实现类似Prism那种EventAggregator机制,也就是说,它们之间仍然可以通讯。
例如,如果这两个模块的窗口都显示出来的情况下,我可能希望在Customer Module里面下了一个订单,能立即在Order Module里面显示出来(请注意,Customer Module里面是不可能直接访问到Order Module的控件的,严格意义上说,它根本不知道是否有Order Module),我们该如何做到呢?
Good question! 哲学告诉我们,问题的答案往往就在问题本身。所以,提问题,提正确的问题,是多么重要
答案就是:Event Aggregation。你可以通过范例代码知道这个小精灵是如何工作的。现在,还是让我们来看一下效果吧
首先,我们在右侧的界面中添加订单信息,然后点击“Create Order”按钮
我们立即就发现,在左侧的订单列表中添加了一条记录。这就是我们需要的,对吧
所以,综上所述,我在这个范例中实现了两个主要功能
1.动态组装模块
2.模块之间的松耦合事件
下面我将大致解释一下内部的原理,大家可以通过下面链接下载到源代码,并且跟我的步骤来进行学习。这些代码并不见得是最优化的,欢迎自行修改
https://files.cnblogs.com/chenxizhang/WindowsFormsCompisitionFrameworkSample.rar
整个架构的核心技术是:MEF,Managed Extensibility Framework
这一篇文章并不是普及MEF的基础文章,事实上,我发现有很多这方面的文章,例如
http://zzk.cnblogs.com/s?w=MEF
MEF的官方站点是:
顺便说一下,Prism从4.0开始,也直接支持MEF来做为组装技术,之前它仅支持Unity Container的方式。
我依次来解释一下有关组件以及他们的关系
Framework项目
这个项目是定义了框架级别的一些接口和类型,例如事件的基类,事件聚合器及其实现。这是一个Class Library项目,需要添加一个特殊的引用:System.ComponentModel.Composition.dll
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
这里只有一个类型,定义了一个事件类别,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项目
该项目,需要有四个外部引用(换句话说,任何模块都应该需要这四个引用)
我们提供了一个用户控件做为主界面。它看起来像是上面这样。并且它拥有下面这样的后台代码
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