VSM Cover Story
Design Reusable Custom Controls
Build custom controls that can inherit from existing .NET controls, add custom properties to inherited controls, and support reuse in any app that needs them.
Technology Toolbox: VB.NET
You can use the power of GDI+ in Windows Forms to create owner-drawn controls with rounded corners and custom gradients (see my Desktop Developer column in last month's issue). That's exciting, but it's not enough. You want to achieve this level of sophistication, yet support code reuse at the same time. You certainly don't want to have to rewrite the code for every owner-drawn control on every form.
Custom controls provide the perfect answer to this problem. Mastering Visual Studio .NET's object-oriented capabilities enables you to design controls that can inherit from existing .NET controls, add custom properties to inherited controls, and support reuse in any application that needs inherited controls.
The more complex your user interfaces, the more functionality you must obtain from the controls in the Toolbox. Inevitably, you'll need to expand on the stock offerings provided in the box and create your own controls.
Several approaches are available when it comes to creating your own custom controls, and as usual each approach comes with its own set of tradeoffs. Mainly, you can create an extender control or an inherited control.
An extender control inherits from the System.ComponentModel.Component class. Extender controls are nonvisual components that add, or extend, properties to controls that exist already on a form.
An inherited control inherits from an existing control, such as a Label or a ListBox. Inherited controls are visual controls that add to or modify the properties and methods of the parent control. I'll show you how to create and employ inherited controls. You'll put this into practice by creating your own custom label, which I've named the FunLabel (see Figure 1).
The FunLabel control project creates a new inherited control, which it derives from the existing System.Windows.Forms.Label control. Start the process of creating the FunLabel control by creating a new Class Library project. Call it FunLabel (see Figure 2). After you've created the project, add these Imports statements:
Imports System.Windows.Forms
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.ComponentModel
The System.Drawing namespaces contain the classes you need to create your custom GDI+ drawing. First, set your new control to inherit the Label control found in the System.Windows.Forms namespace. Next, access the System.ComponentModel namespace, which contains all the classes necessary for implementing your control's runtime and design-time capabilities. System.ComponentModel's classes and interfaces provide features such as licensing, databinding, type conversion, and custom attributes.
Now make these namespaces available to your class so you can start writing the code for your new custom label control. Specify the Label control as the source from which your control inherits within the System.Windows.Forms class:
Public Class FunLabel _
Inherits System.Windows.Forms.Label
Give Your Class a Little Class
You could compile the project right now if you wisheddoing so would produce a FunLabel.dll assembly. You could add this assembly to the Toolbox and use it on any Windows Form in the future. However, it would be nice if your new control actually brought something to the party. So your goal at this point is to add some additional properties to the FunLabel class that will appear at run time and give you a cool custom label.
Any public property you add to the class appears in the Properties window at design time, then is exposed at run time. You create private member variables that are used in Property declarations, as is the case with any other class that includes custom fields. For the FunLabel, add five properties to the base Label class: BackColor1 of type Color, BackColor2 of type Color, GradientFallOff of type Decimal, GradientSpeed of type Decimal, and GradientMode of type LinearGradientMode.
The BackColor1 and BackColor2 properties define what colors you're going to use when you create the gradient brush on the label. The GradientMode property actually exposes the LinearGradientMode enumeration from the System.Drawing.Drawing2D namespace (see Table 1).
The SetBlendTriangularShape method of the LinearGradientBrush class uses the GradientSpeed and GradientMode properties. This method accepts two parameters, Focus and Speed. Focus defines a value between 0 and 1, representing the center of the region where the BackColor2 property begins and starts its linear falloff toward the edges of the control.
The GradientSpeed property determines how fast the colors fall off from BackColor1 to BackColor2. The GradientFallOff property has no effect if you don't set the GradientSpeed property. It takes a bit of code to employ these properties, but nothing overwhelming (see Listing 1).
You'll notice that you define the properties with custom attributes:
<Category("Gradient Appearance"), _
Description( _
"Linear Gradient Mode")> _
Public Property GradientMode() _
As LinearGradientMode
You can extend the definitions of the properties with attributes when you create a custom control. Some of the common attributes you use are Category, DefaultValue, and Description. The Category and Description attributes are particularly important, because they help define properties in the Properties window at design time (see Figure 3).
You must consider two other key points about the property definitions: the use of error handing, and the Invalidate method. I'll talk about error handling first.
The GradientSpeed and GradientFallOff properties require a specific numeric range, so you must implement error handling in the Set routine of the property definition. This error handing should be as descriptive as possible. You need to give developers using your control enough information so they don't have to look in a help file for the answer. And it doesn't take much work:
Set(ByVal Value As Decimal)
If Value <= 1 Then
_gradientSpeed = Value
Else
MessageBox.Show("Enter a value between 0 and 1")
End If
Invalidate()
End Set
Invalidate ControlsOn Purpose
Call the Invalidate method after each property Set. This method forces the control to invalidate itself and redraw its surface when the form repaints itself. Otherwise, the paint message won't be sent to the control, causing unexpected (and unwelcome) results at design time.
This is a good point to stop and consider how you use Visual Studio's type editors and type converters at design time when you're building custom controls. Visual Studio displays the correct UI in the Properties window at design time, based on the data type of the property. VS shows the Color Picker editor at design time for a property of the type Color, while it shows a dropdown list for an enumeration such as LinearGradientMode. These type editors visualize a type at design time.
A type converter then converts a selected value's string representation used in your code. This is a two-way street, meaning values set in code or attributes are converted correctly to their design-time representation based on the type needed.
Native data types, such as numbers, strings, colors, and enumerations, have automatic type editors and type conversion. If you need custom type editors or type conversion, you can create your own. You would want to do this if you're allowing a property setting to update a configuration file, or if you need a custom dialog to edit a text file. Use the classes on the System.ComponentModel.TypeConverter class to implement custom type converters.
You're almost done at this point, but you have one more step. Finish the FunLabel control by actually painting the control, using the values in the custom properties that you created. You accomplish this by overriding the OnPaintBackground method of the control.
The general principle at work here is that you customize the behavior of custom controls by overriding the default methods in the base class. You're modifying the background of the control in the case of the FunLabel, so you need to customize the OnPaintBackground method. The code for doing this executes at design time when properties are changed, and at run time if properties are changed programmatically (see Listing 2).
There's an additional step required to add your control to the Toolbox. First, your custom control needs a custom bitmap. Create a 16-by-16-pixel, 16-color bitmap with the same name as the control you're creating. In the case of the FunLabel control, create a FunLabel.bmp and add it to your project. Then change its Build Action property to Embedded Resource. This compiles the bitmap along with the FunLabel assembly as a resource, and represents the FunLabel control in the Toolbox.
Now you have a basic understanding of how to create custom controls. The power of object-oriented programming teamed with the rich assortment of existing controls in the .NET Framework provides a great launchpad for ambitious developers. This is your chance to see what you can accomplish in your presentation layer development.
About the Author
Jason Beres is the .NET technical evangelist for Infragistics Inc., a Microsoft Visual Studio Industry Partner. Jason is a Microsoft .NET MVP, is on the INETA Speakers Bureau, and is the co-chair of the INETA Academic Committee. He is the author of Sams Teach Yourself Visual Studio .NET 2003 in 21 Days (Sams) and the coauthor of Visual Basic .NET Bible (John Wiley & Sons) and C# Bible (John Wiley & Sons). Reach Jason at [email protected].