DataGridView 在屏幕上刷新缓慢
DataGridView slow to refresh on screen
我有一个程序可以使用通过 TCP 接收的新数据定期更新一些数据网格视图。我遇到的问题是屏幕刷新速度很慢。 Bellow 是我的代码的精简版。本例每次迭代 StartButton_Click 中的循环需要 1.1s 来更新屏幕。如何在不减少显示的数据量的情况下加快速度?
我添加了一个秒表来尝试找出哪些代码行导致了最大的问题。从测试来看,主要问题似乎是用新数字更新 datagridview 单元格。
我不确定如何让它更快,因为我的程序依赖于定期更新的值。 datagridview 不是这个应用程序的正确对象吗?我应该使用其他东西吗?有没有办法让 datagridview 更新得更快?
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
结束Class
编辑:
我不得不承认我完全搞砸了我原来的答案,错误地认为不需要调用 DataGridView.Update
来模拟 OP 条件。我留下了我的原文,因为它可能对其他情况下的人有用。
一个潜在的解决方案是使用 DoubleBuffered DataGridView
。这可以通过创建一个继承自 DataGridView
并启用 DoubleBuffering 的 class 来实现。
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
这样做会导致外观发生变化,因为客户区在绘制内容之前是黑色的。为了缓解这种情况,class 覆盖了 OnPaint 方法来绘制背景。
在我的测试中,这将基准时间从大约 2600 毫秒减少到大约 600 毫秒。
结束编辑
除了@Visual Vincent 在关于消除不必要的更新的评论中提出的非常中肯的建议外,我建议您使用 BindingSource 封装 DataTable
并将其用作 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
这将允许您暂时挂起通过 DataTable
引发的导致 DataGridView
重新绘制单元格的更改事件。
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
这样只会重新绘制一次以反映对底层 DataTable
的所有更改。
我有一个程序可以使用通过 TCP 接收的新数据定期更新一些数据网格视图。我遇到的问题是屏幕刷新速度很慢。 Bellow 是我的代码的精简版。本例每次迭代 StartButton_Click 中的循环需要 1.1s 来更新屏幕。如何在不减少显示的数据量的情况下加快速度?
我添加了一个秒表来尝试找出哪些代码行导致了最大的问题。从测试来看,主要问题似乎是用新数字更新 datagridview 单元格。
我不确定如何让它更快,因为我的程序依赖于定期更新的值。 datagridview 不是这个应用程序的正确对象吗?我应该使用其他东西吗?有没有办法让 datagridview 更新得更快?
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
结束Class
编辑:
我不得不承认我完全搞砸了我原来的答案,错误地认为不需要调用 DataGridView.Update
来模拟 OP 条件。我留下了我的原文,因为它可能对其他情况下的人有用。
一个潜在的解决方案是使用 DoubleBuffered DataGridView
。这可以通过创建一个继承自 DataGridView
并启用 DoubleBuffering 的 class 来实现。
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
这样做会导致外观发生变化,因为客户区在绘制内容之前是黑色的。为了缓解这种情况,class 覆盖了 OnPaint 方法来绘制背景。
在我的测试中,这将基准时间从大约 2600 毫秒减少到大约 600 毫秒。
结束编辑
除了@Visual Vincent 在关于消除不必要的更新的评论中提出的非常中肯的建议外,我建议您使用 BindingSource 封装 DataTable
并将其用作 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
这将允许您暂时挂起通过 DataTable
引发的导致 DataGridView
重新绘制单元格的更改事件。
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
这样只会重新绘制一次以反映对底层 DataTable
的所有更改。