为什么我的CompositeCollection无法正常使用等于项?

时间:2016-04-01 07:29:25

标签: c# wpf icollectionview compositecollection

我发现,当我使用CompositeCollection来组合覆盖Equals()方法的项集合时,会将CompositeCollection表示为ItemsSource ListBox然后,当项目被添加到其中一个合并集合时,ListBox中的项目无法正确显示。

例如:

Four ListBoxes in a window

在上面的窗口中,我刚刚点击了" Duplicate" 按钮,该按钮绑定到一个方法,该方法在{{1}中创建所选项目的副本并将其添加到最后。单击按钮时,选择了每个ListBox中的最后一项。每列中的ListBox配置不同,如下所示(从左到右):

  1. ListBox其中ObservableCollection<T>是已覆盖T的类,会直接分配给Equals()属性。
  2. ItemsSource其中ObservableObject<T>T 被覆盖的类,被Equals()引用CollectionContainer }}。 CompositeCollection已分配给CompositeCollection属性。
  3. 来自{1 ItemsSource的{​​{1}}被ObservableCollection<T>内的ListBox引用。 CollectionContainer已分配给CompositeCollection属性。
  4. #3 CompositeCollection中的ItemsSource已投放到CompositeCollection,然后调用该对象的ListBox方法,并将结果直接分配给ICollectionViewFactory属性。
  5. 换句话说:第一个CreateView()显示直接绑定到ItemsSource的可等项的集合,第二个显示包含在其中的 - 可指定项的集合ListBox又绑定到ItemsSource,第三个通过使用第一个CompositeCollection中的集合实例将两个技术组合在一起,但将其放在ItemsSource中,如同第二个ListBox,第四个只是从第三个CompositeCollection获取,并直接创建一个ListBox集合以供显示。


    对于这个特定的测试,实现项目的相等性,使得两个项目将相等,只要它们都具有相同的基本文本,即在末尾没有副本号(括号中的数字,如果有的话)。我这样做是为了让我可以比较相等的对象,但仍然能够将它们视觉识别为不同的实例。

    请注意,在前两个CompositeCollection列中,最后一项显示为已按预期复制,但在第三个ICollectionView列中,而不是在结尾处显示的新项目在列表中,它的原始源显示两次,列表末尾没有实例。

    具有讽刺意味的是,即使第三个ListBox中的ListBox未正确显示,也直接从中创建了CompositeCollection!也就是说,至少在它首次显示时。如果在创建视图后修改了原始集合,则此ListBox的失败方式与原始集合相同(这是有道理的,因为ICollectionView实际上是ICollectionView是如何首先显示ICollectionView

    XAML:

    ListBox

    MainWindow.cs:

    CompositeCollection

    Item.cs:

    <Window x:Class="TestCompositeCollectionObservable.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:l="clr-namespace:TestCompositeCollectionObservable"
            Title="MainWindow" Height="350" Width="525">
      <Window.Resources>
        <DataTemplate DataType="{x:Type l:Item}">
          <TextBlock Text="{Binding Text}"/>
        </DataTemplate>
      </Window.Resources>
      <StackPanel>
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
          </Grid.RowDefinitions>      
          <TextBlock Text="Equatable Items in ObservableCollection" Grid.Column="0" TextWrapping="Wrap"/>
          <TextBlock Text="Plain Items in CompositeCollection" Grid.Column="1" TextWrapping="Wrap"/>
          <TextBlock Text="Equatable Items in CompositeCollection" Grid.Column="2" TextWrapping="Wrap"/>
          <TextBlock Text="Equatable Items in Explicit View" Grid.Column="3" TextWrapping="Wrap"/>
          <ListBox x:Name="listBox1" Grid.Row="1"/>
          <ListBox x:Name="listBox2" Grid.Column="1" Grid.Row="1"
                   SelectedIndex="{Binding SelectedIndex, ElementName=listBox1}"/>
          <ListBox x:Name="listBox3" Grid.Column="2" Grid.Row="1"
                   SelectedIndex="{Binding SelectedIndex, ElementName=listBox1}"/>
          <ListBox x:Name="listBox4" Grid.Column="3" Grid.Row="1"
                   SelectedIndex="{Binding SelectedIndex, ElementName=listBox1}"/>
        </Grid>
        <Button Content="Duplicate" Click="Button_Click" HorizontalAlignment="Left"/>
      </StackPanel>
    </Window>
    

    EquatableItem.cs:

    public partial class MainWindow : Window
    {
        private static readonly string[] _textValues =
        {
            "item 1",
            "item 2",
            "item 3"
        };
    
        private readonly ObservableCollection<Item> _items;
        private readonly ObservableCollection<EquatableItem> _equatableItems;
    
        public MainWindow()
        {
            InitializeComponent();
    
            _items = new ObservableCollection<Item>(_textValues.Select(text => new Item(text)));
            _equatableItems = new ObservableCollection<EquatableItem>(_textValues.Select(text => new EquatableItem(text)));
    
            _items.Add(new Item(_items[_items.Count - 1]));
            _equatableItems.Add(new EquatableItem(_equatableItems[_equatableItems.Count - 1]));
    
            listBox1.ItemsSource = _equatableItems;
            listBox2.ItemsSource = new CompositeCollection
            {
                new CollectionContainer { Collection = _items },
            };
            listBox3.ItemsSource = new CompositeCollection
            {
                new CollectionContainer { Collection = _equatableItems },
            };
        }
    
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            int index = listBox1.SelectedIndex;
    
            _items.Add(new Item(_items[index]));
            _equatableItems.Add(new EquatableItem(_equatableItems[index]));
    
            listBox4.ItemsSource = ((System.ComponentModel.ICollectionViewFactory)listBox3.ItemsSource).CreateView();
        }
    }
    

    关于可复制项目对象:从头开始创建时,该对象没有&#34;原始&#34;从现有项目创建时,对象的原始内容与现有项目的原始内容相同(如果是原始项目,则为该项目),其文本基于原始文本和副本数量。

    对于class Item { public string Text { get; private set; } public Item Original { get { return _original ?? this; } } protected readonly Item _original; private int _copyCount; public Item(string text) { Text = text; } public Item(Item item) { Item original = item.Original; Text = string.Format("{0} ({1})", original.Text, ++original._copyCount); _original = original; } } 派生类,原始项目对象的所有副本将相互比较。

    以这种方式实现副本和平等可以完成两件事:

    1. 很容易跟踪哪些对象是副本以及存在多少副本。
    2. 它揭示了当使用class EquatableItem : Item { public EquatableItem(string text) : base(text) { } public EquatableItem(EquatableItem item) : base(item) { } public override bool Equals(object obj) { EquatableItem other = obj as EquatableItem; if (other == null) { return false; } return object.ReferenceEquals(Original, other.Original); } public override int GetHashCode() { return Original.Text.GetHashCode(); } } 时,即使将该对象的新副本添加到CompositeCollection中的CollectionContainer源,而新副本,也会错误地再次显示原始对象。根本没有显示!

    3. 我总是担心指责操作系统/框架/库有错误,而不是我自己的代码中有错误。这也不例外。虽然代码对我来说似乎很简单,但我很清楚我可能忽略了一些关于我应该使用EquatableItem的方式的重要事情,甚至是我所使用的方式覆盖了CompositeCollection方法。

      那么,我在这里做错了吗?如果是的话,我做了什么?

      或者,有没有好的解决方法?我至少可以想到三种解决方法:

      1. 将equatable对象包裹在一个普通的非等距对象中,隐藏该集合中的equatable-ness并CompositeCollection
      2. 每次收集更改时重新创建视图,方法是自己致电Equals(),或者每次只创建一个全新的ListBox
      3. 强制默认视图刷新自身,例如致电CreateView()
      4. 前两个看起来都不是理想的,但是推动是推动,要么就好了。在这两个中,第一个似乎是性能更好,但当然需要相当多的额外代码,这是丑陋且容易出错的。

        第三种解决方案似乎是三者中最好的,但在性能方面可能与第二种不同。有没有更好的方法来欺骗视图在更改后正确呈现集合?

        建议的副本只是一个心怀不满的Stack Overflow用户的报复性回复。

1 个答案:

答案 0 :(得分:1)

这似乎是WPF中的一个错误。使用相同的可执行文件,在安装了.NET 4.5.2的系统上运行它会重现该错误,而在安装了.NET 4.6.1的系统上运行它则不会。

似乎没有在Connect上报告,但是在更新到Microsoft已修复的包含集合后,还有一个相关错误也涉及错误的视图状态:

WPF: CompositeCollection causes ItemsControl.SelectedIndex to incorrectly increment if the source collection adds an item

似乎在修复该bug的过程中,该团队还有意或偶然地修复了这个错误。