In this tutorial, we'll explore how to create a splash screen and add these features one at a time. We start with the creation of a simple splash screen, followed by the code changes required for the addition of each feature. You can skip to the bottom of the article to see the complete source code. I've also included a small test project in the download that demonstrates the splash screen.

Background

It was a lot of fun writing the code for this article and, while it's not perfect for all needs, I hope it saves you some coding time. The most fun for me is seeing a completely accurate and smoothly progressing progress bar as my application loads up. Please feel free to post any enhancement suggestions, bugs or other comments you may have.

Create the Simple Splash Screen Project

Start out by creating a Windows Forms project. Name it SplashScreen. Rename Form1.cs to SplashScreen.cs.

Now obtain a product bitmap with a light background suitable for putting text over. If you're lucky, a really talented person (like dzCepheus - see the thread below) will provide one for you. Set the Background Image property to it. Set the following properties on the form:

FormBorderStyle = None
StartPosition = CenterScreen

In the form constructor, add the line:

this.ClientSize = this.BackgroundImage.Size;

Go to the project properties and change the Output type to Class Library. At this point, if you are not adding this project to an existing solution, you may want to add another WinForms project to your solution for testing the splash screen. You can call the public methods from that class as you build the splash screen.

Make it Available from Static Methods

Because the splash screen will only need a single instance, you can simplify your code by using static methods to access it. By just referencing the SplashScreen project, a component can launch, update or close the splash screen without needing an object reference. Add the following code to SplashScreen.cs:

Put it on its Own Thread

A splash screen displays information about your application while it is loading and initializing its components. If you are going to display any dynamic information during that time, you should put it on a separate thread to prevent it from freezing when initialization is hogging the main thread.

Start by using the Threading namespace:

using System.Threading;

Declare a static variable to hold the thread:

static Thread ms_oThread = null;

Now add a method to create and launch the splash screen on its own thread. Wait before returning to ensure that the static methods aren't called before the form exists:

Now ShowForm() can be made private, since the form will now be shown using ShowSplashScreen().

// A static entry point to launch SplashScreen.
staticprivatevoid ShowForm()

Add Code to Fade In and Fade Out

It can add real flair to your splash screen by having it fade in when it first appears, and fade out just as your application appears. The form's Opacity property makes this easy.

Declare variables defining increment and decrement rate. These define how quickly the form appears and disappears. They are directly related to the timer interval, since they represent how much the Opacity increases or decreases per timer tick, so if you modify the timer interval, you will want to change these proportionally.

At this point, you have a splash screen that fades into view when you call the ShowSplashScreen() method and starts fading away when you call the CloseForm() method.

Add Code to Display a Status String

Now that the basic splash screen is complete, we can add status information to the form, so the user can tell that something's going on. To do this, we add the member variable m_sStatus to the form to store the status and a label lblStatus to display it. We then add an accessor method to set the variable and modify the timer tick method to update the label. In general, it is illegal to update a UI element across threads, so, for example, an attempt to update a label on the splash screen from the main application thread will cause an exception (as of .NET 2.0). This code avoids that problem by updating data only in the status and progress updates. The timer event (on the splash screen thread) is used to do the UI updates, based on the member variables that were changed during the updates.

Now Add a Progress Bar

There's no reason to use the standard WinForms progress bar here unless you really want that look. We'll make a gradient progress bar by painting our own Panel control. To do this, add a panel named pnlStatus to the form and set its BackColor to Transparent. In practice, you might want to derive your own control from the Panel if you expect to use it in more than one place. Here, we'll just paint it in response to the timer event.

Declare a variable to hold the percent completion value. It is a double with a value that will vary between 0 and 1 as the progress bar progresses. Also declare a rectangle to hold the current progress rectangle.

Now, still in the UpdateTimer_Tick event handler, paint the gradient progress bar. Start by adding the System.Drawing.Drawing2D namespace. You will probably want to fiddle with the RGB values to get a color scheme that works with your graphic.

Smooth the Progress by Extrapolating Between Progress Updates

I don't know about you, but I've always been annoyed by the way progress bars progress. They're jumpy, stop during long operations, and always cause me vague anxiety that maybe they've stopped responding.

Well, this next bit of code tries to alleviate that anxiety by making the progress bar move even during lengthy operations. We do this by changing the meaning of the Progress updates. Instead of indicating current percent complete, they now indicate the percentage of time we expect the current activity to take before the next Progress update. For example, the first update might indicate that 25% of the total will pass before the second update. This allows us to use the timer to paint more and more of the status bar, up to and including 25% (but not beyond) while we are waiting for the next update. For now, we'll guess at how much to progress per timer tick. Later, we'll calculate this based on experience.

Add member variables to represent the previous progress and the amount to increment the progress bar per timer tick.

Now Make the Progress Bar Calibrate Itself

We can now eliminate the need to specify the progress percentages by calculating the values and remembering them between splash screen invocations. Notice that this will work only if you make a fixed sequence of calls to SetStatus() and SetReferencePoint() during startup.

XML Storage

You can use any persistent storage mechanism for remembering these values (there are only 2 strings to store) between invocations. Here, we'll use an XML file stored in a user-specific location that's writable even when a user is logged in with non-administrative privileges. (In an earlier version of this article, we used the windows registry.)

Member Variables

Now declare variables for keeping track of how long each interval between updates is taking (this time) and what it took per interval last time (from the XML file). Declare some Boolean flags to indicate whether this is the first launch and the timer has been started.

Reference Points

We need to declare methods for recording various reference points during application startup. Reference points are critical to making a self-calibrating progress bar since they replace progress bar percent-complete updates. (The percent complete for each reference point will be calculated from the prior invocation of the progress bar.) To make the best use of this capability, you should sprinkle reference points inside of the initialization code that runs during application startup. The more you place, the smoother and more accurate your progress bar will be. This is when static access really pays off, because you don't need a reference to SplashScreen to call them.

First, we'll need a simple utility function to return elapsed Milliseconds since the Splash Screen first appeared. This is used for calculating the percentage of overall time allocated to each interval between ReferencePoint calls.

Now we'll be modifying SetStatus() and adding a new SetReferencePoint() method. Both call SetReferenceInternal() which records the time of the first call and adds the elapsed time of each subsequent call to an array for later processing. It sets the progress bar values by referencing previous recorded values for the progress bar. For example, if we're processing the third SetReferencePoint() call, we use the actual percentage of the overall load time that occurred between the third and fourth calls during the previous invocation. First, add the SetReferencePoint() method and SetReferenceInternal() which does the work of recording the time taken since the splash screen was started.

We now can modify the SetStatus() method to add a Reference when the Status is updated. We also add an overloaded method to permit a Status update without the SetReferenceInternal() call. This is useful if you are in a section of code that has a variable set of status string updates. Note that depending on how often SetStatus() is called, you may not need many SetReference() calls in your startup code.

// A static method to set the status and update the reference.
staticpublicvoid SetStatus(string newStatus)
{
SetStatus(newStatus, true);
}
// A static method to set the status and optionally update the reference.
// This is useful if you are in a section of code that has a variable
// set of status string updates. In that case, don't set the reference.
staticpublicvoid SetStatus(string newStatus, bool setReference)
{
if (ms_frmSplash == null)
return;
ms_frmSplash.m_sStatus = newStatus;
if (setReference)
ms_frmSplash.SetReferenceInternal();
}

We also need to modify the timer tick handler to paint only when m_bFirstLaunch is false. This prevents the first launch from showing an uncalibrated progress bar.

Add a Time Remaining Counter

Finally, we can fairly accurately estimate the remaining time for initialization by examining what percentage is yet to be done. Add a label called lblTimeRemaining to the splash screen form to display it. Add a member variable m_sTimeRemaining to hold the corresponding string, then add the following code to the UpdateTimer_Tick() event handler to update the lblTimeRemaining label on the SplashScreen form.

Also modify the ReadIncrements() method which clears the time remaining label as follows:

privatevoid ReadIncrements()
{
...
m_sTimeRemaining = "";
...
}

Note that the label is set using a member variable. This makes it possible to set the time remaining text across threads without causing a cross-thread exception.

Using the SplashScreen

To use the splash screen, just call SplashScreen.ShowSplashScreen() on the first line of your Main() entry point. Periodically call either SetStatus() (if you have a new status to report) or SplashScreen.SetReferencePoint() (if you don't) to calibrate the progress bar. When your initialization is complete, call SplashScreen.CloseForm() to start the fade out process. Take a look at the test module provided in the download if you have any questions.

You may want to play around with the various constants to adjust the time of fade in and fade out. If you set the interval to a very short time (like 10 ms), you'll get a beautiful smoothly progressing progress bar but your performance may suffer.

When the application first loads, you will notice that the progress bar and time remaining counter do not display. This is because the splash screen needs one load to calibrate the progress bar. It will appear on subsequent application launches.

Update

This article was originally written over 10 years ago, when the current version of .NET was 1.1. Over time, readers have reported a number of defects and suggestions for workarounds (in the comments below) that haven't, until now, been incorporated into the article. However, a few years back Mahin Gupta was kind enough to update the source code with fixes and changes based on those comments and had provided a link to that source that many folks were downloading and using. This update takes from that source code and incorporates the changes into the text of the article, as well as updating the attached zip file containing the code.

I'd like to thank Mahin, as well as the many folks who posted bug reports and fixes below. Some of the changes included in this update are:

Fixed cross threading oversight (lblTimeRemaining label was being set directly.)

I didn't implement one suggestion. A few of the comments pointed out focus problems when the splash screen closes (in MDI applications). The suggestion was to set the owner of the splash screen to the appropriate parent form as part of closing the window. I tried all the proposed fixes (I think) and couldn't find something that works. The problem appears to be that setting a form's owner invokes code on the owner (AddOwnedForm) which is not thread safe. So even if you do it during an invoke, the operation fails with a cross thread exception. The only 'fix' for this I could find online was to turn off the exception (ControlCheckForIllegalCrossThreadCalls = false;) during the operation. I decided that might not be wise. So either cross thread form ownership is not possible, or I can't figure it out. Any suggestions for resolving this would be appreciated. In the test project, I found that calling this.Activate() before calling SplashScreen.CloseForm() was sufficient.

History

11-16-2003 First Version.

11-18-2003 Corrected some typos and clarified behavior when the application is first called. Changed code to not display the progress bar on the first load.

Share

About the Author

I've been programming in C, C++, Visual Basic and C# for almost 30 years. I've worked at Sierra Systems, ViewStar, Mosaix, Lucent, Avaya, Avinon, Apptero and now Serena in various roles over my career.

All you need to do is unregister the double click event on the form. And to clean things up, you can remove the following handler code:

// Close the form if they double click on it.
privatevoid SplashScreen_DoubleClick(object sender, System.EventArgs e)
{
// Use the overload that doesn't set the parent form to this very window.
CloseForm();
}

Hi,
Are you sure the problem is with the splash screen and not the code running on the other thread? I haven't seen any comments about this problem from others who have used it, so it seems likely it it unique to your project.

In any case, sorry you're running into this problem. Hopefully, your choice to use this freely available code has saved you more time than it has cost you.

Tom,
This is great code. I am wondering though, with the two message loops (i.e. one for splash and another for frmTestSplashScreen) I cannot see how to make the splash topmost. I want it to be in front of frmTestSplashScreen which frmTestSplashScreen comes up. Ideas? I have tried setting the Owner (cross thread issues and only hacks seen so far to work) and timer solution which I do not want since loading items in background. For example, is there a way to replace the message loop with something else that would not cause this, get the message loop to work in this case or ??

Again this is great code!!!! Just this one important item left for me to figure out.

I have following your guide and what i'm trying to do is making a simple static splash screen,but i just can't call SplashScreen.ShowSplashScreen() from the forms outside the SplashScreen project it's always say it doesn't exist in current context.
So it is only happen to me or am i make a mistake ?

Hi Tommy,
Have you included a reference to the splash screen project in yours? Try right clicking on the "references" folder in your project, pick "Add reference..." and then point to the DLL with the splash screen. If that doesn't work, let me know and we can continue the investigation.

Hi,
How do you use your splash screen with an mdi application.
I want the splash screen to show and mdi to load in the background when finished splash screen dissappears.
I have been looking everywhere for this and cannot find any example that works including yours.

I assume you've tried responding to the form's Resize event and tested the WindowState property for the FormWindowState.Minimized value and tried hiding the splash screen form (or reshowing it) based on that value. What's the specific issue you're having?

Yes, I've tried that, it does not work, the splash screen is not minimized even I set its WindowState to FormWindowState.Minimized. I see the splash window is shown as a TOPMOST NOMOVE NOSIZE window, but if I remove NOSIZE flag, the splash screen is not shown at all...