2017-01-03 7 views
9

Мы дали форму приложения Windows, созданный из шаблона Microsoft Visual Studio, (код дизайнера на Pastebin 1234) с ListBox по умолчанию exampleListBox и кнопки exampleButton.Почему событие SelectedIndexChanged запускается в ListBox при изменении выбранного элемента?

Мы заселить ListBox с номерами от 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 срабатывает событие.

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

Кроме того, существует ли способ предотвратить возникновение этого поведения?

+4

Возможно, я ошибаюсь, но я думаю, что при назначении в поле списка запускается '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

+1

@CloseVoter Почему это не по теме? Желаемое поведение включено, и это не опечатка, домашняя помощь или рекомендации. – Zsw

+0

Почему вы не ожидаете, что ничего не произойдет .. вы назначаете 'SelectedIndex', вы должны проверить, был ли' SelectedIndex> = 0', если вы использовали отладчик для проверки того, что это значение SelectedIndex? – MethodMan

ответ

11

Когда вы изменяете элемент в 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 должна быть тривиальной, просто обновив некоторое состояние в вашей форме, например, зависимые элементы управления. Если изменения, видимые извне, не происходили, изменения, которые он вызывает, в основном не будут самими операциями.