In short
When I type a character in a ComboBox, press Alt+Down followed by Enter or Tab, the SelectedIndexChanged event doesn\'t fire, even though the S
I had the ESC problem on a DropDownList-style combobox. I slightly modified what worked for me to accommodate your needs:
public class MyComboBox : System.Windows.Forms.ComboBox
{
private bool _sendSic;
protected override void OnPreviewKeyDown(System.Windows.Forms.PreviewKeyDownEventArgs e)
{
base.OnPreviewKeyDown(e);
if (DroppedDown)
{
switch(e.KeyCode)
{
case System.Windows.Forms.Keys.Escape:
_sendSic = true;
break;
case System.Windows.Forms.Keys.Tab:
case System.Windows.Forms.Keys.Enter:
if(DropDownStyle == System.Windows.Forms.ComboBoxStyle.DropDown)
_sendSic = true;
break;
}
}
}
protected override void OnDropDownClosed(System.EventArgs e)
{
base.OnDropDownClosed(e);
if(_sendSic)
{
_sendSic = false;
OnSelectedIndexChanged(System.EventArgs.Empty);
}
}
}
What this does is listening to keystrokes that come in while the dropdown is open. If it's ESC, TAB or ENTER for a DropDown
-style ComboBox or ESC for a DropDownList
-style ComboBox, a SelectedIndexChanged
-Event is triggered when the DropDown is closed.
I have never ever used ComboBoxStyle.Simple
and don't really know how it does or should work, but since it to the best of my knowledge never displays a DropDown, this should be safe even for that style.
If you don't want to derive from ComboBox
to build your own control, you can also apply similar logic to a ComboBox on a form by subscribing to it's PreviewKeyDown
and DropDownClosed
events.
I ended up deriving my own class from ComboBox:
public class EditableComboBox : ComboBox
{
protected int backupIndex;
protected string backupText;
protected override void OnDropDown(EventArgs e)
{
backupIndex = this.SelectedIndex;
if (backupIndex == -1) backupText = this.Text;
else backupText = null;
base.OnDropDown(e);
}
protected override void OnSelectionChangeCommitted(EventArgs e)
{
backupIndex = -2;
base.OnSelectionChangeCommitted(e);
}
protected override void OnSelectionIndexChanged(EventArgs e)
{
backupIndex = -2;
base.OnSelectionIndexChanged(e);
}
protected override void OnDropDownClosed(EventArgs e)
{
if (backupIndex > -2 && this.SelectedIndex != backupIndex)
{
if (backupIndex > -1)
{
this.SelectedIndex = backupIndex;
}
else
{
string oldText = backupText;
this.SelectedIndex = -1;
this.Text = oldText;
this.SelectAll();
}
}
base.OnDropDownClosed(e);
}
}
I've tried several google searches in order to find a definitive answer on this but didn't find one before. Just now I found a thread that actually refers to a Microsoft knowledge base article about the problem. Article KB948869 describes the problem.
The knowledge base article suggest to create your own combobox and override the ProcessDialogKey method.
using System.Windows.Forms;
public class MyComboBox : ComboBox
{
protected override bool ProcessDialogKey(Keys keyData)
{
if (keyData == Keys.Tab)
this.DroppedDown = false;
return base.ProcessDialogKey(keyData);
}
}
I've tried it, but unfortunately, it doesn't seem to have any effect. Which is a bit strange. I would expect a workaround described in a knowledge base article to be accurate.
I found another workaround though, which is to use the DropDownClosed event in stead.
private void comboBox1_DropDownClosed(object sender, EventArgs e)
{
label1.Text = "DroDownClosed Selected index: " + comboBox1.SelectedIndex;
}
This does seem to work, but only when using DropDownStyle.DropDown. When you set the DropDownStyle to DropDownList, typing a character does not fire the DropDownClosed (as there is no actual drop down in that case). Only if you actually open up the drop down list and select a value the DropDownClosed event is fired.
So, both options are not really a good answer.
Update
I've even tried overriding property SelectedIndex in MyComboBox, having it call OnSelectedIndexChanged(EventArgs.Empty)
. After typing a character and pressing Alt+Down, the setter is executed, but it's setting the value to -1, which it already is. After pressing Tab, the setter isn't executed again, although somehow the SelectedIndex value does change. It looks like the ComboBox is directly changing the backing field for SelectedIndex, bypassing the setting. I believe something like this probably also happens in the real ComboBox.
The appropriate DropDown property value here is DropDownList. It doesn't have this problem.
Coming up with a workaround for your specific problem with the DropDown style set to DropDown is quite difficult. It allows the user type arbitrary text and even a perfect match with one of the dropdown items doesn't change the SelectedIndex. You'd have to implement the Validating event and look for a match yourself. The DropDownClosed event would be good for your specific scenario. But really, always use DropDownList if you want perfect matches.
Correct me if I'm wrong. Here is code I've used.
comboBox1.Items.AddRange(new object[] {
"One",
"Two",
"Three"
});
comboBox1.SelectedIndexChanged+=(sa,ea)=>
{
label1.Text = "Selected index: " + comboBox1.SelectedIndex;
};
comboBox1.TextChanged+= (sa, ea) =>
{
comboBox1.SelectedIndex = comboBox1.FindStringExact(comboBox1.Text);
//OR
//comboBox1.SelectedIndex = comboBox1.Items.IndexOf(comboBox1.Text);
comboBox1.SelectionStart = comboBox1.Text.Length;
};