Search code examples
c#winformsdatagridviewrow-height

Setting the height of Rows of a DataGridView to a custom value is slow


I'm working on a WinForms project, I have a Form with 5 data-bound DataGridView Controls.
I need to set a custom height to each Row:

enter image description here

I use the following code:

private void dgv_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
{
     dgv.SuspendLayout();
     var rowHeight = CalculateRowHeight((MyDto)dgvBatches.Rows[e.RowIndex].DataBoundItem));
     dgvBatches.Rows[e.RowIndex].Height = rowHeight;
     dgvBatches.ResumeLayout();
}

It works but the DataGridView loading and scrolling is slow.
Is there any tricks to improve loading and scrolling speed?


Solution

  • The RowPrePaint event is raised constantly: each time you scroll the DataGridView (multiple times), click on a Cell (2 or more times: 1 for the Header), when you hover the Mouse Pointer over 1 or more Cells, when you change a value etc.
    It appears that the calculation of the Rows' height is only needed when a source of data is assigned to the Control, my suggestion is to perform this operation when data binding is complete.

    The Control notifies that the binding is complete, raising the DataBindingComplete event.
    In its handler, you can then suspend the layout, perform the calculations needed and assign the result to the each Row added to the grid:

    private void dgv_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
    {
        if (e.ListChangedType == ListChangedType.Reset) {
            dgvBatches.SuspendLayout();
            for (int i = 0; i < dgvBatches.Rows.Count - 1; i++) {
                if (i == dgvBatches.NewRowIndex) continue;
                var rowHeight = CalculateRowHeight((MyDto)dgvBatches.Rows[e.RowIndex].DataBoundItem));
                dgvBatches.Rows[e.RowIndex].Height = rowHeight;
            }
            dgvBatches.ResumeLayout(false);
        }
        else if (e.ListChangedType == ListChangedType.ItemChanged) {
            // Partner with CellValueChanged
        }
    }
    

    The DataBindingComplete event lets you know what caused the binding notification.
    When you first assign a value to the DataSource property, the e.ListChangedType member will be set to ListChangedType.Reset, when a DataBoundItem value changes, it will be ListChangedType.ItemChanged, so you can determine what action is needed in this context.
    You probably don't need to recalculate everything if a single Cell value changes, if it can be changed at all.
    In case it does, you can resize a single Row, or all Rows that follow the Current, etc.

    Since this layout may become complex, you could also benefit from double-buffering your DataGridView, unless you have a high number 1 of Rows (it doesn't look like, but it could be).

    Try to add this to the Form Constructor, see whether it's better or not:

    var flags = BindingFlags.Instance | BindingFlags.NonPublic;
    dgvBatches.GetType().GetProperty("DoubleBuffered", flags).SetValue(dgvBatches, true);
    

    1 - What a high number is depends on multiple factors, not just the number of Rows. You know the number of Rows is high when you test how the grid responds to different forms of interaction and you find it doesn't react as expected.