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:
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.
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:
- Start the Visual Studio .NET Windows Forms designer.
- Add a new form by clicking New VB or New C# from the File
menu.
- Click Add Library on the Edit menu.
- Select the DLL containing the control you want to use.
The control appears at the bottom of the toolbox.
- Select the control and add it to the form.
You will see the control appear on the form.
- 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.
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.
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
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
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.
Copyright 2001 Microsoft Corporation. All rights reserved.