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.