Besides the nifty HTML start page that Package Explorer 3.0 will be using, there is another feature that I am quite happy about; the splash screen.

Now you have splash screens and splash screens, and mine of course needed to be a proper splash screen. To me this means the following:
- Send status messages
- Minimum display duration
- Proper threading model
Especially that last item in the list is where I see many splash screens fail. There are many things that you can do with splash screens and threading, and if you do it wrong your splash will look ugly. The most basic implementations use the thread that you are handed on application start to show the splash and then init the application. By then switching the splash form with your real one you have done it. The difficulty is that while you are doing the init work on your UI thread, and you move another window over the splash, your splash UI will not update, leaving those familiar white gaps that Windows users have gotten accustomed to. Some sprinkle the Application.DoEvents call across their initialization code to have the messages appear correctly in the splash screen, not nice!
The second model is to have the application initialization run on a separate thread. That's of course a great solution to keep the splash screen responsive. The UI thread can update the splash screen and the messages which appear there and wait for the initialization thread to complete. Again, one of my demands is making things difficult. What I'd like to do is just create a WaitHandle that gets set from the initialization thread, and have the UI thread wait on this WaitHandle. This will satisfy my second demand, a minimum duration. But, in order to keep the UI responsive, you cannot do a WaitHandle.WaitOne, you might be able to do some polling on IsCompleted and do some Application.DoEvents in the mean time to update the UI, but again, not nice!
So my model then. It does it a bit differently. Both the UI thread and the initialization thread are created on application startup. So instead of the UI thread needing to wait for the application to start, the controller thread can do that. UI responsiveness galore!
In order to encapsulate the difficulties of working with multiple threads I borrowed an idea I found on the web. The Application.Run method can take an ApplicationContext as a parameter. This object holds a reference to the application's main form. When this form closes, the message pump is stopped. You can derive from the ApplicationContext in order to customize how this works. A good place to implement your splash screen logic.
Using the code
The end user scenario looks like the following block of code. This is typically how your main method would look.
static class Program { static void Main(string[] args) { SplashScreenContext context = new SplashScreenContext(); context.MinimumSplashDuration = 2000; try { context.Run(args,typeof(SplashForm), ApplicationInit, UIInit); } catch (Exception e) { MessageBox.Show(e.InnerException.Message); } }
static void ApplicationInit(string[] args, SafeSendMessageCallback messageCallback) { messageCallback("Doing important application init work."); messageCallback("This method runs on a worker thread."); Thread.Sleep(500); }
static Form UIInit(string[] args, SafeSendMessageCallback messageCallback) { messageCallback("Doing important UI init work."); messageCallback("This method runs on the UI thread."); Thread.Sleep(500); return new MainForm(); } } |
You create an instance of SplashScreenContext, and before you call Run you are allowed to provide settings such as minimum duration of the splash display and maximum duration of the init method before an exception will be raised. Next you call Run to start your application. You pass in the command line arguments, the Type of the splashscreen form and two methods. The first will be called on a separate non-ui thread, the second will be called on the UI thread and needs to return the main form to switch to after the splash screen closes. Do be aware that the Main method runs on a different thread than the UIInt method. The UI thread is created by the SplashScreenContext to allow the wait for minimal splash duration to be non ui-blocking.
All exceptions flowing out of the initialization methods will be marshaled to the startup thread and throw there. This will allow you to catch all initialization errors inside your Main method. Both initialization methods are also provided with a callback for sending status messages to the splash screen in a thread-safe manner.
If you want to be able to send messages to the splash screen, just implement IDynamicSplashScreen on the Type passed in to the Run method. Your code will always be called on the UI thread so you have an easy time updating your status.
Here's a diagram.

The SplashScreenContext
Now the inner workings of the SplashScreenContext class are more complex than how you use it (as it should be).
The first interesting part is the Run method of which the signature is show here:
void Run(string[], Type, ApplicationInitCallback, UIInitCallback);
All parameters which have thread affinity are passed in the Run method, so the class doesn't contain any fields. This reduces the risk of me doing stupid coding resulting in contention somewhere.
The first task is setting up some basic stuff. The running Boolean is set to block changes to the properties of the class. The thread name is setup for better debugging, and some waithandles are created. The first WaitHandle is used to wait for the init thread to complete. The second for the UI thread to create the main form. While this second task is almost instantaneous, threading is always hard so I thought it was a good idea to wait for the UI thread to go live before going into the rest of the initialization work.
_running = true; Thread.CurrentThread.Name = "Initial Thread"; // Create waithandle for minimum splash duration _initReadyEvent = new AutoResetEvent(false); _uiReadyEvent = new AutoResetEvent(false); |
So the next thing that happens is the UI thread gets created, and the WaitHandle is used to hold further work until its live. (imagine the scenario where a status message gets sent to the UI thread while it isn't ready, waiting is probably good here). The SplashForm is also passed in as a Type instead of an instantiated class. The form cannot be instantiated yet, because that needs to happen on the UI thread which isn't created yet when the application starts.
Thread uiThread = new Thread(UIThreadMethod); uiThread.Name = "UI Thread"; uiThread.SetApartmentState(_apartmentState); uiThread.Start(splashFormType); if (_uiReadyEvent.WaitOne( new TimeSpan(0, 1, 0), false) == false) { ShutdownUIThread(); throw new ApplicationException( "The UI did not initialize in a timely fashion."); } |
Next the initialization thread gets created. This is the one that will call your initialization method. When your initialization is done, the thread will signal the startup thread that it is finished.
// Create initialization data ApplicationInitData initData = new ApplicationInitData(); initData.FinishedEvent = _initReadyEvent; initData.InitCallback = applicationInitMethod; initData.CommandlineArgs = (string[])args.Clone(); // Fire up initialization thread Thread initializationThread = new Thread(ApplicationInitMethod); initializationThread.Name = "Application Init Thread"; initializationThread.Start(initData); |
Next is the easy part. The startup thread will wait on the initialization thread to complete, after it has waited for the minimum duration. You can specify how long the waits should be.
// now wait for minimum duration if (_minimalSplashDuration > 0) { Thread.Sleep(_minimalSplashDuration); } // now wait for init to complete if (_initReadyEvent.WaitOne( new TimeSpan(0, _maxWaitForInitDuration, 0), false) == false) { throw new ApplicationException( "The application did not initialize in a timely fashion."); } |
The Run method finishes with checking for errors, and switching to the application's main form. This of course needs to be done on the UI thread since controls like to be on the same thread always. First a check occurs to see if initializing the application resulted in an error.
if (_initException != null) { ShutdownUIThread(); throw _initException; } MainForm.Invoke(new ToggleToMainFormCallback(ToggleToMainForm), uiInitMethod, args); if (_initException != null) { ShutdownUIThread(); throw _initException; } |
UI Thread
The UI thread method that is used is rather simple. You find the same code as in the normal Main method from Visual Studio, plus some extra bits to fit it in the model.
void UIThreadMethod(object parameter) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Form splashForm = (Form)Activator.CreateInstance((Type)parameter); MainForm = splashForm; _uiReadyEvent.Set(); Application.Run(this); } |
Init Thread
The Initialization thread method is not more complex.
void ApplicationInitMethod(object parameter) { ApplicationInitData initData = (ApplicationInitData)parameter; try { initData.InitCallback(initData.CommandlineArgs, new SafeSendMessageCallback(SafeSendMessage)); } catch (Exception e) { _initException = new InitializationException( "An error occured during application initialization", e); } initData.FinishedEvent.Set(); } |
That's it, and that's that. A handy class that can help you do a good splash screen keeping responsive and providing the feature set that I need. Download a demo here.
Hope it helps.