0

In .NET 4.0 I'm dealing with an app which has a long loading time (about 20 seconds), so I wanted to display a swish scrolling marquee on a form that comes on top of the application whilst it is loading.

Since the main UI thread is doing all the loading of data UI elements, I couldn't get that to execute on a separate thread, so I ended up trying to run the form on a new thread. I ended up sticking this code in the form itself, to have it show itself on a new thread, like this:

Public Class frmWait

Public Property Message As String
    Get
        Return Me.lblMessage.Text
    End Get
    Set(value As String)
        If Not String.IsNullOrEmpty(value) Then
            Me.lblMessage.Text = value
        Else
            Me.lblMessage.Text = DefaultMessage
        End If
    End Set
End Property

Private OwnerThread As Thread
Private OwnerForm As Form
Private Const DefaultMessage As String = "しばらくお待ちください..."

Public Sub New(ByVal ParentForm As Form)

    InitializeComponent()

    Me.Message = DefaultMessage
    Me.OwnerForm = ParentForm

End Sub

Public Sub New(ByVal Message As String, ByVal ParentForm As Form)

    Call InitializeComponent()

    Me.Message = Message
    Me.OwnerForm = ParentForm

End Sub

Public Sub ShowOnThread()

    ' Position the form in the center of the owner
    With Me.OwnerForm
        Dim ownerCenter As New Point(.Location.X + CInt(.Width / 2), .Location.Y + CInt(.Height / 2))
        Me.Location = New Point(ownerCenter.X - CInt(Me.Width / 2), ownerCenter.Y - CInt(Me.Height / 2))
    End With

    Me.OwnerThread = New Thread(New ThreadStart(AddressOf Me.ShowDialog))
    Call Me.OwnerThread.Start()

End Sub

Public Shadows Sub Close()

    If Me.OwnerThread IsNot Nothing AndAlso Me.OwnerThread.IsAlive Then
        Call Me.OwnerThread.Abort()
    Else
        Call MyBase.Close()
    End If

End Sub

End Class

This is probably quite clumsy, but I am showing it in different places in the application, so this seemed the most code-efficient way of doing this...

It actually works quite well, but I am encountering problems from time to time with this and need some help on how to address these issues.

  • Sometimes when the form gets closed I get an error about the thread being aborted in an unsafe manner.

  • At the moment I position the form manually in the centre of the form I want it to cover form. Ideally I'd like to be able to call .ShowDialog(ParentForm) on it, but of course that raises an exception because of cross-thread access from one form to the other.

Any suggestions on how to resolve this would be most appreciated. Because I know virtually nothing about threading I probably coded this like a monkey, so if there is a better method to get this done, I would very much like to know about it.

The code I list is in VB.NET, but answer code in C# is fine too (for any overzealous retaggers)

UPDATE:

I realise now that I should have given a lot more details in my question... The wait form is actually not the first form I am displaying the app. There first is a login screen. When the user is authenticated, the login form launches the main interface of the app, which is the form which actually takes a long time to load.

I am displaying the wait form in between the login form and the main interface. I also use this form to cover for any long running tasks launched on the main interface by the user.

yu_ominae
  • 2,975
  • 6
  • 39
  • 76
  • Using ShowDialog as the thread-methid is iffy. Better write your own. And the Abort() is dangerous and unnecessary. – H H Mar 22 '13 at 09:03
  • @HenkHolterman Thanks, but what would you suggest instead of these? For `ShowDialog` I imaging `Application.Run()`? – yu_ominae Mar 22 '13 at 09:10

4 Answers4

1

Instead of trying to show a dialog on a separate thread you should be moving your loading code to a separate thread / background worker.

Then just start your threads and show the progressbar on form_load and hide the progressbar when the thread completes:

Dim _progressForm As frmLoading

    Private Sub frmMain_Load(sender As System.Object, e As System.EventArgs) Handles Me.Load
        'start loading on a separate thread
        BackgroundWorker1.RunWorkerAsync()
        'show a marquee animation while loading
        _progressForm = New frmLoading
        _progressForm.ShowDialog(Me)
    End Sub

    Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        'simulate a long load
        For i As Integer = 1 To 10
            System.Threading.Thread.Sleep(1000)
        Next
    End Sub

    Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
        _progressForm.Close()
    End Sub
Matt Wilko
  • 26,994
  • 10
  • 93
  • 143
  • This is not always an option. Startup can sometimes involve a lot of loading and initialization that has to be done by the UI thread (specifically when the UI thread has to be the one to create objects and controls that it must own once the program gets going). This is a special case where heavy lifting is not about background tasks, but is about setting up what can sometimes be a resource intensive interface. – J... Mar 22 '13 at 10:40
  • That is actually the situation I'm in. I initialise a couple of ActiveX controls which I load into tabs in my main form and as the code is now I can't initialise on a different thread. – yu_ominae Mar 24 '13 at 23:41
1

VisualStudio can do this for you. You can certainly write your own, of course, but if you look at your project options under Application there is a setting for Splash Screen. You can assign any form (other than the startup form) to be your splash screen. This runs on its own thread and you can marshall calls to it during startup to advance a progress bar, etc.

MSDN : Splash Screen

J...
  • 30,968
  • 6
  • 66
  • 143
  • That would be very interesting if I could call the splashscreen multiple times from within the application. Is that possible? – yu_ominae Mar 24 '13 at 23:56
  • I just gave it a try, but unfortunately I can't figure out how to get that to work. – yu_ominae Mar 25 '13 at 00:17
  • @yu_ominae - can you be more specific? Make a form, assign it to be the Splash Screen... what doesn't work? This implementation only runs until the startup form has completed loading. Why do you want to call it multiple times? – J... Mar 25 '13 at 00:19
  • The SplashScreen itself works fine. The thing is that I don't need a splash screen exactly.... It's more complicated, because I have a login dialogue which then launches the main interface. I need a wait form in between the login and the main interface. I also use that form to fill in for any time-intensive operations started from the main interface. That's really why I am not already using a splash screen, but attempted to make my half-arsed wait form :( – yu_ominae Mar 25 '13 at 00:24
  • @yu_ominae perhaps an approach like this, then? http://stackoverflow.com/a/11156404/327083 – J... Mar 25 '13 at 02:14
  • I think that's what I need, many thanks for finding that question! – yu_ominae Mar 25 '13 at 02:30
0

Have a look at TaskFactory.New

Run your threaded code of a different thread to keep the UI responsive

http://msdn.microsoft.com/en-us/library/ee782519.aspx?cs-save-lang=1&cs-lang=vb#code-snippet-3

Chris McKelt
  • 1,378
  • 2
  • 17
  • 38
0

You could use the ApplicationContext class to allow the splash screen to be run first and then swap in the Main form when it is ready. You use it in place of your main form in the call to Application.Run:

Application.Run(new MyApplicationCtx())

Just googled up a article for you as well:

http://www.codeproject.com/Articles/5756/Use-the-ApplicationContext-Class-to-Fully-Encapsul

tcarvin
  • 10,715
  • 3
  • 31
  • 52