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

Autofiltering Support for Silverlight DataGrid

, 16 Jan 2009
Rate this:
Please Sign up or sign in to vote.
Allows auto filtering functionality for the datagrid columns

Introduction

One functionality that is good to have for a data grid and quite common for Winform/ASP.NET is auto filtering. At the moment the Silverlight DataGrid doesn’t provide anything for this, so I have decided to address the problem. The idea is to have the option to choose from different filter criteria for each DataGrid column, depending on the bound data type.

In order to support filtering the DataGrid source collection should be able to accept various filtering algorithms and "present" back to the grid only those items matching the criteria. If you have a look at the System.ComponentModel namespace you will find the ICollectionView interface. Here is the MSDN Documentation remark on the interface.

"DataGrid control uses this interface to access the indicated functionality in the data source assigned to its ItemsSource property. If the ItemsSource implements IList , but does not implement ICollectionView , the DataGrid wraps the ItemsSource in an internal ICollectionView implementation."

And another short note: "You can think of a collection view as a layer on top of a binding source collection that allows you to navigate and display the collection based on sort, filter, and group queries, all without having to manipulate the underlying source collection itself. If the source collection implements the INotifyCollectionChanged interface, the changes that raise the CollectionChanged event are propagated to the views."

Code Design

Based on this I have written a collection view on top of an IList. The code should be self explanatory; the collection view manages internally an ordered set of indexes to the original collection for those elements matching the filters criteria and every time there is a change in the filtering context this set is being rebuilt. I made a restriction to my generic collection view, forcing the elements to inherit from INotifyPropertyChanged. There is a small catch to this and I will discuss it at the end, later in the article. The reason behind this is to be able to handle any value change for an element property in order to be able to check if that collection element is matching the filtering criteria.

public interface IFilteredCollection : ICollectionView
{
    void AddFilter(IFilter filter);

    void RemoveFilter(IFilter filter);

    …
}

public class FilteredCollectionView
<t> :  IFilteredCollection, INotifyPropertyChanged
    where T : INotifyPropertyChanged

As seen in the code snippet above the collection view allows to register/unregister an instance of IFilter. All the classes inheriting this interface must implement:

  • bool IsMatch(object target) : determines if the target object matches the criteria;
  • PropertyInfo PropertyInfo : returns the PropertyInfo object for the property (of a collection view element) that is targeted by the matching criteria;
  • EventHandler FilteringChanged: Occurs when the filter has changed and the IsMatch logic has been affected;

Have a look at the next class diagram for a full list of filtering logic implementations.

Filters UML

For each defined filter there is an equivalent UI control to manage, all these controls inherit the IFilterView interface. Every filter UI view is bound to a presentation model represented by the IUIFilterPresentationModel, the model being responsible for holding the actual IFilter and managing its active/disabled state.

Filters Presentation Model diagram

Filters View Diagram

Depending on the data grid column type some of the filters apply while others don’t. The FilterViewInitializersManager class manages a list of IFilterViewInitializer; the initializer is responsible for creating the appropriate IFilter and the corresponding UI view and presentation model:

  public IFilterView GetFilterView(PropertyInfo propertyInfo, ICollectionView collection)
  {
        CheckArgument(propertyInfo);
        IFilter filter = GetFilter(propertyInfo);

        Type multiValuePresenterType = typeof(
            MultiValuePresentationModel<>).MakeGenericType(propertyInfo.PropertyType);
        IMultiValuePresentationModel model = (
            IMultiValuePresentationModel)Activator.CreateInstance(
            multiValuePresenterType, filter, FilterName, collection);

        MultiValueFilterView view = new MultiValueFilterView();
        view.Model = model;
        return view;
  }

  protected virtual IFilter GetFilter(PropertyInfo propertyInfo)
  {
        return (IFilter)Activator.CreateInstance(
            typeof(EqualFilter<>).MakeGenericType(propertyInfo.PropertyType),
            propertyInfo);
  }

By default the manager has a couple of initializers registered, but is up to you to add new ones or remove any existing ones (EqualFilterInitializer, GreterOrEqualFilterInitializer, LessOrEqualFilterInitializer, RangeFilterInitializer, StringFilterInitializer).

The filters option is displayed in the column header. DataGridColumnFilter is responsible for loading the FiltersView and create the corresponding presentation model. In order to find the DataBoundColumn for the corresponding column, it starts walking up the view tree searching for the DataGridColumnHeader and then for the DataGridColumnHeadersPresenter identifying the corresponding index in the grid column collections. Once the column is identified it will grab the binding path. If the binding path has a depth greater than 1 it will be ignored:

DataGridColumnHeader columnHeader = VisualHelper.GetParent
<datagridcolumnheader>(this);
            if (columnHeader == null)
            {
                return null;
            }

            DataGridColumnHeadersPresenter presenter =
                VisualHelper.GetParent<DataGridColumnHeadersPresenter>(columnHeader);
            int columnIndex = presenter.Children.IndexOf(columnHeader);
            if (!(columnIndex >= 0 && columnIndex < presenter.Children.Count))
            {
                return null;
            }
            DataGrid dataGrid = VisualHelper.GetParent<DataGrid>(columnHeader);
            if (dataGrid == null)
            {
                return null;
            }
            if (columnIndex >= dataGrid.Columns.Count)
            {
                return null;
            }
            IFilteredCollection filteredCollection = 
                dataGrid.ItemsSource as IFilteredCollection;
            if (!(filteredCollection != null &&
                typeof(INotifyPropertyChanged).IsAssignableFrom(filteredCollection.Type)))
            {
                return null;
            }
            DataGridBoundColumn column = 
                dataGrid.Columns[columnIndex] as DataGridBoundColumn;
            string bindingPath = null;
            if (column == null || column.Binding == null)
            {
                DataGridTemplateColumn templateColumn =
                    dataGrid.Columns[columnIndex] as DataGridTemplateColumn;
                if (templateColumn != null)
                {
                    //this might not always be the case
                    string header = templateColumn.Header as string;
                    if (header == null)
                    {
                        return null;
                    }
                    bindingPath = header;
                }
                else
                {
                    return null;
                }
            }
            else
            {
                bindingPath = column.Binding.Path.Path;
            }

            if (bindingPath.Contains(".") || string.IsNullOrEmpty(bindingPath))
            {
                return null;
            }
            …

Here are the steps to be done in order to get your DataGrid ready for filtering:

  • Define the FiltersViewInitializersManager if you want to have control on the initializers list
  • Define a column header template containing DataGridColumnFilter (bind the InitalizersManager property to the initializers manager if it has been defined at step no 1)
  • Set the grid ColumnHeaderStyle property to the be the style defined at step no 2

As I have mentioned there is a small catch to INotiftyPropertyChanged. Say we have a class Data that exposes a property called Value. Every time this property changes the PropertyChanged event is raised. Here is the scenario that interests us in particular. Say we apply a filter on this property. When the value falls outside the filter range the item is being removed from the grid; as it is being removed the grid will unregister the handlers for the event handlers while we are in the process of invoking the delegation list. The Silverlight code creates a WeakReferencePropertyListener object for every handler registered (use Reflector to search for it); because the grid, upon item remove clears its handlers if we just call the event it will end up as an exception on the first line of WeakReferencePropertyListener.PropertyChangedCallback. Because of this here is an approach on how to raise your event for the INotifyPropertyChanged elements:

       public double Value
        {
            get { return _value; }
            set
            {
                if (_value != value)
                {
                    _value = value;

                    OnPropertyChanged(args);
                }
            }
        }

       private void OnPropertyChanged(PropertyChangedEventArgs args)
        {
            Delegate[] delegates = _propertyChanged.GetInvocationList();
            foreach (Delegate del in delegates)
            {
                if (_propertyChanged.GetInvocationList().Contains(del))
                {
                    del.DynamicInvoke(this, args);
                }
            }
        }

Using the Code

The first thing you have to do to get this working is defining the ContentTemplate for the DataGridColumnHeader. Within the source code you can find an example, here is how the template looks:

xmlns:primitives =
    "clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Data"
xmlns:stepi="clr-namespace:Stepi.UIFilters;assembly=Stepi.UIFilters"
…
    <Style TargetType="primitives:DataGridColumnHeader" x:Key="columnHeaderStyle" >
        <Setter Property="ContentTemplate">

            <Setter.Value>
                <DataTemplate>
                    <Grid Height="{TemplateBinding Height}" Width="Auto">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>

                        <StackPanel Orientation="Horizontal" Margin="2"
                            HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                        <TextBlock Text="{Binding}" HorizontalAlignment="Center"
                            VerticalAlignment="Center" Margin="0.2"/>

                        <stepi:DataGridColumnFilter Grid.Row="1" 
                            Background="Transparent"
                            Width="Auto" Height="Auto"
                            Margin="1"
                            HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
                        </StackPanel>
                    </Grid>
                </DataTemplate>

            </Setter.Value>
        </Setter>
    </Style>

If you want to control the list of IFilterViewInitializer available you can define a local resource and then bind the InitalizersManager property of the DataGridColumnFilter to it.

<stepi:FilterViewInitializersManager x:Key="filtersViewInitializersManager"/>

The last step is to set up the DataGrid column header style to be the one defined above:

<data:DataGrid  x:Name="dg" ColumnHeaderStyle="{StaticResource columnHeaderStyle}">

That is pretty much it, hopefully this might come handy. On a next entry I will get the same functionality ready for Microsoft WPF DataGrid.

History

  • 15-Jan 2009 - Version 1.0.0

License

This article, along with any associated source code and files, is licensed under The Eclipse Public License 1.0

Share

About the Author

Stefan Bocutiu
Software Developer (Senior) Lab49
United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
QuestionAmazing code PinmemberMarekMalczewski7-May-14 22:18 
QuestionWorking with PagedCollectionView PinmemberRazputin25-Oct-11 22:04 
AnswerRe: Working with PagedCollectionView [modified] PinmemberMrPC196914-Nov-11 23:39 
GeneralThrowing Null ref exception for new row once sorted. Pinmemberrakeshchandra28-Apr-11 2:40 
GeneralUnable to use this project in my own project. PinmemberVIJAY JOSHI2-Mar-11 20:44 
GeneralNeed Help editing Filtered columns. Please dont tell me this artical is dead. PinmemberShady14u27-Jan-11 4:33 
GeneralRe: Need Help editing Filtered columns. Please dont tell me this artical is dead. PinmemberACE_0125-May-11 4:08 
GeneralFilters for Template Columns PinmemberShady14u7-Jan-11 15:00 
GeneralRe: Filters for Template Columns PinmemberACE_0125-May-11 4:03 
GeneralRe: Filters for Template Columns PinmemberRazputin24-Oct-11 19:58 
GeneralHandling nullable types for range/lessthan/greaterthan PinmemberTobyKraft2-Nov-10 8:45 
GeneralMethod to clear all filters PinmemberTobyKraft2-Nov-10 7:35 
GeneralValues in MultiValueFilterView are not sorted - duh! PinmemberTobyKraft18-Oct-10 11:05 
QuestionSupport for PagedCollectionView data source? [modified] PinmemberTobyKraft5-Oct-10 10:50 
AnswerRe: Support for PagedCollectionView data source? Pinmemberkmkuntz6-Apr-11 2:31 
GeneralRe: Support for PagedCollectionView data source? PinmemberMrPC196915-Nov-11 4:30 
GeneralTheme Apply Pinmembermannkaraja16-Jul-10 20:47 
QuestionIs this Grid support paging? Pinmemberrajesh_t2-Jul-10 4:01 
QuestionFilterCollectionView can only work with IList as parameter [modified] Pinmemberskliamis3-Jun-10 4:19 
GeneralProgrammatically add or remove filters PinmemberMember 471513330-Apr-10 7:07 
GeneralRe: Programmatically add or remove filters PinmemberTobyKraft20-Oct-10 12:01 
GeneralRe: Programmatically add or remove filters PinmemberShady14u6-Jan-11 11:31 
QuestionUnusual Behavior in Silverlight V3 DataGrid PinmemberRichard Cook29-Apr-10 10:12 
AnswerRe: Unusual Behavior in Silverlight V3 DataGrid Pinmembernakatayo9-Jun-10 15:33 
QuestionQuery| PinmemberAnonymousCoder11-Mar-10 2:24 
AnswerRe: Query| Pinmemberrchandraa6-Apr-10 1:21 
AnswerRe: Query| PinmemberStefan Bocutiu19-Apr-10 0:06 
GeneralRe: Query| PinmemberAnonymousCoder20-Apr-10 5:15 
GeneralRe: Query| Pinmemberrchandraa23-Apr-10 4:30 
GeneralRe: Query| PinmemberAnonymousCoder27-Apr-10 0:25 
QuestionRe: Query| PinmemberTobyKraft18-Oct-10 7:15 
AnswerRe: Query| [modified] PinmemberTobyKraft18-Oct-10 9:11 
QuestionException when click in a datagrid cell. Pinmembermiguel.luna14-Feb-10 21:28 
AnswerRe: Exception when click in a datagrid cell. PinmemberAnonymousCoder27-Apr-10 0:26 
GeneralRe: Exception when click in a datagrid cell. Pinmemberaris1971-200826-Sep-10 11:41 
AnswerRe: Exception when click in a datagrid cell. PinmemberTobyKraft20-Oct-10 10:33 
AnswerRe: Exception when click in a datagrid cell. PinmemberTobyKraft20-Oct-10 10:45 
GeneralRudimentary "is filter enabled" indicator [modified] PinmemberTobyKraft8-Feb-10 11:29 
QuestionExtending the grid for better usability ... PinmemberCSharpian20-Jan-10 19:19 
QuestionError with Ria services Pinmemberneozack10-Jan-10 9:20 
AnswerRe: Error with Ria services PinmemberShady14u6-Jan-11 11:41 
QuestionAny chances on using this in SL 4 & RIA Services? PinmemberJCKodel12-Dec-09 10:52 
GeneralNeed to show the filters... PinmemberVenkatesan Jagadisan1-Dec-09 22:18 
GeneralRe: Need to show the filters... PinmemberMember 37017893-Dec-09 21:15 
QuestionHow did you make the drop downs? PinmemberVertigoHopes8-Sep-09 12:54 
GeneralRefresh PinmemberMember 38631029-Jun-09 23:07 
GeneralDataBinding Problem With Async Call PinmemberMember 38631029-Jun-09 6:15 
GeneralJust what I was looking for PinmemberStuart Campbell3-Mar-09 16:15 
GeneralVery usefult article PinmemberSathish Raja S19-Feb-09 23:32 
GeneralProblem with Stepi Filter and TreeView ( Dynamic Filter) Pinmembercray12345616-Feb-09 5:05 

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
Web02 | 2.8.140902.1 | Last Updated 16 Jan 2009
Article Copyright 2009 by Stefan Bocutiu
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid