Introduction
This article assumes you have basic knowledge on creating a simple UserControl, so I am not going to go into much detail on creating one here. To demonstrate the Property Editors, I decided to make a control that draws itself in a selected shape. This gave me lots of options to demonstrate different design time editors. This demo uses UITypeEditors, Smart Tags, ControlDesigner Verbs, and Expandable Properties.
Property Value versus Displayed Value
The property value displayed in the property grid is a string representation of the actual property value. For example, a control's Visible property is type Boolean. The value is either 0 or -1, but the property grid doesn't display this. It converts the value 0 to the string "False" and the value -1 to the string "True". Different property value types have different interface types to edit their value.
Property Types
These are the basic property types and a common example:
- TextBox
TabIndex -> Edit the value directly in the display textbox.
- Listbox
BackgroundImageLayout -> Dropdown list of enumerated string values.
- UI Dropdown
ForeColor -> Dropdown area with graphical selection interface.
- UI Modal
BackgroundImage -> Separate dialog form opens to select the property value.
- Expandable
Font -> Property with plus sign next to it to expand out all the child properties. (Note: In addition, Font also has a Modal option.)
Textbox
A simple property type like Single:
Private _BorderWidth As Single = 2
<Description("Get or Set the Width of the border around the shape")> _
<Category("Shape")> _
<DefaultValue(GetType(Single), "2")> _
Public Property BorderWidth() As Single
Get
Return _BorderWidth
End Get
Set(ByVal value As Single)
_BorderWidth = value
Invalidate()
End Set
End Property
Listbox
A property type that has enumerated values like DashStyle. This property will automatically give a list of the DashStyle text values to choose from, or create your own type with an ENUM list.
Enum eFillType
Solid
GradientLinear
GradientPath
Hatch
End Enum
Private _FillType As eFillType = eFillType.Solid
<Description("The Fill Type to apply to the Shape")> _
<Category("Shape")> _
<DefaultValue(GetType(eFillType), "Solid")> _
Public Property FillType() As eFillType
Get
Return _FillType
End Get
Set(ByVal value As eFillType)
_FillType = value
Me.Invalidate()
End Set
End Property
UITypeEditor
What if you want a more visual way of choosing property values? UITypeEditors is the answer. There are two types: Dropdown and Modal. Each has the same overrideable methods.
GetEditStyle - Gets the editor style used by the EditValue method
EditValue - Edits the property value using the UITypeEditor
Optional if the following features are wanted. Sometimes, you want to have a small graphic representation displayed next to the text value, as in the color box next to the color name in a color type property like ForeColor.
GetPaintValueSupported - Returns True to allow overriding the PaintValue method
PaintValue - Paints the graphic in the display value
DropDown mode has one extra method:
IsDropDownResizable - Indicates if a grip handle is visible to allow the user to resize the dropdown.
DropDown
The DropDown mode creates an area that drops down below the property value. Here is where you place a UserControl that edits and returns the property value. I have two kinds of DropDowns demonstrated. One uses an owner-drawn listbox (BorderStyleEditor) and the other uses a separate UserControl, kind of like a mini-form. The BorderStyleEditor uses a simple CustomControl class (LineStyleListBox) that inherits the ListBox and draws its own items in the DrawItem event. Instead of just listing the text values, each DashStyle is drawn in that style. Now that you have a control to visually select the DashStyle, you need to add an IWindowsFormsEditorService to make it work in the editor. Also, if you don't want the custom control to show in the ToolBox, add <ToolboxItem(False)>.
<ToolboxItem(False)> _
Public Class LineStyleListBox
Inherits ListBox
Private _lineColor As Color = Color.Black
Public Property LineColor() As Color
Get
Return _lineColor
End Get
Set(ByVal Value As Color)
_lineColor = Value
End Set
End Property
Private m_EditorService As IWindowsFormsEditorService
Public Sub New(ByVal line_style As DashStyle, _
ByVal editor_service As IWindowsFormsEditorService, _
ByVal Line_Color As Color)
MyBase.New()
m_EditorService = editor_service
For i As Integer = 0 To 4
Items.Add(i)
Next i
LineColor = Line_Color
SelectedIndex = DirectCast(line_style, Integer)
DrawMode = Windows.Forms.DrawMode.OwnerDrawFixed
ItemHeight = 18
End Sub
.
.
.
End Class
To make the editor display the new control, you need a class that inherits the UITypeEditor.
Public Class BorderStyleEditor
Inherits UITypeEditor
Public Overrides Function GetEditStyle(ByVal context As ITypeDescriptorContext) _
As UITypeEditorEditStyle
Return UITypeEditorEditStyle.DropDown
End Function
Public Overrides Function EditValue(ByVal context As ITypeDescriptorContext, _
ByVal provider As IServiceProvider, _
ByVal value As Object) As Object
Dim editor_service As IWindowsFormsEditorService = _
CType(provider.GetService(GetType(IWindowsFormsEditorService)), _
IWindowsFormsEditorService)
If editor_service Is Nothing Then
Return MyBase.EditValue(context, provider, value)
End If
Dim line_style As DashStyle = DirectCast(value, DashStyle)
Using editor_control As New LineStyleListBox(line_style, _
editor_service, _
CType(context.Instance, Shape).BorderColor)
editor_service.DropDownControl(editor_control)
Return CType(editor_control.SelectedIndex, DashStyle)
End Using
End Function
Public Overrides Function GetPaintValueSupported( _
ByVal context As ITypeDescriptorContext) As Boolean
Return True
End Function
Public Overrides Sub PaintValue(ByVal e As PaintValueEventArgs)
e.Graphics.FillRectangle(Brushes.White, e.Bounds)
Dim LnColor As Color
If IsNothing(e.Context) Then
LnColor = Color.Black
Else
LnColor = CType(e.Context.Instance, Shape).BorderColor
End If
DrawSamplePen( _
e.Graphics, _
e.Bounds, _
LnColor, _
DirectCast(e.Value, DashStyle))
End Sub
End Class
To make the whole thing come together, apply the editor to the property declaration:
Private _BorderStyle As DashStyle = DashStyle.Solid
<Category("Shape")> _
<Description("The line dash style used to draw state borders.")> _
<Editor(GetType(BorderStyleEditor), GetType(UITypeEditor))> _
<DefaultValue(GetType(DashStyle), "Solid")> _
Public Property BorderStyle() As DashStyle
Get
Return _BorderStyle
End Get
Set(ByVal value As DashStyle)
_BorderStyle = value
Me.Invalidate()
End Set
End Property
What Does the UITypeEditor Do?
It gets the style using GetEditStyle:
Public Overrides Function GetEditStyle( _
ByVal context As System.ComponentModel.ITypeDescriptorContext) _
As System.Drawing.Design.UITypeEditorEditStyle
Return UITypeEditorEditStyle.DropDown
End Function
To edit the value in the EditValue method:
- Create an
EditorService.
- Create a new
LineStyleListBox control.
- Using
EditorService.DropDownControl (control reference), it opens the dropdown with the control in it. After the item is selected, the CloseDropDown is called to return the value back to EditValue to be displayed in the PropertyGrid.
Public Overrides Function EditValue(ByVal context As ITypeDescriptorContext, _
ByVal provider As IServiceProvider, _
ByVal value As Object) As Object
Dim editor_service As IWindowsFormsEditorService = _
CType(provider.GetService(GetType(IWindowsFormsEditorService)), _
IWindowsFormsEditorService)
If editor_service Is Nothing Then
Return MyBase.EditValue(context, provider, value)
End If
Dim line_style As DashStyle = DirectCast(value, DashStyle)
Using editor_control As New LineStyleListBox(line_style, _
editor_service, _
CType(context.Instance, Shape).BorderColor)
editor_service.DropDownControl(editor_control)
Return CType(editor_control.SelectedIndex, DashStyle)
End Using
End Function
Add a graphic to the display value:
- Using the
PaintValue method, a small graphic of the DashStyle is drawn next to the DashStyle name.
Public Overrides Sub PaintValue(ByVal e As PaintValueEventArgs)
e.Graphics.FillRectangle(Brushes.White, e.Bounds)
Dim LnColor As Color
If IsNothing(e.Context) Then
LnColor = Color.Black
Else
LnColor = CType(e.Context.Instance, Shape).BorderColor
End If
DrawSamplePen( _
e.Graphics, _
e.Bounds, _
LnColor, _
DirectCast(e.Value, DashStyle))
End Sub

The ShapeTypeEditor is another way of using the DropDown UITypeEditor. It uses a UserControl (DropdownShapeEditor) that lets you select the shape by clicking on a graphical representation of the shape. In the EditValue method, after creating a new DropdownShapeEditor UserControl, you could simply highlight the shape the mouse is over, but I wanted it to reflect what the original shape looks like, so the properties of the original Shape control are passed over to the Shape control in the DropdownShapeEditor, so when the mouse passes over the shape, it takes on the visual properties of the original Shape control. Selecting the shape calls CloseDropDown.

Another example of the DropDown is the RadiusInnerTypeEditor. It uses the DropdownRadiusInner UserControl, which contains a TrackBar and a Shape control. If the Shape is a Star, sliding the TrackBar back and forth changes the RadiusInner property value on the Star shape. When you get it the way you want, check the Apply button to call CloseDropDown.
The last Dropdown is the BlendTypeEditor. It uses the DropdownColorBlender UserControl, which is a slight variation on my ColorBlender UserControl[^] The UserControl may be more complex, but there is very little difference in the EditValue method.
There are two examples of editors for HatchStyle. For a quick and dirty editor, there is the HatchStyleEditorEasy.
Public Class HatchStyleEditorEasy
Inherits UITypeEditor
Public Overrides Function GetPaintValueSupported(
ByVal context As ITypeDescriptorContext) As Boolean
Return True
End Function
Public Overrides Sub PaintValue(ByVal e As PaintValueEventArgs)
Dim hatch As HatchStyle = CType(e.Value, HatchStyle)
Using br As Brush = New HatchBrush(hatch, SystemColors.WindowText, SystemColors.Window)
e.Graphics.FillRectangle(br, e.Bounds)
End Using
End Sub
End Class
HatchStyleEditor is a better looking property editor. It is very similar to the BorderStyleEditor, however to reflect the current colors in the property display you need to get a current instance of the control to retrieve those properties. One major hitch I discovered is that for some unknown reason when this editor is used in the SmartTag. The Context value becomes Null in the PaintValue Sub which will cause a fatal error and crash the IDE. To work around this, I saw that the context value is OK in the GetPaintValueSupported function so I grab a reference to it there and store it in the SmartContext variable, and in the PaintValue sub check for Null and use then use the SmartContext variable instead.
Private SmartContext As ITypeDescriptorContext
Public Overrides Function GetPaintValueSupported(
ByVal context As ITypeDescriptorContext) As Boolean
SmartContext = context Return True
End Function
Public Overrides Sub PaintValue(ByVal e As PaintValueEventArgs)
Dim hatch As HatchStyle = CType(e.Value, HatchStyle)
Dim Instance As New Shape
If e.Context IsNot Nothing Then
Instance = CType(e.Context.Instance, Shape)
Else
Instance = CType(SmartContext.Instance, ShapeActionList).CurrShape
End If
Using br As Brush = New HatchBrush(hatch, Instance.ColorFillSolid,
Instance.ColorFillSolidB)
e.Graphics.FillRectangle(br, e.Bounds)
End Using
End Sub
Here you can see the difference between the two representations of the same property type, and which is obviously better.
Modal
Modal is the other type of UITypeEditor. Instead of a dropdown, a separate dialog form opens up, just like Font or BackgroundImage does. The FocalTypeEditor first creates a new dialog form (dlgFocalPoints) that lets you adjust the property values the way you want.
The GetEditValue returns UITypeEditorEditStyle.Modal.
With Modal, the EditValue's EditorService will use EditorService.ShowDialog (dialog reference). There is no CloseDropdown needed, just closing the dialog does the same thing. Using a dialog is nice when you want a little more real estate. I used this for a property I called FocalPoints. Changing the CenterPoint and FocusScales properties of a ColorBlend made more visual sense when I could work with them together. I made a form that would allow the tweaking of these values visually, but because the editors only return one property value, I created a cFocalPoints class that contains two PointF values that represent the CenterPoint and FocusScales as one.
ControlDesigner
Alot of cool DesignMode additions can be added here to make the design time experience even more rich.
- Mouse interaction beyond selection and resizing
- Extra Design time only painting
- Resize restrictions
- Smart Tags
- Verbs
Let's take another look at how the FocalPoints can be handled in a different way. It would be better if you could just click on the control directly and move the points around. Normally mouse events are ignored in the Designer. The control can only be selected or resized with the mouse, but in the ControlDesigner you can override the GetHitTest function to tell the designer to process the mouse events. There is also OnMouseEnter, OnMouseHover, and OnMouseLeave which are located in the ControlDesigner. With the GetHitTest function the control can be told to process the control's Mouse Events by checking DesignMode property and then handle the Design time routines. Note because you override the GetHitTest the control won't select properly so you need to set it manually select it with the ISelectionService.
After the control paints itself, the OnPaintAdornments sub lets you paint additional things on the control only at design time like a selection rectangle. When the control has a FillType of GradientPath a circle is drawn around where the CenterPoint is and a square where the FocusPoint is. Then Click on that spot to drag the point around. or Right-Click to Reset the Point

In the ControlDesigner class:
Protected Overrides Function GetHitTest( _
ByVal point As System.Drawing.Point) As Boolean
point = _Shape.PointToClient(point)
_Shape.CenterPtTracker.IsActive = _
_Shape.CenterPtTracker.TrackerRectangle.Contains(point)
_Shape.FocusPtTracker.IsActive = _
_Shape.FocusPtTracker.TrackerRectangle.Contains(point)
Return _Shape.CenterPtTracker.IsActive Or _Shape.FocusPtTracker.IsActive
End Function
Protected Overrides Sub OnMouseEnter()
MyBase.OnMouseEnter()
TheBox = True
_Shape.Invalidate()
End Sub
Protected Overrides Sub OnMouseLeave()
MyBase.OnMouseLeave()
TheBox = False
_Shape.Invalidate()
End Sub
Protected Overrides Sub OnPaintAdornments _
(ByVal pe As System.Windows.Forms.PaintEventArgs)
MyBase.OnPaintAdornments(pe)
If _Shape.FillType = Shape.eFillType.GradientPath And TheBox Then
Using g As Graphics = pe.Graphics
Using pn As Pen = New Pen(Color.Gray, 1)
pn.DashStyle = DashStyle.Dot
g.FillEllipse( _
New SolidBrush(Color.FromArgb(100, 255, 255, 255)), _
_Shape.CenterPtTracker.TrackerRectangle)
g.DrawEllipse(pn, _Shape.CenterPtTracker.TrackerRectangle)
g.FillRectangle( _
New SolidBrush(Color.FromArgb(100, 255, 255, 255)), _
_Shape.FocusPtTracker.TrackerRectangle)
g.DrawRectangle(pn, _Shape.FocusPtTracker.TrackerRectangle)
End Using
End Using
End If
End Sub
In the Control Class:
Private Sub Shape_MouseDown(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown
If DesignMode Then
Dim selectservice As ISelectionService = _
CType(GetService(GetType(ISelectionService)), ISelectionService)
Dim selection As New ArrayList
selection.Clear()
selectservice.SetSelectedComponents(selection, SelectionTypes.Replace)
selection.Add(Me)
selectservice.SetSelectedComponents(selection, SelectionTypes.Add)
If e.Button = Windows.Forms.MouseButtons.Right Then
If Me.CenterPtTracker.IsActive Then
Me.FocalPoints = New cFocalPoints( _
New PointF(0.5, 0.5), _
Me.FocalPoints.FocusScales)
Me.Invalidate()
ElseIf Me.FocusPtTracker.IsActive Then
Me.FocalPoints = New cFocalPoints( _
Me.FocalPoints.CenterPoint, _
New PointF(0, 0))
Me.Invalidate()
End If
End If
End If
End Sub
Private Sub Shape_MouseMove(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
If DesignMode Then
If e.Button = Windows.Forms.MouseButtons.Left Then
If Me.CenterPtTracker.IsActive Then
Me.FocalPoints = New cFocalPoints( _
New PointF(e.X / Me.Width, e.Y / Me.Height), _
Me.FocalPoints.FocusScales)
Me.Invalidate()
ElseIf Me.FocusPtTracker.IsActive Then
Me.FocalPoints = New cFocalPoints( _
Me.FocalPoints.CenterPoint, _
New PointF(e.X / Me.Width, e.Y / Me.Height))
Me.Invalidate()
End If
End If
End If
End Sub
Another feature you can apply (I'll explain but I didn't put a functioning example in the code) is changing the SelectionRules property. If you want to restrict sizing or moving or visibility this is the one. A normal TextBox can only be sized horizontally unless you make the Multiline property True, then you can resize vertically too. The below example will give your control the horizontal only effect.
Public Overrides ReadOnly Property SelectionRules() _
As System.Windows.Forms.Design.SelectionRules
Get
Return SelectionRules.LeftSizeable _
Or SelectionRules.RightSizeable _
Or Windows.Forms.Design.SelectionRules.Visible _
Or Windows.Forms.Design.SelectionRules.Moveable
End Get
End Property
Smart Tags
Now that you have made these cool property editors, it would be nice to access them in an organized way. The PropertyGrid lists the properties alphabetically in each category, and with a careful naming structure, you can get some organization. A Smart Tag will give you an independent area to organize select properties the way you want and label them differently than the sometimes cryptic property names. Click on the little arrow that appears in the upper right corner of some controls to open up the Smart Tag.

To make Smart Tags, you need two classes. First, create a class that inherits ControlDesigner and override the ActionLists property to add the second class's DesignerActionListCollection. The second class inherits DesignerActionList. In the New event, add a reference to the component being designed and to the DesignerActionUIService, and if you want the Smart Tag to open automatically, add Me.AutoShow = True.
Make a list of properties you want to appear in the Smart Tag (order does not matter here). The property is like the property in the component. except you get the property value from the component reference and set it using the TypeDescriptor SetValue to keep it in sync with the IDE.
Public Class ShapeActionList
Inherits DesignerActionList
Private _ShapeSelector As Shape
Private _DesignerService As DesignerActionUIService = Nothing
Public Sub New(ByVal component As IComponent)
MyBase.New(component)
_ShapeSelector = DirectCast(component, Shape)
_DesignerService = _
CType(GetService(GetType(DesignerActionUIService)), _
DesignerActionUIService)
Me.AutoShow = True
End Sub
<Editor(GetType(ShapeTypeEditor), GetType(UITypeEditor))> _
Public Property Shape() As Shape.eShape
Get
Return _ShapeSelector.Shape
End Get
Set(ByVal value As Shape.eShape)
SetControlProperty("Shape", value)
End Set
End Property
Private Sub SetControlProperty(ByVal property_name As String, ByVal value As Object)
TypeDescriptor.GetProperties(_ShapeSelector) _
(property_name).SetValue(_ShapeSelector, value)
End Sub
.
.
.
End Class
ActionList Item Types
Types of items that can be added to a Smart Tag:
DesignerActionHeaderItem
Bold text heading each area of grouped items.
DesignerActionTextItem
String text that just displays information.
DesignerActionPropertyItem
A control will represent each property in the Smart Tag.
Textbox, Dropdown, and Modal will appear as they do in the PropertyGrid, but Boolean properties will appear as a checkbox. Methods will appear as hypertext. The expandable properties don't play nice, however. Only the string value for all the child properties is shown and no plus sign to expand it. You would have to type the whole string in the exact order and format to make any changes (not very friendly). You could separate them out and handle them individually, but that takes up a lot of space on the Smart Tag. Instead, what is better is an Action Method that opens a dialog to edit all the child properties using an IDesignerHost (more on that later). After all the properties are made, they can be added to the ActionList.
DesignerActionMethodItem
Blue hypertext that when clicked calls the routine assigned to it.
Adding Items to the ActionList
Public Overrides Function GetSortedActionItems() As _
System.ComponentModel.Design.DesignerActionItemCollection
This is where you can order the ActionItems on the Smart Tag the way you want. Create a reference to a new DesignerActionItemCollection and start adding the item headers first.
Dim items As New DesignerActionItemCollection()
Examples
Header
items.Add(New DesignerActionHeaderItem("Shape Appearance"))
Text
Format:
Text,
Header Name to put item in
Sample:
Dim txt As String = "Width=" & _ShapeSelector.Width & _
" Height=" & _ShapeSelector.Height
items.Add( _
New DesignerActionTextItem( _
txt, _
"Information"))
Property
Format:
Property Name,
Label Text,
Header Name to put item in,
Description
Sample:
items.Add( _
New DesignerActionPropertyItem( _
"Shape", _
"Shape", _
"Shape Appearance", _
"The Shape of the Control"))
Method
Format:
ActionList,
Property Name,
Label Text,
Header Name to put item in,
Description,
Show HyperText in PropertyGrid
Sample:
items.Add( _
New DesignerActionMethodItem( _
Me, _
"AdjustCorners", _
"Adjust Corners ", _
"Rectangle Only", _
"Adjust Corners", _
True))

Verbs
At the bottom of the PropertyGrid, you can add hypertext that when clicked, calls a method from the Control Designer. To do this, override the Verbs property and add a new DesignerVerb. This new verb has a reference to an event that does what you want. If you need the text for the verb to change, it can't be changed directly, so you have to remove it from the IMenuCommandService and then add it back with the updated text.
Public Overrides ReadOnly Property Verbs() As _
System.ComponentModel.Design.DesignerVerbCollection
Get
Dim myVerbs As DesignerVerbCollection = _
New DesignerVerbCollection
ClipRegion = New DesignerVerb(GetVerbText, _
New EventHandler(AddressOf ClipToRegionClicked))
myVerbs.Add(ClipRegion)
Return myVerbs
End Get
End Property
Private Function GetVerbText() As String
Return "Region Clipping " & IIf(_Shape.RegionClip, "ON", "OFF")
End Function
Public Sub ClipToRegionClicked(ByVal sender As Object, ByVal e As EventArgs)
Me.VerbRegionClip = Not Me.VerbRegionClip
End Sub
Public Property VerbRegionClip() As Boolean
Get
Return _Shape.RegionClip
End Get
Set(ByVal value As Boolean)
Dim prop As PropertyDescriptor = _
TypeDescriptor.GetProperties(GetType(Shape)) _
("RegionClip")
Me.RaiseComponentChanging(prop)
_Shape.RegionClip = value
Me.RaiseComponentChanged(prop, Not (_Shape.RegionClip), _Shape.RegionClip)
Dim menuService As IMenuCommandService = _
CType(Me.GetService(GetType(IMenuCommandService)), IMenuCommandService)
If Not (menuService Is Nothing) Then
If menuService.Verbs.IndexOf(ClipRegion) >= 0 Then
menuService.Verbs.Remove(ClipRegion)
ClipRegion = New DesignerVerb( _
GetVerbText, _
New EventHandler(AddressOf ClipToRegionClicked))
menuService.Verbs.Add(ClipRegion)
End If
End If
_Shape.Refresh()
End Set
End Property
If you do not see the hyperlink, Right Click and check Commands.
Expandable Property
Now, back to the expandable Corners property. First, how to make one, and then, how to deal with it in the Smart Tag.
For, the Corners property, I wanted to have an expandable property like the Padding property. I could have simply made a property from the Padding type, but the names were wrong (All, Top, Bottom, Left, Right). I wanted All, UpperRight, UpperLeft, LowerRight, LowerLeft.
The Corners property value looks like this: 2, 2, 2, 2 in the property grid, which is actually a string representation of the four properties (with the fifth hidden). There needs to be a process to convert the real properties to and from this string.
First, I needed the TypeConverter properties class:
<TypeConverter(GetType(CornerConverter))> _
Public Class CornersProperty
.
.
.
<DescriptionAttribute("Set the Radius of the Upper Left Corner"))> _
<RefreshProperties(RefreshProperties.Repaint))> _
<NotifyParentProperty(True))> _
<DefaultValue(0)> _
Public Property UpperLeft() As Short
Get
Return _UpperLeft
End Get
Set(ByVal Value As Short)
_UpperLeft = Value
CheckForAll(Value)
End Set
End Property
.
.
.
End Class
Then, the actual converter class which inherits ExpandableObjectConverter. Simply put, this just splits the string into pieces and assigns the parts to each property, or joins each property into the string to display as the Corners property value.
Friend Class CornerConverter : Inherits ExpandableObjectConverter
Public Overloads Overrides Function CanConvertFrom( _
ByVal context As System.ComponentModel.ITypeDescriptorContext, _
ByVal sourceType As System.Type) As Boolean
If (sourceType Is GetType(String)) Then
Return True
End If
Return MyBase.CanConvertFrom(context, sourceType)
End Function
Public Overloads Overrides Function ConvertFrom( _
ByVal context As System.ComponentModel.ITypeDescriptorContext, _
ByVal culture As System.Globalization.CultureInfo, _
ByVal value As Object) As Object
If TypeOf value Is String Then
Try
Dim s As String = CType(value, String)
Dim cornerParts(4) As String
cornerParts = Split(s, ",")
If Not IsNothing(cornerParts) Then
If IsNothing(cornerParts(0)) Then cornerParts(0) = 0
If IsNothing(cornerParts(1)) Then cornerParts(1) = 0
If IsNothing(cornerParts(2)) Then cornerParts(2) = 0
If IsNothing(cornerParts(3)) Then cornerParts(3) = 0
Return New CornersProperty( _
cornerParts(0), _
cornerParts(1), _
cornerParts(2), _
cornerParts(3))
End If
Catch ex As Exception
Throw New ArgumentException("Can not convert '" & _
value & "' to type Corners")
End Try
Else
Return New CornersProperty()
End If
Return MyBase.ConvertFrom(context, culture, value)
End Function
Public Overloads Overrides Function ConvertTo( _
ByVal context As System.ComponentModel.ITypeDescriptorContext, _
ByVal culture As System.Globalization.CultureInfo, _
ByVal value As Object, ByVal destinationType As System.Type) As Object
If (destinationType Is GetType(System.String) _
AndAlso TypeOf value Is CornersProperty) Then
Dim _Corners As CornersProperty = CType(value, CornersProperty)
Return String.Format("{0},{1},{2},{3}", _
_Corners.LowerLeft, _
_Corners.LowerRight, _
_Corners.UpperLeft, _
_Corners.UpperRight)
End If
Return MyBase.ConvertTo(context, culture, value, destinationType)
End Function
End Class
If you need the Control to reflect the changes made to the child properties immediately the <RefreshProperties(RefreshProperties.Repaint))> and <NotifyParentProperty(True))> in the Property are not enough to trigger a refresh. Add the GetCreateInstanceSupported and CreateInstance Functions to the ExpandableObjectConverter Class. GetCreateInstanceSupported should simply always Return True, then in the CreateInstance Function create a new Instance and Return it.
Public Overrides Function GetCreateInstanceSupported( _
ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
Return True
End Function
Public Overrides Function CreateInstance( _
ByVal context As System.ComponentModel.ITypeDescriptorContext, _
ByVal propertyValues As System.Collections.IDictionary) As Object
Dim crn As New CornersProperty
Dim AL As Short = CType(propertyValues("All"), Short)
Dim LL As Short = CType(propertyValues("LowerLeft"), Short)
Dim LR As Short = CType(propertyValues("LowerRight"), Short)
Dim UL As Short = CType(propertyValues("UpperLeft"), Short)
Dim UR As Short = CType(propertyValues("UpperRight"), Short)
Dim oAll As Short = CType(CType(context.Instance, Shape).Corners, _
CornersProperty).All
If oAll <> AL And AL > -1 Then
crn.All = AL
Else
crn.LowerLeft = LL
crn.LowerRight = LR
crn.UpperLeft = UL
crn.UpperRight = UR
End If
Return crn
End Function
The CornersProperty was a bit trickier because each property could change the other properties, so I had to look back to the original values from the context.Instance to see what changed before creating the new instance.
For a more straight forward example, here is the CreateInstance for the FocalPoints:
Public Overrides Function CreateInstance( _
ByVal context As System.ComponentModel.ITypeDescriptorContext, _
ByVal propertyValues As System.Collections.IDictionary) As Object
Dim fPt As New cFocalPoints
fPt.CenterPtX = CType(propertyValues("CenterPtX"), Single)
fPt.CenterPtY = CType(propertyValues("CenterPtY"), Single)
fPt.FocusPtX = CType(propertyValues("FocusPtX"), Single)
fPt.FocusPtY = CType(propertyValues("FocusPtY"), Single)
Return fPt
End Function
To deal with this property in the Smart Tag, I made a dialog (dlgCorners) that allowed me to adjust the corners visually and open it in a method using an IDesignerHost.
If dlg.ShowDialog() = DialogResult.OK Then
Dim designerHost As IDesignerHost = _
CType(Me.Component.Site.GetService( _
GetType(IDesignerHost)), IDesignerHost)
If designerHost IsNot Nothing Then
Dim t As DesignerTransaction = designerHost.CreateTransaction()
Try
SetControlProperty("Corners", _
New CornersProperty( _
dlg.TheShape.Corners.LowerLeft / ratio, _
dlg.TheShape.Corners.LowerRight / ratio, _
dlg.TheShape.Corners.UpperLeft / ratio, _
dlg.TheShape.Corners.UpperRight / ratio))
t.Commit()
Catch
t.Cancel()
End Try
End If
End If
_ShapeSelector.Refresh()

The ColorFillBlend property is another example of an expandable property plus it has a Modal dialog option button too.
How to Override a Base Editor
Some Property Types need a little extra step to work properly. The Color property has a built in editor system. If you create your own Color Picker control, you can substitute yours for the Microsoft Color Picker. If you have a more complex Color Picker and need it to open in a Modal Dialog, you will find a problem. A simple dropdown list will appear instead of the custom dialog. In order to make it play nice, the GetStandardValuesSupported in the TypeConverter must be turned off by creating a new ColorConverter and overriding the Function.
First make a Converter class:
Imports System.ComponentModel
Public Class AltColorConverter
Inherits ColorConverter
Public Overrides Function GetStandardValuesSupported( _
ByVal context As ITypeDescriptorContext) As Boolean
Return False
End Function
End Class
Then add the TypeConverter attribute:
Private _colorC As Color = Color.White
<Category("Appearance")> _
<DefaultValue(GetType(Color), "White")> _
<Editor(GetType(AltColorPickerModalUI), GetType(UITypeEditor))> _
<TypeConverter(GetType(AltColorConverter))> _
Public Property ColorC() As Color
Get
Return _colorC
End Get
Set(ByVal Value As Color)
_colorC = Value
Panel4.BackColor = ColorC
Invalidate()
End Set
End Property
Look at Form2 to see examples of different ways of implementing a custom Color Editor to a Color Property.
ColorExampleControl.vb
The ColorExampleControl is a simple UserControl with different Color Properties utilizing different UIEditors.
ColorA - uses the AltColorPicker Dropdown and AltColorPickerDropDownUI Editor. The IWindowsFormsEditorService is not transferred to the AltColorPicker Dropdown so the property will not update until the Dropdown loses focus and closes on its own.
ColorB - uses the AltColorPicker_ES Dropdown and AltColorPickerDropDownUI_ES Editor. Here the IWindowsFormsEditorService is passed through so the CloseDropDown can be called were ever needed.
ColorC - uses the AltColorModalDialog Dialog and AltColorPickerModalUI Editor.
Global Custom TypeEditor
The TypeDescriptor.AddAttributes allows you to change ALL color property whether Base or Custom to use the same Editor throughout the entire project. Remove the editor attributes from the properties and use the code below.
*** Note if you change your mind and take this out, you have to close the project and re-open to reset correctly.
TypeDescriptor.AddAttributes(GetType(Color), _
New EditorAttribute(GetType(AltColorPickerDropDownUI_ES), _
GetType(UITypeEditor)), _
New TypeConverterAttribute(GetType(Color)))
History
- Version 1.0 - September 2008
- Version 1.1 - September 2008
- Using this
.TheShape.FocalPoints = Instance.FocalPoints doesn't always get committed. For example, if you only change the FocalPoints by themselves, it visually appears to have worked in the designer, but after the Build it reverts back. To fix it, I changed to this .TheShape.FocalPoints = New cFocalPoints(Instance.FocalPoints.CenterPoint, Instance.FocalPoints.FocusScales) and all is right again.
- Version 1.2 - December 2008
- Added New
ControlDesigner Features GetHitTest, OnPaintAdornments, and SelectionRules. Mouse interaction with the control directly on the design surface.
- Version 1.3 - December 2008
- Fixed the
ExpandableObjectConverter classes CornerConverter and FocalPointsConverter so they update the control immediately after a Child property is changed.
- Added two
HatchStyle Editors to show different ways to handle them.
- Version 1.4 - January 2011
- Added the Type Converter to the
ColorFillBlend
- Various of tweaks and fixes
- Version 1.5 - January 2011
- Added
Form2 demo of how to use Custom Editors on the Color Property