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