Control Designer Basics
Adding a basic level of design-time support to your controls is easy. In this section you learn how you can outfit your control with a custom toolbox icon, resource files, and support for the Properties window.
Attributes
Designer attributes are the first line in custom control support. They instruct the IDE how to treat various parts of your control. Attributes are a unique development in .NET programming. To specify this type of information about a custom control in another programming language, you would either need to create a separate file in a proprietary format (and learn a new syntax), or use some sort of visual tool. With attributes, the information that describes your control can be easily created and edited alongside your code, but it is still cleanly separated from the logic that generates the user interface.
The previous chapter developed a Progress user control that displayed a synchronized label paired with a progress bar. To make it work, three properties were added: Value, Step, and Maximum. You may have noticed that these properties appear in the design window grouped under the generic "Misc" category without any additional information (see Figure 8-1).

Figure 8-1: Nondescript properties
You can improve on this situation using attributes. The example below adds a Description, Category, and DefaultValue attribute to the Value property. Note that when you use more than one attribute, they are all enclosed between angled brackets, and separated with commas. The underscore character is used to spread the attributes over several lines for better readability.
[Description("The current value (between 0 and Maximum) which sets " +
"the position of the progress bar"), Category("Behavior"), DefaultValue(0)]
public int Value
{
get
{
return Bar.Value;
}
set
{
Bar.Value = value;
UpdateLabel();
}
}
The result of applying these attributes is shown in Figure 8-2.

Figure 8-2: A property configured with attributes
All these attributes are found in the System.ComponentModel namespace, along with many more that allow you to configure aspects of your control's designtime behavior. Table 8-1 lists the most useful attributes you can use to configure properties.
Table 8-1: Basic Control Property Attributes
AttributeDescription
AmbientValue(true|false)Indicates that the value for a property is derived from the control's parent. For example, most controls have an ambient Font and BackColor property—if these values are not set, the Font and BackColor of the parent is used automatically.
Browsable(true|false)If false, indicates that a property should not be shown in the Properties window. However, the property is still accessible through code.
Category(")Sets the category under which the property appears in the Properties window. If a category with this name doesn't exist, it is created.
DefaultValue()Sets the initial value that will be used for this property when the control is created.
Description(")Specifies the text description that will be displayed for this property in the Object Browser of Properties window.
DesignOnly(true|false)When set to true, this property is only available at design time. This is typically used with special properties that configure how a control behaves at design time (like a SuppressUI property), and don't correspond to a "real" piece of information about the control.
ImmutableObject(true|false)When set to true on an object property, this attribute ensures that the subproperties of this object is displayed as read-only. For example, if you apply this to a property that uses a Point object, the X and Y subproperty will be read-only.
Localizable(true|false)When set to true, the design-time value for this property is stored in a resource file instead of in the generated code. This makes it easy to swap the value later by introducing a new resource file. When the user configures properties that don't use this attribute, the appropriate code is inserted in the hidden designer region of the form, unless it requires a special data type (like an image) that must be stored in a resource file.
MergableProperty(true|false)Configures how the Properties window behaves when more than one instance of this control is selected at once. If false, the property is not shown. If true (the default), the property can be set for all selected controls at once.
NotifyParentProperty(true|false)Set this to true to indicate that a parent property should receive notification about changes to the property's value (and update its display accordingly). For example, the Size property has two nested properties: Height and Width. These nested properties should be marked with this attribute.
ParenthesizePropertyName(true|false)When true, indicates that the property should be displayed with brackets around it in the Properties window (like the Name property).
ReadOnly(true|false)When true, this property is read-only in the Properties window at design time.
RefreshProperties()You use this attribute with a value from the RefreshProperties enumeration. It specifies whether the rest of the Properties window must be updated when this property is changed (for example, if one property procedure could change another property).
A few attributes can be applied to your custom control class declaration, rather than a specific property. These include two attributes that set the default event and property. Here's how you could use these attributes with the DirectoryTree developed in the previous chapter:
[DefaultEvent("DirectorySelected"),
DefaultProperty("Drive")]
public class DirectoryTree : TreeView
Table 8-2 lists the useful designer attributes that you can apply to the class definition.
Table 8-2: Basic Control Class Attributes
AttributeDescription
DefaultEventWhen the application programmer double-clicks your control, Visual Studio .NET automatically adds an event handler for the default event.
DefaultyPropertyThe DefaultProperty is the property that is highlighted in the Properties window by default, the first time the control is selected.
You can also use some advanced attributes to support licensing and custom windows for property settings. You learn about these topics a little later in this chapter.
Basic Serialization
When creating a property, you can add additional methods to configure its default value, and specify whether changes should be serialized to the Windows designer code in the form. You add this extra logic by creating two optional methods for each property: ShouldSerializePropertyName() and ResetPropertyName().
For example, if you have a property named Drive, you could add the following methods:
public void ResetDrive()
{
// (Reset code goes here.)
}
public bool ShouldSerializeDrive()
{
// (Determine if serialization is needed here.)
}
Visual Studio .NET automatically invokes the ResetDrive() method when the control is first created (to set a default value), or whenever the user right-clicks the property in the Properties window and chooses Reset. If you don't use the ResetDrive() method, Visual Studio .NET uses whatever value is specified in the DefaultValue attribute applied to the Drive property. Thus, you should use either a DefaultValue attribute or the custom ResetDrive() method, not both.
One reason that you might want to use the custom ResetDrive() method instead of a DefaultValue attribute is so that you can make a runtime decision about what value to use. For example, you could set this property in accordance with other control properties, or by examining the hard drive to determine what drive letters are valid.
The ShouldSerializeDrive() method performs a slightly different task. It returns true or false to indicate whether the current value of the Drive property should be serialized to the form's designer code. If you don't include this method, Visual Studio .NET always generates the designer code in response to the values chosen by the user at design time. One reason that the ShouldSerializePropertyName() method is often used is to avoid serializing information when this information corresponds to the default value. This results in more economical designer code.
Here's a complete example for the Drive property:
private Char drive;
public Drive
{
get
{
return drive;
}
set
{
drive = value;
RefreshDisplay();
}
}
public void ResetDrive()
{
drive = "C";
}
public bool ShouldSerializeDrive()
{
// Serialize the change as long as it does not equal the default value.
return !(drive == "C");
}
The Toolbox Bitmap
Adding a toolbox icon is refreshingly easy. All you need to do is add a bitmap to your project and ensure it has the same file name as your custom control class. This bitmap must meet some basic criteria:
It must be 16 pixels by 16 pixels. Otherwise, Visual Studio .NET attempts to scale it and the results will be ugly.
It must use only 16 colors.
Once you add the file, use the Properties window to set the Build Action for it to Embedded Resource. Then, recompile the control project. Figure 8-3 shows an example: the DirectoryTree control project with the required image file.

Figure 8-3: Configuring a toolbox bitmap
When you add the control to a client project, the embedded bitmap appears in the toolbox, as shown in Figure 8-4.

Figure 8-4: A custom toolbox bitmap
Resource Files
The previous chapter developed an extender provider that displayed a custom Help icon next to ordinary .NET controls. One of the flaws in the design was that the icon was read from a file. This means it has to be in the current directory of the program that is using the HelpIconProvider.
A better approach is to embed the binary data for the icon as a resource in the compiled DLL. In .NET, this is accomplished using .resx files. Visual Studio .NET creates a .resx file automatically for every form, and uses it to store images (or other binary data) that you set at design time.
To see the .resx files in your project, select Project → Show All Files from the Visual Studio .NET menu. A .resx file appears under each form node in the Solution Explorer, as shown in Figure 8-5.

Figure 8-5: A resource file
If you opened a .resx file in Notepad or a similar text editor, you would discover that it is actually an XML file that contains a name/value pair for each resource. For example, the snippet that follows defines the binary data for a picture in a PictureBox control.
<data name="PictureBox1.Image" type="System.Drawing.Bitmap, System.Drawing,
Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
R0lGODlhEAAQAPcAAP7+/f/99fLy8+7z/O7x9ubt++jt9uPp8+Dm8///7vLx7Ozt7uPj5Mbz/9vq/9rj
9Nfh8s7p/93i7dTe8cre/8rY89Hc783U4MfQ48bP4dzd39LS0sDJ28rKysbIysLCwqjd/qnU/73J4JjP
/JHJ/7vG3brBz6G555Gt6Iev7LG7yqi1z6Kxz5qv2ZCo2J+v1Zar1JWkwoGaz7u7u7Ozs6qssaysrJid
ppOZpZqamnGy/3mk9muY9Xuc4Hea4kyL/3CS2X+c12mO2GaM2myKyWqGwl2H3n2BiVJ/10Bx0Fl+x1F5
xz1z4DZx5itn4jNr2zls0DRp0zpszTRozjJmzTNjxSxizCpfyiNbyytfxSRZwCBWxBVU0RpVyR1VxRFM
wz1luSVXuRFIuRZNtw5JvQ1GugExvX19fXJycm9zeWxsbGNjY1lZWlJSUk5OTgAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//
/yH5BAEAACQALAAAAAAQABAAAAj8AEkIBPBggoWDEyQIEMiQxIAJBwBIlIhggoaGDyU2ADFiRAiJEzwM
nAAggo4UEhQg2PGDAoAJHUg8IODgh5EEAE4ACMCkyQAENxiQ5OGEyAEkZoIAGMLFBwAONiAUeBLlCpYu
XzAASIJFCgALah5UwEKFipUtKwD08GIlC4EJbQp6KYulCAAUX6y0FQDBDQQAVbBMwULkARSyXpYAyNCG
pAsyeqUIuWLlyhgRBnCsWfBXSZkvMQAA+VJGBgAIbHIAuHAggIwxYFpoCfMiwIM0aC4KMPEAAAMWMEoo
4HtEzQyGDGpggPCgOQQVbNDQaEhiwQc1bdxob3NmA8OAADs=
</value>
</data>
You can also view the .resx file in Visual Studio .NET by double-clicking it. It displays the list of pairs in a special dataset view (see Figure 8-6).

Figure 8-6: A .resx file in Visual Studio .NET
When a project is compiled, the .resx is converted to a binary .resources file. This file is then embedded in the compiled assembly (DLL or executable file) for the application. That guarantees that the required information is always available, without needing to rely on an external file that could be moved or deleted.
It is possible to create .resx files programmatically by hand, but the process is labor intensive. With custom control development, you can take a simple shortcut that reaps the same benefits. The process works like this:
Add a new form to your control project. This form will not be shown; its purpose is to store resources that your control will use. For the HelpIconProvider, you might create a form called HelpIconResources.
Create picture boxes for the images you need to access and load the appropriate image into each picture box at design time. Behind the scenes, Visual Studio .NET creates a .resx file for the form and adds the binary data for each picture.
Read through the form's designer region to find the code that reads the images from the resource file.
System.Resources.ResourceManager resources;
resources = new System.Resources.ResourceManager(typeof(HelpIconResources));
this.PictureBox1 = new System.Windows.Forms.PictureBox();
this.PictureBox1.Image = (System.Drawing.Bitmap)
resources.GetObject("PictureBox1.Image");
Use this code to read the appropriate resource in your control class. For example, consider the original HelpIconProvider code:
PictureBox pic = new PictureBox();
pic.Image = Image.FromFile("Help.gif");
This can be replaced with the following code that retrieves the picture from the resource file:
PictureBox pic = new PictureBox();
System.Resources.ResourceManager resources;
resources = new System.Resources.ResourceManager(typeof(HelpIconResources));
pic.Image = (System.Drawing.Bitmap)Resources.GetObject("PictureBox1.Image");
It's important to realize that every code file in Visual Studio .NET has an associated .resx file, even if does not correspond to a form. For example, you can add resources directly to a HelpIconProvider.resx file by adding picture boxes to the design portion of the HelpIconProvider.vb file (see Figure 8-7).

Figure 8-7: Adding resources to an ordinary code file
The fact that HelpIconProvider.vb doesn't have a graphical display doesn't stop you from adding resources. In fact, the appropriate designer code is added directly to a collapsed "Component Designer generated code" region in your control class! The approach you take is up to you. You can refer to the ExtenderProvider project, which is included with the online samples for Chapter 7, to see one example of how a resource file can be embedded in a custom control. As you become more comfortable with the .resx format, you may even want to create your .resx files by hand. Refer to the MSDN documentation for the System.Resources namespace for more information.