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.
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)