清除可观察集合后,ComboBox SelectedItem不会更改

时间:2010-01-15 15:36:25

标签: wpf combobox observablecollection selecteditem selectedindex

我遇到ComboBoxObservableCollection绑定的问题,我想知道是否有人可以指出我错过的内容。

我有一个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 个答案:

答案 0 :(得分: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)。如果清除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;
    }
    
  4. 出于性能,体系结构和清晰度的原因,我强烈推荐选项1,但我意识到您的实际情况可能更复杂,需要3行。


    <强>旁注:

    在使用上面发布的绑定跟踪时,找到这样的绑定问题背后的原因相当容易。通过声明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

答案 1 :(得分:0)

我知道这是一个古老的问题,但我自己刚刚遇到过这个问题,所以根据@Nicholas Armstrong的答案1的答案编写了一个辅助方法,我想我会分享它,希望有人能找到它有用:

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])));
        }
    }
}