2007年8月31日星期五

第五章 在模块中添加MVP模式兼容的视图

本文会涉及到如下内容:

  • 什么是MVP
  • 使用SCSF创建一个视图(View)
  • 设计视图(View)
  • 实现视图(View)逻辑
  • 在Shell中显示View
  • 实现Presenter逻辑
  • 如何实现ISmartPartInfoProvider

一、什么是MVP

当我们在写界面程序时,常常包含各种各样的界面控件,用户事件,事件响应的控制逻辑,如果将这些代码全部放在一个界面类中的话,该类会非常复杂,并且很难进行单元测试,同时,也很难在不同界面中共享相同的行为或服务。

MVP的提出就是为了解决上述问题,它将数据显示和事件处理分离为两个不同的类 - View和Presenter,View负责将数据显示在界面上,并且将事件处理转给Presenter类。下图显示了该模式中各个类之间的关系:

这里的Model拥有View显示的数据,数据状态的改变是有Presenter控制的。

详细定义参考Martin Folwer 的 UI Architecutre

二、使用SCSF创建一个MVP模式兼容的视图

打开一个业务模块项目,在Views目录下,点击鼠标右键,选择“Smart client Factory” ->"Add View (with Presenter)",创建View向导会被打开,输入View的名字(TestView),选中“Create a folder for the view”,这样,系统会为该View创建一个单独的目录,点击完成,向导会创建以下相关的类:

  • ITestView - 该文件是一个为View设计的空的接口,Presenter使用该接口而不是具体的View实现。
  • TestView - 该类继承UserControl基类,可以在该类中设计用户界面,同时该类实现了ITestView接口并包含了一个TestViewPresenter实例,当有用户交互操作的时候,可以使用该Presenter调用相关的业务逻辑和接口。
  • TestViewPresenter - 该类继承了基础的Infrastructure.Interface.Presenter类,包含了View所需要的业务逻辑

设计视图(View)

打开TestView设计,在该界面中添加所需的控件。详细的介绍参考界面设计相关主题。

三、实现视图(View)的业务逻辑

View的设计完成后,我们就要考虑如何将数据显示在用户界面上,一般来说,在处理View的业务逻辑需要考虑两件事情,一件是如何将数据绑定到View的控件上,另外一件是如何处理事件。

数据显示:

由于View只是负责数据的显示(比如将一个Business Entity实例的变量显示),它并不知道数据是如何获取的或更新的,而数据的获取或更新是由Presenter负责,因此,为了能让Presenter将更新的数据及时通知View显示,并且Presenter不需要知道View的具体实现,因此在IView(比如,ITestView)接口中定义一些通用的接口(比如:BindDataToView(Person person))。在View的具体实现类中,我们重载该方法,将数据显示在View中。一旦Presenter知道数据发生更新,可以在Presenter中调用该接口,这样就可以通知所有对该数据更新关心的具体的View。

事件处理:

在View中需要处理的另外一件事情是如何处理View中的事件,比如Button响应事件等。对于每一个事件处理,我们可以在Presenter中定义一个方法,用来处理不同的事件,而在View中,我们需要为每个Button事件创建一个Stub方法,在该方法中调用相应的Presenter方法。对于事件的处理,我们也可以用Command或Event Subscriber&Publish模式,详细参考各自文章。

接下来的一个步骤是将视图(View)显示在Shell中

四、显示View

下列的代码是用来将View显示在Shell中的

TestView view = WorkItem.SmartParts.AddNew<TestView>("TestView");

WorkItem.Workspaces[WorkspaceNames.XXXXXX].show(view);

五、实现Presenter逻辑

前面我们提到显示在View的数据更新,事件的处理是通过Presenter来实现的,因此,在Presenter中,我们可能会调用各种各样的服务(service)来实现真正的业务逻辑,比如调用WebService Proxy。下面的任务将要告诉我们如何实现Presenter调用相关的服务(service)。

  • 如何将Service加到WorkItem中

在业务模块中,打开ModuleController类,找到AddServices方法,使用下述代码将一个Service添加到WorkItem的Service集合(Collection)中。

WorkItem.Services.AddNew(MyService)();

这样,我们就将一个Service实例添加到WorkItem的Services集合中,以便在该模块中的其他类使用。

  • 如何在View载入的时候,调用相关的Service

在View显示或载入的时候,往往会显示一些初始的数据,这是在Presenter的OnViewReady方法中实现的。同时在Presenter中,我们会定义一些服务(Service)变量,在创建Presenter实例的时候,我们需要将这些对象赋值。在这里我们利用了Injection方式来初始化这些服务。

[InjectionConstructor]

public TestViewPresenter

(

[ServiceDependency] MyService1 myservice1,

[ServiceDependency] MyService2 myservice2

)

{

_myservice1 = myservice1;

_myservice2 = myservice2;

}

在上面的代码里面,属性(Attribute)[InjectionConstructor]是应用在构造函数用来告诉ObjectBuilder当创建一个Presenter实例的时候调用该构造函数。而[ServiceDependency]属性是用来告诉ObjectBuilder去WorkItem中检索一个存在的Service实例,并将该实例作为值赋予一个变量。ObjectBuilder首先会在当前的WorkItem中寻找对应的Service实例,如果找不到,则会到上一层的WorkItem中寻找,直到Root WorkItem,当在RootWorkItem中找不到实例的话,会抛出异常。这样,我们就可以在Presenter中使用定义好的服务。

六、如何实现ISmartPartInfoProvider

当我们定义SmartPart的时候,那些使用该SmartPart的组件希望能获取关于SmartPart更多更丰富的信息,该功能是由ISmartPartInfo来实现的,在CAB中,我们定义了一个名为ISmartPartInfoProvider Interface,该接口只定义了一个方法:

public interface ISmartPartInfoProvider

{

ISmartPartInfo GetSmartPartInfo(Type smartPartInfoType);

}

该方法返回一个ISmartPartInfo,和SmartPart相关的丰富的信息可定义在里面,因此我们可以在定义View的时候实现该接口,用来提供更多的信息。

打开TestView,修改如下:

public partial class TestView : UserControl, ITestView, ISmartPartInfoProvider

实现方法如下:

public ISmartPartInfo GetSmartPartInfo(Type smartPartInfo)

{

return _presenter.GetSmartPartInfo(smartPartInfo);

}

当一个外部组件(Shell 中的Workspace)请求获取View的SmartPartInfo的时候,我们都将该请求转发给Presenter来完成。我们可以将当前View显示的数据信息通过Presenter发送给Workspace。

此致,创建一个MVP模式兼容的视图,以及相关的设计已经完成,下一章节中我们将讨论如何调用WebService。

2007年8月30日星期四

第四章 如何使用UIExtensionSites

UIExtensionSite菜单项(MenuItem)菜单工具栏(Menu strips)按钮按钮工具栏(Toolbar Strips)下面的篇幅中,将要介绍如何使用UIExtensionSite: 步骤:

添加一个UIExtensionSite

  • 注册UIExtensionSite
  • 添加一个界面元素到UIExtensionSite中
  • 绑定到一个Command
  • 书写Command处理函数
  • 一、添加一个UIExtensionSite

    在这个例子中,我们添加一个ToolbarStrips类型的UIExtensionSite。打开ShellLayoutView或ShellForm(取决于是否在创建SmartClient工程的时候创建Layout项目),在Toolbox中选中ToolStrip,添加到Designer中,打开属性,在Dock中选中Top,修改Name为“ButtonsBar”。

    在Infrastructure.Interface项目中,打开Constants.UIExtensionSiteNames类,添加如下代码:

    public const string ButtonsBar="ButtonsBar"

    将ButtonsBar的名字定义为常量,在UIExtensionSiteNames中,所有的常量必须唯一。在SmartClient中,是以名字作为一个UIExtensionSite唯一标识的。

    二、注册UIExtensionSite

    下面的代码用来注册一个UIExtensionSite

    WorkItem.UIExtensionSites.RegisteSite(UIExtensionSiteNames.ButtonsBar, this.View.ButtonsBar);

    可以将上述代码加到ShellLayoutViewPresenter类的OnViewSet()方法中或加到ShellApplication类的Run方法中(取决于是否在创建SmartClient工程的时候创建Layout项目)。

    三、添加一个界面元素到UIExtensionSite中

    打开ModuleController.cs,在ExtendToolStrip方法中将一个定义好的按钮加入先前定义好的ButtonsBar中。

    ToolStripButton element = new ToolStripButton()

    //add code here to inital element.

    WorkItem.UIExtensionSites[Constants.UIExtensionSiteNames.ButtonsBar].Add(new ToolStripButton());

    到目前为止,我们已经将一个按钮加到一个UIExtensionSite中,下一步我们将介绍如何使加入的按钮响应鼠标点击事件,并调用相应的方法。

    四、将鼠标响应事件绑定到一个Command

    在CAB中,我们可以使用Command模式来实现事件响应,打开新建模块的Constants.CommandNames类,在该类中,将该按钮鼠标响应事件命令名定义为一个常量:

    public const string TestButtonClick= "TestButtonClick";

    在ModuleController类中的ExtendToolStrip方法中,加入如下代码:

    WorkItem.Commands[Constants.CommandNames.TestButtonClick].AddInvoker(element, "Click");

    下面我们将介绍如何将Command绑定到一个方法

    五、书写Command处理函数

    在CAB中,我们使用AOP来实现Command方法绑定

    [CommandHandler(CommandNames.TestButtonClick)]
    public void OnTestButtonClick(object sender, EventArgs e)
    {
         MessageBox.Show("Successful");
    }

    CommandHandler是CAB中预订义的Attribute,用来申明事件处理方法。

    后续:

    当我们将模块相关的界面元素加到UIExtensionSite中时,如果每个模块自己添加的话,这要求每个模块开发人员需要了解Infrastructure模块的一些细节设计和实现,这对于一个大型应用开发来说是降低效率的,同时也会有很多的冗余代码。因此,我们可以将Infrastructure中的实现细节进行封装,以接口的形式暴露给各个模块,这样业务模块开发人员可以只关心自己本身的业务需求开发,而不用关心基础模块的具体实现,也减少出错的可能。

    第三章 创建业务模块

    • 在一个目录下(一般Source),点击鼠标右键,选择Smart Client Factory -> Add Business Module
    • 输入模块名字,确定。
    • 一个新的界面弹出,在该界面中,可以决定是否选中“Create an interface library for this module”。如果选中该选项,在创建业务模块的时候,会创建一个Module.Interface项目。
    • 点击“Finish”,完成业务模块的创建。
    • 创建完成后,在该业务模块项目中会生成如下几个目录:
      • Constants - 该目录包含四个类,分别是CommandNames, EventTopicNames, UIExtensionSiteNames, 和WorkspaceNames. 这四个类分别继承了Infrastructure模块中相对应的类。
      • Services
      • Views
    • 同时在ProfileCatalog.xml文件中加入如下配置。
      • <Modules>
              <ModuleInfo AssemblyFile="Module2.dll" />
            </Modules>
    • 创建向导会生成两个类,Module.cs和ModuleController.cs
    • 在ModuleController.cs中,向导自动添加了如下方法:
      • AddServices - 添加模块实现的服务
      • AddViews - 添加视图
      • ExtendMenu - 添加菜单选项
      • ExtendToolStrip - 添加快捷按钮

    Module和ModuleController的详细设计参考后续文档。

    第二章 创建SmartClient项目工程

    • Menu->file->new->project
    • 展开Guidance packages,选择smart client development
    • 输入项目名及目录,点击ok

    • 选择CAB编译好的DLL目录

            Microsoft.Practices.CompositeUI.dll

            Microsoft.Practices.CompositeUI.WinForms.dll

              Microsoft.Practices.ObjectBuilder.dll

    每个Smart Client 项目都需要上述的三个CAB dll文件。

    • 选择Enterprise Library DLL目录

    Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.dll

    Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.dll

    Microsoft.Practices.EnterpriseLibrary.Logging.dll

    Microsoft.Practices.EnterpriseLibrary.Common.dll

    Smart Client application use these dlls in Enterprise Library to handle Exception and Logging.

    • 输入该项目的“根命名空间 namespace”
    • 选中第一选项可以决定是否创建一个独立项目来定义Shell的layout

    • 点击Finish
    • 此时,Smart Client 项目创建成功。

    第一章 .Net CAB&SCSF 开发包的安装

    第一步:安装Visual Studio 2005 for C#

    第二步:安装Guidance Automation Extensions - June 2006

    第三步:安装Guidance Automation Toolkit - June 2006

    第四步:安装Enterprise Library for .NET 2.0

    第五步:安装CAB 1.0

    安装完毕,用VSS2005打开Composite项目,编译Source。

    第六步:安装SCSF - June 2006

    选择CAB编译后DLL目录