.NET WinForms ComboBox,相同的项目和SelectedIndexChanged事件

时间:2008-12-09 22:04:29

标签: .net vb.net winforms

似乎当你有一个WinForms .NET应用程序和一个ComboBox(设置为“DropDown”样式),并且ComboBox中有多个相同的项目时,会发生奇怪的事情。具体来说,所选项目的索引可以更改而不触发SelectedIndexChanged事件。

当然,这会导致大规模的混乱和奇怪的,模糊的错误,这就是我最近一直把头发拉出来的。

这是一个简单的例子,你可以用来看我在说什么:

  • 创建一个新的.NET WinForms项目(我使用VB.NET,但随意翻译 - 它很简单)。
  • 将一个ComboBox,一个按钮和一个TextBox(设置MultiLine = True)放到表单上。
  • 使用以下代码加载包含3个相同项目的ComboBox,并在SelectedIndexChanged事件触发时打印一些状态消息,并查看当前所选索引的内容(通过按钮):
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

运行项目并从ComboBox中选择一个项目(例如,中间项目)。然后,单击ComboBox的下拉箭头,但不要选择任何内容。单击按钮(默认情况下为Button1)并查看其内容。

除非我失去理智,否则你应该看到:

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

换句话说,SELECTED INDEX已经改变但没有激活SelectedIndexChanged事件!

仅当ComboBox中的项目相同时才会发生这种情况。如果它们不同,则不会发生这种情况。 (如果ComboBox的“DropDown”样式设置为“DropDownList”,也不会发生。)

我怀疑这可能是.NET框架本身的一个错误,而不是我可以解决的问题,但是关于其他人对于在这里做什么(或者我可能做错了什么)有任何想法的机会,请进来!我无法解释这种行为或解决它(我希望SelectedIndex保持不变,除非你知道,你实际上通过选择别的东西来改变它!)

4 个答案:

答案 0 :(得分:17)

.NET Framework实际上并没有跟踪组合框下拉列表中选定的索引;这是由Windows API在内部处理的。因此,当所选索引通过发送到组合框的窗口句柄的通知消息发生更改时,.NET依赖于Windows API来通知它,以便它可以依次触发SelectedIndexChanged事件。

不幸的是,事实证明,.NET监视的特定通知消息(确切地说CBN_SELCHANGE)并未涵盖所选索引可能发生变化的所有可能情况。具体来说,CBN_SELCHANGE仅在用户点击或使用箭头键选择下拉列表中的项目时由Windows API发送。但是,在DropDown样式组合框中,打开组合框的操作会导致Windows查看组合框的编辑部分中的文本,搜索项目列表以查找匹配项,以及是否找到匹配项选择匹配项(或第一个匹配项,如果有多个匹配项)。这可以更改所选索引,但不发送CBN_SELCHANGE通知消息,因此.NET忽略了它已更改但不会触发SelectedIndexChanged事件的事实。

Windows在DropDown样式组合框中完成所有这些操作,因为用户无需在下拉列表中选择内容;他们可以打字他们想要什么。因此,每次打开组合框时,它都会假定用户可能已更改了文本,并尝试重新同步列表中的内容(如果可以)。

在您的情况下,当您第二次打开组合框时,它将重新同步并选择编辑部分中文本的第一个匹配项,即“John Doe”#0,并更改所选索引在没有.NET知道的情况下为0。

所以它基本上是.NET Framework中的一个错误。遗憾的是,没有完美的解决方法 - 您无法让Windows不进行重新同步,并且在重新同步发生后没有事件会立即触发,您可以在其中获取新选择的索引。 (DropDown事件实际上在重新同步发生之前就会触发,因此它不会看到新的索引。)关于你可以做的最好的事情是处理DropDownClosed事件,假设索引可能在那时发生了变化,并采取相应的行动

答案 1 :(得分:2)

Eric的答案非常彻底,但我很惊讶地发现它并没有结束“......但实际上,你应该问问自己为什么要填充带有重复项目的组合框。”毫无疑问,.Net框架错误已被允许存在,因为当您按预期使用控件时,为了允许用户从列表中选择项目,您不会遇到此错误。

用户如何区分相同的条目?他们为什么选择一个而不是另一个?不同的项目有不同的含义吗?如果是这样,那么重复的条目是不明确的,这总是糟糕的可用性设计。如果没有,那么你不应该有重复的项目。

我能想到的唯一情况是,当你有一个由几组相关项组成的大型列表时,其中一个或多个项逻辑上适合多个组,因此你想要在两个组中显示它部分。

我猜你的设计没有考虑到可能存在多个相同条目的事实,并且这个遗漏将具有比这个问题更重要的其他可用性影响。当然,我知道你可能正在做一些我没想过要做你正在做的事情完全有意义的事情,在这种情况下你可以随意忽略我的评论。

答案 2 :(得分:1)

有些情况下,列表中包含重复项目不仅有效,而且是可取的。考虑按下“打开文件”按钮时在Visual Studio中看到的OpenFileDialog组合框。这会显示一个包含“我的电脑”,“桌面”,“我的文档”等项目的组合框。对于文件夹名称,列表中只有短名称。不显示完整路径。因此,文件夹很可能与其后代之一具有相同(简短)的名称。

想象一下以下文件夹结构:

C:\
C:\A
C:\A\B
C:\A\B\A

完全有效的结构。在我的实现中,我将DataSource属性设置为对象的BindingList。对象的ValueMember是完整文件名,DisplayMember是短文件名。组合框应显示:

C:\
    A
        B
            A

完美的UI设计。缩进建议嵌套文件夹。

但是当我将组合框的SelectedValue设置为“C:\ A \ B \ A”时,选择了错误的项目。应该选择的项目是列表中的最后一个(第4项),而是选择第2项(索引1)。并且设置SelectedIndex = 3的行为不符合预期。同样,选择了第二项,而不是最后一项。

这里似乎发生的事情是,在设置SelectedValue或SelectedIndex时,使用DisplayMember属性转换值,控件将从头到尾搜索匹配项。它应该使用ValueMember属性进行搜索。示例代码如下。如果有人能证实这是一个错误,或者我做错了什么,那就表示赞赏。

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

答案 3 :(得分:0)

如果您输入的自由文本与第一个字符不完全匹配,则在没有相同项目的情况下会出现类似问题。 如果用户未打开下拉列表,则不会发生重新同步,并且所选索引按预期为-1。 (不选择其中一个项目是用户打算做的) 现在,用户关闭对话框并再次打开它。 作为程序员,您使用用户输入的文本恢复组合框,并且文本自动完成到项目部分匹配而不触发事件。 如果用户关闭对话框,则文本已更改,恕不另行通知。 如果文本与任何项目都不匹配,则不会出现此问题。