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 UITypeEditor
s, 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? UITypeEditor
s 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 ActionItem
s 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 UIEditor
s.
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 FocalPoint
s 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