Aug 06, 2006

How to filter items from a collection

28FilterSample

Today I will show you two ways of using the filter feature of data binding.

There are two ObservableCollections of items in this sample. The first one contains a list of GreekGods, and the ListBox that displays it shows their Roman names. The second one contains a list of GreekHeroes. I will use these two collections to show two different ways of filtering items.

I decided to filter out all items that start with “A” from the first collection. I started out by adding a new instance of the GreekGods collection to the resources and bound a ListBox’s ItemsSource to that collection:

    <Window.Resources>
        <local:GreekGods x:Key="src1"/>
        (…)
    </Window.Resources>

    <Label>Items that start with "A" are filtered out:</Label>
    <ListBox ItemsSource="{Binding Source={StaticResource src1}}" DisplayMemberPath="RomanName"/>

In the constructor for the Window, I added code after initialization to get the default view for this collection. Remember that we never bind directly to the collection; there is always a view on top of that collection that we bind to. In this case I am not creating that view explicitly with the help of a CollectionViewSource, so a default view is created behind the scenes. I can get to that default view by using the GetDefaultView static method of CollectionViewSource:

    public Window1()
    {
        InitializeComponent();

        object src1 = this.Resources["src1"];
        ICollectionView collectionView = CollectionViewSource.GetDefaultView(src1);
        collectionView.Filter = new Predicate<object>(FilterOutA);
    }

Once I have a handle to the view, I am able to set its Filter property to the Predicate<object> callback function below:

    public bool FilterOutA(object item)
    {
        GreekGod gg = item as GreekGod;
        if ((gg == null) || gg.RomanName.StartsWith("A"))
        {
            return false;
        }
        else
        {
            return true;
        }
    }

This method is called once for each item. If the item is a GreekGod whose Roman name begins with “A”, it will return false, and that item will be filtered out. Otherwise, it will return true and the item will be displayed.

Similarly, I decided to filter out all items that start with B from the second collection. However, this time I am using CollectionViewSource explicitly. I set the Source property of the CollectionViewSource to the collection and bind the ListBox’s ItemsSource to the CollectionViewSource:

    <Window.Resources>
        (…)
        <local:GreekHeroes x:Key="src2"/>

        <CollectionViewSource Source="{StaticResource src2}" x:Key="cvs" Filter="FilterOutB"/>
    </Window.Resources>

    <Label>Items that start with "B" are filtered out:</Label>
    <ListBox ItemsSource="{Binding Source={StaticResource cvs}}" DisplayMemberPath="HeroName"/>

We came up with the CollectionViewSource class to serve two purposes: 1) To allow us to do view related operations in XAML, such as grouping, sorting, filtering or creating a custom view of a certain type and 2) To serve as a container for all the view methods, such as GetDefaultView or IsDefaultView. Before we came up with this class these methods were in the Binding class, which cluttered it with unrelated methods and made it hard for users to find them.

I’ve been asked several times about the difference between CollectionView and CollectionViewSource. The difference is simple: CollectionViewSource is not a view, but a class that contains handles to the source and the corresponding view and is used for the purposes I described above. CollectionView is an actual view on top of a collection, which contains information about the current item, filtering, sorting and grouping.

Unfortunately you still need code to specify which items are filtered in or out:

    private void FilterOutB(object sender, FilterEventArgs e)
    {
        GreekHero gh = e.Item as GreekHero;
        if ((gh == null) || gh.HeroName.StartsWith("B"))
        {
            e.Accepted = false;
        }
        else
        {
            e.Accepted = true;
        }
    }

Note that the Filter property in CollectionViewSource is not a delegate like the one in CollectionView, but an event handler. We made this decision so that you could specify the name of the filter method in XAML. At this point, there is no support to add the name of the callback method of a delegate in XAML, but this support exists for events. We realize that the two ways of filtering are a little inconsistent, but this design brings us closer to our goal of allowing the user to do as much as possible in XAML with CollectionViewSource.

The filter event handler has a FilterEventArgs argument, which has two interesting properties: the Item property that provides the item on which we need to make the filter decision, and the Accepted property where we set the result of that decision. If we set Accepted to false, the item will be filtered out, and if we set it to true, the item will be displayed.

Here is a screenshot of the completed sample:

Here you can find the VS project with this sample code. This works with July CTP WPF bits.

16 Comments
  1. paul shmakov

    Hi Beatriz,

    Thanks a lot for your articles! Awesome work. I’m looking forward for new posts.

    The only problem - looks like your RSS feed is broken - it says that the most recent post is “How do I show the Status of a Binding?” Apr 4 2006.

    paul

    • Bea

      Paul,

      Thanks a lot for letting me know, I really appreciate it. I will investigate what is going on with my feed.

      Bea

      • Bea

        Hi Paul,

        I fixed the problem with the feed. Let me know if your RSS reader picked it up.

        Thanks a lot for letting me know of this issue.

        Bea

  2. miksu

    Great examples. My knowledge is more in COM and XSL and now that it looks like WPF doesn’t support pluggable protocols (COM objects through URLMon), that leaves me only XSL. Is it possible to run some client side xslt to binded XML so that binded elements are automatically updated?

    • Bea

      Hi Miksu,

      We don’t have any special support for XSLT. However, any change you make to a source XML document will be propagated to the target of the bindings. Did this answer your question?

      Bea

  3. eiro

    Hi!

    i want to filter a collection depending on the Text of a Textbox, but the only problem is:

    how can i force the CollectionView to refresh the filtering process ??

    eiro

    • Bea

      Hi Eiro,

      There is a Refresh method on CollectionView that allows you to do what you’re looking for.

      Thanks!
      Bea

      • Shimmy

        I am using a CollectionViewSource and I am unable to find that method, could you please help me out?

        • Shimmy

          Oh, it’s m_CollectionViewSource.View.Refresh()

          • Bea

            Yes, that’s one way to get to the view. There are other ways - I explain all the different options in this post.

  4. Zach

    how does one use a single observable collection, and then have two different control view that single collection in different ways with different filters? i am assuming multiple views on top of the single collection, but am having trouble with the XAML.

    • Bea

      Hi Zach,

      Your assumption is correct.
      You can add two CollectionViewSource instances to the resources, both pointing to the same source data (same Source property), but with different Filter methods. Then you can bind one control to one CVS, and another control to the other.

      Let me know if you still have trouble with the syntax.

      Bea

  5. dan

    Hi Bea,

    I am trying to use collectionview filtering with your AsyncVirtualizingCollection over a large underlying data source. The problem is that when a filter predicate is added to the ListCollectionView.Filter it goes off and enumerates over the entire underlying collection applying the filter. I’m wondering if there could be a workaround if I wrote my own ICollectionView so that filtering is applied as late as possible as items are scrolled into view. This would obviously meam that the total filtered items count is unknown. Could this possibly work?

    Thanks
    Dan

    • Bea

      Yep, ListCollectionView’s filtering doesn’t play well with data virtualization.

      I have a solution for your problem and can make that my next blog post. So stay tuned - I’ll post it in a few days.

      Bea

  6. Jason

    Hi Bea,

    Great post! I just have a quick question about CollectionViewSource. Is there a way to turn off Selection Syncing? I’m using several CollectionViewSource instances connected to the same list but I need each one to have a seperate selected item. I’ve posted on MSDN but so far no luck so I came to the expert:)

    Thanks,
    Jason

    • Bea

      Hi Jason,

      Yes, that is possible. One way to achieve that is to connect each of your ListBoxes to a different CollectionViewSource.
      If you want to know more about that, this blog post may help.

      Bea

Comments are closed.