Posted by: Brian Pepin | June 20, 2008

Using Visual Studio Whidbey to Design Abstract Forms

A long time ago I posted an article explaining why Visual Studio can’t design abstract forms. I also promised that I’d show you a way you could make it work in Whidbey using Whidbey’s type description provider mechanism. Well, a long time has passed and I never wrote the follow-up. It’s time I fulfill my promise.

Type Description Providers

The heart of my mechanism is a custom TypeDescriptionProvider. I need to give you a little background on this new and powerful way of providing metadata to designers. First, while Visual Studio does use reflection to get to the properties and events on objects, it never uses System.Reflection directly. Instead, it uses a class called TypeDescriptor which is found in the System.ComponentModel namespace. In Visual Studio 7.0 and 7.1, TypeDescriptor was built on top of System.Reflection. It provided a simpler API for designers t use, and it also provided a simple extensibility mechanism through the ICustomTypeDescriptor interface.

We have completely redesigned TypeDescriptor’s internals for Visual Studio Whidbey. Instead of being built on reflection, TypeDescriptor is now built on TypeDescriptionProviders. There is a default TypeDescriptionProvider that uses reflection, but anyone can attach a provider to any class or instance they choose. This provides you with a huge increase in flexibility, as you now have control over the implementation of the entire TypeDescriptor API.

I’m going to write a custom TypeDescriptionProvider for my abstract for that takes some “creative liberties” with type metadata.

The Abstract Form

In my example, I have an abstract form called, not surprisingly, “AbstractForm”. It also defines a single abstract property called AbstractProperty:

public abstract class AbstractForm : Form {
    public abstract string AbstractProperty { get; set; }
}

It is impossible to create an instance of this class unless I derive from it and implement the abstract property. So, I have a class that does that too:

internal class ConcreteForm : AbstractForm {
    string _abstractProperty;

    public override string AbstractProperty {
        get { return _abstractProperty; }
        set { _abstractProperty = value; }
    }
}

My goal is to make things that derive from AbstractForm really look like they're deriving from ConcreteForm. My first step in doing this is to put an attribute on AbstractForm that declares that it, and all classes that derive from it, should receive a custom type description provider:

[TypeDescriptionProvider(typeof(ConcreteClassProvider))]
public abstract class AbstractForm : Form {…}

Now I need to write a ConcreteClassProvider that does all of the dirty work. I'll cover that in the next section.

ConcreteClassProvider

When I associated my type description provider to AbstractForm above, what I'm telling TypeDescriptor is that I know how to supply all metadata for AbstractForm and all of its derived types. If I had to write all of that logic myself it would be very complex and I probably wouldn't get it right. Luckily, TypeDescriptionProvider has a constructor that takes an existing provider and delegates to it. By using this constructor all I have to do is override the areas I'm interested in and the rest will be taken care of for me. So, let's dig in and write the skeleton for ConcreteClassProvider:

internal class ConcreteClassProvider : TypeDescriptionProvider {
    public ConcreteClassProvider() :
        base(TypeDescriptor.GetProvider(typeof(AbstractForm))) {
    }
}

There are a couple of noteworthy things about this code:

  1. It is internal, but its constructor is public. TypeDescriptor does not require your class to be public, but it does require the constructor to be public. I've made the class internal because "there are no user-serviceable parts inside". It just isn't interesting for anyone other than TypeDescriptor to get at this class, and TypeDescriptor always gets at it through its public base class.
  2. In my constructor, I invoked the base class's constructor and I passed in the current type description provider for AbstractForm. In this way my custom provider behaves no differently than the default provider except for the places where I override methods.

I've now setup a way that I can hook into the metadata that is offered by AbstractForm. One thing to keep in mind is that I am actually hooked into providing metadata for AbstractForm as well as all classes that derive from it. I have to be careful here. A simple mistake could make all derived classes look just like AbstractForm, which isn't what I want!

There are several virtual methods on TypeDescriptionProvider, but I'm only concerned with one thing: I want ConcreteForm to replace AbstractForm. To do this there are two methods I need to override: GetReflectionType and CreateInstance. Let's cover each of these in turn.

When the form designer needs to query type information, such as type.IsAbstract, for example, it always uses TypeDescriptor.GetReflectionType to obtain the type it should be reflecting against. This method finds the TypeDescriptionProvider for the type and calls, not surprisingly, GetReflectionType on the provider. The default provider simply returns the type passed into it. I want my provider to return ConcreteForm so that the designer thinks it is designing a concrete class:

public override Type GetReflectionType(Type objectType, object instance) {
    if (objectType == typeof(AbstractForm)) {
        return typeof(ConcreteForm);
    }
    return base.GetReflectionType(objectType, instance);
}

With this code the designer no longer thinks forms derived from AbstractForm are abstract classes. Instead, it sees ConcreteForm, which is not abstract.

That's half the story. As soon as the designer determines that the base class isn't abstract it will try to create an instance of it. That will lead to problems – it will try to create an instance of AbstractForm, fail, and put me right back where I started. That's where the second override comes in. I need to override CreateInstance so I create an instance of ConcreteForm, not AbstractForm:

public override object CreateInstance(
    IServiceProvider provider,
    Type objectType,
    Type[] argTypes,
    object[] args) {

    if (objectType == typeof(AbstractForm)) {
        objectType = typeof(ConcreteForm);
    }

    return base.CreateInstance(provider, objectType, argTypes, args);
}

Now I'm ready to try it out. After building the project I invoked the New Inherited Form dialog. What's this? I don't see AbstractForm as an option in the dialog. It turns out that the inheritance dialog doesn't use GetReflectionType – it should, and I'll follow up on that. Don't despair, though, we just need to fiddle with the code a little bit. Instead, I created a standard Form, opened the code, and changed the form to derive from AbstractForm. When I opened the designer…it actually opened. What's more, I can even change the value of AbstractProperty in the property window. Of course, as my new form is actually deriving from AbstractForm I do get compile errors unless I implement AbstractProperty on my new form class. That's actually great news – it is why I wanted an abstract class in the first place.

Generalizing ConcreteClassProvider

This mechanism works, but it only works for a single class. What happens if I want to support lots of abstract classes? I wouldn't want to write a ConcreteClassProvider for each and every class, would I? Instead, I want to generalize ConcreteClassProvider so it can be used to provide a concrete class for any abstract class.

The challenge lies in getting our general-purpose ConcreteClassProvider to know what concrete class should be used for a given abstract class. To do this, I've defined a new attribute called ConcreteClassAttribute:

[AttributeUsage(AttributeTargets.Class)]
internal class ConcreteClassAttribute : Attribute {
    Type _concreteType;
    public ConcreteClassAttribute(Type concreteType) {
        _concreteType = concreteType;
    }

    public Type ConcreteType { get { return _concreteType; } }
}

Then, I changed ConcreteClassProvider to look for this attribute on a type when it needs to locate the concrete class. To use it, I need to declare both my custom provider and my new attribute on a class:

[TypeDescriptionProvider(typeof(GeneralConcreteClassProvider))]
[ConcreteClass(typeof(GeneralConcreteForm))]
abstract partial class GeneralAbstractForm : Form {
    public GeneralAbstractForm() {
        InitializeComponent();
    }

    public abstract string AbstractProperty { get; set; }
}

I now have a mechanism I can use for a large scale project.

Summary

While it is a bit of a drag that the Windows Forms designer doesn't support abstract classes, by using this simple technique you can trick it into doing so. This requires at least Beta 1 of Visual Studio Whidbey. As I found when trying to use the inheritance picking dialog, there may still be pitfalls here, so before going hog wild with your own projects you should make sure this technique works for you in all the scenarios you care about. Microsoft doesn't officially support designing abstract base classes, so I doubt they'd be very motivated to hotfix any problems you find.

Posted by: Brian Pepin | June 20, 2008

System.ComponentModel

Just about anyone who has programmed in the .NET Framework has come across the System.ComponentModel namespace. The Windows Forms, ASP.NET and data classes all implement interfaces from this namespace. But other than that, most people don’t know what this namespace is for. Most think it is just a random collection of stuff necessary to support designers. Well, that’s partially true: designers make extensive use of this namespace. System.ComponentModel is not just for designers, however. It provides a group of generally useful interfaces and classes you can use in your own applications. In current versions of the .NET Framework all we’ve really done with component model is provide a useful design pattern; we haven’t really made much use of it outside of designers. This post will show you how you can use this namespace in your own applications, utilizing the same design patterns we’ve used in the designer.

Components, Sites and Containers

At the core of System.ComponentModel is an interface called IComponent. IComponent doesn’t have much to it: it derives from IDisposable, offers a Site property and raises an event when the component has been disposed. Anything that implements this interface is called a component. I’ve drawn a picture relating components, sites and containers below:

Components have two important characteristics:

  • They can be owned by a container
  • They can retrieve services from the container that owns them

The first characteristic doesn’t get a component very far, but it does allow a component’s lifetime to be controlled by a container. The second characteristic is far more interesting. It allows a component to gain access to services from other parts of the application. A service is simply an instance of an object that is stored in a dictionary keyed off of the object’s type. One part of an application can provide a service for another part of the application to use. The power of services lies in their loose coupling: an application publishes the interface or base class that defines the service, but does not publish the class that implements the service. Components can request the service and retrieve an instance they can use, but never have to know about the actual implementation nor where it comes from. This design pattern allows you to develop extremely large applications because each of the parts of the application is only loosely coupled to the other.

Visual Studio makes extensive use of this service pattern for software design, as do the designers we provide within the framework. You can use this pattern in your own applications, too. As an example of this, let’s take a look at a service that is already defined in Windows Forms: the AmbientProperties service.

Windows Forms AmbientProperties Service

Try this: change the font of a Form and then place a button on the form. What happens? The button receives the forms font. Why? Because all controls in Windows Forms have several properties that, if unset, ask their parent for a value. This allows you to set fonts and colors once, and have them flow down to child controls. Of course, if you don’t set a font on the form a default font is used. Where do you think Windows Forms gets the default font? It actually checks two places: first, it checks to see if it can get to a service that will tell it what font to use. If it can’t get to this service it then asks Windows for the default dialog font for the user.

The service Windows Forms looks for is called AmbientProperties and has been sitting in the Windows Forms namespace since the framework originally shipped. By using a container to site all of the dialogs in your application, you can setup application-wide fonts and colors. Our first step is to modify Main so we can create a container for our application:

[STAThread]
static void Main()
{
    AppContainer c = new AppContainer();
    Form1 f = new Form1();
    c.Add(f);
    Application.Run(f);
}

 
AppContainer is an internal class I wrote that derives from Container. It implements a single service: AmbientProperties. The entire class is shown below:

internal class AppContainer : Container
{
    AmbientProperties _props;
    protected override object GetService(Type service)
    {
        if (service == typeof(AmbientProperties))
        {
            if (_props == null)
            {
            _props = new AmbientProperties();
            _props.Font = new Font("Arial", 16);
            }
        return _props;
        }
    return base.GetService(service);
    }
}

If you run this code, and have controls on Form1, you'll see that their fonts have enlarged to be 16 points in size. In addition, you can make other forms also have this same behavior by siting them as well. The following code does this in a button click handler for a button I have on form1:

Form2 f = new Form2();
if (Site != null) Site.Container.Add(f);
f.ShowDialog();

You can add other services to the AppContainer class that can be used elsewhere in your application.

Service Containers

In the above example I showed you a really simple mechanism for providing services. Even this simple mechanism has a big advantage: the service wasn't created until someone asked for it. This is what allows large applications to scale well: they can have vast lists of services but those services are not actually instantiated until someone needs them. This pay for play enhances the performance of the application.

In my previous example I specifically check for a service type of AmbientProperties and create the object on demand. This technique has its drawbacks, however. For one thing, as the number of services I want to supply increases, I need to forever expand my if statement. More importantly, only my main method today can offer services. One very powerful concept of the service mechanism is that anyone can provide services to anyone else. The component model namespace can help here too: in addition to service providers, it also defines a service container. As their name implies, service containers contain a table of services. Perfect. Even better, service containers support delayed instantiation of service objects. Let's look at what it would take to change my sample to use service providers.

First, I am going to use a pre-built class in System.ComponentModel called ServiceContainer that implements the IServiceContainer interface for me. I will plug this into my AppContainer class as follows:

internal class AppContainer : Container
{
    ServiceContainer _services = new ServiceContainer();

    internal IServiceContainer Services
    {
        get { return _services; }
    }

    protected override object GetService(Type service)
    {
        object s = _services.GetService(service);
        if (s == null) s = base.GetService(service);
        return s;
    }
}

Notice that my AppContainer class no longer has any code in it to handle AmbientProperties. It simply routes any service requests into the service container, and calls base if the service wasn't found in the service container. Now that I've done this, I need to add AmbientProperties back into the service container. I do this in Main:

[STAThread]
static void Main()
{
    AppContainer c = new AppContainer();
    AmbientProperties p = new AmbientProperties();
    p.Font = new Font("Arial", 16);
    c.Services.AddService(typeof(AmbientProperties), p);
    Form1 f = new Form1();
    c.Add(f);
    Application.Run(f);
}

A quick run of the code shows that I still have my big 16 point font in my dialog. But, I lost something here. I've lost the pay for play feature I had before. Now, even if no one ever asks for AmbientProperties, I've still created it, and that takes resources. I'd like to only create AmbientProperties if someone asked for it. Can I still use service containers? You bet! Service containers can accept a callback delegate in place of a service instance. Let's just modify Main a bit more:

[STAThread]
static void Main()
{
    AppContainer c = new AppContainer();
    c.Services.AddService(typeof(AmbientProperties),
    new ServiceCreatorCallback(OnCreateService));
    Form1 f = new Form1();
    c.Add(f);
    Application.Run(f);
}

Here I've supplied a callback delegate instead of an instance of AmbientProperties. This delegate will be invoked the first time someone asks for AmbientProperties. My implementation of OnCreateService is pretty straightforward too:

private static object OnCreateService(IServiceContainer c, Type service)
{
    AmbientProperties p = new AmbientProperties();
    p.Font = new Font("Arial", 16);
    return p;
}

The final thing that ServiceContainer provides for me is an instance of IServiceContainer as -- you guessed it -- a service! Anyone who is sited in my AppContainer class can call GetService(typeof(IServiceContainer)) and add their own services to the application. In the next section I'll demonstrate how you can put that to use.

Services in your Application

Using the IServiceContainer service provided by ServiceContainer (wow, thats a lot of "service" terminology) you can add services from anywhere in your application. For example, say youve written an MDI application and you have a status bar on the bottom of your main MDI window. You can add the status bar as a service in your forms Load event:

private void Form1_Load(object sender, System.EventArgs e)
{
    IServiceContainer s = GetService(typeof(IServiceContainer)) as IServiceContainer;
    s.AddService(typeof(StatusBar), statusBar1);
}

 
Then, you can use the status bar in other MDI children within your application:

private void Form2_Load(object sender, System.EventArgs e)
{
    StatusBar sb = GetService(typeof(StatusBar)) as StatusBar;
    if (sb != null) sb.Text = "My Status";
}

Notice that I am checking for null when I call GetService for the status bar. This is one of the fundamental things to remember about services: you should have some sort of fallback if the service isn't around. Here, the consequences of not having status text are not major, so I simply handle null by doing nothing. If I needed the service as a requirement of my application, I would probably display an error to the user, or log an error to the event log so support personnel could diagnose the problem.

Wrapping Up

Well, hopefully your eyes aren't glazing over yet. The component model provides a simple design pattern you can use in your applications. Using containers and sites to offer services to the various parts of your application can help your development process scale better as your application becomes more complex. Once you get into the service groove you'll find them quite flexible and easy to use.

Posted by: Brian Pepin | June 20, 2008

New Blog

For the last few years I’ve maintained a web site at www.urbanpotato.net. It was my first major ASP.NET project and it’s showing its age. It is just not a joy to add content to, and now that I’ve got a toddler I have little time for re-coding. So I’m going to try the WordPress thing and see what it’s like to use someone else’s software.

I’m going to copy a select set of my technical posts from Urban Potato to this blog as a seed. Let’s see if I can keep this one a little more up-to-date.

Categories

Follow

Get every new post delivered to your Inbox.