WorkItem是一个运行时容器,该容器中包含完成一个用例所需要了各种各样的组件,组件可以是可视化的也可以是非可视化的,比如:SmartPart,Service,Commonds等等。
WorkItem中定义了如下的属性:
- Services
Services是一个集合,用来管理所有和实现一个用例相关的Service,可以通过如下代码将一个Service添加到WorkItem中:
WorkItem.Services.AddNew<TestService, ITestService>();
上述的代码中,第一个参数是一个具体Service的实现,第二个参数是该Service的接口。
一旦将一个Service添加到WorkItem中,该WorkItem中的其它组件(比如SmartPart)可以使用如下代码获取该Service的一个引用。
ITestSerivce service = WorkItem.Services.Get<ITestService>();
- SmartParts
同样,我们也可以向一个WorkItem中添加一个SmartPart:
TestView view = WorkItem.SmartParts.AddNew<TestView>("TestView");
我们也可以通过下面方法获取一个已经存在的SmartPart
TestView view = WorkItem.SmartPart.Get("TestView");
同时使用下面代码将一个SmartPart显示在Workspace中:
if(view == null )
{
TestViewview=WorkItem.SmartParts.AddNew<TestView> ("TestView");
WorkItem.Workspaces[WorkspaceNames.XXXXX].Show(view);
}
else
{
WorkItem.Workspaces[WorkspaceNames.XXXXX].Activate(view);
}
在这段代码中,我们不难发现,当一个SmartPart已经存在的时候,我们不需要再添加一个实例到WorkItem中,这样可以保证对同一个SmartPart来说,只有一个实例在WorkItem中。
- Workspaces
Workspaces的设计和上面SmartParts的设计一致,我们可以通过一个Workspace的唯一名字来获取对应的Workspace实例。
WorkItem.Workspaces[WorkspaceNames.XXXXX]
- UIExtensionSites
同样,UIExtensionSites也是一个集合,用来管理UIExtensionSite,我们可以通过下列方法将一个UIExtensionSite添加到WorkItem中:
WorkItem.UIExtensionSites.RegisteSite(UIExtensionSiteNames.XXXX, XXXXXXX;
将一个UIExtensionSite添加WorkItem之后,我们可以用下列方法添加一个UIElement到UIExtensionSite中,比如在ToolBarStrip上添加一个Button:
WorkItem.UIExtensionSites[Constants.UIExtensionSiteNames.ButtonsBar].Add(new ToolStripButton());
- Commands
Commands集合用来管理Command,
WorkItem.Commands[Constants.CommandNames.TestButtonClick].AddInvoker(element, "Click");
WorkItem中还定义了其它一些集合变量,比如Events,WorkItems和Items。这里我们就不一一详述。
WorkItem hierarchy
在CAB中,WorkItem具有一定的层次,最顶端的WorkItem是RootWorkItem,它在整个应用程序中是唯一的。RootWorkItem会在应用程序开始的时候加载,当我们使用SCSF创建一个业务模块的时候,它自动会创建一个WorkItem,并在该模块加载的时候,将该WorkItem添加到RootWorkItem中去。一个WorkItem中的组件可以访问同一WorkItem中的其它组件,同时也可以访问父WorkItem中的组件。基本的访问规则如下,一个组件可以访问下列组件:
- 同一WorkItem中的
- 父WorkItem中的
- 祖父WorkItem中的(以此类推)
由此我们可以看出,RootWorkItem中的组件对于整个应用程序来说是共享的。我们可以activated and deactivated WorkItem。在同一时刻,只有一个WorkItem是处于激活状态。
从更高的层次上来说,一个WorkItem封装了一个Use-Case。WorkItem的层次性关系反映了业务中UseCases之间的关系。这样的一种关系可以帮助我们在设计阶段识别出相应的WorkItems。但是也并不是粒度越细越好,详细WorkItem设计参考其它文章。
Event Broker Service
CAB中我们使用Event Broker Service来处理不同Module之间的通信。这样的目的是为了保持各个业务模块之间的松耦合。当CAB加载一个Module的时候,它会遍历该模块中所有标记为EventPublications的事件和标记为EventSubscriptions的方法,将他们放入事件容器中,所有的EventTopic的实例都会放在WorkItem.EventTopics集合中。
Publish an Event
在CAB中,我们可以通过在一个Event前面添加一个EventPublication属性来发布一个事件。该属性(Attribute)有两个参数,一个是事件名,另外一个是事件发布的范围。这里我们有三种发布方式:
- PublicationScope.WorkItem,只对该WorkItem下有效
- PublicationScope.Descendants,对该WorkItem有效,同时对它子的WorkItem也有效
- PublicationScope.Global,对整个应用程序有效,对所有的WorkItem有效
下面的例子告诉我们如何发布一个全局有效的事件:
[EventPublication("event://UpdatesAvailable/New", PublicationScope.Global)]
public event SomeEventHandler UpdatesAvailable;
Subscribe an Event
同样,我们可以在一个方法前面添加EventSubscription属性来订阅一个事件(方法必须在事件发布范围之内)。不同的方法可以订阅同一个事件。在订阅事件的时候,我们可以指定该方法运行的线程规则,有三种可选的方案:
- ThreadOption.Background,系统会创建一个独立的后台线程运行该方法。
- ThreadOption.Publisher,该方法的执行和Publisher线程同步。
- ThreadOption.UserInterface,在当前激活的界面线程中执行。该选项可以保证编辑数据同时没有更新的数据。
下面的例子说明如何订阅一个事件,并其和当前界面同一线程处理:
[EventSubscription("event://UpdatesAvailable/New", Thread=ThreadOption.UserInterface)]
public void NewUpdates(object sender, SomeEventArgs numUpdates)
{
MessageBox.Show(numUpdates.ToString(), "Updates available");
}
Event Broker 的实现
在CAB中,Event Broker系统包含了下面几个类和接口:
- EventTopic,定义一个在Publisher和Subscriber之间的事件主题
- WorkItem,暴露一个EventTopics集合,它拥有一个注册EventTopic实例列表
- EventInspector,检查所有的对象或组件,看是否存在事件发布和订阅(EventPublication或EventSubscription属性)。如果有,将publications和subscriptions注册到EventTopic中,同时将EventTopic添加到WorkItem.EventTopics集合中。
Command
当我们处理界面事件的时候,往往会遇到不同的界面元素处理同样的事件,比如:定义一个OpenFile菜单选项用来打开一个文件,同时定义在ToolBar上定义一个按钮来打开一个文件,这两个界面元素处理相同的业务。这时候,我们可以使用Command模式来处理此类设计。在CAB中,我们可以写一个事件处理方法,将该方法绑定到多个界面元素事件处理。
[CommandHandler(“TestButtonClick”)]
public void OnTestButtonClick(object sender, EventArgs e)
{
MessageBox.Show("Successful");
}
上述代码定义了一个Command处理方法,CommandHandler属性(Attribute)“伴随一个Command名字”,用来申明该方法是用来处理名为TestButtonClick的命令。我们可以通过下列方法将一个Command绑定到一个UIElement事件:
WorkItem.Commands[Constants.CommandNames.TestButtonClick].AddInvoker(element1, "Click");
WorkItem.Commands[Constants.CommandNames.TestButtonClick].AddInvoker(element2, "Click");
其中参数element1或element2是我们想要绑定的UIElement,比如一个Button和MenuItem。我们可以看出不同的UIElement绑定到相同的事件处理方法。
参考:Command Patter http://www.cnblogs.com/zhenyulu/articles/69858.html
Module & Component
一个CAB应用程序是由若干个Modules(DLLs)组成的。每个Module包含了很多的组件(Componet),这些组件既可以是可视化的也可以非可视化的组件,比方说SmartPart,Workspace,WorkItems,Services等等。
Component
CAB应用程序的最小单元是Component,它包含了下列类型的Component;
Visual Element
- SmartParts
- Items(Views 和 Controls)
Support for visual elements
- WorkItems
- Workspaces
- UIExtensionSites
Non-visual elements
- Commands
- EventTopics
- Services
Module
前面我们提到CAB应用程序是由Modules组成的,每个Module是一个独立的部署单元,CAB中提供一个在运行时加载Module的服务,在缺省情况下,该服务使用一个名为ProfileCatalog.xml文件来加载Modules。
当CAB加载一个模块的时候,它使用反射(Reflection)来判断该Module中是否包含一个实现IModule接口的类(通常情况下通过集成ModuleInit类来实现)。
我们可以使用SCSF来创建两种不同类型的Module:
- Functional Module -- 只是给其它模块提供一些服务,并不实现一个Use-case,不包含一个WorkItem
- Business Module -- 实现一系列相关的用例,包含WorkItems。
UI Elements
对于一些通用的界面元素,比如MenuItem,Toolbar,StatusBar等等,CAB有它统一的定义和实现。
在CAB中,将这些统一的界面元素定义为UIExtensionSite,每一个UIExtensionSite是用一个唯一名字标识的。开发人员(通常来说是Shell开发人员)将一个界面元素注册为一个UIExtensionSite,下面代码描述将一个MenuStrip注册为一个UIExtensionSite:
RootWorkItem.UIExtensionSites.RegisterSite(“FileMenu”, Shell.MainMenuStrip);
一旦注册成功后,模块开发人员就可以使用它添加相关的界面控件,比如下面代码将一个MenuItem添加到菜单栏中:
ToolStripMenuItem printItem = new ToolStripMenuItem("Print"); RootWorkItem.UIExtensionSites[“FileMenu”].Add(printItem);
注意:虽然开发人员可以直接使用RootWorkItem.UIExtensionSites["XXXX"]来获取相关的UIExtensionSite,并添加相应控件,但是一般来说这不是一个好的设计,Shell开发人员最好定义一组通用的Shell服务接口,在这些接口中实现如何添加控件到UIExtensionSite中,对于Module开发人员,他们只需要调用相关的接口去添加控件到UIExtensionSite中,这样,Module开发人员不需要关心Shell实现的具体细节。比如:AddButtonToToolBarStrip, AddMenuItemToMenuStrip,等等。
SmartPart & Workspce
SmartPart是一个应用程序的可视化组件,我们可以通过继承System.Windows.Forms.UserControl类来实现。一旦继承了UserControl类,开发人员就可以进行界面的设计。接下来我们需要将SmartPart显示在界面中(Workspace)。下面的代码描述了如何将SmartPart显示在指定的Workspace中:
TestView view = WorkItem.SmartParts.AddNew<TestView>();
WorkItem.Workspaces[“TestWorkSpace”].Show(view);
在上面的代码中,我们将TestView添加到WorkItem的SmartParts集合中,并且显示在名为“TestWorkSpace”的Workspace中。
Workspace是用来显示控件或SmartPart的组件,在CAB中包含了如下的Workspace类型:
- WindowWorkspace.
- MdiWorkspace.
- TabWorkspace.
- DeckWorkspace.
- ZoneWorkspace.