Paul Williams
Paul Williams

Reputation: 1598

Removing or disable ComboBox item which is selected in another list

I have three combo boxes on a WinForm as the following:

                  ___________________________
ComboBox List 1: |_______________________|___|
                  ___________________________
ComboBox List 2: |_______________________|___|
                  ___________________________
ComboBox List 3: |_______________________|___|

Each of these combo boxes will have, at design-time, a list of "A", "B", "C".

By default, List 1 is the only one active on the form and List 2 and List 3 will become active when its predecessor is given a selection.

What I would like to do, is iF the user choose option C, I would like to have Option C no longer be available for list 2 and 3.

I know this will involve the .SelectedIndexChanged event of the combobox but do not know where to get started with the coding.

I found the following answer on StackOverflow, how my situation doesn't apply since I'm supplying the items at design time and not via an import of a file: vb.net change combobox options depending on selected item in 2 previous comboboxes

Upvotes: 1

Views: 4443

Answers (2)

This is similar to Steve's answer, but uses a DataSource. Also, there is no real need to Enable/Disable each CBO. As they make changes, previous selections are filtered from the source. Any duplicate "previous" changes get undone.

CBO1 always has the full list, while the others omit previous selections. You cant end up with duplicate picks because re-picking #2 to be the same as #3, changes the contents of #3 replacing the selection to whatever is first in the list.

' the master list
Private OptList As New List(Of String)
Private ignore As Boolean

' initialize somewhere:
OptList.AddRange(New String() {"Red", "Green", "Blue", "Violet", "Orange"})
' set up cbos
' ignore changes while setting up:
ignore = True
cb1.DataSource = OptList.ToArray
cb1.SelectedIndex = -1
cb2.DataSource = OptList.ToArray
cb2.SelectedIndex = -1
cb3.DataSource = OptList.ToArray
cb3.SelectedIndex = -1
ignore = False

With them all enabled, they can pick them in any order.

Private Sub cbo_SelectedIndexChanged(sender As Object, e As EventArgs) _
             Handles cb1.SelectedIndexChanged, cb2.SelectedIndexChanged
    If ignore Then Exit Sub

    Dim cbo = TryCast(sender, ComboBox)
    If cbo IsNot Nothing AndAlso cbo Is cb1 Then
        cb2.DataSource = GetFilteredList(New String() {cb1.Items(cb1.SelectedIndex)})
    Else
        cb3.DataSource = GetFilteredList(New String() {cb1.SelectedItem.ToString,
                                                   cb2.SelectedItem.ToString})
    End If
End Sub

Private Function GetFilteredList(items As String()) As String()
    Return OptList.Except(items).ToArray()
End Function

Since cbo3 is limited to those items not picked in #1 or #2 (a slave) you dont have to do anything when that selection changes.


Could this be expanded to say 9 cbo by continuing multiple if/else statements and How would I keep the first option. Say if I wanted to include a "None" option always available.

What that many, I would do it more abstractly. The code may be harder to read/follow, but there is less of it (i was using 4 cbos). This may need some tweaking as it is off the top of my head for the follow up revised form:

' additional array to hold the CBOs involved
Private tgtCBOs As ComboBox()
...
' initialization:
OptList.AddRange(New String() {"(None)", "Red", "Green", "Blue", "Violet", 
                       "Orange", "Mauve", "White"})
tgtCBOs = New ComboBox() {cb1, cb2, cb3, cb4}

' set initial index to 0 with a default item

Private Sub cb2_SelectedIndexChanged(sender As Object, e As EventArgs) Handles _ 
        cb2.SelectedIndexChanged, cb1.SelectedIndexChanged, cb3.SelectedIndexChanged

    If ignore Then Exit Sub

    Dim cbo = TryCast(sender, ComboBox)
    ' identify which one this is
    Dim ndx = Array.IndexOf(tgtCBOs, cbo)
    ' get all the selections from 0 to here
    Dim exclude = GetExclusionList(ndx)
    ' remove excludes from the NEXT cbo
    Dim newList As New List(Of String)
    If ndx + 1 < tgtCBOs.Length Then
        newList = OptList.Except(exclude).ToList()
        If newList(0) <> OptList(0) Then newList.Insert(0, "(None)")
        tgtCBOs(ndx + 1).DataSource = newList.ToArray()
    End If
End Sub

Private Function GetExclusionList(ndx As Int32) As List(Of String)
    Dim exclude As New List(Of String)
    For n As Int32 = 0 To ndx
        If tgtCBOs(n).SelectedIndex <> -1 Then
            exclude.Add(tgtCBOs(n).Items(tgtCBOs(n).SelectedIndex).ToString())
        End If
    Next
    Return exclude
End Function

Note that the last cbo is not connected to that handler because there it has no "slave" . Connect it to its own handler if you need to respond to the event.

Also, the act of resetting the DataSource for the "next" (or clearing Items for that matter) will cause that event to fire for the next/child/slave CBO. So a change to CBO2 will fire the event for 3, 4, 5..n. It also resets the previous selection in the child/slave/next ones.

Upvotes: 2

Steve
Steve

Reputation: 216343

Well, suppose that your comboboxes are named b1, b2 and b3. All these combos are linked to the same SelectedIndexChanged eventhandler and (as you say) you have set the initial elements on the b1,b2 and b3 to a fixed list of string.

Now you should also set the Enabled property of b2 and b3 to false and let the selection on b1 drive your logic in the SelectedIndexChanged event

Sub onIndexChanged(sender As Object, e As EventArgs)
    Dim b As ComboBox = CType(sender, System.Windows.Forms.ComboBox)
    If b.Equals(b1) Then
        If b1.SelectedIndex = -1 Then

            b2.Enabled = False
            b3.Enabled = False
        Else
            b2.Enabled = True
            b3.Enabled = False
            b2.Items.Clear()
            b3.Items.Clear()
            b2.Items.AddRange(b1.Items.Cast(Of String)() _
             .Where(Function(x) x <> b1.SelectedItem.ToString()).ToArray())
        End If
    Else if b.Equals(b2) Then
        If b2.SelectedIndex <> -1 Then

            b3.Items.Clear()
            b3.Items.AddRange(b2.Items.Cast(Of String)(). _
                Where(Function(x) x <> b2.SelectedItem.ToString()).ToArray())
            b3.Enabled = True
            b3.SelectedIndex = 0
        End If 
    End If 
End Sub

Upvotes: 1

Related Questions