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, thusDesignModeis always false in the constructor. -
"Running in the designer" is a matter of point of view:
-
When editing a control
MyControlin the designer,MyControlis obviously in "design mode". -
When using
MyControlon a form being edited in the designer,MyControlis still in "design mode". -
But if
MyControlis placed onMyOtherControl, andMyOtherControlis placed on a form,MyControlis no longer in design mode (butMyOtherControlstill 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.