Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Contextual Filtered List in a DataGridComboBoxCell

4.50/5 (3 votes)
27 Feb 2017CPOL2 min read 7.9K   276  
How to Filter contextually the list shown by a DataGridComboBoxCell?

Introduction

This article explains how to get a DataGridViewComboBoxColumn with a contextual list of elements depending on the selected row. Even if I use a BindingSource with a BindingList, I realized that the DataSource of a DataGridViewComboBoxColumn can’t be filtered dynamically. My method consists in keeping the entire list of the DataSource and just show filtered items in the DataGridViewComboBoxCell.

Background

  • C# WinForms
  • DataGridView

Using the Code

The first step is to create my own DataGridViewComboBoxColumn: ContextualDataGridViewComboBoxColumn.

C#
/// <summary>
/// Contextual DataGridViewComboBoxColumn based on a filtered request
/// </summary>
public class ContextualDataGridViewComboBoxColumn : DataGridViewComboBoxColumn
{
    public ContextualDataGridViewComboBoxColumn()
    {
        CellTemplate = new ContextualDataGridViewComboBoxCell();
    }
        
    //Contextual list obtained through a request
    public IEnumerable<object> FilteredRequest { get; set; }
}

I add to this class a property of IEnumarable<object> where the contextual query can be stocked to get the list of authorized elements to show.

In the same way, I create my own DataGridViewComboBoxCell (ContextualDataGridViewComboBoxCell) and instance it into CellTemplate in the constructor of my ContextualDataGridViewComboBoxColumn.

C#
/// <summary>
/// Contextual DataGridViewComboBoxCell based on a filtered request
/// </summary>
public class ContextualDataGridViewComboBoxCell : DataGridViewComboBoxCell
{
    /// <summary>
    /// return the type of my own combo box editing control
    /// </summary>
    public override Type EditType
    {
        get
        {
            return typeof(ContextualDataGridViewComboBoxEditingControl);
        }
    }

    //Contextual list obtained through the request of the owning column
    public IEnumerable<object> FilteredRequest
    {
        get
        {
            return (OwningColumn as ContextualDataGridViewComboBoxColumn).FilteredRequest;
        }
    }
}

The FilteredRequest is obtained from its owning column. The EditType property is overridden to apply my own DataGridViewComboBoxEditingControl which represents the hosted combo box control in a DataGridViewComboBoxCell.

Indeed, to prevent any changes in the DataSource, I write a personal DataGridViewComboBoxEditingControl to graphically show filtered elements only.

The FilteredRequest is obtained through the CurrentCell of the dataGridView. To draw and resize elements of the dropdown list by yourself, set the DrawMode to OwnerDrawVariable. Then, we can override OnMeasureItem and OnDrawItem methods:

  • The first one is adopted when describing the size of the Item. For each item that we don't want to be visible, we simply set the ItemHeight to 0.
  • The second one is the drawing element method. If an item is not in the filtered list, then we just don’t draw it.
C#
/// <summary>
/// DataGridViewComboBoxEditingControl that manages the filtered list to display
/// </summary>
public class ContextualDataGridViewComboBoxEditingControl : DataGridViewComboBoxEditingControl
{
    public ContextualDataGridViewComboBoxEditingControl()
    {
        //Allow to manually draw and resize items of the Datasource
        DrawMode = DrawMode.OwnerDrawVariable;
        MouseWheel += ContextualDataGridViewComboBoxEditingControl_MouseWheel;
        KeyDown += ContextualDataGridViewComboBoxEditingControl_KeyDown;
    }

    //Contextual list obtained through the current cell of the DataGridView
    public IEnumerable<object> FilteredRequest
    {
        get
        {
            try
            {
                var currentCell = 
                  (EditingControlDataGridView.CurrentCell as ContextualDataGridViewComboBoxCell);
                if (currentCell != null)
                    return currentCell.FilteredRequest;
                return null;
            }
            catch (Exception)
            {
                return null;
            }
        }
    }

    /// <summary>
    /// Manage the size of items in the dropdown list
    /// </summary>
    protected override void OnMeasureItem(MeasureItemEventArgs e)
    {
        if (e.Index < 0)
            return;

        //If the item is not in the filtered query, set its ItemHeight to 0
        if (FilteredRequest != null && !FilteredRequest.Contains(Items[e.Index]))
            e.ItemHeight = 0;
        else
            base.OnMeasureItem(e);
    }

    /// <summary>
    /// Draw items in the dropdown list
    /// </summary>
    protected override void OnDrawItem(DrawItemEventArgs e)
    {
        if (e.Index < 0)
            return;

        //If the item is not in the filtered query, return
        if (FilteredRequest != null && !FilteredRequest.Contains(Items[e.Index])) return;

        //Draw Background
        e.DrawBackground();

        //Draw item text
        e.Graphics.DrawString(GetItemText(Items[e.Index]), this.Font, Brushes.Black,
            new RectangleF(e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height));
    }
}

It’s almost done! For now, you can display any elements you want in the dropdown list of your combobox. But there still is a problem when you change the selected item without opening the list (with the mouse wheel or up and down keys). For that purpose, you can just manage the selected item using MouseWheel and KeyDown events.

C#
/// <summary>
/// Control the KeyDown event when the combo box is closed
/// </summary>
private void ContextualDataGridViewComboBoxEditingControl_KeyDown(object sender, KeyEventArgs e)
{
    if (!DroppedDown && FilteredRequest != null)
    {
        if (e.KeyCode == Keys.Up)
            changeSelectedItem(-1);
        else if (e.KeyCode == Keys.Down)
            changeSelectedItem(1);
        e.Handled = true;
    }
}

/// <summary>
/// Control the MouseWheel event when the combo box is closed
/// </summary>
private void ContextualDataGridViewComboBoxEditingControl_MouseWheel(object sender, MouseEventArgs e)
{
    if (!DroppedDown && FilteredRequest != null)
    {
        if (e.Delta < 0)
            changeSelectedItem(1);
        else if (e.Delta > 0)
            changeSelectedItem(-1);
        ((HandledMouseEventArgs)e).Handled = true;
    }
}

/// <summary>
/// Select the first item available in the filtered list
/// </summary>
private void changeSelectedItem(int delta)
{
    int tmpIndex = SelectedIndex;
    tmpIndex += delta;
    while (tmpIndex > -1 && tmpIndex < Items.Count)
    {
        if (FilteredRequest.Contains(Items[tmpIndex]))
        {
            SelectedIndex = tmpIndex;
            break;
        }
        tmpIndex += delta;
    }
}

History

  • 27/02/2017: First publication

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)