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.
Note: While this is a cool and funky example of the power you wield with custom type description providers, Microsoft doesn't support abstract base classes in the designer, so if you use this technique in your own code, you are on your own.
Update: There is a bug in Visual Studio Beta 2 that may prevent this code from working. I fixed the bug in post Beta 2 builds of VS, but unfortunately the fix did not make it into Beta 2. You may want to keep this code in your back pocket until VS is finally out the door.
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 to 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 form 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.
The ConcreteClassProvider Class
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:
- 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.
- 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 The ConcreteClassProvider Class
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 as I said above, you will be on your own if something doesn't work for you.
You can download the project I used below.
AbstractBaseClass.zip (37.95 kb)