When your WinForms UserControl drives you nuts
WinForms user controls are that kind of technology that seems to be very easy at first, works pretty well for quite a while and then BAM! explodes in your face. I spent some hours today solving problems with a control that was working fine in the running application, but just didn't like the IDE's designer anymore, throwing exceptions all over the place. Here are some tips based on what I learned today searching Google's Usenet archive:
1. Don't take things for granted
Just because you can create a simple user control (e.g. combining a text field and a buttons) without reading any documentation, doesn't mean that knowledge about what is actually happening in design mode isn't necessary. Always keep in mind that there's some code running even though your program is not started. When a form or a control is displayed in the IDE in designer view, its (default) constructor is called which in turn calls InitializeComponent(). And this is where the trouble usually starts when this form/control contains a badly written user control.
2. Take care of your control's public properties
If you don't add any attributes to a read/write property (huh, what attributes?), two things happen:
- The property is shown in the "properties" window in the IDE.
- The initial value of the property is explicitly set in the
InitializeComponent()
method.
Hiding properties that don't make sense to be edited at design time is a good idea even if you don't intend to write a commercial-quality, bullet-proof control ready to be shipped to thousands of developers. It's simply a matter of good style and it's done with very little effort using the Browsable
attribute:
[ Browsable(false) ]
public string MyProperty
{
...
}
The second point can be quite a shocker in some cases, because it means that
- the property is read when the control is placed on a form (quick experiment: throw an exception in the "getter" and see what happens in the designer).
- the property is set at the time the control is created, not when it is actually added to the form.
As long as your control only gets or sets the value of a private member variable, things are fine. But if e.g. reading the property triggers some actions that require the control to be completely initialized, you can get into trouble (remember the "Add any initialization after the InitializeComponent call" comment in the constructor?). And setting the property "too early" can be a problem e.g. if your property's setter contains code that performs some kind of automatic update.
This behavior of the designer can be avoided by using the DefaultValue
attribute:
private string m_strMyText="";
...
[ DefaultValue("") ]
public string MyText
{
...
}
The MyText
property will not be set to "" in the InitializeComponent()
method (which would be the case without the attribute).
While you're at it, consider adding the Description
and Category
attributes as well. Adds a nice touch.
3. Don't rely on this.DesignMode
too much
The inherited DesignMode
property of your user control is an easy way to find out whether the control's code is running in the designer. Two problems:
- You can't use it in the control's constructor. At that point, the control's "Site" (which provides the actual value of
DesignMode
) hasn't been set, thusDesignMode
is always false in the constructor. - "Running in the designer" is a matter of point of view:
- When editing a control
MyControl
in the designer,MyControl
is obviously in "design mode". - When using
MyControl
on a form being edited in the designer,MyControl
is still in "design mode". - But if
MyControl
is placed onMyOtherControl
, andMyOtherControl
is placed on a form,MyControl
is no longer in design mode (butMyOtherControl
still is)
- When editing a control
4. If you just don't know what to do: debug!
Open the project's properties dialog, set the debug mode to "program", apply, set "start application" to your VS.Net IDE ("devenv.exe" in the "Common7\IDE" directory). Set some breakpoints in your code (e.g. in property getters/setters) and start debugging. A new IDE will open (empty), you load the project and then open the control in designer view. Cool stuff.