Oct 31, 2009

WPF’s CollectionViewSource

59WPFCollectionViewSource

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.ItemsSource = 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).

21 Comments
  1. Keith

    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

      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. Matt Serbinski

    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

    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

      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

    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

      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.

  5. John

    Hello Bea,

    This along with the CollectionView articles have been a great help, but I’m still having problems understanding filtering hierachical items.

    I have an ObservableCollection(Of Projects) and each Project has a collection of Items, and each Item has its own collection of Items. So far I have the following:

    OpenProjectsView = CollectionViewSource.GetDefaultView(OpenProjectsVM)

    OpenProjectsView.Filter = New System.Predicate(Of Object)(AddressOf FilterByProp)

    Public Function FilterByProp(ByVal ent As Object) As Boolean
    Dim entTest As ProjectViewModel = CType(ent, ProjectViewModel)
    ‘Problem – filtering here filters out Projects, not Items
    End Function

    Can you help point me in the right direction please?

    Thanks

    John

    • Bea

      Hi John,

      WPF doesn’t have good intuitive support for filtering at different levels of the hierarchy, unfortunately. This scenario was discussed a few times when I was still on the team, but we never ended up adding better support for it.

      To filter at different levels, you need to have a filter method for each of those levels. All collections of a level need to have that filter specified in their view. At first sight it seems like this would be easy to achieve through XAML, by adding a CollectionViewSource with a Filter to each HierarchicalDataTemplate, but it’s not as easy as it seems. I have seen people try three approaches to this problem, all of which don’t work. (Excuse me for any typos in the code/XAML below, as I’m just typing it on the fly, not compiling it.)

      1. Add the CollectionViewSources for all levels to the window’s resources, and bind the ItemsSource of each HierarchicalDataTemplate to the corresponding CollectionViewSource:

      (This assume the DataContext for the window is an ObservableCollection of Mountain.)

      <Window.Resources>
      <CollectionViewSource x:Key=”MountainsCVS” Source=”{Binding}” Filter=”FilterMountain” />
      <CollectionViewSource x:Key=”LiftsCVS” Source=”{Binding Lifts}” Filter=”FilterLift” />
      <CollectionViewSource x:Key=”RunsCVS” Source=”{Binding Runs}” Filter=”FilterRun” />

      <HierarchicalDataTemplate DataType=”{x:Type local:Mountain}” ItemsSource=”{Binding Source={StaticResource FilterLift}}”>
      <TextBlock Text=”{Binding Name}” />
      </HierarchicalDataTemplate>

      <HierarchicalDataTemplate DataType=”{x:Type local:Lift}” ItemsSource=”{Binding Source={StaticResource FilterRuns}}”>
      <TextBlock Text=”{Binding Name}” />
      </HierarchicalDataTemplate>
      </Window.Resources>

      This doesn’t work because all CollectionViewSources have the same data context – the collection of Mountain. Their data context doesn’t change depending on where they’re used.

      2. Add the CollectionViewSource to the resources of the HierarchicalDataTemplate:

      <HierarchicalDataTemplate DataType=”{x:Type local:Mountain}”>
      <HierarchicalDataTemplate.Resources>
      <CollectionViewSource x:Key=”LiftsCVS” Source=”{Binding Lifts}” Filter=”FilterLift” />
      </HierarchicalDataTemplate.Resources>
      <HierarchicalDataTemplate.ItemsSource>
      <Binding Source=”{StaticResource LiftsCVS}” />
      </HierarchicalDataTemplate.ItemsSource>
      <TextBlock Text=”{Binding Name}” />
      </HierarchicalDataTemplate>

      The data context for the template is a specific Mountain instance, which leads people to believe that the CollectionViewSource would pick up the rigth data context. However, this doesn’t work because, although the each instance of the HierarchicalDataTemplate has its data context set to a specific Mountain, the resources section does not inherit that data context (it has no data context). This happens because the resources added there are shared across all instantiations of the template. So this solution doesn’t work either.

      3. Add the CollectionViewSource directly in the ItemsSource of the HierarchicalDataTemplate:

      <HierarchicalDataTemplate DataType=”{x:Type local:Mountain}”>
      <HierarchicalDataTemplate.ItemsSource>
      <Binding>
      <Binding.Source>
      <CollectionViewSource Source=”{Binding Lifts}” Filter=”FilterLift” />
      </Binding.Source>
      </Binding>
      </HierarchicalDataTemplate.ItemsSource>
      <TextBlock Text=”{Binding Name}” />
      </HierarchicalDataTemplate>

      In this case you end up with a nested binding, which also doesn’t pick up the data context as may be expected.

      So, what can you do instead?

      If your data doesn’t change, you can use a converter to filter the data at first load. You can take a look at a simple project that uses a converter for this purpose. The concept is quite simple – the converter gets added in the ItemsSource Binding, and the filter logic goes in the converter implementation.

      <HierarchicalDataTemplate DataType=”{x:Type local:Lift}” ItemsSource=”{Binding Path=Runs, Converter={StaticResource FilterRunsConverter}}”>
      <TextBlock Text=”{Binding Name}” />
      </HierarchicalDataTemplate>

      If your data changes, you can write code that ensures that all TreeViewItems are data bound to views that wrap their original collections with the Filter property set. You may also consider adding filtering on the data logic side of the app, instead of using the client-side built-in filtering capabilities of WPF.

      In addition, there may be other creative and tricky ways of adding CollectionViewSources with the Filter method directly in XAML, and if you find an elegant way of doing it, please let me know.

      Hope this helps.
      Bea

      • John

        Wow – thank for a truly comprehensive reply.

        For some reason I’d never considered using DataTemplate resources (as per option 2) although now you explain it I can understand why that doesn’t work (and I’ll remember that point for future).

        Using a converter in the ItemsSource binding seems like a good idea and one that will work for me I think – The sample project is a great help, thank you.

        I’d been using Linq up to now to populate a secondary collection, which works but feel like a rather heavy solution.

        Anyway, thanks again for taking the time to reply in such detail. It has really helped fill in the gaps for me.

        Best regards

        John

        • Owen

          Hi John,
          I’ve actually gotten this working the way one would expect: items at different levels filter out (based on a text search in my case) unless children pass that same filter. I’ll warn you that this solution is not pretty. It is code-concise but has the major drawback of regenerating the containers (ie reapplying the template) every time the filter text changes.

          public class CollectionViewConverter : IValueConverter, INotifyPropertyChanged
          {
          private string _filterText = string.Empty;

          public string FilterText
          {
          get
          {
          return _filterText;
          }
          set
          {
          _filterText = value;
          foreach (CollectionViewSource cvs in _sources.Values)
          {
          cvs.View.Refresh();
          }
          PropertyChangedEventHandler localHandler = PropertyChanged;
          if (localHandler != null)
          {
          localHandler(this, new PropertyChangedEventArgs(“FilterText”));
          }
          }
          }

          private void OnFilter(object sender, FilterEventArgs e)
          {
          e.Accepted = (e.Item as IOutput).Name.Contains(FilterText) || !_sources[e.Item].View.IsEmpty;
          }

          #region INotifyPropertyChanged Members

          public event PropertyChangedEventHandler PropertyChanged;

          #endregion

          #region IValueConverter Members

          private Dictionary _sources = new Dictionary();

          public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
          {
          if (!_sources.ContainsKey(value))
          {
          CollectionViewSource childrenCvs = new CollectionViewSource();
          childrenCvs.Source = value;
          childrenCvs.Filter += OnFilter;
          _sources.Add(value, childrenCvs);
          }
          return _sources[value].View;
          }

          public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
          {
          throw new NotSupportedException();
          }

          #endregion
          }

          If you need top-level filtering as well you’ll need to hook up an identical filter method at that level (I have that sitting in the MainWIndow and use
          e.Accepted = (e.Item as IOutput).Name.Contains(_filterBox.Text) || !((_treeView.ItemContainerGenerator.ContainerFromItem(e.Item) as Telerik.Windows.Controls.ItemsControl).ItemsSource as ICollectionView).IsEmpty;

          • Bea

            Thanks for posting your code Owen.
            I’ll actually be discussing a scenario similar to this one in my next blog post, so stay tuned.

            Bea

  6. Oleg

    Hi,

    As usual – great explanations, thank you!
    I did not quite understand this snippet though

    > ListCollectionView lcv = new ListCollectionView(myList);
    > this.MyItemsControl.Items = lcv;

    Isn’t Items property of ItemsControl readonly property?

    • Bea

      Hi Oleg,

      It should be “ItemsSource”, not “Items”. I’ve corrected that in the post.
      Thanks a lot for letting me know!

      Bea

  7. John

    Hello Bea,

    Did you get a chance to look at my comment of the 24th Dec? If you feel it’s not relevent to this post then let me know and I’ll look elsewhere.

    Thanks

    John

    • Bea

      Hi John,
      See above for a reply.
      Bea

  8. Beginner

    I’ve found what was looking for! :-)
    Thanks for detailed explanations and working sample code!

    Your blog is amazing and very very usefull!
    Keep shining! :-)

  9. Kevin

    Hi Bea,

    I thought this might work for the scenario I’m using:

    Suppose I have one ObservableCollection. I want to show the items of this collection in two different locations, each with a different sorting. The way to go, I presume, would be to create a CollectionViewSource in each of the two different locations, with different sorting specified in xaml.

    However, two problems arise:

    1) As featured here http://msdn.microsoft.com/en-us/library/system.windows.data.collectionviewsource.aspx , the CollectionViewSource is defined in the resources section and its source is set to a locally defined resource/observableCollection. However, what if I’m working with MVVM? I would then like to bind the Source property of the CollectionViewSource to an ObservableCollection in my view model, which apparently can’t be done, since the CollectionViewSource has no access to the datacontext of the window/control it’s defined in (whose datacontext would be the view model). A possible solution would be this: http://www.silverlightplayground.org/post/2009/07/18/Use-CollectionViewSource-effectively-in-MVVM-applications.aspx The CollectionViewSource gets created in the view model and sorting gets applied there. You would then bind your itemscontrol.itemssource to the CollectionViewSource.View in the view model. However, you would lose the advantage of doing it all in XAML.
    2) How is this all supposed to work in Blend? Ideally I would like the programmer to be able to write a viewmodel, which the user interface maker could use in Blend. However, when I’ve created the CollectionViewSource in the view model (as in the above scenario), it looks like Blend can’t resolve the object type when setting the ItemsSource of an ItemsControl to the view model’s CollectionViewSource.View. Which is understandable, since the CollectionViewSource.view isn’t typed.

    Any ideas on this?

    • Bea

      Hi Kevin,

      I’m assuming you’re using Silverlight. If you were using WPF, you would be able to place the two CollectionViewSources in the resources section of your view, and they would inherit the data context as expected. In Silverlight this doesn’t happen though. The Silverlight workaround you point out in the link is good. It’s unfortunate that it can’t be done in XAML, but it’s really not that much extra code. At some point, I expect to see the WPF DataContext inheritance in Silverlight too.

      Sorry I can’t help you with the Blend question.

      Bea

      • Kevin

        Hey Bea,

        As a matter of fact, I am using WPF (sorry for not pointing that out). I didn’t realize there was a difference, so I didn’t bother checking if it worked, since I read it didn’t for silverlight. Thanks for point that out!

        The only piece of the puzzle that’s missing now is the blend issue.

        Kevin

  10. Catherine

    Oh. I see. The datagrid binds to FirstName and Lastname the others to FullName which is a calculated property in the Employee partial class (Employee is Linq to SQL). When I bind to FirstName, it works. Spent all day on it and couldn’t see it until I posted here.

Comments are closed.