The Perfect Host: Create And Host Custom Designers With The .NET Framework 2.0 
Dinesh Chandnani - 三月 2006 (MSDN Magazine: C#)
This article discusses: 
  • Understanding designers and services
  • Creating a hosting form
  • Building and using a toolbox
  • Loading and managing designers

Code download available at: DesignerHosting.exe (官网VS2005)  (VS2010)

Contents

Extensibility with Services
DesignSurface and DesignSurfaceManager
The Hosting Form
The Toolbox
Multiple DesignSurfaces
DesignerLoaders
CodeDomDesignerLoader
Conclusion

(点击访问译文)
Version 1.0 of the Microsoft® .NET Framework provided a very flexible design-time architecture, but offered virtually no implemented code to actually create and host designers. All of the hosting logic was implemented in Visual Studio® .NET, requiring third parties to rewrite all of this complex logic. This has now changed; the .NET Framework 2.0 introduces a set of classes that can be used to host designers right out of the box.
Figure 1 Runtime 
To understand how .NET Framework designers work, it is important to know how designers are used. A designer is an object that only exists at design time and that connects to an object that normally exists at run time. The framework connects these two objects and provides a conduit for the design-time object to augment the behavior of the run-time object. At run time, a form and a button on that form are connected only through the parent/child relationship between the two controls (see Figure 1). There is no other object that controls the lifetime of these controls.
Figure 2  
The picture looks a little more complex at design time. Both the form and the button have designers associated with them. Both objects are also connected to a host container (see Figure 2), which owns these two objects. The host container also provides services—such as selection services to select one or more objects at design time, and UI services for displaying messages, invoking help, and interacting with the development environment—that the objects and designers may use.
The host container has many responsibilities. It creates components, binding them to designers and providing services to the components and designers it maintains. It loads designers from some sort of persistent state and saves them back to that state. The host container provides logic for undo, clipboard functions, and many other services that the designers rely upon to deliver a robust design-time environment.
Figure 3 Designer Hosting 
The host container can also persist the state of the designer. To do this, it would use a designer loader. The designer loader may, in turn, use serializers to serialize the components, as shown in Figure 3.
Extensibility with Services
The .NET Framework designer architecture is extensible. Critical to this extensibility, services provide the ability to enhance the functionality available to the various designers. A service is any object that can be queried by a type. Typically you would define some abstract class or interface that represents the service and then provide an implementation of that service. You can add services to and remove services from an object called a service container. IDesignerHost, the primary host interface for designers, is a service container. A service is a feature that may be shared between components written by different parties. Because of this, you must follow certain rules when using and creating services.
Services are not guaranteed. Whenever you ask for a service by calling the GetService method, you must always check to see whether GetService returned a valid object. Not all services are available on all platforms, and services that were once available may not be available in the future. Thus, your code should be written to degrade gracefully, usually by disabling the feature that required the service, in case a service does become unavailable.
If you add a service, remember to remove it when your designer is disposed. The designer host may destroy and recreate your designer from time to time. If you fail to remove a service, the old designer may be left in memory.
DesignSurface and DesignSurfaceManager
The .NET Framework 2.0 introduces two classes that are used for hosting designers and providing services to the designers: DesignSurface and DesignSurfaceManager. DesignSurface is what the user perceives as a designer; it is the UI the user manipulates to change design-time features. DesignSurface may be used as a standalone designer or it may be coupled with DesignSurfaceManager to provide a common implementation for an application that hosts multiple DesignSurfaces.
DesignSurface provides several design-time services automatically (see Figure 4). Most of these can be overridden in the service container. It is illegal to replace the non-replaceable services because their implementations all depend on each other. Note that all services added to the service container that implement IDisposable will be disposed when the design surface is disposed.
Figure 4 Default DesignSurface Design-Time Services 

 

In addition to the default services, DesignSurface also provides IDictionaryService, available through a component's site. This service, which provides a generic dictionary of key/value pairs that can be used to store arbitrary data about a component, is unique to each component. It is not possible to replace these services because there is no way to replace services on a per-site basis.
DesignSurfaceManager is intended to be a container of designers. It provides common services that handle event routing between designers, property windows, and other global objects. Using DesignSurfaceManager is optional, but recommended if you intend to have several designer windows.
DesignSurfaceManager also provides several design-time services automatically (see Figure 5). Each of these can be overridden by replacing their value in the protected ServiceContainer property. As with DesignSurface, all DesignSurfaceManager services added to the service container that implement IDisposable will be disposed when the designer application is disposed.

Figure 5 DesignSurfaceManager Design-Time Services

Service Description
IUIService Provides a way for components to show a UI such as error messages and dialog boxes
IDesignerEventService Provides a global eventing mechanism for designer events
IDesignerEventService is a particularly useful service. It allows an application to know when a designer becomes active. IDesignerEventService provides a collection of designers and is a single place where global objects, such as the Property window, can listen to selection change events.
The Hosting Form
To demonstrate how simple it is to host a designer, I wrote the following sample code to create a basic Windows® Forms designer and display it: 
// 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();

In this code snippet, I have loaded the DesignSurface with a Form. Similarly, you can load the DesignSurface with any component that has a root designer available for it. For example, you could load a UserControl or a Component instead.
The sample provided in the code download for this article hosts four different root components: Form, UserControl, Component, and MyTopLevelComponent (a graph designer). When you run the sample, a shell UI will open. This interface includes a toolbox, a properties browser, a tab control for hosting designers, an output window, and a Solution Explorer, as shown in Figure 6. Selecting File | New | Form from the menu opens a new designer host with Windows Forms Designer. This essentially uses the code I've just shown you to load the designer. Rather than loading a Form, the sample app illustrates how to load a UserControl or Component.
Figure 6 Hosting Windows Forms Designer 
To create a root component, create a designer that implements IRootDesigner and then associate this designer with your component. The View property of the root component specifies the view that will be presented to the user.
One of the main services provided by DesignSurface is IDesignerHost. It is the master interface used to provide designers and controls access to types, services, and transactions. It can also be used to create and destroy components. To add a button to the Windows Forms designer I created earlier, all I need to do is get the IDesignerHost service from the DesignSurface and use it to create the button as shown in Figure 7.
Figure 7 Create a Button with IDesignerHost
// 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 specifies that the designer supports adding controls from the toolbox. This means that if you do have a toolbox that implements ToolboxService, you can use the IToolboxUser interface to add controls to the root component. For instance:
/* 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)));

When controls are added to the custom RootDesigner in the sample application by double-clicking the items in the toolbox, the view of the RootDesigner updates to display a pie chart as shown in Figure 8. Clicking on the GraphStyle link changes the view to a bar graph.
Figure 8 Custom RootDesigner Updates 
The Toolbox
MyRootDesigner implements the IToolboxUser interface. This has two methods: GetToolSupported and ToolPicked. You can use GetToolSupported to filter the items that can be added onto the designer. ToolPicked eventually calls into the CreateComponents method (which, as the name implies, is responsible for creating the components) of ToolboxItem.
Now that I have added the controls and components to the designer, let's take a closer look at how to implement a toolbox. First, your toolbox needs to implement IToolboxService—this service is added to the service container and can be accessed by anyone who needs to use it. The main functions of IToolboxService are shown in Figure 9.
Figure 9 IToolboxService Members (Close Figure 9)
Method or Property Description
GetSelectedToolboxItem Returns the selected toolbox item
SerializeToolboxItem Creates a binary serialized object (DataObject) from ToolboxItem
DeserializeToolboxItem Returns the ToolboxItem from a binary serialized object (DataObject)

To allow the items from the toolbox to be added onto the designer using the mouse or keyboard, the toolbox in the sample hooks onto the KeyDown and MouseDown events. For the Enter key or a mouse double-click, IToolboxUser.ToolPicked is called. The sample shows how to serialize the ToolboxItem into DataObject and DoDragDrop for when a mouse single-click event occurs. IToolboxService.SerializeToolboxItem will be called on mouse up and the item will be added to the designer.
When a new control or component is added to the designer, you can provide a custom name for the control by implementing INameCreationService. The sample application shows an example of this service in action using CreateName, ValidateName, and IsValidName.
Multiple DesignSurfaces
When managing multiple DesignSurfaces, it is a good idea to use a DesignSurfaceManager. This makes it easier to manage these DesignSurfaces. (Note that the services of DesignSurfaceManager are also available to DesignSurface.)
Calling DesignSurfaceManager.CreateDesignSurface calls into CreateDesignSurfaceCore. You can override this function to create a custom DesignSurface and add the services. The sample app creates a custom HostSurface by overriding this function in the HostSurfaceManager class:
protected override DesignSurface CreateDesignSurfaceCore(IServiceProvider parentProvider)
{return new HostSurface(parentProvider);
}

You can then hook into the ActiveDesignSurfaceChanged event and update the output window in the HostSurfaceManager class, as shown here:
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
So far I've created DesignSurfaces, hosted designers, added controls, implemented a toolbox, and added and accessed services like OutputWindow. The next step is to persist the designer. The designer loader is, as you'd expect, responsible for loading the designer from some persistent state. Simple and flexible, designer loaders have very few requirements. In fact, you can create an instance of the Windows Forms designer with a one-line designer loader that simply creates an instance of System.Windows.Forms.Form.
In addition to loading a form design, a designer loader is also responsible for saving the design. Because saving is an optional behavior, a designer loader listens to change events from the designer host and automatically saves state according to these events.
The .NET Framework 2.0 introduces two new classes for writing custom loaders: BasicDesignerLoader and CodeDomDesignerLoader. The sample app illustrates implementations of both loader types. Earlier I demonstrated loading the DesignSurface with a root component by passing in the type of the component. However, if you are using a loader, it should be used to load the design surface. The BeginLoad code snippet you'll need when using loaders should look something like this:
// Load it using a Loader
ds.BeginLoad(new MyLoader());

The DesignerLoader is responsible for loading the root component in the DesignSurface and creating any components. When creating a new form or any other root component, the loader simply loads it. In comparison, when loading from a code file or some other store, the loader is responsible for parsing the file or store and recreating the root component along with any other necessary components.
The .NET Framework defines an abstract base class called DesignerLoader that is used to load and save designers from persistent storage. The base class is abstract so that any type of persistence model can be used. This, however, adds to the complexity of implementing the class.
BasicDesignerLoader provides a complete and common implementation of a designer loader minus any information related to the persistence format. Like DesignerLoader, it is abstract, not dictating anything about the persistence format. BasicDesignerLoader does, however, handle the standard work of knowing when to save, knowing how to reload, and tracking change notifications from designers. Its features include support for multiple load dependencies, tracking of the modified bit to indicate a need to save changes, and deferred idle time reload support.
The services shown in Figure 10 are added to the designer host's service container by BasicDesignerLoader. As with other services, you can change replaceable services by editing their value in the protected LoaderHost property.The sample app implements a BasicDesignerLoader that persists the state in an XML format. To see how it works, select File | Type | BasicDesignerLoader. Then create a new Form by selecting File | New | Form. To see the XML code that is generated, select View | Code | XML. The XML code that is generated by the app will look something like this:
<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 and PerformLoad are the two abstract functions of the BasicDesignerLoader that you need to implement for serializing and deserializing, respectively.
Figure 10 BasicDesignerLoader Services
CodeDomDesignerLoader
Design-time serialization is handled by generating source code. One of the challenges of any code-generation scheme is the handling of multiple languages. The .NET Framework is designed to work with a variety of languages, so I also want the designers to emit to several languages. There are two ways to address this. The first is to require each language vendor to write the code-generation engine for their language. Unfortunately, no language vendor can anticipate the wide variety of code-generation requirements that third-party component vendors may need. The second approach is to require each component vendor to provide code generation for each language they want to support. This is equally bad, because the number of languages supported is not fixed.
To solve this problem, the .NET Framework defines an object model called the Code Document Object Model (CodeDOM). All source code can essentially be broken down into primitive elements and the CodeDOM is an object model for those elements. When code adheres to CodeDOM, the generated object model can later be sent to a code generator for a particular language to render the appropriate code.
The .NET Framework 2.0 introduces CodeDomDesignerLoader, which inherits from BasicDesignerLoader. The CodeDomDesignerLoader is a full loader that works by reading and writing CodeDOM. It is a turnkey designer loader, so all you need to do is provide the CodeDOM.
In the sample app you can select File | Type | CodeDomDesigner-Loader to see an example of the CodeDOM in action. Create a new form by selecting File | New | Form—this creates a DesignSurface and loads it using a CodeDomDesignerLoader. To check the code, select View | Code | C# to see the C# version of the form's code, or View | Code | VB to see the Visual Basic® version.
To generate the code, the sample app uses CSharpCodeProvider and VBCodeProvider. It also uses the code providers to compile the code and run the executable (see Figure 11).
Figure 11 Generate, Compile, and Run
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.");
}

ITypeResolutionService, which is required when using the CodeDomDesignerLoader, is responsible for resolving a type. One scenario, for example, where this service is called to resolve types is when adding a control from the toolbox into the designer. The sample application resolves all types from the System.Windows.Forms assembly so that you can add controls from the Windows Forms tab of the toolbox.
Conclusion
As you've seen, the .NET Framework provides a powerful and flexible designer hosting infrastructure. Designers provide straightforward extensibility that help to address very specific needs or more advanced scenarios than were supported by previous versions of Visual Studio. Of course, designers can be hosted easily outside Visual Studio, as well. Be sure to download the sample application, so you can play around with the code and start implementing your own custom designers today.
------------------------------------------------------------------------
Dinesh Chandnani is a Software Design Engineer in Test for the .NET Client Team at Microsoft, working on designer hosting and other designer features. He started working for Microsoft in 2002 after completing his masters' degree in Computer Science at the University of Arizona.
(src:http://msdn.microsoft.com/zh-cn/magazine/cc163634(en-us).aspx)

转载于:https://www.cnblogs.com/code1992/p/10170358.html

DesignSurface简介相关推荐

  1. etcd 笔记(01)— etcd 简介、特点、应用场景、常用术语、分布式 CAP 理论、分布式原理

    1. etcd 简介 etcd 官网定义: A highly-available key value store for shared configuration and service discov ...

  2. Docker学习(一)-----Docker简介与安装

    一.Docker介绍 1.1什么是docker Docker是一个开源的应用容器引擎,基于Go语言并遵从Apache2.0协议开源 Docker可以让开发者打包他们的应用以及依赖包到一个轻量级,可移植 ...

  3. 【Spring】框架简介

    [Spring]框架简介 Spring是什么 Spring是分层的Java SE/EE应用full-stack轻量级开源框架,以IOC(Inverse Of Control:反转控制)和AOP(Asp ...

  4. TensorRT简介

    TensorRT 介绍 引用:https://arleyzhang.github.io/articles/7f4b25ce/ 1 简介 TensorRT是一个高性能的深度学习推理(Inference) ...

  5. 谷粒商城学习笔记——第一期:项目简介

    一.项目简介 1. 项目背景 市面上有5种常见的电商模式 B2B.B2C.C2B.C2C.O2O B2B 模式(Business to Business),是指商家和商家建立的商业关系.如阿里巴巴 B ...

  6. 通俗易懂的Go协程的引入及GMP模型简介

    本文根据Golang深入理解GPM模型加之自己的理解整理而来 Go协程的引入及GMP模型 一.协程的由来 1. 单进程操作系统 2. 多线程/多进程操作系统 3. 引入协程 二.golang对协程的处 ...

  7. Linux 交叉编译简介

    Linux 交叉编译简介 主机,目标,交叉编译器 主机与目标 编译器是将源代码转换为可执行代码的程序.像所有程序一样,编译器运行在特定类型的计算机上,输出的新程序也运行在特定类型的计算机上. 运行编译 ...

  8. TVM Operator Inventory (TOPI)简介

    TOPI简介 这是 TVM Operator Inventory (TOPI) 的介绍.TOPI 提供了比 TVM 具有更高抽象的 numpy 风格的,通用操作和调度.TOPI 如何在 TVM 中,编 ...

  9. 计算机视觉系列最新论文(附简介)

    计算机视觉系列最新论文(附简介) 目标检测 1. 综述:深度域适应目标检测标题:Deep Domain Adaptive Object Detection: a Survey作者:Wanyi Li, ...

  10. 2021年大数据ELK(二十三):Kibana简介

    全网最详细的大数据ELK文章系列,强烈建议收藏加关注! 新文章都已经列出历史文章目录,帮助大家回顾前面的知识重点. Kibana简介 通过上面的这张图就可以看到,Kibana可以用来展示丰富的图表. ...

最新文章

  1. python可视化窗口制作一个摇骰子游戏_使用python制作一个抽奖小游戏——骰子游戏...
  2. IO: BIO ? NIO ? AIO?
  3. AI:人工智能概念之机器学习中常用算法的思维导图集合(非常经典、建议收藏)之详细攻略
  4. php中的全局异常,PHP的全局错误处理详解
  5. 关于部署传统的Dynamic Web项目
  6. int默认值为0,Integer默认值为null
  7. html5自动打印图片,html2canvas生成清晰的图片实现打印代码示例
  8. Lesson6 how to use HTML QT Widgets
  9. webbench之编译安装(一)
  10. delphi三层架构中注册服务器
  11. [Everyday Mathematics]20150112
  12. 可能是最全的java架构师面试题
  13. Unity LitJson的教程
  14. 【软件】一个好用的QQ截图工具(百度云免费下载链接)
  15. 创新大赛成就创业梦想 超30%入榜应用获投资意向
  16. 各种css文字样式效果——多个行内元素居中,三点,缩进,阴影等
  17. 性能测试的价值、全链路压测了解
  18. java json 合并_java json 合并
  19. 《论语》原文及其全文翻译 学而篇11
  20. storm mysql trident_Storm Trident状态

热门文章

  1. python环境变量设置失败
  2. python闭包和函数调用区别_对python闭包(内嵌函数)的理解
  3. php unix时间戳单位,PHP对于Unix时间戳的转换
  4. 怎么使用systemctl启动rabbitmq_光纤激光切割机已经很久没有使用了。再次重新启动它,该怎么办?...
  5. 从java库学设计模式_java I/O库的设计模式
  6. 5.Hbase API 操作开发
  7. MySQL主从数据同步延时分析
  8. bash脚本基础概念注意点
  9. C# 传递给C++函数的结构体没有对齐的问题
  10. Net设计模式实例之桥接模式( Bridge Pattern)(1)