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

Permanent editor in a filter cell

Environment

Product Version 2018.1 220
Product RadGridView for WinForms

Description

By design, RadGridView uses editing mechanism. Thus, only one cell can be edited at a moment. A common scenario is to display a permanent editor in the filter cells in order to indicate better to the user that there are several options to choose.

Solution

At the end of this solution the illustrated result in the following video will be achieved:

gridview-permanenteditor-in-filter-cell 001

Follow the detailed steps in order to create a custom filter cell and embed a permanent RadDropDownListElement to manage filtering.

1. Create a derivative of GridFilterCellElement.

  • In its CreateChildElements method you can add a RadDropDownListElement which holds the distinct column values, a LightVisualElement which displays the filter operator and a StackLayoutPanel which is the host container.

  • Subscribe to the RadDropDownListElement.SelectedIndexChanged event where the respective FilterDescriptor will be added/deleted according to the currently selected drop down item.

  • Override the SetContentCore method where to bind the RadDropDownList and synchronize its selection. Since RadGridView uses data virtualization and cell elements are being reused during operations like scrolling, the RowInfo.Tag property will store the currently selected index.

  • In the UpdateFilterButtonVisibility method the default filter button is collapsed.

  • The IsCompatible method indicates with which columns and data rows the custom cell element is compatible during the cells reuse. Since this filter cell will be used only for e specific column and the filtering row, the returned Boolean result is true only in this case.

Custom GridFilterCellElement


public class DropDownGridFilterCellElement : GridFilterCellElement
{
    RadDropDownListElement dropDown;
    LightVisualElement operatorElement;
    StackLayoutPanel container; 
    BindingList<string> filterValues = new BindingList<string>();

    public DropDownGridFilterCellElement(GridViewDataColumn column, GridRowElement row) : base(column, row)
    {
    }

    BindingContext bc = new BindingContext();

    protected override void CreateChildElements()
    {
        base.CreateChildElements();
        operatorElement = new LightVisualElement();
        container = new StackLayoutPanel();
        dropDown = new RadDropDownListElement();

        dropDown.BindingContext = bc;
        dropDown.DropDownStyle = RadDropDownStyle.DropDownList; 
        dropDown.SelectedIndexChanged += dropDown_SelectedIndexChanged;

        this.Children.Add(container);
        container.Children.Add(operatorElement);
        container.Children.Add(dropDown);
    }

    private void dropDown_SelectedIndexChanged(object sender, Telerik.WinControls.UI.Data.PositionChangedEventArgs e)
    {
        GridViewDataColumn dataColumn = this.ColumnInfo as GridViewDataColumn;

        if (dataColumn.FilterDescriptor == null)
        {
            dataColumn.FilterDescriptor = new Telerik.WinControls.Data.FilterDescriptor(this.ColumnInfo.Name, Telerik.WinControls.Data.FilterOperator.Contains, null);
            dataColumn.FilterDescriptor.IsFilterEditor = true;
        }
        if (e.Position > -1)
        {
            this.Value = dropDown.Items[e.Position].Text;
            this.RowInfo.Tag = e.Position;
            dataColumn.FilterDescriptor.Value = this.Value;
        }
        if (e.Position == 0)
        {
            dataColumn.FilterDescriptor = null;
        }
    }

    public override void Detach()
    {
        dropDown.SelectedIndexChanged -= dropDown_SelectedIndexChanged;
        base.Detach();
    }

    protected override void SetContentCore(object value)
    {
        base.SetContentCore(value);

        GridViewDataColumn dataColumn = this.ColumnInfo as GridViewDataColumn;
        if (dropDown.DataSource == null)
        {
            filterValues.Add("No filter");
            foreach (string distinctValue in dataColumn.DistinctValues)
            {
                filterValues.Add(distinctValue);
            }
            dropDown.SelectedIndexChanged -= dropDown_SelectedIndexChanged;
            dropDown.DataSource = filterValues;

            dropDown.SelectedIndex = 0;
            dropDown.SelectedIndexChanged += dropDown_SelectedIndexChanged;
        }
        this.DrawText = false;
        this.ForeColor = Color.Transparent;
        operatorElement.ForeColor = Color.Black; 

        if (this.RowInfo.Tag != null && this.RowInfo.Tag.ToString() != dropDown.SelectedIndex.ToString())
        {
            dropDown.SelectedIndexChanged -= dropDown_SelectedIndexChanged;
            //synchronize the selected option
            dropDown.SelectedIndex = (int)this.RowInfo.Tag;
            dropDown.SelectedIndexChanged += dropDown_SelectedIndexChanged;
        }

        if (dataColumn != null && dataColumn.FilterDescriptor != null)
        {
            //synchronize the filter operator
            this.operatorElement.Text = dataColumn.FilterDescriptor.Operator.ToString();
        }
        else
        {
            dropDown.SelectedIndexChanged -= dropDown_SelectedIndexChanged;
            dropDown.SelectedIndex = 0;
            dropDown.SelectedIndexChanged += dropDown_SelectedIndexChanged;
        }
    }

    //hide the default filter button
    protected override void UpdateFilterButtonVisibility(bool enabled)
    {
        enabled = false;
        base.UpdateFilterButtonVisibility(enabled);
    }

    public override bool IsCompatible(GridViewColumn data, object context)
    {
        return data is CustomColumn && context is GridViewFilteringRowInfo;
    }
}

Public Class DropDownGridFilterCellElement
    Inherits GridFilterCellElement
    Private dropDown As RadDropDownListElement
    Private operatorElement As LightVisualElement
    Private container As StackLayoutPanel
    Private filterValues As BindingList(Of String)
    Private bc As BindingContext
    Public Sub New(column As GridViewDataColumn, row As GridRowElement)
        MyBase.New(column, row)
    End Sub
    Protected Overrides Sub CreateChildElements()
        MyBase.CreateChildElements()
        filterValues = New BindingList(Of String)()
        bc = New Windows.Forms.BindingContext()
        operatorElement = New LightVisualElement()
        container = New StackLayoutPanel()
        dropDown = New RadDropDownListElement()
        dropDown.BindingContext = bc
        dropDown.DropDownStyle = RadDropDownStyle.DropDownList
        AddHandler dropDown.SelectedIndexChanged, AddressOf dropDown_SelectedIndexChanged
        Me.Children.Add(container)
        container.Children.Add(operatorElement)
        container.Children.Add(dropDown)
    End Sub
    Private Sub dropDown_SelectedIndexChanged(ByVal sender As Object, ByVal e As Telerik.WinControls.UI.Data.PositionChangedEventArgs)
        Dim dataColumn As GridViewDataColumn = TryCast(Me.ColumnInfo, GridViewDataColumn)
        If dataColumn.FilterDescriptor Is Nothing Then
            dataColumn.FilterDescriptor = New Telerik.WinControls.Data.FilterDescriptor(Me.ColumnInfo.Name, Telerik.WinControls.Data.FilterOperator.Contains, Nothing)
            dataColumn.FilterDescriptor.IsFilterEditor = True
        End If
        If e.Position > -1 Then
            Me.Value = dropDown.Items(e.Position).Text
            Me.RowInfo.Tag = e.Position
            dataColumn.FilterDescriptor.Value = Me.Value
        End If
        If e.Position = 0 Then
            dataColumn.FilterDescriptor = Nothing
        End If
    End Sub
    Public Overrides Sub Detach()
        RemoveHandler dropDown.SelectedIndexChanged, AddressOf dropDown_SelectedIndexChanged
        MyBase.Detach()
    End Sub
    Protected Overrides Sub SetContentCore(ByVal value As Object)
        MyBase.SetContentCore(value)
        Dim dataColumn As GridViewDataColumn = TryCast(Me.ColumnInfo, GridViewDataColumn)
        If dropDown.DataSource Is Nothing Then
            filterValues.Add("No filter")
            For Each distinctValue As String In dataColumn.DistinctValues
                filterValues.Add(distinctValue)
            Next
            RemoveHandler dropDown.SelectedIndexChanged, AddressOf dropDown_SelectedIndexChanged
            dropDown.DataSource = filterValues
            dropDown.SelectedIndex = 0
            AddHandler dropDown.SelectedIndexChanged, AddressOf dropDown_SelectedIndexChanged
        End If
        Me.DrawText = False
        Me.ForeColor = Color.Transparent
        operatorElement.ForeColor = Color.Black
        If Me.RowInfo.Tag IsNot Nothing AndAlso Me.RowInfo.Tag.ToString() <> dropDown.SelectedIndex.ToString() Then
            RemoveHandler dropDown.SelectedIndexChanged, AddressOf dropDown_SelectedIndexChanged
            dropDown.SelectedIndex = CInt(Me.RowInfo.Tag)
            AddHandler dropDown.SelectedIndexChanged, AddressOf dropDown_SelectedIndexChanged
        End If
        If dataColumn IsNot Nothing AndAlso dataColumn.FilterDescriptor IsNot Nothing Then
            Me.operatorElement.Text = dataColumn.FilterDescriptor.[Operator].ToString()
        Else
            RemoveHandler dropDown.SelectedIndexChanged, AddressOf dropDown_SelectedIndexChanged
            dropDown.SelectedIndex = 0
            AddHandler dropDown.SelectedIndexChanged, AddressOf dropDown_SelectedIndexChanged
        End If
    End Sub
    Protected Overrides Sub UpdateFilterButtonVisibility(ByVal enabled As Boolean)
        enabled = False
        MyBase.UpdateFilterButtonVisibility(enabled)
    End Sub
    Public Overrides Function IsCompatible(ByVal data As GridViewColumn, ByVal context As Object) As Boolean
        Return TypeOf data Is CustomColumn AndAlso TypeOf context Is GridViewFilteringRowInfo
    End Function
End Class

2. Create a custom column which will use the newly implemented filter cell element.

  • Create a derivative of GridViewTextBoxColumn.

  • Override its GetCellType method. Thus, you can return the type of the custom filter cell for the GridViewFilteringRowInfo.

Custom column


public class CustomColumn : GridViewTextBoxColumn
{
    public override Type GetCellType(GridViewRowInfo row)
    {
        if (row is GridViewFilteringRowInfo)
        {
            return typeof(DropDownGridFilterCellElement);
        }
        return base.GetCellType(row);
    }
}

Public Class CustomColumn
    Inherits GridViewTextBoxColumn
    Public Overrides Function GetCellType(ByVal row As GridViewRowInfo) As Type
        If TypeOf row Is GridViewFilteringRowInfo Then
            Return GetType(DropDownGridFilterCellElement)
        End If
        Return MyBase.GetCellType(row)
    End Function
End Class

3. Next, add the custom column to RadGridView and fill the grid with data:

Fill data and add the custom column


DataTable dt = new DataTable();
dt.Columns.Add("Id", typeof(int));
dt.Columns.Add("IssueTitle", typeof(string));
dt.Columns.Add("Priority", typeof(string));
for (int i = 1; i < 20; i++)
{
    if (i % 2 == 0)
    {
        dt.Rows.Add(i, "Issue#" + i, "Low");
    }
    else
    {
        dt.Rows.Add(i, "Issue#" + i, "High");
    }
}

this.radGridView1.AutoGenerateColumns = false;

GridViewDecimalColumn idColumn = new GridViewDecimalColumn("Id");
this.radGridView1.Columns.Add(idColumn);
GridViewTextBoxColumn titleColumn = new GridViewTextBoxColumn("IssueTitle");
this.radGridView1.Columns.Add(titleColumn);
CustomColumn myColumn = new CustomColumn();
myColumn.FieldName = myColumn.Name = myColumn.HeaderText = "Priority";
this.radGridView1.Columns.Add(myColumn);

this.radGridView1.DataSource = dt;
this.radGridView1.BestFitColumns(BestFitColumnMode.AllCells);
this.radGridView1.EnableFiltering = true;

Dim dt As DataTable = New DataTable()
dt.Columns.Add("Id", GetType(Integer))
dt.Columns.Add("IssueTitle", GetType(String))
dt.Columns.Add("Priority", GetType(String))
For i As Integer = 1 To 20 - 1
    If i Mod 2 = 0 Then
        dt.Rows.Add(i, "Issue#" & i, "Low")
    Else
        dt.Rows.Add(i, "Issue#" & i, "High")
    End If
Next
Me.radGridView1.AutoGenerateColumns = False
Dim idColumn As GridViewDecimalColumn = New GridViewDecimalColumn("Id")
Me.radGridView1.Columns.Add(idColumn)
Dim titleColumn As GridViewTextBoxColumn = New GridViewTextBoxColumn("IssueTitle")
Me.radGridView1.Columns.Add(titleColumn)
Dim myColumn As CustomColumn = New CustomColumn()
myColumn.FieldName = "Priority"
myColumn.Name = "Priority"
myColumn.HeaderText = "Priority"
Me.radGridView1.Columns.Add(myColumn)
Me.radGridView1.DataSource = dt
Me.radGridView1.BestFitColumns(BestFitColumnMode.AllCells)
Me.radGridView1.EnableFiltering = True

4. The last thing we need to do is to prevent entering edit mode for the filter cell since there is a permanent editor now. For this purpose, it is necessary to cancel the CellBeginEdit event:

Fill data and add the custom column


private void radGridView1_CellBeginEdit(object sender, GridViewCellCancelEventArgs e)
{
    if (e.Row is GridViewFilteringRowInfo && e.Column.Name == "Priority")
    {
        e.Cancel = true;
    }
}

Private Sub radGridView1_CellBeginEdit(ByVal sender As Object, ByVal e As GridViewCellCancelEventArgs)
    If TypeOf e.Row Is GridViewFilteringRowInfo AndAlso e.Column.Name = "Priority" Then
        e.Cancel = True
    End If
End Sub

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

See Also

In this article