Creating Designable Components for Microsoft Visual Studio .NET Designers

 

Shawn Burke
Microsoft Corporation

July 2000

Summary: This article discusses how Microsoft .NET components, written in managed code and built upon the common language runtime, provide developers with a great new mix of development ease similar to that of Microsoft Visual Basic while delivering the power of lower level programming more commonly associated with ATL or MFC. (26 printed pages)

Contents

Introduction
What Components Look Like
Custom Metadata
Interface with the Property Browser
In Through the Out Door: Persistence Through Code
Component Designers
Accessing Designer Services and Infrastructure
Licensing Your Components
Conclusion

Introduction

Microsoft's forthcoming release of Microsoft® Visual Studio .NET will allow developers to have an integrated environment that provides rich facilities for developing not only traditional C/C++ applications, but also exciting new Microsoft® .NET (.NET) components. These components, written in managed code and built upon the common language runtime, will provide developers a great new mix of development ease similar to that of Microsoft Visual Basic®, with the power of lower level programming more commonly associated with Active Template Library (ATL) or Microsoft Foundation Class (MFC) Library. With the advent of a managed productivity-centric environment that interoperates well with traditional COM components, developers can spend more of their time building great components and less time worrying about memory leaks, security, and header files.

In addition to providing for the development of .NET Framework components, Microsoft® Visual Studio .NET (VS .NET) exposes many facilities that allow components to take advantage of the designer architecture in VS .NET so they can look and behave like the components that will ship with VS .NET. All of the features that you'll find in the VS .NET designer for developing managed components use the .NET Framework of the components themselves, allowing tight integration between design-time and runtime components.

What Components Look Like

It turns out that .NET Framework components are simple to write. The only requirement for making them work with a Microsoft® Visual Studio .NET designer is that they implement System.ComponentModel.IComponent, which usually means deriving from System.ComponentModel.Component, the default implementation of IComponent. IComponent allows a component to keep track of design-time information, such as its container component or its name, or to access services that the designer may expose.

Let's say we write a simple .NET component that looks like this:

using System;
    using System.ComponentModel;
    public class BoolTracker : Component {
        private bool state;
        private EventHandler handler;
        private static object EventValueChanged = new object();

        public BoolTracker() {
        }

        public bool Value {
            get {
                return state;
            }
            set {
                if (this.state != value) {
                    this.state = value;
                    OnValueChanged(new EventArgs());
                }
            }
        }

        public void AddOnValueChanged(EventHandler h) {
            handler  = (EventHandler)Delegate.Combine(handler, h);
        }

        protected virtual void OnValueChanged(EventArgs e) {
            if (handler != null) {
                handler(this, e);
            }
        }

        public void RemoveOnValueChanged(EventHandler h) {
            handler = (EventHandler)Delegate.Remove(handler, h);
        }
        
    }

Clearly, this component would not do much, but you could drop it onto the VS .NET Win Forms Designer or Component Designer, and you would see in the property browser that it has a name, as well as a property called "Value," with a drop down arrow that would allow you to set that value to True or False. It would also fire an event called OnValueChanged when the value was toggled between True and False.

In the case of designers, the component is only half the picture. The most important piece of the picture is attributes*,* which make up metadata. Metadata is information about a class, property, event, and so forth. Let's take our Value property. Just by being a property, there is already metadata associated with it, such as type (Boolean), behavior (read/write), or name ("Value"). Basic metadata is retrieved using "reflection," which is how the common language runtime allows users, at runtime, to inspect objects for their type, base types, properties, methods, constructors, fields, or access level. All of this information is considered metadata.

Custom Metadata

Custom metadata includes arbitrary pieces of information that can be attached to a class or class member (field, property, or method), which are actually Types themselves that are recognized by specific clients. In the case of the VS .NET designers, Custom metadata forms the basis for all the extensibility that can be found there. All of the metadata attributes that a VS .NET designer understands are based on a class called System.ComponentModel.MemberAttriubute. It provides a base class so that the attributes that the designer is concerned about can be quickly identified by their type.

It's much easier to understand with a concrete example. Let's say we didn't want to allow the Value property to show up in the property browser. We can add a metadata attribute called System.ComponentModel.BrowsableAttribute, which controls whether a property can be browsed.

 [Browsable(false)]
public bool Value {
   get {
      return state;
              }
   set {
   if (this.state != value) {
         this.state = value;
         OnValueChanged(new EventArgs());
      }
   }
}

We can shorten "BrowsableAttribute" to "Browsable" when we specify attributes—the C# compiler adds the word "Attribute" for us. The only restrictions are that the attribute values, if any are specified, must correspond to constructors on the attribute type, and the values must be constants. In this case, BrowsableAttribute has a constructor that takes a single Boolean parameter, "browsable," and the compiler binds this metadata attribute to that constructor and creates an instance of the attribute class. If the property browser gets hold of this object, it will enumerate the properties on the object and ignore this one because it is tagged with this attribute. It will look as if this object has no properties. The BrowsableAttribute can also be applied to events.

The .NET Framework has a rich set of attributes for controlling how a designer works with a component. Here's a list of some of the useful ones,which will make more sense as you read farther along:

Attribute Name Description
BrowsableAttribute Controls whether a property or event will be displayed in the property browser.
BindableAttribute Determines whether a property is appropriate for a data binder to bind to.
CategoryAttribute Specifies the category in which a property should be grouped in the property browser ("Appearance," "Layout," "Behavior," "Misc." and so forth).
DefaultEventAttribute/ DefaultPropertyAttribute Specifies the default event/property for an object.
HelpAttribute Specifies the Help file and topic for a property or event.
LicenseProviderAttribute Points to a LicenseProvider that can supply licensing information for a component.
MergablePropertyAttribute Allows or prevents a property from being included when multiple components are selected and browsed in a property browser.
DesignerSerializationVisibilityAttribute Determines whether a property's value should be persisted to code during code generation by a visual designer, such as the Win Forms Designer or the Component Designer.
PersistContentsAttribute Determines whether code generation should recurse into a nonvalue type property of an object and persist the code for the property's value. ICollection property types are a typical example where this would be used.
ShowInToolboxAttribute Determines whether this component should be allowed on the toolbox.
ToolBoxItemAttriubte Specifies the type of ToolboxItem that should be used to create a class from the toolbox

Interface with the Property Browser

You've probably noticed that the property browser is mentioned several times in the previous section. That's because a great deal of a component's participation in the designer occurs in the property browser. The designer organizes and displays the components, but it's mostly up to the property browser to allow a user to modify them. In past versions, the browser displayed native COM objects that had a defined subset of data types to display: numbers, strings, fonts, colors, images, Booleans, and enumerations. Beyond that, it was up to property pages or dialog boxes to do any additional property manipulation. However, the VS .NET designer allows component designers to define how the property browser handles any property types that the object exposes, as well as how a user can edit those types.

The .NET Framework uses these editors for many of its built-in types, such as System.Drawing.Color, System.Drawing.Font, or for the Dock and Anchor properties on Win Forms controls.

Figure 1. Property editors

This property browser extensibility architecture supplies four basic types of functionality:

  • Value conversion
  • Subproperties
  • Enumeration
  • Dropdown/popup editors

System.ComponentModel.TypeConverter handles the first three and editors derived from System.WinForms.Design.UITypeEditor handle the last.

All of the types that you'll encounter as properties within the .NET Framework have built-in TypeConverters. Let's take a look at the TypeConverter class and what it does, and then we'll talk about specific examples. The following code is an abbreviated version of TypeConverter. It has several flavors of many of the methods listed, but those are omitted for brevity and only the core methods remain. All the methods listed here are virtual, so they can be overridden if desired. However, since TypeConverter is implemented as a base class instead of an interface, most of the important functionality is already built-in and you can override the portions that are relevant to your application.

public class TypeConverter {

   //
   // value conversion methods
   //

   // determines this TypeConverter can convert
      // from the specified type to its target type
        public virtual bool CanConvertFrom(
ITypeDescriptorContext context, 
Type sourceType);
        
   // determines if this TypeConverter can convert 
// from its target type to the specified type.
        public virtual bool CanConvertTo(
ITypeDescriptorContext context, 
Type destinationType);
         
   // converts the value in a value of the target 
// type for this TypeConverter
        public virtual object ConvertFrom(
ITypeDescriptorContext context, 
object value, 
object[] arguments);
        
   // converts the value from the TypeConverters 
// target type to the destination type
      public virtual object ConvertTo(
ITypeDescriptorContext context, 
object value, 
Type destinationType, 
object[] arguments);
        
   //
   // instance creation methods
   //
   
   // create an object of the target type
      public virtual object CreateInstance(
ITypeDescriptorContext context, 
PersistInfo persistInfo);
        
   // create an object of the target type and 
// populate its values from the given
   // IDictionary
      public virtual object CreateInstance(
ITypeDescriptorContext context, 
IDictionary propertyValues);
        
   // specifies wheter this TypeConverter 
// knows how to create an object instance
   // of the target type
      public virtual bool GetCreateInstanceSupported(
ITypeDescriptorContext context);
        
   // gets the PersistInfo for the value, 
// which is an object that describes the
   // pereisted state of the given value
      public virtual PersistInfo GetPersistInfo(
ITypeDescriptorContext context, 
object value);
       
   // 
   // sub properties methods
   //

   // retrieves any sub properties do be displayed 
// in the property browser for this
   // type.   
      public virtual PropertyDescriptorCollection GetProperties(
ITypeDescriptorContext context, 
object value, 
MemberAttribute[] attributes);
        
   // specifies if this type should be displayed with a '+' 
// in the property browser and should surface sub properties.       
      public virtual bool GetPropertiesSupported(
ITypeDescriptorContext context);
        

   //
   // predefined/standard values methods   
   //

   // retrieves a collection of the predefined 
// "standard" values for this type
      public virtual StandardValuesCollection GetStandardValues(
ITypeDescriptorContext context);
        
   // specifies whether the value collection from 
// GetStandardValues is a complete
   // list of possible or valid values.
      public virtual bool GetStandardValuesExclusive(
ITypeDescriptorContext context);

   // specifies if this TypeConverter can return 
// a list of standard values        
      public virtual bool GetStandardValuesSupported(
ITypeDescriptorContext context);
        
   // checks if a value is a valid value for this type.
      public virtual bool IsValid(
ITypeDescriptorContext context, object value);
       
   //
   // utility methods
   //
     
   // sorts the property collection such that the properties are in
   // the order specified by the names array.       
      protected PropertyDescriptorCollection SortProperties(
PropertyDescriptorCollection props,
string[] names);
 }

public interface ITypeDescriptorContext : IServiceObjectProvider {
    
   // returns the IContainer that pertains to this context
      IContainer Container { get; }

   // returns the instance that is being inspected. 
// If a value is passed to a TypeConverter, 
// ITypeDescriptorContext::Instance will return the object
   // that supplied the value.
      object Instance { get; }

      // called before a change is made to the object returned 
// from Instance. If this returns False, the change must 
// not be made and should be aborted.
      bool OnComponentChanging();
        
   // called after a change or changes have been made to 
// the object returned from Instance
      void OnComponentChanged();
}

As you can see, there's quite a bit to TypeConverter. A brief description of ITypeDescriptorContext is also included, because it appears as a parameter to most of TypeConverter's methods. ITypeDescriptorContext allows access to services as well as the component object that supplied any values that are passed to TypeConverter.

However, a closer look at TypeConverter reveals that it is not as complicated as it appears. Its functions break down into four groups:

  • Value conversion: Clients such as the property browser routinely need to easily convert a type to and from a string representation. TypeConverter provides a standard methodology for this, as well as a way to prequalify whether a given conversion is possible. Since conversion to and from a string is most common, TypeConverter provides helper methods for this ConvertFromString and ConvertToString (not shown).
  • Instance creation and persistence: TypeConverter assumes the responsibility for creating instances of a given type. Rather than forcing clients to inspect constructors or know default values, TypeConverter allows type authors to have control over this process. In this way, TypeConverter also plays a role in persisting components to and from code in the designers. It can both create and consume a PersistInfo object, which describes what parts of an objects state are relevant for persistence (usually to and from code) and how to go about persisting them.
  • Subproperties methods: TypeConverter allows objects to control the subproperties that will be exposed in the property browser. System.Drawing.Font is an example of this; the properties that are shown when an instance is expanded do not perfectly match up with the actual properties on the Font object itself. They are changed and reordered for clarity and usability.
  • Standard values: Many types, such as enums, have a defined subset of values. Other types may have a predefined set but may accept other values. TypeConverter allows clients to inspect these lists and check if values not in the list are acceptable.

A TypeConverter can be associated with a property in one of two ways. Types can specify their own TypeConverter by having a TypeConverterAttribute on the class declaration. Often these classes have their converter as a nested class so the Type and design-time information are one unit. Alternatively, properties can override the TypeConverter associated with the property type by specifying a TypeConverter on the property declaration itself. Of course, the TypeConverter specified there must have knowledge of the particular type. You can't specify just any TypeConverter, but this method can be useful if you want to tweak how a given property is displayed, say to add or remove sub properties or add support for different string conversions. You can accomplish this tweaking by deriving from the default TypeConverter for a type, and specifying the derived type as the TypeConverter for a given property. The other half of interfacing with the property browser involves property editors. Property editors are much less structured than TypeConverters. In order to leave the possibility open for editors that do not rely on or link with Win Forms, there is no base Editor type. Because editors are generally User Interface (UI) driven, all of the standard editors in the .NET Framework are derived from System.Drawing.Design.UITypeEditor.

public class UITypeEditor {
    
   // called when a value is to be edited.
      public virtual object EditValue(
ITypeDescriptorContext context, IServiceObjectProvider provider, 
object value);

   // specifies whether this editor can paint 
// representations of values.
      public virtual bool GetPaintValueSupported(
ITypeDescriptorContext context);

   // returns the ui style of this editor, if any.  
      public virtual UITypeEditorEditStyle GetEditStyle(
ITypeDescriptorContext context);
        
   // called when the client wants the UI representation 
// of a value to be painted
      public virtual void PaintValue(
ITypeDescriptorContext context, 
object value, 
Graphics canvas, 
Rectangle rectangle);      
}

public enum UITypeEditorEditStyle {
   
   // No interactive UI component
      None = 1,

   // Modal UI.  Properties will display a […] 
// button to launch a dialog.
      Modal = 2,

   // Dropdown UI.  Properties will display a 
// down arrow button and UI will be
   // hosted in dropdown similar to combobox. 
      DropDown = 3
}


// this interface can be retrieved by calling 
// provider.GetServiceObject(typeof(IwinFormsEditorService)) 
// in UITypeEditor::EditValue.
public interface IWinFormsEditorService {
        
   // close the dropdown that is currently being shown
        void CloseDropDown();
        
   // host the given control in a dropdown for UI 
// editing of a value.
      void DropDownControl(Control control);
        
   // launch a modal dialog for editing a property value.
      DialogResult ShowDialog(Form dialog);
}

The EditorAttribute, like the TypeConverterAttribute, can be specified either on the Type that they apply to or on a property of a particular type, which overrides any value specified on the Type itself.

When a user clicks a dropdown or ellipsis button on the property browser, UITypeEditor.EditValue is called. Here is what a typical implementation of EditValue looks like for a dropdown editor:

public override object EditValue(
ITypeDescriptorContext context, IServiceObjectProvider provider, 
object value) {
                object returnValue = value;

                if (provider != null) {
IWinFormsEditorService edSvc = (IWinFormsEditorService)
provider.GetServiceObject(
typeof(IWinFormsEditorService));

                    if (edSvc != null) {
         MyUIEditorControl uiEditor = 
new MyUIEditorControl(edSvc);
                        edSvc.DropDownControl(uiEditor);
                        value = uiEditor.NewValue;
                    }
                }

                return value;
 }

PaintValue allows an editor to display a visual representation of a particular value. The WinForms objects use this editor for images, colors, and fonts, for example.

Figure 2. Editor display

A code-based example is:

public override void PaintValue(
ITypeDescriptorContext context, 
object value, 
Graphics canvas, 
Rectangle rectangle) {
            if (value is Color) {
                Color color = (Color)value;
                SolidBrush b = new SolidBrush(color);
                canvas.FillRectangle(b, rectangle);
                b.Dispose();
            }
        }

               ColorEditor's PaintValue code.

In Through the Out Door: Persistence Through Code

Unlike some past designers, the Win Forms and other VS .NET Designers for the .NET Framework components rely only on code persistence for form state. No magic formats, no hidden data, just good plain code. Of course, things like bitmaps and localized strings are packaged along with the code as binary data, but the state of the component and anything it comprises is persisted though code. When you make changes in the designer, code is generated. If you manipulate that code, it is reparsed and the changes are reflected in the designer.

Designers in the .NET Framework have provided all the plumbing to take advantage of this feature. All the designers want to know for any Type is:

  1. What information about the state of the object is interesting for persistence?
  2. How can that information be turned back into a live object?

This was discussed briefly in the prior section. Again, TypeConverter is at the heart of that process. Code-generating and code–parsing designers are concerned with a particular type of PersistInfo called CreationBundle. For example:

[TypeConverter(typeof(IntBoolString.IntBoolStringConverter))]
   public class IntBoolString {
      private int intVal;
      private string stringVal;
      private bool boolVal;

      public IntBoolString(string s, int I, bool b) {
         This.intVal = I;
         This.stringVal =s ;
         This.boolVal = b;      
      } 
      public bool Bool{
         get {return boolVal;}
         set {boolVal = value;}
      }

      public int Int {
         get {return intVal;}
         set {intVal = value;}
      }

      public string String {
         get {return stringVal;}
         set {stringVal = value;}
      }

      public override string ToString() {
         return intVal + "," + boolVal + "," + stringVal;
      }

      public class IntBoolStringConverter : TypeConverter {

         public override bool CanConvertFrom(
ITypeDescriptorContext context, 
Type sourceType) {
            return (sourcetType == typeof(string));
         }

            public virtual object ConvertFrom(
ITypeDescriptorContext context, 
object value, 
object[] arguments) {

            if (value is string) {
               string stringValue = (string)value;
               int  intValue;
               bool boolValue;

int commaIndex = 
stringValue.IndexOf(',');

               if (commaIndex != -1) {
                  intValue = Int32.
Parse(stringValue.
Substring(0, commaIndex));
                  commaIndex = stringValue.
IndexOf(',', 
commaIndex + 1);
                  if (commaIndex != -1) {
                     int nextComma = stringValue.IndexOf(',', commaIndex + 1);
                     if (nextComma != -1) {
                        boolValue = Boolean.Parse(stringValue.Substring(commaIndex+1,
                           nextComma - commaIndex));
                        stringValue = stringValue.Substring(nextComma+1);
                        return new IntBoolString(intVal, boolVal, stringValue);
                     }

                  }
               }
               throw new FormatException("Can't convert '" + stringValue + "' to IntBoolString Object");
            }
         }


            
         public override PersistInfo GetPersistInfo(ITypeDescriptorContext context, object value) {
            if (value is IntBoolString) {
               IntBoolString ibs = (IntBoolString)value;

               return new CreationBundle(typeof(IntBoolString), null,
                  new CreationArgument[] {
                     new CreationArgument(ibs.Int, typeof(Int32)),
                        new CreationArgument(ibs.Bool, typeof(bool)),
                        new CreationArgument(ibs.String, typeof(string))});
            }

            return base.GetPersistInfo(context, value);
         }
      }

      public override object CreateInstance(ITypeDescriptorContext 
                              context, IDictionary propertyValues) {
         return new IntBoolString((int)propertyValues["Int"],
            (bool)propertyValues["Bool"],
            (string)propertyValue["String"]);
      }

      public override bool GetCreateInstanceSupported(ITypeDescriptorContext context) {
         return true;
      }

   }

A benefit in using CreationBundle objects is that they know how to create the object for which they are storing the information if it has a constructor that matches each of the types of the CreationArguments that are passed in. The default implementation of TypeConverter calls CreationBundle::Invoke when TypeConverter::CreateInstance is called and attempts to create and initialize an object in this way. If a constructor is not available, the CreateInstance call taking an IDictionary allows for more customized creation of an object. The IDictionary passed in contains the depersisted values for each property name.

Components often have properties with values composed of more than one object. Other frameworks usually use property arrays for this purpose. However, arrays have several disadvantages. For example, arrays need to be copied when they are handed out and copied again when handed back in, resulting in a performance problem. They are also unable to provide intelligent notification when values are added, modified, or deleted. In fact, if a property hands back an array, it's a lot of work to add or delete items at all. Arrays are also a snapshot value and won't update if the underlying object changes.

Instead, the .NET Framework uses collections, which are objects that implement ICollection, for this purpose. An object can create a collection and hand it to any other object and the reference will stay up-to-date with any changes in the underlying object. It will also notify the object if another object makes a change to it. For the .NET Framework designers to use collections, they also need to support an All property that has a get and a set, and whose type is an array of the objects that the collection would hold. For example:

   public class IntCollection : ICollection {
      private int[] values;

      public IntCollection(int[] intValues) {
         this.values = (int[])intValues.Clone();
      }

      public int[] All {
         get {
            return (int[])values.Clone();
         }
         set {
            values  = (int[])value.Clone();
         }   
      }

      public int Count {
         get {
            if (values == null)  {
               return 0;
            }
            return values.Length;
         }
      }


      [Browsable(false)]
      public object SyncRoot {
         get {
            return this;
         }
      }

      [Browsable(false)]
      public bool IsReadOnly {
         get {
            return false;
         }
      }

      [Browsable(false)]
      public bool IsSynchronized {
         get {
            return true;
         }
      }
   }

The .NET Framework persistence mechanism is able to persist and depersist this Collection. If the collection is composed of more advanced types, such as the BoolIntString sample type above, all that is required is that the TypeConverter associated with the type create a valid PersistInfo (specifically, CreationBundle for the VS .NET designers) for each item in the collection.

Component Designers

As mentioned earlier, the built-in designers in the .NET Framework will suffice for the vast majority of components. Nonetheless, the .NET Framework includes a fully extensible architecture for component designers. All designers are based on the System.ComponentModel.Design.IDesigner interface, listed below:

public interface IDesigner {

        // the component associated with this designer
        IComponent Component {get;}
        
        // design-time verbs associated with the component, 
        // such as "Add Tab" for a TabControl
        DesignerVerb[] Verbs {get;}

        // disposes any resources used by a designer.  
        // The designer is unusable after this call.
        void Dispose();

        // called to ask the designer to perform the "default action".  
        // This is usually called in response to a double click
        // on the component at runtime.
        void DoDefaultAction();
        
        // Initializes the designer with the given component.
        void Initialize(IComponent component);
   }

As you can see, IDesigner is straightforward. A designer is associated with a component through the DesignerAttribute:

 [Designer("MyNameSpace.Design.MyComponentDesigner, MyNameSpace.DLL")]
Public class MyComponent : Component {
}

If a DesignerAttribute is not present on the class, the class hierarchy is traversed until a designer is located. In the preceding example, the default ComponentDesigner would be located on the Component base class, and that would be used. Some designers present a UI and some do not. In the case of ComponentDesigner, you'll see an icon representing the objects, as components generally do not have a UI. Win Forms controls, on the other hand, have designers that show the actual control at design time.

Figure 3. Win Form control at design time

Note that controls that show no UI are just icons below the designer, and the Win Forms controls that have a UI are shown in the Form Designer as they would be at run time. All of these are built upon the IDesigner foundation. Often, designers for controls hook the WindowProc of the control they are designing (designers that derive from System.WinForms.Design.ControlDesigner can simply override the WndProc method to accomplish this) to perform such complex tasks as hit testing. However, for most components the built-in, default designers should be sufficient.

Accessing Designer Services and Infrastructure

The .NET Framework Designer in VS .NET exposes many services and infrastructure components that simplify complex operations or allow a designer to learn about the state of other parts of the designer. These services are always accessed through an IServiceObjectProvider using the GetServiceObject method. Here is a listing of some interesting designer services:

Type Description
IDesignerHost Main class associated with any top-level designer. Provides methods to add services, create and site components, remove components, and batch operations together.
IComponentChangeService Provides notification when a component is added, removed, renamed, or modified.
ISelectionService Sets or gets the items that are currently selected in the designer.
IToolboxService Allows inspection and modification of the items on the toolbox, their selection state, and so on.
IUndoService Provides facilities for creating undo/redo units for actions, and manages the undo/redo stacks.
IHelpService Allows setting help topics or invoking help items.
IMenuCommandService Allows handling of designer menu commands and verbs.
IReferenceService Maps references in a designer to objects. For example, name "button1" to component button1).

IDesignerHost is the basis of all the designers in VS .NET. The IDesignerHost is also an IServiceObjectProvider, and is a dynamic way to add and remove services. It also provides the method to create components to ensure that they are sited properly. Here is an example of the use of the IDesignerHost to create a component:

Public class MyDesigner : IDesigner {
   
   // …..

      Private void OnSurfaceDoubleClick(object s, EventArgs e) {
   IDesignerHost host = 
      (IDesignerHost)this.Component.Site.GetServiceObject(typeof(IDesignerHost));
If (host != null) {
   Object newComponent = host.CreateComponent(typeof(MyComponent));
   DoSomethingInterestingWithComponent(newComponent);
}      
      }

   // …
}

Licensing Your Components

In the .NET Framework model of extensibility, the licensing architecture is also extensible. For ease of use, the framework defines a built-in, standard licensing method for controlling whether or not your components are licensed for design-time usage, but developers are free to replace this scheme with any they see fit.

// Add the LicenseProviderAttribute to the control.
[LicenseProvider(typeof(LicFileLicenseProvider))]
public class MyControl : RichControl {
   // Create a new null license.
   private License license = null;
   public MyControl () {
      // Add Validate to the control's constructor.
      license = LicenseManager.Validate(typeof(MyControl), this);
      // Perform other instantiation tasks…
   }
   public override void Dispose() {
      if (license != null) {
         license.Dispose();
         license = null;
      }
   }
   protected override void Finalize() {
      Dispose();
      base.Finalize();
   }
}

This example will enable licensing using the built-in licensing support. The LicFileLicenseProvider simply looks for a file called <classname>.lic in the same directory as the Assembly for the class, where classname is the full type name. For the type String, the name would be System.String.lic. The file would contain the string "System.String is a licensed component." If this file is found, LicenseManager.Validate returns a License object, which should be disposed along with the class instance.

Implementing your own license scheme is easy as well. Just create your own class that derives from LicenseProvider and implement your own GetLicense method. You might implement a registry based licensing scheme where licensed design-time components have an entry in the registry:

  public class
 RegistryLicenseProvider: LicenseProvider {
public override License GetLicense(
LicenseContext context, 
Type type, 
object instance, 
bool allowExceptions) {

      RegistryKey licenseKey = Registry.LocalMachine.
OpenSubKey("Software\\MyCompany\\ComponentLicenses");

if (context.UsageMode == LicenseUsageMode.Designtime) {
if (licenseKey != null && licenseKey.GetValue(type.FullName) != null) {
               return new RegLicense(this, type);
            }
            if (allowExceptions) {
               throw new LicenseException(type, instance,
                  "Couldn''t get design-time license for ''"" + 
                                          type.FullName + "''");
            }
            return null;
         }
   else {
      return new RuntimeRegLicense(this, type);
   }
      }

      private class RuntimeRegLicense : License {
         public string LicenseKey {
            get {
               return type.FullName;
            }
         }
         public override void Dispose() {
         }


      }

      private class RegLicense : License {
         private RegistryLicenseProvider owner;
         private Type type;

         public RegLicense(RegistryLicenseProvider owner, Type type) {
            this.owner = owner;
            this.type = type;
         }

         public string LicenseKey {
            get {
               return type.FullName;
            }
         }
         public override void Dispose() {
         }
      }

      [LicenseProvider(typeof(RegistryLicenseProvider))]
      public class MyControl : Control {
      }

Components that use this license will have an entry in the registry under the following:

HKEY_LOCAL_MACHINE\Software\MyCompany\ComponentLicenses
                  <Type full name>="true"


         

Conclusion

Writing controls in managed code offers many benefits over the traditional C++/COM methodology. Microsoft has engineered everything from the ground up, from the common language runtime to C# to the retooled Visual Basic language, to provide developers with efficient, common methodologies to build software with the least number of obstacles possible. The Microsoft® .NET Framework is the first example of a large code base developed with these technologies and principles, and integrated designer support is a pivotal aspect of this methodology.