Bitwise/Flags Enum UITypeEditor

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