Introduction
As developers, we are always trying to put some sort of �cool� functionality in our applications. We can�t help it, it�s our nature. If it wasn�t, we�d be working in Accounting or Marketing. One way to add a little bit of �coolness� to our WinForm applications is to add a splash screen that pops up for a few seconds when the application starts.
I�m not going to give a tutorial on the hows of creating splash screen forms. There are a million and one articles on how to do this. What I�m going to go over is a clean way to encapsulate splash screen functionality into a helper class. We are going to use a class called the ApplicationContext
class, inherit from it and put our splash screen logic into it. This way you don�t have to put any special code into your main or splash forms to make them work. In fact, once you implement your custom ApplicationContext
class, you can use any form you want, and turn it into a splash screen.
Application Context: What does it do?
I�m going to spend some time going over, in detail, what the ApplicationContext
does. This will make it easier to understand what our inherited class is doing behind the scenes. If you don�t really care about what the ApplicationContext
does, just skip ahead to the section called �Putting it all together�.
So what does the ApplicationContext
do? Not too much really, which is what makes it so easy to configure it to our needs. Every WinForms application has an instance of an ApplicationContext
class, you just may not know about it. Let's take a look at a standard Main()
function of a WinForms application.
static void Main()
{
Application.Run(new Form1());
}
I�m sure you�ve seen this before. But did you know you could also write it this way?
static void Main()
{
ApplicationContext appCtx = new ApplicationContext(new Form1());
Application.Run(appCtx);
}
They are really one and the same. In the first case, the Application.Run
function just creates a new ApplicationContext
instance and passes the form object into its constructor. In the second case, we did all this manually. So for now, just remember that every WinForm application has one ApplicationContext
instance, and it holds an instance to the Form
object that will serve as our main form.
The purpose of the ApplicationContext
is to serve as an application start and termination notification link between the main Form
in your application and the UI thread. The UI thread is the main thread that your application's user interface is running in, and it is the work horse that processes the application�s message loop. The message loop receives event messages from the operating system like �Mouse Right Click� or �Space Bar Button Down�, and sends these messages to the form that needs to handle it.
The message loop is inside the ThreadContext
class, which is a private sub class, defined inside the Application
class. There is very little documentation on the internals of the ThreadContext
, but you can peek inside it using the Anikrino tool.
So let's go back to the Main
function. When Application.Run
is called from Main()
, it in turn calls ThreadContext.RunMessageLoop
, passing in the newly created ApplicationContext
instance, which contains an instance to the application�s main form. RunMessageLoop
then registers ThreadContext
�s callback function OnAppThreadExit
with the ApplicationContext
�s ExitThread
event. This will let the message loop get notified when the user closes the application�s main form (which I�ll talk about shortly). RunMessageLoop
then sets the main form�s Visible
property to true
, which is the point that the user finally sees the main form. The final thing RunMessageLoop
does is enter into the actual message loop so it can start receiving and processing event messages from the operating system.
The ApplicationContext
class has only one property, which is called MainForm
. In the Main
function code example we went over, the Form instance that we passed into the ApplicationContext
�s constructor gets set to the MainForm
property. The set_MainForm
property will register the ApplicationContext.OnMainFormDestroy
callback function with the passed in form�s HandleDestroyed
event, which will get invoked when the user closes the form. This way the ApplicationContext
will get notified whenever the main form of the application ever gets destroyed.
This callback is very important because in WinForms applications, forms are not the application, they are just objects that the Application
object holds a reference to. If the Application
object did not get notified when the main form was destroyed, it would continue to run the message loop endlessly. So when the user closes the main form, the form invokes its HandleDestroyed
event, which calls the ApplicationContext
�s OnMainFormDestroy
callback function. This callback then invokes the ThreadContext
�s ExitThread
event, which calls the ThreadContext
�s OnAppThreadExit
callback function. Calling this function tells the ThreadContext
that the main form of the application has been destroyed and it is ok to terminate the UI thread. It posts the application quit message to the operating system, which will cause the application to clean up any resources it needs to and terminate the UI thread.
Putting it all together to create a splash screen
That was probably more than you ever wanted to know about what happens when the Main
function calls Application.Run
. But I wanted to go over it so you would understand exactly why we are doing what we�re going to do in this next section.
Microsoft didn�t have to expose the ApplicationContext
class for us to use, but they did, so we could inherit from it and customize this startup process. And that�s just what we�re going to do, make a customized ApplicationContext
and hold two forms, a splash screen form and the application�s main form.
First create a WinForms project and add a second form to it called Splash.cs. Next add a new class to your project and call it SplashAppContext
. The first thing you want to do is make this class inherit from ApplicationContext
:
public class SplashAppContext : ApplicationContext
{
. . .
We also need a private field of type Form
and another of type Timer
:
Form mainForm = null;
Timer splashTimer = new Timer();
Next create a constructor for SplashAppContext
. This constructor will have two input properties, both of type Form
. The first one is your splash screen form and the second one is your main application form:
public SplashAppContext(Form mainForm, Form splashForm) : base(splashForm)
{
this.mainForm = mainForm;
splashTimer.Tick += new EventHandler(SplashTimeUp);
splashTimer.Interval = 2000;
splashTimer.Enabled = true;
}
Be sure to call the base constructor and pass in the splash screen form instance. This will cause the base ApplicationContext
to store the splash screen form in its MainForm
property. The constructor then stores off the main application form in its private field and sets up the timer�s default values. The timer object will wait, by default for two seconds, and then calls the SplashTimeUp
callback function.
private void SplashTimeUp(object sender, EventArgs e)
{
splashTimer.Enabled = false;
splashTimer.Dispose();
base.MainForm.Close();
}
When SplashTimeUp
gets called, after a two second delay, it first disposes off the timer object, and then closes the base ApplicationContext
�s main form. This will cause the splash screen to close itself. Remember that when the ApplicationContext
�s MainForm
property gets set, it registers the ApplicationContext
�s OnMainFormClosed
callback function with the form�s HandleDestroyed
event? Well, when we call base.MainForm.Close()
, this triggers the splash screen�s HandleDestroyed
event, which calls the OnMainFormClosed
function:
protected override void OnMainFormClosed(object sender, EventArgs e)
{
if (sender is Splash)
{
base.MainForm = this.mainForm;
base.MainForm.Show();
}
else if (sender is Form1)
{
base.OnMainFormClosed(sender, e);
}
}
In the callback function, we first check to see if the closed form, the sender object, was the splash screen. If it is, we replace the splash screen form stored in the base.MainForm
property with the application�s main form instance. Remember what I just said setting this property did? It registers the OnMainFormClosed
with the form�s HandleDestroyed
event. So now that we�ve reset the MainForm
property, when the user closes the main application form, OnMainFormClosed
will get called a second time.
This time, we check the closed form to see if it is the main application form. If it is, we call base.OnMainFormClosed
, which will invoke the ThreadExit
event, which in turn calls the ThreadContext
�s OnAppThreadExit
call back function, cleaning up any resources and terminating the UI thread.
I also added a public property to this class to expose the splash screen timer, in case you want to change the default splash screen delay.
public int SecondsSplashShown
{
set
{
splashTimer.Interval = value * 1000;
}
}
Now that we have this class, using it is very easy. First create an instance of SplashAppContext
, and pass in the main application form in the first parameter, and the splash screen form as the second parameter. Then just call Application.Run
, passing in the new instance of SplashAppContext
.
static void Main()
{
SplashAppContext splashContext =
new SplashAppContext(new Form1(), new Splash());
Application.Run(splashContext);
}
When the ApplicationContext
gets passed to ThreadContext.RunMessageLoop
, the function will set the visible
property of the splash screen to true
, showing the splash screen to the user. After two seconds, the timer calls its callback function which closes the splash screen. This triggers the OnMainFormClosed
function to be called, which swaps the splash screen form with the main application form in the ApplicationContext.MainForm
property, and then shows the main form to the user. This is all the code you need to put in your application�s Main
function. The splash screen function doesn�t need any code at all, its all taken care of in the custom ApplicationContext
.
Conclusion
I prefer this model because it encapsulates all the splash screen logic away from the forms. The forms just have to sit there and look pretty, nothing else. With this model, you can pass in any two forms into the SplashAppContext
constructor and they will work.
You can also take this class and expand on it to make a �cooler� splash screen, one with more bells and whistles. I created a second class that inherits from ApplicationContext
, called SplashFadeAppContext
, that lets you configure the splash screen to fade in, fade out, both, or neither. This class is in the same cs file as the SplashAppContext
class, just below it.