Filtering a list of items with the PropertyFilterGroupView control

I have been working on a new component that allows you to filter any list of items shown in a WPF user interface.  My new PropertyFilterGroupView is definitely still a work-in-progress, so please do not drop this control into a production WPF application just yet!  If you are interested in this, please leave me a comment with constructive criticism, feature requests, bug reports, etc.  I’m interested to see what people have to say about it.  The source code is available at the end of this blog post.

Binding to a collection of objects really means binding to an ICollectionView implementation that wraps the underlying collection.  One property exposed by ICollectionView is Filter, of type Predicate<object>.  When a filter is applied, all controls bound to the view will display only the items that passed through the filter, or were “filtered in.”  PropertyFilterGroupView is a very fancy and elaborate way to assign the Filter property of an ICollectionView.

Below is a screenshot of the demo application when it first loads up.  The control in the Expander up top is the PropertyFilterGroupView control.  The ListBox and ListView seen beneath it are bound to the ICollectionView that the PropertyFilterGroupView affects.  When the app first loads up all items in both lists are visible, as seen below:

After applying a few filters, the two lists display a subset of the original set of items.  The items still seen in the lists are the ones that satisfy all of the filters applied.  The modified UI looks like this:

In this demo app, the list of items shown in the UI is a collection of Person objects.  The Person class exposes some properties, as seen below:

Now that you know what shape the data objects have, let’s see how to configure the PropertyFilterGroupView control so that it allows the user to filter a list of Person objects.  The following XAML is from the Window1.xaml file in the demo project:

Each property on the Person object that the user should be able to filter on has a PropertyFilter object added to the filter group.  PropertyFilter is a class I made which represents a filter that determines whether a certain property on an object satisfies a criterion.  The user can select the filter’s criterion via a ComboBox in the PropertyFilterViewGroup control.

When you create a PropertyFilter, you must set the PropertyName and PropertyType properties to valid values.  If you do not set them, or set them to invalid values, an exception will be thrown when that filter is used.  The PropertyName property allows you to indicate which property to filter on, and PropertyType must be set to the data type of that property.  If you want the property’s name to be more user-friendly, set the DisplayName property to a more readable name.

Look at the last PropertyFilter object again, which filters on the MaritalStatusID property.  That filter has two additional properties set on it: PropertyConverter and DisplayType.  The former is set to the IValueConverter used by the ListView to translate an integer to a value from the MaritalStatus enum.  The ListView uses that converter in this column:

The PropertyFilter that filters the MaritalStatusID property needs access to that value converter so that it, too, can transform the integer value into the corresponding enum value.  Without that in place, the user would not be able to filter on the text in the Marital Status column, but only the underlying numeric identifiers.

The DisplayType property is set on that PropertyFilter so that it can intelligently decide what the available filtering criteria are for that property, such as ‘Starts With’ and ‘Is Less Than’.  The list of filtering criteria for, say, a numeric type is not the same as for text, which is not the same for Booleans.  To ensure that the filtering criteria makes sense for a property whose value is passed through a value converter, you must indicate what type the output value should be treated as for filtering purposes.  I chose String as the DisplayType for the MaritalStatusID filter, because enum values are easy to compare via their textual representations.

The PropertyFilterGroupView control also has its CollectionView property set.  It is bound to the inherited DataContext. To ensure that this property is able to pick up a reference to a CollectionView, I created a CollectionViewSource as the Window’s DataContext.  That XAML is below:

As I mentioned before, this project is a work-in-progress.  There are some big pieces of the puzzle missing.  For example, I do not yet support filtering on properties of type DateTime.  However, with that said, I encourage you to try this component out and see what you think.  I’m open for suggestions…

Download the source code here: PropertyFiltering Source Code .  Be sure to change the file extension from .DOC to .ZIP and then decompress the file.

18 Responses to Filtering a list of items with the PropertyFilterGroupView control

  1. indyfromoz says:

    Hi Josh,

    As always, this is yet another gem from you! Your article comes at a time when I am working with filtering in a ListView, the timing is sweet. I have a question that I shall try my best to explain. It will be great to have a mode for the PropertyFilterGroupView which when used can automatically generate the PropertyFilters’ using the properties of the object that the collection consists of. I am specifically talking about a scenario where the objects are simple with only strings or integers and a option like “AutoGeneratePropertyFilters=true” will be enough to setup the control. Pardon me if this does not make sense, I am still amazed in awe looking at this powerful control.

    Thanks again for sharing this really useful control!

    Cheers,
    indyfromoz

  2. Mike Brown says:

    Indy,
    That solution would be relatively easy to implement using reflection. You basically iterate over the properties of the type of object in the collection and generate a corresponding PropertyFilter for each. For added excitement, you can use the System.ComponentModel.DisplayNameAttribute to designate that the LastName property should have a displayname of “Last Name”.

  3. Mike Brown says:

    Once again Josh…great job!

  4. indyfromoz says:

    Mike,

    After the post, I started poking around with reflection, wasn’t quite sure if using reflection would be the ideal/best way. Thanks very much for the pointer🙂

    Cheers,
    indyfromoz

  5. Josh Smith says:

    Indy,

    I agree with Mike. You can use reflection to find those properties. My implementation does not use reflection because it would not provide enough information, such as the display order, value converters, display type, etc.

    Mike,

    Thanks a lot!

    /josh

  6. Karl Shifflett says:

    Josh,

    Super idea and implementation. I’m thinking of several very cool applications and could very easily be greatly enhanced with your PropertyFiltering.

    Super!!!

    Cheers,

    k-dawg

  7. peteohanlon says:

    Josh – yet another gem. There are so many places this could be applied.

  8. Josh Smith says:

    Thanks Karl and Pete. I agree, this is one of those components that many apps can use.

  9. Chris says:

    Hey Josh,
    Are you planning to consider conditions, for example, where you want to filter WHERE age > 20 OR marital status = married (emphasis on the OR)? It seems like this sort of filtering would only work where all conditions are TRUE. I realize that would raise the complexity of the control. I guess I am wondering if the filtering mechanism can handle boolean operators or not. I really enjoy your blog BTW🙂 …
    Chris

  10. Josh Smith says:

    Chris,

    Yeah, I’ve thought about that. I’ve also thought about adding multiple conditions to a single property. As you pointed out, this drastically increases the complexity of the component. I need to be thoroughly convinced that it’s important before I’ll be willing to spend the time…

    Josh

  11. klauswiesel says:

    Hi Josh

    As a definite beginner in WPF I am really happy for all shortcuts I find to create a nice user interface. So I downloaded your property filter control and I got it running in my environment.

    Now I have one problem, maybe you can point me in the right direction:
    I use the new ctp datagrid from MS (mainly to avoid another third party component) for displaying my business objects. I get a LIST(of Blabla) and bind it through code

    Dim oSrc As New CollectionViewSource
    oSrc.Source = mltModule
    DataContext = oSrc.View

    Here is my Xaml snippet of the grid:

    They show up and I can filter – perfect
    But: Some column props are not set up the way I want, and the most examples are showing doing this stuff in xaml. But in my case, I have to use the Autogenerate-Property of the grid, because after changing to this Xaml

    the grid remains empty. Maybe it has something to do with the CollectionViewSource? But I have no idea

    Regards and thanks for your support
    Klaus

  12. klauswiesel says:

    Josh

    it seems your page did “reject” then xaml somehow ;-))

    Here is the xaml which works:
    dg:DataGrid x:Name=”dgModules”
    AutoGenerateColumns=”true”
    IsSynchronizedWithCurrentItem=”True”
    Background=”Transparent”
    RowHeaderWidth=”25″
    RowBackground=”White”
    AlternatingRowBackground=”LightBlue”
    AlternationCount=”2″
    ItemsSource=”{Binding}”

    and here the one which fails:
    dg:DataGrid

    dg:DataGrid x:Name=”dgModules”
    AutoGenerateColumns=”false”
    IsSynchronizedWithCurrentItem=”True”
    Background=”Transparent”
    RowHeaderWidth=”25″
    RowBackground=”White”
    AlternatingRowBackground=”LightBlue”
    AlternationCount=”2″

    dg:DataGridTextColumn DataFieldBinding=”{Binding ModuleName}”
    Header=”ModuleName”
    dg:DataGrid

    Regards and thanks for your support
    Klaus

  13. Josh Smith says:

    Klaus,

    Try removing the filtering component from your UI, and you should see that the DataGrid is still not displaying correctly. If so, I suggest you post your question on Microsoft’s WPF Forum for further assistance with their DataGrid control.

    Josh

  14. klauswiesel says:

    Josh

    there simply was a missing dg:DataGrid.Columns , too bad that xaml error messages are so clearly ;-))

    One suggestion: I plan to use your component for my business objects, but I need support for multiple languages. So I tried to set the display name for one property in xaml by using something like this:
    jas:PropertyFilter DisplayName=”{DynamicResource myPropName}”
    which ended in an error stating that DisplayName is no dependy property.
    So maybe it may be a good idea to be able to (dynamically) customize all labels, including the criteria display name (“contains”,…)
    Regards
    Klaus

  15. steph says:

    Hi Josh,
    Congratulation for your job !
    How use your control if you have a property (ex a string) on your object like :

    PropertyName=”SomeClassOrStructProperty.SomeStringPropertyUnder”

    Thank by advance !

  16. Josh Smith says:

    Hi Steph,

    I doubt my filtering component prototype supports that. You should create a data object that exposes all of the properties that can be filtered, instead of trying to filter on sub-object properties.

    josh

  17. Steph says:

    If you add this class :
    public class Test
    {
    public string A
    {
    get;
    set;
    }
    public int B
    {
    get;
    set;
    }
    }

    and this property to Person Class

    public Test UnderProperty
    {
    get;
    private set;
    }

    And modify this part of cose you can achieve this

    //PropertyDescriptor desc = TypeDescriptor.CreateProperty(dataItem.GetType(), this.PropertyName, this.PropertyType);
    //if (desc == null)
    // return false;
    //object propertyValue = desc.GetValue(dataItem);

    object propertyValue = GetNestedPropertyValue(dataItem, this.PropertyName);

    private object GetNestedPropertyValue(object currentObject, string nestedPropertyPath)
    {
    string[] nestedProperties = null;
    object propertyValue = null;

    if (!string.IsNullOrEmpty(nestedPropertyPath))
    {
    nestedProperties = nestedPropertyPath.Split(‘.’);
    Type t = currentObject.GetType();

    PropertyDescriptor desc = TypeDescriptor.CreateProperty(t, nestedProperties[0], t.GetProperty(nestedProperties[0]).PropertyType);
    if (desc == null)
    return null;

    propertyValue = desc.GetValue(currentObject);

    if (nestedProperties.Length > 1)
    {
    string nextsNestedProperties = nestedPropertyPath.Replace(string.Format(“{0}.”, nestedProperties[0]), “”);

    propertyValue = GetNestedPropertyValue(propertyValue, nextsNestedProperties);
    }
    }

    return propertyValue;
    }

    So you can add property filter like that

    Probably a best way to do that but i tired and i’m going to counting sheep in my bed !!

    Congratulations again Josh !

  18. Klaus says:

    Just brilliant. I am currently looking into adding DateTime support.

%d bloggers like this: