【问题标题】:Why does the SelectedIndexChanged event fire in a ListBox when the selected item is modified?为什么在修改所选项目时会在 ListBox 中触发 SelectedIndexChanged 事件?
【发布时间】:2017-05-17 22:48:15
【问题描述】:

我们得到一个从 Microsoft Visual Studio 的模板(PasteBin 上的设计器代码1234)创建的 Windows 窗体应用程序,默认列表框exampleListBox 和按钮exampleButton

我们用 1 到 10 的数字填充 ListBox。

for (int i = 0; i < 10; ++i)
{
    exampleListBox.Items.Add(i);
}

然后我们添加两个事件处理程序。

exampleListBox_SelectedIndexChanged 只会将当前选择的索引写入控制台。

private void exampleListBox_SelectedIndexChanged(object sender, EventArgs e)
{
    Console.WriteLine(exampleListBox.SelectedIndex);
}

exampleButton_Click 会将当前选定索引处的项目设置为自身。如此有效,这应该不会改变任何事情。

private void exampleButton_Click(object sender, EventArgs e)
{
    exampleListBox.Items[exampleListBox.SelectedIndex] = exampleListBox.Items[exampleListBox.SelectedIndex];
}

当按钮被点击时,我希望什么都不会发生。然而,这种情况并非如此。即使SelectedIndex 没有更改,单击该按钮也会触发exampleListBox_SelectedIndexChanged 事件。

例如,如果我单击 exampleListBox 中索引 2 处的项目,则 exampleListBox.SelectedIndex 将变为 2。如果我按 exampleButton,则 exampleListBox.SelectedIndex 仍为 2。然而,exampleListBox_SelectedIndexChanged 事件将触发.

为什么即使选定的索引没有更改,事件也会触发?

此外,有没有办法防止这种行为发生?

【问题讨论】:

  • 我可能是错的,但我认为分配给列表框运行internal void SetItemInternal(int index, object value),如果项目具有相同的string.Compare 它运行此代码,请注意注释:// NEW - FOR COMPATIBILITY REASONS // Minimum compatibility fix for VSWhidbey 377287 if (selected) { owner.OnSelectedIndexChanged(EventArgs.Empty); //will fire selectedvaluechanged
  • @CloseVoter 为什么这是题外话?包括期望的行为,而不是印刷错误、家庭作业帮助或建议。
  • 为什么你期望什么都不会发生..你分配SelectedIndex你应该检查SelectedIndex &gt;= 0然后你使用调试器检查SelectedIndex值是什么?
  • @MethodMan 我没有分配给SelectedIndex 本身。是的,我确实检查了 SelectedIndex 的值是什么。例如,如果我选择索引 2 处的项目,SelectedIndex 将变为 2。然后我按下按钮,SelectedIndex 仍然是 2。
  • 您可以存储lastSelectedIndex 的值,每次SelectedIndexChanged 触发时,将最后存储的值与当前值进行比较,以确定它是否实际更改。老实说,虽然这是一个“错误”,但我更喜欢保留这种行为。例如当项目更改时,我希望得到通知,无论所选索引是否更改。

标签: c# winforms listbox selectedindexchanged .net-4.5.2


【解决方案1】:

当您修改 ListBox 中的项目(或者实际上是 ListBox 关联的 ObjectCollection 中的项目)时,底层代码实际上会删除并重新创建该项目。然后它选择这个新添加的项目。因此,选择的索引已经发生了变化,并引发了相应的事件。

我没有特别令人信服的解释为什么控件会以这种方式运行。它要么是为了编程方便,要么只是 WinForms 原始版本中的一个错误,而后续版本出于向后兼容的原因不得不保持这种行为。此外,后续版本必须保持相同的行为即使项目没有被修改。这是您观察到的违反直觉的行为。

很遗憾,它是not documented——除非您了解它发生的原因,然后您知道 SelectedIndex 属性实际上正在在您不知情的情况下在幕后发生变化。 p>

Quantic 留下评论指向the relevant portion of the code in the Reference Source

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 the native control has been created, and the display text of the new list item object
    // is different to the current text in the native list item, recreate the native list item...
    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 {
            // NEW - FOR COMPATIBILITY REASONS
            // Minimum compatibility fix for VSWhidbey 377287
            if (selected) {
                owner.OnSelectedIndexChanged(EventArgs.Empty); //will fire selectedvaluechanged
            }
        }
    }
    owner.UpdateHorizontalExtent();
}

在这里,您可以看到,在初始运行时错误检查之后,它会更新 ListBox 的最大项目宽度,设置内部数组中的指定项目,然后检查是否已创建本机 ListBox 控件。实际上所有 WinForms 控件都是原生 Win32 控件的包装器,ListBox 也不例外。在您的示例中,肯定已经创建了本机控件,因为它在表单上可见,因此 if (owner.IsHandleCreated) 测试评估为真。然后它比较项目的文本以查看它们是否相同:

  • 如果它们不同,它会删除原始项目,删除选择,添加一个新项目,如果原始项目被选中,则选择它。这会引发 SelectedIndexChanged 事件。

  • 如果它们相同并且当前选择了该项目,则如注释所示,“出于兼容性原因”,将手动引发 SelectedIndexChanged 事件。

我们刚刚分析的这个 SetItemInternal 方法是从 ListBox.ObjectCollection 对象的默认属性的设置器中调用的:

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 事件的处理应该很简单——只需更新表单上的某些状态,例如依赖控件。如果没有发生外部可见的变化,那么它触发的变化本身基本上就是无操作的。

【讨论】:

    【解决方案2】:

    Cody Gray 在最后一个答案中给出了解决方案。我的代码示例:

    private bool lbMeas_InhibitEvent = false; // "some state on your form" 
    private void lbMeas_SelectedIndexChanged(object sender, EventArgs e)
    {
     // when inhibit is found, disarm it and return without action
     if (lbMeas_InhibitEvent) { lbMeas_InhibitEvent = false; return; }
     // ... find the new item
     string cNewItem = "ABCD";
     // set new item content, make sure Inhibit is armed
     lbMeas_InhibitEvent = true;
     // now replace the currently selected item
     lbMeas.Items[lbMeas.SelectedIndex] = cNewItem;
     // ... your code will proceed here
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-16
      • 1970-01-01
      • 1970-01-01
      • 2018-06-02
      • 1970-01-01
      • 2012-01-11
      相关资源
      最近更新 更多