GridControl tutorial part11: Data filtering
Data filtering actually controls visibility of individual grid rows basing on rules set by the programmer. These rules should be convenient and easy to use, should not depend on data sorting or grouping and should be equally applied to static and real-time data. Data filtering should not depend on hierarchy level or on data being expanded or collapsed by parent rows.
Filtering methods
The grid provides three complementary methods of data filtering.
- Row.Filtered - the easiest method of displaying or hiding data. However, it is doesn't support even-driven data model and therefore it is not useful for more complex applications. With this method the programmer has to control row visibility manually and to consider that data can be sorted or grouped and may have complex hierarchical structure.
-
IFilter interface - the most efficient and convenient filtering method. The programmer has to inherit IFilter interface and to implement IsFiltered method. This method uses row as argument for which the grid determines whether filtering is required.
This method is called in the following cases:
- Column filters
Programmatic filter
C# |
---|
class Quote : INotifyPropertyChanged
{
private readonly int _quantity;
private double _price;
public Quote(int quantity, double price)
{
_quantity = quantity;
_price = price;
}
public int Quantity { get { return _quantity; } }
public double Price
{
get { return _price; }
set
{
if (_price != value)
{
_price = value;
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Price"));
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
grid.Filter = new Filter(delegate(Row row)
{
double price = (double) row["Price"].Value;
return price < 1000;
});
List<Quote> collection = new List<Quote>();
for (int i = 0; i < 5; ++i)
{
collection.Add(new Quote(i, 1000 + 100 * i));
}
grid.ItemsSource = collection;
collection[4].Price = 900;
|
Column filters
Column filters provide user with visual controls to
manage row filtering. Dapfor.Wpf.dll library provides a set of filters that can
be used in most projects. These filters are provided in form of DataTemplate elements stored in Filters.FilterFactory
Developers can set filters with xaml code:
XAML |
<Window x:Class="SomeApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
xmlns:my="clr-namespace:Dapfor.Wpf.Controls;assembly=Dapfor.Wpf"
xmlns:filters="clr-namespace:Dapfor.Wpf.Filters;assembly=Dapfor.Wpf">
<my:GridControl>
<my:GridControl.Headers>
<my:Header>
<my:Header.Columns>
<my:Column Id="Status" Title="Status" FilterTemplate="{x:Static filters:FilterFactory.CheckBoxFilter}" FilterAlignment="Stretch"/>
<my:Column Id="Established" Title="Established" FilterTemplate="{x:Static filters:FilterFactory.DateMultiFilter}"/>
</my:Header.Columns>
</my:Header>
</my:GridControl.Headers>
</my:GridControl>
</Window> |
It can also be done programmatically:
C# |
Column column = gridControl1.Headers[0]["Established"];
column.FilterTemplate = FilterFactory.WildcardFilter; |
When a filter template is set for a column, the grid automatically displays
filter icon in its right part. When a user clicks the icon, the grid
automatically opens a popup window and places the specified template to this
window. The grid provides Column object to the template
for data content. You can set the initial popup position withColumn.FilterAlignment property.
Column also provides the Column.CloseFilter() method to hide a
control when it is no longer necessary. Filter is set when the Column.Filter property is called and the IFilter object is transferred
to this property. The grid verifies filtering conditions for each row and hides
or shows it as necessary. Users may change filtering conditions many times, and
a new object has to be created for every such change IFilter. If you need to
store filter values between calls, you can use the Column.Tag property or take the values from a
previously installed filter.
Custom column filters
Now we will show you how to create a custom filter for Double type data.
First of all, we create a CustomControl that will be displayed in a popup
window:
XAML |
<UserControl x:Class="Dapfor.Wpf.Grid.Demo.Examples.Basics.CustomFilters.SalesFilter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
x:Name="ThisControl">
<Border BorderThickness=".5">
<Slider x:Name="Slider" Height="23" HorizontalAlignment="Stretch" Value="100" Maximum="100" />
</Border>
</UserControl> |
To transfer a column to the control in xaml code, we add a DependencyProperty
of the Column type:
C# |
public partial class SalesFilter : UserControl
{
public SalesFilter()
{
InitializeComponent();
}
#region ColumnProperty
public static readonly DependencyProperty ColumnProperty = DependencyProperty.Register("Column",
typeof (Column),
typeof (SalesFilter),
new FrameworkPropertyMetadata(null, OnChangeColumn));
private static void OnChangeColumn(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public Column Column
{
get { return (Column) GetValue(ColumnProperty); }
set { SetValue(ColumnProperty, value); }
}
#endregion
} |
After that we can use binding to set control background and foreground so
that it does not differ too much from the current grid style. We can also add an
event handler for the slider value change event. Full xaml code of our control
now may look as follows:
XAML |
<usercontrol x:class="Dapfor.Wpf.Grid.Demo.Examples.Basics.CustomFilters.SalesFilter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:ignorable="d" x:name="ThisControl">
<Border Background="{Binding ElementName=ThisControl, Path=Column.Header.Grid.Background}"
BorderBrush="{Binding ElementName=ThisControl, Path=Column.Header.Grid.AppearanceColumnBackground}"
BorderThickness=".5">
<Slider x:Name="Slider" Height="23" HorizontalAlignment="Stretch" Value="100" Maximum="100" ValueChanged="OnSliderValueChanged" />
</Border>
</usercontrol>
|
Sorting conditions change when the slider position changes. We will create
rules displaying only rows with values not exceeding the slider value.
C# |
private class FilterRules : IFilter
{
private readonly Column _column;
private readonly double _value;
public FilterRules(Column column, double value)
{
_column = column;
_value = value;
}
public bool IsFiltered(Row row)
{
var cell = row[_column.Id];
var v = cell != null ? cell.Value : null;
return (v as double?) > _value;
}
}
public partial class SalesFilter : UserControl
{
...
private void OnSliderValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
Column.Filter = new FilterRules(Column, e.NewValue);
}
} |
It would be great to hide the control when user presses the Esc key. We will
add a corresponding event handler and use the Column.CloseFilter() method.
C# |
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (e.Key == Key.Escape)
{
Column.CloseFilter();
}
} |
We also need to preserve the current slider value for the case if user
suddenly closes the control. We can do it by saving values in Column.Tag. Finally we will add the
_suspendUpdates variable so that we don't have to recreate the IFilter object on control initialization. The full code of our control will now look as
follows:
C# |
public partial class SalesFilter : UserControl
{
private bool _suspendUpdates;
public SalesFilter()
{
InitializeComponent();
}
private void OnSliderValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (!_suspendUpdates && Column != null)
{
Column.Tag = e.NewValue;
Column.Filter = new FilterRules(Column, e.NewValue);
}
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (e.Key == Key.Escape)
{
Column.CloseFilter();
}
}
#region ColumnProperty
public static readonly DependencyProperty ColumnProperty = DependencyProperty.Register("Column", typeof (Column), typeof (SalesFilter), new FrameworkPropertyMetadata(null, OnChangeColumn));
private static void OnChangeColumn(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (SalesFilter) d;
if (control.Column != null && control.Column.Tag is double)
{
control._suspendUpdates = true;
control.Slider.Value = (double) control.Column.Tag;
control._suspendUpdates = false;
}
}
public Column Column
{
get { return (Column) GetValue(ColumnProperty); }
set { SetValue(ColumnProperty, value); }
}
#endregion
} |
Setting our slider in grid column is quite easy. To do it, we will create
DataTemplate resource and put our control inside it.
Don't forget to transfer the produced column to the control:
XAML |
<UserControl x:Class="SomeControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d">
<UserControl.Resources>
<DataTemplate x:Key="SalesFilter">
<customFilters:SalesFilter Column="{Binding}"/>
</DataTemplate>
</UserControl.Resources>
</UserControl> |
Finally we have to transfer our template to grid column:
XAML |
<my:GridControl>
<my:GridControl.Headers>
<my:Header>
<my:Header.Columns>
<my:Column Id="sales" Title="Sales" FilterTemplate="{StaticResource SalesFilter}"/>
</my:Header.Columns>
</my:Header>
</my:GridControl.Headers>
</my:GridControl> |
Back to Wpf GridControl tutorial