Reputation: 1124
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:
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!)
Upvotes: 5
Views: 11120
Reputation:
A similar problem occurs without having identical items if you enter free text, which does not match exactly but the first characters. If the user does not open the dropdown no re-sync happen and the selected index is -1 as expected. (Not selecting one of the items is what the user intends to do) Now the user closes the dialog and open it again. You, as the programmer, restore the combobox with the text the user entered and the text is auto-completed to the item partial matching without firing the event. If the user closes the dialog the text has changed without notice. This problem does not occur if the text does not match any item.
Upvotes: 0
Reputation:
There are cases where having duplicate items in the list is not only valid, but desirable. Consider the OpenFileDialog combo box that you see in Visual Studio when you press the Open File button. This shows a combo box with items like 'My Computer', 'Desktop', 'My Documents', etc. For folder names, only the short name is in the list. The full path is not displayed. And so it is very possible that a folder has the same (short) name as one of its descendants.
So imagine the following folder structure:
C:\
C:\A
C:\A\B
C:\A\B\A
A perfectly valid structure. In my implementation I set the DataSource property to a BindingList of objects. The ValueMember of the object is the full file name, and the DisplayMember is the short file name. The combo box should display:
C:\
A
B
A
Perfectly good UI design. The indentation suggests the nesting of the folders.
But when I set the combo box's SelectedValue to "C:\A\B\A" the wrong item gets selected. The item that should be selected is the last (4th item) in the list, but instead the 2nd item (index 1) is selected. And setting SelectedIndex=3 does not behave as intended. Again, the second item is selected, not the last.
What appears to be happening here is that when setting SelectedValue or SelectedIndex, the value is being converted using the DisplayMember property, and the control is searching from beginning to end for a match. It should be searching using the ValueMember property. Sample code is below. Appreciated if anyone can confirm this is a bug, or something that I have done wrong.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace ComboBoxTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
if (DesignMode)
return;
BindingList<CBItem> items = new BindingList<CBItem>();
items.Add(new CBItem("A", @"C:\A"));
items.Add(new CBItem("B", @"C:\A\B"));
items.Add(new CBItem("A", @"C:\A\B\A"));
comboBox.DisplayMember = "DisplayValue";
comboBox.ValueMember = "RealValue";
comboBox.DataSource = items;
comboBox.SelectedValue = @"C:\A\B\A";
}
}
class CBItem
{
public CBItem(string displayValue, string realValue)
{
_displayValue = displayValue;
_realValue = realValue;
}
private readonly string _displayValue, _realValue;
public string DisplayValue { get { return _displayValue; } }
public string RealValue { get { return _realValue; } }
}
}
Upvotes: 1
Eric's answer was very thorough, but I was surprised to see that it didn't end with "...but really, you should ask yourself why you are populating a combo box with duplicate items." The .Net framework bug no doubt has been allowed to exist because when you use the control as intended, to allow the user to pick an item from a list, you don't run into this bug.
How is the user going to differentiate between the identical entries? Why would they choose one over another? Is there a different meaning to the different items? If so, then having duplicate entries is ambiguous, which is always bad usability design. If not, then you shouldn't have duplicate items.
The only scenario I can think of where this might make sense is when you have a large list consisting of several groups of related items, where one or more items logically fits into more than one group so you want to display it in both sections.
I'm guessing your design didn't account for the fact that there may be multiple identical entries and that this omission will have other usability repercussions that are more significant than this problem. Of course, I understand that you may be doing something I haven't thought of where it totally makes sense to do what you're doing, in which case you can feel free to ignore my comments.
Upvotes: 2
Reputation: 9117
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.
Upvotes: 17