【问题标题】:Painting from a separate thread?从单独的线程中绘画?
【发布时间】:2013-04-10 13:41:14
【问题描述】:

我目前正在开发一款使用图形类创建游戏中所有图片的游戏,我还尝试从单独的线程绘制图片以阻止我的主线程冻结。但是每次我尝试运行该程序时,窗体上什么都没有出现,并且我收到此错误...

System.NullReferenceException:对象引用未设置为对象的实例。

在 c:\users\samuel\documents\visual studio 2012\Projects\Single_Player\Single_Player\Form1.vb:line 12 中的 Single_Player.Form1.mainPaint(PaintEventArgs e) 处

如果我的代码确实有效,我希望它会在屏幕上移动一个椭圆,无论如何这是我的代码...

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
    Dim paintT As New Threading.Thread(AddressOf mainPaint)
    paintT.Start()
End Sub

Private Sub mainPaint(e As PaintEventArgs)
    e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
    e.Graphics.CompositingQuality = Drawing2D.CompositingQuality.HighQuality
    Dim playerX As Integer = 0
    Dim playerY As Integer = 206
    Do While 0 / 0
        e.Graphics.DrawRectangle(New Pen(Color.FromArgb(128, Color.Black)), 0, 0, 884, 24)
        e.Graphics.FillRectangle(New SolidBrush(Color.FromArgb(128, Color.Black)), 0, 0, 884, 24)
        e.Graphics.DrawString("Life: 5", New Font("DigifaceWide", 20, GraphicsUnit.Pixel), New SolidBrush(Color.FromArgb(191, Color.Green)), 2, 0)
        e.Graphics.DrawString("Score: 0", New Font("DigifaceWide", 20, GraphicsUnit.Pixel), New SolidBrush(Color.FromArgb(191, Color.White)), 100, 0)
        e.Graphics.FillEllipse(New SolidBrush(Color.FromArgb(128, Color.Blue)), playerX, playerY, 24, 24)
        playerX = playerX + 1
        playerY = playerY + 1
        e.Graphics.Clear(Color.Transparent)
        Threading.Thread.Sleep(50)
    Loop
End Sub

更新(再次) 到目前为止我的代码(这次我通过设计窗口添加了一个计时器)...

Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
    Timer1.Interval = 50
    Timer1.Start()
End Sub

Private Sub mainBackgroundPB_Paint(sender As Object, e As PaintEventArgs) Handles Timer1.Tick
    e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
    e.Graphics.CompositingQuality = Drawing2D.CompositingQuality.HighQuality
    e.Graphics.DrawRectangle(New Pen(Color.FromArgb(128, Color.Black)), 0, 0, 884, 24)
    e.Graphics.FillRectangle(New SolidBrush(Color.FromArgb(128, Color.Black)), 0, 0, 884, 24)
    e.Graphics.DrawString("Life: 5", New Font("DigifaceWide", 20, GraphicsUnit.Pixel), New SolidBrush(Color.FromArgb(191, Color.Green)), 2, 0)
    e.Graphics.DrawString("Score: 0", New Font("DigifaceWide", 20, GraphicsUnit.Pixel), New SolidBrush(Color.FromArgb(191, Color.White)), 100, 0)
    e.Graphics.FillEllipse(New SolidBrush(Color.FromArgb(128, Color.Blue)), playerX, playerY, 24, 24)
    playerX = playerX + 1
    playerY = playerY + 1
    e.Graphics.Clear(Color.Transparent)
End Sub

【问题讨论】:

  • 这是因为PaintEventArgs为空,你没有从调用者线程将它传递给mainPaint
  • 还有你为什么要在不同的线程上绘画,如果你想重复发生某些事情,只需使用计时器,因为你不能从另一个线程绘画。
  • Do while 0/0 是一个奇怪的结构。这首先生成Double.Infinity,然后将其转换为布尔值。为什么不只使用 while true
  • @Sam stackoverflow.com/a/11753379/1112547 将绘画移至另一个方法并参考mainPaint中的该方法创建新线程
  • 处理Timer.Tick 事件而不是.paint 事件

标签: .net vb.net multithreading winforms graphics


【解决方案1】:

你不能在工作线程中绘制,窗口基本上是线程不安全的。当计时器滴答时,您的计时器 Tick 事件处理程序将崩溃并烧毁。 Tick 事件没有 PaintEventArgs 参数。

只有 Paint 事件具有这些参数,添加该事件并移动您的代码。您可以使用计时器触发绘制,使事件处理程序如下所示:

Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
    Me.Invalidate()
End Sub

【讨论】:

    【解决方案2】:

    以上答案不正确。它可以完成,然后它可以做得更好。

    定时器以预设的时间间隔运行,这对可预测性有好处,但对性能不利,因为它总是以相同的速度或更慢地运行。计时器在游戏中很昂贵,因此请相应地使用它们(如果有的话)。

    下面是我写的一个快速示例,展示了在线程中绘图,将其传递给委托并使用扫描层将图像写入主 UI。

    还有改进建议。

    Public Class Form1
    
    Enum enumGameObjectTypes
        RedBox = 0
        BlueBox = 1
        YellowCircle = 2
    End Enum
    
    Structure structGameObjects
        Dim Type As enumGameObjectTypes
        Dim Location As Point
    End Structure
    
    ' When you have global variables, they are accessible outside the thread.
    ' You cannot access them on the drawing thread however, without using SyncLock to lock
    ' the object.
    Public GameObjects As New List(Of structGameObjects)
    Public tDrawingThread As New System.Threading.Thread(AddressOf DrawingThread)
    
    ' We have to use delegates to pass the image
    Delegate Sub DrawingDelegateSub(TheDrawnImage As Image)
    Public DrawingDelegate As DrawingDelegateSub
    
    ' Set this to true when it's time to exit cleanly
    Public _ExitThreadSafelyNow As Boolean = False
    
    Public Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
        Dim rand As New Random
    
        For cnt = 1 To rand.Next(5, 20)
            ' Create some game objects
            Dim newObj As New structGameObjects
            With newObj
                .Type = rand.Next(0, 2)
                .Location = New Point(rand.Next(0, cnt * 100), rand.Next(0, cnt * 50))
            End With
        Next
    
        ' Set up a delegate (basically a worker that can "move data between threads" safely) 
        DrawingDelegate = AddressOf DrawImage
    
        ' Start the thread
        tDrawingThread.Start()
    End Sub
    
    Public Sub DrawImage(ByVal DrawnImage As Bitmap)
    
        Dim tmpImage As New Bitmap(1024, 768)
    
        ' Draw the image to the picture box using an intermediary layer...
        ' The reason we dont just say pb.Image = DrawnImage is because the bitmaps are passed by pointer references NOT just by the image
        ' So, passing it direct would be non-thread safe.
        '
        ' This also isn't the *right* way to do this, but it is just an example.
        '
        ' What we're doing is all the heavy lifting in the thread and then we're scanning pixel by pixel through the image drawn by the thread.
        ' 
        ' The performance here will be awful, so the fix; if you can understand how it works, is to use Bitmap.LockBits;
        ' Performance will ramp up 1000x right away. https://msdn.microsoft.com/en-us/library/5ey6h79d(v=vs.110).aspx
        For x = 1 To DrawnImage.Width - 1
            For y = 1 To DrawnImage.Height - 1
                tmpImage.SetPixel(x, y, DrawnImage.GetPixel(x, y))
            Next
        Next
    
        pb.Image = tmpImage
        Debug.Print("Wrote frame !")
    End Sub
    
    ' Main drawing loop
    Sub DrawingThread()
        Dim rectBounds As New Rectangle(0, 0, 1024, 768)
    
        '
        '
        Do Until _ExitThreadSafelyNow = True
    
            ' Set up the next frame
            Dim ImageBuffer As New Bitmap(rectBounds.Width, rectBounds.Height)
            Dim g As Graphics = Graphics.FromImage(ImageBuffer) ' Create the graphics object
            g.FillRectangle(Brushes.Black, rectBounds)
    
            ' Lock the gameobjects object until we're done using it.
            SyncLock GameObjects
                For Each obj In GameObjects
                    Select Case obj.Type
                        Case enumGameObjectTypes.RedBox
                            g.FillRectangle(Brushes.Red, New Rectangle(obj.Location.X, obj.Location.Y, 15, 15))
                            '
                        Case enumGameObjectTypes.BlueBox
                            g.FillRectangle(Brushes.Blue, New Rectangle(obj.Location.X, obj.Location.Y, 20, 20))
                            '
                        Case enumGameObjectTypes.YellowCircle
                            g.FillEllipse(Brushes.Yellow, New Rectangle(obj.Location.X, obj.Location.Y, 15, 15))
                            '
                        Case Else
                            g.FillEllipse(Brushes.Pink, New Rectangle(obj.Location.X, obj.Location.Y, 15, 15))
                    End Select
                Next
            End SyncLock
    
    
            '/// All done drawing by this point...
    
            'TODO: Eventually implement Bitmap.LockBits here!
    
    
            SyncLock ImageBuffer
                ' Send the image onto the main thread.
                Call DrawingDelegate(ImageBuffer)
            End SyncLock
    
    
        Loop
    End Sub
    
    Private Sub Form1_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing
    
        _ExitThreadSafelyNow = True
    End Sub
    End Class
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-27
      • 2021-11-19
      • 1970-01-01
      • 2012-05-29
      • 1970-01-01
      相关资源
      最近更新 更多