monty monty - 2 months ago 8x Question

Create Modeless Form - starts on top

[Warning - this is a trap for young players, you would normally never put any of your UI stuff to run in separate threads as the correct way is to put your long processing work into background threads instead. However, this solution was required due to the limitations of a legacy system.]

We have a fairly complicated application and wish to have a general-purpose modeless form that pops up over all the other (both modal and modeless) forms, with a caption (set new each time asked to show) and a spinning gif to show the user that there is background processing going on. This can get called from multiple places to start, and all overlapping, the last call should get to set the title/comment.

However, the original code did not always show up the form, and never showed it above the current modal form:

Private Sub StartLongRunningProcess(comment As String, title As String)
LongRunningProcess.MdiParent = MainForm
'...set title and comment here....
DoCenter(LongRunningProcess, MainForm)
End Sub
Public Sub HideLongRunningProcess()
End Sub

I've tried improving things by:

  • using actual objects instead of their classes

  • using a normal, non-Mdi, modeless form that I start with:

    _longRunningProcess = New LongRunningProcess(frmPC9main)

  • .TopMost = True

  • using
    instead of

  • even creating the
    in a separate thread such as suggested here which almost works except that it puts up the message "(Not Responding)" sometimes, which will make the user think to force-quit the application... the exact thing we're trying to avoid when they have long-running processes. BTW, I can't put my long-running processes into background threads as there are just too many, not possible to do with all anyway - they all need access to the main thread.

This really should be such a basic thing to accomplish, am I missing something?

Also note: apart from show/hide of the form, I don't need any other interaction with it.

[edit] The threading I've been trying is:

Dim _longRunningProcessThread As Thread
Private _longRunningProcess As LongRunningProcess = Nothing

Public Sub ShowLongRunningProcess(Optional ByVal comment As String = "", Optional ByVal title As String = "Processing in background")
MainForm.CheckForIllegalCrossThreadCalls = False

If _longRunningProcess Is Nothing Then
_longRunningProcessThread = New Thread(New ThreadStart(Sub() StartLongRunningProcess()))
_longRunningProcessThread.IsBackground = True
DoCenter(_longRunningProcess, _mainForm)
End If

MainForm.CheckForIllegalCrossThreadCalls = True

End Sub

Private Sub StartLongRunningProcess()
If _longRunningProcess Is Nothing Then
_longRunningProcess = New LongRunningProcess(_mainForm)
End If
End Sub

Public Sub HideLongRunningProcess()
MainForm.CheckForIllegalCrossThreadCalls = False
MainForm.CheckForIllegalCrossThreadCalls = True
End Sub

This now will appear on top, but when the main thread is accessing the db it shows as (not responding).


Here's a version of the code that runs on seperate threads. The code you provided above wasn't running a message loop on the second thread. Also setting CheckForIllegalCrossThreadCalls = false is a really bad idea for what you're trying to achieve.



    ProgressForm.ShowProgressForm("hello", "hello")

    Dim modalForm As New Form With {.Text = "MyModalForm"}



And here's the ProgressForm definition.

Public Class ProgressForm
    Inherits Form

    Private Shared sForm As ProgressForm
    Private Shared sThread As Thread

    Public Sub New()
        Me.TopMost = True
        Me.BackColor = Color.Green
        Me.Text = "Progress"
    End Sub

    Public Shared Sub Initialise()
        'Create the form
        sThread = New Thread(AddressOf ThreadFunc)
        While sForm Is Nothing OrElse sForm.InvokeRequired = False
        End While
    End Sub

    Public Shared Sub TearDown()
        sForm.BeginInvoke(Sub() Application.ExitThread())
    End Sub

    Private Shared Sub ThreadFunc()
        sForm = New ProgressForm
        'Dim handle = sForm.Handle
    End Sub

    Public Shared Sub ShowProgressForm(caption As String, title As String)
        If sForm.InvokeRequired Then
            sForm.BeginInvoke(Sub() ShowProgressForm(caption, title))
            sForm.Text = title
            'TODO: Caption
            sForm.Visible = True
            sForm.TopMost = True
        End If
    End Sub

    Public Shared Sub HideProgressForm()
        If sForm.InvokeRequired Then
            sForm.BeginInvoke(Sub() HideProgressForm())
            sForm.Visible = False
        End If
    End Sub

    Private Sub ProgressForm_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        If e.CloseReason = CloseReason.UserClosing Then
            Me.Visible = False
            e.Cancel = True
        End If
    End Sub
End Class