DataGrid Filtering in Depth: Silverlight and WPF

The ComponentOne Studio DataGrid for Silverlight and WPF provides advanced data visualization with a lot of bells and whistles when compared to the standard DataGrid control. One of those whistles is easy data filtering. The control has evolved over the past few years and so has its filtering features. The C1DataGrid supports several different filtering techniques and each is easy to implement and advanced in functionality. In this post I will describe the various filtering techniques or “flavors” if you will, and a show a quick example of each.

Default Filters

When you bind C1DataGrid to an IEnumerable data source and set CanUserFilter to True, you will get the default conditional filtering. Conditional filtering finds values based upon a condition set by the user, such as “Greater than 10” or “Contains R.” There are special filters provided depending on the column’s data type. For instance, DateTime and Boolean fields have more appropriate conditional filters.

Advanced Filters

C1DataGrid supports more advanced filter techniques through help of the auxiliary assembly, C1.Silverlight.DataGrid.Filters.dll (or C1.WPF.DataGrid.Filters.dll). This way, we augment the filtering flexibility without bloating the assembly size for those who do not need advanced filtering.

Filter Row

Another basic flavor of filtering is the classic filter row. A filter row is a fixed row consisting of text boxes that allows the user to enter filter criteria in any column. This is easily implemented in C1DataGrid through its TopRows or BottomRows collection. Simply add an element of type DataGridFilterRow (make sure you add a reference to the C1.Silverlight.DataGrid.Filters assembly), and set the frozen row count.

<c1:C1DataGrid Name="c1DataGrid1" CanUserFilter="True" FrozenTopRowsCount="1"
 RowHeight="30">
    <c1:C1DataGrid.TopRows>
        <c1:DataGridFilterRow />
    </c1:C1DataGrid.TopRows>
</c1:C1DataGrid>

Full Text Filter

A unique technique to C1DataGrid is its full text filtering for the entire grid. This enables the user to search and filter on all columns at once! You can simply bind a control, such as a TextBox, to the FullTextSearchBehavior attached property and the rest is handled for you. For example, the following markup binds a TextBox to search and filter across all columns on the grid without writing a single line of code:

<c1:C1DataGrid Name="c1DataGrid1" CanUserFilter="True" Margin="0,27,0,0">
    <c1:C1FullTextSearchBehavior.FullTextSearchBehavior>
        <c1:C1FullTextSearchBehavior Filter="{Binding ElementName=textBox1, Path=Text}" />
    </c1:C1FullTextSearchBehavior.FullTextSearchBehavior>
</c1:C1DataGrid>
<TextBox Height="23" HorizontalAlignment="Left" Name="textBox1" VerticalAlignment="Top" Width="177" />

Multi-Value Filter

A multi-value filter is also commonly known as Excel-like filtering. A checked list of possible values is displayed allowing the user to select which values they wish to filter out of view. This is a good option for columns where you have several unique and repeated values (like a category or type field). There are two ways you can implement multi-value filters. The first approach is to take advantage of C1DataGrid’s built-in advanced filter behavior feature. Simply add the C1AdvancedFiltersBehavior class to the grid. This adds a range of built-in advanced filters, including multi-value.

<c1:C1DataGrid Name="c1DataGrid1" AutoGenerateColumns="False" CanUserFilter="True" CanUserAddRows="False" RowHeight="30">
    <!--Enable Advanced Filters-->
    <c1:C1AdvancedFiltersBehavior.AdvancedFiltersBehavior>
        <c1:C1AdvancedFiltersBehavior />
    </c1:C1AdvancedFiltersBehavior.AdvancedFiltersBehavior>
</c1:C1DataGrid>

The C1AdvancedFiltersBehavior adds both a simple conditional as well as an advanced multi-value filter for each column. In this case you don’t have much control over which columns get this type of filter, but it’s the easiest way to turn on advanced filters for the entire grid. The second approach is more fine-tuned. With advanced filters off, you can explicitly set a specific filter type for each column in XAML or code. For example, to add a multi-value filter to one column it could be done with the following markup:

<c1:C1DataGrid Name="c1DataGrid1" ItemsSource="{Binding Flavors}" AutoGenerateColumns="False" CanUserFilter="True">
    <c1:C1DataGrid.Columns>
        <c1:DataGridTextColumn Header="Flavor" Binding="{Binding FlavorName}" FilterMemberPath="FlavorName">
            <!--Add Multi-Value Filter-->
            <c1:DataGridTextColumn.Filter>
                <c1:DataGridContentFilter >
                            <c1:DataGridMultiValueFilter ItemsSource="{Binding Flavors}" DisplayMemberPath="FlavorName" ValueMemberPath="FlavorName" MaxHeight="200"/>
                        </c1:DataGridContentFilter>
            </c1:DataGridTextColumn.Filter>
        </c1:DataGridTextColumn>
        <!--Other Columns-->
        <c1:DataGridCheckBoxColumn Header="Has Nuts" Binding="{Binding IncludesNuts}" FilterMemberPath="IncludesNuts" />
        <c1:DataGridTextColumn Header="Percent" Format="p1" Binding="{Binding Percent}" FilterMemberPath="Percent"/>
    </c1:C1DataGrid.Columns>
</c1:C1DataGrid>

Note that here we are specifying the ItemsSource for our multi-value filter.

Multi-Line Conditional Filters

A multi-line type of filter is an expansion to the default conditional filtering as it enables you to filter on more than one condition. In fact, the number of conditions is unlimited and can grow as the user chooses. Just like I showed with multi-value, you can customize the filter of any column by setting the Filter property in XAML. For example, to implement multiple conditional filters, add a C1MultiLineTextFilter to any Text column (Note: there are also multi-line filters for DateTime and Numeric columns too).

<c1:C1DataGrid Name="c1DataGrid1" AutoGenerateColumns="False" CanUserFilter="True">
    <c1:C1DataGrid.Columns>
        <c1:DataGridTextColumn Header="Flavor" Binding="{Binding FlavorName}" FilterMemberPath="FlavorName">
            <!--Add Multi-Line Filter-->
            <c1:DataGridTextColumn.Filter>
                <c1:DataGridContentFilter >
                        <c1:DataGridMultiLineTextFilter />
                </c1:DataGridContentFilter>
            </c1:DataGridTextColumn.Filter>
        </c1:DataGridTextColumn>
        <!—Add Other Columns -->
        ...
    </c1:C1DataGrid.Columns>
</c1:C1DataGrid>

Combining Filters

It’s common to make more than one filter technique available to users. C1DataGrid supports so many different types of filters, so why just limit ourselves to one per column? C1DataGrid resolves this by allowing you to explicitly define more than one filter using the DataGridFilterList class. For example, if you wanted to display both a conditional text and numeric filter, your markup could look like this:

<c1:C1DataGrid Name="c1DataGrid1" ItemsSource="{Binding Flavors}" AutoGenerateColumns="False" CanUserFilter="True">
    <c1:C1DataGrid.Columns>
        <c1:DataGridTextColumn Header="Flavor" Binding="{Binding FlavorName}" FilterMemberPath="FlavorName" />
        <c1:DataGridCheckBoxColumn Header="Has Nuts" Binding="{Binding IncludesNuts}" FilterMemberPath="IncludesNuts" />
        <c1:DataGridTextColumn Header="Percent" Format="p1" Binding="{Binding Percent}" FilterMemberPath="Percent">
                    <c1:DataGridTextColumn.Filter>
                        <c1:DataGridContentFilter >
                            <c1:DataGridFilterList>
                                <c1:DataGridTextFilter />
                                <c1:DataGridNumericFilter Format="p1" />
                            </c1:DataGridFilterList>
                        </c1:DataGridContentFilter>
                    </c1:DataGridTextColumn.Filter>
          </c1:DataGridTextColumn>
     </c1:C1DataGrid.Columns>
</c1:C1DataGrid>

Filtering Template Columns

Template Columns do not support the default filtering. But you can still customize a filter for by setting the Filter property. For example, here is how you can display a multi-value filter for a Template Column.

<c1:C1DataGrid Name="c1DataGrid1" ItemsSource="{Binding Flavors}" AutoGenerateColumns="False" CanUserFilter="True">
    <c1:C1DataGrid.Columns>
        <!--Add Template Column-->
        <c1:DataGridTemplateColumn Header="Flavor" FilterMemberPath="FlavorName">
            <c1:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding FlavorName}" />
                </DataTemplate>
            </c1:DataGridTemplateColumn.CellTemplate>
            <!--Add Multi-Value Filter-->
            <c1:DataGridTemplateColumn.Filter>
                <c1:DataGridContentFilter >
                    <c1:DataGridMultiValueFilter ItemsSource="{Binding Flavors}" MaxHeight="200" DisplayMemberPath="FlavorName" ValueMemberPath="FlavorName" />
                </c1:DataGridContentFilter>
            </c1:DataGridTemplateColumn.Filter>
        </c1:DataGridTemplateColumn>
        <!--Add Other Columns-->
...
    </c1:C1DataGrid.Columns>
</c1:C1DataGrid>

Working with Filters in Code

For those scenarios when you need full control over the filter in code, there are two key events to work in. FilterLoading fires as the filter is loaded for each column and the place where you can insert a custom filter programmatically. FilterOpened fires when the filter is opened at run-time and the place where you can customize settings dynamically. For example, here we assign a new multi-value filter programmatically.


private void dataGrid_FilterLoading(object sender, DataGridColumnEditableValueEventArgs e)  
{  
    var column = e.Column;  

    var multiValueFilter = new DataGridMultiValueFilter();  
    (e.Value as DataGridFilter).InnerControl = multiValueFilter;  
}  

Here we get the filter once it's opened and repopulate the multi-value list with some custom items.


void dataGrid_FilterOpened(object sender, DataGridColumnValueEventArgs e)  
{  
    var boundColumn = e.Column as DataGridBoundColumn;  
    if (boundColumn == null)  
        return;  

    // create and set multi-value filter control into the column  
    var filterControl = (DataGridMultiValueFilter)((DataGridFilter)e.Value).InnerControl;  
    filterControl.ItemsSource = items.Select(selector).Distinct().OrderBy(o => o);  
}  

If you're working with default filters or advanced default filters, you can access them at run-time within the FilterOpened event and apply dynamic changes. For instance if you wish to change the StartDate of a DateTime filter, or if you need to adjust the filter of a Multi-value list you can do that. The e.Column.Filter parameter will give you the default filter. The e.Value parameter will give you the advanced filter.


void c1DataGrid1_FilterOpened(object sender, C1.Silverlight.DataGrid.DataGridColumnValueEventArgs e)  
{  
    // e.Column.Filter gives you default column filter  
    DataGridDateTimeFilter dgdf = e.Column.Filter as DataGridDateTimeFilter;  
    if (dgdf != null)  
    {  
        dgdf.StartDate = new DateTime(2011, 1, 1);  
    }  

    // e.Value gives you advanced filter  
    DataGridContentFilter dgcf = e.Value as DataGridContentFilter;  
    if (dgcf != null)  
    {  
        DataGridFilterList dgfl = dgcf.Content as DataGridFilterList;  
        if (dgfl != null)  
        {  
            if (dgfl.Items.Count > 1)  
            {  
                DataGridMultiValueFilter dgmv = dgfl.Items[1] as DataGridMultiValueFilter;  
                // set filter  
                // dgmv.Filter =  
            }  
        }  
    }  
}  

Custom Filters

The filtering capabilities don’t stop here. You can even write your own custom filters and assign them to specific columns as demonstrated above. Depending on your data, it may make sense to have a tree view, histogram or calendar inside the filter drop-down. You can find some samples of along with the rest of C1DataGrid samples. The code to implement these is a bit too much for this blog post.

Tips

  • If you are explicitly defining your columns, make sure you set the FilterMemberPath property on each. Otherwise, filtering will not be available to the user.
  • If you need to customize the filtering for each column, do not use the C1AdvancedFiltersBehavior, as this class automatically listens to filter loading event and will not honor custom filters you explicitly define.
  • The default filtering does not work on Template Columns, however, you can still use advanced filtering as described above.

Learn more:

ComponentOne Product Manager Greg Lutz

Greg Lutz

comments powered by Disqus