0

Here is a simple VB.Net Forms program. The form contains a TabControl, which has 2 pages. On TabPage1 there is a Button, and a PictureBox, which contains a small 'Waiting' image. Initially the PictureBox is hidden. The TabControl starts by showing TabPage1.

What I would like to happen is that when the Button is pressed, the PictureBox is made visible, then, my SlowRoutine() is called, then the PictureBox is hidden, then I swap to TabPage2.

What actually happens is that when the Button is pressed, we wait 2 seconds, then we swap to TabPage2. I never see the PictureBox.

If I uncomment the MessageBox line, just to add a halt to the program-flow, then press the Button, the following occurs: 2 seconds passes, and then the PictureBox and the MessageBox appear. Clicking on the MessageBox closes it, and we go to TabPage2. Flipping back to TabPage1 shows that the PictureBox has been hidden.

The order of events is not happening in a logical way. How can I fix this, please?

Public Class Form1
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
        PictureBox1.Visible = False
        PictureBox1.Hide()
        PictureBox1.SendToBack()
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        PictureBox1.Visible = True
        PictureBox1.Show()
        PictureBox1.BringToFront()

        SlowRoutine()

        ' MessageBox.Show("I am waiting")

        PictureBox1.Visible = False
        PictureBox1.Hide()
        PictureBox1.SendToBack()
        TabControl1.SelectTab("TabPage2")
    End Sub

    Private Sub SlowRoutine()
        ' My SLOW ROUTINE: wait 2 seconds
        Threading.Thread.Sleep(2000)
    End Sub

End Class
Tim Makins
  • 394
  • 4
  • 12
  • 2
    This: `Threading.Thread.Sleep(2000)` is blocking the UI thread. The Controls in your Form won't be updated anymore, You can set the `PicutereBox.Visible = true` then `.Refresh()` it (all the `.Hide()`, `.SendToBack()` things you can remove). You should use an async method and await for it (simulate work with `await Task.Delay(2000)`) – Jimi Apr 22 '19 at 15:38
  • 1
    The events occur in the way you described, however your `SlowRoutine()` blocks the UI thread, making it impossible for the user interface to redraw itself. Due to this the drawing of the picture box is delayed until _after_ your routine has finished, which is at the same time that you switch tab page. Long-running code should never be run on the UI thread. Instead, start a separate thread (or preferably a task) that runs the routine "in the background", allowing your UI to remain free and redraw itself whenever necessary. – Visual Vincent Apr 22 '19 at 15:38
  • If I re-write my Slow Routine as: For a As Integer = 0 To 1000000000 Next the same things happen. – Tim Makins Apr 22 '19 at 15:42
  • 1
    In addition to my previous comment, when your task is finished you need to marshal a call to the UI thread, telling it to do whatever you want to do when the routine has finished (in this case switch tabs). Links to get you started: [Task-based asynchronous programming](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming) || [How can I run code in a background thread and still access the UI?](https://stackoverflow.com/a/45571728) (the latter starting from _**Accessing the UI thread**_) – Visual Vincent Apr 22 '19 at 15:45
  • If you use a close loop, that also blocks the UI thread (the current thread). Since it's busy looping, it cannot do anything else. Thus, your Controls won't be updated (repainted). Use a Task. You can then have: `Private Async Sub Button1_Click(...) await SlowRoutine()` and `Private Async Function SlowRoutine() as Task await Task.Delay(2000) End Function` – Jimi Apr 22 '19 at 16:02

1 Answers1

3

Thanks to all. Here is the working code based on those comments, in case anyone else needs to do a similar task:

Public Class Form1
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
        PictureBox1.Visible = False
    End Sub

    Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        PictureBox1.Visible = True
        Await SlowRoutineAsync()
        PictureBox1.Visible = False
        TabControl1.SelectTab("TabPage2")
    End Sub

    Private Async Function SlowRoutineAsync() As Task
        ' My SLOW ROUTINE: wait 2 seconds
        Await Task.Delay(2000)
    End Function

End Class
Tim Makins
  • 394
  • 4
  • 12