2017-02-21 38 views
4

Мне нужно отслеживать выбранный элемент в ListBox для обновления/отключения других элементов управления в соответствии с текущим выбранным значением.ListBox SelectedValueChanged/SelectedIndexChanged не срабатывает при изменении источника данных

Это код, чтобы воспроизвести проблему:

public partial class Form1 : Form 
{ 
    private readonly BindingList<string> List = new BindingList<string>(); 

    public Form1() 
    { 
     InitializeComponent(); 
     listBox1.DataSource = List; 

     listBox1.SelectedValueChanged += (s, e) => System.Diagnostics.Debug.WriteLine("VALUE"); 
     listBox1.SelectedIndexChanged += (s, e) => System.Diagnostics.Debug.WriteLine("INDEX"); 

     addButton.Click += (s, e) => List.Add("Item " + (List.Count + 1)); 
     removeButton.Click += (s, e) => List.RemoveAt(List.Count - 1); 

     logSelectionButton.Click += (s, e) => 
     { 
      System.Diagnostics.Debug.WriteLine("Selected Index: " + listBox1.SelectedIndex); 
      System.Diagnostics.Debug.WriteLine("Selected Value: " + listBox1.SelectedValue); 
     }; 
    } 
} 

Моя форма имеет окно со списком listBox1 и три кнопки: addButton, removeButton и logSelectionButton.

Если нажать addButton (начиная с пустым списком), затем removeButton и, наконец addButton снова, ни SelectedValueChanged, ни SelectedIndexChanged будут стрелять в последнем addButton прессы, несмотря на то, если нажать logSelectionButton до и после последней addButton печати, вы Посмотрим, что значения как SelectedIndex, так и SelectedValue изменились с -1 на 0 и с null на «Пункт 1» соответственно, и что «Элемент 1» выглядит выбранным в списке.

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

Я не могу придумать никакого обходного пути. Возможно, также подписавшись на мое событие ListChanged моего BindingList, чтобы узнать, пуст ли он или нет, но я не знаю, будут ли элементы в списке обновляться до или после моего обработчика событий, что вызовет другие проблемы.

ответ

3

Похоже, вы обнаружили ошибку в ListControl внутренней обработке события PositionChanged, когда данные связаны (если вы включите Исключения в VS, вы увидите исключение, когда первый элемент будет добавлен в пустой список).

С ListControl производных классами, как ListBox, ComboBox и т.д. в данных в режиме синхронизации, связанные их выбор с Position свойством BindingManagerBase, надежным обходного путем (и в основном более общим абстрактным раствором) состоит в обработке CurrentChanged события основных данных менеджер связывания источника:

listBox1.BindingContext[List].CurrentChanged += (s, e) => 
    System.Diagnostics.Debug.WriteLine("CURRENT"); 
+0

Это не похоже на работу. По-видимому, «CurrentChanged» срабатывает до того, как выбор действительно изменится, и мне нужно следить за выбранным элементом. Я нашел другое решение (см. Мой собственный ответ). – Juan

+0

Как пожелаете. Я не понимаю, почему вас волнует, если выбор управления изменяется, когда он доступен с помощью свойства «Current» менеджера привязки и является правильным. Все автоматические привязки главной страницы и навигаторы используют этот подход, а не реагируют на конкретные события/свойства управления. –

+0

Не знал, что вы можете получить выбранное значение из 'Current'. Хороший подход. – Juan

0

Я нашел обходное решение, которое, кажется, работает нормально. Поскольку ListBox обновляет выбранный индекс, установив SelectedIndex свойство и свойство является виртуальным я могу переопределить его, чтобы следить за ним:

public class ListBoxThatWorks : ListBox 
{ 
    private int LatestIndex = -1; 
    private object LatestValue = null; 

    public EqualityComparer<object> ValueComparer { get; set; } 

    public override int SelectedIndex 
    { 
     get { return base.SelectedIndex; } 
     set { SetSelectedIndex(value); } 
    } 

    private void NotifyIndexChanged() 
    { 
     if (base.SelectedIndex != LatestIndex) 
     { 
      LatestIndex = base.SelectedIndex; 
      base.OnSelectedIndexChanged(EventArgs.Empty); 
     } 
    } 

    private void NotifyValueChanged() 
    { 
     if (!(ValueComparer ?? EqualityComparer<object>.Default).Equals(LatestValue, base.SelectedValue)) 
     { 
      LatestValue = base.SelectedValue; 
      base.OnSelectedValueChanged(EventArgs.Empty); 
     } 
    } 

    private void SetSelectedIndex(int value) 
    { 
     base.SelectedIndex = value; 
     NotifyIndexChanged(); 
     NotifyValueChanged(); 
    } 
}