快速检查DataGridView的每一行以获取信息

时间:2017-01-09 11:24:50

标签: .net vb.net datagridview

我正在尝试将图片分配到数据集的每一行的列中。数据保存在DataGridView

加载表单后,将填充DataGridView,然后检查每一行以生成状态,然后将与此状态对应的图像加载到第一列。

下面的代码工作正常,但对于2000行数据集,它需要30秒以上的时间来填充图像,这太长了。我能做些什么来加快速度,或者采用另一种方法来减轻系统负载?

For i = 0 To dgvViewOrders.RowCount - 1

            If dgvViewOrders.Rows(i).Cells("DepositPaid").Value = False Then
                dgvViewOrders.Rows(i).Cells("ColImg").Value = My.Resources.WaitingForDeposit_25

            ElseIf dgvViewOrders.Rows(i).Cells("DepositPaid").Value = True And CDate(dgvViewOrders.Rows(i).Cells("DateProcessed").Value).AddDays(20) < Now And dgvViewOrders.Rows(i).Cells("AllMaterialsDelivered").Value = False Then
                dgvViewOrders.Rows(i).Cells("ColImg").Value = My.Resources.ItemsRequireOrdering_25

            ElseIf dgvViewOrders.Rows(i).Cells("AllMaterialsDelivered").Value = False Then
                dgvViewOrders.Rows(i).Cells("ColImg").Value = My.Resources.WaitingForDelivery_25

            Else
                If dgvViewOrders.Rows(i).Cells("FullPaid").Value = True Then
                    dgvViewOrders.Rows(i).Cells("ColImg").Value = My.Resources.Checked_25
                Else
                    dgvViewOrders.Rows(i).Cells("ColImg").Value = My.Resources.WaitingForPayment_25
                End If

            End If



        Next

编辑:我还应该注意,我无法轻松更改数据库标头以包含存储此状态的位置,因此必须在加载时动态完成。

更新1

使用Jinx88909的方法我现在使用以下代码:

Private Sub dgvViewOrders_RowsAdded(sender As Object, e As DataGridViewRowsAddedEventArgs) Handles dgvViewOrders.RowsAdded
        Try

            Debug.Print("Processing Row: " & e.RowIndex)

            If dgvViewOrders.Columns.Contains("ColImg") = False Then
                Dim ColImage As DataGridViewImageColumn = New DataGridViewImageColumn
                ColImage.Name = "ColImg"
                dgvViewOrders.Columns.Add(ColImage)
                ColImage.HeaderText = "Status"
                ColImage.ImageLayout = DataGridViewImageCellLayout.Zoom
                ColImage.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells

            End If


            If dgvViewOrders.Rows(e.RowIndex).Cells("DepositPaid").Value = False Then
                dgvViewOrders.Rows(e.RowIndex).Cells("ColImg").Value = My.Resources.WaitingForDeposit_25

            ElseIf dgvViewOrders.Rows(e.RowIndex).Cells("DepositPaid").Value = True And CDate(dgvViewOrders.Rows(e.RowIndex).Cells("DateProcessed").Value).AddDays(20) < Now And dgvViewOrders.Rows(e.RowIndex).Cells("AllMaterialsDelivered").Value = False Then
                dgvViewOrders.Rows(e.RowIndex).Cells("ColImg").Value = My.Resources.ItemsRequireOrdering_25

            ElseIf dgvViewOrders.Rows(e.RowIndex).Cells("AllMaterialsDelivered").Value = False Then
                dgvViewOrders.Rows(e.RowIndex).Cells("ColImg").Value = My.Resources.WaitingForDelivery_25

            Else
                If dgvViewOrders.Rows(e.RowIndex).Cells("FullPaid").Value = True Then
                    dgvViewOrders.Rows(e.RowIndex).Cells("ColImg").Value = My.Resources.Checked_25
                Else
                    dgvViewOrders.Rows(e.RowIndex).Cells("ColImg").Value = My.Resources.WaitingForPayment_25
                End If

            End If
        Catch ex As Exception
            Debug.Print(ex.Message)
        End Try

    End Sub

但似乎存在一个问题,即添加图像时会导致RowAdded事件再次触发。我放了一个debug.print,这是从不再调用sub之前的输出:

  

处理行:0,   处理行:1,   处理行:0,   处理行:1

前两行显示DataGridView中的正确图像,但其余部分没有显示任何内容(或没有图像的红叉)。

附加说明:使用以下代码填充DataGridView

dgvViewOrders.DataSource = dtOrders - 其中dtOrders是从Access数据库获取的数据表

更新2

我尝试过将图像分配给数组的方法,并且只有当用户可以看到该行并且它才有效时才加载它们。但是当滚动DataGridView时,响应很糟糕。所有图像都会恢复为&#34; No Image Assigned&#34;并且所有行需要大约2到3秒钟。要绘制的状态图像。

我错过了什么吗?这是我的代码:

Private Sub dgvViewOrders_CellFormatting(sender As Object, e As DataGridViewCellFormattingEventArgs) Handles dgvViewOrders.CellFormatting

If LoadingComplete = False Then Exit Sub
If dgvViewOrders.Rows(e.RowIndex).IsNewRow Then Return
If e.ColumnIndex <> 0 Then Return


If DirectCast(dgvViewOrders.Rows(e.RowIndex).Cells("DepositPaid").Value, Boolean) = False Then
    dgvViewOrders.Rows(e.RowIndex).Cells("ColImg").Value = StatusImages(2)

ElseIf DirectCast(dgvViewOrders.Rows(e.RowIndex).Cells("DepositPaid").Value, Boolean) = True And
   CDate(dgvViewOrders.Rows(e.RowIndex).Cells("DateProcessed").Value).AddDays(20) < Now And
   DirectCast(dgvViewOrders.Rows(e.RowIndex).Cells("AllMaterialsDelivered").Value, Boolean) = False Then
    dgvViewOrders.Rows(e.RowIndex).Cells("ColImg").Value = StatusImages(5)

ElseIf DirectCast(dgvViewOrders.Rows(e.RowIndex).Cells("AllMaterialsDelivered").Value, Boolean) = False Then
    dgvViewOrders.Rows(e.RowIndex).Cells("ColImg").Value = StatusImages(3)
Else
    If DirectCast(dgvViewOrders.Rows(e.RowIndex).Cells("FullPaid").Value, Boolean) = True Then
        dgvViewOrders.Rows(e.RowIndex).Cells("ColImg").Value = StatusImages(0)
    Else
        dgvViewOrders.Rows(e.RowIndex).Cells("ColImg").Value = StatusImages(1)
    End If

End If

End Sub

2 个答案:

答案 0 :(得分:1)

如OP所述,当使用RowAdded方法时,事件多次触发,仅针对第1行和第2行。经过一些研究,我发现了this,特别是这个评论:

  

问题:每行都会触发rowsadded事件吗?

     

答案:不。它被称为事件合并,每次发生数以千计的微不足道的行为之一时触发事件将是非常繁琐和处理器密集的。事实上,方法(存在BeginLoadData / EndLoadData以完全关闭事件触发,因为它只会减慢一切。

相反,我查看了RowStateChanged方法:

Private Sub DataGridView1_RowStateChanged(sender As Object, e As DataGridViewRowStateChangedEventArgs) Handles DataGridView1.RowStateChanged

    If DataGridView1.Columns.Contains("ColImg") = False Then
        Dim ColImage As DataGridViewImageColumn = New DataGridViewImageColumn
        ColImage.Name = "ColImg"
        DataGridView1.Columns.Add(ColImage)
        ColImage.HeaderText = "Status"
        ColImage.ImageLayout = DataGridViewImageCellLayout.Zoom
        ColImage.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells

    End If

    If e.StateChanged = DataGridViewElementStates.Displayed Then
        Debug.Print("Processing row: " & e.Row.Index.ToString)

        DataGridView1.Rows(e.Row.Index).Cells("ColImg").Value = My.Resources.Image1
    End If

End Sub

这就是我加载数据的方式:

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load

    _dt = New DataTable

    DataGridView1.DataSource = _dt

    Using con As New SqlConnection(connection),
          com As New SqlCommand(command, con)

        con.Open()
        _dt.Load(com.ExecuteReader)
    End Using

End Sub
  

请注意,我在填写之前将_dt分配给DataGridView1.DataSource。通过执行此操作,事件将按预期触发。

这是我的图片的截图(我很欣赏它的垃圾,但不得不快速嘲笑):

enter image description here

这就是你的代码的样子:

Private Sub dgvViewOrders_RowStateChanged(sender As Object, e As DataGridViewRowStateChangedEventArgs) Handles dgvViewOrders.RowStateChanged

    If DataGridView1.Columns.Contains("ColImg") = False Then
        Dim ColImage As DataGridViewImageColumn = New DataGridViewImageColumn
        ColImage.Name = "ColImg"
        DataGridView1.Columns.Add(ColImage)
        ColImage.HeaderText = "Status"
        ColImage.ImageLayout = DataGridViewImageCellLayout.Zoom
        ColImage.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells

    End If

    If e.StateChanged = DataGridViewElementStates.Displayed Then

        If dgvViewOrders.Rows(e.Row.Index).Cells("DepositPaid").Value = False Then
            dgvViewOrders.Rows(e.Row.Index).Cells("ColImg").Value = My.Resources.WaitingForDeposit_25

        ElseIf dgvViewOrders.Rows(e.Row.Index).Cells("DepositPaid").Value = True And CDate(dgvViewOrders.Rows(e.Row.Index).Cells("DateProcessed").Value).AddDays(20) < Now And dgvViewOrders.Rows(e.Row.Index).Cells("AllMaterialsDelivered").Value = False Then
            dgvViewOrders.Rows(e.Row.Index).Cells("ColImg").Value = My.Resources.ItemsRequireOrdering_25

        ElseIf dgvViewOrders.Rows(e.Row.Index).Cells("AllMaterialsDelivered").Value = False Then
            dgvViewOrders.Rows(e.Row.Index).Cells("ColImg").Value = My.Resources.WaitingForDelivery_25

        Else
            If dgvViewOrders.Rows(e.Row.Index).Cells("FullPaid").Value = True Then
                dgvViewOrders.Rows(e.Row.Index).Cells("ColImg").Value = My.Resources.Checked_25
            Else
                dgvViewOrders.Rows(e.Row.Index).Cells("ColImg").Value = My.Resources.WaitingForPayment_25
            End If

        End If
    End If

End Sub

答案 1 :(得分:1)

发布的代码中有几个初步的内容:

启用Option Strict 此代码无法编译:

If dgvViewOrders.Rows(i).Cells("DepositPaid").Value = False Then

DataGridView小区返回/暂停Object,您无法将ObjectBoolean进行比较

从概念上讲,驳回了The data is held in a DataGridView的概念,因此你必须&#34;必须&#34;循环遍历所有行。由于您使用的是DataSource,因此数据位于此处,控件只是向用户显示数据视图的方法。

在大多数情况下,对数据进行操作比在UI控件上进行操作要快很多倍。 This Q&A example,做一些东西需要17秒左右。在这种情况下,使用数据会将时间缩短到毫秒。这里做的事情要少得多,一切都可以完成。

此外,您的应用正在泄露

dgvViewOrders.Rows(i).Cells("ColImg").Value = My.Resources.WaitingForDelivery_25

这是每次创建一个新的图像对象。你的2000行不需要2000个单独的对象;等待传递的所有行都可以显示相同的图像。此外,创建该对象后,您的代码需要处理任何以前的图像(例如,当状态发生变化时)。

为避免泄漏,请将图像加载到数组中:

' form level declatation
Private ImgBalls As Image()
... 
' elsewhere, load the images from resources:
ImgBalls = New Image() {
                        My.Resources.ballblack, My.Resources.ballblue,
                        My.Resources.ballgreen,
                        My.Resources.ballorange, My.Resources.ballpurple,
                        My.Resources.ballred, My.Resources.ballyellow
                        }

为了分配图像,您可以迭代DataSource中的所有行并设置指示状态的代码,但在这种情况下更好的是使用其中一个DGV事件。有两个好的候选人:RowPrePaintCellFormatting。两者都是高效的:如果用户从不滚动到第1565行,则对其执行操作的代码永远不会运行。

此代码还使用Enum索引图像(Status.Blue)而不是幻数:

Private Sub dgv1_CellFormatting(sender As Object,..etc etc

    ' exit if the new user row, or not the ImgCol
    If dgv1.Rows(e.RowIndex).IsNewRow Then Return
    If e.ColumnIndex <> 8 Then Return

    If DirectCast(dgv1.Rows(e.RowIndex).Cells("Value").Value, Int32) <= 3 Then
        dgv1.Rows(e.RowIndex).Cells("ImgCol").Value = ImgBalls(Status.Green)

    ElseIf DirectCast(dgv1.Rows(e.RowIndex).Cells("Animal").Value, String) = "rat" Then
        dgv1.Rows(e.RowIndex).Cells("ImgCol").Value = ImgBalls(Status.Black)

    ElseIf DirectCast(dgv1.Rows(e.RowIndex).Cells("Price").Value, Decimal) > 14.99 Then
        dgv1.Rows(e.RowIndex).Cells("ImgCol").Value = ImgBalls(Status.Purple)
    Else
        dgv1.Rows(e.RowIndex).Cells("ImgCol").Value = ImgBalls(Status.Yellow)
    End If
End Sub

将事件用于此类事情的好处是代码仅作用于用户查看的行。当处理时间被分解为比特时,更难以注意到。

结果:

enter image description here

因为代码为使用蓝色图像/状态的所有人分配相同的图像对象,所以它不再泄漏资源。

使用各种滚动方法的性能:

enter image description here

我添加了另一个布尔列,以更接近地模拟您在代码中显示的测试/条件。它还使用默认的问号图像代替标准&#34;破坏&#34;图片。动画显示a)使用向下拇指,b)鼠标滚轮然后c)拖动拇指。最后一个允许多达10行绘制有一点延迟,但是在2-3秒附近没有(即使在帖子中有动画GIF的滞后呈现 - 下载到本地运行并且它更快)。 / p>

也就是说,您可以采取一些措施来优化代码:

  • 使用AndAlso而非And(并使用OrElse而不是Or)进行布尔测试。一旦失败,这些将短路并且不评估其他条件。
  • 按测试条件的顺序利用它。例如,在第二组中,我将首先测试DepositPaidAllMaterialsDelivered,这样如果其中任何一个为假,则不需要具有日期的hokey-pokey(我不希望这会产生重大差异,但转换bool似乎应该比日期的东西更简单,更快。)

总的来说,你可能还有其他东西吃点时间。动画图像的代码与此类似:

' exit if the new user row, or not the ImgCol
If dgv1.Rows(e.RowIndex).IsNewRow Then Return
If e.ColumnIndex <> 9 Then Return

If DirectCast(dgv1.Rows(e.RowIndex).Cells("Active").Value, Boolean) = False Then
    dgv1.Rows(e.RowIndex).Cells("ImgCol").Value = ImgBalls(Status.Green)

ElseIf DirectCast(dgv1.Rows(e.RowIndex).Cells("Active").Value, Boolean) = True AndAlso
    DirectCast(dgv1.Rows(e.RowIndex).Cells("Delivered").Value, Boolean) = False AndAlso
    DirectCast(dgv1.Rows(e.RowIndex).Cells("ItemDate").Value, DateTime).AddDays(20) > DateTime.Now Then

    dgv1.Rows(e.RowIndex).Cells("ImgCol").Value = ImgBalls(Status.Purple)

ElseIf DirectCast(dgv1.Rows(e.RowIndex).Cells("Price").Value, Decimal) > 14.99 Then
    dgv1.Rows(e.RowIndex).Cells("ImgCol").Value = ImgBalls(Status.Orange)
Else
    If DirectCast(dgv1.Rows(e.RowIndex).Cells("Value").Value, Int32) < 5 Then
        dgv1.Rows(e.RowIndex).Cells("ImgCol").Value = ImgBalls(Status.Yellow)
    Else
        dgv1.Rows(e.RowIndex).Cells("ImgCol").Value = ImgBalls(Status.Blue)
    End If
End If