【问题标题】:Animate Images in DataGridViewDataGridView 中的动画图像
【发布时间】:2018-04-08 12:40:30
【问题描述】:

我编写了下面的帮助程序类,以便为 DataGridView 中的图像设置动画,但它不起作用(图像没有动画)。

在此之前,我在wen上找到了一些示例代码,但它们也不起作用。

我想了解它是如何工作的,而不是仅仅因为它有效就在我的应用中塞进一段代码。为什么我的代码没有达到预期的效果?

编辑

我发现了它不起作用的原因。源DataTable 本身不包含图像:它们通过其CellFormatting 处理程序方法分配给代码中其他地方的DataGridView 的单元格。由于这个事件也一直触发,所以总是传递一个新的图像对象,所以它总是显示图像的第 1 帧。当我创建一个存储有原生图像值的新列时,它们会根据需要进行动画处理。

现在的问题是:是否可以在DataGridViewCellFormatting 事件处理方法中为分配给.FormattedValue 属性的图像设置动画?

Public Class DataGridViewImageAnimator

    Private WithEvents MyDataGridView As DataGridView

    Public Sub New(dataGridView As DataGridView)

        MyDataGridView = dataGridView

    End Sub

    Private MyAnimatedImages As New Dictionary(Of Point, Image)

    Private Sub ImageAnimator_FrameChanged(sender As Object, e As EventArgs)

        Dim imageCells = MyDataGridView.Rows.Cast(Of DataGridViewRow).SelectMany(
            Function(dgvr) dgvr.Cells.OfType(Of DataGridViewImageCell))

        For Each cell In imageCells

            Dim img = TryCast(cell.FormattedValue, Image)

            If img IsNot Nothing AndAlso MyAnimatedImages.ContainsValue(img) Then

                MyDataGridView.InvalidateCell(cell)

            End If
        Next

    End Sub

    Private Sub MyDataGridView_CellPainting(
            sender As Object,
            e As DataGridViewCellPaintingEventArgs
            ) Handles MyDataGridView.CellPainting

        If e.ColumnIndex >= 0 AndAlso e.RowIndex >= 0 Then

            Dim cell = MyDataGridView(e.ColumnIndex, e.RowIndex)

            Dim drawPoint = MyDataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, True).Location

            Dim pt = New Point(e.ColumnIndex, e.RowIndex)

            Dim cellImg = TryCast(cell.FormattedValue, Image)

            If MyAnimatedImages.ContainsKey(pt) AndAlso Equals(MyAnimatedImages(pt), cellImg) Then
                'If image is already registered as animated, and is still in cell

                ImageAnimator.UpdateFrames()

                e.Graphics.DrawImage(cellImg, drawPoint)

            Else

                If MyAnimatedImages.ContainsKey(pt) Then
                    'If image registered as animated is no longer in cell

                    ImageAnimator.StopAnimate(MyAnimatedImages(pt), AddressOf ImageAnimator_FrameChanged)

                    MyAnimatedImages.Remove(pt)

                End If

                If cellImg IsNot Nothing AndAlso ImageAnimator.CanAnimate(cellImg) Then
                    'If cell contains an image not yet registered as animated

                    MyAnimatedImages(pt) = cellImg

                    ImageAnimator.Animate(MyAnimatedImages(pt), AddressOf ImageAnimator_FrameChanged)

                    ImageAnimator.UpdateFrames()

                    e.Graphics.DrawImage(cellImg, drawPoint)

                End If

            End If

        End If

    End Sub

End Class

【问题讨论】:

  • 您的ImageAnimator.Animate() 好像放错了地方。该方法应该为动画图像调用一次(在绘画事件中)。它主要用于指定OnFrameChanged 处理程序。我通常有一个从Paint() 事件调用的方法(比如说Animate())。作为第一件事,Animate() 方法检查动画是否已经启动(使用 bool 变量检查 a)。如果是,则立即返回。 Check the example in MSDN Docs
  • 抱歉,我想我不明白我所做的与您的指示有何不同...我将它放在仅在检测到新图像时运行的代码的一部分中...位于CellPainting 事件处理程序中...我真的很抱歉,如果您能澄清一下,我会非常感谢。
  • 嗯,我想了想(我现在唯一能做的,手头没有VS),我看到了你的编辑。我认为您对单元格格式行为是正确的。您也不能存储图像,您最终会得到一个无效的图像。我认为您应该创建一个包含 ImageAnimator 逻辑的自定义单元类型(从 ImageCell 派生),并且可能为自定义单元创建一个自定义列。如果在此期间没有人回答您的问题,明天我会试一试。
  • 嗨@Jimi。当我通过创建一个图像列来显示而不是格式化一个整数列来做出一个肮脏的解决方法时......我隐藏了整数列并处理了 CellValueChanged 来选择和存储正确的图像。无论如何,我会继续以一种更整洁的方式思考......
  • 有了自定义的Column和自定义的Cells,我觉得效果还是挺不错的。没有闪烁,您可以在 DataGridView 设计器中设置 Column 属性,因此您可以将位图设置为 Zoom 或 Stretch 等。只有一件事我不喜欢:单元格是从自定义 Column 提供的 CellTemplate 构建的,但它们不是新的(ed),它们是克隆的。这意味着在显示列后必须使单元格无效。如果你愿意,我会发布这个自定义列类,可能与你想要的足够接近。顺便说一句,我还没有找到任何好的例子。

标签: vb.net winforms animation datagridview


【解决方案1】:

带有自定义单元格的自定义列提供了一些优势。

所有设计逻辑都集中在一个地方,并且可以在设计时使用DataGridView设计器将其选为列模板。


性能相当好(用 200 个动画单元测试),我没有注意到任何闪烁。

可以像往常一样使用设计器设置、通过代码或手动调整行/列的大小来拉伸或缩放动画 Gif。

但是,我不能认为它是完整的,因为我找不到使用此自定义 Column 类方法或属性来启动所有动画的好方法。

编辑:
DataGridView (DataGridView.Animate()) 添加了扩展方法。
这允许隐藏无效过程。
DataGridView数据绑定完成后,只需调用扩展方法即可:

DataGridView1.DataSource = [DataSource]
DataGridView1.Animate()

包含扩展方法的模块:

Imports System.Runtime.CompilerServices

Module DGVExtesions

    <Extension()>
    Public Sub Animate(ByVal AnimatedGrid As DataGridView)
        Try
            For Each row As DataGridViewRow In AnimatedGrid.Rows
                For Each cell As DataGridViewCell In row.Cells.OfType(Of AnimatedDGVColumn.AnimatedCell)()
                    AnimatedGrid.InvalidateCell(cell)
                Next
            Next
        Catch ex As Exception
            Trace.WriteLine("Exception: {0}", ex.Message)
        End Try

    End Sub

End Module

当然,这还不够好。需要进行更多研究。

这是自定义动画列类:

Imports System.ComponentModel
Imports System.Windows.Forms

Public Class AnimatedDGVColumn
    Inherits System.Windows.Forms.DataGridViewColumn

    Private custCellTemplate As AnimatedCell

    Public Sub New()
        Me.custCellTemplate = New AnimatedCell
        Me.custCellTemplate.ImageLayout = DataGridViewImageCellLayout.Zoom
        MyBase.CellTemplate = custCellTemplate
        Me.AutoSizeMode = DataGridViewAutoSizeColumnMode.None
        Me.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
    End Sub

    <Description("The ImageLayout in the Cells for this Column"), Category("Appearance")> _
    <EditorBrowsable(EditorBrowsableState.Always), Browsable(True)>
    Public Property ImageLayout As DataGridViewImageCellLayout
        Get
            Return Me.custCellTemplate.ImageLayout
        End Get
        Set(ByVal value As DataGridViewImageCellLayout)
            Me.custCellTemplate.ImageLayout = value
        End Set
    End Property

    Public Overloads Property CellTemplate As AnimatedCell
        Get
            Return Me.custCellTemplate
        End Get
        Set(value As AnimatedCell)
            Me.custCellTemplate = value
            MyBase.CellTemplate = value
        End Set
    End Property

    Public Class AnimatedCell
        Inherits System.Windows.Forms.DataGridViewImageCell

        Private Animation As Image
        Private IsAnimating As Boolean

        Public Sub New()
            Me.Animation = Nothing
            Me.IsAnimating = False
        End Sub

        Public Overloads Property ImageLayout() As DataGridViewImageCellLayout
            Get
                Return MyBase.ImageLayout
            End Get
            Set(ByVal value As DataGridViewImageCellLayout)
                MyBase.ImageLayout = value
            End Set
        End Property

        Protected Overrides Sub Paint(graphics As Graphics, clipBounds As Rectangle, cellBounds As Rectangle, rowIndex As Integer, elementState As DataGridViewElementStates, value As Object, formattedValue As Object, errorText As String, cellStyle As DataGridViewCellStyle, advancedBorderStyle As DataGridViewAdvancedBorderStyle, paintParts As DataGridViewPaintParts)
            If (IsDBNull(value)) OrElse (value Is Nothing) Then Return
            If Me.Animation Is Nothing Then
                Me.Animation = CType(formattedValue, Image)
            End If
            Animate()
            ImageAnimator.UpdateFrames()

            MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, Nothing, Me.Animation, errorText, cellStyle, advancedBorderStyle, paintParts)
        End Sub

        Private Sub Animate()
            If Me.IsAnimating = True Then Return
            If (Me.Animation IsNot Nothing) AndAlso ImageAnimator.CanAnimate(Me.Animation) = True Then
                ImageAnimator.Animate(Me.Animation, AddressOf Me.RotateFrame)
                Me.IsAnimating = True
            End If
        End Sub

        Private Sub RotateFrame(o As Object, e As EventArgs)
            If Me.RowIndex > -1 Then
                Me.DataGridView.InvalidateCell(Me)
            End If
        End Sub

        Public Overrides Function Clone() As Object
            Dim result As AnimatedCell = New AnimatedCell With {
                        .IsAnimating = False,
                        .Animation = Nothing,
                        .ImageLayout = Me.ImageLayout
                        }
            Return result
        End Function

    End Class

End Class

【讨论】:

  • 在该类中处理来自父级 DataGridView 的事件以开始动画是不是一个坏主意?我考虑过处理RowsAddedCellValueChanged 事件...
  • 我尝试过这样的事情,但 DGV 对此非常不满。不过,我还没有尝试过你提到的事件。不要以为我会放手。一旦我有正常工作的东西,我会在这里发布。使单元无效的(丑陋的)程序现在是 DGV 扩展方法(DataGridView.Animate())。如果您认为这可能是一种改进,我可以发布它。
  • @VBobCat 我添加了扩展方法。为克隆的单元格在内部生成 Paint 事件仍然没有运气。 DGV 不会升高它(仅针对第一行)。当您试图侵入其内部程序时,这确实非常明智。找到一些可行的例子也没有运气。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-14
  • 2014-02-22
相关资源
最近更新 更多