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

AID: 3671
  • Status: Published

10650 points

  • ByIdle_Mind
  • TypeTutorial
  • Posted on2010-09-08 at 23:15:22
Awards
  • Community Pick
  • Experts Exchange Approved
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:
SplashScreenSetViaProjectPropert.jpg
  • 130 KB
  • Splash Screen set via 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

                                    
1:
2:
3:
4:
5:
6:

Select allOpen 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

                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:

Select allOpen 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:
SplashScreenFormSetup.jpg
  • 48 KB
  • Splash Screen Form Setup
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

                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:

Select allOpen 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

                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:

Select allOpen in new window



The Splash Screen in Action!
Idle-Mind-346085.flv
  • 157 KB
  • 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!
    Asked On
    2010-09-08 at 23:15:22ID3671
    Tags

    VB.Net

    ,

    Visual Basic

    ,

    Splash Screen

    ,

    Update

    ,

    Progress

    ,

    WinForms

    Topic

    Microsoft Visual Basic.Net

    Views
    5678

    Comments

    Expert Comment

    by: MrFantastic6 on 2010-10-18 at 17:21:40ID: 20604

    I get a null reference exception error occured when the splash.updateprogressbar gets hit.

    Author Comment

    by: Idle_Mind on 2010-10-18 at 18:19:37ID: 20605

    Hi MrFantasic6,

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

    Expert Comment

    by: MrFantastic6 on 2010-10-18 at 18:24:35ID: 20606

    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.  

    Expert Comment

    by: jtoutou on 2010-12-15 at 08:01:41ID: 22086

    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
    
                                            
    1:
    2:
    3:
    4:
    5:
    6:
    7:
    8:
    9:
    10:
    11:
    12:
    13:
    14:
    15:
    16:
    

    Select allOpen in new window

    Expert Comment

    by: jtoutou on 2010-12-15 at 08:38:52ID: 22087

    I found it ...i forgot the Application.DoEvents()

    Thank you for this this great article...

    Author Comment

    by: Idle_Mind on 2010-12-15 at 08:39:22ID: 22088

    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.

    Expert Comment

    by: jtoutou on 2010-12-15 at 10:40:53ID: 22094

    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
    
                                            
    1:
    2:
    3:
    4:
    5:
    6:
    7:
    8:
    9:
    10:
    11:
    12:
    13:
    14:
    15:
    16:
    17:
    18:
    19:
    20:
    21:
    22:
    23:
    24:
    25:
    26:
    27:
    28:
    29:
    30:
    31:
    32:
    33:
    34:
    35:
    36:
    37:
    38:
    39:
    40:
    41:
    42:
    43:
    44:
    45:
    46:
    47:
    48:
    49:
    50:
    51:
    52:
    53:
    54:
    55:
    56:
    57:
    58:
    59:
    60:
    61:
    62:
    63:
    

    Select allOpen in new window

    Author Comment

    by: Idle_Mind on 2010-12-15 at 11:50:50ID: 22099

    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').

    Expert Comment

    by: jtoutou on 2010-12-15 at 12:04:28ID: 22100

    that is clear ...the other values of i are just for textbox2 display...
    Thank you very very much...

    Expert Comment

    by: DaveJ-UK on 2011-08-02 at 04:57:47ID: 30223

    Excellent example - very easy to understand. Thank you.

    Add your Comment

    Please Sign up or Log in to comment on this article.

    Join Experts Exchange Today

    Gain Access to all our Tech Resources

    Get personalized answers

    Ask unlimited questions

    Access Proven Solutions

    Search 3.2 million solutions

    Read In-Depth How-To Guides

    1000+ articles, demos, & tips

    Watch Step by Step Tutorials

    Learn direct from top tech pros

    And Much More!

    Your complete tech resource

    See Plans and Pricing

    30-day free trial. Register in 60 seconds.

    Loading Advertisement...

    Top Visual Basic.NET Experts

    1. CodeCruiser

      1,541,075

      Genius

      8,400 points yesterday

      Profile
      Rank: Genius
    2. kaufmed

      303,871

      Wizard

      500 points yesterday

      Profile
      Rank: Genius
    3. Idle_Mind

      230,817

      Guru

      2,010 points yesterday

      Profile
      Rank: Savant
    4. nepaluz

      192,076

      Guru

      0 points yesterday

      Profile
      Rank: Sage
    5. PaulHews

      161,438

      Guru

      520 points yesterday

      Profile
      Rank: Genius
    6. BuggyCoder

      150,598

      Guru

      0 points yesterday

      Profile
      Rank: Sage
    7. JamesBurger

      123,179

      Master

      0 points yesterday

      Profile
      Rank: Sage
    8. emoreau

      112,211

      Master

      0 points yesterday

      Profile
      Rank: Genius
    9. Masteraco

      102,128

      Master

      0 points yesterday

      Profile
      Rank: Wizard
    10. TheLearnedOne

      80,982

      Master

      0 points yesterday

      Profile
      Rank: Savant
    11. Dhaest

      63,803

      Master

      2,000 points yesterday

      Profile
      Rank: Genius
    12. MlandaT

      53,803

      Master

      2,100 points yesterday

      Profile
      Rank: Genius
    13. wdosanjos

      53,796

      Master

      0 points yesterday

      Profile
      Rank: Genius
    14. mlmcc

      53,048

      Master

      0 points yesterday

      Profile
      Rank: Savant
    15. RolandDeschain

      41,679

      10 points yesterday

      Profile
      Rank: Sage
    16. srosebabu

      31,025

      2,000 points yesterday

      Profile
      Rank: Guru
    17. mas_oz2003

      28,400

      0 points yesterday

      Profile
      Rank: Genius
    18. sedgwick

      27,350

      0 points yesterday

      Profile
      Rank: Genius
    19. jacko72

      26,596

      0 points yesterday

      Profile
      Rank: Genius
    20. tommyBoy

      25,850

      0 points yesterday

      Profile
      Rank: Genius
    21. dlmille

      22,160

      0 points yesterday

      Profile
      Rank: Genius
    22. imnorie

      21,664

      1,600 points yesterday

      Profile
      Rank: Genius
    23. Cluskitt

      21,418

      0 points yesterday

      Profile
      Rank: Wizard
    24. robert_schutt

      20,440

      0 points yesterday

      Profile
      Rank: Guru
    25. navneethegde

      20,332

      0 points yesterday

      Profile
      Rank: Wizard

    Hall Of Fame