【问题标题】:Wpf ListBoxes' ItemsSource strange behaviourWpf ListBoxes 的 ItemsSource 奇怪行为
【发布时间】:2011-10-10 13:43:32
【问题描述】:

编辑地址F Ruffell's answer

我有以下 xaml

<StackPanel>
    <ListBox x:Name="_list1"/>
    <ListBox x:Name="_list2"/>
</StackPanel>

还有这个代码隐藏:

var ints = new[] { 1, 2, 3 };
_list1.ItemsSource = ints;
_list2.ItemsSource = ints;

_list1.Items.Filter = i => ((int)i) < 2;

由于某些原因,仅对第一个 ListBox 设置了过滤器后 两个列表都被过滤了。我希望列表具有完全不同的CollectionViews 和确实_list1.Items != _list2.Items。同时,将过滤器设置为其中一个也会以某种方式将该过滤器设置为另一个。
问题是为什么如何CollectionViews同步?

【问题讨论】:

标签: .net wpf .net-4.0


【解决方案1】:

当您设置ItemsSource 时,WPF 实际上会从指定的IEnumerable 创建一个CollectionView。这样做是为了有一个选定项目、过滤、分组等的概念(分配给ItemsSourceIEnumerable 都不支持这些概念)。当多次使用同一个底层集合时,WPF 将同步这两个 CollectionView。 如果您不想要这种行为,只需在每个 ListBox 上将 IsSynchronizedWithCurrentItem 设置为 False

欲了解更多信息,请参阅:

WPF Combobox binding

编辑

经过进一步调查,似乎设置IsSynchronizedWithCurrentItem 确实仅适用于所选项目,并且两个ICollectionViews 中的所有其他属性仍然同步(尽管每个ListBox 都有自己的ICollectionView - 更改Filter 或将SortDescription 添加到一个,会将其添加到另一个,您每天都会学到新东西:))。

要改变这种行为,你需要自己为每个ListBox创建ICollectionView,然后直接修改Filter属性,例如:

        var ints = new[] { 1, 2, 3 };
        var viewOne = new ListCollectionView(ints);
        var viewTwo = new ListCollectionView(ints);
        _list1.ItemsSource = viewOne;
        _list2.ItemsSource = viewTwo;

        viewOne.Filter = i => ((int)i) < 2;

干杯!

【讨论】:

  • 您也可以通过将ItemsSource 设置为适当ICollectionView 的新实例来实现相同的效果(例如new ListCollectionView(ints)
  • 我的第一个想法是 WPF 重用了CollectionView。这里有两个问题:1)我不明白,为什么? 2) _list1.Items != _list2.Items。而且,将IsSynchronizedWithCurrentItem 设置为false 也无济于事。
  • @alpha-mouse:ItemsSource 控件不直接与集合交互。它使用CollectionView。当您将ItemsSource 设置为集合时,它会自动调用CollectionViewSource.GetDefaultView(...) 以获取该集合的CollectionView,并使用它来代替。为同一个集合调用GetDefaultView(...) 会为您提供CollectionView 的相同实例,因此两个ListBoxes 都使用相同的CollectionView,这会导致这种同步。就像这个答案大纲一样,您必须明确创建一个新的CollectionView 以避免这种行为。
【解决方案2】:

问题是 CollectionView 为何以及如何同步?

它们是同步的,因为即使 ListBoxes 有不同的 Items,它们共享相同的 CollectionView,这是源集合的默认视图。

ItemsControlItems 属性是 ItemCollection 类型,而 ItemCollectionCollectionView 属性是 internal,因此我们无法直接访问它来验证这是真的。但是,我们可以在调试器中输入这三个值来验证这一点,它们都是正确的

_list1.Items.CollectionView == _list2.Items.CollectionView // true
_list1.Items.CollectionView == CollectionViewSource.GetDefaultView(ints) // true
_list2.Items.CollectionView == CollectionViewSource.GetDefaultView(ints) // true

或者,我们可以使用反射在代码中进行比较

PropertyInfo collectionViewProperty =
    typeof(ItemCollection).GetProperty("CollectionView", BindingFlags.NonPublic | BindingFlags.Instance);
ListCollectionView list1CollectionView = collectionViewProperty.GetValue(_list1.Items, null) as ListCollectionView;
ListCollectionView list2CollectionView = collectionViewProperty.GetValue(_list2.Items, null) as ListCollectionView;
ListCollectionView defaultCollectionView = CollectionViewSource.GetDefaultView(ints) as ListCollectionView;

Debug.WriteLine(list1CollectionView == list2CollectionView);
Debug.WriteLine(list1CollectionView == defaultCollectionView);
Debug.WriteLine(list2CollectionView == defaultCollectionView);

F Ruffell 已经发布了解决此问题的方法,为每个ListBox 创建一个新的ListCollectionView 作为ItemsSource

_list1.ItemsSource = new ListCollectionView(ints);
_list2.ItemsSource = new ListCollectionView(ints);

还要注意,在此之后,上面的 3 个比较结果为假

【讨论】:

  • 感谢您的调查。微软在CollectionViewSource.GetDefaultView 上的文章证实了你的观点。虽然我仍然无法想象为什么 WPF 会重用 CollectionView 而不是为每个列表创建一个新的。
  • collectionview 被重用,因此每个 itemsource 绑定都将有一个默认视图,而不会将大量新的、大部分冗余(并且经常未充分使用)的 collectionview 扔到内存中。出于这个原因,这种行为是可以明确地覆盖的。它必须是其中之一,保持对象数量精简是最有效的默认设置。
【解决方案3】:

由于引用类型和为两个列表框提供相同的 itemSource,如果您操作另一个列表框也将被操作。如果您想实现这一点,只需使用 new 并重新分配第二个列表框,然后再将其提供给 ItemsSource。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-13
    • 1970-01-01
    • 1970-01-01
    • 2016-02-01
    • 2011-08-22
    • 2020-10-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多