1

I would like to show a gif inside a Picture Box for 2 seconds on a separate thread from the main thread. I am running a timer that moves a Picture Box with an Image on the main thread.

To test I created a Picture Box and added same Image I start the background thread with a button click. The obvious ERROR or Issue is that the supposed Background Thread slows the Main Thread.

Creating and Implementing a Threads seems to offer two options BackgroundWorker and Task.Run.

I looked at this Code Magazine Article which offered way more options than I am grasping: Code Magazine Article

Also looked at this Article could not convert the C# code to VB YES I used a code converter: Stephen Cleary

My code is posted below for the Background Thread No need to post the Timer Tick Code.

Question what am I missing or what am I doing wrong OR is this just not possible?

Private Sub myThreadMethod()
    'Await
    'Async
    Dim myThread As New Thread(AddressOf myThreadMethod)
    myThread.IsBackground = True

    myThread.Start()
    If Me.InvokeRequired = True Then
        Me.Invoke(Sub()
                      'PbT.Location = New Point(128, 132)
                      PbT.Left -= 1
                      PbT.Top += 2
                  End Sub)
        'If PbT.Bounds.IntersectsWith(btnBot.Bounds) Then
        'TextBox1.Invoke(Sub() TextBox1.Text =
    End If
    If PbT.Location.Y > 500 Then
        PbT.Invoke(Sub() PbT.Location = New Point(350, 230))
        Thread.Sleep(9000)
        myThread.Abort()
    End If

End Sub

Answer to Question was added to by Craig and Answered by James_Duh


Public Class frmStart

Dim running As Boolean = False
Dim stopWatch As Stopwatch = New Stopwatch

Private Sub frmStart_MouseMove(sender As Object, e As MouseEventArgs) Handles Me.MouseMove
    Cursor.Clip = New Rectangle(Me.Location, Me.Size)
    btnLPad.Left = e.X
    btnCPad.Left = e.X + 28
    btnRPad.Left = e.X + 56
End Sub

Private Sub tmrMove_Tick(sender As Object, e As EventArgs) Handles tmrMove.Tick

    Static direction As New Point(0, 4)

    Static endTime As DateTime = DateTime.Now.AddYears(1)
    If DateTime.Now > endTime Then
        PbT.Visible = False
        endTime = DateTime.Now.AddYears(1)
    End If

    If _buttons.All(Function(x) x.Button.Visible = False) Then
        pbOne.Top = 300
        PbT.Visible = False
        tbAns.Visible = True

        stopWatch.Stop()
        Dim ts = stopWatch.Elapsed
        Dim elapsedTime = $"{ts.Minutes:0} Min {ts.Seconds:00} Sec"
        tbAns.Text = elapsedTime

        running = False
        direction = New Point(0, 4)

        tmrMove.Stop()
        MsgBox("You Win")
        stopWatch.Reset()
        '================
        tbAns.Visible = False
        ResetButtons()

    End If

    If pbOne.Bounds.IntersectsWith(btnLPad.Bounds) Then
        direction = New Point(-2, -3)
    End If
    If pbOne.Bounds.IntersectsWith(btnRight.Bounds) Then
        Static spC As Integer = 1
        spC += 1
        direction = If(spC Mod 2 = 0, New Point(-3, 2), New Point(-5, 1))
    End If

    If pbOne.Bounds.IntersectsWith(btnLeft.Bounds) Then
        direction = New Point(4, 2)
    End If

    If pbOne.Bounds.IntersectsWith(btnCPad.Bounds) Then
        direction = New Point(direction.X, -4)
    End If

    If pbOne.Bounds.IntersectsWith(btnRPad.Bounds) Then
        Static spA As Integer = 1
        spA += 1
        direction = If(spA Mod 2 = 0, New Point(1, -5), New Point(-3, -4))
    End If

    If pbOne.Bounds.IntersectsWith(btnTop.Bounds) Then
        Static spE As Integer = 1
        spE += 1
        direction = If(spE Mod 2 = 0, New Point(-3, 2), New Point(4, 2))
    End If

    If pbOne.Bounds.IntersectsWith(btnBot.Bounds) Then
        tmrMove.Stop()
        running = False
        pbOne.Top = 200
        PbT.Visible = False
        MsgBox("Press S to Start")
    End If

    pbOne.Left += direction.X
    pbOne.Top += direction.Y

    For Each x In _buttons
        If pbOne.Bounds.IntersectsWith(x.Button.Bounds) Then
            endTime = DateTime.Now.AddSeconds(2.0)
            x.Button.Visible = False
            x.Button.Location = New Point(350, -30)
            PbT.Location = New Point(x.Location.X + 20, 31)
            PbT.Visible = True
            direction = New Point(3, 3)
        End If
    Next
End Sub
Private Sub frmStart_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown

    If running AndAlso e.KeyCode = Keys.P Then
        tmrMove.Stop()
    End If

    If e.KeyCode = Keys.S Then

        If Not running Then
            stopWatch.Start()
            running = True
        End If

        tmrMove.Interval = 1
        tmrMove.Start()

    End If

End Sub
Public Sub frmStart_KeyPress(sender As Object, e As KeyPressEventArgs) Handles Me.KeyPress
    'Form Property KeyPreview needs to be set to True
    '=================================================

    If Asc(e.KeyChar) = 27 Then

        Const message As String = "YES" & "   Exit Program" + vbCrLf + vbNewLine + "NO" & "     Read Directions"
        Const caption As String = "Exit OR Return"

        Dim result = MessageBox.Show(message, caption, MessageBoxButtons.YesNo, MessageBoxIcon.Question)

        If result = DialogResult.Yes Then
            Application.Exit()
        ElseIf result = DialogResult.No Then
            frmInfo.Show()
            Close()
        End If
    End If

End Sub

Private _buttons As (Button As Button, Location As Point)() = Nothing

Private Sub frmStart_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    If _buttons Is Nothing Then
        _buttons =
        {
            (btnB1, New Point(29, 32)),
            (btnB2, New Point(110, 32)),
            (btnB3, New Point(191, 32)),
            (btnB4, New Point(272, 32)),
            (btnB5, New Point(353, 32)),
            (btnB6, New Point(434, 32)),
            (btnB7, New Point(515, 32)),
            (btnB8, New Point(596, 32)),
            (btnB9, New Point(677, 32))
        }
    End If
    ResetButtons()
End Sub

Private Sub ResetButtons()
    For Each x In _buttons
        x.Button.Visible = True
        x.Button.Location = x.Location
    Next
End Sub

End Class

This Code above was from Enigmativity and FIXES a number of issues. See his comments about the Stopwatch and playing the gif. As well the game plays 70% Faster with his code

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
Vector
  • 3,066
  • 5
  • 27
  • 54
  • 3
    You can't create, access, or update any UI element on a thread that is not the UI thread. Period. – Enigmativity Sep 14 '20 at 23:25
  • 2
    Also calling `Thread.Abort()` is dangerous. It should only ever be called when you are trying to forcibly close your app. It can leave the run-time in an undefined state and affect all of the remaining threads and code that you're running. You need to let threads end naturally. – Enigmativity Sep 14 '20 at 23:26
  • Why do you want to use a different thread for this? What is it that is taking a lot of processing that you want to move off of the UI thread? – Enigmativity Sep 14 '20 at 23:30
  • 2
    Move all from `Dim myThread ...` to `myThread.Start()` outside the method and start the thread from somewhere. Put the rest of the code in a `While true` loop and add e.g., `Thread.Sleep(50)`. Remove `myThread.Abort()` and just `Return`. You have to check whether your Control is disposed, because, if you close the Form in the meanwhile, *crash*. – Jimi Sep 14 '20 at 23:32
  • 2
    This can be simply done with a Timer or running a Task. In this case, you await `Task.Delay()` instead of `Thread.Sleep()` and `BeginInvoke()` instead of `InvokeRequired/Invoke()`. Pass a `CancellationToken` (from a `CancellationTokenSource`) if you want to `Cancel()` the Task before it completes. All 3 methods are now *asynchronous* (none will block the UI Thread. Not even the standard Timer, since you're just moving a Control). – Jimi Sep 14 '20 at 23:39
  • @Enigmativity Thanks for the Thread Abort I tried to STOP the Background Thread that was a last ditch effort I tried to put a gif in a Picture Box and play it for 2 sec when the btnBALL woiuld IntersectsWith a btnBRICK like the game Breakout – Vector Sep 14 '20 at 23:40
  • @Jimi OK Let be sure I understand can I Delay the Main Thread which is using most of the processing power and just show the Background thread for 2 sec But how do I stop the Background tread from continuing to RUN it has a gif which seems to never stop – Vector Sep 14 '20 at 23:45
  • 4
    Maybe I'm missing something here, but isn't three a bit of perhaps unintended recursion happening here? Creating the thread with a delegate inside the delegate method? that seems a little off to me – Hursey Sep 14 '20 at 23:53
  • 1
    You don't *Delay the Main Thread*. If you're referring to a Task, you use `await Task.Delay(milliseconds)` to create a Task that continues after the specified time. Using your Thread (modified as described), just `Return` after `Thread.Sleep(9000)` to end the Thread. I don't know what the GIF thing is referring to, since you have no GIF here. If you want some help about this, you'll have to explain what you're trying to achieve a lot better. If your PictureBox (I think), contains an animated GIF, there's nothing to do. The ImageAnimator will animate the GIF anyway. – Jimi Sep 14 '20 at 23:54
  • @Vector - What do you mean by "a gif which seems to never stop"? – Enigmativity Sep 15 '20 at 00:03
  • @Jimi Main Thread has a Picture Box that moves via a timer if it IntersectsWith a Button then the Button is moved off the Form at this point I want the Picture Box that has the gif to move to the old location of the Button and play the gif for 2 sec all the time this is happening the Picture Box with the that the timer is moving continues to move – Vector Sep 15 '20 at 00:06
  • @Enigmativity As long as the picture box with the gif is visible it is playing perhaps because it is inside the timer method just the picture box with the gif just siting on the form plays in an endless loop – Vector Sep 15 '20 at 00:08
  • Let me try and write a method that gets called when IntersectsWith happens that moves the Picture Box to the old location of the Button which represents a Brick like the game Breakout and have it visible for 2 sec STILL NOT sure this is a workable idea I did this same game in JavaFX and needed a lot of process to deal with threads – Vector Sep 15 '20 at 00:14
  • NO you can not control the gif on the main thread unless you set Enable to False it runs and trying to set the Picture Box back to Enabled to True with code no change Need a new Design Concept Thanks for the Comments – Vector Sep 15 '20 at 01:29
  • @Vector - I'm struggling to understand what you're saying. This is not a single sentence - "NO you can not control the gif on the main thread unless you set Enable to False it runs and trying to set the Picture Box back to Enabled to True with code no change Need a new Design Concept Thanks for the Comments". Can you please try to think through what you're saying and let us know clearly what your goals are? We would be very happy to help you if we knew what it was you need done. – Enigmativity Sep 15 '20 at 03:54
  • @Vector - Please don't make changes to the question like you did. Always add to the end if you need to add new information. Edits like that that invalidate existing answers make the questions hard to follow. – Enigmativity Sep 23 '20 at 07:33

1 Answers1

0

Trying to reproduce this NOT seeing your Timer tick code I wrote my own
Understanding the GAME design of Breakout will help for anyone trying to follow Vectors steps need to show the gif for X amount of Seconds
First you need to Stopwatch integrated into the Timer
Second you need to know when to set the END time in Seconds Logic would require this happens when the BALL IntersectsWith the BRICK
So we write a Function called Fire see code below
NO need to have a gif for each BRICK so now we need to move our one and only gif to the correct BRICK and let it run for X Seconds We also need to make the gif Visible WHY you might ask if Enabled and Visible they run for ever It was easier to just manage Visibility
You also need code inside the Timer Tick method to make the gif Invisible after X Seconds
Excuse my lack of declarative variables
pbOne = the BALL & btnB1 = BRICK & PbT = Picture Box with the gif

 Public Function Fire() As Integer
    'Set Stopwatch 5 sec in the future
    nT = CDbl(DateTime.Now.AddSeconds(5).ToString("ss"))
    Return CInt(nT)
End Function


Private Sub tmrMove_Tick(sender As Object, e As EventArgs) Handles tmrMove.Tick

    'nS is Rolling Time
    nS = CDbl(DateTime.Now.ToString("ss"))
    If nS > nT Then
        PbT.Visible = False
    End If

    If pbOne.Bounds.IntersectsWith(btnB1.Bounds) Then
        btnB1.BackColor = Color.Red
        btnB1.Visible = False
        Fire()
        Y = btnB1.Location.Y
        X = btnB1.Location.X
        PbT.Location = New Point(X + 20, Y)
        PbT.Visible = True
        btnB1.Location = New Point(350, -30)
        rndNUM = 8
    End If
James_Duh
  • 1,321
  • 11
  • 31
  • This is terrible. It doesn't work. But it can be fixed. What if the current time is `2020/09/16/ 07:57:54`? Then the timer would set `nT` to `"59"` and the timer would never set `PbT` to hidden. – Enigmativity Sep 15 '20 at 22:28
  • It also doesn't appear to do the computation for the `PbT` location correctly. – Enigmativity Sep 15 '20 at 22:30
  • @Enigmativity Make you a deal I will put the finished project EXE file and CODE in my GitHub so you can see it works I did change a few things in both the function Fire and the tmrMove I hard coded the Location data for PbT but honest the light weight game plays If I were a game developer I would not have used VB I would be over at Unity – Vector Sep 20 '20 at 20:47
  • @Vector - I'd love to see the final code. – Enigmativity Sep 20 '20 at 22:40
  • @Enigmativity I created a zip file of the project and included the Inno Setup EXE File it is at https://github.com/e-d-n/Breakout Enjoy – Vector Sep 21 '20 at 17:08
  • @Vector - I checked the code. If you hit one of the bricks at the top of the screen when your clock is at 58 or 59 seconds then your `PbT` will remain visible until another brick is hit. It doesn't go away after two seconds. – Enigmativity Sep 22 '20 at 06:56
  • 2
    @Vector - Have a look at this code: https://dotnetfiddle.net/eFqbKA . I did some refactoring. It should run the same as your original code - but without the 58 & 59 second bug. You can't run it from the link - just copy and paste that source into your code. – Enigmativity Sep 22 '20 at 08:14
  • 2
    @Enigmativity Thanks for the code The use of New Point really makes the game play faster I need to keep looking at the stopwatch code part I am missing the improvement So we are clear your code only shows the gif for 2 sec mine stumbles now and then It is a vast improvement we all get trapped by IF IT RUNS it must be correct I did try testing the time issue by changing my computer clock to 58 but I was looking at other issue YES I get it now Thanks – Vector Sep 22 '20 at 23:12