(转载)用.NET Framework 2.0创建 Form设计器【MSDN】

原址:http://www.microsoft.com/china/MSDN/library/netFramework/netframework/DesignerHosting.mspx?mfr=true

 

Microsoft®

.NET 中实现,需要第三方代码来重写所有这些复杂的逻辑。现在,这种情况已经有所改变;.NET Framework 2.0 引入了一组类,用于宿主现成的设计器。

 


图 1 运行时


要了解 .NET Framework 设计器的工作方式,重要的是了解如何使用这些设计器。设计器是一个对象,它仅存在于设计时,并连接到运行时存在的对象。.Net Framework 连接这两种对象,并为设计时对象提供一种渠道以扩大运行时对象的行为。在运行时,窗体及其上面的按钮仅通过两个控件间的父/子关系进行连接(参见图 1)。不存在其他对象控制这些控件的生存期。


图 2


该图在设计时看起来比较复杂。窗体和按钮均有与之相关的设计器。这两个对象也连接到拥有这两个对象的宿主容器(参见图 2)。宿主容器还提供以下服务,例如选择服务(在设计时选择一个或多个对象)、显示消息的 UI 服务、调用帮助以及与开发环境进行交互 — 对象和设计器可以使用。

宿主容器还承担许多职责。它创建组件,将它们绑定到设计器,并为其维护的组件和设计器提供服务。它从某种持久性状态加载设计器,并将它们保存回该状态。宿主容器提供撤销逻辑、剪贴板功能、以及其他服务 — 设计器需要以其为基础来提供一个健壮的设计时环境。


图 3 设计器宿主


宿主容器还能够持久保持设计器的状态。为此,它需要使用一个设计器加载器。设计器加载器可以使用序列化程序依次对组件进行序列化,如图 3 所示。

利用服务提高可扩展性


.NET Framework 设计器体系结构是可扩展的。可扩展性的关键在于,服务能够增强各种设计器的可用功能。服务是一种对象,可根据类型进行查询。通常,您定义一些代表服务的抽象类或接口,然后提供对该服务的实现。您可以将服务添加到调用服务容器的对象,也可以从该对象中删除服务。IDesignerHost — 设计器的主要宿主接口,它是一个服务容器。服务是一种功能,可在由不同方编写的组件之间进行共享。因为这个原因,您必须在使用和创建服务时沿袭某些规则。

服务无法获得保证。无论在什么情况下,通过调用 GetService 方法来请求服务,您必须一直查看 GetService 是否返回一个有效的对象。并非所有服务在任意平台中都是可用的,并且服务一次可用并不代表它将来也可用。因此,通常通过禁用请求服务的功能,编写降低等级的代码,以防止服务变得不可用。

如果添加一个服务,请谨记,一旦处置设计器即移除该服务。设计器宿主可以销毁,有时还可以重新创建您的设计器。如果无法移除服务,旧的设计器将保留在内存中。

DesignSurface 和DesignSurfaceManager


.NET Framework 2.0 引入两个类,用于宿主设计器并为设计器提供服务:DesignSurface 和 DesignSurfaceManager。DesignSurface 是用户眼中的设计器;它是用户进行操作以更改设计时功能的 UI。DesignSurface 可作为一个单独的设计器使用,或者可与 DesignSurfaceManager 联合使用以提供宿主多个 DesignSurfaces 的应用程序的一个公共实现。

DesignSurface 自动提供一些设计时服务(参见图 4。其中的大部分服务可在服务容器中重写。替换不可替换的服务是非法的,原因是这些服务的实现均相互依赖。注意,添加到服务容器中(实现 IDisposable)的所有服务将在处置设计表面时进行处置。

除默认服务之外,DesignSurface 还提供 IDictionaryService,在组件的任意场合均可用。该服务提供键/值对的常规字典,该键/值对可用于存储有关组件的任意数据,并且该服务对于每个组件而言是唯一的。这些服务不可能进行替换,因为无法根据场合替换服务。

DesignSurfaceManager 旨在成为设计器的容器。它提供常规服务,用于处理设计器、属性窗口和其他全局对象之间的事件路由。DesignSurfaceManager 的使用是可选的,但建议在有若干设计器窗口的情况下使用它。

DesignSurfaceManager 还自动提供一些设计时服务(参见图 5)。其中的每个服务均可通过替换其受保护的 ServiceContainer 属性中的值进行重写。对于 DesignSurface,所有添加到服务容器中并且实现 IDisposable 的 DesignSurfaceManager 服务均会在处置设计器应用程序时进行处置。

IDesignerEventService 是一个特别有用的服务。它使应用程序能够获悉设计器变为活动的时刻。IDesignerEventService 提供一个设计器的集合,同时它也是存放全局对象(例如,Property 窗口)的一个位置,并能够侦听选择变化事件。

宿主窗体


要演示如何简便地宿主一个设计器,我编写了下面的示例代码,它创建并显示了一个基本的Windows® 窗体设计器:

// 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 或一个 Component。

本文的代码示例下载中有四种不同的根组件:Form、UserControl、Component 和 MyTopLevelComponent(一个图形设计器)。运行该代码时,将打开一个外壳 UI。该界面包括一个工具箱、一个属性浏览器、一个宿主设计器的选项卡控件、一个输出窗口以及一个解决方案资源管理器,如图 6 所示。从菜单中选择 File | New | Form,用 Windows 窗体设计器打开一个新的设计器宿主。该操作基本上使用了我为您演示加载设计器的代码。不仅仅是加载一个 Form,示例应用程序演示如何加载一个 UserControl 或 Component。


图 6 宿主 Windows 窗体设计器


要创建一个根组件,首先创建实现 IRootDesigner 的设计器,然后将该设计器与组件进行关联。根组件的 View 属性指定将呈现给用户的视图。

由 DesignSurface 提供的一个主要服务是 IDesignerHost。它是用于提供设计器和对类型、服务和事务进行访问的主要接口。它还可用于创建和销毁组件。要向我之前已创建的 Windows 窗体设计器添加一个按钮,只需从 DesignSurface 获得 IDesignerHost,然后用它创建如图 7 所示的按钮。

IToolboxUser 指定设计器支持从工具箱中添加控件。即,如果有一个实现 ToolboxService 的工具箱,您可使用 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))); 

当通过双击工具箱中的项目将控件添加到示例应用程序中的自定义 RootDesigner 时,RootDesigner 的视图进行更新以显示如图 8 所示的饼图。单击 GraphStyle 链接,将视图改为条形图。


图 8 自定义 RootDesigner 更新


工具箱

MyRootDesigner 实现 IToolboxUser 接口。有两种方法:GetToolSupported 和 ToolPicked。可以使用 GetToolSupported 筛选可添加到设计器的项目。最后,ToolPicked 调用 ToolboxItem 的 CreateComponents 方法(顾名思义,该方法用于创建组件)。

既然已经向设计器添加了控件和组件,那么让我们更细致地看看如何实现工具箱。首先,工具箱需要实现 IToolboxService — 该服务添加到服务容器,并且可由任何需要使用它的用户访问。IToolboxService 的主要功能如图 9 所示。

要使工具箱中的项目可通过鼠标或键盘添加到设计器,则可以将示例中的工具箱挂钩到 KeyDown 和 MouseDown 事件。对于 Enter 键或鼠标双击,调用 IToolboxUser.ToolPicked。该示例说明,在鼠标单击事件发生时,如何将 ToolboxItem 序列化到 DataObject 和 DoDragDrop 中。将在松开鼠标键时调用 IToolboxService.SerializeToolboxItem,然后将项目添加到设计器。

当一个新的控件或组件添加到设计器时,您可以为由 INameCreationService 实现的控件提供一个自定义的名称。该示例应用程序通过使用 CreateName、ValidateName 和 IsValidName 说明该服务的一个活动示例。

多种DesignSurface


管理多个 DesignSurface 时,一个好办法就是使用 DesignSurfaceManager。它能够更轻松地管理这些 DesignSurfaces。(请注意,DesignSurfaceManager 的服务还可用于 DesignSurface。)

DesignSurfaceManager.CreateDesignSurface调用将调用 CreateDesignSurfaceCore。您可以重写该函数来创建一个自定义的 DesignSurface 并添加服务。该示例应用程序通过重写 HostSurfaceManager 类中的该函数创建一个自定义的 HostSurface:

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

然后,您可以挂构到 ActiveDesignSurfaceChanged 事件,并更新 HostSurfaceManager 类中的输出窗口,如下所示:

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

DesignerLoaders


迄今为止,我们已经创建了 DesignSurfaces,宿主了设计器,添加了控件,实现了工具箱并添加、访问了如 OutputWindow 这样的服务。下一步是持久保持设计器。如您所愿,设计器加载器用于从某些持久状态载入设计器。简单又灵活,设计器加载器只有少量需求。实际上,您只需一行设计器加载器代码(只需创建 System.Windows.Forms.Form 的一个实例)即可创建 Windows 窗体设计器的一个实例。

除了加载窗体设计,设计器加载器还可以保存设计。因为保存是可选操作,所以设计器加载器要进行侦听以改变设计器宿主的事件,然后自动保存与这些事件相关的状态。

.NET Framework 2.0 引入两个新类,用于编写自定义的加载器:BasicDesignerLoader 和 CodeDomDesignerLoader。示例应用程序说明这两个加载器类型的实现。以前,我演示过通过传递组件的类型来加载 DesignSurface 的根组件。然而,如果您使用加载器,则它可用于加载设计表面。当使用加载器时,将使用如下所示的 BeginLoad 代码片断:

// Load it using a Loader ds.BeginLoad(new MyLoader()); 

DesignerLoader 用于加载 DesignSurface 中的根组件,以及创建任意组件。创建一个新窗体或任意其他根组件时,只载入加载器。对比一下,当从代码文件或其他存储进行加载时,加载器用于分析文件或存储,重新创建根组件以及任何其他需要的组件。

.NET Framework 定义一个名为 DesignerLoader 的抽象基类,它用于加载和保存持久存储的设计器。该基类是抽象的,因此可使用任意类型的持久性模型。然而,它也增加了该类实现的复杂性。

BasicDesignerLoader 提供一个完整且通用的设计器加载器实现,但不包括与持久性格式相关的信息。象 DesignerLoader 一样,它是抽象的,不表示任意有关持久性格式的信息。然而,BasicDesignerLoader 所作的就是处理一些标准工作,如了解何时进行保存,了解如何重新加载,以及跟踪设计器的更改通知。它的功能还包括,支持多个加载依赖项,跟踪修改过的位以指示需要保存变更,延缓重新加载支持的空闲时间。

图 10 所示的服务通过 BasicDesignerLoader 添加到设计器宿主的服务容器。对于其他服务,可通过在受保护的 LoaderHost 属性中编辑其值来更改可替换的服务。该示例应用程序实现一个 BasicDesignerLoader,它以 XML 格式保持状态。要了解它如何实现,选择 File | Type | BasicDesignerLoader。然后,选择 File | New | Form 创建一个新窗体。要查看生成的 XML 代码,选择 View | Code | XML。由应用程序生成的 XML 代码,如下所示:

<Object type="System.Windows.Forms.Form, System.Windows.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> 

PerformFlush 和 PerformLoad 是 BasicDesignerLoader 的两个抽象函数,分别用于实现序列化和反序列化。

CodeDomDesignerLoader

设计时序列化由生成的源代码处理。代码生成方案的困难之一是多对语言的处理。.NET Framework 旨在与各种语言配合,因此我还希望设计器能生成一些其他语言。要解决这个问题,有两种方法。第一,需要每个语言提供商编写其语言的代码生成引擎。遗憾的是,没有一个语言提供商能预见到如此众多的代码生成需求,这些需求都是第三方组件提供商需要的。第二种方法是,需要每个组件提供商为他们要支持的每种语言提供代码生成。这同样也很糟糕,因为所支持语言的数量并不固定。

要解决这一问题,.NET Framework 定义一个名为代码文档对象模型(Code Document Object Model,CodeDOM)的对象模型。所有源代码基本上均可拆分为基元元素,并且 CodeDOM 是这些元素的一个对象模型。当代码符合 CodeDOM 时,生成的对象模型可以稍后发送到特殊语言的代码生成器,以呈现适当的代码。

.NET Framework 2.0 引入 CodeDomDesignerLoader,它从 BasicDesignerLoader 继承而来。CodeDomDesignerLoader 是一个完整的加载器,在读和写 CodeDOM 时使用。它是一个 turnkey 设计器加载器,所以您只需提供 CodeDOM.。

在示例应用程序种,您可以选择 File | Type | CodeDomDesigner-Loader 来查看一个 CodeDOM 活动的示例。通过选择 File | New | Form 创建一个新窗体 — 创建一个 DesignSurface 并通过 CodeDomDesignerLoader 进行加载。要检查代码,选择 View | Code | C# 查看窗体代码的 C# 版本,或选择 View | Code | VB 查看 Visual Basic® 版本。

要生成代码,示例应用程序使用 CSharpCodeProvider 和 VBCodeProvider。它还使用代码提供程序编译代码,并运行可执行文件(参见图 11)。

ITypeResolutionService(使用 CodeDomDesignerLoader 时需要它)用于解析类型。例如,一种情况是,当从工具箱向设计器添加控件时,调用该服务以解析类型。示例应用程序解析了 System.Windows.Forms 程序集的所有类型,因此,您可以从 Windows 窗体的工具箱选项卡添加控件。

小结

正如您看到的,.NET Framework 提供了一个强大、灵活的设计器宿主基础结构。设计器提供了直观的可扩展性,可帮助解决特定的需求,或较之早期 Visual Studio 版本所支持的更高级的方案。当然,设计器也可以轻松地宿主到 Visual Studio 外部。.请确保下载了示例应用程序,这样,您就可以试运行代码并开始实现您自己的自定义设计器。

Dinesh Chandnani 是一位软件设计工程师,在 Microsoft 的 .NET Client 小组中负责测试,工作涉及设计器宿主和其他设计器功能。他于 2002 年硕士(亚利桑那大学的计算机科学硕士学位)毕业之后即在 Microsoft 工作。

转到原英文页面

本人的一点补充:

用VS2010编译时会碰到“Microsoft"下找不到“Tools”的问题,

解决方法就是”解决方案“下的所有项目的目标框架都改为”.Net FrameWork4“ 。

很简单,不过可花了我两三天的时间才找到答案。

转载于:https://www.cnblogs.com/aloneone/archive/2012/07/28/2612913.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值