Expandable Properties - ColorEditor
This is a control I wrote as another experiment with Expandable Properties.
You can download the demo project: here
The Control itself only serves as a visual representation of the Expandable BorderColors Property, which has four sub-Properties, Bottom, Left, Right, + Top, all of type Color, which can all be set independently, and changing any of the sub-Properties is reflected in the display color of the corresponding border line on the control.
The Expandable Property - BorderColors - I've written so as to mimic a Color Property, which can be used in much the same way as a Color Property to set the color of all four border lines in one Property. I achieved this by utilizing a TypeEditor class as the UITypeEditor, which contains most of the code needed to give the Property very similar functionality to a Color Property.
Imports System.Drawing.Design
Imports System.Windows.Forms.Design
Imports System.Windows.Forms.PropertyGridInternal.PropertyGridCommands
Public Class TypeEditor1
Inherits UITypeEditor
Public Overrides Function GetEditStyle(ByVal context As System.ComponentModel.ITypeDescriptorContext) As System.Drawing.Design.UITypeEditorEditStyle
Return UITypeEditorEditStyle.DropDown
End Function
Public Overrides Function EditValue(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal provider As System.IServiceProvider, ByVal value As Object) As Object
If provider IsNot Nothing Then
Dim editorService As IWindowsFormsEditorService = TryCast(provider.GetService(GetType(IWindowsFormsEditorService)), IWindowsFormsEditorService)
If editorService Is Nothing Then
Return value
End If
' This is the Color type editor - it displays the drop-down UI calling
' our IWindowsFormsEditorService implementation.
Dim Editor As New ColorEditor
' Display the UI.
Dim c As Color
Dim NewValue As Object = Editor.EditValue(editorService, c)
' If the user didn't cancel the selection, return the new color.
If Not CType(NewValue, Color) = Color.Empty Then
Return New BorderColors(CType(NewValue, Color))
End If
End If
Return value
End Function
Public Overrides Function GetPaintValueSupported(ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
Return True
End Function
Public Overrides Sub PaintValue(ByVal e As System.Drawing.Design.PaintValueEventArgs)
Dim img As New Bitmap(18, 14)
Dim gr As Graphics = Graphics.FromImage(img)
Dim parts() As String = e.Value.ToString.Split(","c)
Dim c As Color = Nothing
If parts.Length = 3 Then
Dim values() As Integer = Array.ConvertAll(parts, Function(s) CInt(Val(s)))
c = Color.FromArgb(values(0), values(1), values(2))
gr.Clear(c)
Else
c = Color.FromName(e.Value.ToString)
gr.Clear(c)
End If
e.Graphics.DrawImage(img, New Point(2, 2))
MyBase.PaintValue(e)
End Sub
End Class
As you can see, this class contains 3 Overridden Functions + 1 Overridden Sub-procedure:
- GetEditStyle
Here, I specified that the Property uses a DropDown UI Editor
- EditValue
This is the Function that displays + retrieves the value from, the (DropDown) ColorEditor
- GetPaintValueSupported
Here I returned True to specify that I would be supplying a custom Property image
- PaintValue
This is where I draw the small colored rectangle which appears to the left of the Color name.
As an alternative the BorderColors Property can be changed by typing a value in the Properties Window, for example, typing in 255,0,0 would result in the displayed Property showing a Red rectangle + the word 'Red'. You can also just type in a Color name + it'll be recognized (if it exists). This functionality is achieved by using a TypeConverter class, (ExpandableObjectConverter) which converts between String + BorderColors.
This is the borderColors class, which contains the four sub-Properties, Bottom, Left, Right, + Top.
Sub New has 2 Overloads, allowing one Color for all 4 Properties, or independent Colors. I've Overridden the ToString Function which is what is displayed in the Properties Window + contains the name of the Color you've chosen. I used a Function to return the correct Color name, as there is some overlapping in the Colors class + if you choose [SystemColors] WindowText, it'll display 'WindowText' + not 'Black' + vice versa.
The BorderColors Property will only display a value in the Properties Window if all 4 sub-Properties are the same.
The 4 Properties have Attributes, that specify what to do if the Property is changed.:
Imports System.Drawing
Imports System.ComponentModel
<TypeConverter(GetType(BorderColorsConverter))>
Public Class BorderColors
' Auto Properties.
<RefreshProperties(RefreshProperties.All), _
NotifyParentPropertyAttribute(True)> _
Public Property Top As Color
<RefreshProperties(RefreshProperties.All), _
NotifyParentPropertyAttribute(True)> _
Public Property Right As Color
<RefreshProperties(RefreshProperties.All), _
NotifyParentPropertyAttribute(True)> _
Public Property Bottom As Color
<RefreshProperties(RefreshProperties.All), _
NotifyParentPropertyAttribute(True)> _
Public Property Left As Color
Public Sub New(all As Color)
Dim c As Color = GetColor(all)
Left = c
Top = c
Right = c
Bottom = c
End Sub
Public Sub New(cLeft As Color, cTop As Color, cRight As Color, cBottom As Color)
Left = GetColor(cLeft)
Top = GetColor(cTop)
Right = GetColor(cRight)
Bottom = GetColor(cBottom)
End Sub
Public Overrides Function ToString() As String
If Left = Top AndAlso Left = Right AndAlso Left = Bottom Then
Return GetColorName(Left)
Else
Return String.Empty
End If
End Function
Private Function GetColorName(c As Color) As String
If (c.IsSystemColor OrElse c.IsNamedColor OrElse c.IsKnownColor) And Not c.Name.Contains(",") Then Return c.Name
Dim allColors = [Enum].GetNames(GetType(System.Drawing.KnownColor))
For i = 0 To allColors.Count - 1
Dim thisCol = Color.FromName(allColors(i))
If (c.R = thisCol.R) AndAlso (c.G = thisCol.G) AndAlso (c.B = thisCol.B) Then
Return thisCol.Name
End If
Next
Return c.R & ", " & c.G & ", " & c.B
End Function
Private Function GetColor(c As Color) As Color
If (c.IsSystemColor OrElse c.IsNamedColor OrElse c.IsKnownColor) And Not c.Name.Contains(",") Then Return c
Dim allColors = [Enum].GetNames(GetType(System.Drawing.KnownColor))
For i = 0 To allColors.Count - 1
Dim thisCol = Color.FromName(allColors(i))
If (c.R = thisCol.R) AndAlso (c.G = thisCol.G) AndAlso (c.B = thisCol.B) Then
Return thisCol
End If
Next
Return Color.FromArgb(c.R, c.G, c.B)
End Function
End Class
I removed all of the extraneous designtime Properties from the Control, this time by using a ControlDesigner class, as this gives the Properties Window a much less cluttered appearance.
This ControlDesigner has just 1 Overridden sub-Procedure, where any Properties not named in the keep() array are removed.
Imports System.Windows.Forms.Design
Public Class borderControlDesigner
Inherits ControlDesigner
Protected Overrides Sub PreFilterProperties(ByVal properties As System.Collections.IDictionary)
MyBase.PreFilterProperties(properties)
'this removes all of the control's properties except those contained in the keep() array
Dim pi As List(Of DictionaryEntry) = properties.Cast(Of DictionaryEntry).ToList
Dim keep() As String = {"AutoSize", "BackColor", "BorderColors", "Font", "ForeColor", "Location", "Name", "Size"}
For x As Integer = pi.Count - 1 To 0 Step -1
If Not keep.Any(Function(s) pi(x).Key.ToString = s OrElse pi(x).Key.ToString.StartsWith(s & "_")) Then
properties.Remove(pi(x).Key.ToString)
End If
Next
End Sub
End Class