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.
...
using SplashScreenControl;namespace WindowsApplication1
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault( false );
SplashScreen.Instance.Font =
new System.Drawing.Font( "Verdana", 11.75F,
System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point, ((byte)(0)) );
SplashScreen.SetBackgroundImage(
WindowsApplication1.Resources.splashbg );
SplashScreen.SetTitleString( "SplashScreen Demo" );
SplashScreen.BeginDisplay();
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.
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.
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
.
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.
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;
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