Over the last couple of months, I have been developing a new skinning engine for the new versions of my applications. To help me test the new skin object model, I have created a skin builder application that relies heavily on the property grid control.
I quickly found that I didn't like the default support for bitwise enum types in the property grid. For these enum types, the property grid uses the normal drop-down list and allows for only one item to be selected. This means that if you want to set multiple values, you have to manually enter them in the property grids textbox.
I thought that a better way of doing this would be to use a CheckedListBox control instead of the normal ListBox control in the UITypeEditor. True to my style, I rushed in to coding it, only to find out that several other people have done it. It was fun, but with my lack of time, I should have checked Google first. In any case, to maintain some sense of worth and satisfaction, instead of throwing away my code, I made it better than the examples I have seen so far.
My version of the enum editor will auto-detect if the flags enum has been defined. It will either display a CheckedListBox if the Flags attribute is defined, or the standard ListBox if the Flags attribute isn't defined. This makes the editor more flexible as it can be used on any enum.
Hope you find this helpful.
''' -----------------------------------------------------------------------------
''' Class : EnumEditor
'''
''' -----------------------------------------------------------------------------
''' <summary>
''' Generic Enum Editor.
''' </summary>
''' <remarks>
''' The default enum editor only display a normal drop-down list.
''' As such, to define multiple enum values, they must be typed into the property grid.
''' This editor will determine if an enum is a Flags enum and display either a
''' normal drop-down list or a checkbox drop-down list. This will make it easier to use the editor
''' to set multiple enum values, while still supporting not-bitwise enums.
''' </remarks>
''' <history>
''' [rprimrose] 15/Mar/2005 Created
''' </history>
''' -----------------------------------------------------------------------------
Public Class EnumEditor
#Region " Declarations "
Inherits System.Drawing.Design.UITypeEditor
''' -----------------------------------------------------------------------------
''' <summary>
''' The service object used to reference the editor.
''' </summary>
''' <remarks>
''' None.
''' </remarks>
''' <history>
''' [rprimrose] 15/Mar/2005 Created
''' </history>
''' -----------------------------------------------------------------------------
Private m_objService As IWindowsFormsEditorService
#End Region
#Region " Sub Procedures "
''' -----------------------------------------------------------------------------
''' <summary>
''' SelectedIndexChanged event handler.
''' </summary>
''' <param name="sender">The object that raised the event.</param>
''' <param name="e">The EventArgs object related to the event.</param>
''' <remarks>
''' This event fires when a new item is selected for a non-bitwise enum.
''' This is required in order to hide the drop-down list when a new item is selected.
''' </remarks>
''' <history>
''' [rprimrose] 15/Mar/2005 Created
''' </history>
''' -----------------------------------------------------------------------------
Private Sub ItemSelected(ByVal sender As Object, ByVal e As EventArgs)
' Check if the service object exists
If Not m_objService Is Nothing Then
' Close the drop down control
m_objService.CloseDropDown()
End If ' End checking if the service object exists
End Sub
#End Region
#Region " Functions "
''' -----------------------------------------------------------------------------
''' <summary>
''' Gets the editor style used by the EditValue method.
''' </summary>
''' <param name="context">An ITypeDescriptorContext that can be used to gain additional context information.</param>
''' <returns>A UITypeEditorEditStyle enumeration value that indicates the style of editor used by the current UITypeEditor.</returns>
''' <remarks>
''' None.
''' </remarks>
''' <history>
''' [rprimrose] 14/Feb/2005 Created
''' </history>
''' -----------------------------------------------------------------------------
Public Overloads Overrides Function GetEditStyle(ByVal context As System.ComponentModel.ITypeDescriptorContext) As System.Drawing.Design.UITypeEditorEditStyle
' This is a drop-down editor
Return UITypeEditorEditStyle.DropDown
End Function
''' -----------------------------------------------------------------------------
''' <summary>
''' Edits the value of the specified object using the editor style indicated by GetEditStyle.
''' </summary>
''' <param name="context">An ITypeDescriptorContext that can be used to gain additional context information. </param>
''' <param name="provider">An IServiceProvider that this editor can use to obtain services.</param>
''' <param name="value">The object to edit.</param>
''' <returns>The new value of the object.</returns>
''' <remarks>
''' None.
''' </remarks>
''' <history>
''' [rprimrose] 14/Feb/2005 Created
''' </history>
''' -----------------------------------------------------------------------------
Public Overloads Overrides Function EditValue( _
ByVal context As ITypeDescriptorContext, _
ByVal provider As IServiceProvider, _
ByVal value As Object) As Object
' Check if the required objects exist
If Not context Is Nothing _
AndAlso Not context.Instance Is Nothing _
AndAlso Not provider Is Nothing Then
' Get the editor used to display the list box
m_objService = CType(provider.GetService(GetType(IWindowsFormsEditorService)), IWindowsFormsEditorService)
' Check if the service exists
If Not m_objService Is Nothing Then
Dim objType As System.Type = context.PropertyDescriptor.PropertyType
Dim bFlagsDefined As Boolean
Dim aAttributes() As Object = objType.GetCustomAttributes(False)
Dim nIndex As Int32
Dim nValue As Int32 = CType(value, Int32)
Dim nItemValue As Int32
Dim aValues As Array = System.Enum.GetValues(objType)
Dim nNewValue As Int32
' Loop through each of the attributes
For nIndex = 0 To aAttributes.Length - 1
' Check if this attribute is Flags
If CType(aAttributes(nIndex), System.Attribute).GetType.Equals(GetType(System.FlagsAttribute)) Then
' We have found the flags attribute
bFlagsDefined = True
' Break
Exit For
End If ' End checking if this attribute is Flags
Next ' Loop through each of the attributes
' Check if Flags is defined
If bFlagsDefined = True Then
Dim objList As New System.Windows.Forms.CheckedListBox
Dim bChecked As Boolean
' Set up the ComboBox
objList.BorderStyle = System.Windows.Forms.BorderStyle.None
objList.CheckOnClick = True
' Loop through all the values
For nIndex = 0 To aValues.Length - 1
' Get the value of this item
nItemValue = CType(aValues.GetValue(nIndex), Int32)
' Enums that are bitwise can be defined with a 0 value
' For example: None = 0
' These are often used for default values
' We don't want to display these as a bitwise comparison is always true
' Check if the item has a valid bitwise value
If nItemValue > 0 Then
' Determine if this item is selected
bChecked = ((nValue And nItemValue) = nItemValue)
' Add the image to the list
objList.Items.Add(System.Enum.Parse(objType, CType(aValues.GetValue(nIndex), String)), bChecked)
End If ' End checking if the item has a valid bitwise value
Next ' Loop through all the values
' Check if the listbox height is too large
If objList.Height > (objList.Items.Count * objList.ItemHeight) Then
' Adjust the height of the list
objList.Height = objList.Items.Count * objList.ItemHeight
End If ' End checking if the listbox height is too large
' Display the drop down list
m_objService.DropDownControl(objList)
' Loop through each item in the listbox
For nIndex = 0 To objList.CheckedItems.Count - 1
' Get the value of the selected item
nItemValue = CType(System.Enum.Parse(objType, CType(objList.CheckedItems.Item(nIndex), String)), Int32)
' Add this value to the final value
nNewValue = nNewValue Or nItemValue
Next ' Loop through each item in the listbox
' Store the values selected
value = System.Enum.ToObject(objType, nNewValue)
Else ' Flags is not defined
Dim objList As New System.Windows.Forms.ListBox
' Set up the ComboBox
objList.BorderStyle = System.Windows.Forms.BorderStyle.None
' Loop through all the values
For nIndex = 0 To aValues.Length - 1
' Add the image to the list
objList.Items.Add(System.Enum.Parse(objType, CType(aValues.GetValue(nIndex), String)))
Next ' Loop through all the values
' Preselect the current item
objlist.SelectedIndex = objlist.Items.IndexOf(value)
' Hook up the IndexChanged event to hide the drop down list
AddHandler objList.SelectedIndexChanged, AddressOf ItemSelected
' Check if the listbox height is too large
If objList.Height > (objList.Items.Count * objList.ItemHeight) Then
' Adjust the height of the list
objList.Height = objList.Items.Count * objList.ItemHeight
End If ' End checking if the listbox height is too large
' Display the drop down list
m_objService.DropDownControl(objList)
' Check if we have a value to convert
If Not objlist.SelectedItem Is Nothing Then
' Store the value selected
value = System.Enum.Parse(objType, CType(objlist.SelectedItem, String))
End If ' End checking if we have a value to convert
End If ' End checking if Flags is defined
End If ' End checking if the service exists
End If ' End checking if the required objects exist
' Return the value
Return value
End Function
#End Region
End Class