Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET WinForms ComboBox, identical items, and the SelectedIndexChanged event

It seems like when you have a WinForms .NET application, and a ComboBox (set to the "DropDown" style), and that ComboBox has multiple items in it that are identical, weird things happen. Specifically, the index of the selected item can change without firing the SelectedIndexChanged event.

Of course, this causes mass confusion and weird, obscure errors, which is what I've been pulling my hair out over lately.

Here's a simple example you can use to see what I'm talking about:

  • Make a new .NET WinForms project (I use VB.NET, but feel free to translate - it's simple enough).
  • Drop a ComboBox, a button, and a TextBox (set MultiLine=True) onto the form.
  • Use the following code to load the ComboBox with 3 identical items and to print some status messages when the SelectedIndexChanged event fires, and to see what the currently selected index is (via a button):
Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged
        TextBox1.Text = TextBox1.Text & vbNewLine & "ComboBox SelectedIndexChanged event fired." & vbNewLine & _
            "SelectedIndex is: " & ComboBox1.SelectedIndex
    End Sub

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        ComboBox1.Items.Add("John Doe")
        ComboBox1.Items.Add("John Doe")
        ComboBox1.Items.Add("John Doe")

    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        TextBox1.Text = TextBox1.Text & vbNewLine & _
        "Button clicked." & vbNewLine & _
        "SelectedIndex is: " & ComboBox1.SelectedIndex
    End Sub

Run the project and select an item from the ComboBox (say, the middle one). Then, click the ComboBox's drop-down arrow, but DON'T SELECT ANYTHING. Click the Button (Button1 by default) and see what it says.

Unless I've lost my mind, here's what you should see:

ComboBox SelectedIndexChanged event fired.
SelectedIndex is: 1
Button clicked.
SelectedIndex is: 0

In other words, the SELECTED INDEX HAS CHANGED but without the SelectedIndexChanged event firing!

This only happens when the items in the ComboBox are identical. If they're different, this doesn't happen. (It also doesn't happen if the ComboBox's "DropDown" style is set to "DropDownList.")

I suspect this may be a bug in the .NET framework itself and not something I can fix, but on the off chance that anyone else has any ideas on what to do here (or what I might be doing wrong!), please chime in! I'm at a loss to explain this behavior or work around it (I expect the SelectedIndex to stay the same unless, y'know, you actually CHANGE it by selecting something else!)

like image 528
Keithius Avatar asked Dec 09 '08 22:12

Keithius


1 Answers

The .NET Framework does not actually keep track of the selected index of the combo box's drop down list; this is handled internally by the Windows API. As a consequence of this, .NET is reliant on the Windows API to notify it when the selected index changes by means of a notification message sent to the combo box's window handle, so that it can in turn fire the SelectedIndexChanged event.

Unfortunately, it turns out that the particular notification message that .NET watches for (CBN_SELCHANGE to be exact) does NOT cover all possible scenarios in which the selected index could change. Specifically, CBN_SELCHANGE is only sent by the Windows API if the user clicks on, or selects using the arrow keys, an item in the drop down list. However, in a DropDown style combo box, the act of opening the combo box causes Windows to look at the text in the edit portion of the combo box, search through the list of items for a match, and if a match is found, automatically select the matching item (or the first matching item, if there are multiple matching items). This can change the selected index, but does NOT send a CBN_SELCHANGE notification message, so .NET misses the fact that it changed and does not fire the SelectedIndexChanged event.

Windows does all this in a DropDown style combo box because the user does not HAVE to pick something in the drop down list; they can type whatever they want. So each time you open the combo box it assumes that the user might have changed the text and tries to re-sync with what's in the list if it can.

In your case, when you open the combo box for the second time, it is re-syncing and selecting the first match for the text in the edit portion, which is "John Doe" #0, and changing the selected index to 0 without .NET being aware.

So it basically is a bug in the .NET Framework. Unfortunately, there is no perfect workaround -- you can't get Windows to not do the re-sync, and there is no event that fires right after the re-sync occurs in which you can get the new selected index. (The DropDown event actually fires right before the re-sync occurs, so it will not see the new index.) About the best you can do is handle the DropDownClosed event, assume that the index might have changed at that point, and act accordingly.

like image 150
Eric Rosenberger Avatar answered Sep 30 '22 03:09

Eric Rosenberger