Search code examples
vb.netperformancedatagridviewdatatablerefresh

DataGridView slow to refresh on screen


I have a program that regularly updates a few datagridviews with new data that is received via TCP. The problem I am having is that the screen refresh is quite slow. Bellow is a stripped back version of my code. This example takes 1.1s to update the screen each time the loop in StartButton_Click is iterated. How can I make this faster without reducing the amount of data that is shown?

I added a stopwatch to try and work out what lines of code were causing the biggest issue. From the tests, it seemed that the main issue was updating the datagridview cells with a new number.

I'm not sure how to make this faster as my program relies on the values being updated regularly. Is a datagridview not the right object for this application? Should i be using something else? Is there a way to get a datagridview to update faster?

Public Class Form1

Public DataTable1 As New DataTable

Private Sub Load_From(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load

    DataGridView1.DataSource = DataTable1

    Me.Height = 700
    Me.Width = 1000
    DataGridView1.Location = New Point(10, 10)
    DataGridView1.Width = Me.Width - 10
    DataGridView1.Height = Me.Height - 10

    For c As Integer = 0 To 20
        DataTable1.Columns.Add("col" & c)
        If DataTable1.Rows.Count = 0 Then
            DataTable1.Rows.Add()
        End If
        DataGridView1.Columns(c).AutoSizeMode = DataGridViewAutoSizeColumnMode.None '0%

        DataTable1.Rows(0).Item(c) = "col" & c
        DataGridView1.Columns(c).Width = 40
        'Header
        DataGridView1.Rows(0).Cells(c).Style.Alignment = DataGridViewContentAlignment.MiddleCenter
        DataGridView1.Rows(0).Cells(c).Style.WrapMode = DataGridViewTriState.True
        DataGridView1.Rows(0).Cells(c).Style.Font = New Font("Verdana", 8, FontStyle.Bold)
        'Data
        DataGridView1.Columns(c).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
        DataGridView1.Columns(c).DefaultCellStyle.WrapMode = DataGridViewTriState.False
        DataGridView1.Columns(c).DefaultCellStyle.Font = New Font("Verdana", 8, FontStyle.Regular)
    Next

    For r As Integer = 1 To 25
        DataTable1.Rows.Add()
    Next

End Sub

Private Sub StartButton_Click(sender As Object, e As EventArgs) Handles StartButton.Click

    Dim stpw As New Stopwatch
    stpw.Reset()
    stpw.Start()

    For i As Integer = 0 To 10
        Dim rand As New Random
        Dim randnumber As Double = rand.Next(5, 15) / 10

        UpdateDataTable(randnumber)
        DataGridView1.Update()
        Me.Text = i & "/100"
    Next

    stpw.Stop()
    MsgBox(stpw.Elapsed.TotalMilliseconds)

End Sub

Private Sub UpdateDataTable(ByVal offset As Double)

    For r As Integer = 1 To DataTable1.Rows.Count - 1 'loop through rows

        For c As Integer = 0 To DataTable1.Columns.Count - 1 '89%

            DataTable1.Rows(r).Item(c) = (r / c) * offset
        Next

    Next

End Sub

End Class


Solution

  • Edit:

    I have to admit that I totally botched my original answer by erroneously believing that the call to DataGridView.Update was not needed to emulate the OP conditions. I am leaving my original text as it may be of use for someone in another situation.

    A potential solution is to use a DoubleBuffered DataGridView. This can be accomplished by creating a class that inherits from DataGridView and enables DoubleBuffering.

    Public Class BufferedDataGridView : Inherits DataGridView
        Public Sub New()
            MyBase.New()
            Me.DoubleBuffered = True
        End Sub
        Protected Overrides Sub OnPaint(e As PaintEventArgs)
            e.Graphics.Clear(Me.BackgroundColor)
            MyBase.OnPaint(e)
        End Sub
    End Class
    

    Doing this yields a change in appearance in that the client area is black until something is drawn on it. To alleviate this, the class overrides the OnPaint method to draw a background.

    In my testing this reduced the bench-march time from approximately 2600 ms to approximately 600 ms.

    End Edit


    In addition to the highly pertinent suggestions of @Visual Vincent in the comments regarding eliminating unnecessary updating, I would recommend that you use a BindingSource to encapsulate the DataTable and use that as the DataGridview.DataSource.

    Private bs As New BindingSource
    
    Private Sub Load_From(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        bs.DataSource = DataTable1
        DataGridView1.DataSource = bs
    

    This will allow you to temporary suspend change events raised through the DataTable that cause the DataGridView to repaint cells.

    Private Sub UpdateDataTable(ByVal offset As Double)
        ' prevent each item change from raising an event that causes a redraw
        bs.RaiseListChangedEvents = False
    
        For r As Integer = 1 To DataTable1.Rows.Count - 1 'loop through rows
            For c As Integer = 0 To DataTable1.Columns.Count - 1 '89%
                DataTable1.Rows(r).Item(c) = (r / c) * offset
            Next
        Next
    
        bs.RaiseListChangedEvents = True ' re-enable change events
        bs.ResetBindings(False) ' Force bound controls to re-read list
    End Sub
    

    This way the will only repaint once to reflect all the changes to the underlying DataTable.