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

Automatic WPF Toolkit DataGrid Filtering

By , 21 May 2010
 

Automated content filtering for the WPF Toolkit DataGrid - demo application

Introduction

The article discusses a component that enables automated content filtering for the DataGrid (WPF Toolkit DataGrid or System.Windows.Controls.DataGrid). The inspiration was the article "DataGrid with built-in filter functionality" (for Windows Forms) that was also published on CodeProject.

A specific characteristic of this component is that it is not an inherited DataGrid control, but instead a new style is made for the DataGrid's header. That makes future upgrades easier, and increases compatibility with the DataGrid control.

Using this component is extremely simple, you only need to set a new style for the DataGrid header: ColumnHeaderStyle.

With the release of .NET Framework 4.0, DataGrid has become part of the framework, so there also is a Visual Studio 2010 solution without WPFToolkit.dll.

Background

The component uses the LINQ Dynamic Query Library to build up the query string for DataGrid filtering. You should be familiar with the WPF concept of binding, templates, and styles.

Using the Code

Use of the component should be easy. You first have to add a reference to the DataGrid component and for the DataGridFilterLibrary project (or DLL if you wish). For .NET 4.0, add a "PresentationFramework.dll" reference, and for .NET 3.5, add a "WPFToolkit.dll" reference.

Then, in the XAML code where you have the DataGrid, you have to add the xmlns attribute to the root element of the markup file (namespaces wpftoolkit and filter).

Note that for a project that targets .NET 4.0, there is no need to add the WPF Toolkit reference.

<Window x:Class="DataGridFilterTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:wpftoolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
    xmlns:filter="clr-namespace:DataGridFilterLibrary;assembly=DataGridFilterLibrary"

Finally, you have to set a custom header style for the DataGrid:

<DataGrid ColumnHeaderStyle="{StaticResource {ComponentResourceKey 
           TypeInTargetAssembly={x:Type filter:DataGridHeaderFilterControl}, 
           ResourceId=DataGridHeaderFilterControlStyle}}"

Please note that if you want to use shared resources from a Custom Control Library, you must use the ComponentResourceKey markup extension to reference the resource in the DataGridFilterLibrary.

Using DataGridComboBoxColumn

The filter for distinct values (see the EmployeePosition type in the demo project) can be distinct, or the user can enter any text in the TextBox. In the latter case, the search is executed as a text search with the LIKE operator. This behaviour is adjusted using the attached property IsTextFilter.

Example

<DataGridComboBoxColumn Header="Position (List Filter)"
    filter:DataGridComboBoxExtensions.UserCanEnterText="True"
    SelectedItemBinding="{Binding Path=Position}"
    ItemsSource="{Binding Source={StaticResource EmployeeData}, Path=EmployeePositionList}" 
    SelectedValuePath="Id"
    DisplayMemberPath="Name">

The filter control for this DataGridComboBoxColumn is going to be a simple TextBox instead of the ComboBox with all-possible values for the employee position (see the column "Position - List Filter" in the demo project).

This is handy when the list has many items, e.g., 100 employee positions.

How Text Search Works

All text searches through the <textbox> are by default case insensitive searches (as the LIKE SQL clause). The filter control also supports wildcard character % operators.

Configuration Options

Configuration is implemented through attached properties. See the following classes: DataGridColumnExtensions, DataGridComboBoxExtensions, and DataGridExtensions.

UserCanEnterText

  • When applied to the combobox column, the filter combobox is fully editable, i.e., the user can type part of the word and the combobox automatically selects the first appropriate item (like System.Windows.Controls.TextSearch).
  • Default is false.
  • For example, see Position (Text Filter) column.

Example

<DataGridComboBoxColumn
    Header="Position (List Filter)"
    filter:DataGridComboBoxExtensions.UserCanEnterText="True"
    <!-- The rest was left out to keep it more simple -->

IsCaseSensitiveSearch

  • When applied to the text column, search is case sensitive.
  • Default is false.
  • For example, see Email (Case Sensitive Search) column.

Example

<DataGridTextColumn Binding="{Binding Path=Email}"
    filter:DataGridColumnExtensions.IsCaseSensitiveSearch="True"
    Header="Email (Case Sensitive Search)"/>

IsBetweenFilterControl

  • For numeric and date columns, there is the option to filter between, i.e., that gives you the ability to specify a range in your search.
  • Default is false.
  • For example, see the Work Experience (Between) and Date Of Birth (Between) columns.

Example

<DataGridTextColumn Header="Work Experience (Between)" 
    filter:DataGridColumnExtensions.IsBetweenFilterControl="True"
    Binding="{Binding Path=WorkExperience}"/>

UseBackgroundWorkerForFiltering

  • When applied to the datagrid, searches are performed on a separate thread using the BackgroundWorker.
  • Default is false.
  • For example, see the grid on the second tab item (myGrid2).

Example

<DataGrid filter:DataGridExtensions.UseBackgroundWorkerForFiltering="True" 
    <!-- The rest was left out to keep it more simple -->

DoNotGenerateFilterControl

  • Gives the possibility to switch off the filter for a single column (useful for columns that do not need a filter, e.g., button columns).
  • For example, see the first grid where the filter for the column "Id" is hidden.

Example

<DataGridTextColumn filter:DataGridColumnExtensions.DoNotGenerateFilterControl="True" 
    <!-- The rest was left out to keep it more simple -->

IsClearButtonVisible

  • Shows/hides the "Clear filter" button.
  • Default is true.
  • For example, see the first grid where the clear filter button is hidden.

Example

<DataGrid 
    filter:DataGridExtensions.IsClearButtonVisible="False"
    <!-- The rest was left out to keep it more simple -->

Control Commands

IsFilterVisible

It is not really a command, but an attached property. The property controls the visibility of the filter. It can be bound to a toolbar item. For example, see the "Show/Hide Filter (bind to attached property)" toolbar item.

<CheckBox IsChecked="{Binding Path=(filter:DataGridExtensions.IsFilterVisible),
                              ElementName=myGrid1}">
    Show/Hide Filter (bind to attached property)
</CheckBox/>

ClearFilterCommand

Clears the filter content and resets the filter. For example, see the "Clear Filter" toolbar item.

<Button Command="{Binding Path=(filter:DataGridExtensions.ClearFilterCommand), 
                          ElementName=myGrid1}">
    Clear Filter
</Button>

Please note that in the above case, we are binding to the attached properties. Syntax for this is in the form: Path=(namespace prefix:class name.property name, surrounded by parentheses), e.g.:

Path=(filter:DataGridExtensions.ClearFilterCommand)

Notes

The filter library internally uses (also indicated in the code):

  • Delay Textbox
  • (My WPF port of the Windows Forms version: http://www.codeproject.com/KB/miscctrl/CustomTextBox.aspx) - for smooth and responsive performance, thus simulating a kind of incremental search. For example, if the user types letters "A-B-C" in intervals less than 250 milliseconds, a search for an ABC text is performed, not 3 searches for A, AB, ABC.

  • EnumDisplayer
  • For better handling of enum values of the filter (http://www.ageektrapped.com/blog/the-missing-net-7-displaying-enums-in-wpf)

  • DataGridComboBoxColumn vs. DataGridComboBoxColumnWithBindingHack
  • To show the data in the combo box, the previous version used the DataGridComboBoxColumnWithBindingHack class. The solution for the modified combo column can be found here.

    Now I am using ObjectDataProvider as a binding source, so the ComboBox.ItemsSource binds to the EmployeePositionList through a StaticResource. For more information, see: WPF DataGrid – Dynamically updating DataGridComboBoxColumn. For this reason, the DataGridComboBoxColumn class is used instead of DataGridComboBoxColumnWithBindingHack.

    The example usage (both) is shown in the XAML code of the test project.

  • Start inserting Employees with new position
  • When pressed, every second, an employee (also with a new Position, i.e., Position 1, Position 2, Position 3 ...) is inserted in the EmployeeList list. If DataGrid.ItemsSource is an observable collection (source implements INotifyCollectionChanged) and a filter is set, the filter detects a collection change and then re-executes the filtering. Try and set the letter "p" in the filter for the "Position (Text Filter)" column and then press the button.

History

19-05-2010

  • Added DoNotGenerateFilterControl configuration options (see chapter: Configuration options).
  • Filter detects collection change (only if source implements INotifyCollectionChanged) and then re-executes the filtering.
  • (Some) Bug fixes - all bugs were found, thanks to the posted messages :).

10-01-2009

  • Thanks to Bob Ranck and jsh_ec for their proposals and suggestions.
  • Added the between control for date and numeric fields (option).
  • Added various configuration options (see chapter: Configuration options).
  • Added a clear filter command and a property that controls filter visibility.
  • Now the filter adds a blank combobox item at the beginning of each list - quick and easy filter reset.
  • Internal code revision.

09-09-2009

  • This is the first version. There are places for improvements in the internal design and functionality of the code components. Suggestions and comments are welcome.

License

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

About the Author

smatusan
Software Developer Novatec (www.novatec.hr)
Croatia Croatia
Member
Sanjin Matusan - C#, WPF, SQL, .NET

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionBug or not?memberxam8re29 Dec '11 - 21:15 
That's grid work fine.
I've only a problem. If a column is filtered and the Itemssource is refreshed the datagrid start an infinite loop.
I have a Observable collection, i Clear all data and i Refill the collection the filter start an infinite loop Frown | :(
 
Another question, how i can send programmaticaly the "clear filter" command and save the filter information?
 

Thank you.
Massimiliano
AnswerRe: Bug or not?membersmatusan29 Dec '11 - 22:01 
Yes that was a bug.
 
Take the latest version from here: DataGridFilterLibrary_src_VS2010_Nested_Object_Filtering[^]. It should work.
 
To save the filter see the following post: Automatic WPF Toolkit DataGrid Filtering - Re: Getting filter controls search criteria[^]
 
Hope this helps.
QuestionHelp on filtering with nested object information.memberWorldly1330 Nov '11 - 15:12 
Hello,
 
I am using the datagrid filter and it works well, except if list of objects I have contain a nested object, which I display in the datagrid. For example I have a Company object that has CompanyName, Address, YearsInService and EmployeeObj. The EmployeeObj has Name, Age, Title.
 
In my xaml I have the following, which works fine, I can filter accordingly:
 
<<DataGridTextColumn Header="Years In Service "
filter:DataGridColumnExtensions.IsBetweenFilterControl="True"
Binding="{Binding Path=YearsInService }"/>
 

I also have in my xaml the following, which cause the filter to display a textbox above the column (doesn't seem to identity the int/double value so no <, <=, etc is shown):
 
<<DataGridTextColumn Header="Employee Age"
filter:DataGridColumnExtensions.IsBetweenFilterControl="True"
Binding="{Binding Path=EmployeeObj.Age"/>
 

I thought the issue was that maybe the nested object was being seen as a string, so I added a converter "{Binding Path=EmployeeObj.Age, Converter={StaticResource StringToIntConverter}}" but no luck.
 
Any help would be appreciated. I feel that it may have to do with the Collection not being able to sort/filter nested objects?
 
Thank you in advance,
 
Mike
AnswerRe: Help on filtering with nested object information.membersmatusan3 Dec '11 - 7:19 
Hi,
 
it should work ...
 
Look in the following demo on this link: DataGridFilterLibrary_src_VS2010_Nested_Object_Filtering.zip
 
I added Age property on Position object that is nested object of Employee. I think that this is similar situation as yours.
 
Look for the column "Position Age" on first tab.
 
<DataGridTextColumn Header="Position Age" Binding="{Binding Path=Position.Age}"
filter:DataGridColumnExtensions.IsBetweenFilterControl="True" />  
Hope this helps.
 
Regards
GeneralRe: Help on filtering with nested object information.memberWorldly1313 Jan '12 - 14:17 
Thank you for the response/project/support. It is much appreciated!
QuestionWorking with data virtualization [modified]membercatson12331 Oct '11 - 16:48 
First thanks for this great piece of article, it really helps me a lot.
 

I have been using Bea's data virtualization on my datagrid, http://bea.stollnitz.com/blog/?p=378[^]
and the item source of the datagrid is binded to Data Wrapper containing the "virtualizated" data.
 
A problem I have encounter when filtering, is that after the query filtered items from datagrid, the grid only shows pageSize items, and the scrolling stopped to fetch new item in other pages.
 
I checked the QueryController.applayFilter, and the result seems to correctly filtered the complete data source. Originally the fetching process is done by checking the current index of data wrapper, so I think the problem is the filter created a view for datagrid, in turn stopped the data wrapper from working.
 

I have read the past discussion about paging and virtualization but seems they are different from my concern,
any hints where I could start work on?
 
Thanks in advance!

modified 31 Oct '11 - 23:59.

AnswerRe: Working with data virtualizationmembersmatusan2 Nov '11 - 12:04 
Hm, I think this is a tough one to solve.
 
Well, the collection is filtered based on the collection view (applayFilter: ICollectionView view = CollectionViewSource.GetDefaultView(ItemsSource) ). The problem is that when filter is applied, async virtual collection contains only small percent of the items (of course that is its normal and desired behavior – for async collection).
 
Filter gets collectionview from the grid itemssource. This view contains current small set of the items. Than filter is applied according to that small set and items are displayed in the grid. Now fetching process ends because all data is fetched and displayed in the grid.
 
I looked around the web for the possible solution, and I think that one way can be to implement your ICollectionView for AsyncVirtualizingCollection and then to bind to it using the CollectionViewSource class.
 
I do not know how difficult it will be to implement your collection view.
 
See the following blog post for more info:http://blogs.microsoft.co.il/blogs/tomershamam/archive/2009/10/01/ui-virtualization-vs-data-virtualization-part-2.aspx[^]
 
Hope this helps.
GeneralRe: Working with data virtualization [modified]membercatson1232 Nov '11 - 16:03 
Thanks, now I see where the problem lies ,
 
when checking applayFilter I notice the view "does" contains full numbers of items, so assumed it does correctly retrieved the complete data source, but probably in this case only part of the data are actually loaded and the rest is empty in AsyncVirtualizingCollection.
 
I also tried to expose the complete set of data to applayFilter (i.e. CollectionViewSource.GetDefaultView(Complete List) but the filter then stopped working. However that won't be a good solution anyway, as even if it success the result view will lost data virtualization.
 
Implement ICollectionView for AsyncVirtualizingCollection would be a better idea, to filter the data before binding it to AsyncVirtualizingCollection and datagrid seems is the solution to my problem, so I may now give it a try or perform the filtering on data source/server side like Bea suggest in his blog
 
thanks Smile | :)

modified 2 Nov '11 - 23:17.

GeneralRe: Working with data virtualizationmembersmatusan2 Nov '11 - 21:41 
I think that would be more feasible to filter data on the server side, that would be my first choice.
 
Good luck in either case Smile | :)
QuestionGreat job! any idea how I can use the filter on paging grid?memberyh201124 Oct '11 - 23:27 
To clarify more:
I have Grid with too many rows to display, I'd like to make it paging.
Using the filter as is, it relies on the DataGrid's SourceItem, so it will filter only from current page.
 
Can you suggest a method to apply the filter on a wrapper of the DataGrid's sourceitem, which always contains all the population? (and not only of the current page)
 
Thanks very much!
yh

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 21 May 2010
Article Copyright 2009 by smatusan
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid