<

Still celebrating National IT Professionals Day with 3 months of free Premium Membership. Use Code ITDAY17

x

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

Published on
43,715 Points
32,914 Views
8 Endorsements
Last Modified:
Awarded
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
Comment
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 4
  • 3
  • 2
  • +5
16 Comments
 
LVL 2

Expert Comment

by:MrFantastic6
I get a null reference exception error occured when the splash.updateprogressbar gets hit.
0
 
LVL 86

Author Comment

by:Mike Tomlinson
Hi MrFantasic6,

What version VB.Net are you working in?...and at what line are you getting the error?
0
 
LVL 2

Expert Comment

by:MrFantastic6
I think I got it fixed now. i had to replace splash. with my form name splashscreen1.updateprogressbar to get it going.  I really liked the original progressbar1.performstep routine that was in the original coding, so i'm having to rework my math on a somewhat complex method of determining the number of steps to apply the percentage value.  
0
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 18

Expert Comment

by:John (Yiannis) Toutountzoglou
Hi mike...
What is the difference if i set your code just in a button click
i have my progress bar moving but the madeup string just shows only the last one ..i your case the
"Updating Doomahickies..."
if we are reffering in a certain form i am using just "Me." without a delegate function ... because i think it is not necessary..
what am i doing wrong?



PS: if you want me to ask a separate question i will do it..
Thank you
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        If Not IsValidIP(Me.TextBox1.Text) Then
            MessageBox.Show("This is not a correct IP Format", "Wrong IP", MessageBoxButtons.OK, MessageBoxIcon.Warning)
            Me.TextBox1.Text = ""
            Me.TextBox1.Focus()
            Return
        Else
            Dim MadeUpSteps() As String = {"Initializing...", "Authenticating...", "Retrieving Widgets...", "Loading Components...", "Updating Doomahickies..."}
' i am using your string just for example
            For i As Integer = 0 To MadeUpSteps.Length - 1
                Me.UpdateProgress(MadeUpSteps(i), CInt((i + 1) / MadeUpSteps.Length * 100))
                System.Threading.Thread.Sleep(1500)
            Next

        End If
    End Sub

Open in new window

0
 
LVL 18

Expert Comment

by:John (Yiannis) Toutountzoglou
I found it ...i forgot the Application.DoEvents()

Thank you for this this great article...
0
 
LVL 86

Author Comment

by:Mike Tomlinson
It only shows the last one because you have a tight loop in the main UI thread that Sleeps() and doesn't allow the message pump to be processed.  Therefore only the last message is displayed since it is processed AFTER the loop exits.  This wasn't an issue in the original code because it was simulating processing that occurs BEFORE the actual form was to be displayed...and the progress was being shown in a form in a different thread (the splash screen).

Your code could be "fixed" by adding Application.DoEvents() in the loop:

            For i As Integer = 0 To MadeUpSteps.Length - 1
                Me.UpdateProgress(MadeUpSteps(i), CInt((i + 1) / MadeUpSteps.Length * 100))
                Application.DoEvents()
                System.Threading.Thread.Sleep(1500)
            Next

*Though the long sleep in the main UI thread will cause a sluggish form.
0
 
LVL 18

Expert Comment

by:John (Yiannis) Toutountzoglou
Thank you for your reply..I solved it like you said..
something else now...
during the progress is it correct to call function according to the variable i ???
like the code below

(TextBox2 is a multiline textbox) and GetConnected is connection Function..

Seems to work fine..but i have doubts
..
Try
                Me.Button1.Enabled = False
                Me.Cursor = Cursors.WaitCursor
                Dim MadeUpSteps() As String = {"Initializing...", "Pink IP Address...", "Connecting to Controller...", "Loading Components...", "Completed..."}

                For i As Integer = 0 To MadeUpSteps.Length - 1
                    Me.UpdateProgress(MadeUpSteps(i), CInt((i + 1) / MadeUpSteps.Length * 100))
                    Application.DoEvents()
                    System.Threading.Thread.Sleep(1500)
                    If i = 0 Then
                        Me.TextBox2.Text += MadeUpSteps(0) & "..OK" & vbNewLine
                    ElseIf i = 1 Then
                        Me.Cursor = Cursors.WaitCursor
                        GetConnected(Me.TextBox1.Text, "10001")
                        If exFlag = True Then
                            Me.TextBox2.Text += exceptionMessage & vbNewLine
                        End If
                        Me.TextBox2.Text += MadeUpSteps(1) & ".." & tcpClient.Connected.ToString & vbNewLine
                    ElseIf i = 2 Then
                        Me.TextBox2.Text += MadeUpSteps(2) & ".." & tcpClient.Connected.ToString & vbNewLine

                    ElseIf i = 3 Then
                        Me.TextBox2.Text += MadeUpSteps(3) & ".." & tcpClient.Connected.ToString & vbNewLine
                    ElseIf i = 4 Then
                        If tcpClient.Connected = False Then
                            Me.TextBox2.Text += "Connection to Controller Failed...Check Controller's Cable or Verify Valid IP Address"
                            Me.Button3.Visible = True
                            Me.Button1.Enabled = True
                            Me.Button1.Visible = False
                        Else
                            Me.TextBox2.Text += MadeUpSteps(3)
                        End If
                    End If

                Next
                Me.Cursor = Cursors.Default
            Catch ex As ArgumentOutOfRangeException
                Me.TextBox2.Text += ex.Message & vbNewLine
            Catch ex2 As SocketException
                Me.TextBox2.Text += ex2.Message & vbNewLine
            Catch ioEx As IO.IOException
                Me.TextBox2.Text += ioEx.Message & vbNewLine
            End Try
        End If




Public Sub GetConnected(ByVal IpAdd As String, ByVal Iport As String)
        Try
            tcpClient.Connect(IpAdd, Iport)
            networkStream = tcpClient.GetStream()
                   Catch ex As ArgumentOutOfRangeException
            exceptionMessage = ex.Message
            exFlag = True
        Catch ex2 As SocketException
            exceptionMessage = ex2.Message
            exFlag = True
        Catch ioEx As IO.IOException
            exceptionMessage = ioEx.Message
            exFlag = True
        End Try
    End Sub

Open in new window

0
 
LVL 86

Author Comment

by:Mike Tomlinson
Except for i=2 and i=3, something slightly different is going on in each block so there really isn't any better way of doing it (since the code can't be generalized for all values if 'i').
0
 
LVL 18

Expert Comment

by:John (Yiannis) Toutountzoglou
that is clear ...the other values of i are just for textbox2 display...
Thank you very very much...
0
 

Expert Comment

by:DaveJ-UK
Excellent example - very easy to understand. Thank you.
0
 

Expert Comment

by:silencox
Hello

Thanks for article it's excellent example and right what i need in my application

however i encountered one strange issue
my splash screen is displayed twice
first time ( IMO ) from application properties, and second time when i call update progress from main form

can you provide me full code for this ( there is slight possibility that i interpret my self something wrong ) since i start to code in VB.Net few month ago

Thanks in advanced
Sven
0
 
LVL 28

Expert Comment

by:Ark
Just my 2 cents to add - 1 more delegate might be usefull
Public Delegate Sub CloseDelegate()
Public Sub CloseSplash()
    If Me.InvokeRequired Then
        Me.Invoke(New CloseDelegate(AddressOf Me.CloseSplash))
    Else
        Me.Close()
    End If
End Sub

Open in new window

0
 

Expert Comment

by:chmarch
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
0
 

Expert Comment

by:BetterWaysForward
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
0
 
LVL 28

Expert Comment

by:Ark
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
0
 

Expert Comment

by:BetterWaysForward
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!
0

Featured Post

What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

Join & Write a Comment

In this video, Percona Solutions Engineer Barrett Chambers discusses some of the basic syntax differences between MySQL and MongoDB. To learn more check out our webinar on MongoDB administration for MySQL DBA: https://www.percona.com/resources/we…
We’ve all felt that sense of false security before—locking down external access to a database or component and feeling like we’ve done all we need to do to secure company data. But that feeling is fleeting. Attacks these days can happen in many w…

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month