XamGrid - Implementing Custom Column Filtering UI

XamGrid has tons of features to make it easier for you to show and manipulate your data. It provides two different UI patterns for filtering - FilterRow and Excel-like Filter Menu. But what if these two do not fit your needs?

In this post I'll show you how I created a custom Filtering UI. You can see the final result on the screenshot below:

You can find the code at the end of the post.

Since the whole implementation is hundreds of line, I won't be able to guide you through all steps and to explain every line. I'll focus on the parts where the filtering happens and the usage of the IG Commanding Framework.

Adding HeaderDropDown

The first step was to create a class ColumnSearchBoxControl that derives from Control. This control will be displayed in the popup of a HeaderDropDown control. The only template part will be the TextBox.

Then I opened the genric.xaml file of the XamGrid and copied the style of the HeaderCellControl and all brushes and styles that are used in it.

With that done I was ready to create the ControlTemplate of the ColumnSearchBoxControl and place it in the HeaderCellControl.

<Grid Grid.Column="2"
      x:Name="SortAndPinIndicators">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>

    ...
    <!-- Default XamGrid Indicators and menus -->
    ...

    <igPrim:HeaderDropDownControl Grid.Column="5">
        <igPrim:HeaderDropDownControl.OpenButtonContent>
            <Grid>
                ...
                <!-- Icon Displayed in the HeaderCellControl -->
                ...
            </Grid>
        </igPrim:HeaderDropDownControl.OpenButtonContent>

        <!-- The ColumnSearchBoxControl displayed in popup when the Icon in the header is clicked -->
        <local:ColumnSearchBoxControl ... >
        </local:ColumnSearchBoxControl>
    </igPrim:HeaderDropDownControl>
    ...

Control Implementation

Filtering

The filtering logic is in the ApplyFilter() method:

protected internal void ApplyFilter()
{
    if (!this._isDirty || this._dataType == null || this._searchTextBox == null)
    {
        return;
    }

    CellBase cell = this.Cell;
    string columnKey = cell.Column.Key;
    RowsManager rm = (RowsManager)cell.Row.Manager;
    RowsFilter rf = rm.RowFiltersCollectionResolved[columnKey];

    if (rf == null)
    {
        return;
    }

    rf.Conditions.Clear();

    if (!string.IsNullOrEmpty(this._searchTextBox.Text))
    {
        ComparisonCondition compCond = new ComparisonCondition
                                       {
                                           Operator = ComparisonOperator.Contains,
                                           FilterValue = this._searchTextBox.Text
                                       };

        rf.Conditions.Add(compCond);
    }

    this._isDirty = false;
}

Cell is a dependency property bound to the HeaderCell using this code in the control template of the HeaderCellControl:

<local:ColumnSearchBoxControl
    Cell="{Binding Path=Cell, RelativeSource={RelativeSource TemplatedParent}}" ... />

The goal is to get to the RowsFilter for the current ColumnLayout.

The rest of the code sets a filter using the Filtering API of the XamGrid. Note that when ApplyFilter() is invoked all conditions are cleared and if the text from the TextBox is just an empty string a filter won't be applied.

Maybe you are wondering where does the RowsFilter come from and what is _dataType and why the filter won't be applied when it's null. This all comes from the SetupControl method which is called in OnApplyTemplate.

protected virtual void SetupControl()
{
    RowsManager rm = this.Cell.Row.Manager as RowsManager;

    if (rm != null && rm.ItemsSource != null && this.Cell.Column != null &&
        this.Cell.Column.DataType == typeof(string))
    {
        string searchTerm = string.Empty;
        this._dataType = DataManagerBase.ResolveItemType(rm.ItemsSource);

        RowsFilter rf = rm.RowFiltersCollectionResolved[this.Cell.Column.Key];

        if (rf == null)
        {
            rf = new RowsFilter(this._dataType, this.Cell.Column);
            rm.RowFiltersCollectionResolved.Add(rf);
        }
        else if (rf.Conditions.Count > 0 && rf.Conditions[0] is ComparisonCondition)
        {
            ComparisonCondition condition = (ComparisonCondition)rf.Conditions[0];

            if (condition.Operator == ComparisonOperator.Contains)
            {
                searchTerm =
                    condition.FilterValue != null
                        ? Convert.ToString(condition.FilterValue, CultureInfo.InvariantCulture)
                        : string.Empty;
            }
        }

        this.SetTextSilently(searchTerm);
        this.IsEnabled = true;
    }
    else
    {
        this._dataType = null;
        this.SetTextSilently(string.Empty);
        this.IsEnabled = false;
    }
}

_dataType stores the type of the objects in the collection used by the ChildBand where the ColumnSearchBoxControl is placed. This type is used in the RowsFilter constructor. Another thing that needs to be mentioned is that the current implementation supports only columns with DataType - string. If the column is not representing a string property the control is disabled and _dataType is set to null, which is used as flag to prevent filtering.

Commands

My goal was to allow the following behavior:

  • Clear the filters applied on the column  using "Clear" button - this was easy, the XamGrid already has this command (ClearFiltersCommand from the ColumnSearchBoxControlCommandSource).
  • Close the popup using a close button "X" and close the popup when the "Clear" button is pressed - same as above, I used ClosePopupCommand from the XamGridPopupCommandSource.
  • Apply the filter and Close the popup when Enter is pressed in the TextBox - The first part is easy… I just had to create custom command and command-source (see Custom Commands with IG Commanding and About Infragistics Commanding ). Hm … but what about the second requirement - I can't use the ClosePopupCommand and command-source wired to KeyDown, because  it will close the popup after the first keystroke, not only when Enter is pressed.

The solution  - I created a custom command that derives from ClosePopupCommand and overrode the CanExecute method, allowing execution only when Enter is pressed.

public class ClosePopupCommandEx : ClosePopupCommand
{
    public override bool CanExecute(object parameter)
    {
        KeyEventArgs keyEventArgs = this.CommandSource.OriginEventArgs as KeyEventArgs;

        if (keyEventArgs != null)
        {
            return keyEventArgs.Key == Key.Enter;
        }

        return base.CanExecute(parameter);
    }
}

This command is returned from the Resolve method of a custom command-source derived from XamGridPopupCommandSource:

public class XamGridPopupCommandSourceEx : XamGridPopupCommandSource
{
    protected override ICommand ResolveCommand()
    {
        if (this.CommandType == XamGridPopupCommand.ClosePopup)
        {
            return new ClosePopupCommandEx();
        }

        return base.ResolveCommand();
    }
}

Other implementation notes

The ColumnSearchBoxControl has a boolean property IsIncrementalFilteringEnabled. When this property is set to true the filters will be executed after every keystroke.

// This code is executed when the text in the TextBox is changed
private void SearchTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
    ...
    if (this.IsIncrementalFilteringEnabled)
    {
        this.ApplyFilter();
    }
}

Source Code

ColumnSearchBoxSample.zip


Comments  (3 )

comayjo
on Thu, Aug 25 2011 5:44 PM

Very useful example.  But how do you implement this only for certain columns in a grid, yet give other columns the default out-of-the-box filtermenu functionality and icons (FilteringSettings AllowFiltering="FilterMenu").  The custom example in this blog applies to the whole grid, and whether it is available for a particular column is determined by the SearchBoxSupportedTypesVisibilityConverter.  For the other columns, how do you bring up the "regular" filter UI (including the Custom sub-popup).  Is there XAML and Code for that?  Or is an entirely different approach needed.

asummers1
on Wed, Sep 7 2011 1:18 PM

I have the same question as comayjo. I would like to have different custom filters on different columns.

Nikolay Zhekov
on Fri, Nov 25 2011 9:32 AM

Hi,

I'm sorry for the late response. /* The email notification for the comments was turned off :( */

Regarding your question. To set the custom filtering UI on a particular column you need to modify the code a little bit:

1. In Resources\NewHeaderStyle.xaml set x:Key for the HeaderCellControl Style. (for example: x:Key="NewHeaderStyle"). The style won't be implicitly applied on all header, you'll have to set it manually on the columns.

2. To use the custom filtering UI set HeaderStyle='{StaticResource NewHeaderStyle}' on a column. Also, set IsFilterable='False' to hide the funnel icon of the filter menu.

<ig:XamGrid ItemsSource="{Binding Data}">
    <ig:XamGrid.Columns>
        <ig:TextColumn Key"LastName"
                        IsFilterable="False"
                        HeaderStyle="{StaticResource NewHeaderStyle}" />
    </ig:XamGrid.Columns>
    <ig:XamGrid.FilteringSettings>
        <ig:FilteringSettings AllowFiltering="FilterMenu" />
    </ig:XamGrid.FilteringSettings>
</ig:XamGrid>

Add a Comment

Please Login or Register to add a comment.