您的位置:首页 > 运维架构 > 网站架构

如何在Windows Forms应用程序中实现可组装式(Composite)的架构以及松耦合事件机制

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

http://compositewpf.codeplex.com/

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

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

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

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

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

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

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





备注:

Common目录中的东西是每个模块都要公用的,例如对象定义,事件定义等
Modules目录中的东西是可以不断添加的模块,例如客户管理,订单管理等
MainApplication是主程序
我知道有人已经等不及了,那么我们就来看看到底这是一个什么效果吧

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





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

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





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

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

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

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

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





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





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

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

1.动态组装模块

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

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

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

整个架构的核心技术是:MEF,ManagedExtensibilityFramework这一篇文章并不是普及MEF的基础文章,事实上,我发现有很多这方面的文章,例如http://zzk.cnblogs.com/s?w=MEFMEF的官方站点是:http://mef.codeplex.com/顺便说一下,Prism从4.0开始,也直接支持MEF来做为组装技术,之前它仅支持UnityContainer的方式。我依次来解释一下有关组件以及他们的关系

Framework项目

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





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

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;

namespaceFramework
{
///<summary>
///事件聚合器的接口
///</summary>
publicinterfaceIEventAggregator
{
TGetEvent<T>();
}
}

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

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;
usingSystem.ComponentModel.Composition;

namespaceFramework
{
[Export(typeof(IEventAggregator))]
publicclassEventAggregator:IEventAggregator
{

privateList<EventBase>events=newList<EventBase>();

#regionIEventAggregatorMembers

publicTGetEvent<T>()
{
//如果事件存在就返回,否则创建一个新的
if(events.OfType<T>().FirstOrDefault()==null)
{
varevt=Activator.CreateInstance<T>();
events.Add(evtasEventBase);
}

varresult=events.OfType<T>().FirstOrDefault();

returnresult;

}

#endregion
}
}

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

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

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;

namespaceFramework
{
publicclassEventBase
{
}

publicclassCompositePresentationEvent<T>:EventBase
whereT:new()
{
//这里保存所有的处理程序
privateList<Action<T>>handlers=newList<Action<T>>();

publicvoidSubscribe(Action<T>callback)
{
///将处理程序添加到集合中
handlers.Add(callback);
}

publicvoidPublish(Tparameter)
{
///依次执行所有的处理程序
handlers.ForEach(a=>a(parameter));
}

}
}


Events项目

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





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

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;
usingFramework;
usingModels;

namespaceEvents
{
publicclassCreateOrderEvent:CompositePresentationEvent<Order>
{
}
}

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

Models项目

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

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;

namespaceModels
{
publicclassOrder
{
publicintOrderID{get;set;}
publicstringCustomerID{get;set;}

publicoverridestringToString()
{
returnstring.Format("OrderID:{0},CustomerID:{1}",OrderID,CustomerID);
}
}
}


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

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

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

CustomerModule项目





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





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

usingSystem;
usingSystem.ComponentModel.Composition;
usingSystem.Windows.Forms;
usingEvents;
usingFramework;
usingModels;

namespaceCustomerModule
{
[Export(typeof(UserControl))]
[ExportMetadata("ModuleName","CustomerModule")]
publicpartialclassUserControl1:UserControl
{
publicUserControl1()
{
InitializeComponent();
}

[Import]
publicIEventAggregatorEventAggregator{get;set;}

privatevoidbutton1_Click(objectsender,EventArgse)
{
if(EventAggregator!=null)
{
EventAggregator.GetEvent<CreateOrderEvent>().Publish(newOrder()
{
OrderID=int.Parse(txtOrderID.Text),
CustomerID=txtCustomerID.Text
});
}
}

}
}

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

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

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

某个部件需要支持动态组装,就提供Export

我需要用到其他一个部件,虽然我不知道谁会给我,我只要声明Import

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

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

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

OrderModule项目

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

usingSystem;
usingSystem.ComponentModel.Composition;
usingSystem.Windows.Forms;
usingEvents;
usingFramework;

namespaceOrderModule
{
[Export(typeof(UserControl))]
[ExportMetadata("ModuleName","OrderModule")]
publicpartialclassUserControl1:UserControl
{
publicUserControl1()
{
InitializeComponent();

Load+=newEventHandler(UserControl1_Load);
}

voidUserControl1_Load(objectsender,EventArgse)
{

EventAggregator.GetEvent<CreateOrderEvent>().Subscribe((o)=>
{
listBox1.Items.Add(o);
});
}

[Import]
publicIEventAggregatorEventAggregator{get;set;}
}
}

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

MainApplication项目

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

usingSystem;
usingSystem.Collections.Generic;
usingSystem.ComponentModel.Composition;
usingSystem.ComponentModel.Composition.Hosting;
usingSystem.IO;
usingSystem.Windows.Forms;
usingFramework;

namespaceMainApplication
{
publicpartialclassForm1:Form,IPartImportsSatisfiedNotification
{
publicForm1()
{
InitializeComponent();
}

protectedoverridevoidOnLoad(EventArgse)
{

//这里一方面要加载那些模块,还要加载Framework,因为里面有一个默认实现好的EventAggregator
varcatalog=newAggregateCatalog(
newDirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"modules")),//所有的模块必须放在应用程序根目录下面的modules目录
newAssemblyCatalog(typeof(EventAggregator).Assembly));

varcontainer=newCompositionContainer(catalog);

//立即组装这个EventAggregator部件,明确地定义
container.ComposeExportedValue<IEventAggregator>(newEventAggregator());

//执行导入
container.SatisfyImportsOnce(this);

base.OnLoad(e);

}

//导入多个模块,以及它们的元数据
[ImportMany(typeof(UserControl),AllowRecomposition=true)]
publicLazy<UserControl,Dictionary<string,object>>[]Modules{get;set;}

#regionIPartImportsSatisfiedNotificationMembers

///<summary>
///当导入成功时触发该方法
///</summary>
publicvoidOnImportsSatisfied()
{
//循环所有模块,并且添加工具栏按钮,绑定事件
Array.ForEach<Lazy<UserControl,Dictionary<string,object>>>(Modules,l=>
{
vartoolItem=newToolStripButton(l.Metadata["ModuleName"].ToString());
toolItem.Click+=(o,a)=>{
varform=newForm();
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
}
}

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

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

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

http://files.cnblogs.com/chenxizhang/WindowsFormsCompisitionFrameworkSample.rar
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐