To clearly restate the problem - you need to provide a visual component to a client who will use it within their own program. The client's program, outside of your control, ties up its main(ie:UI) thread and you are required to have your visual component continue working while the client's program is frozen.
Let's be clear here - the fact that the client's program is freezing is their problem and it is the direct result of defects in their application code. What follows below is a hack, and an awful one that nobody should ever be using. So, while you CAN do the following, it should almost never be considered a recommended solution for anything. It flies in the face of all best-practice.
With that said, you need to create a second application context and a new message loop which can continue running beside the main UI thread. A class something like this would work :
Imports System.Threading
Public Class SecondUIClass
Private appCtx As ApplicationContext
Private formStep As Form
Private trd As Thread
Private pgBar As ProgressBar
Delegate Sub dlgStepIt()
Public Sub New()
trd = New Thread(AddressOf NewUIThread)
trd.SetApartmentState(ApartmentState.STA)
trd.IsBackground = True
trd.Start()
End Sub
Private Sub NewUIThread()
formStep = New Form()
pgBar = New ProgressBar()
formStep.Controls.Add(pgBar)
appCtx = New ApplicationContext(formStep)
Application.Run(appCtx)
End Sub
Public Sub StepTheBar()
formStep.Invoke(New dlgStepIt(AddressOf tStepIt))
End Sub
Private Sub tStepIt()
pgBar.PerformStep()
End Sub
End Class
Essentially what you are doing with the above class is creating a new application context within a new STA thread (giving that thread a message loop). That context contains a main form (giving the thread ownership of it and responsibility for its message processing) which can continue to operate aside from the main UI thread. This is much like having a program within a program - two UI threads, each with their own mutually exclusive set of controls.
Calls which interact with any of the controls owned by the new UI thread (or its form) must be marshalled with Control.Invoke
from the primary UI thread (or others) to ensure that your new UI thread is the one doing the interaction. You could also use BeginInvoke
here.
This class has NO cleanup code, no checks for safety, etc (be warned) and I'm not even sure it would finish up gracefully - I leave that task to you. This just illustrates a way to get started. In the main form you would do something like :
Public Class Form1
Private pgClass As New SecondUIClass
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Dim i As Integer
For i = 1 To 10
System.Threading.Thread.Sleep(1000)
pgClass.StepTheBar()
Next
End Sub
End Class
Running the application above would create Form1
as well as a second form created by pgClass
. Clicking Button1
on Form1
would lock up Form1 while it went through its loop but the second form would still remain alive and responsive, updating its progress bar each time Form1
called .StepTheBar()
.
The best solution in this case, really, is to have the 'client' learn how to program properly and avoid being stuck in this conundrum in the first place. In the case where that is completely impossible and you MUST create for them a component whch will remain alive despite their poor code then the above approach is probably your only recourse.
SetupProgressIndicator
method at the start, and anIncrementProgressIndicator
method from time to time. Can that be made to work? – CommunionDoLotsOfWork(progress_callback)
. Where do I use client to refer to ProgressDisplay itself? – CommunionProgressDisplay.Update
method? If so, to sounds like your saying ProgressDisplay is the client. – Bentlee