New to Telerik UI for WinForms? Download free 30-day trial

Filtering Indicators in GridView

Environment

Product Version 2018.2 515
Product RadGridView for WinForms

Description

RadGridView indicates that a filter has been added to a certain column by changing the icon. At some point it may become difficult to keep track of the filtered and non-filtered columns.

Solution

The solution in the KB project will demonstrate how the columns with applied filters can be additionally highlighted by adding a separate button. The button will also present a convenient way for the end user to remove the applied filter. The header cells in the filter row will be also customized with an additional element which once clicked will remove all of the filters.

The animation below demonstrates the end result in RadGridView.

Figure 1: Filtering Indicators

filtering-indicators-gridview 001

1. Create a custom GridFilterCellElement and initialize a RadButtonElement in it. The layout of the cell will also need adjustments so the ArrangeOverride method of the cell will need customizations. The click event of the button will be responsible for removing the filter and the button`s visibility will by synced in the SetContentCore method.

Custom GridFilterCellElement

public class MyGridFilterCellElement : GridFilterCellElement
{
    private RadButtonElement clearButtonElement;
    public MyGridFilterCellElement(GridViewDataColumn column, GridRowElement row)
        : base(column, row)
    {
    }
    protected override Type ThemeEffectiveType
    {
        get
        {
            return typeof(GridFilterCellElement);
        }
    }
    public RadButtonElement ClearButtonElement
    {
        get
        {
            return this.clearButtonElement;
        }
    }
    protected override void CreateChildElements()
    {
        base.CreateChildElements();
        this.clearButtonElement = new RadButtonElement();
        this.clearButtonElement.Image = Properties.Resources.cross16x16;
        this.clearButtonElement.Click += ClearButtonElement_Click;
        this.clearButtonElement.NotifyParentOnMouseInput = false;
        this.Children.Add(this.clearButtonElement);
    }
    protected override SizeF ArrangeOverride(SizeF finalSize)
    {
        SizeF size = base.ArrangeOverride(finalSize);
        if (this.IsFilterApplied)
        {
            RectangleF arrangeRect = new RectangleF(finalSize.Width - this.FilterButton.DesiredSize.Width - this.clearButtonElement.DesiredSize.Width - this.ElementSpacing, 4,
                this.clearButtonElement.DesiredSize.Width, this.clearButtonElement.DesiredSize.Height);
            this.clearButtonElement.Arrange(arrangeRect);
        }
        if (this.ColumnInfo is GridViewDecimalColumn)
        {
            this.TextAlignment = ContentAlignment.MiddleLeft;
        }
        return size;
    }
    protected override void SetTextAlignment()
    {
        base.SetTextAlignment();
        if (this.ColumnInfo is GridViewDecimalColumn)
        {
            this.TextAlignment = ContentAlignment.MiddleLeft;
        }
    }
    protected override void SetContentCore(object value)
    {
        base.SetContentCore(value);
        this.clearButtonElement.Visibility = this.DataColumnInfo.FilterDescriptor != null ? ElementVisibility.Visible : ElementVisibility.Collapsed;
    }
    public override bool IsCompatible(GridViewColumn data, object context)
    {
        return context is GridFilterRowElement;
    }
    private void ClearButtonElement_Click(object sender, EventArgs e)
    {
        this.DataColumnInfo.FilterDescriptor = null;
    }
}

Public Class MyGridFilterCellElement
    Inherits GridFilterCellElement
    Private _clearButtonElement As RadButtonElement
    Public Sub New(ByVal column As GridViewDataColumn, ByVal row As GridRowElement)
        MyBase.New(column, row)
    End Sub
    Protected Overrides ReadOnly Property ThemeEffectiveType As Type
        Get
            Return GetType(GridFilterCellElement)
        End Get
    End Property
    Public ReadOnly Property ClearButtonElement As RadButtonElement
        Get
            Return Me._clearButtonElement
        End Get
    End Property
    Protected Overrides Sub CreateChildElements()
        MyBase.CreateChildElements()
        Me._clearButtonElement = New RadButtonElement()
        Me._clearButtonElement.Image = My.Resources.cross16x16
        AddHandler Me._clearButtonElement.Click, AddressOf ClearButtonElement_Click
        Me.ClearButtonElement.NotifyParentOnMouseInput = False
        Me.Children.Add(Me.ClearButtonElement)
    End Sub
    Protected Overrides Function ArrangeOverride(ByVal finalSize As SizeF) As SizeF
        Dim size As SizeF = MyBase.ArrangeOverride(finalSize)
        If Me.IsFilterApplied Then
            Dim arrangeRect As RectangleF = New RectangleF(finalSize.Width - Me.FilterButton.DesiredSize.Width - Me.ClearButtonElement.DesiredSize.Width - Me.ElementSpacing, 4, Me.ClearButtonElement.DesiredSize.Width, Me.ClearButtonElement.DesiredSize.Height)
            Me.ClearButtonElement.Arrange(arrangeRect)
        End If
        If TypeOf Me.ColumnInfo Is GridViewDecimalColumn Then
            Me.TextAlignment = ContentAlignment.MiddleLeft
        End If
        Return size
    End Function
    Protected Overrides Sub SetTextAlignment()
        MyBase.SetTextAlignment()
        If TypeOf Me.ColumnInfo Is GridViewDecimalColumn Then
            Me.TextAlignment = ContentAlignment.MiddleLeft
        End If
    End Sub
    Protected Overrides Sub SetContentCore(ByVal value As Object)
        MyBase.SetContentCore(value)
        Me.ClearButtonElement.Visibility = If(Me.DataColumnInfo.FilterDescriptor IsNot Nothing, ElementVisibility.Visible, ElementVisibility.Collapsed)
    End Sub
    Public Overrides Function IsCompatible(ByVal data As GridViewColumn, ByVal context As Object) As Boolean
        Return TypeOf context Is GridFilterRowElement
    End Function
    Private Sub ClearButtonElement_Click(ByVal sender As Object, ByVal e As EventArgs)
        Me.DataColumnInfo.FilterDescriptor = Nothing
    End Sub
End Class

2. Clicking on filtering row will set the filter cell in edit mode. In our custom scenario we would like to avoid that and not enter edit mode when clicking on the newly created button. That is why we need a special row behavior class in which we can override the OnMouseDown method:

Custom GridFilterRowBehavior

public class MyGridFilterRowBehavior : GridFilterRowBehavior
{
    protected override bool OnMouseDownLeft(MouseEventArgs e)
    {
        MyGridFilterCellElement filterCell = this.GetCellAtPoint(e.Location) as MyGridFilterCellElement;
        if (filterCell != null && filterCell.ClearButtonElement.ControlBoundingRectangle.Contains(e.Location))
        {
            return false;
        }
        return base.OnMouseDownLeft(e);
    }
}

Public Class MyGridFilterRowBehavior
    Inherits GridFilterRowBehavior
    Protected Overrides Function OnMouseDownLeft(ByVal e As MouseEventArgs) As Boolean
        Dim filterCell As MyGridFilterCellElement = TryCast(Me.GetCellAtPoint(e.Location), MyGridFilterCellElement)
        If filterCell IsNot Nothing AndAlso filterCell.ClearButtonElement.ControlBoundingRectangle.Contains(e.Location) Then
            Return False
        End If
        Return MyBase.OnMouseDownLeft(e)
    End Function
End Class

3. The image in the header cell located in the filter row can be customized as well. In its virtual OnMouseDown method we will add our special logic for clearing all the filters.

Custom GridRowHeaderCellElement

public class MyGridRowHeaderCellElement : GridRowHeaderCellElement
{
    private Image clearImage;
    public MyGridRowHeaderCellElement(GridViewColumn column, GridRowElement row)
      : base(column, row)
    {
        this.clearImage = Properties.Resources.cross16x16;
    }
    protected override Type ThemeEffectiveType
    {
        get
        {
            return typeof(GridRowHeaderCellElement);
        }
    }
    public override bool IsCompatible(GridViewColumn data, object context)
    {
        return context is GridFilterRowElement;
    }
    protected override void UpdateImage()
    {
        if (this.GridControl != null && this.GridControl.FilterDescriptors.Count > 0 && !this.RowElement.IsCurrent && this.RowElement is GridFilterRowElement)
        {
            this.Image = this.clearImage;
            return;
        }
        base.UpdateImage();
    }
    protected override void OnMouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);
        if (this.GridControl != null && this.GridControl.FilterDescriptors.Count > 0 && !this.RowElement.IsCurrent && this.RowElement is GridFilterRowElement)
        {
            this.GridControl.FilterDescriptors.Clear();
        }
    }
}

Public Class MyGridRowHeaderCellElement
    Inherits GridRowHeaderCellElement
    Private clearImage As Image
    Public Sub New(ByVal column As GridViewColumn, ByVal row As GridRowElement)
        MyBase.New(column, row)
        Me.clearImage = My.Resources.cross16x16
    End Sub
    Protected Overrides ReadOnly Property ThemeEffectiveType As Type
        Get
            Return GetType(GridRowHeaderCellElement)
        End Get
    End Property
    Public Overrides Function IsCompatible(ByVal data As GridViewColumn, ByVal context As Object) As Boolean
        Return TypeOf context Is GridFilterRowElement
    End Function
    Protected Overrides Sub UpdateImage()
        If Me.GridControl IsNot Nothing AndAlso Me.GridControl.FilterDescriptors.Count > 0 AndAlso Not Me.RowElement.IsCurrent AndAlso TypeOf Me.RowElement Is GridFilterRowElement Then
            Me.Image = Me.clearImage
            Return
        End If
        MyBase.UpdateImage()
    End Sub
    Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)
        MyBase.OnMouseDown(e)
        If Me.GridControl IsNot Nothing AndAlso Me.GridControl.FilterDescriptors.Count > 0 AndAlso Not Me.RowElement.IsCurrent AndAlso TypeOf Me.RowElement Is GridFilterRowElement Then
            Me.GridControl.FilterDescriptors.Clear()
        End If
    End Sub
End Class

4. Below is the sample code demonstrating how the custom solution can be utilized. The custom cell elements can be specified in the RadGridView.CreateCell event. The custom row behavior class needs to be registered and this can be done in the form`s constructor.

Initial Setup and Events

public partial class FilteringIndicatorsForm : Telerik.WinControls.UI.RadForm
{
    public FilteringIndicatorsForm()
    {
        InitializeComponent();
        this.radGridView1.CreateCell += RadGridView1_CreateCell;
        BaseGridBehavior gridBehavior = radGridView1.GridBehavior as BaseGridBehavior;
        gridBehavior.UnregisterBehavior(typeof(GridViewFilteringRowInfo));
        gridBehavior.RegisterBehavior(typeof(GridViewFilteringRowInfo), new MyGridFilterRowBehavior());
        this.radGridView1.DataSource = this.GetData();
        this.radGridView1.AutoSizeColumnsMode = Telerik.WinControls.UI.GridViewAutoSizeColumnsMode.Fill;
        this.radGridView1.EnableFiltering = true;
    }
    private void RadGridView1_CreateCell(object sender, GridViewCreateCellEventArgs e)
    {
        if (e.CellType == typeof(GridFilterCellElement))
        {
            e.CellElement = new MyGridFilterCellElement((GridViewDataColumn)e.Column, e.Row);
        }
        else if (e.CellType == typeof(GridRowHeaderCellElement))
        {
            e.CellElement = new MyGridRowHeaderCellElement(e.Column, e.Row);
        }
    }
    private DataTable GetData()
    {
        DataTable dt = new DataTable();
        dt.Columns.Add("Id", typeof(int));
        dt.Columns.Add("Name", typeof(string));
        dt.Columns.Add("Date", typeof(DateTime));
        dt.Columns.Add("Bool", typeof(bool));
        for (int i = 0; i < 20; i++)
        {
            dt.Rows.Add(i, "Name " + i, DateTime.Now.AddDays(i), i % 2 == 0);
        }
        return dt;
    }
}

Public Class FilteringIndicatorsForm
    Public Sub New()
        InitializeComponent()
        AddHandler Me.RadGridView1.CreateCell, AddressOf RadGridView1_CreateCell
        Dim gridBehavior As BaseGridBehavior = TryCast(RadGridView1.GridBehavior, BaseGridBehavior)
        gridBehavior.UnregisterBehavior(GetType(GridViewFilteringRowInfo))
        gridBehavior.RegisterBehavior(GetType(GridViewFilteringRowInfo), New MyGridFilterRowBehavior())
        Me.RadGridView1.DataSource = Me.GetData()
        Me.RadGridView1.AutoSizeColumnsMode = Telerik.WinControls.UI.GridViewAutoSizeColumnsMode.Fill
        Me.RadGridView1.EnableFiltering = True
    End Sub
    Private Sub RadGridView1_CreateCell(ByVal sender As Object, ByVal e As GridViewCreateCellEventArgs)
        If e.CellType = GetType(GridFilterCellElement) Then
            e.CellElement = New MyGridFilterCellElement(CType(e.Column, GridViewDataColumn), e.Row)
        ElseIf e.CellType = GetType(GridRowHeaderCellElement) Then
            e.CellElement = New MyGridRowHeaderCellElement(e.Column, e.Row)
        End If
    End Sub
    Private Function GetData() As DataTable
        Dim dt As DataTable = New DataTable()
        dt.Columns.Add("Id", GetType(Integer))
        dt.Columns.Add("Name", GetType(String))
        dt.Columns.Add("Date", GetType(DateTime))
        dt.Columns.Add("Bool", GetType(Boolean))
        For i As Integer = 0 To 20 - 1
            dt.Rows.Add(i, "Name " & i, DateTime.Now.AddDays(i), i Mod 2 = 0)
        Next
        Return dt
    End Function
End Class

A complete solution providing a C# and VB.NET project is available here.

In this article