1.1. Commands
1.1.1. 类关系图
图 2‑6 主要类关系图
说明:
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. 接口定义
图 2‑7 ICommandAdapterMapService
其中CommandAdapterMapService是CAB提供的默认实现。
1.1.3. 接口协作
图 2‑8接口协作
说明:
1. 在Register时,是根据具体类型添加相应的CommandAdapter,比如
图 2‑9 注册自定义的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运行程序了。
<script type="text/javascript">function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}</script>
<script src="http://wz.csdn.net/javascripts/vote.js" type="text/javascript"></script>
Composite UI Application Block 之自定义UIElement(二)
<script src="http://blog.csdn.net/count.aspx?ID=2069975&Type=Rank" type="text/javascript"></script> 目标:让我们建立一个树形结构菜单,具体的说用一个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 == null) throw 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 == null) throw 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(this, new 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 == null) return; // 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
...
{
IUIElementManagerFactory Members#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
IUIElementAdapterFactory Members#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);
}