Когда вы изменяете элемент в ListBox (или, фактически, элемент связанного с ListBox ObjectCollection), базовый код фактически удаляет и воссоздает элемент. Затем он выбирает этот вновь добавленный элемент. Поэтому выбранный индекс имеет, и соответствующее событие поднято.
У меня нет особо убедительного объяснения, почему управление ведет себя таким образом. Это было сделано либо для удобства программирования, либо просто ошибкой в исходной версии WinForms, а в последующих версиях пришлось поддерживать поведение по соображениям обратной совместимости. Кроме того, последующие версии должны были поддерживать одинаковое поведение , даже если элемент не был изменен. Это противоречивое поведение, которое вы наблюдаете.
И, к сожалению, это not documented -Если вы понимаете, почему это происходит, и тогда вы знаете, что свойство SelectedIndex фактически является получение изменилось за кулисами, без вашего ведома.
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)
оценивается как 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 и поместив ваше обходное решение здесь. Этот производный класс даст вам удобное место для хранения информации отслеживания состояния (в качестве переменных-членов), и это позволит вам использовать измененный элемент управления ListBox в качестве замены для замены во всем вашем проекте без необходимости изменения обработчиков событий SelectedIndexChanged везде.
Но, честно говоря, это не должно быть большой проблемой или чем-то, что вам даже нужно обойти. Ваша обработка события SelectedIndexChanged должна быть тривиальной, просто обновив некоторое состояние в вашей форме, например, зависимые элементы управления. Если изменения, видимые извне, не происходили, изменения, которые он вызывает, в основном не будут самими операциями.
Возможно, я ошибаюсь, но я думаю, что при назначении в поле списка запускается 'internal void SetItemInternal (int index, object value)', и если элемент имеет идентичный 'string.Compare', он запускает этот код, обратите внимание на комментарий: ['// NEW - ДЛЯ ПРИГОДНОСТИ СОВМЕСТИМОСТИ // Минимальное исправление совместимости для VSWhidbey 377287 if (selected) {owner.OnSelectedIndexChanged (EventArgs.Empty); // будет убирать selectedvaluechanged'] (https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/ListBox.cs,3341) – Quantic
@CloseVoter Почему это не по теме? Желаемое поведение включено, и это не опечатка, домашняя помощь или рекомендации. – Zsw
Почему вы не ожидаете, что ничего не произойдет .. вы назначаете 'SelectedIndex', вы должны проверить, был ли' SelectedIndex> = 0', если вы использовали отладчик для проверки того, что это значение SelectedIndex? – MethodMan