Composite UI Application Block(Cab)实例学习资料收集

来源:互联网 发布:卢甘斯克共和国 知乎 编辑:程序博客网 时间:2024/05/18 19:20

1.1. Commands

1.1.1.    类关系图

26 主要类关系图

说明:

1.         Command定义了一个命令,如“FileExit”,它跟一系列的CommandAdapter相关,好比一个命令可以从多个地方下发(菜单或工具栏等等)。

2.         Command持有一个ICommandAdapterMapService接口,也正是这个接口,通过void Register(Type invokerType, Type adapterType)完成了后期的扩展,比如添加自定义的CommandAdapter

3.         在各个CommandAdapter中触发的事件最终都是调用Command中的事件处理程序,这样的设计很显然是为了保持命令的一致性,如果菜单和工具栏上同样的命令,操作却不一样,那就神奇了J,请看Command中的代码:

            public virtual void AddCommandAdapter(CommandAdapter adapter)

           {

                 adapter.ExecuteCommand += this.OnExecuteAction;

                 adapter.BindCommand(this);

                 adapters.Add(adapter);

           }

1.1.2.    接口定义

27 ICommandAdapterMapService

其中CommandAdapterMapServiceCAB提供的默认实现。

1.1.3.    接口协作

28接口协作

说明:

1.         Register时,是根据具体类型添加相应的CommandAdapter,比如

29 注册自定义的CommandAdapter

因此在调用AddInvoker时传入ClickableTreeNode类型实例时,将会调用到TVClickableNodeCommandAdapter进行事件的注册。

WindowsFormsApplication基础类中默认提供了两种Adapter

private void RegisterCommandAdapters()

           {

                 ICommandAdapterMapService mapService = RootWorkItem.Services.Get<ICommandAdapterMapService>();

                 mapService.Register(typeof(ToolStripItem), typeof(ToolStripItemCommandAdapter));

                 mapService.Register(typeof(Control), typeof(ControlCommandAdapter));

           }


解Composite UI Application Block的基本应用
理解容器、WorkItem、Shell的概念
1.1.2                    步骤
1、新建Windows应用程序项目 CustomerDemo。引用Microsoft.Practices.CompositeUI.dll、Microsoft.Practices.CompositeUI.WinForms.dll、Microsoft.Practices.ObjectBuilder.dll、WinFormsUI.Docking.dll、DSS.CompositeUI.WinForms.dll项。
2、更改Form1类为CustomerForm类。在工具箱的选择项中添加对DSS.CompositeUI.WinForms.dll库的引用。将控件DockWorkspace添加到CustomerForm上,设置其属性Name为MainWorkspace,DocumentStyle为DockingWindow,Dock为Fill。
3、新建CustomersWorkItem类,在该类中引用命名空间Microsoft.Practices.CompositeUI,使CustomersWorkItem为WorkItem的子类。
4、新建MainApplication类,在该类中引用命名空间Microsoft.Practices.CompositeUI、Microsoft.Practices.CompositeUI.WinForms,使其继承自FormShellApplication,使MainApplication的Shell为CustomerForm,主WorkItem为CustomersWorkItem。如下所示:
class MainApplication:FormShellApplication<CustomersWorkItem,CustomerForm>
5、更改Program类Main方法的内容:new MainApplication().Run();F5运行。
1.2           练习二
1.2.1                    目的
了解Module的设计
了解如何向壳中添加自定义模型
1.2.2                    步骤
1、在练习一的基础上,新建Windows控件库CustomerModule,删除掉原有的类UserControl1.cs。添加Microsoft.Practices.CompositeUI.dll、Microsoft.Practices.CompositeUI.WinForms.dll、Microsoft.Practices.ObjectBuilder.dll、DSS.CompositeUI.WinForms.dll、WinFormsUI.Docking.dll项
2、参照案例中的View。添加两个窗体CustomerListForm、CustomerDetailForm两个窗体,引用WinFormsUI、Microsoft.Practices.CompositeUI、Microsoft.Practices.ObjectBuilder命名空间,使他们都继承自DockContent类。
 

为CustomerListForm添加ListBox控件,设置ListBox控件的属性Name为customerList,Dock为Full。将CustomerListForm的属性ShowHint为DockLeft。
 
为CustomerDetailForm添加四个标签和四个文本框。
3、添加新类CustomerWorkItem,声明其为Public的,添加对Microsoft.Practices.CompositeUI和Microsoft.Practices.CompositeUI.WinForms、Microsoft.Practices.CompositeUI.SmartParts命名空间的引用,使其作为WorkItem的子类。在CustomerWorkItem中,添加customerListForm、customerDetailForm两个窗体的实体:
CustomerListForm customerListForm;
CustomerDetailForm customerDetailForm;
重写OnRunStarted方法:
protected override void OnRunStarted()
{
      base.OnRunStarted();
      IWorkspace workSpace = Workspaces["MainWorkspace"];
      customerListForm=this.SmartParts.AddNew<CustomerListForm>();
      customerDetailForm = this.SmartParts.AddNew<CustomerDetailForm>();
      workSpace.Show(customerListForm);
      workSpace.Show(customerDetailForm);
}
通过CustomerWorkItem组织窗体的显示。
4、添加新类CustomerModule,声明其为Public的,添加对Microsoft.Practices.CompositeUI和Microsoft.Practices.CompositeUI.WinForms命名空间的引用。使其作为ModuleInit的子类。在CustomerModule类中,添加mainWorkItem成员从容器中获取主WorkItem。
private WorkItem mainWorkItem;
[ServiceDependency]
public WorkItem MainWorkItem {
    set { mainWorkItem = value; }
}
重写load方法:
public override void Load()
{
     base.Load();
     CustomerWorkItem customerWorkItem=mainWorkItem.Items.AddNew<CustomerWorkItem>();
     customerWorkItem.Run();
}
当Module被调用时,Load方法被执行。
5、编译项目CustomerModule,生成CustomerModule.Dll程序集,将其Copy到CustomerDemo项目的生成目录下。或者更改CustomerModule的生成目录为CustomerDemo项目的生成目录。
6、在CustomerDemo项目下添加ProfileCatalog.xml文件。设置该文件的复制到输出目录为始终复制,并使CustomerModule.Dll作为模块集合的一部分:
<?xml version="1.0" encoding="utf-8" ?>
<SolutionProfile xmlns="http://schemas.microsoft.com/pag/cab-profile">
  <Modules>
    <ModuleInfo AssemblyFile="CustomerModule.dll"/>
  </Modules>
</SolutionProfile>
7、编译解决方案,F5运行。
1.3           练习三
1.3.1                    目的
了解View、Controller、Model的概念
1.3.2                    步骤
1、在CustomerModule下添加Model中的实体类:Customer:
public class Customer
{
        private string firstName;
        private string lastName;
        private string address;
        public Customer(string firstName, string lastName, string address, string comments)
        {
            this.firstName = firstName;
            this.lastName = lastName;
            this.address = address;
        }
        public string FullName{get { return firstName + " " + lastName; }}
        public string LastName {
            get { return lastName; }
            set { lastName = value; }
        }
        public string FirstName {
            get { return firstName; }
            set { firstName = value; }
        }
        public string Address {
            get { return address; }
            set { address = value; }
        }
    }
2、为CustomerDetailForm窗体添加BindingSource控件,设计其属性Name为customerBindingSource。设置其DataSource属性,在弹出的对话框中选择"添加项目数据源":
 
选择对象,点击下一步,将对象设置为Customer类。同时分别设置每个TextBox的DataBindings的Text属性为Customer类的Address、LastName、FirstName和FullName属性。
为CustomerDetailForm添加Customer属性:
private Customer customer;
public Customer Customer {
     get { return customer; }
     set {
           if (this.customer != value) {
              customer = value;
              this.CustomerBindingSource.Clear();
              this.CustomerBindingSource.Add(value);
           }
     }
}
3、为CustomerWorkItem类添加ShowCustomerDetails方法
public void ShowCustomerDetails(Customer customer)
{
     customerDetailForm.Customer = customer;
}
4、添加分析类CustomerController,引用Microsoft.Practices.CompositeUI命名空间。使其继承自Controller:
public class CustomerController : Controller
同时添加Controller所需要具有的两个职能:显示客户列表GetCustomers,显示客户详细信息ShowCustomerDetails。
显示客户列表GetCustomers:
public List<Customer> GetCustomers()
{
    List<Customer> customers = new List<Customer>();
    customers.Add(new Customer("Jesper", "Aaberg", "One Microsoft Way, Redmond WA 98052", "CAB Rocks!"));
    customers.Add(new Customer("Martin", "Bankov", "One Microsoft Way, Redmond WA 98052", "This is awesome"));
    customers.Add(new Customer("Shu", "Ito", "One Microsoft Way, Redmond WA 98052", "N/A"));
    customers.Add(new Customer("Kim", "Ralls", "One Microsoft Way, Redmond WA 98052", "N/A"));
    customers.Add(new Customer("John", "Kane", "One Microsoft Way, Redmond WA 98052", "N/A"));
    return customers;
 }
显示客户详细信息ShowCustomerDetails。
public void ShowCustomerDetails(Customer customer)
{
     customerWorkItem.ShowCustomerDetails(customer);
}
customerWorkItem是CustomerModule中CustomerWorkItem的实例。该实例来自于CompositeUI Application Block的容器。
private CustomerWorkItem customerWorkItem;
 [ServiceDependency]
 public CustomerWorkItem CustomerWorkItem
 {
     set { customerWorkItem = value; }
}
将上述代码添加到CustomerController中,customerWorkItem会自动从容器中直接获取。
5、为CustomerListForm类添加CustomerController的实例:
private CustomerController controller;
[CreateNew]
public CustomerController Controller {
    set { controller = value; }
}
当窗体加载的时候,我们需要将客户的信息添加到CustomerListForm中:
protected override void OnLoad(EventArgs e)
{
     base.OnLoad(e);
     customerList.DataSource = controller.GetCustomers();
     customerList.DisplayMember = "FullName";
     customerList.SelectedIndexChanged += new EventHandler(customerList_SelectedIndexChanged);
}
同时设置当员工选择不同客户时执行的函数:
void customerList_SelectedIndexChanged(object sender, EventArgs e)
{
    controller.ShowCustomerDetails((Customer)this.customerList.SelectedValue);
}
6、F5运行。
1.4           练习四
1.4.1                    目的
了解State的用法
1.4.2                    步骤
1、为CustomerController添加Customers属性,需要添加System.Windows.Forms命名空间
[State("Customers")]
public List<Customer> customers
{
    get { return (List<Customer>)State["Customers"]; }
    set
    {
        try{
           if ((value != null) && (State != null))
           {
                State["Customers"] = value;
           }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace);
       }
   }
}
更改GetCustomers方法:
public void GetCustomers()
{
     customers.Add(new Customer("Jesper", "Aaberg", "One Microsoft Way, Redmond WA 98052", "CAB Rocks!"));
     customers.Add(new Customer("Martin", "Bankov", "One Microsoft Way, Redmond WA 98052", "This is awesome"));
     customers.Add(new Customer("Shu", "Ito", "One Microsoft Way, Redmond WA 98052", "N/A"));
     customers.Add(new Customer("Kim", "Ralls", "One Microsoft Way, Redmond WA 98052", "N/A"));
     customers.Add(new Customer("John", "Kane", "One Microsoft Way, Redmond WA 98052", "N/A"));
}
2、为CustomerListForm类添加Customers属性
private List<Customer> customers = null;
[State]
public List<Customer> Customers
{
    set { customers = value; }
}
更改CustomerListForm的OnLoad方法
base.OnLoad(e);
controller.GetCustomers();
customerList.DataSource = Customers;

3、为CustomerWorkItem的OnRunStarted方法添加代码
State["Customers"] = new List<Customer>();
base.OnRunStarted();
… …
4、F5运行
1.5           练习五
1.5.1                    目的
系统结果的解藕
理解在系统中的分布情况
1.5.2                    步骤
1、新建一个类库项目DomainModel,将Customer提取出来,成为领域模型专用库。因为领域模型对象需要在客户端和服务器两端移动,所以需要将其标识为可序列化的:
[Serializable]
public class Customer
2、删除CustomerModel中原有的Customer类,添加对DomainModel的引用,在原来需要使用Customer的类中添加对DomainModel的引用。
3、将CustomerController类中GetCustomers方法获取数据的部分提出出来形成服务。新建一个类库项目Interface,添加对DomainModel项目的引用,同时引用DSS.Core.dll。新建ICustomerService接口,声明引用DomainModel命名空间,为ICustomerService接口添加GetCustomers方法声明:
[ServiceInfomation("CustomerSerivice", ServiceType.Infrustructure)]
public interface ICustomerService {
   List<Customer> GetCustomers();
}
4、新建类库项目Implement,添加对DomainModel和Interface项目的引用。将class1.cs类更改为CustomerService,声明它对DomainModel和Interface命名空间的引用。使其继承自ICustomerService,完成对GetCustomers方法的实现:
public class CustomerService:ICustomerService
{
   public List<Customer> GetCustomers()
   {
      List<Customer> customers = new List<Customer>();
      customers.Add(new Customer("Jesper", "Aaberg", "One Microsoft Way, Redmond WA 98052", "CAB Rocks!"));
      customers.Add(new Customer("Martin", "Bankov", "One Microsoft Way, Redmond WA 98052", "This is awesome"));
      customers.Add(new Customer("Shu", "Ito", "One Microsoft Way, Redmond WA 98052", "N/A"));
      customers.Add(new Customer("Kim", "Ralls", "One Microsoft Way, Redmond WA 98052", "N/A"));
      customers.Add(new Customer("John", "Kane", "One Microsoft Way, Redmond WA 98052", "N/A"));
      return customers;
  }
}
5、为CustomerModel项目添加对Interface项目的引用。为CustomerController类添加Interface命名空间。添加ICustomerService的实例:
private ICustomerService service;
[ServiceDependency]
public ICustomerService Service
{
    set { service = value; }
}
更改CustomerController的GetCustomers方法:
public void GetCustomers()
{
      Customers.AddRange(service.GetCustomers());
}
6、为CustomerDemo项目中添加App.Config文件,将Interface和Implement两个实现库Copy到CustomerDemo的运行目录下。将这两个库的服务添加到App.Config文件中:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="CompositeUI"
      type="Microsoft.Practices.CompositeUI.Configuration.SettingsSection,
            Microsoft.Practices.CompositeUI"
      allowExeDefinition="MachineToLocalUser" />
  </configSections>
  <CompositeUI>
    <services>
      <add serviceType="Interface.ICustomerService, Interface"
           instanceType="Implement.CustomerService, Implement"/>
    </services>
  </CompositeUI>
</configuration>
7、F5运行

Composite UI Application Block学习笔记之Event Broker

     Composite UI Application Block着重于将应用逻辑和界面分开,让应用系统具备更清晰的结构,更强的扩展性、可移植性。在曹严明先生的讲座中,提及到了关于应用CAB开发的几个指导性原则:

  • 将 views (SmartPart)设计为独立于 controllers 的单元
  • 共享模块状态
  • 共享基础服务
  • 封装用例 - 重用
  • 降低模块间的依赖性
  • 尽量使用 events, services, and interfaces

     我在学习的过程中也理解到以上原则的重要性和指导性,在我学习模块状态和Event Broker的过程中,也将上述部分原则做了特意的应用。那么我们还是通过一个实例来学习Event Broker和这些原则。

一、文中有关术语

    下面这些术语是CAB中常用到的,以下的解释仅是我个人的理解,不敢保证完全准确,园子里的朋友请指教。

    Event Broker:事件代理,通过事件源和订阅事件源来达成对象之间的协作。

    Event Publisher: 事件发布者,在CAB里是一个用属性EventPublication修饰的事件对象,提供特定的URL给Event Subscriber订阅。

    Event Subscriber: 事件订阅者,在CAB里是一个用属性EventSubscription修饰的方法,根据修饰提供的URL自动寻找事件发布者。Publisher和Subscriber之间由主题(由URL决定),消息(特定的 EventArgs),事件域(来确定是全局事件还是局部事件)来达成一致。其实这也是观察者模式的具体实现。

    WorkItem:代表一个用例,也可以看成是某个业务完成的过程,它包含在WorkSpace中,服务于Service Agents(服务代理),并且加载其状态。创建其他组件或者视图,CAB来创建controller.组件共享WorkItem的状态,并且可以通过状态来控制用例的生命周期。

    WorkItem State:状态,实际上是把业务对象或者业务对象的属性,通过WorkItem State共享出来,方便其他业务对象或者视图访问。

二、体验Event Broker应用

    讲了这么多有关Event Broker的理论和概念了,我们还是通过一个简单的例子来体验Event Broker这种实现模式的优越性吧。

1.应用场景

     平时我们在开发过程中碰到最多的例子大概就是,一个业务对象数据集要通过dataGrip,ListBox甚至Chart控件等将其表现出来了。今天,我在学习笔记里也以这个例子来阐述Event Broker,在开发中带来的好处。

    场景是这样的:某人事信息管理软件要求输入人员的性别和姓名,并且能将输入的人员在通过表格和列表框的形式表现出来,同时录入人员的男女比例要能适时的通过饼图显示。

2.分析场景,确定开发模式

a.需求中涉及到的唯一业务对象是人员,具有性别和姓名两个属性。为了简单起见我们可以建立数据集来代替该对象。

b.需求要求能输入姓名、性别,我们可以用文本框和下拉框来完成信息采集。

c.需求要求人员信息,通过表格,ListBox和饼图来显示,我们可以在VS2005中用DataGrid、ListBox、ReportView来实现此项需求。

d.由于业务对象单一,而信息表现却又多个,适合用观察者模式进行开发。我们便采用CAB中的Event Broker作为重要的实现手段。

3.建立应用程序

第一步:新建项目

     启动VS2005,新建Windows Application,添加以下引用:

Microsoft.Practices.CompositeUI

Microsoft.Practices.CompositeUI.WinForms

Microsoft.Practices.ObjectBuiler

Microsoft.Practices.CompositeUI.Utility

Microsoft.Practices.CompositeUI.WinForms

第二步:建立数据集

    右击项目文件夹,添加新项,选择数据集,建立用户信息数据集(没有通过代码创建,主要是为了设计报表方便)。为数据集添加DataTable1的表,为DataTable1添加列Sex和Name。

第三步:绘制界面

     在VS2005默认生成的Form1上建立饼图、DataGrid、ListBox和相关相关控件,具体操作我在此略过,最终效果如下图:



第四步:修改入口程序

    为了让程序能使用CAB,我们必须修改程序的入口类Program.cs。最终修改结果如下:
   

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Data;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.WinForms;

namespace TestReport
{
  
class Program : FormShellApplication<WorkItem, Form1>
{
/// <summary>
/// The main entry point for the application.
/// </summary>

[STAThread]
static void Main()
{
  
new Program().Run();
}


protected override void BeforeShellCreated()
{
  
base.BeforeShellCreated();
//共享状态,通过"dataset"关键字访问
  RootWorkItem.State["dataset"= new DataSet1();
}

}

}


   需要注意的是:为了能使用WorkItem的State,在Shell创建之前必须给共享的状态赋初值,否则在访问该状态时将出现状态没有创建实例的运行时错误。本例中就是加入以下代码:

protected override void BeforeShellCreated()
{
  
base.BeforeShellCreated();
  RootWorkItem.State[
"dataset"= new DataSet1();
}


第五步:建立controller

建立controller负责用户信息添加,建立事件源。添加类文件,命名为Form1Controller,将该类从controller继承。如下代码所示:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.EventBroker;
using Microsoft.Practices.CompositeUI.Utility;
using System.Data;
namespace TestReport
{
 
public class Form1Controller: Controller
 
{
  }


}


在controller中公布一个事件发布者,通过"topic://TestReport/DataRowAdded"来标识Publisher,默认的事件域为全局。也可以通过PublicationScope枚举来设置事件的作用域。事件作用域有以下三种:

PublicationScope.WorkItem :仅作用于引发当前发布的WorkItem实例

PublicationScope.Global:作用于引发当前发布的WorkItem所有实例

PublicationScope.Descendants:仅作用于引发当前发布的WorkItem实例,以及该WorkItem的任何级别的子WorkItem实例。

本例通过以下代码发布事件:

[EventPublication("topic://TestReport/DataRowAdded")]
public event EventHandler<DictionaryEventArgs> DataRowAdded;

controller中主要来实现业务逻辑,于是我们需要添加一个方法AddNewRow(int sex, string name),用来实现人员信息的添加,代码如下:


private DataSet1 ctldataset;
 
//controller的AddNewRow方法,引发事件DataRowAdded
public void AddNewRow(int sex, string name)
{
if (DataRowAdded != null)
{
  DataRow myRow 
= ctldataset.DataTable1.NewRow();
  myRow[
0= sex;
  myRow[
1= name;
  ctldataset.DataTable1.Rows.Add(myRow);
  ctldataset.AcceptChanges();

  DictionaryEventArgs args 
= new DictionaryEventArgs();
  args.Data[
"dataRow"= myRow;
  DataRowAdded(
this, args);

  State.RaiseStateChanged(
"dataset", myRow);
}

}


   大家请注意下面代码,其实是定义了一个DictionaryEventArgs参数,并且将当前添加的行对象作为该参数的值。当DataTable1中行添加后,我们引发事件DataRowAdded(this, args)。  此时,事件源被触发了,订阅者就可以接收到该事件广播了。

DictionaryEventArgs args = new DictionaryEventArgs();
args.Data["dataRow"] = myRow;
DataRowAdded(this, args);

   到此,我们已经完成了事件源的创建和发布,为了达到演示的效果,我们还需要实现共享WorkItem State来广播事件。如以下代码:


[State("dataset")]
public DataSet1 CtlDataSet 
{
set 
{
  ctldataset 
= value;
}

}

public new State State
{
  
get return base.State; }
}


    我们注意到[State("dataset")]这行代码,它是用来表示WorkItem的属性CtlDataSet,将通过[State("dataset")]共享出去,同时当CtlDataSet改变时,通过代码State.RaiseStateChanged("dataset", myRow),来引发状态改变事件,其他地方就可以得到该事件的委托。

第六步:整合界面和controller
    我们回到Form1.cs编辑代码。为了让界面和controller和界面结合,我们将controller作为界面对象的一个属性,用以下代码实现:

//定义该窗体相关的Controller
private Form1Controller controller;

//将该窗体相关的Controller标记为自动创建实例
[CreateNew]
public Form1Controller Controller
{
  
set { controller = value; }
}

 

   为添加按钮加入代码,实现添加一个人员信息:

private void btn_AddToTable_Click(object sender, EventArgs e)
{
if((this.textBox1.Text.Trim().Length >0))
{
 
this.controller.AddNewRow(this.cmbSex.SelectedIndex, this.textBox1.Text.Trim());

}

}

   还有为了让Grid和report view能够同步显示人员信息,我们需要订阅由topic://TestReport/DataRowAdded标示的事件:

[EventSubscription("topic://TestReport/DataRowAdded")]
public void OnCustomerAdded(object sender, DictionaryEventArgs e)
{
this.dataGridView1.DataSource = ((DataSet1)this.controller.State["dataset"]).DataTable1.DefaultView;
this.DataTable1BindingSource.DataSource = ((DataSet1)this.controller.State["dataset"]).DataTable1.DefaultView;
this.reportViewer1.RefreshReport();
}

 

   这样每添加一个人员,Grid和Reoport View就能适时更新自身表现了,这就是Event Broker的实现方式,简单并且简洁。前面我们还提到了通过共享状态来实现视图和业务对象的关联,在本例中也提供实现。

首先,在FormLoad事件中订阅StateChanged事件:

 

private void Form1_Load(object sender, EventArgs e)
{
this.controller.State.StateChanged += new EventHandler<StateChangedEventArgs>(State_Changed);
}

然后,通过代码更新List状态:

void State_Changed(object sender, StateChangedEventArgs e)
{
this.listBox1.DataSource = ((DataSet1)this.controller.State["dataset"]).DataTable1.DefaultView;
this.listBox1.DisplayMember = "Name";
this.listBox1.ValueMember = "Name";
}

好了,到此我们的例程已经大功告成,最终的运行效果如下图:


本文相关代码通过此连接下载:/Files/hyphappy/TestReport.rar

Composite UI Application Block 学习笔记之Commands

   听了曹严明先生的《组合型智能客户端应用 With Composite Application Block》的讲座后,对CAB有了一个初步的理解,同时感觉CAB将大有用武之地。于是,本人从微软网站http://practices.gotdotnet.com/projects/cab下载了源代码,开始研究。

    这个学习笔记将主要讲述CAB中Commands的应用,以及一些本人的疑惑,期望园子里的朋友予以指点。

一、何谓Commands.

    Commands是CAB程序集里一个重要的对象,它主要用来关联控件、WorkSpace和业务逻辑,也就是让一个命令可以被多个控件的事件引发。

一般的情况下可以通过以下代码关联一个命令和控件的事件:

    Commands[strCommandName].AddInvoker(objControl, strEventName);

二、建立测试Commands的程序

1.打开VS2005,新建Windows Application项目。

2.添加以下引用

  • Microsoft.Practices.CompositeUI;
  • Microsoft.Practices.CompositeUI.WinForms;
  • Microsoft.Practices.ObjectBuilder

3.此时VS将自动产生program.cs,Form1.cs。将Form1.cs命名为TestForm。

4.给TestForm加入菜单。命名为menuStrip1。依次加入菜单子项,名称如下:

  • AddNew
  • SaveFileTool
  • DeleteFile

5.重命名program.cs为CommandsApplication.cs,并且将内容修改成以下形式:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.Commands;
using Microsoft.Practices.CompositeUI.WinForms;
using Microsoft.Practices.CompositeUI.UIElements;
class CommandsApplication : FormShellApplication<MainWorkItem, TestForm>
{
/// <summary>
/// The main entry point for the application.
/// </summary>

[STAThread]
public static void Main()
{
  
new CommandsApplication().Run();
}

}


 

修改时要注意将static class 修改成 class,否则无法通过编译,提示类似错误1所示的错误信息

Static class 'TestUIBlock.CommandsApplication' cannot derive from type 'Microsoft.Practices.CompositeUI.WinForms.
FormShellApplication<TestUIBlock.MainWorkItem,TestUIBlock.TestForm>'.
Static classes must derive from object.CommandsApplication.cs 13 40 TestUIBlock。

   如果将static class 修改成 public class,也会无法通过编译,提示类似错误2的信息:

Inconsistent accessibility: base class 'Microsoft.Practices.CompositeUI.WinForms.
FormShellApplication<TestUIBlock.MainWorkItem,TestUIBlock.TestForm>'
is less accessible than class 'TestUIBlock.CommandsApplication' E:/WorkSpace/Projects/TestUIBlock/TestUIBlock/TestUIBlock/CommandsApplication.cs 13 18 TestUIBlock。

    错误1很好理解,就是静态的类无法从类型继承,但是错误2就让我有些费解,在CAB的Quick Start中就是这样写的。和我新建立的不同区别是,我是通过引用dll来添加引用的,Quick Start是直接引用解决方案中的项目。查阅MSDN帮助对Inconsistent accessibility的解释,好像也不能解释我遇到的这个问题。

还有就是要将static void Main()修改成 public static void Main(),同时删除VS2005默认生成的代码。

6.建立Controller,也就是命令。通常业务行为都放到这个类里。

     新建类MainControler文件,将类MainControler从Controller继承。

     编写过程,并且以属性[CommandHandler(strKey)]进行修饰,其中strKey是Commands集合中注册的命令关键字。示例过程写法如下:


 

[CommandHandler("AddNew")]
public void AddNewFileHandler(object sender, EventArgs e)
{
  MessageBox.Show(
"You Had Added A File");
}


 

7.建立WorkItem。

    新建类MainWorkItem,并且从WorkItem继承。

   重载Run()方法,代码如下所示:


 

using System;
using System.Windows.Forms;
using System.Collections.Generic;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.UIElements;
using Microsoft.Practices.CompositeUI.WinForms;
using Microsoft.Practices.ObjectBuilder;
using System.Text;

namespace TestUIBlock
{
 
class MainWorkItem:WorkItem
 
{
  
public override void Run()
  
{
    Items.AddNew
<MainControler>();
    Activate();}

  }

 }

}

 

    在Quick Start中是将通过XML文件,将菜单项记录下来,和Commands集合映射,然后动态加载到主菜单上的。由于我们已经在TestForm中创建了菜单项,所以可以去掉动态加载到主菜单的操作,但是和Commands集合映射是必不可少的。可是在MainWorkItem中我不知道如何访问TestForm实例,去获取每一个菜单项(哪位大哥知道的话,请告诉我),于是我只好将映射到Commands部分的代码放到CommandsApplication中。

    8.建立菜单项到Commands的映射。

    建立方法ProcessCommandMap(IUIElementService uiService):


 

private void ProcessCommandMap(IUIElementService uiService)
{

foreach (ToolStripMenuItem item in ((ToolStripMenuItem)Shell.MainMenuStrip.Items["FileMenue"]).DropDownItems)
{
  
this.RootWorkItem.Commands[item.Name].AddInvoker(item, "Click");
}


     这里是通过菜单项的名称和命令的名称进行映射的。我们也可以将多个控件的事件映射到同一个命令如:

Button btn1 = new Button();
btn1.Text 
= "test";
btn1.Name 
= "btn1";

Panel panel1 
= (Panel)Shell.Controls["panel1"];
panel1.Controls.Add(btn1);
this.RootWorkItem.Commands["AddNew"].AddInvoker(btn1, "Click");

     不知道看过Commands QuikStart的朋友发现没有,通过Service的RegisterUIExtensionSite方法注册菜单的根后,就可以直接通过Service的Add方法将子菜单添加到父菜单。如以下代码:

IUIElementService uiService = RootWorkItem.Services.Get<IUIElementService>();
ToolStripMenuItem fileItem 
= (ToolStripMenuItem)Shell.MainMenuStrip.Items["File"];
uiService.RegisterUIExtensionSite(
"File", fileItem.DropDownItems);


 

 

三、程序的执行顺序

通过调试程序我们不难发现程序是按照以下顺序执行的:

1.通过入口程序CommandsApplication调用 new CommandsApplication().Run();
2.初始化TestForm
3.创建Shell后建立控件和命令的映射,执行AfterShellCreated方法。
4.运行MainWorkItem,激活主窗体。
文中完整代码下载:/Files/hyphappy/TestUIBlock.rar

 

    我曾经尝试通过同样的方法在一个Panel中加入一个Button,但是失败了,原因是没有注册对应的UIElementManagerFactory。

9.至此我们已经做好了一个Commands的例子,可以通过F5运行程序了。


 Composite UI Application Block 之自定义UIElement(二)  
目标:让我们建立一个树形结构菜单,具体的说用一个TreeView来显示菜单。先看效果图

 

1、首先我们需要建立一个自定义的TreeView,实现单击事件。

 

namespace CustomUIElements
{
    
public delegate void NodeClickedEvent(object sender, TreeNode nodeClicked);

    
public class ClickableTreeNode : TreeNode
    
{
        ClickableTreeView _parent 
= null;
        
public event EventHandler NodeClick;

        
private bool _needsSetup = false;
        
public bool NeedsSetup
        
{
            
get return _needsSetup; }
        }

        
public ClickableTreeNode()
            : 
base()
        
{
            _needsSetup 
= true;
        }


        
public new ClickableTreeView TreeView
        
{
            
get return _parent; }
        }


        
public void SetUp(ClickableTreeView tv)
        
{
            ClickableTreeView clickable 
= tv as ClickableTreeView;
            
if (clickable == nullthrow new Exception("The clickable tree node was added to a non-clickable tree view");

            _parent 
= clickable;
            
// register for the node clicked event, when *a* node gets clicked
            clickable.NodeClicked += new NodeClickedEvent(clickable_NodeClicked);

            _needsSetup 
= false;
        }


        
public ClickableTreeNode(ClickableTreeView tv) : base()
        
{
            ClickableTreeView clickable 
= tv as ClickableTreeView;
            
if (clickable == nullthrow new Exception("The clickable tree node was added to a non-clickable tree view");

            _parent 
= clickable;
            
// register for the node clicked event, when *a* node gets clicked
            clickable.NodeClicked += new NodeClickedEvent(clickable_NodeClicked);
        }


        
void clickable_NodeClicked(object sender, TreeNode nodeClicked)
        
{
           
// if the node is actually us, then fire the NodeClick event...
            if ((nodeClicked as ClickableTreeNode) == this)
            
{              
                
if (NodeClick != null)
                    NodeClick(
thisnew EventArgs());
            }
              
        }

    }


    
public class ClickableTreeView : TreeView
    
{
        
public event NodeClickedEvent NodeClicked;

        
public ClickableTreeView() : base()
        
{
            
this.MouseClick += new MouseEventHandler(ClickableTreeView_MouseClick);
        }


        
void ClickableTreeView_MouseClick(object sender, MouseEventArgs e)
        
{
            TreeViewHitTestInfo inf 
= HitTest(e.X, e.Y);
            
if (inf.Node == nullreturn// didnt click a node...

            
// we clicked a node, so notify that one was clicked...
            if (NodeClicked != null)
                NodeClicked(
this, inf.Node);
            
        }
       
    }

}

2、我们需要的是建立两个我们自己的类,分别实现IUIElementAdapterFactory和UIElementAdapter<T>。

 

 public class ClickableTreeViewAdapterFactory : IUIElementAdapterFactory
    
{
        
#region IUIElementManagerFactory Members

        
public IUIElementAdapter GetAdapter(object managedExtension)
        
{
            
if (managedExtension is ClickableTreeNode)
                
return new ClickableTreeViewAdapter(managedExtension as ClickableTreeNode);
            
else if (managedExtension is ClickableTreeView)
                
return new ClickableTreeViewAdapter(managedExtension as ClickableTreeView);
            
return null;
        }


        
#endregion


        
#region IUIElementAdapterFactory Members

        
public bool Supports(object uiElement)
        
{
            
if (uiElement is ClickableTreeNode)
                
return true;
            
else if (uiElement is ClickableTreeView)
                
return true;
            
else
                
return false;

        }


        
#endregion

    }

 

 

 public class ClickableTreeViewAdapter: UIElementAdapter<ClickableTreeNode>
    
{
        
private ClickableTreeView _managed = null;
        
private ClickableTreeNode _managedNode = null;
        
public ClickableTreeViewAdapter(ClickableTreeView element)
        
{
            _managed 
= element;
        }


        
public ClickableTreeViewAdapter(ClickableTreeNode node)
        
{
            _managedNode 
= node;
        }


        
protected override void Remove(ClickableTreeNode uiElement)
        
{
            
if (_managed != null)
            
{
                
if (!_managed.IsDisposed)
                
{
                    
if (_managed.Nodes.Contains(uiElement))
                        _managed.Nodes.Remove(uiElement);
                }

            }

            
else if (_managedNode != null)
            
{
                
if (!_managedNode.TreeView.IsDisposed)
                
{
                    
if (_managedNode.Nodes.Contains(uiElement))
                        _managedNode.Nodes.Remove(uiElement);
                }

            }

        }

    
        
protected override ClickableTreeNode Add(ClickableTreeNode item)
        
{
            
if (_managed != null)
            
{
                
if (item.NeedsSetup)
                    item.SetUp(_managed);
                _managed.Nodes.Add(item);
            }

            
else if (_managedNode != null)
            
{
                
if (item.NeedsSetup)
                    item.SetUp(_managedNode.TreeView);
                _managedNode.Nodes.Add(item);
            }


            
return item;
        }
        
    }

 

3、在初始化阶段注册我们自定义的界面工厂

 

        protected override void AfterShellCreated()
        
{
            
base.AfterShellCreated();

            IUIElementAdapterFactoryCatalog catalog 
= RootWorkItem.Services.Get<IUIElementAdapterFactoryCatalog>();
            catalog.RegisterFactory(
new ClickableTreeViewAdapterFactory());

            
// both main menus should have their items added to the root nodes collection
            RootWorkItem.UIExtensionSites.RegisterSite(UIExtensionConstants.MAINMENU, Shell.MainNavigationTree);

            
// Load the menu structure from App.config
            UIElementBuilder.LoadFromConfig(RootWorkItem, Shell.MainNavigationTree);
        }