Using ApplicationContext to Encapsulate Splash Screen Functionality


Introduction

As developers we are always trying to put some sort of cool functionality in our applications. We cant help it, its our nature. If it wasnt wed 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.

Im not going give a tutorial on the hows of creating splash screen form. There are a million and one articles on how to do this. What Im 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, inheriting from it and putting our splash screen logic into it. This way you dont 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.

ApplicationContext: What does it do?

Im 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 dont 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. Lets take a look at a standard Main() function of a WinForms application.

static void Main()
{
Application.Run(
new Form1());
}

Im sure youve 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 in 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 server 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 applications user interface is running in, and it is the work horse processes the applications 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 lets 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 applications main form. RunMessageLoop then registers ThreadContexts callback function OnAppThreadExit with the ApplicationContexts ExitThread event. This will let the the message loop get notified when the user closes the applications main form(which Ill talk about shortly). RunMessageLoop then sets the main forms 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 called MainForm. In the Main function code example we went over, the Form instance that we passed into the ApplicationContexts constructor gets set to the MainForm property. The set_MainForm property will register the ApplicationContext.OnMainFormDestroy callback function with the passed in forms 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 too. 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 ApplicationContexts OnMainFormDestroy callback function. This callback then invokes the ThreadContexts ExitThread event, which calls the ThreadContexts 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 were going to do in this next section.

Microsoft didnt 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 thats just what were going to do, make a customized ApplicationContext and holds two forms, a splash screen form and the applications main form.

First create a WinForms project and add a second form to 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 timers default values. The timer object will wait, by default for two seconds and then call 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 of the timer object, and then closes the base ApplicationContexts main form. This will cause the splash screen to close itself. Remember that when the ApplicationContexts MainForm property gets set, it registers the ApplicationContexts OnMainFormClosed callback function with the forms HandleDestroyed event? Well, when we call base.MainForm.Close(), this triggers the splash screens 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 applications main form instance. Remember what I just said setting this property did? It registered the OnMainFormClosed with the forms HandleDestroyed event. So now that weve reset the MainForm property, when the user closes the main application form, OnMainFormClosed will get called a second time.

This time, when 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 ThreadContexts 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 all the code you need to put in your applications Main function. The splash screen function doesnt need any code at all, its all takes 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 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.


Similar Articles