2

У меня проблема с ComboBox, которая привязана к ObservableCollection, и мне было интересно, может ли кто-нибудь указать на то, чего я не вижу.ComboBox SelectedItem не изменяется после очистки наблюдаемой коллекции

У меня есть ComboBox, который связан с простым ObservableCollection<string>. Также я связываю SelectedIndex в привязке OneWay к некоторому свойству.

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

Я прилагаю немного репро проблемы:

public partial class Window1 : Window, INotifyPropertyChanged 
{ 
    private int j; 
    public event PropertyChangedEventHandler PropertyChanged; 

    public Window1() 
    { 
     InitializeComponent(); 
     DataContext = this; 
     Tables = new ObservableCollection<string>(); 
    } 

    public ObservableCollection<string> Tables { get; set; } 

    private int _TheIndex; 
    public int TheIndex 
    { 
     get { return _TheIndex; } 
     set 
     { 
      _TheIndex = value; 
      if (PropertyChanged != null) 
      { 
       PropertyChanged.Invoke(this, new PropertyChangedEventArgs("TheIndex")); 
      } 
     } 
    } 

    private void aaaa(object sender, RoutedEventArgs e) 
    { 
     j = (j + 1)%10; 
     Tables.Clear(); 
     for(int i = 0; i < 10 ; i++) 
     { 
      Tables.Add(i.ToString()); 
     } 
     TheIndex = j; 
    } 
} 

XAML является:

<Window x:Class="WpfApplication1.Window1" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="Window1" Height="300" Width="300"> 
    <Grid> 
     <StackPanel> 
      <ComboBox x:Name="TablesCombobox" 
         ItemsSource="{Binding Tables}" 
         SelectedIndex="{Binding TheIndex, Mode=OneWay}"/> 
      <Button Content="asdasd" Click="aaaa"/> 
     </StackPanel> 
    </Grid> 
</Window> 
+0

Любая причина, по которой вы не просто устанавливаете TablesCombobox.SelectedIndex в aaaa() вместо TheIndex? Не знаю, почему привязка не работает. –

+0

Я просто копировал/вставлял ваш код для воспроизведения в VS2010 Express Beta 2 - и он работает без заминки ... –

ответ

4

Проблема полностью вызвана Tables.Clear() линии в методе aaaa(). Поскольку Tables является наблюдаемой коллекцией, уничтожение всего содержимого коллекции заставляет WPF обновлять дисплей новым пустым списком. Затем он пытается выбрать текущий активный элемент, используя SelectedIndex, которого нет (потому что список теперь пуст). В результате связывания двигатель остается со значением, которое не может быть применена, и решает отключить и отсоединить связывающую логику:

System.Windows.Data Warning: Got PropertyChanged event from Window1 for TheIndex 
System.Windows.Data Warning: GetValue at level 0 from Window1 using DependencyProperty(TheIndex): '1' 
System.Windows.Data Warning: TransferValue - got raw value '1' 
System.Windows.Data Warning: TransferValue - using final value '1' 
System.Windows.Data Warning: Deactivate 
System.Windows.Data Warning: Replace item at level 0 with {NullDataItem} 
System.Windows.Data Warning: Detach 

К тому времени, он получает в «TheIndex = J;» line, привязка больше не активна и не видит изменения в TheIndex, что означает, что желаемый индекс больше не выбран.

Есть несколько решений для решения этой проблемы:

  1. Не сдует всю коллекцию каждый раз. Без очистки коллекции логика привязки данных всегда имеет индекс для выбора, то есть он никогда не отделяется.
  2. Используйте привязку TwoWay. Это работает, потому что теперь ComboBox участвует в привязке; вы очищаете Tables, привязка пытается установить, но не может найти индекс, поэтому ComboBox сбрасывается в специальную позицию «без индекса» -1, которая затем записывает обратно в TheIndex (двухсторонняя часть), которая действительна так что логика привязки не отсоединяется.
  3. Перед очисткой коллекции не выберите индекс (-1). Если ни один индекс (-1) не выбран, если Tables очищен, то ComboBox не пытается применить SelectedItem, что означает, что он не «видит» сбор, опустошенный и повторно заполненный, и, следовательно, не отделяется.

    private void aaaa(object sender, RoutedEventArgs e) 
    { 
        TheIndex = -1; 
        j = (j + 1)%10; 
        Tables.Clear(); 
        for (int i = 0; i < 10; i++) 
        { 
         Tables.Add(i.ToString()); 
        } 
        TheIndex = j; 
    } 
    

производительность, архитектурная и ясность, я настоятельно рекомендую вариант 1, хотя я понимаю, что ваш фактический сценарий может быть более сложным и требую что-то вдоль линий 3.


Sidenote:

Расположение причину обязательных вопросов, как это достаточно легко при использовании связывания следов, как один написал выше. Включите их для одного связывания с объявлением имен system.diagnostics и добавления PresentationTraceSources.TraceLevel=High к связывающим, который вызывают проблемы:

<Window xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase" /> 
... 
<TextBlock Text="{Binding Path=x, diag:PresentationTraceSources.TraceLevel=High}" /> 

Больше способов отладки WPF привязки here.

0

Я знаю, что это старый вопрос, но я только что испытал эту проблему самостоятельно, поэтому написал метод помощника, основанный на варианте 1 ответа @Nicholas Armstrong, и подумал, что поделюсь им, надеюсь, что кто-то найдет его полезным:

public void refreshDropdownOptions(ObservableCollection<object> OldOptions, ObservableCollection<object> NewOptions) 
{ 
    MainWindow application = Application.Current.MainWindow as MainWindow; 

    int highestCount = 0; 

    if(OldOptions.Count() > NewOptions.Count()) 
    { 
     highestCount = OldOptions.Count(); 
    } 
    else 
    { 
     highestCount = NewOptions.Count(); 
    } 

    for (int i = 0; i < highestCount; i++) 
    { 
     if(i < OldOptions.Count() && i < NewOptions.Count()) 
     {// If we have not exceeded the count of either list, copy the new value over the old 
      application.Dispatcher.Invoke((Action)(() => OldOptions[i] = NewOptions[i]));     
     } 
     else if (i < OldOptions.Count() && i >= NewOptions.Count()) 
     {// If we have no more new options remove the old option 
      application.Dispatcher.Invoke((Action)(() => OldOptions.RemoveAt(i))); 
      highestCount = OldOptions.Count(); 
      i--; 
     } 
     else if (i >= OldOptions.Count() && i < NewOptions.Count()) 
     {// if we have no more old options to replace, add the new option to the end of the collection 
      application.Dispatcher.Invoke((Action)(() => OldOptions.Add(NewOptions[i]))); 
     } 
    } 
}