Creating Controls

This topic demonstrates how you can create Windows Forms controls. It covers the basics of creating a control. In addition, this topic covers adding painting logic to a control, exposing properties and events, using control licensing, and adding design-time behavior to your control. The following topic areas are covered in this topic:

Note: This topic is covered in much more detail in the control creation section of the Microsoft .NET Framework SDK documentation.

There are two types of controls:

  • Custom controls: Controls that display UI by making calls to a Graphics object in the Paint event. Custom controls typically derive from Control. A Chart control is an example of a custom control. There is limited design-time support for creating custom controls.

  • User or Composite controls: Controls that are composed of other controls. User controls derive from UserControl. A control that displays a customer address using TextBox controls is an example of a user control. There is full design-time support for creating user controls with the Visual Studio .NET Windows Forms Designer.

The majority of the samples in this topic demonstrate building custom controls. However, the sections on exposing properties and events, defining design-time behavior, and licensing apply to both custom and user controls.

Getting Started
Writing a Simple Control

The following example creates a simple control that displays the value of its Text property by handling the Paint event. In order to create this control and handle the event, you must create a class that inherits from Control and create a method that overrides the OnPaint method.


public class HelloWorldControl: Inherits Control

    Overrides Protected Sub OnPaint(e As PaintEventArgs)

        Dim rect As RectangleF = new RectangleF(ClientRectangle.X,
                                                 ClientRectangle.Y,
                                                 ClientRectangle.Width,
                                                 ClientRectangle.Height)

        'Paint the Text property on the control
        e.Graphics.DrawString(Me.Text, Font, new SolidBrush(ForeColor), rect)

    End Sub

End Class
VB

View and run this sample.

 
VB Hello World Control

[Run Sample] | [View Source]

Examining the Design-Time Behavior of a Control

You can test the design-time behavior of a control in the Visual Studio .NET Windows Forms designer:

  1. Start the Visual Studio .NET Windows Forms designer.
  2. Add a new form by clicking New VB or New C# from the File menu.
  3. Click Add Library on the Edit menu.
  4. Select the DLL containing the control you want to use.
    The control appears at the bottom of the toolbox.
  5. Select the control and add it to the form.
    You will see the control appear on the form.
  6. If you are adding the control from the previous example, you will notice that even this very simple control has a full set of properties and a broad range of design-time behavior. This default behavior is inherited from the Control class.

Note: For a control to be displayed in the Windows Forms Designer, it must have a public constructor that takes no parameters.

Adding Properties, Events and Metadata

Now that you have created a simple control, you can add properties, events, and metadata information to it. The following example:

  • Adds a property called DrawMode that is used to determine how the control paints.
  • Adds an event that is raised when the DrawMode property is changed.
  • Adds metadata to the control to describe its design-time behavior.
  • Overrides a property inherited from Control to hide it at design-time.
Adding a Property

First, create a simple enumeration called DrawingMode.


Public Enum DrawingModeStyle
    Happy = 0
    Sad = 1
    Angry = 2
End Enum
VB

Next, add a DrawingMode property to the control. The following code adds this property to the control that was created previously.


'DrawingMode - controls how the control paints

Public Property DrawingMode As DrawingModeStyle

    Get
        Return myDrawingMode
    End Get

    Set
        myDrawingMode=value

        'Set BackColor and ForeColor based on DrawingMode
        SetColors

        ' Raise property changed event for DrawingMode
        RaisePropertyChangedEvent("DrawingMode")
    End Set

End Property
VB

Note: The property Set code includes a call to the RaisePropertyChangedEvent method. This method raises a property change notification event. It is important to raise the property change notification event, since the Windows Forms Designer listens to this event. It listens to this event so it can track when a property has changed. Later in the code, the control takes advantage of this event.

The call to the SetColors method simply sets the BackColor and ForeColor of the control, based on the value of DrawingMode. Add the following code to the control.


Private Sub SetColors()

    Select Case myDrawingMode

       Case DrawingModeStyle.Happy
           MyBase.BackColor = Color.Yellow
           MyBase.ForeColor = Color.Green


       Case DrawingModeStyle.Sad
           MyBase.BackColor = Color.LightSlateGray
           MyBase.ForeColor = Color.White


      Case DrawingModeStyle.Angry
          MyBase.BackColor = Color.Red
          MyBase.ForeColor = Color.Teal


      Case Else
           MyBase.BackColor = Color.Black
           MyBase.ForeColor = Color.White

   End Select

End Sub
VB

You can now use this information when the control paints its contents. Add this code to the control class to override the OnPaint method of Control.


Overrides Protected Sub OnPaint(e As PaintEventArgs)

    e.Graphics.FillRectangle(new SolidBrush(BackColor), ClientRectangle)

    Dim textSize As SizeF = e.Graphics.MeasureString(Me.Text, Font)

    Dim xPos As Single = CSng((ClientRectangle.Width/2) - (textSize.Width/2))
    Dim yPos As Single = CSng((ClientRectangle.Height/2) - (textSize.Height/2))

    e.Graphics.DrawString(Me.Text, Font,new SolidBrush(ForeColor),xPos, yPos)

End Sub
VB

Note: In order to have the control automatically repaint when it is resized, set the ResizeRedraw style bit when the control is created.


Public Sub New()
    MyBase.New
    myDrawingMode = DrawingModeStyle.Happy
    SetColors
    SetStyle(ControlStyles.ResizeRedraw, True)
End Sub
VB

Adding an Event

In this next step, you add a simple event that is raised when the DrawingMode property changes. There is already an event that is raised, the PropertyChange event, so why must you add your own event? The PropertyChange event is not exposed to control consumers by default, it is a hidden event that is used by the framework infrastructure.

Typically, the first thing you do is declare an EventHandler and EventArgs class for your event. Because this example event is simple, you can use the standard EventHandler and EventArgs classes.

After the EventHandler and EventArgs classes are declared, you need to declare the event and add a method to raise it. The method that is used to raise the event is typically called On<EventName>. This method is protected, because only developers deriving from the control need to directly raise the event.


Public Class SimpleControl: Inherits Control

    Private myDrawingMode As DrawingModeStyle
    ....

    'Declare the DrawingModeChanged Event
    Public Event DrawingModeChanged(sender As Object, ev As EventArgs)

    Overridable Protected Sub OnDrawingModeChanged(e As EventArgs)
        Invalidate
        RaiseEvent DrawingModeChanged(Me, e)
    End Sub

    ....

End Class
VB

After adding the event, you need to raise it. You can go to the DrawingMode property Set statement and add a call to OnDrawingModeChanged. However, because this method already raises a property change notification event, you can simply listen for that event to be raised, rather than adding an event-handling method. You can override the OnPropertyChanged method.


....
' Override OnPropertyChanged to raise the DrawingModeChanged event
Overrides Protected Sub OnPropertyChanged(e As PropertyChangedEventArgs)
    MyBase.OnPropertyChanged(e)
    Dim d As string = e.PropertyName

    If (d.Equals("DrawingMode")) Then
        OnDrawingModeChanged(EventArgs.Empty)
    End If

End Sub
VB

Every time the property is changed, the control will redraw and raise the DrawingModeChanged event.

Using the Control

Now that you have written your control and added some behavior to it, you can compile it into a DLL and use it in an application. The new control can be used in the same manner as any other Windows Forms control. The following example demonstrates how to use the control in an application.


....

' Create the control and set its properties
simpleControl1 = New SimpleControl()
simpleControl1.Size = new System.Drawing.Size(304, 328)
simpleControl1.TabIndex = 0
simpleControl1.Anchor = System.Windows.Forms.AnchorStyles.All
simpleControl1.Text = "Windows Forms Mood Control"

' Add an event handling method for the DrawingModeChanged event
AddHandler simpleControl1.DrawingModeChanged, AddressOf simpleControl1_DrawingModeChanged

....

Private Sub simpleControl1_DrawingModeChanged(sender As object, e As System.EventArgs)
    MessageBox.Show("DrawingMode changed")
End Sub
VB

Adding Design-Time Information for the Control

Now that you have a working control, you can augment it with design-time information that improves the usability of the control in the Forms Designer. Design-time information is recorded using metadata on the control binary. This metadata is defined using a series of attribute classes from the System.ComponentModel namespace. For example, the default event for your control is the DrawingModeChanged event. When the user double-clicks the control, you want the Forms Designer to add an event-handling method for the default event. You have to register the default event using the DefaultEvent class attribute. Similarly, you can define the default property on a control using the DefaultProperty class attribute. This attribute determines which property is given focus in the Properties window by default. Class attributes are declared as part of the class declaration. The following example demonstrates class attributes.


Public Class _
    <DefaultProperty("DrawingMode"), DefaultEvent("DrawingModeChanged")> _
    SimpleControl
        Inherits Control
    ...
End Class


VB

The next step is to add more design-time information for the DrawingMode property.


'DrawingMode - controls how the control paints
Public Property _
<Category("Appearance"), _
 Description("Controls how the control paints"), _
 DefaultValue(DrawingModeStyle.Happy), _
 Bindable(true)> _
DrawingMode As DrawingModeStyle

    Get
        return myDrawingMode
    End Get

    Set
        myDrawingMode=value

        'Set BackColor and ForeColor based on DrawingMode
        SetColors

        'Raise property changed event for DrawingMode
        RaisePropertyChangedEvent("DrawingMode")

    End Set

End Property
VB

The attributes that you assign to the property are as follows:

  • The Category attribute determines the category for the property. This is used when the property is displayed in categorized view in a Properties window.
  • The Description attribute sets the short description that is displayed at the bottom of the Properties window when the property is selected.
  • The DefaultValue attribute is used by the Forms Designer to determine whether a property value must be persisted.
  • The Bindable attribute is used to determine whether this property is displayed in the default data-binding view.

You now need to add design-time information for the DrawingModeChanged event.


Public Event _
    <Description("Raised when the DrawingMode changes")> _
DrawingModeChanged(sender As Object, ev As EventArgs)
VB

In the last step, you need to hide two properties. Because the DrawingMode determines what background and foreground colors are used to draw the control, you don't want the user to be able to set the BackColor and ForeColor properties. You cannot remove the properties because they are declared in the Control class. However, you can hide them so that they do not show up in the property browser by using the Browsable attribute.


'Remove the BackColor property from the properties window
Overrides Public Property <Browsable(false)> BackColor As Color
    Get
        return MyBase.BackColor
    End Get

    Set
        'No Action
    End Set

End Property

'Remove the ForeColor property from the properties window
Overrides Public Property <Browsable(false)> ForeColor As Color
    Get
        return MyBase.ForeColor
    End Get

    Set
        'No Action
    End Set

End Property
VB

Examining the Design-Time Behavior of a Control

You can test the design-time behavior of this control by using the Visual Studio.NET Windows Forms Designer and following the same procedure that was used for the HelloWorldControl demonstrated previously in this topic. Take note of the following:

  • The Properties window automatically can edit the DrawingMode property. It has the capacity to use the enumeration we defined for our property.
  • The Properties window displays descriptions for the DrawingMode property and the DrawingModeChanged event.
  • The ForeColor and BackColor properties are not displayed in the Properties window.
  • Double-clicking on the control creates an event-handling method for the DrawingModeChanged event.
  • Changing the DrawingMode property causes the control to change immediately.
  • Changing the DrawingMode property from Happy to Sad causes the property to be highlighted. This indicates that it has changed. Changing DrawingMode back to Happy causes the highlight to be removed. The Properties window uses the default value (Happy) to determine whether the property value changes.

View and run this sample.

 
VB SimpleControl

[Run Sample] | [View Source]

Adding Custom Editors to the Properties Window

In the previous sample, we added a property that uses an enumeration to our control. When we looked at this property at design time, the Properties window recognized that the property used an enumeration and automatically used an enumeration property editor. If the Properties window cannot recognize the type of the property, the .Net Framework Class Model includes a mechanism that allows a control author to create an editor for a property and register that editor as the editor for that type. Again, this is controlled by metadata and attributes. The following sample explains how add a custom property editor to a control.

In this sample, you have a FlashTrackBar control that is like the Windows TrackBar control, but it uses a GDI+ LinearGradientBrush to paint its contents. The position of the FlashTrackBar is determined by the Value property. This property has a custom property editor. The following code demonstrates the property declaration.


Public Property <Category("Flash"), _
     Editor(typeof(FlashTrackBarValueEditor), typeof(UITypeEditor)), _
     DefaultValue(0)> _
Value As Integer
    ....
End Property
VB

The Editor attribute informs the Windows Forms Designer that the property browser should use the class FlashTrackBarValueEditor as the custom property editor for this property.

The property editor is implemented using a FlashTrackBar. The most interesting methods in the FlashTrackBarValueEditor are the following.


Overrides OverLoads Public Function GetEditStyle(context As ITypeDescriptorContext) _
                                                     As UITypeEditorEditStyle
    if (Not(context Is Nothing) AND Not(context.Instance Is Nothing)) Then
        return UITypeEditorEditStyle.DropDown
    End If
    return MyBase.GetEditStyle(context)
End Function
VB

This method informs the properties window that the editor uses a drop-down style UI.


Overrides OverLoads Public Function EditValue(context As ITypeDescriptorContext, _
         provider As IServiceObjectProvider, value As object) As Object

    if (Not(context Is Nothing) AND Not(context.Instance Is Nothing) AND Not(provider Is Nothing)) Then

        edSvc = CType(provider.GetServiceObject(GetType(IWinFormsEditorService)),IWinFormsEditorService)

        if Not (edSvc Is Nothing) Then

            Dim trackBar As FlashTrackBar = new FlashTrackBar()
            AddHandler trackBar.ValueChanged, AddressOf Me.ValueChanged
            SetEditorProps(CType(context.Instance,FlashTrackBar), trackBar)

            Dim asInt As Boolean = true

            if (TypeOf value Is Integer) Then
                trackBar.Value = CInt(value)
            else if (TypeOf value Is System.Byte) Then
                asInt = false
                trackBar.Value = CType(value, Byte)
            End If

            edSvc.DropDownControl(trackBar)

            if (asInt) Then
                value = trackBar.Value

            else
                value = CType(trackBar.Value, Byte)
            End If

        End If

    End If

     return value

 End Function
VB

EditValue implements the editor UI.

Note FlashTrackBar also demonstrates the use of the ShouldPersist<PropertyName> pattern. The DefaultValue attribute can only be used for simple types. For complex types, such as Colors, you use the ShouldPersist<PropertyName> pattern.


 Public Property <Category("Flash")> EndColor As Color
     ....
 End Property

Public Function ShouldPersistEndColor() As Boolean
    return Not (myEndColor.Equals(Color.LimeGreen))
End Function
VB

In the previous code, the Color.LimeGreen value is the default value. The designer calls the ShouldPersist method in order to determine whether to save the value of the EndColor property.

View and run the following sample.

 
VB CustomUITypeEditor

[Run Sample] | [View Source]

Examine the behavior of this control in the Visual Studio .NET Windows Forms designer in order to fully understand how the custom property editor works.

Extender Providers

An extender provider is a component that provides properties to other components. For example, the ToolTip control is implemented as an extender provider. When you add a ToolTip control to a Form, all other controls on the Form have a ToolTip property added to their properties list.

The following sample demonstrates how to build an extender provider by creating the HelpLabelControl. The following code shows the implementation of the CanExtend method and the HelpText property. The CanExtend method is used by the designer to determine whether to extend this property to a given control. The HelpLabelControl extends the HelpText property for use with the controls on a form. The help text for a control is displayed in a panel when the control has focus.


Function CanExtend(Target As Object ) As Boolean Implements IExtenderProvider.CanExtend
    If TypeOf Target Is Control And Not TypeOf Target Is HelpLabel Then


        Return True
    End If
    Return False
End Function




Public Function _
  <DefaultValue(""), ExtenderProperty(GetType(Control))> _
  GetHelpText(Ctrl As Control) As String
    Dim text As String = CStr(helpTexts(Ctrl))
    If text Is Nothing Then
        text = String.Empty
    End If
    Return text
End Function
VB



 
C# HelpLabel

[Run Sample] | [View Source]

User Controls

This sample demonstrates how to build a user control.



CustomerControl1.Anchor=System.Windows.Forms.AnchorStyles.All
CustomerControl1.AutoScrollMinSize = new System.Drawing.Size(0, 0)
CustomerControl1.Size = new System.Drawing.Size(400, 310)
CustomerControl1.TabIndex = 0
VB



 
VB UserControl

[Run Sample] | [View Source]

Licensing Controls

The following sample demonstrates how to build a control that uses the default licensing provider. The LicenseProvider attribute is used to indicate to the LicenseManager that the class is using the LicFileLicenseProvider. The constructor for the control calls LicenseManager.Validate to validate that the license is present.


Public Class <LicenseProviderAttribute(GetType(LicFileLicenseProvider))> _
LicensedControl : Inherits Control
    Private license As License = Nothing

    Public Sub New()
        MyBase.New

        license = LicenseManager.Validate(GetType(LicensedControl), Me)
    End Sub

    Public Overloads Overrides Sub Dispose()
        If license <> Nothing
            license.Dispose()
            license = Nothing
        End If
    End Sub

    Overrides Protected Sub OnPaint(e As PaintEventArgs)
        ' Paint the Text property on the control
         e.Graphics.DrawString(Me.Text, Font, New SolidBrush(ForeColor), _
                               ClientRectangle.x, ClientRectangle.y)
    End Sub
End Class ' LicensedControl
VB

Note: You must dispose of the license when your control is disposed.

 
VB LicensedControl

[Run Sample] | [View Source]


Copyright 2001 Microsoft Corporation. All rights reserved.