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, thus DesignMode 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 on MyOtherControl, and MyOtherControl is placed on a form, MyControl is no longer in design mode (but MyOtherControl still is)
    Strange? Not really, if you think about it (if... I didn't)

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.

11 Comments

  • Thanks so much for this article; I have been in window forms inheritance hell for several hours now. I now have a better understanding about design mode; would have never thought much about this until my inherited forms stop showing in design mode. The exceptions that were being thrown made me start thinking about what I had added to my base form recently and I started back tracking to remove problem areas. I'm finally back; but still scared for future problems.



    I do have one question... I followed your instructions on how to debug and run a new IDE. I got the point of running the new IDE, opening my project in question, opening the form in design mode that was throwing the exception. And then what, nothing happened. I put a break point in the constructor of the form, is that right place?



  • Try:

    if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)

    {

    // DesignMode

    }



    to check for DesignMode in constructor.



  • Thanks David!



    The check for (LicenseManager.UsageMode == LicenseUsageMode.Designtime) not only works inside the constructor, but also inside nested controls.



    Roland

  • Thanks a lot for this article. You saved me a lot of time and much more trouble.



    Anton

  • Thanks for the tip on LicenseManager.UsageMode.

    That really helped.

  • interesting! was wondering why my control was still always hiding some children on creation even when in design mode :)

  • Thanks. The
    if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)
    {
    // DesignMode
    }
    tip was great.

    I had a user controls on my form at one stage but removed it (along with referecens to System.Designer). But for some reason VStudio was left in a state where it kept executing code in my form at design time even with no user controls.

  • I am currently in "my design-mode properties settings are not being saved" hell. I noticed the comment about [ DefaultValue("") ], but am wondering about the ResetPROPNAME() and ShouldSerializePROPNAME() methods. Aren't these supposed to do the same thing as the DefaultValue attribute? Also, could I be having problems because all my properties are setting the sub-control's values directly? For instance;

    public string SKUDescriptionLabel
    {
    get { return lblDescDisplay.Text; }
    set { lblDescDisplay.Text = value; }
    }

    In other words, do I need a

    private string m_strMyDescLabelText = "";

    in the mix?

  • Hi Scott,

    Hmm, I have used properties setting properties of sub-controls in the past without problems, including the scenario of your example.

    Is it actually the code in your example that is causing problems, or is the sub-control a self-written control?

    For a good description of DefaultValue vs. ResetPROPNAME / ShouldSerializePROPNAME see
    http://windowssdk.msdn.microsoft.com/en-us/library/53b8022e.aspx

    The important parts are "ShouldSerialize and Reset are optional methods that you can provide for a property, if the property does not a have simple default value" and "Either apply the DefaultValueAttribute or provide ResetPropertyName and ShouldSerializePropertyName methods. Do not use both."

  • The example was just to show that I was talking to the UserControl's sub-controls 'directly' and did not have m_strIntermediateVars involved. All the sub-controls (at least at this point) are standard TextBox, Label, ComboBox type controls. Some of my properties have [DefaultValue(true)] attributes and others are using the Reset/ShouldSerialize methods, but none are mixed.

    I really don't get what the heck is going on here.

    The deal is, I've got a BUNCH of screens that are pretty much the same, so I made a UserControl (with a bunch of fields on it that can be turned on and off via propertys and methods) and dropped copies of it on several tabs of a TabControl.

    Then, when I set their settings in design mode, they seem to stick, but then I run the app and ... poof, nothing's right. All the settings are out the window.

    In fact, its even ignoring the default settings I have set up in the UC's code using ResetPROPNAME() and ShouldSerializePROPNAME().

    I guess I'll try checking the newsgroups. :/

    Thanks for your comments.
    -S

  • > Some of my properties have [DefaultValue(true)] attributes

    Sorry if I sound stupid -- just to be absolutely sure (sometimes it's better to check even the most obvious things): This is only the case for boolean properties, i.e. the "true" is just an example, right?

Comments have been disabled for this content.