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 is also 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.