A WPF Splash Screen

When I was first looking into displaying a splash screen in a WPF app, I found a large number of examples using some managed code. I then found some samples using .Net 3.5 Sp1′s WPF Splash Screen implementation. None of these really suited my requirements. We weren’t looking for only displaying an image. We were looking to display some sort of status for the users and, quite frankly, for ourselves. We wanted the flexibility to display any WPF content and, more importantly, we had other initialization logic that needed to execute on the UI thread!

The motivation

The UI was a composite UI and we were initializing numerous plugins. At first, the shell displayed the Main Window and then we called the Initialize() logic for each plugin. The problem was that plugins were able to do anything in the initialize code, including calling services, initializing controls, etc. The resulting behavior was a window that was unresponsive and looked frozen until all plugins were initialized. Even if they did not do things on the UI thread, initializing over 8 plugins was not seamless (load from disk, JIT some code, etc). We decided that we did not want to suffer the frozen main window on startup curse. Instead, let’s initialize everything first and only show the window once everything is good to go.

Only now we had nothing on the screen for some time. The solution: a splash screen that can display messages and allow execution to occur on the UI thread. Since there was one large initialization method executing on the main UI thread, we did not want to bring in a complex splash screen that needs message pumping on the same thread.

Solution

The solution is fairly simple. A WPF app has one main UI thread. However, we are able to create any number of separate, independent UI threads. The solution is to create a new STA thread in which we run a separate Dispatcher that hosts the splash screen window. In retrospect, this would have been a perfect place to use the power of DispatcherFrames, but I’ll address this in a future post.

Our client code should be something like this:

using (ISplashScreen splashScreen = SplashScreenManager.CreateSplashScreen())
{
    // do lots of work on UI thread
}

with ISplashScreen defined as:

/// 
/// Contract through which client application can talk to the splash screen
/// 
public interface ISplashScreen : IDisposable
{
    /// 
    /// The text message being displayed in the splash screen
    /// 
    string Message { get; set; }

    /// 
    /// The content object displayed in the splash screen window
    /// 
    /// 
    /// This method sets the Content element in the Splash Screen. Cannot 
    /// accept an object because any UI element needs to be initialized in 
    /// the splash screen's UI thread.
   void SetContentObject(Type objectType);
}

The idea behind SetContentType is that we are unable to set the Content of the screen directly, since the UI element would need to be created in the same thread and the splash screen. For simplicity, we assume the content object is static (maybe an image) and simply have the splash screen manager initialize the type.

The magic behind CreateSplashScreen is pretty straightforward.

public class SplashScreenManager
{
    private static object mutex = new object();

    public static ISplashScreen CreateSplashScreen()
    {
        lock (mutex)
        {
            SplashScreenWindowViewModel vm = new SplashScreenWindowViewModel();

            AutoResetEvent ev = new AutoResetEvent(false);

            Thread uiThread = new Thread(() =>
            {
                vm.Dispatcher = Dispatcher.CurrentDispatcher;
                ev.Set();

                Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate()
                {
                    SplashScreenWindow splashScreenWindow = new SplashScreenWindow();
                    splashScreenWindow.DataContext = vm;
                    splashScreenWindow.Show();
                });

                Dispatcher.Run();
            });

            uiThread.SetApartmentState(ApartmentState.STA);
            uiThread.IsBackground = true;
            uiThread.Start();
            ev.WaitOne();

            return vm;
        }
    }
}

The result is a splash screen with its own UI thread. We use the MVVM pattern in the background. The SplashScreenViewModel object is the Window’s ViewModel and implements the ISplashScreen interface for clients.

The CreateSplashScreen method creates a new Background STA thread. This thread queues up the SplashScreenWindow initialization code and runs the Dispatcher on the thread. As soon as the Dispatcher is ready, the splash screen will show up. The ViewModel’s Dispatcher object must be set before we can call SetContentObject(Type) on it (this method needs to create the Type using the Dispatcher object), so we synchronize between the calling thread and the splash screen initialization thread so that Dispatcher will always be set when the ISplashScreen instance is returned.

The library currently only supports a rectangular window with static content. It should be fairly easily to modify this library to support non-rectangular windows and dynamic content, as long as all UI operations are somehow executed on the splash screen’s Dispatcher.

Notes

  • Performance is not a real consideration for this approach to the splash screen. The idea here is not to shave milliseconds off, but rather provide some feedback to the user. We prefer to display something live to the user as soon as possible rather than worry about milliseconds during start up. On a fast machine, the extra time spent to spin off a new thread and display the splash screen is not significant.
  • At one point, we had our UI initialization logic spread across two AppDomains. In that case, the splash screen manager class inherited from MarshalByRef and we were able to share one instance across multiple AppDomains.

You can download the library here.