If you want to marshal a method call to the UI thread in a form, you usually call Invoke
on the form or one of its controls to invoke a method on the thread that owns it. That's not an option in this case, because the form is not owned by the UI thread.
In classes other than forms, you usually use the SynchronizationContext
class, getting the Current
value when the object is created and then using it in code executed on a secondary thread. That's what you should do here, but with a variation.
The variation is that you can't get SynchronizationContext.Current
in the form itself, because it is created on a secondary thread. You need to get SynchronizationContext.Current
in the calling form first, in code that is executed on the UI thread. When you then execute code on a secondary thread to create and show the second form, you pass in that SynchornizationContext
instance and then use it to execute code on the UI thread. Here's a simple example:
Primary (startup) form:
Imports System.Threading
Public Class Form1
'The SynchronizationContext for the UI thread.
Private uiContext As SynchronizationContext = SynchronizationContext.Current
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Display a new Form2 instance on a secondary thread.
Call New Thread(Sub() Call New Form2(uiContext).ShowDialog()).Start()
End Sub
End Class
Secondary form:
Imports System.Threading
Public Class Form2
'The SynchronizationContext for the UI thread.
Private uiContext As SynchronizationContext
Public Sub New(uiContext As SynchronizationContext)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.uiContext = uiContext
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Display text on the default instance of Form1.
'This text will not be seen because the open Form1 instance is not owned by the current thread.
Form1.Label1.Text = Date.Now.ToString()
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
'Display a new Form3 instance on the UI thread.
uiContext.Send(Sub(state) Call New Form3().Show(), Nothing)
Close()
End Sub
End Class
Tertiary form:
Public Class Form3
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Display text on the default instance of Form1.
'This text will be seen because the open Form1 instance is owned by the current thread.
Form1.Label1.Text = Date.Now.ToString()
End Sub
End Class
The primary form will require a Button
and a Label
, the secondary form will require two Buttons
and the tertiary form will require one Button
.
When you click the Button
on the primary form, it will create and display the secondary form on a secondary thread and pass it the SynchrinizationContext
for the UI thread. You can prove to yourself that the secondary form is owned by a secondary thread because it was displayed by calling ShowDialog
but you can still access the primary form. Also, you can click the first Button
and the Label
on the primary form will be unaffected. That's because the code is executed on a secondary thread and thus the default instance of Form1
for that thread is not the one displayed.
You can then click the second Button
on the secondary form to create and display the tertiary form on the UI thread and close the secondary form. You can prove to yourself that it is owned by the UI thread by clicking the Button
and seeing the Label
on the primary form update. That shows that the default instance for the current thread is the instance currently displayed, so the current thread must be the UI thread.