Hosting Windows Forms Designers

Starting Off

Starting Off

First we'll create our class, DesignerHost, and make sure we have a reference to System.Design.dll. A lot of the interfaces we'll be using are in there. We will accept an instance of something implementing IServiceContainer as the parameter to the constructor, and since we have to provide an IServiceContainer implementation ourselves we'll simply wrap that one. Since that's the easiest part we'll do it first.

public object GetService(System.Type serviceType)
{
    return parent.GetService(serviceType);
}
public void AddService(System.Type serviceType,
System.ComponentModel.Design.ServiceCreatorCallback callback, bool promote)
{
    parent.AddService(serviceType, callback, promote);
}
public void AddService(System.Type serviceType,
System.ComponentModel.Design.ServiceCreatorCallback callback)
{
    parent.AddService(serviceType, callback);
}
public void AddService(System.Type serviceType, object serviceInstance, bool promote)
{
    parent.AddService(serviceType, serviceInstance, promote);
}
public void AddService(System.Type serviceType, object serviceInstance)
{
    parent.AddService(serviceType, serviceInstance);
}
public void RemoveService(System.Type serviceType, bool promote)
{
    parent.RemoveService(serviceType, promote);
}
public void RemoveService(System.Type serviceType)
{
    parent.RemoveService(serviceType);
}

At the same time as implementing IDesignerHost we will implement IContainer. This is the design container I spoke of before. All the code we're writing really revolves around the code that gets and instantiates designers for the objects added to the host.

IContainer.Add

This method has two overloads, one where a name is passed and one without. We'll just defer the latter to the former, passing null and dealing with it correctly. This is one of the methods with the real meat in.

The first thing we do after checking the passed component isn't null, is to check the component isn't already sited somewhere. If it is, we remove it from its current container. Then we generate a name for the component if we haven't been passed one, using INameCreationService. We'll come on to implementing this interface later. Next we make sure there isn't already a component with the passed name in the container.

The next stage is to give the new component a Site, which we do with our ISite implementation. I'll come on to that later. Giving it a site as early as possible is important, because that's how the component is able to request services from its environment. Then we make the important call to TypeDescriptor.CreateDesigner, which actually finds and created the designer associated with this component. If this is the first component being added we look for an IRootDesigner and set the rootComponent field.

Next comes a check to make sure we got a designer, and if so we initialize it. Remember the Initialize method of a designer that you override when making your own? This is where we actually call that method. After this we see if this component is an extender provider, and if so add it to the list we maintain. Yes, we have to write the functionality to keep track of those. All the magic is taken out of the designer architecture when you have to implement it yourself. Finally we add the component to our internal container instance.

public void Add(System.ComponentModel.IComponent component, string name)
{
    IDesigner designer = null;
    DesignSite site = null;
    // Check we're not trying to add a null component
    if (component == null)
        throw new ArgumentNullException("Cannot add a null component " +
        "to the container.");
    // Remove this component from its existing container, if applicable
    if (component.Site != null && component.Site.Container != this)
        component.Site.Container.Remove(component);
    // Make sure we have a name for the component
    if (name == null)
    {
        INameCreationService nameService = (INameCreationService)GetService(
        typeof(INameCreationService));
        name = nameService.CreateName(this, component.GetType());
    }
    // Make sure there isn't already a component with this name in the container
    if (ContainsName(name))
        throw new ArgumentException("A component with this name already " +
        "exists in the container.");
    // Give the new component a site
    site = new DesignSite(this, name);
    site.SetComponent(component);
    component.Site = site;
    // Let everyone know there's a component being added
    if (ComponentAdding != null)
        ComponentAdding(this, new ComponentEventArgs(component));
    // Get the designer for this component
    if (components.Count == 0)
    {
        // This is the first component being added and therefore
        // must offer a root designer
        designer = TypeDescriptor.CreateDesigner(component,
        typeof(IRootDesigner));
        rootComponent = component;
    }
    else
    {
        designer = TypeDescriptor.CreateDesigner(component,
        typeof(IDesigner));
    }
    // If we got a designer, initialize it
    if (designer != null)
    {
        designer.Initialize(component);
        designers[component] = designer;
    }
    else
    {
        // This should never happen
        component.Site = null;
        throw new InvalidOperationException("Failed to get designer for " +
        "this component.");
    }
    // Add to our list of extenderproviders if necessary
    if (component is IExtenderProvider)
    {
        IExtenderProviderService e = (IExtenderProviderService)GetService(
        typeof(IExtenderProviderService));
        e.AddExtenderProvider((IExtenderProvider)component);
    }
    // Finally we're able to add the component
    components.Add(component.Site.Name, component);
    if (ComponentAdded != null)
        ComponentAdded(this, new ComponentEventArgs(component));
}

IContainer.Remove

Although not quite as complicated as IContainer.Add, this method is responsible for the cleanup process of removing a component from the design surface.

The first couple of check we do are to make sure the component passed isn't null, and to make sure the component actually belongs to our design surface. Then we wrap the rest of the method in ComponentRemoving and then ComponentRemoved calls so that classes listening to our IComponentChangeService implementation know what's going on.

We have to remove the component from our list of extender providers if necessary, then dispose of and remove the designer associated with it. After that, we set the component's Site to null and that's all that's needed.

public void Remove(System.ComponentModel.IComponent component)
{
    ISite site = component.Site;
    IDesigner designer = null;
    // Make sure component isn't null
    if (component == null)
        return;
    // Make sure component is sited here
    if (component.Site == null || component.Site.Container != this)
        return;
    // Let the nice people know the component is being removed
    if (ComponentRemoving != null)
        ComponentRemoving(this, new ComponentEventArgs(component));
    // Remove extender provider (if any)
    if (component is IExtenderProvider)
    {
        IExtenderProviderService e = (IExtenderProviderService)GetService(
        typeof(IExtenderProviderService));
        e.RemoveExtenderProvider((IExtenderProvider)component);
    }
    // Remove the component and dispose of its designer
    components.Remove(site.Name);
    designer = (IDesigner)designers[component];
    if (designer != null)
    {
        designer.Dispose();
        designers.Remove(component);
    }
    // Let the nice people know the component has been removed
    if (ComponentRemoved != null)
        ComponentRemoved(this, new ComponentEventArgs(component));
    // Kill the component's site
    component.Site = null;
}

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“The difference between theory and practice is smaller in theory than in practice.”