Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Splash Screen Control

0.00/5 (No votes)
26 Oct 2007 1  
A lightweight and easy-to-use splash screen control that starts life before the Application Form
Screenshot - SplashScreen.jpg

Introduction

Awhile back, 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.

// 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.

// 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.

// 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.

// 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.

// 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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here