1

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

Community
  • 1
  • 1
Paul Williams
  • 1,554
  • 7
  • 40
  • 75

2 Answers2

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.

Ňɏssa Pøngjǣrdenlarp
  • 38,411
  • 12
  • 59
  • 178
  • Could this be expanded to say 9 cbo by continuing multiple if/else statements? – Paul Williams Jan 16 '16 at 21:53
  • Okay... The second option works! There is just one small problem. How would I keep the first option. Say if I wanted to include a "None" option always available. I can't see how to preserve an option choice.. – Paul Williams Jan 17 '16 at 05:36
  • @Plutonix Sir I owe you a drink... Been at it the entire night and even managed to get to (kind of) work with the NVP answer you gave me a few months ago. (Funny thing about reusing code...) http://stackoverflow.com/questions/31777645/assigning-a-value-to-combobox-items/31811533. Just one question. How come it is that when I use this, the first option list doesn't seem to want to update? – Paul Williams Jan 17 '16 at 14:20
  • 1
    There it is...I was looking for that answer the other day. I couldnt remember enough about the Q or A to find it – Ňɏssa Pøngjǣrdenlarp Jan 17 '16 at 14:27
  • @Plutonix: Just one question how do you define <> For a custom data type like the NVP? – Paul Williams Jan 17 '16 at 15:01
  • 1
    Since they are objects you can only test if they reference the same object. They are versatile, so they can be used many ways, but *usually* I would compare the Value. `If nvp1.Value <> nvp2.Value Then...`. The reason is that something like {"Plutonium", 94} the value is usually the important part with the name being the human readable/understandable version. It could be otherwise though (Value==some index of the Name}. That version is pretty base bones - you can beef it up with a custom comparer (app to app) to use "= / <>" to compare whatever part(s). – Ňɏssa Pøngjǣrdenlarp Jan 17 '16 at 15:08
  • @Plutonix: Sorry about this one, a bit of google searching actually got me to that same exact answer. (http://geekswithblogs.net/viktorbergman/archive/2007/04/16/Overloading-operators-in-VB.Net.aspx) – Paul Williams Jan 17 '16 at 15:12
  • 1
    Yes, and I meant `Equals` not "= / <>" but the grace period to edit the comment had expired. see also `Implements IEquatable(Of NVP)` – Ňɏssa Pøngjǣrdenlarp Jan 17 '16 at 15:15
  • @Plutonix: Wait, sorry, different problem. That `Imports` instead. Also is there a reason why that the first combo box is "skipped"? – Paul Williams Jan 17 '16 at 15:25
  • what is not required, `IEquatable`? No it isnt required for overloading `Equals`, but there are cases where it is desirable: The interface is a contract that your Type can do this or that, other code can ask/see if it does. Sorry, no I cant do chat – Ňɏssa Pøngjǣrdenlarp Jan 17 '16 at 15:32
  • @Plutonix: No no. My edit must have came too late. And my finger slipped on the "chat button". I was referring to the "Imports Reflection" and other such on top of my code. Side question: Is there a reason though the code is skipping or not triggering regarding the first combobox? – Paul Williams Jan 17 '16 at 15:34
  • How is it skipped? Its contents would never change, but whatever is selected in it should be removed from the list for all the others, UNLESS it is "None". In that case, you might want to disable things if they cant pick 2...N unless there is a valid selection in #1. The cbos of course dont know None from Blue – Ňɏssa Pøngjǣrdenlarp Jan 17 '16 at 15:37
1

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
Steve
  • 213,761
  • 22
  • 232
  • 286