WinForms ComboBox在键入少量字符后按Alt+Down时无法触发SelectedIndexChanged事件

15

简述

当我在ComboBox中输入一个字符,然后按下Alt+Down键,再按Enter或Tab键时,SelectedIndexChanged事件不会触发,尽管SelectedIndex的值确实发生了变化!为什么事件不会触发呢?

更新 如果你在输入字符后按Alt+Down,然后再按Esc键,也会出现同样的错误。你本来期望Esc键可以取消选择,但是SelectedIndex却改变了,并且SelectedIndexChanged事件也没有触发。

如果你只是输入Alt+Down,使用箭头键浏览到某个条目,然后再输入Esc键,会发生什么情况?选定的索引会被设置回原始值吗?


详述

我有一个WinForm应用程序,其中包含一个ComboBox。ComboBox的SelectedIndexChanged事件已连接到一个事件处理程序,用于在一个Label控件中显示SelectedItem。ComboBox的Items集合有三个值:"One"、"Two"和"Three"。

  • 当我使用鼠标选择一个项目时,事件会触发。
  • 当我滚动鼠标时,事件会触发。
  • 当我使用Alt+Down展开组合框并通过上下方向键浏览项目时,事件会触发。
  • 但是... 当我输入值的第一个字符,然后按Alt+Down,接着按Enter或Tab键,值确实被选中并显示在ComboBox中,但事件却没有触发。

我还添加了一个按钮来显示SelectedIndex。它显示SelectedIndex已经改变。所以,即使SelectedIndex确实发生了变化,SelectedIndexChanged事件也不会触发!

如果我只是输入一个有效的值,比如 One事件也不会触发。但在这种情况下,单击按钮会显示SelectedIndex确实没有改变。因此,在这种情况下,行为是正常的。


要复制,请创建一个Form,添加一个ComboBox、一个Label和一个Button。将以下代码放置在Form1.cs中:

using System;
using System.Windows.Forms;

namespace ComboBoxSelectedIndexChanged
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            comboBox1.Items.AddRange(new object[] {
                "One",
                "Two",
                "Three"
            });
        }

        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            label1.Text = "Selected index: " + comboBox1.SelectedIndex;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Selected item: " + comboBox1.SelectedItem +
                "\nSelected index: " + comboBox1.SelectedIndex);
        }
    }
}

我曾经遇到过同样的问题,所以我处理了TextChanged事件。根据MSDN页面 - 当SelectedIndex属性的值更改时,将触发SelectedIndexChanged事件。 - KV Prajapati
@adatapost:我在这里描述的方式(输入第一个字符,然后按Alt+Down,然后单击按钮)表明SelectedIndex实际上确实会改变。 - comecme
5个回答

6

我曾尝试通过谷歌搜索来找到关于这个问题的权威答案,但是一直没有找到。直到现在,我发现了一个帖子,其中提到了一个有关该问题的微软知识库文章。文章KB948869描述了该问题。

该知识库文章建议创建自己的组合框并覆盖ProcessDialogKey方法。

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);
    }
}

我尝试过了,但不幸的是,它似乎没有任何效果。这有点奇怪。我期望在知识库文章中描述的解决方法应该是准确的。

不过,我找到了另一个解决方法,那就是使用DropDownClosed事件。

private void comboBox1_DropDownClosed(object sender, EventArgs e)
{
    label1.Text = "DroDownClosed Selected index: " + comboBox1.SelectedIndex;
}

这似乎有效,但仅在使用DropDownStyle.DropDown时才有效。当您将DropDownStyle设置为DropDownList时,输入字符不会触发DropDownClosed(因为在这种情况下没有实际的下拉列表)。只有您真正打开下拉列表并选择值时,才会触发DropDownClosed事件。
因此,两个选项都不是一个很好的答案。
更新: 我甚至尝试重写MyComboBox中的SelectedIndex属性,让它调用OnSelectedIndexChanged(EventArgs.Empty)。在键入字符并按下Alt+Down后,setter被执行,但它将该值设置为-1,而已经是-1了。按Tab键后,setter不再执行,尽管SelectedIndex值似乎确实发生了变化。看起来ComboBox直接更改了SelectedIndex的后备字段,绕过了设置。我相信这样的事情在真正的ComboBox中也会发生。

5
适当的DropDown属性值是DropDownList。它没有这个问题。
针对您使用DropDown样式设置为DropDown的特定问题,想要找到解决方法是相当困难的。它允许用户输入任意文本,即使与下拉列表中的项目完全匹配也不会更改SelectedIndex。您需要实现Validating事件并自行查找匹配项。DropDownClosed事件可能适用于您的特定场景。但是,如果您想要完美匹配,请始终使用DropDownList。

我的情况不是用户输入完整条目,而只是第一个字符,然后按Alt+Down。正如我的按钮所显示的那样,这种方式确实会更改SelectedIndex。这正是让我困惑的地方。SelectedIndex确实会更改,但事件却没有触发。 - comecme
2
是的,很遗憾吧?我的意思是,除非将DropDown样式设置为DropDownList,否则您永远不能依赖SelectedIndex。您试过了吗? - Hans Passant
这永远不会让用户感到困惑。 - Hans Passant
4
我建议你实现自己的ComboBox控件,这样你就可以按照自己想要的方式使其工作。顺便提一下,下拉列表有点棘手,因为它是一个顶级窗口。请让普通用户在早期使用您的版本,程序员和用户的需求往往不同。一个常见的用户愿望是“让它像我使用的任何其他程序一样工作”。他们有点固执。 - Hans Passant
我已将您的回答标记为答案,尽管它并没有解决我的问题。我想你说得对,用户有点固执。 - comecme
显示剩余3条评论

3

我在一个下拉式组合框中遇到了键盘按键ESC无法生效的问题。我稍微修改了适用于自己的解决方法以满足您的需求:

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);
    }
  }
}

这段代码的作用是在下拉菜单打开时监听输入的按键。如果是ESCTABENTER,对于DropDown样式的组合框或ESC对于DropDownList样式的组合框,在关闭下拉菜单时会触发SelectedIndexChanged事件。
我从未使用过ComboBoxStyle.Simple,也不知道它如何工作,但据我所知,它从不显示下拉菜单,因此即使对于该样式,这也是安全的。
如果您不想从ComboBox派生出自己的控件,则还可以通过订阅其PreviewKeyDownDropDownClosed事件,将类似的逻辑应用于窗体上的组合框。

2

如果我说错了,请纠正我。这是我使用过的代码。

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

1
如果我输入一个完整的现有条目,这确实会触发SelectedIndexChanged并在标签上显示索引。但是,如果我只输入一个字符(例如T),然后按Alt+Down,然后按Tab键,组合框中的条目将更改为Two,TextChanged事件将触发,您的代码将SelectedIndex设置为1,但是SelectedIndexChanged不会触发。这是因为在TextChanged事件触发之前,按Tab键已经将SelectedIndex更改为1。从TextChanged中设置SelectedIndex实际上不会更改索引,因此SelectedIndexChanged不会触发。 - comecme
1
还有其他问题。在输入“Two”后,TextChanged触发匹配,导致SelectedIndex的更改,进而触发SelectedIndexChanged,将标签设置为显示索引。但是...当我离开组合框时,实际的SelectedIndex被设置为-1!这不会触发SelectedIndexChanged,但如果您在调试器中检查该值,您将看到它被设置为-1。 - comecme
2
同意!您可以尝试自动完成功能,它会起作用。根据社交MSDN页面 - SelectedIndexChanged事件:“当SelectedIndex属性更改时发生。”这不是一个错误 - 这是一个功能。线程 - http://social.msdn.microsoft.com/Forums/en/winforms/thread/59a23260-bf7b-455e-bbf2-2ad9c4aa07e0 - KV Prajapati
1
将AutoComplete设置为AppendSuggest和AutoCompleteSource设置为ListItems确实使事情变得更好,尽管SelectedIndexChanged事件要等你离开控件后才会触发。然而,我的原始问题仍然存在。当您键入字符、然后按Alt+Down、然后按Tab时,SelectedIndex仍然不会改变。此外,使用Del键清除控件也不会触发事件。 - comecme

2
我从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);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接