5

Edited to address F Ruffell's answer

I have the following xaml

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

and this code-behind:

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

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

For some reason after setting filter only for the first ListBox both lists are filtered. I expect the lists to have completely different CollectionViews and indeed _list1.Items != _list2.Items. Meanwhile setting filter to one of them also somehow sets that very filter to the other.
The question is why and how are CollectionViews synchronized?

Community
  • 1
  • 1
alpha-mouse
  • 4,953
  • 24
  • 36
  • 1
    You will find this article helpful http://drwpf.com/blog/2007/11/05/itemscontrol-c-is-for-collection/ – Grokodile Oct 15 '11 at 11:02

3 Answers3

7

When you set the ItemsSource WPF actually creates a CollectionView from the specified IEnumerable. It does this so that there is a notion of a selected item(s), filtering, grouping etc (none of which are supported by the IEnumerable assigned to the ItemsSource). When the same underlying collection is used more than once, WPF will synchronized the two CollectionViews. If you don't want this behaviour, simply set IsSynchronizedWithCurrentItem to False on each ListBox.

For more information see:

WPF Combobox binding

Edit

Upon further investigation it appears as though setting IsSynchronizedWithCurrentItem really only applies to the selected item, and all other properties across the two ICollectionViews are still synchronized (even though each ListBox has its own ICollectionView - changing the Filter or adding a SortDescription to one, will add it to the other, you learn something new everyday :) ).

To change this behaviour, you need to create the ICollectionView for each ListBox yourself and then directly modify the Filter property, for example:

        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;

Cheers!

Community
  • 1
  • 1
FunnyItWorkedLastTime
  • 3,225
  • 1
  • 22
  • 20
  • You can also achieve the same effect by setting `ItemsSource` to a new instance of an appropriate `ICollectionView` (e.g. `new ListCollectionView(ints)`) – Steve Greatrex Oct 10 '11 at 14:06
  • My first thought was that WPF reuses `CollectionView`. There are two problem here: 1) I cannot understand, why? 2) _list1.Items != _list2.Items. And also, setting `IsSynchronizedWithCurrentItem` to `false` did not help. – alpha-mouse Oct 10 '11 at 14:12
  • @alpha-mouse: An ItemsSource control does not interact with a collection directly. It uses a `CollectionView`. When you set the `ItemsSource` to a collection, it will automatically call `CollectionViewSource.GetDefaultView(...)` to get a `CollectionView` for that collection, and use that instead. Calling `GetDefaultView(...)` for the same collection gives you the same instance of `CollectionView`, and so both `ListBox`es are using the same `CollectionView` which causes this syncronization. Like this answer outlines, you must explicitly create a new `CollectionView` to avoid this behavior. – Allon Guralnek Oct 15 '11 at 10:27
5

The question is why and how are CollectionViews synchronized?

They are synchronized because even though both ListBoxes have different Items, they share the same CollectionView, which is the default view for the source collection.

The Items property of ItemsControl is of type ItemCollection and the CollectionView property of ItemCollection is internal so we can't access it directly to verify that this is true. However, we can just enter these three values in the debugger to verify this, they all come out as true

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

Alternatively, we can use reflection to do the comparison in code

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

The way to work around this has already been posted by F Ruffell, create a new ListCollectionView as ItemsSource for each ListBox.

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

Also note that after this, the 3 comparisons above comes out as false

Fredrik Hedblad
  • 83,499
  • 23
  • 264
  • 266
  • Thanks for your investigation. Microsoft article on [CollectionViewSource.GetDefaultView](http://msdn.microsoft.com/en-us/library/system.windows.data.collectionviewsource.getdefaultview.aspx) confirmed your point. Although I still cannot imagine why WPF reuses CollectionView instead of creating a new one for each list. – alpha-mouse Oct 18 '11 at 14:14
  • The collectionview is reused so that each itemsource binding will have a default view without throwing lots of new, largely redundant (and often underused) collectionviews into memory. This behaviour is over-ridable expressly for this reason. It has to be one or the other, and keeping object counts lean is the most efficient default. – Gusdor Feb 28 '12 at 10:19
0

Due to the reference type and giving both the Listboxes same itemSource if you manipulate one other one will also be manipulated. If you want to achive this just use new and reassign the second listbox before you give it to ItemsSource.

Nivid Dholakia
  • 5,272
  • 4
  • 30
  • 55