Click here to Skip to main content
15,885,278 members
Articles / Multimedia / GDI+

Splash Screen Control

Rate me:
Please Sign up or sign in to vote.
4.73/5 (28 votes)
26 Oct 2007CPOL5 min read 88.7K   5K   120   18
A lightweight and easy-to-use splash screen control that starts life before the Application Form

Screenshot - SplashScreen.jpg

Introduction

A while ago, when I first browsed around for splash screen examples, I couldn't find anything that met my requirements for a splash screen. I wanted something simple, flexible, reliable, intrinsically safe -- no threads or child controls, etc. -- and that would start up immediately, stay on top, go away when required and also reappear if wanted. I have noticed there are some good examples based entirely within .NET using Forms, whereas the approach I have taken uses the System.Runtime.InteropServices to create a topmost top-level window from a Control-based class. I prefer this approach, as I have found it to be reliable and very flexible with regards to customisation. Because of the required simplicity, the control handles its own painting, which provides the freedom to use the GDI to create an application-personalised splash screen.

Background

I had been developing an application that required a splash screen. The motivation to provide a splash screen is given by any delay between launching the application and the user interface subsequently becoming available.

There's something reassuring about an application that starts up straightaway, without a splash screen, ready to use. However, if a program has any reason to initialize before starting, which implies a potential for delay or failure, then it would be best to present something right away via a splash screen with a commentary. This can have its benefits, as well.

A Branding Opportunity

With a visually pleasing background graphic for your banner, you can immediately create a better first impression with your users. It's an opportunity to brand your software and, just as importantly, brand a version. Users of Office, for instance, know immediately what version they are starting up, 2003 or 2007, for example.

A Moment of Greater User Attention

It's a good opportunity to take advantage of the increased attention you have from your user. After all, they've just thought about your software and launched it. So whilst loading, you could, for example, remind them to visit your website for up-to-date news about your products.

A Chance to Relay Start-up and Configuration Information

If your program relies on establishing connections -- or dynamically loads plug-ins or the like -- then a splash screen can make the user aware of what you are connecting to or loading. This information could prove useful to both you and your users.

Using the Code

To use the class SplashScreen, add the file SplashScreen.cs to your application. Locate the entry point Main in your program, typically in Program.cs for a generated project.

SplashScreen is implemented as a Singleton pattern class. This is a class that allows a maximum of one instance of itself. This is achieved by using a private constructor with a static member reference to the created instance and public static access to that instance. SplashScreen also provides public static methods to manage using the control.

After the calls to the application visual styles and text rendering settings, initialize SplashScreen with your image resource (if required) and call the static method BeginDisplay() to present the splash screen. From now on, use the static methods SetTitleString and SetCommentaryString at your start-up checkpoints to keep the user informed of progress. Remember that you initialize SplashScreen in Main, but you will continue to use it and end its display in your Forms application. The principal places of interest in your application's main form are the OnLoad and OnShown overrides. Of most importance is that you call the static method EndDisplay() in the OnShown override.

C#
// program.cs

...
using SplashScreenControl;namespace WindowsApplication1
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault( false );

            // the following demonstrates how to get hold of the 
            // instance object and change control properties
            SplashScreen.Instance.Font = 
                new System.Drawing.Font( "Verdana", 11.75F, 
                System.Drawing.FontStyle.Regular, 
                System.Drawing.GraphicsUnit.Point, ((byte)(0)) );

            // begin displaying the splash screen before running the 
            // application form
            SplashScreen.SetBackgroundImage( 
                WindowsApplication1.Resources.splashbg );
            SplashScreen.SetTitleString( "SplashScreen Demo" );
            SplashScreen.BeginDisplay();

            // run the application
            Application.Run( new Form1() );
        }
    }
}

//

The SplashScreen Class

The SplashScreen class itself uses System.Runtime.InteropServices to create a top-level window parented by the desktop. This is achieved by first overriding the CreateParams Property Getter and adding the WS_EX_TOPMOST window style, which will make it a top-level window, along with WS_EX_TOOLWINDOW, which will prevent the window appearing in the task bar or the windows that can be cycled with Alt+Tab.

C#
// splashscreen.cs

protected override CreateParams CreateParams
{
    get
    {
        CreateParams cp = base.CreateParams;
        cp.ExStyle |= WS_EX_TOOLWINDOW| WS_EX_TOPMOST;
        cp.Parent = IntPtr.Zero;
        return cp;
    }
}

//

Next, we override OnHandleCreated and set the Parent of our window to the Desktop window.

C#
// splashscreen.cs

protected override void OnHandleCreated(EventArgs e)
{
    if ( this.Handle != IntPtr.Zero )
    {
        IntPtr hWndDeskTop = GetDesktopWindow();
        SetParent( this.Handle, hWndDeskTop );
    }

    base.OnHandleCreated(e);
}

//

This is enough to get the control on top for the start-up situation, but at this point, it is only a top-level window. When the instance is created, it will become the topmost top-level window. If we want to display the splash again, and in the meantime have had another top-level window go topmost (Task Manager with Options -> Always On Top set to True, for example), then the control needs its topmost attribute set again. This is done using the SetWindowPos Windows API call. The control makes this call during BeginDisplay, which ensures the control will always start out on top.

The Paint Routines

The work of painting the splash screen is shared by two overrides, OnPaint and OnPaintBackground. During OnPaintBackground, the control either draws the BackgroundImage property if set or fills the background with the current BackColor.

C#
// splashscreen.cs

protected override void OnPaintBackground( PaintEventArgs e )
{
    if ( Bounds.Width > 0 && Bounds.Height > 0 && Visible )
    {
        try
        {
            Rectangle rect = 
                new Rectangle(0, 0, Bounds.Width, Bounds.Height);
            Graphics g = e.Graphics;
            g.SetClip(e.ClipRectangle);
            if (BackgroundImage == null)
            {
                SolidBrush solidBrush = new SolidBrush(BackColor);
                g.FillRectangle(solidBrush, rect);
                solidBrush.Dispose();
            }
            else
            {
                g.DrawImage(BackgroundImage, rect, 0, 0, 
                    BackgroundImage.Width, BackgroundImage.Height, 
                    GraphicsUnit.Pixel);
            }
        }
        catch (Exception exception)
        {
            System.Diagnostics.StackFrame stackFrame = 
                new System.Diagnostics.StackFrame(true);
            Console.WriteLine(
                "\nException: {0}, \n\t{1}, \n\t{2}, \n\t{3}\n",
                this.GetType().ToString(), stackFrame.GetMethod().ToString(),
                stackFrame.GetFileLineNumber(), exception.Message);
        }
    }
}

//

During OnPaint, the control gets the values of the TitleString and CommentaryString, calculates their size and position, and uses Graphics.DrawString to render the text.

C#
// splashscreen.cs

protected override void OnPaint( PaintEventArgs e )
{
    if ( Bounds.Width > 0 && Bounds.Height > 0 && Visible )
    {
        try
        {
            Graphics g = e.Graphics;
            g.SetClip(e.ClipRectangle);
            g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;

            float nClientHeight = ClientRectangle.Height;
            //start the text two thirds down:
            m_nTextOffsetY = 
                Convert.ToInt32(Math.Ceiling(((nClientHeight / 3) * 2))) + 
                m_nLeading;

            if (TitleString != string.Empty)
            {
                Font fontTitle = new Font(Font, FontStyle.Bold);
                SizeF sizeF = g.MeasureString(TitleString, fontTitle, 
                    ClientRectangle.Width, m_stringFormat);
                m_nTextOffsetY += Convert.ToInt32(
                    Math.Ceiling(sizeF.Height));
                RectangleF rectangleF = new RectangleF(
                    ClientRectangle.Left + m_nTextOffsetX, 
                    ClientRectangle.Top + m_nTextOffsetY, sizeF.Width, 
                    sizeF.Height);
                SolidBrush brushFont = new SolidBrush(ForeColor);
                g.DrawString(TitleString, fontTitle, brushFont, 
                    rectangleF, m_stringFormat);
                brushFont.Dispose();
                fontTitle.Dispose();

                m_nTextOffsetY += m_nLeading;
            }

            if (CommentaryString != string.Empty)
            {
                SizeF sizeF = g.MeasureString(CommentaryString, Font, 
                    ClientRectangle.Width, m_stringFormat);
                m_nTextOffsetY += Convert.ToInt32(
                    Math.Ceiling(sizeF.Height));
                RectangleF rectangleF = 
                    new RectangleF(ClientRectangle.Left + m_nTextOffsetX, 
                    ClientRectangle.Top + m_nTextOffsetY, sizeF.Width, 
                    sizeF.Height);
                SolidBrush brushFont = new SolidBrush(ForeColor);
                g.DrawString(CommentaryString, Font, brushFont, 
                    rectangleF, m_stringFormat);
                brushFont.Dispose();
            }
        }
        catch (Exception exception)
        {
            System.Diagnostics.StackFrame stackFrame = 
                new System.Diagnostics.StackFrame(true);
            Console.WriteLine("\nException: {0}, \n\t{1}, \n\t{2}, 
                \n\t{3}\n", this.GetType().ToString(), 
                stackFrame.GetMethod().ToString(), 
                stackFrame.GetFileLineNumber(), exception.Message);
        }
    }
}

//

Points of Interest

The control handles its painting routines based upon the static methods SetTitleString and SetCommentaryString. You may well have a different requirement or want to go about things in another way. In this respect, I hope you find that the design leaves you free to do so. Simply add your required properties or methods and alter your paint routines to implement those.

History

  • Version 1.0.0 Released: 24th October, 2007

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionQ: Pin
mzyfbz15-Aug-18 22:54
mzyfbz15-Aug-18 22:54 
GeneralThank you Pin
fishmeal28-Jan-12 8:23
fishmeal28-Jan-12 8:23 
GeneralRe: Thank you Pin
TwinLane28-Jan-12 23:14
TwinLane28-Jan-12 23:14 
Generaltransparent Pin
Kiko.1721-Aug-09 23:00
professionalKiko.1721-Aug-09 23:00 
GeneralRe: transparent Pin
TwinLane24-Aug-09 3:34
TwinLane24-Aug-09 3:34 
GeneralRe: transparent Pin
Kiko.177-Sep-09 5:44
professionalKiko.177-Sep-09 5:44 
GeneralRe: transparent Pin
TwinLane5-Jul-10 12:39
TwinLane5-Jul-10 12:39 
QuestionAdd listbox or other Pin
ppro28-Aug-08 5:44
ppro28-Aug-08 5:44 
Answer[Message Deleted] Pin
CarCake31-Aug-08 2:05
CarCake31-Aug-08 2:05 
GeneralRe: Add listbox or other Pin
ppro31-Aug-08 4:40
ppro31-Aug-08 4:40 
GeneralUse of System.Diagnostics.StackFrame Pin
Tatworth14-May-08 4:47
Tatworth14-May-08 4:47 
GeneralThreading Pin
Wouter van Vugt28-Oct-07 5:49
Wouter van Vugt28-Oct-07 5:49 
GeneralRe: Threading Pin
Wouter van Vugt28-Oct-07 5:54
Wouter van Vugt28-Oct-07 5:54 
GeneralRe: Threading Pin
Wouter van Vugt28-Oct-07 5:59
Wouter van Vugt28-Oct-07 5:59 
GeneralSuper! Pin
jukov27-Oct-07 23:42
jukov27-Oct-07 23:42 
GeneralMe too..... Pin
Doncp27-Oct-07 14:48
Doncp27-Oct-07 14:48 
GeneralVery cool Pin
Paul Conrad27-Oct-07 9:00
professionalPaul Conrad27-Oct-07 9:00 
General5 points from me... :) Pin
Izrialkiller26-Oct-07 19:13
Izrialkiller26-Oct-07 19:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.