Search code examples
c#winformsdatagridviewdatagridviewtextboxcelldatagridviewcomboboxcolumn

Replacing huge DataGridViewComboBoxColumn for performance


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)?


Solution

  • 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:

    enter image description here

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