I have a dialog with a WinForms DataGridView that shows a user list. Each row contains an AD-User combobox (DataGridViewComboboxColumn) that can grow very large (10K+ items). This DataGridView is used to link the users with their corresponding AD-Users. The content of this combobox does not change between rows.
For performance reasons, I want to change this combobox to something that scales better.
I was thinking about using a TextBox with a small selection button in it. The button then would open a modal dialog for selecting the AD-User. The cell would then contain the selected AD-User object. Alternatively this dialog could also open on a cell-doubleclick, but I think that would not be very intuitive.
Would this be the best option for replacing this combobox? If you have other/better ways of handling such a selection, please let me know.
And if so: How can I create such a custom DataGridView-Cell (TextBox + Button)?
The easiest solution is making the column readonly, then use a button column after that. Then handle CellContentClick
event of DataGridView
to detect button clicks.
Another option is creating a custom column based on DataGridViewButtonColumn. Something like this with a bit change to shows the value of cell instead of label text.
You can also achieve it without creating any new column type by just custom painting the cell, something like this.
Example
My choice is deriving from DataGridView
button column for better user experience, for example highlighting the button on mouse move. To do so, I'll change this answer which shows how to render a label and a button in a single cell. Here I change it a bit to show cell values as a label text:
using System.Drawing;
using System.Windows.Forms;
public class DataGridViewLookupColumn : DataGridViewButtonColumn
{
public DataGridViewLookupColumn()
{
CellTemplate = new DataGridViewLookupCell();
ButtonText = "...";
}
public string ButtonText { get; set; }
public override object Clone()
{
var c = (DataGridViewLookupColumn)base.Clone();
c.ButtonText = this.ButtonText;
return c;
}
}
public class DataGridViewLookupCell : DataGridViewButtonCell
{
protected override void Paint(Graphics graphics, Rectangle clipBounds,
Rectangle cellBounds, int rowIndex,
DataGridViewElementStates elementState,
object value, object formattedValue, string errorText,
DataGridViewCellStyle cellStyle,
DataGridViewAdvancedBorderStyle advancedBorderStyle,
DataGridViewPaintParts paintParts)
{
var g = this.DataGridView;
var c = (DataGridViewLookupColumn)this.OwningColumn;
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState,
value, formattedValue, errorText, cellStyle, advancedBorderStyle,
DataGridViewPaintParts.All &
~DataGridViewPaintParts.ContentBackground &
~DataGridViewPaintParts.ContentForeground);
var cellRectangle = g.GetCellDisplayRectangle(c.Index, rowIndex, false);
var buttonRectangle = GetContentBounds(rowIndex);
var textRectangle = new Rectangle(cellRectangle.Location,
new Size(cellRectangle.Width - GetButtonWidth(),
cellRectangle.Height));
buttonRectangle.Offset(cellRectangle.Location);
var alignment = cellStyle.Alignment;
cellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
base.Paint(graphics, clipBounds, buttonRectangle, rowIndex, elementState,
value, c.ButtonText, errorText, cellStyle, advancedBorderStyle,
DataGridViewPaintParts.All &
~DataGridViewPaintParts.Border);
cellStyle.Alignment = alignment;
base.Paint(graphics, clipBounds, textRectangle, rowIndex, elementState,
value, formattedValue, errorText, cellStyle, advancedBorderStyle,
DataGridViewPaintParts.ContentForeground);
}
protected override Rectangle GetContentBounds(Graphics graphics,
DataGridViewCellStyle cellStyle, int rowIndex)
{
var w = GetButtonWidth();
var r = base.GetContentBounds(graphics, cellStyle, rowIndex);
return new Rectangle(r.Right - w, r.Top, w, r.Height);
}
private int GetButtonWidth()
{
var c = (DataGridViewLookupColumn)this.OwningColumn;
var text = c.ButtonText;
return TextRenderer.MeasureText(text, c.DefaultCellStyle.Font).Width
+ 10 /*a bit padding */;
}
}
Handle click on button
To handle the click on the button, handle CellContentClick
like a normal DataGridViewColumnButton
and check if e.RowIndex > -1
and e.ColumnIndex == your desired column index
:
void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
//if click is on row header or column header, do nothing.
if(e.RowIndex < 0 || e.ColumnIndex < 0)
return;
//Check if click is on specific column
if( e.ColumnIndex == dataGridView1.Columns["specific column name"].Index)
{
//Put some logic here, for example show a dialog and use result.
}
}