Community Pick: Many members of our community have endorsed this article.
Editor's Choice: This article has been selected by our editors as an exceptional contribution.

Updating a Splash Screen with Loading Progress in a VB.Net WinForms Application

Mike TomlinsonHigh School Computer Science, Computer Applications, Digital Design, and Mathematics Teacher
CERTIFIED EXPERT
Published:
Updated:
Since .Net 2.0, Visual Basic has made it easy to create a splash screen and set it via the "Splash Screen" drop down in the Project Properties.  A splash screen set in this manner is automatically created, displayed and closed by the framework itself.  The default minimum display time is two seconds, and the splash will stay open longer if necessary until the main form has completely loaded.  All of this is well documented on MSDN, commonly known, and discussed pretty extensively in tutorials easily found on the internet.

The "Splash Screen" Setting in Project Properties:
Splash Screen set via Project --> Properties
What isn't commonly found, though, are good examples of how to update the splash screen with progress information from the main form as it loads.  Many examples and tutorials simply use a static splash screen and leave it at that.  MSDN provides an example of how to "update the splash screen with status information" in the MSDN documentation of: My.Application.SplashScreen() property.

In that example, the code is being run from the Application.Startup() event, and changes to the splash screen are done in a direct manner:
Private Sub MyApplication_Startup(ByVal sender As Object, ByVal e As Microsoft.VisualBasic.ApplicationServices.StartupEventArgs) Handles Me.Startup
                          ' Get the splash screen.
                          Dim splash As SplashScreen1 = CType(My.Application.SplashScreen, SplashScreen1)
                          ' Display current status information.
                          splash.Status = "Current user: " & My.User.Name
                      End Sub
                      

Open in new window


Careful!  Multi-threading ahead...

The code is misleading because it implies that we can do this from anywhere.  What isn't well documented or being made clear is that the splash screen and the main form of an application actually run in two different threads.  Attempts to use similar code from the Load() event of your main form will result in varying degrees of success depending upon your version of Visual Studio.  The code does demonstrate that "My.Application.SplashScreen" can be used to obtain a reference to the instance of the splash screen displayed automatically by the framework.  Well use that little nugget later...

Since the splash screen and the main form are in two different threads, the correct approach to communicating between them is to use the Invoke() method with a delegate.  This is covered in the MSDN article, How to: Make Thread-Safe Calls to Windows Forms Controls.

Many don't even think to use this approach, though, since cross-thread communication is not normally an issue when dealing with two forms.  The splash screen, then, is an exception to the rule!

The pattern outlined in the article above is as follows:
Delegate Sub SetTextCallback([text] As String)
                      
                      Private Sub SetText(ByVal [text] As String)
                          If Me.textBox1.InvokeRequired Then
                              Dim d As New SetTextCallback(AddressOf SetText)
                              Me.Invoke(d, New Object() {[text]})
                          Else
                              Me.textBox1.Text = [text]
                          End If
                      End Sub
                      

Open in new window


We use InvokeRequired() to determine if the calling thread is different from the thread that created the control.  If yes, then we create an instance of the delegate that points to the same exact method.  Next we use Invoke() to run the delegate on the thread that created the control and pass the parameters using an array of Object.  This actually results in a recursive call since the delegate points to the same method.  On the second run InvokeRequired() will return false and the Else block will execute where the control can be safely updated.

So let's apply the same pattern to a splash screen.  Below is a simple setup consisting of a Borderless Form with a BackgroundImage, Label and a ProgressBar:
Splash Screen Form Setup
Here is the code for frmSplashScreen:
Public Class frmSplashScreen
                      
                          Private Delegate Sub UpdateProgressDelegate(ByVal msg As String, ByVal percentage As Integer)
                      
                          Public Sub UpdateProgress(ByVal msg As String, ByVal percentage As Integer)
                              If Me.InvokeRequired Then
                                  Me.Invoke(New UpdateProgressDelegate(AddressOf UpdateProgress), New Object() {msg, percentage})
                              Else
                                  Me.Label1.Text = msg
                                  If percentage >= Me.ProgressBar1.Minimum AndAlso percentage <= Me.ProgressBar1.Maximum Then
                                      Me.ProgressBar1.Value = percentage
                                  End If
                              End If
                          End Sub
                      
                          Private Sub frmSplashScreen_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
                              ' Draw a Black Border around the Borderless Form:
                              Dim rc As New Rectangle(0, 0, Me.ClientRectangle.Width - 1, Me.ClientRectangle.Height - 1)
                              e.Graphics.DrawRectangle(Pens.Black, rc)
                          End Sub
                      
                      End Class
                      

Open in new window


Note that I'm using "Me" instead of a control name to check for InvokeRequired().  The "Me" in this case represents the Form itself and is valid since Forms also Inherit from Control.  All controls run in the same thread as the form that contains them so this is a clean and safe method of checking.  Also note that in the Else block I am updating both the Label and the ProgressBar at the same time.  You don't need a separate method with an accompanying delegate for every control; just check against the form and update all controls at once.  The delegate being used receives both parameters and the Object array contains both parameters passed to the method.

With that code in place, all the main form has to do is call the UpdateProgress() method and the splash screen will take care of the rest.  Remember, a reference to the splash screen instance can be obtained with "My.Application.SplashScreen".  So here is an example Load() event for a main form that updates the splash screen with made up statuses:
Public Class Form1
                      
                          Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
                              Dim splash As frmSplashScreen = CType(My.Application.SplashScreen, frmSplashScreen)
                              Dim MadeUpSteps() As String = {"Initializing...", "Authenticating...", "Retrieving Widgets...", "Loading Components...", "Updating Doomahickies..."}
                      
                              For i As Integer = 0 To MadeUpSteps.Length - 1
                                  splash.UpdateProgress(MadeUpSteps(i), CInt((i + 1) / MadeUpSteps.Length * 100))
                                  System.Threading.Thread.Sleep(1500)
                              Next
                          End Sub
                      
                      End Class
                      

Open in new window


The Splash Screen in Action!
Idle-Mind-346085.flv

This article hasn't really introduced anything new or mind blowing...it just puts two and two together to accomplish something that really should be much simpler!
8
42,847 Views
Mike TomlinsonHigh School Computer Science, Computer Applications, Digital Design, and Mathematics Teacher
CERTIFIED EXPERT

Comments (17)

Commented:
Hello,

Thank you for the article, an excellent example and what I need for my application.

My question now becomes, how can I determine what is occurring in my application prior to the formMain_Load() event being triggered?  The splash screen is visible and active for 15 seconds prior to reaching the breakpoint at the first line of the MyBase.Load event of formMain.  Afterwards, everything is as expected.  What is happening during this time prior to the main form getting loaded?  Any information is greatly appreciated.  Thank you for your time and help.

Christopher
Excellent article from which I gained a better understanding of the use of Delegates.

But I have a similar but different scenario with a common problem: We have a DLL that has inbuilt  forms that provide tools to manage user preferences. A lot of data is loaded when instantiating some classes in the DLL, so I wanted to create an embedded SplashScreen form (within the DLL) that can be called by processes that take a while to load. Of course, I found out that the Application.SplashScreen feature (in Project.Properties)  is NOT available in a DLL (disabled).
So, inside the DLL, I created a borderless form (SplashScreen_PLM) which has a label (lblMessageText), a progressbar, and a Timer control. The timer control was set to 100ms, and upon firing it would increment the progressbar value by 1, update the label with "Please wait..." & progressbar.value, by calling a routine that implements my version of your CallBack delegate routine (see code below).

Upon loading a form internal to the DLL, I instantiate an instance of SplashScreen_PLM (form), .Show() it, load the desired form, and close the SplashScreen_PLM form after loaded.

The loading and unloading of the splash form works just fine, but the problem is that the ProgressBar and Label do not update!

(When loading the splash form from outside the DLL, by itself, the progress bar and label update as it should.)

Any thoughts or suggestions will be welcome. Thank you!


Public Class SplashScreenPLM
    Inherits Windows.Forms.Form

    Private Delegate Sub SetTextCallback([text] As String)

    Private Sub SetText([text] As String)
        If Me.InvokeRequired Then
            Dim d As New SetTextCallback(AddressOf SetText)
            Me.Invoke(d, New Object() {[text]})
        Else
            lblMessageText.Text = "Please wait ... " & [text]
            ProgressBar1.Increment(1)
            If ProgressBar1.Value >= ProgressBar1.Maximum Then
                ProgressBar1.Value = 0
            End If
            Application.DoEvents()
        End If
    End Sub
    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        SetText(ProgressBar1.Value.ToString)
    End Sub

    Private Sub SplashScreenPLM_Load(sender As Object, e As EventArgs) Handles Me.Load
        Timer1.Enabled = True
        Timer1.Start()
    End Sub
End Class
Ark
CERTIFIED EXPERT

Commented:
Start your splash form in a different thread:
Dim trd as New Threading.Thread(AddressOf StartSplash)
trd.IsBackground=True
trd.Start
Private Sub StartSplash()
   Dim f As New SplashScreenPLM
   f.Show
End Sub

Open in new window

Or use backgroundworker to start splash
I used the threading example above, but oddly, the SplashScreenPLM form just 'flashed' for an instant then disappeared. To keep things simple, I elected to NOT try using a progress bar with updated message, using instead a static image on the background of my splash form. This works just fine.

Thanks all the same, everyone!
To the Autor,
I'm trying to use your code in windows app.
It is working as described, but I'm having Login form in my Main Form Load event and
when Login form opend it is not active.
What could be done to reslove this isue.
Thank you for asistance (if this post comes to you)

View More

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.