我发现,当我使用CompositeCollection
来组合覆盖Equals()
方法的项集合时,会将CompositeCollection
表示为ItemsSource
ListBox
然后,当项目被添加到其中一个合并集合时,ListBox
中的项目无法正确显示。
例如:
在上面的窗口中,我刚刚点击了" Duplicate" 按钮,该按钮绑定到一个方法,该方法在{{1}中创建所选项目的副本并将其添加到最后。单击按钮时,选择了每个ListBox
中的最后一项。每列中的ListBox
配置不同,如下所示(从左到右):
ListBox
其中ObservableCollection<T>
是已覆盖T
的类,会直接分配给Equals()
属性。ItemsSource
其中ObservableObject<T>
是T
未被覆盖的类,被Equals()
引用CollectionContainer
}}。 CompositeCollection
已分配给CompositeCollection
属性。ItemsSource
的{{1}}被ObservableCollection<T>
内的ListBox
引用。 CollectionContainer
已分配给CompositeCollection
属性。CompositeCollection
中的ItemsSource
已投放到CompositeCollection
,然后调用该对象的ListBox
方法,并将结果直接分配给ICollectionViewFactory
属性。换句话说:第一个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;
}
}
派生类,原始项目对象的所有副本将相互比较。
以这种方式实现副本和平等可以完成两件事:
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源,而新副本,也会错误地再次显示原始对象。根本没有显示!
我总是担心指责操作系统/框架/库有错误,而不是我自己的代码中有错误。这也不例外。虽然代码对我来说似乎很简单,但我很清楚我可能忽略了一些关于我应该使用EquatableItem
的方式的重要事情,甚至是我所使用的方式覆盖了CompositeCollection
方法。
那么,我在这里做错了吗?如果是的话,我做了什么?
或者,有没有好的解决方法?我至少可以想到三种解决方法:
CompositeCollection
。Equals()
,或者每次只创建一个全新的ListBox
。CreateView()
。前两个看起来都不是理想的,但是推动是推动,要么就好了。在这两个中,第一个似乎是性能更好,但当然需要相当多的额外代码,这是丑陋且容易出错的。
第三种解决方案似乎是三者中最好的,但在性能方面可能与第二种不同。有没有更好的方法来欺骗视图在更改后正确呈现集合?
建议的副本只是一个心怀不满的Stack Overflow用户的报复性回复。
答案 0 :(得分:1)
这似乎是WPF中的一个错误。使用相同的可执行文件,在安装了.NET 4.5.2的系统上运行它会重现该错误,而在安装了.NET 4.6.1的系统上运行它则不会。
似乎没有在Connect上报告,但是在更新到Microsoft已修复的包含集合后,还有一个相关错误也涉及错误的视图状态:
似乎在修复该bug的过程中,该团队还有意或偶然地修复了这个错误。