Imports System
Imports System.Collections
Imports System.ComponentModel
Imports System.Drawing
Imports System.Data
Imports System.Windows.Forms
Imports System.Drawing.Design
Imports System.Windows.Forms.Design


Friend Class FlagCheckedListBox
    Inherits CheckedListBox

#Region "Component Designer generated code"
    Private components As System.ComponentModel.Container = Nothing

    Public Sub New()
        ' This call is required by the Windows.Forms Form Designer. 

        ' TODO: Add any initialization after the InitForm call 

        InitializeComponent()
    End Sub

    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If components IsNot Nothing Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    Private Sub InitializeComponent()
        ' 
        ' FlaggedCheckedListBox 
        ' 
        Me.CheckOnClick = True

    End Sub
#End Region

    ' Adds an integer value and its associated description 
    Public Function Add(ByVal v As Integer, ByVal c As String) As FlagCheckedListBoxItem
        Dim item As New FlagCheckedListBoxItem(v, c)
        Items.Add(item)
        Return item
    End Function

    Public Function Add(ByVal item As FlagCheckedListBoxItem) As FlagCheckedListBoxItem
        Items.Add(item)
        Return item
    End Function

    Protected Overloads Overrides Sub OnItemCheck(ByVal e As ItemCheckEventArgs)
        MyBase.OnItemCheck(e)

        If isUpdatingCheckStates Then
            Return
        End If

        ' Get the checked/unchecked item 
        Dim item As FlagCheckedListBoxItem = TryCast(Items(e.Index), FlagCheckedListBoxItem)
        ' Update other items 
        UpdateCheckedItems(item, e.NewValue)
    End Sub

    ' Checks/Unchecks items depending on the give bitvalue 
    Protected Sub UpdateCheckedItems(ByVal value As Integer)

        isUpdatingCheckStates = True
        For i As Integer = 0 To Items.Count - 1

            ' Iterate over all items 
            Dim item As FlagCheckedListBoxItem = TryCast(Items(i), FlagCheckedListBoxItem)

            If item.value = 0 Then
                SetItemChecked(i, value = 0)
            Else

                ' If the bit for the current item is on in the bitvalue, check it 
                If (item.value And value) = item.value AndAlso item.value <> 0 Then
                    SetItemChecked(i, True)
                Else
                    SetItemChecked(i, False)
                    ' Otherwise uncheck it 
                End If
            End If
        Next

        isUpdatingCheckStates = False

    End Sub

    ' Updates items in the checklistbox 
    ' composite = The item that was checked/unchecked 
    ' cs = The check state of that item 
    Protected Sub UpdateCheckedItems(ByVal composite As FlagCheckedListBoxItem, ByVal cs As CheckState)

        ' If the value of the item is 0, call directly. 
        If composite.value = 0 Then
            UpdateCheckedItems(0)
        End If


        ' Get the total value of all checked items 
        Dim sum As Integer = 0
        For i As Integer = 0 To Items.Count - 1
            Dim item As FlagCheckedListBoxItem = TryCast(Items(i), FlagCheckedListBoxItem)

            ' If item is checked, add its value to the sum. 
            If GetItemChecked(i) Then
                sum = sum Or item.value
            End If
        Next

        ' If the item has been unchecked, remove its bits from the sum 
        If cs = CheckState.Unchecked Then
            sum = sum And (Not composite.value)
        Else
            sum = sum Or composite.value
            ' If the item has been checked, combine its bits with the sum 
        End If

        ' Update all items in the checklistbox based on the final bit value 
        UpdateCheckedItems(sum)

    End Sub

    Private isUpdatingCheckStates As Boolean = False

    ' Gets the current bit value corresponding to all checked items 
    Public Function GetCurrentValue() As Integer
        Dim sum As Integer = 0
        For i As Integer = 0 To Items.Count - 1

            Dim item As FlagCheckedListBoxItem = TryCast(Items(i), FlagCheckedListBoxItem)

            If GetItemChecked(i) Then
                sum = sum Or item.value
            End If
        Next

        Return sum
    End Function

    Private enumType As Type
    Private m_enumValue As [Enum]

    ' Adds items to the checklistbox based on the members of the enum 
    Private Sub FillEnumMembers()
        For Each name As String In [Enum].GetNames(enumType)
            Dim val As Object = [Enum].Parse(enumType, name)
            Dim intVal As Integer = CInt(Convert.ChangeType(val, GetType(Integer)))

            Add(intVal, name)
        Next
    End Sub

    ' Checks/unchecks items based on the current value of the enum variable 
    Private Sub ApplyEnumValue()
        Dim intVal As Integer = CInt(Convert.ChangeType(m_enumValue, GetType(Integer)))
        UpdateCheckedItems(intVal)

    End Sub

    <DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)> _
    Public Property EnumValue() As [Enum]
        Get
            Dim e As Object = [Enum].ToObject(enumType, GetCurrentValue())
            Return DirectCast(e, [Enum])
        End Get
        Set(ByVal value As [Enum])

            Items.Clear()
            m_enumValue = value
            ' Store the current enum value 
            enumType = value.[GetType]()
            ' Store enum type 
            FillEnumMembers()
            ' Add items for enum members 
            ' Check/uncheck items depending on enum value 
            ApplyEnumValue()
        End Set
    End Property


End Class

' Represents an item in the checklistbox 
Friend Class FlagCheckedListBoxItem
    Public Sub New(ByVal v As Integer, ByVal c As String)
        value = v
        caption = c
    End Sub

    Public Overloads Overrides Function ToString() As String
        Return caption
    End Function

    ' Returns true if the value corresponds to a single bit being set 
    Public ReadOnly Property IsFlag() As Boolean
        Get
            Return ((value And (value - 1)) = 0)
        End Get
    End Property

    ' Returns true if this value is a member of the composite bit value 
    Public Function IsMemberFlag(ByVal composite As FlagCheckedListBoxItem) As Boolean
        Return (IsFlag AndAlso ((value And composite.value) = value))
    End Function

    Public value As Integer
    Public caption As String
End Class

' UITypeEditor for flag enums 
''' <summary>
''' <para>A UI Editor for flag enums.</para>
''' <para>This code is downloaded from <a href="http://www.codeproject.com/cs/miscctrl/flagenumeditor.asp">ghimangi's</a> Code Project.</para>
''' </summary>
''' <remarks>Thanks ghimangi.</remarks>
Public Class FlagEnumUIEditor
    Inherits UITypeEditor
    ' The checklistbox 
    Private flagEnumCB As FlagCheckedListBox

    ''' <summary>
    ''' Initializes a new instance of the <see cref="FlagEnumUIEditor" /> class.
    ''' </summary>
    Public Sub New()
        flagEnumCB = New FlagCheckedListBox()
        flagEnumCB.BorderStyle = BorderStyle.None
    End Sub

    ''' <summary>
    ''' Edits the specified object's value using the editor style indicated by the <see cref="M:System.Drawing.Design.UITypeEditor.GetEditStyle"></see> method.
    ''' </summary>
    ''' <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"></see> that can be used to gain additional context information.</param>
    ''' <param name="provider">An <see cref="T:System.IServiceProvider"></see> that this editor can use to obtain services.</param>
    ''' <param name="value">The object to edit.</param>
    ''' <returns>
    ''' The new value of the object. If the value of the object has not changed, this should return the same object it was passed.
    ''' </returns>
    Public Overloads Overrides Function EditValue(ByVal context As ITypeDescriptorContext, ByVal provider As IServiceProvider, ByVal value As Object) As Object
        If context IsNot Nothing AndAlso context.Instance IsNot Nothing AndAlso provider IsNot Nothing Then

            Dim edSvc As IWindowsFormsEditorService = DirectCast(provider.GetService(GetType(IWindowsFormsEditorService)), IWindowsFormsEditorService)

            If edSvc IsNot Nothing Then

                Dim e As [Enum] = DirectCast(Convert.ChangeType(value, context.PropertyDescriptor.PropertyType), [Enum])
                flagEnumCB.EnumValue = e
                edSvc.DropDownControl(flagEnumCB)

                Return flagEnumCB.EnumValue
            End If
        End If
        Return Nothing
    End Function

    ''' <summary>
    ''' Gets the editor style used by the <see cref="M:System.Drawing.Design.UITypeEditor.EditValue(System.IServiceProvider,System.Object)"></see> method.
    ''' </summary>
    ''' <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"></see> that can be used to gain additional context information.</param>
    ''' <returns>
    ''' A <see cref="T:System.Drawing.Design.UITypeEditorEditStyle"></see> value that indicates the style of editor used by the <see cref="M:System.Drawing.Design.UITypeEditor.EditValue(System.IServiceProvider,System.Object)"></see> method. If the <see cref="T:System.Drawing.Design.UITypeEditor"></see> does not support this method, then <see cref="M:System.Drawing.Design.UITypeEditor.GetEditStyle"></see> will return <see cref="F:System.Drawing.Design.UITypeEditorEditStyle.None"></see>.
    ''' </returns>
    Public Overloads Overrides Function GetEditStyle(ByVal context As ITypeDescriptorContext) As UITypeEditorEditStyle
        Return UITypeEditorEditStyle.DropDown
    End Function

End Class
