Creating a Native Win32 Splash Screen

Splash screens are all the rage. They’re cool, they’re fun, and they can be a pain to program right.

I though I would share a native Win32 splash solution with you on this rainy night in June (well, it is June here and it is raining from where I am, YMMV). This is slightly different from your typical splash screen as it’s done using the Win32 API calls and it’s fired off before the .NET Forms engine even gets started. As a result it’s quick and snappy and doesn’t intrude on your normal WinForms programming.

First off, let’s look at how we’re going to invoke it. Here’s the Program class that will call our normal splash screen:

   1: [STAThread]
<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">   2:</span> <span style="color: rgb(0, 0, 255);">private</span> <span style="color: rgb(0, 0, 255);">static</span> <span style="color: rgb(0, 0, 255);">void</span> Main()</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: white;"><span style="color: rgb(96, 96, 96);">   3:</span> {</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">   4:</span>     SplashWindow.Current.Image = <span style="color: rgb(0, 0, 255);">new</span> Bitmap(<span style="color: rgb(0, 0, 255);">typeof</span>(Form1), <span style="color: rgb(0, 96, 128);">"splash.jpg"</span>);</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: white;"><span style="color: rgb(96, 96, 96);">   5:</span>     SplashWindow.Current.ShowShadow = <span style="color: rgb(0, 0, 255);">true</span>;</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">   6:</span>     SplashWindow.Current.MinimumDuration = 3000;</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: white;"><span style="color: rgb(96, 96, 96);">   7:</span>     SplashWindow.Current.Show();</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">   8:</span>&nbsp; </pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: white;"><span style="color: rgb(96, 96, 96);">   9:</span>     Application.EnableVisualStyles();</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">  10:</span>     Application.SetCompatibleTextRenderingDefault(<span style="color: rgb(0, 0, 255);">false</span>);</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: white;"><span style="color: rgb(96, 96, 96);">  11:</span>     Application.Run(<span style="color: rgb(0, 0, 255);">new</span> Form1());</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">  12:</span> }</pre>

Note that it’s the first thing called (even before we do Application calls or create the main form). We’re launching it using a JPG image but any embedded or external resource file will do (JPEG, PNG, BMP, etc.). There are a couple of options we turn on here like showing a shadow (if the OS supports it) and setting a duration.

The duration is the minimum number of milliseconds to display the splash screen for. For example you can set this to 5000 (5 seconds) and no matter how much or how little your app is doing, the splash screen will stay around for at least this long. This is handy to keep it up even though your app may find a burst of speed and be ready before you know it.

Now that we’ve launched the splash screen, we just go about our normal business and at the right time launch the main window and tell the splash screen to go away. We’ll do this in our Main form class by overriding the OnActivate event:

protected override void OnActivated(EventArgs e)
{


    base.OnActivated(e);


    if (_firstActivated)


    {


        _firstActivated = false;


        SplashWindow.Current.Hide(this);


    }


}

The call here to SplashWindow.Current.Hide passes in the Form derived class of our window. The SplashWindow will keep a reference to this Form object so later in the splash thread it can invoke Activate on the Form class to pop it up after destroying itself. The “_firstActivated” variable is just a boolean set on the Form class and set to true at creation. This prevents us from hiding the splash screen if the main form is activated more than once (can happen).

And that’s it for using the SplashWindow. Simple huh? Here’s our splash in action over top of our important business application (another Bil Simser UI Special):

Splash Window:

image

Main Window with Splash in Front:

image

Ready to work!

image

One of the other options you can do with this class is to provide it a custom event handler. This is called during the WM_PAINT event and will allow you to get a copy of the Graphics object that the SplashWindow owns (the surface holding the bitmap image you provide) and a Rectangle class of the boundaries of the splash window. This is really handy for doing fancy stuff to your splash screen without having to fuss around with the image itself.

For example here’s the call to our SplashWindow again but using a custom handler:

   1: [STAThread]
<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">   2:</span> <span style="color: rgb(0, 0, 255);">private</span> <span style="color: rgb(0, 0, 255);">static</span> <span style="color: rgb(0, 0, 255);">void</span> Main()</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: white;"><span style="color: rgb(96, 96, 96);">   3:</span> {</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">   4:</span>     SplashWindow.Current.Image = <span style="color: rgb(0, 0, 255);">new</span> Bitmap(<span style="color: rgb(0, 0, 255);">typeof</span>(Form1), <span style="color: rgb(0, 96, 128);">"splash.jpg"</span>);</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: white;"><span style="color: rgb(96, 96, 96);">   5:</span>     SplashWindow.Current.ShowShadow = <span style="color: rgb(0, 0, 255);">true</span>;</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">   6:</span>     SplashWindow.Current.MinimumDuration = 3000;</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: white;"><span style="color: rgb(96, 96, 96);">   7:</span>     SplashWindow.Current.SetCustomizer(CustomEventHandler);</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">   8:</span>     SplashWindow.Current.Show();</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: white;"><span style="color: rgb(96, 96, 96);">   9:</span>&nbsp; </pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">  10:</span>     Application.EnableVisualStyles();</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: white;"><span style="color: rgb(96, 96, 96);">  11:</span>     Application.SetCompatibleTextRenderingDefault(<span style="color: rgb(0, 0, 255);">false</span>);</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">  12:</span>     Application.Run(<span style="color: rgb(0, 0, 255);">new</span> Form1());</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: white;"><span style="color: rgb(96, 96, 96);">  13:</span> }</pre>

And here’s the custom handler. This simply uses the GDI+ function of drawing a string on the Graphics surface. You could use this to display version information from your app, progress messages, etc. without having to build a form and adding labels to it.

   1: private static void CustomEventHandler(SplashScreenSurface surface)
<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">   2:</span> {</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: white;"><span style="color: rgb(96, 96, 96);">   3:</span>     Graphics graphics = surface.Graphics;</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">   4:</span>     Rectangle bounds = surface.Bounds;</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: white;"><span style="color: rgb(96, 96, 96);">   5:</span>&nbsp; </pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">   6:</span>     graphics.DrawString(<span style="color: rgb(0, 96, 128);">"Welcome to the Application!"</span>,</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: white;"><span style="color: rgb(96, 96, 96);">   7:</span>                         <span style="color: rgb(0, 0, 255);">new</span> Font(<span style="color: rgb(0, 96, 128);">"Impact"</span>, 32),</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">   8:</span>                         <span style="color: rgb(0, 0, 255);">new</span> SolidBrush(Color.Red),</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: white;"><span style="color: rgb(96, 96, 96);">   9:</span>                         <span style="color: rgb(0, 0, 255);">new</span> PointF(bounds.Left + 20, bounds.Top + 150));</pre>

<pre style="border-style: none; margin: 0em; padding: 0px; overflow: visible; font-size: 8pt; width: 100%; color: black; line-height: 12pt; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244, 244, 244);"><span style="color: rgb(96, 96, 96);">  10:</span> }</pre>

And here’s the result:

Splash with custom event handler

Of course this is pretty simplistic. I would love to see some creative geniuses out there do something cool with this. Since your have the Graphics object (already loaded with the splash screen image) and the GDI+ at your disposal, the sky is the limit. Let me know what you come up with.

Like I said, this is simple and easy. A few lines of code in your main program to launch it, one line to hide it, and the initialization is just providing it an image to display. All of the code is available for download below in source and binary form. You can just add the SplashLib.dll to your projects and go. Or feel free to enhance it, the code is released under the Creative Commons Attribution-Share Alike 3.0 Unported License. You can share and adapt it (even in commercial work) but please give back to the community.

One side note, .NET doesn’t provide an interface to winuser.h and other Win32 headers so the structures and constants that are needed by SplashWindow to work are in the class. If you’re a ReSharper junkie you’ll notice that R# complains that the file has a lot of dead code. Don’t for the love of all that is holy remove the unused structure members as the SplashWindow will fall down and go boom.

Of course there’s room for improvement so feel free to send me your changes or enhancements!

SplashLib Source Files


SplashLib Binary Files

Enjoy!

4 Comments

  • Could you post the code in .zip files?

  • Brilliant stuff...the best performing splash screen example I've run across for winforms.

    Great post.

  • Just tried to download your SplashLib and got an evil HTTP status code:
    ==========================================
    Internal Server Error

    The server encountered an internal error or misconfiguration and was unable to complete your request.

    Please contact the server administrator, webmaster@download.bilsimser.com and inform them of the time the error occurred, and anything you might have done that may have caused the error.

    More information about this error may be available in the server error log.

    Additionally, a 500 Internal Server Error error was encountered while trying to use an ErrorDocument to handle the request.
    ==========================================

    Could you repost it or put the source on CodePlex, GitHub, SourceForge, or the like?

  • Just wanted to let you know that I came across a bug in your library. If an application using your library is run from the exe, your main window will go behind the window which was last in focus. To resolve this I edited the SplashWindow.Hide method to look like this:

    public void Hide(Form formToActivate)
    {
    _formToActivate = formToActivate;
    if (_minimumDuration > 0)
    {
    _waitingForTimer = true;
    if (_minimumDurationComplete == false)
    {
    return;
    }
    }
    if (_hwnd != IntPtr.Zero)
    {
    PostMessage(_hwnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
    }

    _formToActivate.Focus();
    }

Comments have been disabled for this content.