【Visual Studio风格开发系列 - 可视化界面设计】利用 .NET Framework 2.0 创建并宿主自定义的设计(全部图)

(点击访问英文原文)

Microsoft .NET Framework 1.0提供了一个非常通用的设计时框架,不过没有提供所有实现代码来完成一个设计器,Visual Studio? .NET实现了所有的复杂逻辑,要第三方去重新实现这个复杂的逻辑。.NET Framework 2.0引入了一组类能够用于设计器的实现。


  理解.NET Framework怎么工作,非常重要的是要了解设计器是怎么使用的。设计器是负责管理设计界面上的组件的设计时期行为和表现的对象。框架关联设计时对象和运行时对象,为设计时组件提供了一个管道扩展运行时对象的行为。运行时,Form上的一个form和button这两个控件只是通过父子关系相关联,没有其他的对象来控制这些控件的生命周期。


Figure 2

  上面的图片看出设计时比较复杂,Form和button都有一个设计器相关联,两个对象都和Host容器相关联,host容器拥有这两个对象,host容器也提供服务---例如选取服务处理设计时的组件选取,并跟踪所选取的组件,UI服务用于显示对话,调用帮助系统和设计环境相联系。

  Host container有许多职责,包括创建组件、绑定组件到设计器和为组件和设计器提供服务。从持久化介质上加载组件和保存组件状态到持久化介质。Host container提供撤销、剪贴板功能和其他的服务等为实现鲁棒的设计器所依赖的功能。


Figure 3 Designer Hosting
 

  host container使用designer loader持久化设计器状态,designer loader使用序列化机制序列化组件。

  服务扩展

  The .NET Framework设计时框架是可扩展,提供的服务可用于实现各式各样的设计器。一个服务是提供对象可通过类型进行查询,典型的是你定义了服务的抽象类和接口和服务的实现。你能从service container中添加或删除服务。IDesignerHost是设计器主要的host接口,是个Service Container。服务是个组件间能共享的,正因如此,在创建和使用Service的时候必须遵循确定的规则。

  Services不被确保的,无论何时通过GetService方法请求一个服务(Services),你一定要检查返回的是否是个有效对象。并不是所有的服务在所有的平台上都是可用的,而且原来可用的服务未来不可能是可得。因此你的代码应当被写的降低优雅型,通常籍由需要某种服务而丢失某些特性,以防万一一个服务也得不到。

  如果你添加一个服务,记得在设计器的被disposed的时候移除他。设计器会时不时地创建和消毁,如果你没有去清除一个服务的话,旧的设计器就会遗留在内存中。

DesignSurface 和 DesignSurfaceManager


  .NET Framework 2.0引入了两个类DesignSurface 和DesignSurfaceManager.给设计器提供宿主及给设计器提供服务。DesignSurface是使用者所感知的设计器,他是UI使用者操纵改动设计时特征,DesignSurface 可能被当作一个独立的设计者使用或和DesignSurfaceManager结合使用为设计器应用程式提供多个DesignSurface。

  DesignSurface提供好几个设计时服务,大多数服务都能在服务容器中被覆盖,替换不可替换的服务是非法的,因为他们之间彼此仰赖.注意添加到Service Containe实现了接口IDisposabler的服务当DesignSurface 销毁的时候都会被销毁。



  除了提供缺省的服务,DesignSurface也提供了IDictionaryService,此服务提供一个使用关联键设置、检索和查找对象的简单接口。不可能替换这些服务因为在每个站点上无法替换这些服务。

  DesignSurfaceManager是设计器的容器,他提供通用的服务以处理在设计者,属性窗口和其他的全局对象之间的事件路由. 使用 DesignSurfaceManager 是可选择的, 不过如果你想需要有一组设计者窗口,推荐使用DesignSurfaceManager。

  DesignSurfaceManager也提供了几个设计时服务(see Figure 5).。每一个都能在Protected属性ServiceContainer(服务容器)中被覆盖。和DesignSurface相同,DesignSurfaceManager所有的 实现了接口IDisposabler的服务 当设计器应用程式销毁的时候都会被销毁。

  IDesignerEventService 是个特别地有用的服务. 当一个设计器变成活跃的时候 , 他允许一个设计器应用程式被通知到. IDesignerEventService 提供了一组设计器和全局对象的访问点, 例如属性窗口能够侦听到选择变化事件.


  宿主form


  为了示范一下宿主一个设计器是多么简单,我写了下面的简单代码来创建一个基本的视窗系统? Forms designer并显示他:

// Create the DesignSurface and load it with a form

DesignSurface ds = new DesignSurface();
ds.BeginLoad(typeof(Form));

// Get the View of the DesignSurface, host it in a form, and show it

Control c = ds.View as Control;
Form f = new Form();
c.Parent = f;
c.Dock = DockStyle.Fill;
f.Show();
  在这一个代码片断中,我已用Form方式装载 DesignSurface. 同样地,你能用拥有根设计器的所有组件装载 DesignSurface. 举例来说,你能改为装载 UserControl 或一个组件.


Figure 6 Hosting 视窗系统 Forms Designer

  提供下载的例子代码中有四种根组件:Form, UserControl, Component, and MyTopLevelComponent (一个图像设计器). 当你运行例子的时候,一个Shell UI 将会打开. 他包括一个工具箱,一个属性窗口, 一个tab Control来宿主设计器,一个Output window和一个Solution Explorer,如图6所示..使用菜单的File | New | Form 用窗口打开一个新的视窗系统 Forms Designer。这本质上就是使用上面所展示的代码加载一个设计器。和装载一个Form相比较,例子中还展示了怎么装载UserControl或组件。

  创建一个根组件,也就是创建一个设计器实现IRootDesigner接口,然后指定这个组件的designer相关联,根组件的视图属性将呈现给使用者。

  DesignSurface 提供的主要服务之一是 IDesignerHost,IDesignerHost是用于提供设计器和对类型、服务和事务控制的主要接口。他也用于创建和销毁组件。添加一个按钮到视窗系统 Forms designer所要做的工作就是从DesignSurface获得IDesignerHost接口并创建button,代码如图7
// Add a Button to the Form
IDesignerHost idh = (IDesignerHost)ds.GetService(typeof(IDesignerHost));
Button b = (Button)idh.CreateComponent(typeof(Button));
// Set the Parent of this Button to the RootComponent (the Form)
b.Parent = (Form)idh.RootComponent;
// Use ComponentChangeService to announce changing of the
// Form’s Controls collection */
IComponentChangeService icc = (IComponentChangeService) idh.GetService(typeof(IComponentChangeService));
icc.OnComponentChanging(idh.RootComponent, TypeDescriptor.GetProperties(idh.RootComponent)["Controls");
  ItoolboxUser指定设计器支持从Toolbox中增加控件到设计器,这意味着你确实需要一个实现ToolboxService的Toolbox,你能够用IToolboxUser接口把控件添加到根组件。例如:

/* Add a Button to the Form using IToolboxUser */

IDesignerHost idh = (IDesignerHost)ds.GetService(typeof(IDesignerHost));
IToolboxUser itu = (IToolboxUser)idh.GetDesigner(idh.RootComponent);
itu.ToolPicked(new ToolboxItem(typeof(Button)));

Figure 8 Custom RootDesigner Updates

  例子程式中双击Toolbox中的控件,控件被添加到自定义的根设计器,根设计器的视图中显示一个pie chart如图8所示,点击GraphStyle链接改动视图到bar graph.

   工具箱

  MyRootDesigner实现IToolboxUser接口,这个接口有两个方法:GetToolSupported and ToolPicked. 你能使用 GetToolSupported 过滤项目能被填加到设计器上的组件. 进入ToolboxItem 的 CreateComponents 方法 (如名字应用,负责创造组件) 调用的时候调用ToolPicked。

  既然我们已成功添加控件和组件到设计器,让我们来看一下怎么实现一个Toolbox。首先,你的工具箱需要实现 IToolboxService ?这一个服务被增加到服务容器,所有需要使用的所有人都能被存取。

  允许项目从工具箱通过老鼠或键盘的添加到设计器上,示例程式的工具箱处理KeyDown 和 MouseDown 事件。Enter键或鼠标双击事件, IToolboxUser.ToolPicked 被调用. 示例展示了鼠标单击拖动控件怎么序列化ToolboxItem 到 DataObject和DoDragDrop方法调用,鼠标mouse up事件IToolboxService.SerializeToolboxItem被调用,而且项目将会被增加到设计器.

  当一个控件或组件被添加到设计器,你能藉由实现 INameCreationService 提供一个制定的名字给组件,示例程式展示了CreateName, ValidateName, and IsValidName的代码实现。

 

  Multiple DesignSurfaces


  当管理多个DesignSurfaces,一个好主意是使用DesignSurfaceManager。他使得容易管理这些DesignSurfaces(注意 DesignSurfaceManager 的服务也是可得的到 DesignSurface.)

  调用DesignSurfaceManager.CreateDesignSurface将调用CreateDesignSurfaceCore,你能够重写这个函数去创建一个自定义的DesignSurface和增加服务。示例程式在类HostSurfaceManager通过重写这个函数创建了自定义的HostSurface:

protected override DesignSurface CreateDesignSurfaceCore(IServiceProvider parentProvider)
{
 return new HostSurface(parentProvider);
}

  你能通过HostSurfaceManager类的事件ActiveDesignSurfaceChanged更新output窗口,代码如下:

void HostSurfaceManager_ActiveDesignSurfaceChanged(object sender, ActiveDesignSurfaceChangedEventArgs e)
{
 Tool视窗系统.OutputWindow o = this.GetService(typeof(Tool视窗系统.OutputWindow)) as Tool视窗系统.OutputWindow;
 o.RichTextBox.Text += "New host added.\n";

   DesignerLoaders

  到目前为止我已实现了DesignSurfaces、宿主设计器、添加控件、Toolbox和存取服务,像 OutputWindow. 下一个步骤要持久化设计器。设计器载入程式如同你将会期待相同, 负责从持久化介质加载Designer form. 设计器载入程式只有少许的需求. 事实上,你能创建视窗系统 Forms designer的一个实例。

  除了载入设计器,设计器载入程式对设计结果的保存也是设计器的职责。因为保存是可选择的行为,一个设计者载入程式侦听改动来自设计器的改动事件,而且自动的保存这些状态。.

  .NET Framework 2.0引入两个新的类来自定义加载器:BasicDesignerLoader 和CodeDomDesignerLoader,示例应用举例说明两者的载入程式类型的实现。然而,如果你正在使用一个载入程式,他应该用来装载DesignSurface. 你将使用的 BeginLoad 代码片断当使用载入程式的时候应该看起来有点像下面的代码:

// Load it using a Loader
ds.BeginLoad(new MyLoader());
  DesignerLoader 负责载入 DesignSurface 的根组件而且创建所有组件. 当创造一个新的Form或所有其他的根组件的时候,载入程式只是装载他. 和从代码文件或一些其他的存储介质的载入,载入程式负责解析文件或存储而且再创建根组件的所有其他的必需组件.

  .NET Framework定义了一个抽象基类叫做DesignerLoader,用于加载和保存设计器到持久介质。基类是abstract,因此所有持久化模型都能使用这个类,不过,这也增加了实现类的复杂性。

  BasicDesignerLoader提供了除所有数据的持久格式外设计者载入程式的完全和通常的实现. 像 DesignerLoader ,他是abstract, 不处理关于持久化格式的所有事情. BasicDesignerLoader处理标准的工作:怎么时该保存,知道该怎么再装载, 而且追踪来自设计器的变化通知. 他的特征包括对多依赖加载,保存变化, 而且延期加载支持。

  服务被 BasicDesignerLoader 添加到设计器的服务容器(service container)中。像其他的服务相同,你能够修改被保护的 LoaderHost 属性来修改可替换的服务。示例应用程式实现持久化XML格式的类是BasicDesignerLoader. 为了了解他怎么工作,选择菜单 File | Type | BasicDesignerLoader.. 然后选择菜单File | New | Form创建一个新的Form,查看他所生成的XML文件,选择菜单View | Code | XML. 所看到的XML文件的内容类似于下面的内容:

<Object type="System.视窗系统.Forms.Form, System.视窗系统.Forms,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
name="Form1" children="Controls">

<Property name="Name">Form1</Property>
<Property name="DataBindings">
<Property name="DefaultDataSourceUpdateMode">OnValidation</Property>
</Property>
<Property name="ClientSize">292, 273</Property>
</Object>
  BasicDesignerLoader的PerformFlush 和 PerformLoad 是二个abstract方法是你为实现序列化和反序列化的功能必须实现的方法.


   CodeDomDesignerLoader

  设计时序列化是通过产生代码来实现,代码生成Schema的一个挑战是怎么处理多语言。.NET Framework被设计为多语言协同工作,因此我也希望设计器能够生成多语言。有二个方法来达到解决这个问题. 第一要需要每个语言厂商为他们的语言写代码生成引擎. 不幸的是,没有语言厂商能够预期第三方组件厂商代码生成的多样性需求. 第二种方式要需要每个组件厂商提供代码生成器给他们支持的每种语言.因为被支持的语言的数量是未知的,所以这相当糟糕。

  为了解决这个问题,.Net Framework定义了一个对象模型叫做代码文件对象模型(CodeDOM),所有的原始代码能本质上分解为原始的元素的组合,而且 CodeDOM 是那些元素的对象模型.当代码依附在CodeDOM, 生成的对象模型能够给不同语言的代码生成器生成适当的代码。

  .NET Framework 2.0引入了CodeDomDesignerLoader类,继承自BasicDesignerLoader。CodeDomDesignerLoader是个通过CodeDom进行读写支持的全功能的加载器。他是设计者加载器, 因而你所需要做全部的是CodeDomProvider.

  示例应用中你能选择菜单File | Type | CodeDomDesigner-Loader来看CodeDom的实做例子。创建新的Form通过菜单File | New | Form---这创建一个DesignSurface和用CodeDomDesignerLoader加载他。查看代码,通过选择菜单View | Code | C#查看Form生成的C#代码,或选择菜单View | Code | VB查看Visual Basic代码。

CompilerParameters cp = new CompilerParameters();

AssemblyName[] assemblyNames = Assembly.GetExecutingAssembly().GetReferencedAssemblies();

foreach (AssemblyName an in assemblyNames)
{
 Assembly assembly = Assembly.Load(an);
 cp.ReferencedAssemblies.Add(assembly.Location);
}

cp.GenerateExecutable = true;
cp.OutputAssembly = executable;

cp.MainClass = "DesignerHostSample." +
this.LoaderHost.RootComponent.Site.Name;

// Compile CodeCompileUnit using CodeProvider
CSharpCodeProvider cc = new CSharpCodeProvider();
CompilerResults cr = cc.CompileAssemblyFromDom(cp, codeCompileUnit);

if (cr.Errors.HasErrors)
{
string errors = string.Empty;
foreach (CompilerError error in cr.Errors)
{
errors += error.ErrorText + "\n";
}
MessageBox.Show(errors, "Errors during compile.");
}


  示例程式使用CSharpCodeProvider 和VBCodeProvider生成代码,他也使用代码提供程式编译代码和运行可执行程式。

  ITypeResolutionService是个使用CodeDomDesignerLoader的时候的必须服务,负责类型解析。例如当从Toolbox添加一个控件到设计器的时候,这个服务被调用解析控件的类型。示例程式解析程式集System.视窗系统.Forms的所有类型,所以你能够将Toolbox的视窗系统 Forms下的控件添加到设计器

   结论

  你所看到的是,.NET Framework提供了一个强大的和灵活的设计器宿主基础结构。比上一个版本的Visual Studio设计器的可扩展性有助于设计着解决特别的需求或更多高级的场合. 当然, 设计者也能容易地宿主和Visual Studio外面 。同样地. 下载样例程式代码,你就能够参照例子设计你自己的设计器。
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值