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;
    }
}

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);
    }
}

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;

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;
    }
}

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