I've got some tips for people who say the TypeDescriptionProvider
by Juan Carlos Diaz is not working and don't like the conditional compilation neither:
First of all, you may have to restart Visual Studio for the changes in your code to work in the form designer (I had to, simple rebuild didn't work - or not every time).
I will present my solution of this problem for the case of abstract base Form. Let's say you have a BaseForm
class and you want any forms based on it to be designable (this will be Form1
). The TypeDescriptionProvider
as presented by Juan Carlos Diaz didn't work for me also. Here is how I made it work, by joining it with the MiddleClass solution (by smelch), but without the #if DEBUG
conditional compiling and with some corrections:
[TypeDescriptionProvider(typeof(AbstractControlDescriptionProvider<BaseForm, BaseFormMiddle2>))] // BaseFormMiddle2 explained below
public abstract class BaseForm : Form
{
public BaseForm()
{
InitializeComponent();
}
public abstract void SomeAbstractMethod();
}
public class Form1 : BaseForm // Form1 is the form to be designed. As you see it's clean and you do NOTHING special here (only the the normal abstract method(s) implementation!). The developer of such form(s) doesn't have to know anything about the abstract base form problem. He just writes his form as usual.
{
public Form1()
{
InitializeComponent();
}
public override void SomeAbstractMethod()
{
// implementation of BaseForm's abstract method
}
}
Notice the attribute on the BaseForm class. Then you just have to declare the TypeDescriptionProvider
and two middle classes, but don't worry, they are invisible and irrelevant for the developer of Form1. The first one implements the abstract members (and makes the base class non abstract). The second one is empty - it's just required for the VS form designer to work. Then you assign the second middle class to the TypeDescriptionProvider
of BaseForm
. No conditional compilation.
I was having two more problems:
- Problem 1: After changing Form1 in designer (or some code) it was giving the error again (when trying to open it in designer again).
- Problem 2: BaseForm's controls was placed incorrectly when the Form1's size was changed in designer and the form was closed and reopened again in the form designer.
The first problem (you may not have it because it's something that haunts me in my project in few another places and usually produces a "Can't convert type X to type X" exception). I solved it in the TypeDescriptionProvider
by comparing the type names (FullName) instead of comparing the types (see below).
The second problem. I don't really know why the base form's controls are not designable in Form1 class and their positions are lost after resize, but I've worked it around (not a nice solution - if you know any better, please write). I just manually move the BaseForm's buttons (which should be in bottom-right corner) to their correct positions in a method invoked asynchronously from Load event of the BaseForm: BeginInvoke(new Action(CorrectLayout));
My base class has only the "OK" and "Cancel" buttons, so the case is simple.
class BaseFormMiddle1 : BaseForm
{
protected BaseFormMiddle1()
{
}
public override void SomeAbstractMethod()
{
throw new NotImplementedException(); // this method will never be called in design mode anyway
}
}
class BaseFormMiddle2 : BaseFormMiddle1 // empty class, just to make the VS designer working
{
}
And here you have the slightly modified version of TypeDescriptionProvider
:
public class AbstractControlDescriptionProvider<TAbstract, TBase> : TypeDescriptionProvider
{
public AbstractControlDescriptionProvider()
: base(TypeDescriptor.GetProvider(typeof(TAbstract)))
{
}
public override Type GetReflectionType(Type objectType, object instance)
{
if (objectType.FullName == typeof(TAbstract).FullName) // corrected condition here (original condition was incorrectly giving false in my case sometimes)
return typeof(TBase);
return base.GetReflectionType(objectType, instance);
}
public override object CreateInstance(IServiceProvider provider, Type objectType, Type[] argTypes, object[] args)
{
if (objectType.FullName == typeof(TAbstract).FullName) // corrected condition here (original condition was incorrectly giving false in my case sometimes)
objectType = typeof(TBase);
return base.CreateInstance(provider, objectType, argTypes, args);
}
}
And that's it!
You don't have to explain anything to the future developers of forms based on your BaseForm and they don't have to do any tricks to design their forms! I think it's the most clean solution it can be (except for the controls repositioning).
One more tip:
If for some reason the designer still refuses to work for you, you can always do the simple trick of changing the public class Form1 : BaseForm
to public class Form1 : BaseFormMiddle1
(or BaseFormMiddle2
) in the code file, editing it in the VS form designer and then changing it back again. I prefer this trick over the conditional compilation because it's less likely to forget and release the wrong version.