Blog

Books

Books I tech reviewed and recommend
Sorry I can't reply to every email and comment. If you need a fast response, try the WPF Forum or Silverlight Forum.
October 31, 2009

WPF’s CollectionViewSource

CollectionViewSource has existed for a long time in WPF and was recently introduced in Silverlight 3. My next post will cover CollectionViewSource in the context of Silverlight. But before covering that topic, I’ve decided to provide some background about why we introduced this class in WPF.

Views in WPF

When a user binds a WPF property to a collection of data, WPF automatically creates a view to wrap the collection, and binds the property to the view, not the raw collection. This behavior always happens, and is independent of CollectionViewSource.

Views provide four types of functionality: sorting, filtering, grouping and tracking the current item. The scenarios you can implement with these four simple features are endless!

The type of view created by WPF depends on the collection type. There are essentially 3 types of views automatically generated by WPF, all deriving from the CollectionView base class:

  • ListCollectionView -> Created when the collection implements IList.
  • BindingListCollectionView -> Created when the collection implements IBindingList.
  • EnumerableCollectionView -> Created when the collection implements nothing but IEnumerable. This class is internal to WPF.

Before CollectionViewSource was introduced in WPF, you could manipulate the automatically generated view (called the “default view”) by obtaining the ItemCollection returned by the ItemsControl.Items property. ItemCollection is a “hybrid” class – it’s both a collection (implementing ICollection, IList, and IEnumerable) containing the list of items in the ItemsControl, and a view (deriving from CollectionView) that exposes properties to manipulate the default view. Here’s an example:

    this.MyItemsControl.Items.SortDescriptions.Add(new SortDescription(…));

ItemCollection doesn’t contain the implementation for any “view” related functionality though – all of its “view” methods are delegated. If the ItemsControl has items added directly to it in XAML (not data bound), ItemCollection delegates view operations to a private property of type “InnerItemCollectionView” (you can find it in Reflector – it’s appropriately called “_internalView”). If the ItemsControl is data bound, the ItemCollection delegates all view operations to the automatically generated default view that wraps the collection.

There is one more view in WPF that you may come across: CompositeCollectionView. Naturally, this view is automatically created when the property is data bound to a CompositeCollection. CompositeCollections have also existed in WPF for a long time. They allow merging several individual collections and individual items into one single collection in XAML.

    <ItemsControl.ItemsSource>
        <CompositeCollection>
            <ListBoxItem><TextBlock Text="Hello" /></ListBoxItem>
            <CollectionContainer Collection="{Binding Source={StaticResource Collection1}}"/>
            <CollectionContainer Collection="{Binding Source={StaticResource Collection2}}"/>
        </CompositeCollection>
    </ItemsControl.ItemsSource>

Instead of using the default view, the user can also create his own view to wrap the collection. This could be a custom view (implementing ICollectionView) or one of the existing public views – typically ListCollectionView or BindingListCollectionView. For example:

    ListCollectionView lcv = new ListCollectionView(myList);
    this.MyItemsControl.Items = lcv;
    lcv.SortDescriptions.Add(new SortDescription(…));

In this scenario, WPF will not wrap this view in another view, it simply uses the one it’s given, as expected. The user can then use his ListCollectionView object to perform view-related operations (such as adding sort descriptions).

CollectionViewSource in WPF

All of these features already existed in WPF before we decided to add CollectionViewSource. We could already do filtering, sorting, grouping, we could track and set the current item, we could retrieve the default view created by WPF and manipulate it, and we could manually create multiple views of the same collection. So, why did we add CollectionViewSource?

The main reason was to enable those view-related operations in XAML – previously they could only be done in code. Without XAML support, tools like Blend can not provide a good tooling experience for these features. The most common uses of CollectionViewSource are to specify sorting and grouping directly in XAML, or to use XAML to hook up a filter handler defined in code. I’ve shown several samples of this syntax before (this post shows filtering, this post shows grouping, and this post shows sorting and grouping combined), so I won’t go into that here.

CollectionViewSource is NOT a view, unlike the classes I described above. If you look in Reflector, you will notice that it doesn’t even implement ICollectionView – a requirement for a class to be considered a “view”. CollectionViewSource is simply a class that once given a collection (by setting its Source property) creates and exposes the corresponding view (through the View property), and that allows adding sorting and grouping (unfortunately not filtering) directly in XAML.

CollectionViewSource also provides several other methods useful to obtain and manipulate views. Among them is yet another way to retrieve the view that wraps a collection:

    ListCollectionView lcv = CollectionViewSource.GetDefaultView(myCollection) as ListCollectionView;
    lcv.SortDescriptions.Add(new SortDescription(…));

This is my favorite way of getting a view for a collection because 1) it doesn’t require a handle to the ItemsControl like when using ItemCollection and 2) I don’t need to create the view myself. This method completes the list of ways to retrieve the view for a particular collection.

CollectionViewSource also enables another interesting scenario. If a particular CollectionViewSource points to different collections at different times, it remembers all the views that it created to wrap those collections. If a source that has already been set in the past is set again, CVS recognizes it and reuses the view it created originally. This behavior is useful in hierarchical binding scenarios. To illustrate this point, I created a very simple three-level master-detail scenario with the following XAML:

    <Window.Resources>
        <CollectionViewSource Source="{Binding}" x:Key="cvs1"/>
        <CollectionViewSource Source="{Binding Source={StaticResource cvs1}, Path=Lifts}" x:Key="cvs2"/>
        <CollectionViewSource Source="{Binding Source={StaticResource cvs2}, Path=Runs}" x:Key="cvs3"/>
    </Window.Resources>

    <ListBox ItemsSource="{Binding Source={StaticResource cvs1}}" DisplayMemberPath="Name"/>
    <ListBox ItemsSource="{Binding Source={StaticResource cvs2}}" DisplayMemberPath="Name" />
    <ListBox ItemsSource="{Binding Source={StaticResource cvs3}}" />

The DataContext for this window is set to a data source with the following structure:

    public class Mountains : ObservableCollection<Mountain>
    {
        …
    }
    
    public class Mountain
    {
        public ObservableCollection<Lift> Lifts { get; set; }
        …
    }
    
    public class Lift
    {
        public ObservableCollection<string> Runs { get; set; }
        …
    }

Here is a screenshot:

In this sample, cvs1 points to the mountains collection and creates a ListCollectionView to wrap it; cvs2 holds one of three collections: the Lifts collection for the first, second or third mountain; and cvs3 holds one of eight collections of Runs. Now imagine that you pick the first mountain (Whistler) and the second lift from that mountain (Garbanzo Express), then you switch to the second mountain (Stevens Pass) and back to the first mountain again (Whistler). At this point, you would expect the second lift (Garbanzo Express) to still be selected. And it is. Because the second CollectionViewSource stores the view last used to wrap that particular Lifts collection, reusing it next time it needs to display that same collection, the current item is preserved.

If you made it so far, you know more about CollectionViewSource than you will ever need! My next post will include a similar discussion in the context of Silverlight.

Download the WPF project (built with .NET 4.0 Beta 1).


Posted by Bea under WPF | Comments (7)

7 Responses to “WPF’s CollectionViewSource”

  1. Keith says:

    This may be obvious but I’m just not seeing it. cvs2 has its Source set to cvs1, but cvs1 being a CollectionViewSource doesn’t actually have a property called Lifts. I know the Source of cvs1 does, how does this get wired up? i.e. how does {Binding Source={StaticResource cvs1}, Path=Lifts} know to look at the Lifts property on the underlying collection rather than an actually Lifts property on CollectionViewSource.

    I see this behaviour is allowing for the dynamic population of the lifts via the bindings but am just missing some pieces of the puzzle.
    Thanks for all the great posts, I love your blog BTW :)

    • Bea says:

      Hi Keith,

      That is a great question. When you bind to a CollectionViewSource, the Binding automatically binds to the source collection wrapped by the CollectionViewSource. Essentially, it does the equivalent of adding “Path=Source”. We decided to add his behavior because it’s the 99.9% scenario – most of the time when you bind to a CVS, you really want to bind to the source it wraps. For the 0.1% scenario where you want to bind to some other property of CVS, we added a property on Binding called “BindsDirectlyToSource”, which defaults to false but you can set to true.

      The same happens when you bind to an ObjectDataProvider – we automatically do the equivalent of adding “Path=Data”, and you can override that behavior by setting BindsDirectlyToSource to true.

      These are the only two scenarios where the Binding adds extra default behavior.

      Bea

  2. This is great! I especially love the part where the CollectionViewSource does not require a handle to the ItemsControl like the ItemCollection did! I also enjoy how silverlight now includes the CollectionViewSource.

  3. KierenH says:

    Hi Bea

    In the toolkit there is a master-detail sample using a DataGrid and the DataForm. If there is a validation error while editing the current item, then you can’ select a row in the DataGrid until the validation error is resolved or the edit is cancelled. The DataGrid and the DataForm don’t ‘know’ about each other.

    Are they connected by obtaining the same reference to a CollectionViewSource? Is this what makes the magic happen here?

    Cheers
    KierenH

    • Bea says:

      In the master-detail scenario, the master (DataGrid) and detail (DataForm) are both databound to the view that corresponds to the source collection. The master display all the items of that view, and the detail displays the current item of the view.

      In WPF, you don’t necessarily need a CollectionViewSource to be present for the master and detail to be in sync, because a view is generated even if CVS is not used.

  4. KierenH says:

    So to replicate this behavior without a DataForm, I would need to wire up a control that wraps an ItemsSource property in an ICollectionView – and bind to the current item?

    Under the covers, the DataForm and my control would both get the same reference to the collection view?

    • Bea says:

      This behavior is independent of the DataForm – you could be using any other control in its place (ContentControl, ContentPresenter, Button…) If the binding engine can detect that the source is a collection and the target can only display a single item, it will automatically grab the current item from the default view that wraps the collection. This view is created automatically, behind the scenes – there is nothing for you to do here.

      If the binding engine can’t detect that, you may need to give it a little help. For example, if I bind a ContentControl’s Content property (of type object) without a DataTemplate to a collection, I need to set “Path=/” to specify that I want the current item of that collection. Note that I am also not creating a view manually (that happens automatically), and I don’t have to do anything special for the binding to grab the view that wraps the collection.

      You can take a look at the simplest possible master-detail scenario in this post. Notice that it doesn’t use a DataForm, and that the ListBox and ContentControl are in sync because they are both data bound to the same view.

Leave a Reply

For spam filtering purposes, please copy the number 5744 to the field below: