devxlogo

.NET Building Blocks: Custom User Controls in Practice

.NET Building Blocks: Custom User Controls in Practice

ccording to MSDN’s documentation (specifically Varieties of Custom Controls) there are three types of custom controls. The names Microsoft has chosen to use for these three in the documentation are confusing: composite control, extended control, and custom control. The inclusion of “custom control” as a type means you can never be sure which type is being referred to by the generic term “custom control.” The following discussion substitutes “raw control” for the third type of custom control.

Furthermore, this list of three custom controls should really be a list of four custom controls: composite control, extended user control, extended system control, and raw control, because although there’s no conceptual difference between extending a user control and extending a system control, there is a practical difference, as you will see shortly.

What You Need

  • Visual Studio 2008 or VS2005
  • C# 3.0 or 2.0 (there are a couple of spots you’ll need to adjust if using C# 2.0 in VS2005)
  • .NET Framework: 2.0 or higher
?
Figure 1. Control Class Hierarchy: The figure indicates the three types of controls defined by MSDN documentation: an extended user control derives from one of your own controls, a composite control derives from UserControl, and a raw control derives from Control.

Figure 1 shows a class diagram of the AlarmClockControl, which was discussed extensively in an earlier related article called .NET Building Blocks: Custom User Control Fundamentals. The diagram shows its inheritance from ClockControl, through several intermediaries, to the ultimate parent class of all controls, the generically named Control. The figure indicates three of the four types of controls so you can match the concepts to the implementation. The following sections provide details for each of the four types of custom controls, showing how to create each type, what Visual Studio provides to get you started, and what rendering you see in the visual designer.

Obviously not all controls have equivalent capabilities. The earlier article showed how the Visual Studio’s UserControl Test Container lets you execute disembodied controls (without embedding them in a form). One subtle question that can now be addressed is: which controls may be loadable in the UserControl Test Container? According to MSDN documentation (How to: Test the Run-Time Behavior of a UserControl), the only requirement is that your user control must have at least one public constructor. The catch, however, is what they mean by “user control.” Notice that the list of three types of custom controls from MSDN does not include “user control.” In this context, user control really means a control that derives directly or indirectly from UserControl, in other words: composite controls or extended user controls only. Extended system controls or raw controls do not qualify. That’s why the test container is called the UserControl Test Container.

Implementation Details: The Composite Control
A composite control is a collection of Windows Forms controls encapsulated in a common container derived from UserControl. This kind of control is thus referred to generically as a user control. The contained controls are called constituent controls. See Table 1.

Table 1. Composite Control Usage
Composite Control Manifestation Description
Add User Control Dialog Start with the User Control template.
Generated Code public partial class UserControl1 :
UserControl
{
??public UserControl1()
??{
?????InitializeComponent();
??}
}
The composite control derives from UserControl.
Designer Canvas The designer opens with a blank canvas.
Runnable in test container? Yes ?

Implementation Details: The Extended User Control
An extended user control derives from a single, existing control that you have created (e.g., the ClockControl), as distinct from the extended system control, a control derived from a standard .NET control (e.g., GroupBox) shown next. Table 2 provides details on the extended user control.

Table 2. Extended User Control Usage
Extended User Control Manifestation Description
Add User Control Dialog Use the Inherited User Control template. Upon pressing Add, an additional dialog opens requiring you to select the base control.
Generated Code public partial class UserControl1 :
ControlLib.ClockControl
{
??public UserControl1()
??{
?????InitializeComponent();
??}
}
The extended control derives from the base control you selected.
Designer Canvas The designer canvas renders the base control immediately.
Runnable in test container? Yes ?

Implementation Details: The Extended System Control
An extended system control derives from a single, existing standard .NET control (e.g., GroupBox), as distinct from the extended user control, a control derived from one of your own controls (such as the ClockControl discussed in the earlier article). See Table 3.

Table 3. Extended System Control Usage
Extended System Control Manifestation Description
Add User Control Dialog Start as if you are making a composite control; use the User Control template.
Generated Code In the editor change the base class to the system control that you wish to inherit from.

Compile and you will get one or two errors in the accompanying designer-generated code (xxx.Designer.cs). Just comment out the offending assignments to AutoScaleDimensions and AutoScaleMode.

Designer Canvas When you change the base class, the designer automatically updates the canvas from a blank drawing canvas to a canvas without a drawing surface. You are limited to setting properties for components you add.
Runnable in test container? No ?

Unlike the previous controls you have seen, the designer canvas presents a different rendering for this control as the table shows. For the previous controls, and indeed for standard Windows Forms, the designer has two parts: the top part is the canvas where you may drag visual controls; the bottom?the component tray?is where your non-visual components reside. That is, in the .NET framework, a control has a visual rendering while a component does not. This difference dictates the behavior when you drag one onto the designer surface from the toolbox. When you drag a control, Visual Studio renders a visual representation (e.g., a button, label, grid, etc.) on the canvas. When you drag a component, nothing shows up on the graphic canvas; instead, Visual Studio places the component in the component tray at the bottom. Whether on the canvas or in the component tray, you can still select the element and view/edit its properties in the Properties window.

?
Figure 2. A Component Without a Drawing Canvas: Extended system controls don’t use the design surface; Visual Studio places them all in the component tray. You may drag controls and components here to give you access to their properties but the controls will not be rendered visually.

However, Visual Studio is unable to render the base class for an extended system control (it’s unclear why) so it does not even attempt to show the canvas: the component tray occupies the entire designer window. That’s why you get the message in the Designer Canvas row of Table 3. You may still drag controls and components onto the designer surface, but they all go into the component tray as in the example in Figure 2.

As an aside, note that this capability supports an interactive, visually-oriented programming methodology even for non-visual components. The designer in Visual Studio lets you add components to the component tray and then set their properties in the Properties window, leveraging appropriate property editors as needed. That is, if your component has a property that is a Color, the Properties window will automatically open a color picker when you click on the value field of that property. That is a lot more intuitive than trying to decide in code whether you want Color.CornHusk or Color.VeryLightMagenta.

If you really want to have a visual rendering of your extended system control, then you need to make it a composite control instead of an extended system control, where your composition includes only one constituent control. Think back to the ProgressBarMessager control discussed in the earlier article. As discussed, the ProgressBarMessager is a composition of several elements?a ProgressBar, a Button, and several Labels. But if you simply wanted to create some type of enhanced ProgressBar, you could discard all the other controls, leaving only the ProgressBar, and it would then be viewable in the designer.

Implementation Details: The Raw Control
The raw control provides the most flexibility to handle custom rendering or custom functionality, but also requires the most work to implement, deriving from the base class of all controls, the Control. (Remember, MSDN documentation calls this type of custom control a custom control). Table 4 shows Raw Control usage.

Table 4. Raw Control Usage
Raw Control Manifestation Description
Add User Control Dialog Use the Custom Control template.
Generated Code public partial class CustomControl1 :
Control
{
???public CustomControl1()
??{
?????InitializeComponent();
??}
??protected override void
?????OnPaint(PaintEventArgs pe)
??{
?????base.OnPaint(pe);
??}
}
The OnPaint event allows you complete flexibility over how the control will render.
Designer Canvas The canvas is available but does not provide a drawing surface. You are limited to setting properties for components you add.
Runnable in test container? No ?

Implementation Details: The Abstract Parent Control

?
Figure 3. An Abstract Parent: Here, UserControl2 inherits from an abstract control, UserControl1, which prohibits the visual designer from showing UserControl2 in design view.

There is one more control variation that bears mention, though it is not really a separate type. This type of control is an inherited user control that inherits from an abstract parent. The class diagram in Figure 3 shows an example. In the diagram, UserControl2 inherits from the abstract UserControl1, which inherits from a concrete control. That base concrete control may be either a user control or a standard .NET control.

To create this abstract parent control, you begin the same way as you would a composite control, with the UserControl template. Then, like an inherited system control, change the parent class in code to your abstract class. If you then attempt to open the control (UserControl2) in the designer, Visual Studio responds with an error message similar to Figure 4. Interestingly, you can open UserControl1 in the designer just fine! Both of these simple controls are available in the downloadable ControlLib sample project.

?
Figure 4. Abstract Problems: Attempting to open the designer for a control that inherits from an abstract control produces this error.

While VS2008 has made this error report much more stylish than in VS2005, if you click on the suggested link to the MSDN forums you’ll end up in the Windows Form Designer forum?not in any specific thread. Navigating to the MSDN help link shown, on the other hand, takes you here which provides one sentence that doesn’t help much: “This error occurred because the base class of the object being passed to the designer is abstract, which is not allowed.”

For controls such as this, you’ll need to work with the control strictly in code view. One workaround for this issue involves instrumenting your code for conditional compilation, and making the class conditionally concrete.

Perhaps a more straightforward workaround is to design the control as a composite control instead of an inherited control. Your composite control could instantiate a concrete version of the abstract control as one of its constituent controls, avoiding the issue.

Integrating Your Controls with Visual Studio
Now that you have learned the ins and outs of building controls for your own libraries, the next step?perhaps the most important?is to integrate your controls into Visual Studio so they operate as cleanly, intuitively, and professionally as the native .NET framework controls. You achieve this primarily by assigning attributes to your classes, properties, methods, and events.

An attribute is essentially a property, that is, a characteristic of some object. The terms attribute and property are often considered synonymous. In XML, for example, you provide attributes for elements. In .NET, you provide properties for classes. Because you can attach attributes to almost any entity in the .NET framework?including attaching them to properties?you must take care to use the right terminology. Attaching attributes to properties is, in fact, the main technique you will see in this and subsequent sections. To attach an attribute to an element in your code, determine the attribute you want to use along with its arguments, surround it with square brackets (in C#), and place it on the line immediately preceding the element’s definition. This code fragment shows four attributes being attached to the ClockForeColor property:

   [Description("Foreground color of the clock")]   [Category("Clock")]   [EditorBrowsable(EditorBrowsableState.Always)]   [Localizable(True)]   public Color ClockForeColor   {   . . .   }

Attributes behave like other elements in the code editor in two important respects. First, Intellisense will help with auto-completion of your choice of attribute. Second, you must have the appropriate references in your project for the attribute you intend to use; otherwise you will get a compilation error.

See these MSDN documents for more information:

The document Attributes in Windows Forms Controls is probably the best reference for attributes relevant to controls. And for purists, I found this (possibly outdated) Attribute Hierarchy that contains a complete list of attributes, including some not applicable to user controls.

Table 5 lists the more universal attributes for integrating your controls into Visual Studio, along with one important aspect of integration (in the final row) that you do with documentation comments instead of attributes.

Table 5. Aspects of Integration: The table lists attributes that are commonly helpful in integrating your controls into Visual Studio. The shaded row is not an attribute; you get documentation comments by adding XML comments to code.
Attribute Operates On Description
Browsable Property, event Specifies whether a property or event should be displayed or hidden in the Properties window.
Category Property, event Organizes your property into one of the existing categories (e.g. Accessibility, Appearance, Behavior, Data, Design, etc.) in the property grid or a new one of your own choosing. Only relevant when the property grid is set to Categorized mode (as opposed to alphabetic mode).
Description Property, event Defines a short block of text that displays at the bottom of the property browser when you select a property or event.
DisplayName Property, event, method Specifies an alias for the name of a property, event, or public void method that takes no arguments. This alias is shown in the Properties window instead of the actual name of the property.
EditorBrowsable Property, event, method Specifies whether a property or method is revealed via Intellisense in the code editor.
Localizable Property Specifies that a property can be localized. Any properties that have this attribute are automatically persisted into the resources file (rather than in the designer-generated portion of code) when a user chooses to localize a form.
PasswordPropertyText Property Indicates that an object’s text representation is obscured by characters such as asterisks.
ReadOnly Property Specifies whether a property is read-only or read/write at design time. If the property has no setter accessor, then this attribute has no effect.
DefaultEvent Class Specifies the event that VS initially selects when you view the Events window for a control. Double-clicking the control in the designer generates stub code to handle this event.
DefaultProperty Class Specifies the property that VS initially selects when you view the Properties window for a control.
DefaultValue Property The .NET framework distinguishes between an initial value of a property and a default value; this attribute does not assign an initial value; it determines only whether the property is serialized in the list of property assignments in your winformapp.Designer.cs file. See MSDN for more details.
Documentation comments Class, property, event, method XML documentation comments that you apply to document your classes, methods, properties, and events are automatically incorporated by Visual Studio’s Intellisense.

EditorBrowsable Attribute
This attribute may be set to Always, Advanced, or Never. The use of Always or Never is self-evident: your code editor either will or will not display the attribute via Intellisense. The Advanced option is essentially undocumented; here’s how it works. In the Visual Studio options (see Figure 5) under Auto list members you may select or unselect Hide Advanced Members.

?
Figure 5. EditorBrowsable Attribute Dependency: When the EditorBrowsable attribute is set to Advanced and you choose to hide advanced members, tagged members will not show up in Intellisense in the code editor.
?
Figure 6. EditorBrowsable Attribute: Determines whether a property is exposed via Intellisense or not. Possible values are Always, Never, or Advanced. The Advanced setting depends on the checkbox setting from Figure 5.

When you elect to hide advanced members, properties marked with the Advanced value for the EditorBrowsable attribute will not appear in Intellisense. Note that the attribute alone does not hide members; it works in conjunction with the Hide Advance Members option. If you uncheck the Hide Advanced Members checkbox, the properties will reappear (see Figure 6).

The documentation page contains a prominent note stating that this attribute “does not suppress members from a class in the same assembly.” That is true?but not complete. Actually, the attribute does not suppress members from a class in the same solution. That is, if you use the attribute on a member of a library, it has no effect from a Windows application project in the same solution; it will be honored only if you use the library from a different solution.

Category and Description Attributes

?
Figure 7. Category and Description Attributes: Specifying a category moves your attributes from the default “Misc” category to the new or existing category label that you specify, as shown, and the Description attribute displays the specified description at the bottom of the Properties pane.

The Category and Description attributes provide contextual help in the Properties window when your control is selected in the designer. The Description attribute defines a short block of text that displays at the bottom of the property browser (see Figure 7). The Category attribute organizes your property into one of the existing categories (e.g., Behavior, Data, Layout, etc) in the property grid. You can also use it to add a custom category. Note that this attribute is applicable only when the property grid is set to Categorized mode (as opposed to Alphabetic mode). The code for ClockControl from the earlier article is shown again below with the additional lines of code for these attributes:

   namespace ControlLib   {      public partial class ClockControl :          UserControl      {         public ClockControl()         {            InitializeComponent();         }            private Color clockForeColor;            [Description("Foreground color of the clock")]         [Category("Clock")]         public Color ClockForeColor         {            get { return clockForeColor; }            set { clockForeColor = value; clockLabel.ForeColor =                ClockForeColor; }         }         private Color clockBackColor;            [Description("Background color of the clock")]         [Category("Clock")]         public Color ClockBackColor         {            get { return clockBackColor; }            set { clockBackColor = value; clockLabel.BackColor =                clockBackColor; }         }            protected virtual void timer1_Tick(object sender, EventArgs e)         {            clockLabel.Text = DateTime.Now.ToLongTimeString();         }         }   }

DefaultProperty and DefaultEvent Attributes
These two attributes operate on classes rather than properties. Conceptually, they are very similar. Each takes a string argument that must identify either a property or an event. For DefaultProperty, the string must designate the name of a property. When a user selects your control in the designer, the property you have designated is the one that will be selected in the Properties window. Note that this may or may not be in view; the Properties window does not scroll to it automatically.

You are certainly familiar with default events though you may not have noticed them. For example double-clicking on the default Form1 in a new project creates an event handler named Form1_Load, attaches it to the Load event of Form1, and opens the code editor to that method. In other words, the Load event is the DefaultEvent for the Form class. To use the DefaultEvent attribute, specify a string that designates an event such as TextChanged or Load. Placing your control on the designer surface and then double-clicking it creates the event handler, attaches it to your control, and opens the method in the code editor.

The string arguments to DefaultProperty and DefaultEvent are not strongly typed by Visual Studio (though I see no reason why they could not be). That is, they are simply strings. If you mistype a name, it silently fails. Again, I see no reason why this should be a silent failure. Given that Visual Studio does not check it at compile time, it should be able to perform the check at design time (when you double-click a control). If the event you specify does not exist, Visual Studio could report the problem.

XML Documentation Comments

?
Figure 8. XML Documentation Comments: After you define documentation comments for a property, the value of the element appears along with its type when Intellisense pops up.

As mentioned earlier, you perform one aspect of integration in a completely different way. Intellisense help comes from XML documentation comments, not attributes. This article won’t go into much detail about XML comments?that would require an article in itself?but you’ll see enough to get useful information for Intellisense. First take a look at Figure 8; you can see the whole concept at a glance. In both the “before” and “after” pictures, a developer has started typing “clo.” Intellisense pops up and indicates the first such item is ClockBackColor. Before XML comments were added to the source code, the tooltip for ClockBackColor does not provide much additional information, just the base type Color. But after adding comments, the tooltip also shows a description.

This added description came from adding special comments to the source code. Here’s the relevant code:

   ///    /// Sets the background color    /// of the clock display.   ///    public Color ClockBackColor   {      get { return clockBackColor; }      set { clockBackColor = value;          clockLabel.BackColor = clockBackColor; }   }

Documentation comments:

  • Begin with a triple virgule rather than a double virgule
  • Are written in a specific XML dialect
  • Are syntactically validated at compile time

Visual Studio provides a handy convenience mechanism called smart comment editing for using simple doc-comments. On the line above a method, property, or class declaration, type the triple virgule and Visual Studio will automatically replace that with an empty

element, positioning your cursor inside the element so you can type a description (see Delimiters for Documentation Tags). If you have attributes on the given element you must be positioned on the line just above those. You can control whether this feature is enabled via the Options dialog for C# (see Figure 9).
?
Figure 9. XML Documentation Comments Smart Editing: After you enable the indicated option, position your cursor on the line immediately before a definition in the code editor and type the triple virgule, Visual Studio expands it to a minimal doc-comment.
?
Figure 10. XML Documentation Generation: Enabling the indicated option and then building your project not only generates an XML documentation file, but also causes Intellisense to use your doc-comments.

As you can see in Figure 8, the contents of that

element appear in the Intellisense tooltip. For this to work you must enable XML documentation generation for your control library, not for the application (see Figure 10). That is, it does not matter whether you enable XML documentation generation for the windows application containing your form that instantiates your control. It is the control library that contains the definition of the control (e.g., ControlLib in the example) that must have the XML documentation generation enabled in order for the form to see the doc-comments for the library via Intellisense.
Author’s Note: Although I encourage you to fully document the libraries you write, only the contents of the element will appear in the Intellisense tooltip.

Visual Studio’s smart comment editing includes XML Intellisense. For example, if you type a triple virgule and then an opening left angle bracket (), Visual Studio will show you XML tags that are valid for the context. That’s useful, but a third-party plug-in called GhostDoc?available for both VS2005 and VS2008?takes XML Intellisense to the next level. Here’s the original property code:

   public Color ClockBackColor   {      get { return clockBackColor; }      set { clockBackColor = value;          clockLabel.BackColor = clockBackColor; }   }

With GhostDoc installed, you can place your cursor anywhere on the initial line containing ClockBackColor, then invoke Tools ? GhostDoc ? Document This. GhostDoc decorates your code thusly:

   ///    /// Gets or sets the color of the clock back.   ///    /// The color of the clock back.   public Color ClockBackColor   {      get { return clockBackColor; }      set { clockBackColor = value;          clockLabel.BackColor = clockBackColor; }   }

GhostDoc not only adds the

element, it also populates it with a reasonable guess. Furthermore, it also provides other required XML elements such as the element shown in the preceding code. GhostDoc doesn’t always guess right, but you will be surprised how often it does. The better the names you use, the more success you will have with this timesaving tool.

For more on XML documentation comments, see Microsoft’s reference and the excellent XML Documentation Comments Guide which provides compatibility charts between C# and NDoc doc-comment implementations.

Three User Control Creation Tips
This section details just a few miscellaneous tips that may forewarn you and possibly save you some time.

Tip 1
The trusty Console.WriteLine() method gets used frequently during development. Even with a Windows Forms application, the output pane shows this console output when you run from within VS. Not so from the UserControl Test Container! You will not see the output of such WriteLine statements at all.

Tip 2
During development of a user control library, you will inevitably need to make changes to the API: adding properties, deleting properties, or changing initial values of properties. These changes generally do not propagate automatically to applications that use your control library. Example: define an integer property IntProperty and initialize it to 25. Include this control in a Windows Forms application. Now go back to your control and change the initial value to 42. Neither recompiling nor re-opening the Windows Form in the visual designer will change the designer-generated code. Why? Visual Studio evaluates the properties and values of a control only once?at instantiation time, when you drag the control onto the designer?and never again. So you need to either manually propagate any changes when you make them or (usually simpler) just delete the control in the visual designer and then drag a fresh copy from the toolbox.

You get a little bit more help when adding a property. If you add a new property to a control, recompile your control library, and then recompile the Windows Forms application that uses it? nothing happens. But if you close the Windows Forms application solution, re-open it, and bring up the form in the visual designer, the new property appear in the Properties pane when you select the control. (This scenario assumes you are running two instances of Visual Studio, which is common, so you may not have ever closed the application.)

You get the most help when you delete a property from your control. When you compile the application you will promptly get a compilation error indicating there is no definition for the now-missing property (assuming you had referenced it in your code, of course).

Tip 3
In your control library you provide initial values for your properties that will then appear in the properties window for your control when you drag it from the toolbox into your Windows Forms application, as discussed in the prior point. But where do these initial values come from?

In prior versions of C# you would typically use a private field that is declared and initialized at the same time. This private field backs a property of the same name except for the initial capital. The value of 42 in the code fragment will automatically be picked up as the property’s initial value and displayed in your instance of it:

   private int myProperty = 42;   public int MyProperty {      get { return myProperty; }      set { myProperty = value; }   }

But C# 3.0 introduces a slight twist with auto-implemented properties, allowing you to use this more elegant and concise form:

   public int MyProperty { get; set; }

So now, you initialize the property in the object constructor, referring to the property rather than the anonymous (and inaccessible) backing variable:

   public MyClass()   {      . . .      MyProperty = 42;   }

The value you specify in the constructor will now be recognized by the Visual Designer as the initial property value!

Next Steps
Now that you have seen how to create custom controls, run them at design-time or within a test container, learned the subtleties of the different types of controls, and seen ways to integrate your controls seamlessly with Visual Studio, you are well on your way to creating professional building blocks.

For further reading, the Windows Forms Programming reference contains overview pages at Developing Windows Forms Controls at Design Time and Windows Forms Controls.

For some other neat tricks you can do with controls, see Drag and Drop Windows Form Controls, which shows you how to enable dragging your controls around at runtime just like you could at design time. Finally, the ExtenderProvider is a standard .NET framework component that allows you to in effect remotely add properties to objects. The best example of this is the ToolTip component that allows you to add tool tips to controls that do not have them.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist