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.
看起来当你有一个WinForms .NET应用程序和一个ComboBox(设置为“DropDown”样式),并且ComboBox中有多个相同的项目时,会发生奇怪的事情。具体而言,所选项的索引可以更改而不触发SelectedIndexChanged事件。
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:
这是一个简单的例子,你可以用来看看我在说什么:
- Make a new .NET WinForms project (I use VB.NET, but feel free to translate - it's simple enough).
- Drop a ComboBox, a button, and a TextBox (set MultiLine=True) onto the form.
- Use the following code to load the ComboBox with 3 identical items and to print some status messages when the SelectedIndexChanged event fires, and to see what the currently selected index is (via a button):
创建一个新的.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
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.
运行项目并从ComboBox中选择一个项目(例如,中间项目)。然后,单击ComboBox的下拉箭头,但不要选择任何内容。单击按钮(默认为Button1)并查看其内容。
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!
换句话说,SELECTED INDEX已经改变但没有激活SelectedIndexChanged事件!
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.")
仅当ComboBox中的项目相同时才会发生这种情况。如果它们不同,则不会发生这种情况。 (如果ComboBox的“DropDown”样式设置为“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!)
我怀疑这可能是.NET框架本身的一个错误,而不是我可以解决的问题,但是如果其他人对这里做什么(或者我可能做错了什么)有任何想法的话,请加入!我无法解释这种行为或解决它(我希望SelectedIndex保持不变,除非,你知道,你实际上通过选择其他东西来改变它!)
4 个解决方案
#1
17
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.
.NET Framework实际上并不跟踪组合框的下拉列表中选定的索引;这是由Windows API在内部处理的。因此,.NET依赖于Windows API,当所选索引通过发送到组合框的窗口句柄的通知消息发生更改时通知它,以便它可以依次触发SelectedIndexChanged事件。
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.
不幸的是,事实证明,.NET监视的特定通知消息(准确地说是CBN_SELCHANGE)并未涵盖所选索引可能发生变化的所有可能情况。具体来说,如果用户单击或使用箭头键选择下拉列表中的项目,则CBN_SELCHANGE仅由Windows API发送。但是,在DropDown样式组合框中,打开组合框的操作会导致Windows查看组合框的编辑部分中的文本,搜索项目列表以查找匹配项,以及是否找到匹配项选择匹配项(或第一个匹配项,如果有多个匹配项)。这可以更改所选索引,但不发送CBN_SELCHANGE通知消息,因此.NET忽略了它已更改的事实,并且不会触发SelectedIndexChanged事件。
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.
Windows在DropDown样式组合框中完成所有这些操作,因为用户无需在下拉列表中选择内容;他们可以打字他们想要什么。因此,每次打开组合框时,它都会假定用户可能已更改了文本,并尝试重新同步列表中的内容(如果可以)。
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.
在您的情况下,当您第二次打开组合框时,它将重新同步并选择编辑部分中文本的第一个匹配项,即“John Doe”#0,并将所选索引更改为0 .NET意识到了。
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.
所以它基本上是.NET Framework中的一个错误。遗憾的是,没有完美的解决方法 - 您无法让Windows不进行重新同步,并且在重新同步发生后没有事件会立即触发,您可以在其中获取新选择的索引。 (DropDown事件实际上在重新同步发生之前就会触发,因此它不会看到新的索引。)关于你可以做的最好的事情是处理DropDownClosed事件,假设索引可能在那时发生了变化,并采取相应的行动。
#2
2
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.
Eric的回答非常彻底,但我很惊讶地看到它并没有结束“......但实际上,你应该问问自己为什么要填充带有重复项目的组合框。”毫无疑问,.Net框架错误已被允许存在,因为当您按预期使用控件时,为了允许用户从列表中选择项目,您不会遇到此错误。
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.
我猜你的设计没有考虑到可能存在多个相同条目的事实,并且这个遗漏将具有比这个问题更重要的其他可用性影响。当然,我知道你可能正在做一些我没想过要做你正在做的事情完全有意义的事情,在这种情况下你可以随意忽略我的评论。
#3
1
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.
在某些情况下,列表中包含重复项目不仅有效,而且是可取的。考虑按下“打开文件”按钮时在Visual Studio中看到的OpenFileDialog组合框。这会显示一个包含“我的电脑”,“桌面”,“我的文档”等项目的组合框。对于文件夹名称,列表中只有短名称。不显示完整路径。因此,文件夹很可能与其后代之一具有相同(短)的名称。
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:
一个完全有效的结构。在我的实现中,我将DataSource属性设置为对象的BindingList。对象的ValueMember是完整文件名,DisplayMember是短文件名。组合框应显示:
C:\
A
B
A
Perfectly good UI design. The indentation suggests the nesting of the folders.
完美的UI设计。缩进建议嵌套文件夹。
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.
但是当我将组合框的SelectedValue设置为“C:\ A \ B \ A”时,选择了错误的项目。应该选择的项目是列表中的最后一个(第4项),而是选择第2项(索引1)。并且设置SelectedIndex = 3的行为不符合预期。再次,选择第二项,而不是最后一项。
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.
这里似乎发生的是,当设置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; } }
}
}
#4
0
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.
如果您输入的*文本与第一个字符完全不匹配,则会出现类似的问题而没有相同的项目。如果用户未打开下拉列表,则不会发生重新同步,并且所选索引按预期为-1。 (不选择其中一个项目是用户打算做的事情)现在用户关闭对话框并再次打开它。作为程序员,您使用用户输入的文本恢复组合框,并且文本自动完成到项目部分匹配而不触发事件。如果用户关闭对话框,则文本已更改,恕不另行通知。如果文本与任何项目不匹配,则不会发生此问题。
#1
17
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.
.NET Framework实际上并不跟踪组合框的下拉列表中选定的索引;这是由Windows API在内部处理的。因此,.NET依赖于Windows API,当所选索引通过发送到组合框的窗口句柄的通知消息发生更改时通知它,以便它可以依次触发SelectedIndexChanged事件。
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.
不幸的是,事实证明,.NET监视的特定通知消息(准确地说是CBN_SELCHANGE)并未涵盖所选索引可能发生变化的所有可能情况。具体来说,如果用户单击或使用箭头键选择下拉列表中的项目,则CBN_SELCHANGE仅由Windows API发送。但是,在DropDown样式组合框中,打开组合框的操作会导致Windows查看组合框的编辑部分中的文本,搜索项目列表以查找匹配项,以及是否找到匹配项选择匹配项(或第一个匹配项,如果有多个匹配项)。这可以更改所选索引,但不发送CBN_SELCHANGE通知消息,因此.NET忽略了它已更改的事实,并且不会触发SelectedIndexChanged事件。
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.
Windows在DropDown样式组合框中完成所有这些操作,因为用户无需在下拉列表中选择内容;他们可以打字他们想要什么。因此,每次打开组合框时,它都会假定用户可能已更改了文本,并尝试重新同步列表中的内容(如果可以)。
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.
在您的情况下,当您第二次打开组合框时,它将重新同步并选择编辑部分中文本的第一个匹配项,即“John Doe”#0,并将所选索引更改为0 .NET意识到了。
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.
所以它基本上是.NET Framework中的一个错误。遗憾的是,没有完美的解决方法 - 您无法让Windows不进行重新同步,并且在重新同步发生后没有事件会立即触发,您可以在其中获取新选择的索引。 (DropDown事件实际上在重新同步发生之前就会触发,因此它不会看到新的索引。)关于你可以做的最好的事情是处理DropDownClosed事件,假设索引可能在那时发生了变化,并采取相应的行动。
#2
2
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.
Eric的回答非常彻底,但我很惊讶地看到它并没有结束“......但实际上,你应该问问自己为什么要填充带有重复项目的组合框。”毫无疑问,.Net框架错误已被允许存在,因为当您按预期使用控件时,为了允许用户从列表中选择项目,您不会遇到此错误。
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.
我猜你的设计没有考虑到可能存在多个相同条目的事实,并且这个遗漏将具有比这个问题更重要的其他可用性影响。当然,我知道你可能正在做一些我没想过要做你正在做的事情完全有意义的事情,在这种情况下你可以随意忽略我的评论。
#3
1
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.
在某些情况下,列表中包含重复项目不仅有效,而且是可取的。考虑按下“打开文件”按钮时在Visual Studio中看到的OpenFileDialog组合框。这会显示一个包含“我的电脑”,“桌面”,“我的文档”等项目的组合框。对于文件夹名称,列表中只有短名称。不显示完整路径。因此,文件夹很可能与其后代之一具有相同(短)的名称。
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:
一个完全有效的结构。在我的实现中,我将DataSource属性设置为对象的BindingList。对象的ValueMember是完整文件名,DisplayMember是短文件名。组合框应显示:
C:\
A
B
A
Perfectly good UI design. The indentation suggests the nesting of the folders.
完美的UI设计。缩进建议嵌套文件夹。
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.
但是当我将组合框的SelectedValue设置为“C:\ A \ B \ A”时,选择了错误的项目。应该选择的项目是列表中的最后一个(第4项),而是选择第2项(索引1)。并且设置SelectedIndex = 3的行为不符合预期。再次,选择第二项,而不是最后一项。
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.
这里似乎发生的是,当设置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; } }
}
}
#4
0
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.
如果您输入的*文本与第一个字符完全不匹配,则会出现类似的问题而没有相同的项目。如果用户未打开下拉列表,则不会发生重新同步,并且所选索引按预期为-1。 (不选择其中一个项目是用户打算做的事情)现在用户关闭对话框并再次打开它。作为程序员,您使用用户输入的文本恢复组合框,并且文本自动完成到项目部分匹配而不触发事件。如果用户关闭对话框,则文本已更改,恕不另行通知。如果文本与任何项目不匹配,则不会发生此问题。