ComboBox SelectedItem не изменяется после очистки наблюдаемой коллекции - PullRequest
3 голосов
/ 15 января 2010

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

У меня есть 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>

Ответы [ 2 ]

4 голосов
/ 16 января 2010

Проблема полностью вызвана строкой 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;" линия, привязка больше не активна и не видит изменения в TheIndex, что означает, что желаемый индекс больше не выбран.

Существует несколько способов решения этой проблемы:

  1. Не выбрасывать всю коллекцию каждый раз. Без очистки коллекции у логики привязки данных всегда есть индекс для выбора, что означает, что он никогда не отсоединяется.
  2. Использовать TwoWay привязку. Это работает, потому что теперь ComboBox участвует в привязке; Вы очищаете Tables, привязка пытается установить, но не может найти индекс, поэтому ComboBox сбрасывается в специальную позицию без индекса -1, которая затем записывает обратно в TheIndex (двусторонняя часть), это допустимое значение, поэтому логика привязки не отсоединяется.
  3. Не выбирать индекс (-1) перед очисткой коллекции. Если при очистке Tables не выбран индекс (-1), 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 здесь .

0 голосов
/ 18 августа 2017

Я знаю, что это старый вопрос, но я только что сам столкнулся с этой проблемой, поэтому написал вспомогательный метод, основанный на варианте 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])));
        }
    }
}
...