3

我遇到了与 aComboBox绑定的问题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;
    }
}

xml是:

<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>
4

2 回答 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;' 行,绑定不再活动并且看不到对 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 的内容。


边注:

使用上面发布的绑定跟踪时,定位此类绑定问题背后的原因相当容易。PresentationTraceSources.TraceLevel=High通过声明 System.Diagnostics 命名空间并将它们添加到导致问题的绑定中,为单个绑定打开它们:

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

更多调试 WPF 绑定的方法在这里

于 2010-01-15T22:05:55.653 回答
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])));
        }
    }
}
于 2017-08-18T15:57:00.610 回答