Browse DevX
Sign up for e-mail newsletters from DevX


.NET Building Blocks: Custom User Controls in Practice  : Page 6

Learn the differences between the various types of custom controls, and find out how to integrate your controls into Visual Studio and make them easy to use.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

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.

Michael Sorens is a freelance software engineer, spreading the seeds of good design wherever possible, including through his open-source web site, teaching (University of Phoenix plus community colleges), and writing (contributed to two books plus various articles). With BS and MS degrees in computer science and engineering from Case Western Reserve University, he has worked at Fortune 500 firms and at startups, using C#, SQL, XML, XSL, Java, Perl, C, Lisp, PostScript, and others. His favorite project: designing and implementing the world's smallest word processor, where the medium was silicon, the printer "head" was a laser, and the Declaration of Independence could literally fit on the head of a pin. You can discuss this or any other article by Michael Sorens here.
Thanks for your registration, follow us on our social networks to keep up-to-date