+9  A: 

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.

Eric Rosenberger
Thank you so much for that answer! That was *incredibly* informative! (It's also nice to know I'm not crazy and that there is a legitimate explanation for this behavior!)
Keithius
I agree that was agreat reply so I up voted the answer
MikeScott8
A: 

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.

Consider for a moment that most users are NOT programmers, and have no idea that 2 items in a list being the same could be a problem, and you'll understand how this problem can arise. Also: users may enter the same thing in twice by accident, but not notice it at first, leading to this situation.
Keithius
+1  A: 

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; } }
   }
}
A: 

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.