Click here to Skip to main content
Click here to Skip to main content

Tagged as

(untagged)
Go to top

Woring with ICollectionViewSource in WPF

, 24 Aug 2010
Rate this:
Please Sign up or sign in to vote.
If you are working with WPF for long, you might already have come across with ICollectionView. It is the primary Data object for any WPF list controls (like ComboBox, ListBox, ListView etc) that allows flexibilities like Sorting, Filtering, Grouping, Current Record Management etc. Thus it ensures th
If you are working with WPF for long, you might already have come across with ICollectionView. It is the primary Data object for any WPF list controls (like ComboBox, ListBox, ListView etc) that allows flexibilities like Sorting, Filtering, Grouping, Current Record Management etc. Thus it ensures that all the related information like filtering, sorting etc is decoupled from the actual control.  It is been very popular to those working with data object because of inbuilt support for all WPF List controls. So I thought I would consider to describe it a bit so that people might easily plug in the same to their own solution.


What is a CollectionView ? 

It is a layer that runs over the Data Objects which allows you to define rules for Sorting, Filtering, Grouping etc and manipulate the display of data rather than modifying the actual data objects. Therefore in other words, a CollectionView is a class which takes care of the View totally and giving us the capability to handle certain features incorporated within it.

How to Get a CollectionView ?

Practically speaking, getting a CollectionView from an Enumerable the most easiest thing I ever seen in WPF. Just you need to pass the Enumerable to CollectionViewSource.GetDefaultView. Thus rather than defining

<span class="kwrd">this</span>.ListboxControl.ItemsSource = <span class="kwrd">this</span>.Source;

you need to write :

<span class="kwrd">this</span>.ListboxControl.ItemsSource = CollectionViewSource.GetDefaultView(<span class="kwrd">this</span>.Source);

The List will get the CollectionView it requires.



So the CollectionView actually separates the View object List Control with the actual DataSource and hence gives an interface to manipulate the data before reflecting to the View objects. Now let us look how to implement the basic features for the ICollectionView.



Sorting

Sorting can be applied to the CollectionView in a very easy way. You need to add a SortDescription to the CollectionView. The CollectionView actually maintains a stack of SortDescription objects, each of them being a Structure can hold information of a Column and the Direction of Sorting. You can add them in the collectionView to get desired output.

Say I store the CollectionView as a property :

ICollectionView Source { get; set; }

Now if you want to sort the existing collection :
<span class="kwrd">this</span>.Source.SortDescriptions.Add(<span class="kwrd">new</span> SortDescription(<span class="str">"Name"</span>, ListSortDirection.Descending));

Hence the CollectionView will be sorted based on Name and in Descending order.

Note : The default behavior of CollectionView automatically Refresh when a new SortDescription is added to it. For performance issue, you might use DeferRefresh() if you want to refresh only once to add SortDescription more than once.

Grouping

You can create custom group for ICollectionView in the same way as you do for sorting. To create Group of elements you need to use GroupStyle to define the Template for the Group and to show the name of the Group in Group Header.

<span class="kwrd"><</span><span class="html">ListView.GroupStyle</span><span class="kwrd">></span>
    <span class="kwrd"><</span><span class="html">GroupStyle</span><span class="kwrd">></span>
        <span class="kwrd"><</span><span class="html">GroupStyle.HeaderTemplate</span><span class="kwrd">></span>
            <span class="kwrd"><</span><span class="html">DataTemplate</span><span class="kwrd">></span>
                <span class="kwrd"><</span><span class="html">TextBlock</span> <span class="attr">Text</span><span class="kwrd">="{Binding Name}"</span> <span class="kwrd">/></span>
            <span class="kwrd"></</span><span class="html">DataTemplate</span><span class="kwrd">></span>
        <span class="kwrd"></</span><span class="html">GroupStyle.HeaderTemplate</span><span class="kwrd">></span>
    <span class="kwrd"></</span><span class="html">GroupStyle</span><span class="kwrd">></span>
<span class="kwrd"></</span><span class="html">ListView.GroupStyle</span><span class="kwrd">></span>

Here we define the Group HeaderTemplate for each groups so that the TextBlock shows the name of the Group item by which the grouping is made. You can specify more than one Grouping information for a single collection. To group a collection you need to use :

<span class="kwrd">this</span>.Source.GroupDescriptions.Add(<span class="kwrd">new</span> PropertyGroupDescription(<span class="str">"Department"</span>));

Note : Grouping turns off virtualization. So if you are dealing with large amount of data, Grouping may lead to performance issue.

You can apply custom grouping as well by defining IValueConverter in PropertyGroupDescription as well.

Filtering

Filtering requires a delegate (Predicate) based on which the filter will occur. The Predicate takes in the item an based on the value true or false it returns, it selects or unselect an element.

<span class="kwrd">this</span>.Source.Filter = item =>
{
    ViewItem vitem = item <span class="kwrd">as</span> ViewItem;
    <span class="kwrd">if</span> (vitem == <span class="kwrd">null</span>) <span class="kwrd">return</span> <span class="kwrd">false</span>;

    <span class="kwrd">return</span>  vitem.Name.Contains(<span class="str">"A"</span>);

};

This will select only the elements which have A in their names.


Current Record Manipulation

ICollectionView also allows to synchronize items with the Current position of the element in the CollectionView. Each ItemsControl which is the base class of any ListControl in WPF exposes a property called IsSynchronizedWithCurrentItem when set to true will automatically keeps the current position of the CollectionView in synch.

There are methods like :
<span class="kwrd">this</span>.Source.MoveCurrentToFirst();
<span class="kwrd">this</span>.Source.MoveCurrentToPrevious();
<span class="kwrd">this</span>.Source.MoveCurrentToNext();
<span class="kwrd">this</span>.Source.MoveCurrentToLast();

These allows you to navigate around the CurrentItem of the CollectionView.You can also use CurrentChanged event to intercept your selection logic around the object.

Sample Application

To demonstrate all the features, I have created on Demo application which allows you to Sort, Filter, Group and navigate between data objects. Lets see how it works :


The application contains a ListView with few data in it. The header is created using GridView which can be clicked and based on which the items will sort.

<span class="kwrd"><</span><span class="html">ListView</span> <span class="attr">ItemsSource</span><span class="kwrd">="{Binding}"</span> <span class="attr">x:Name</span><span class="kwrd">="lvItems"</span> <span class="attr">GridViewColumnHeader</span>.<span class="attr">Click</span><span class="kwrd">="ListView_Click"</span> <span class="attr">IsSynchronizedWithCurrentItem</span><span class="kwrd">="True"</span> <span class="attr">Grid</span>.<span class="attr">Row</span><span class="kwrd">="1"</span><span class="kwrd">></span>
        <span class="kwrd"><</span><span class="html">ListView.GroupStyle</span><span class="kwrd">></span>
            <span class="kwrd"><</span><span class="html">GroupStyle</span><span class="kwrd">></span>
                <span class="kwrd"><</span><span class="html">GroupStyle.HeaderTemplate</span><span class="kwrd">></span>
                    <span class="kwrd"><</span><span class="html">DataTemplate</span><span class="kwrd">></span>
                        <span class="kwrd"><</span><span class="html">TextBlock</span> <span class="attr">Text</span><span class="kwrd">="{Binding Name}"</span> <span class="kwrd">/></span>
                    <span class="kwrd"></</span><span class="html">DataTemplate</span><span class="kwrd">></span>
                <span class="kwrd"></</span><span class="html">GroupStyle.HeaderTemplate</span><span class="kwrd">></span>
            <span class="kwrd"></</span><span class="html">GroupStyle</span><span class="kwrd">></span>
        <span class="kwrd"></</span><span class="html">ListView.GroupStyle</span><span class="kwrd">></span>
            <span class="kwrd"><</span><span class="html">ListView.View</span><span class="kwrd">></span>
                <span class="kwrd"><</span><span class="html">GridView</span> <span class="attr">AllowsColumnReorder</span><span class="kwrd">="True"</span><span class="kwrd">></span>
                    <span class="kwrd"><</span><span class="html">GridViewColumn</span> <span class="attr">Header</span><span class="kwrd">="Id"</span> <span class="attr">DisplayMemberBinding</span><span class="kwrd">="{Binding Id}"</span> <span class="kwrd">/></span>
                    <span class="kwrd"><</span><span class="html">GridViewColumn</span> <span class="attr">Header</span><span class="kwrd">="Name"</span> <span class="attr">DisplayMemberBinding</span><span class="kwrd">="{Binding Name}"</span> <span class="kwrd">/></span>
                    <span class="kwrd"><</span><span class="html">GridViewColumn</span> <span class="attr">Header</span><span class="kwrd">="Developer"</span><span class="kwrd">></span>
                        <span class="kwrd"><</span><span class="html">GridViewColumn.CellTemplate</span><span class="kwrd">></span>
                            <span class="kwrd"><</span><span class="html">DataTemplate</span><span class="kwrd">></span>
                                <span class="kwrd"><</span><span class="html">TextBlock</span> <span class="attr">Text</span><span class="kwrd">="{Binding Path=Developer}"</span> <span class="kwrd">/></span>
                            <span class="kwrd"></</span><span class="html">DataTemplate</span><span class="kwrd">></span>
                        <span class="kwrd"></</span><span class="html">GridViewColumn.CellTemplate</span><span class="kwrd">></span>
                    <span class="kwrd"></</span><span class="html">GridViewColumn</span><span class="kwrd">></span>
                    <span class="kwrd"><</span><span class="html">GridViewColumn</span> <span class="attr">Header</span><span class="kwrd">="Salary"</span><span class="kwrd">></span>
                        <span class="kwrd"><</span><span class="html">GridViewColumn.CellTemplate</span><span class="kwrd">></span>
                            <span class="kwrd"><</span><span class="html">DataTemplate</span><span class="kwrd">></span>
                                <span class="kwrd"><</span><span class="html">TextBlock</span> <span class="attr">Text</span><span class="kwrd">="{Binding Path=Salary}"</span> <span class="kwrd">/></span>
                            <span class="kwrd"></</span><span class="html">DataTemplate</span><span class="kwrd">></span>
                        <span class="kwrd"></</span><span class="html">GridViewColumn.CellTemplate</span><span class="kwrd">></span>
                    <span class="kwrd"></</span><span class="html">GridViewColumn</span><span class="kwrd">></span>
                <span class="kwrd"></</span><span class="html">GridView</span><span class="kwrd">></span>
            <span class="kwrd"></</span><span class="html">ListView.View</span><span class="kwrd">></span>
        <span class="kwrd"></</span><span class="html">ListView</span><span class="kwrd">></span>


In the above image it is shown how the items automatically gets sorted when the header is clicked. To handle this I have used GridViewColumnHeader.Click which allows you to get control over the column in which it is clicked.

<span class="kwrd">private</span> <span class="kwrd">void</span> ListView_Click(<span class="kwrd">object</span> sender, RoutedEventArgs e)
        {
            GridViewColumnHeader currentHeader = e.OriginalSource <span class="kwrd">as</span> GridViewColumnHeader;
            <span class="kwrd">if</span>(currentHeader != <span class="kwrd">null</span> && currentHeader.Role != GridViewColumnHeaderRole.Padding)
            {
                <span class="kwrd">using</span> (<span class="kwrd">this</span>.Source.DeferRefresh())
                {
                    Func<SortDescription, <span class="kwrd">bool</span>> lamda = item => item.PropertyName.Equals(currentHeader.Column.Header.ToString());
                    <span class="kwrd">if</span> (<span class="kwrd">this</span>.Source.SortDescriptions.Count(lamda) > 0)
                    {
                        SortDescription currentSortDescription = <span class="kwrd">this</span>.Source.SortDescriptions.First(lamda);
                        ListSortDirection sortDescription = currentSortDescription.Direction == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;


                        currentHeader.Column.HeaderTemplate = currentSortDescription.Direction == ListSortDirection.Ascending ?
                            <span class="kwrd">this</span>.Resources[<span class="str">"HeaderTemplateArrowDown"</span>] <span class="kwrd">as</span> DataTemplate : <span class="kwrd">this</span>.Resources[<span class="str">"HeaderTemplateArrowUp"</span>] <span class="kwrd">as</span> DataTemplate;

                        <span class="kwrd">this</span>.Source.SortDescriptions.Remove(currentSortDescription);
                        <span class="kwrd">this</span>.Source.SortDescriptions.Insert(0, <span class="kwrd">new</span> SortDescription(currentHeader.Column.Header.ToString(), sortDescription));
                    }
                    <span class="kwrd">else</span>
                        <span class="kwrd">this</span>.Source.SortDescriptions.Add(<span class="kwrd">new</span> SortDescription(currentHeader.Column.Header.ToString(), ListSortDirection.Ascending));
                }                 
               
                   
            }

            
        }

In the above code I need to handle the Sorting gracefully so that we always remove the current Sorting before we insert a new sort.


The FilterBy section allows you to handle Filtering. You can select the ColumnName from the ComboBox and then apply the selection criteria for the column. To do this I have used FilterButton Click command.


<span class="kwrd">this</span>.Source.Filter = item =>
            {
                ViewItem vitem = item <span class="kwrd">as</span> ViewItem;
                <span class="kwrd">if</span> (vitem == <span class="kwrd">null</span>) <span class="kwrd">return</span> <span class="kwrd">false</span>;

                PropertyInfo info = item.GetType().GetProperty(cmbProperty.Text);
                <span class="kwrd">if</span> (info == <span class="kwrd">null</span>) <span class="kwrd">return</span> <span class="kwrd">false</span>;

                <span class="kwrd">return</span>  info.GetValue(vitem,<span class="kwrd">null</span>).ToString().Contains(txtFilter.Text);

            };

Hence the predicate will be applied to the filter criteria.





You can use Grouping Section to group based on Column Name. Here you can see I have grouped items on Developer names. The applied group header will be shown in the Group Header section.

<span class="kwrd">this</span>.Source.GroupDescriptions.Clear();

            PropertyInfo pinfo = <span class="kwrd">typeof</span>(ViewItem).GetProperty(cmbGroups.Text);
            <span class="kwrd">if</span> (pinfo != <span class="kwrd">null</span>)
                <span class="kwrd">this</span>.Source.GroupDescriptions.Add(<span class="kwrd">new</span> PropertyGroupDescription(pinfo.Name));



Navigation is handled using a sets of buttons which eventually calls the respective methods to automatically keep CollectionView in sync with ListView items.

Download Sample - 74KB

Notice : I have used reflection to get  PropertyInfo for many cases. If you dont want to use, you might also do this statically. I have used reflection only to handle this dynamically.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Abhishek Sur
Architect
India India
Did you like his post?
 
Oh, lets go a bit further to know him better.
Visit his Website : www.abhisheksur.com to know more about Abhishek.
 
Abhishek also authored a book on .NET 4.5 Features and recommends you to read it, you will learn a lot from it.
http://bit.ly/EXPERTCookBook
 
Basically he is from India, who loves to explore the .NET world. He loves to code and in his leisure you always find him talking about technical stuffs.
 
Presently he is working in WPF, a new foundation to UI development, but mostly he likes to work on architecture and business classes. ASP.NET is one of his strength as well.
Have any problem? Write to him in his Forum.
 
You can also mail him directly to abhi2434@yahoo.com
 
Want a Coder like him for your project?
Drop him a mail to Dotnet Tricks and Tips



Dont forget to vote or share your comments about his Writing
Follow on   Twitter   Google+

Comments and Discussions

 
GeneralMy vote of 1 Pinmemberapoplex20084-Mar-14 8:13 
GeneralMy vote of 1 PinmvpJohn Simmons / outlaw programmer10-Aug-12 6:18 
GeneralMy vote of 5 Pinmemberthatraja5-Oct-10 21:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140922.1 | Last Updated 24 Aug 2010
Article Copyright 2010 by Abhishek Sur
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid