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 <summary> 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:
/// <summary>
/// Sets the background color
/// of the clock display.
/// </summary>
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:
/// <summary>
/// Gets or sets the color of the clock back.
/// </summary>
/// <value>The color of the clock back.</value>
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.