Почему событие SelectedIndexChanged срабатывает в списке при изменении выбранного элемента?

нам предоставляется приложение Windows Form, созданное из шаблона Microsoft Visual Studio (код конструктора на PasteBin 1 2 3 4) со списком по умолчанию exampleListBox и кнопку exampleButton.

мы заполняем список номерами от 1 до 10.

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

когда кнопка нажата, я ожидаю, что ничего не произойдет. Однако это не так. При нажатии на кнопку срабатывает exampleListBox_SelectedIndexChanged событие, хотя SelectedIndex не изменилась.

например, если я нажму элемент в индексе 2 в exampleListBox, потом exampleListBox.SelectedIndex станет 2. Если я нажму exampleButton, потом exampleListBox.SelectedIndex еще 2. Однако, после exampleListBox_SelectedIndexChanged событие.

почему событие срабатывает, даже если выбранный индекс не был изменен?

кроме того, есть ли способ предотвратить такое поведение?

1 ответов


при изменении элемента в ListBox (или, фактически, элемента в связанном ObjectCollection ListBox), базовый код фактически удаляет и воссоздает элемент. Затем он выбирает этот вновь добавленный элемент. Поэтому выбранный индекс и изменено, и вызывается соответствующее событие.

у меня нет особенно убедительного объяснения, почему контроль ведет себя таким образом. Это было сделано либо для удобства программирования, либо просто ошибка в исходная версия WinForms и последующие версии должны были поддерживать поведение по причинам обратной совместимости. Кроме того, последующие версии должны были поддерживать то же самое поведение даже если элемент не был изменен. Это противоинтуитивное поведение, которое вы наблюдаете.

и, к сожалению, это не документирован-если вы не понимаете, почему это происходит, и тогда вы знаете, что свойство selectedIndex на самом деле is переодевается за кулисами, без вашего ведома.

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 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) test оценивает значение true. Затем он сравнивает текст элементов, чтобы увидеть, являются ли они одинаковыми:

  • если они отличаются, он удаляет исходный элемент, удаляет выделение, добавляет новый элемент и выбирает его, если исходный элемент был выбран. Это приводит к 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 и размещения обходного пути здесь. Этот производный класс даст вам удобное место для хранения информации отслеживания состояния (как переменных-членов), и это позволит вам использовать измененный список управление в качестве замены в течение всего проекта, без необходимости изменять обработчики событий SelectedIndexChanged везде.

но, честно говоря, это не должно быть большой проблемой или чем-то, что вам даже нужно работать. Обработка события SelectedIndexChanged должна быть тривиальной-просто обновление некоторого состояния в форме, например зависимых элементов управления. Если внешне видимых изменений не произошло, изменения, которые он запускает, в основном не будут сами по себе.