Contextual Filtered List in a DataGridComboBoxCell
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
.
/// <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
.
/// <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 theItemHeight
to0
. - The second one is the drawing element method. If an item is not in the filtered list, then we just don’t draw it.
/// <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.
/// <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