当你修改一个ListBox中的项目(或实际上是修改其所关联的ObjectCollection中的项目)时,底层代码实际上会删除并重新创建该项。然后它将选择这个新添加的项目。因此,选定的索引已经被更改,并会引发对应的事件。
我没有特别令人信服的解释,解释为什么控件会以这种方式运行。这可能是为了编程方便,或者仅仅是在最初的WinForms版本中出现了bug,而后续版本必须为了向后兼容性而保持这种行为。此外,即使未修改项目,后续版本也必须保持相同的行为。这就是您观察到的违反直觉的行为。
不幸的是,它没有被记录,除非你了解其中的原因,你才知道SelectedIndex属性实际上在背后被更改,而无需你的知识。
Quantic留下了一个评论,指向参考源代码中相关部分的链接:
internal void SetItemInternal(int index, object value) {
if (value == null) {
throw new ArgumentNullException("value");
}
if (index < 0 || index >= InnerArray.GetCount(0)) {
throw new ArgumentOutOfRangeException("index", SR.GetString(SR.InvalidArgument, "index", (index).ToString(CultureInfo.CurrentCulture)));
}
owner.UpdateMaxItemWidth(InnerArray.GetItem(index, 0), true);
InnerArray.SetItem(index, value);
if (owner.IsHandleCreated) {
bool selected = (owner.SelectedIndex == index);
if (String.Compare(this.owner.GetItemText(value), this.owner.NativeGetItemText(index), true, CultureInfo.CurrentCulture) != 0) {
owner.NativeRemoveAt(index);
owner.SelectedItems.SetSelected(index, false);
owner.NativeInsert(index, value);
owner.UpdateMaxItemWidth(value, false);
if (selected) {
owner.SelectedIndex = index;
}
}
else {
if (selected) {
owner.OnSelectedIndexChanged(EventArgs.Empty);
}
}
}
owner.UpdateHorizontalExtent();
}
在这里,你可以看到,在进行了初始运行时错误检查后,它更新了ListBox的最大项目宽度,设置了内部数组中指定的项目,然后检查本地ListBox控件是否已创建。几乎所有的WinForms控件都是本地Win32控件的包装器,ListBox也不例外。在你的示例中,本地控件肯定已经被创建,因为它在表单上可见,所以if(owner.IsHandleCreated)
测试评估为true。然后它比较项目的文本是否相同:
我们刚刚分析的这个SetItemInternal
方法是从ListBox.ObjectCollection对象的默认属性setter中调用的:
public virtual object this[int index] {
get {
if (index < 0 || index >= InnerArray.GetCount(0)) {
throw new ArgumentOutOfRangeException("index", SR.GetString(SR.InvalidArgument, "index", (index).ToString(CultureInfo.CurrentCulture)));
}
return InnerArray.GetItem(index, 0);
}
set {
owner.CheckNoDataSource();
SetItemInternal(index, value);
}
}
这是您在exampleButton_Click
事件处理程序中调用的方法。
无法阻止此行为发生。您需要编写自己的代码来解决SelectedIndexChanged事件处理程序方法中的问题。您可以考虑从内置ListBox类派生自定义控件类,覆盖OnSelectedIndexChanged方法,并将您的解决方案放在此处。这个派生类将为您提供一个方便的地方来存储状态跟踪信息(作为成员变量),并允许您在整个项目中使用修改后的ListBox控件作为一种方便的替代品,而不必在所有地方修改SelectedIndexChanged事件处理程序。
但实际上,这不应该是一个大问题,也不需要您去解决。您对SelectedIndexChanged事件的处理应该很简单——只需更新表单上的某些状态,如相关控件。如果没有外部可见的更改发生,它触发的更改本身基本上是无操作。
internal void SetItemInternal(int index, object value)
函数,并且如果该项与已有项相同,则运行以下代码(注意注释):// NEW - FOR COMPATIBILITY REASONS // Minimum compatibility fix for VSWhidbey 377287 if (selected) { owner.OnSelectedIndexChanged(EventArgs.Empty); //will fire selectedvaluechanged
。 - QuanticSelectedIndex
,你应该检查是否SelectedIndex >= 0
,然后你是否使用调试器来检查SelectedIndex
的值? - MethodManSelectedIndex
赋值。是的,我确实检查了SelectedIndex
的值。例如,如果我选择索引为2的项目,则SelectedIndex
将变为2。然后我按下按钮,SelectedIndex
仍然是2。 - ZswlastSelectedIndex
的值,每次SelectedIndexChanged
被触发时,将上一次存储的值与当前值进行比较,以确定它是否实际上已更改。 说实话,虽然这是一个“bug”,但我更喜欢保留这种行为。例如,当项目更改时,我希望收到通知,无论所选索引是否更改。 - Thariq Nugrohotomo