by andrea.nospam@nospam.boschin.it (Andrea Boschin) via Silverlight & XAML Playground on 7/10/2009 5:45:00 PM
Since the first release of WPF the framework contains a component called CollectionViewSource. The CollectionViewSource allow developer to apply sorting, filtering and grouping to in-memory collections of objects. This component is very useful when small collections (up to hundreds of items) is loaded in memory and need to give a better experience to the user.
Some months ago I provided an implementation of the CollectionViewSource for Silverlight 2.0. I worked hard to implement the component and finally I published a version with the only limitation of not allowing grouping. While sorting and filtering are implemented directly on the data seamless, the grouping require an infrastructure to be consumed (grouping support in ItemsControl) that I was unable to provide.
The new release of Silverlight 3.0 RTW comes with an implementation of this component that fill this hole but it has the same limitations of my CollectionViewSource. This limitations come from the same reason I cannot implement by myself: the missing grouping support in ItemsControls. However the native component has a some advantages on mine, first of all a very strict compatibility with the architecture of the WPF CollectionViewSource. In this post I will show you how to configure and use the component in a Silverlight application.
The Silverlight CollectionViewSource is a class that inherits from DependencyObject and usually can be inserted in a resource dictionary in the page where it has to work. The principle of the CVS (this is the shorter name I will use from this point in advace) is acting as a filter between the source collection (IEnumerable or IList) and the component where we are showing the data. While binding a ListBox directly to a flat collection will show all the instances in the collection, putting the CVS in the middle enable us to operate on its properties to sort and filter the source.
Here is an example that shows how to embed the CVS in a xaml page; The first part show a DataSource object SampleDataCollection in the UserControl.Resources, and a CollectionViewSource that reference it using StaticResource. In the second part there is a ListBox that reference the CVS also using a StaticResource markup extension.
1: ... omissis ...
2:
3: <UserControl.Resources>
4: <local:DataSource x:Key="dataSource" />
5: <CollectionViewSource x:Name="cvs" Source="{Binding Names, Source={StaticResource dataSource}}">
6: <CollectionViewSource.SortDescriptions>
7: <scm:SortDescription Direction="Ascending" />
8: </CollectionViewSource.SortDescriptions>
9: </CollectionViewSource>
10: </UserControl.Resources>
11:
12: ... omissis ...
13:
14: <ListBox ItemsSource="{Binding Source={StaticResource cvs}}"
15: Margin="5,5,5,1" Grid.ColumnSpan="4" />
16:
17: ... omissis ...
The magic around CVS come from an interface called ICollectionView that is esposed by the CVS in the View property. This interface represent the real working object that behind the scenes understand the source type and manage to apply filtering and sorting seamless. When a datasource is connected to the CVS a specialized instance of the ICollectionView is created - EnumerableCollectionView for IEnumerable or ListCollectionView for IList - and this instance work knowing the source data. When we connect a consumer control to the CVS it is binded directly to the ICollectionView. Watching at this interface we will see it implements INotifyCollectionChanged. This mean that if we connect the CVS to an ObservableCollection<T> all the updates to the source collection will be transmitted to the consumer.
Now after the basics of the CVS it is time to try to use its properties to apply the features it implements. If we see at the CVS we will see a Filter event exposed to the markup or to the code. This event is raised by the CVS every time it need to understand if an item of the collection is valid for the filter the user is appling. In the semple I included at the end of this post I use the CVS to filter a collection of italian names (strings). In the Filter event I simply compare every item in the collection to the letters the use typed in a TextBox:
1: /// <summary>
2: /// Handles the Filter event of the cvs control.
3: /// </summary>
4: /// <param name="sender">The source of the event.</param>
5: /// <param name="e">The <see cref="System.Windows.Data.FilterEventArgs"/> instance containing the event data.</param>
6: void cvs_Filter(object sender, FilterEventArgs e)
7: {
8: e.Accepted = ((string)e.Item).ToLower().StartsWith(searchKey.Text.ToLower());
9: }
The FilterEventArgs class expose an Accepted property useful to indicate if the element (e.Item) we are evaluating has to be shown or not. This event is very simple to handle and let the developer to apply various filtering rules. We have only to pay attention to the time that the evaluation take because it is repeated one time for every element in the collection.
While the user type in the TextBox we catch the TextChanged event and force the refresh of the CVS this way:
2: /// Handles the TextChanged event of the searchKey control.
5: /// <param name="e">The <see cref="System.Windows.Controls.TextChangedEventArgs"/> instance containing the event data.</param>
6: private void searchKey_TextChanged(object sender, TextChangedEventArgs e)
8: this.cvs.View.Refresh();
Applying sorting if simpler than filtering. It is done by using markup with the SortDescriptions property. This property accept a collection of SortDescription, one for every property we need to sort. For each property we can choice the direction of sorting using the Direction property:
<scm:SortDescription Direction="Ascending" PropertyName="Name" />
Here is a snippet of code from the sample i provide:
1: <CollectionViewSource x:Name="cvs">
2: <CollectionViewSource.SortDescriptions>
3: <scm:SortDescription Direction="Ascending" />
4: </CollectionViewSource.SortDescriptions>
5: </CollectionViewSource>
While I'm binding strings there is no need to specify a property because the sort has to be applied directly to the source string. If you need a more complex sorting logic it is always available the IComparable interface that is used to evaluate the elements during the sort operation.
While at the first look the CollectionViewSource implementation in Silverlight appear to be very close to the WPF component, there is some subtle differences. First of all if we examine the component we may found a property called GroupDescriptions. This property is used in WPF to apply grouping but in the Silverlight version it throw a NotSupportedException. Probably it is a placeholder for future extensions of the component.
The most important difference to me if about the structure of the object. The CVS inherits from DependencyObject but while in silverlight only classes derived from FrameworkElement may be subject of DataBinding the only way to put a CVS in markup would be using a StaticResource.
This would be a huge limitation to CVS but fortunately the people from the team found a way to workaround it. Also if CVS inherits from DependencyObject it is still possible to use databinding but it is needed to specify an explicit Source like the following example:
1: <UserControl.Resources>
2: <CollectionViewSource x:Name="cvs" Source="{Binding Names, Source={StaticResource dataSource}}">
3: <CollectionViewSource.SortDescriptions>
4: <scm:SortDescription Direction="Ascending" />
5: </CollectionViewSource.SortDescriptions>
6: </CollectionViewSource>
7: </UserControl.Resources>
This is very useful if we have a complex object like a ViewModel we want to use in a page with a CVS instance. The better would be to bind directly to the datasource but is is possible to put the ViewModel in the resources and then statically reference it from both the DataContext and the CollectionViewSource.
Last months I decided to start writing a component like CVS because I think it is really useful in some cases. Now I'm very glad to rely on a framework component. For some of you that have been used my component take note that the CVS interface is very close to my component so it is very easy to switch to it.
Download: Elite.Silverlight3.CollectionViewSample.zip (1,04 MB)
Video: CollectionViewSource.wmv (1.08 MB)
Original Post: Silverlight 3.0 RTW: The CollectionViewSource
The content of the postings is owned by the respective author. Silverlight Feeds is not responsible for the contents of the postings. This site is automatically generated and cannot be reviewed for abusive content. If you find abusive content on Silverlight Feeds, please contact us. Designated trademarks and brands are the property of their respective owners. All rights reserved.