Easily create a WPF splash screen with status updates via MVVM

Standard
Share

I was in search of an easy implementation of a splash screen for my current project. I wanted to be able to show a splash screen and update it with current status information as my application initialized.

The standard SplashScreen class provided by Microsoft does not support showing dynamic content on the SplashScreen. For this reason, I was not able to use the supplied class. Instead I decided to create my own.

The splash screen I created is simply a regular window bound to a view model. I’ve created a SplashScreenHelper static class that we’ll use to send status updates to the splash screen.

First, let’s take a look at the view model. This is the data context used for the splash screen, and simply needs two things:

  1. a string property to store the status text, and
  2. an implementation of the INotifyPropertyChanged interface.

I won’t get into the details of the INotifyPropertyChanged interface implementation; for that you can see my article on that subject. Just know that it is required in order for the UI to properly update with new status text when desired.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using jkshay.Examples.EasySplashScreen.MVVM;

namespace jkshay.Examples.EasySplashScreen.ViewModels
{
    public class SplashScreenViewModel : ViewModelBase
    {
        private string splashScreenText = "Initializing...";
        public string SplashScreenText
        {
            get { return splashScreenText; }
            set
            {
                splashScreenText = value;
                NotifyPropertyChanged(() => SplashScreenText);
            }
        }
    }
}

Let’s take a look at this in detail – it really is quite simple. Line #10 declares our class and specifies that it inherits from ViewModelBase. This provides the SplashScreenViewModel class with the change notification necessary to update the UI.

Lines #12-21 define the public SpashScreenText property and its private backing store. Note that line #19 places a call to NotifyPropertyChanged whenever the property value is set. I set the private field value to “Initializing…” so that the window says this as soon as it’s displayed.

That’s the view model in its entirety. Now let’s take a look at the View. Again, this is quite simple – just a two-row, one column grid housing an image and a TextBlock.

<Window x:Class="jkshay.Examples.EasySplashScreen.Views.SplashScreenView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="SplashScreenView" Height="385" Width="585" WindowStartupLocation="CenterScreen" WindowStyle="None" ShowInTaskbar="False" Topmost="True" ResizeMode="NoResize">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>
        <Image Grid.Row="0" Source="pack://application:,,,/EasySplashScreen;component/Resources/Images/EasySplashScreen.png"/>
        <TextBlock Text="{Binding SplashScreenText}" Margin="9,0" Grid.Row="1"/>
    </Grid>
</Window>

and the code-behind for the SplashScreenView class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using jkshay.Examples;

namespace jkshay.Examples.EasySplashScreen.Views
{
    /// <summary>
    /// Interaction logic for SplashScreenView.xaml
    /// </summary>
    public partial class SplashScreenView : Window
    {
        ViewModels.SplashScreenViewModel ssvm = new ViewModels.SplashScreenViewModel();
        
        public SplashScreenView()
        {
            InitializeComponent();
            DataContext = ssvm;
        }
    }
}

Let’s take a look at the view’s XAML first. As you can see in lines #1-4, it’s just a regular window (with some specific properties set (WindowStartupLocation=”CenterScreen”, WindowStyle=”None”, ShowInTaskbar=”False”, Topmost=”True”, and ResizeMode=”NoResize”).

Lines #5-9 define the grid that will house both my application logo and the status update TextBlock.

Line #10 is an image pointing to my application logo stored in the Resources/Images folder of my project. This image is stored in the grid’s first row (remember, grid row and column numbering is 0-based).

Line #11 is the TextBlock that will display status updates. Note that its Text property is bound to a SplashScreenText property. This TextBlock is stored in the grid’s second row. I’ve also added a bit of padding to this TextBlock to move the text away from the edge of the window.

In the code-behind file, we create an instance of the SplashScreenViewModel class (line #24) and set it as the DataContext for the SplashScreenView (line #29). This creates the association between the SplashScreenView and the SplashScreenViewModel, effectively tying the Text property of the View’s TextBlock to the SplashScreenText property of the view model.

Up to this point, this has been no different than any other MVVM implementation. Normally, I’d instantiate a window (View), display it to the user with the ShowDialog() method (which halts the calling code until the dialog is dismissed), then close it with an Action.

However, this is where splash screens differ. If we show the View using the ShowDialog() method, the calling code will halt until the View is closed. The application will never continue initializing because the displayed dialog is blocking the thread. And the splash screen will never dismiss because the application doesn’t continue initializing.

Instead, we’ll simply use the View’s Show() method. Show() will display the dialog, but allows the calling code to continue running.

Let’s take a look at the SplashScreenHelper class, then look at a complete implementation.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using jkshay.Examples.EasySplashScreen.ViewModels;
using jkshay.Examples.EasySplashScreen.Views;


namespace jkshay.Examples.EasySplashScreen.Utilities
{
    internal class SplashScreenHelper
    {
        public static SplashScreenView SplashScreen { get; set; }

        public static void Show()
        {
            if (SplashScreen != null)
                SplashScreen.Show();
        }

        public static void Hide()
        {
            if (SplashScreen == null) return;

            if (!SplashScreen.Dispatcher.CheckAccess())
            {
                Thread thread = new Thread(
                    new System.Threading.ThreadStart(
                        delegate()
                        {
                            SplashScreen.Dispatcher.Invoke(
                                DispatcherPriority.Normal,
                                new Action(delegate()
                                    {
                                        Thread.Sleep(2000);
                                        SplashScreen.Hide();
                                    }
                            ));
                        }
                ));
                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
            }
            else
                SplashScreen.Hide();
        }

        public static void ShowText(string text)
        {
            if (SplashScreen == null) return;

            if (!SplashScreen.Dispatcher.CheckAccess())
            {
                Thread thread = new Thread(
                    new System.Threading.ThreadStart(
                        delegate()
                        {
                            SplashScreen.Dispatcher.Invoke(
                                DispatcherPriority.Normal,

                                new Action(delegate()
                                    {
                                        ((SplashScreenViewModel)SplashScreen.DataContext).SplashScreenText = text;
                                    }
                            ));
                            SplashScreen.Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, new Action(() => { }));
                        }
                ));
                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
            }
            else
                ((SplashScreenViewModel)SplashScreen.DataContext).SplashScreenText = text;            
        }
    }
}

This SplashScreenHelper class contains a reference to our SplashScreenView, as well as provides three methods for manipulating our splash screen – Show, Hide, and ShowText(string text).

A quick note about UI element dispatchers: no thread can update a UI element that didn’t create the element. Instead, we must use the dispatcher on the thread that created the UI element, and invoke a method that performs the necessary task. It is through the use of threads and UI element dispatchers that we’re able to get the UI to update with current status information.

Looking at the SplashScreenHelper class, line #16 defines a property to store our SplashScreenView. Lines #18-22 defines the Show method, and lines #24-49 defines the Hide method. Finally, lines #51-77 define the ShowText() method.

Let’s take a closer look at the ShowText(string text) method. First, the method’s purpose is to accept a string and update the SplashScreenViewModel’s SplashScreenText property. We’ll achieve this (if necessary) by invoking the dispatcher on the SplashScreenView.

In line #53, we first check to ensure that the SplashScreenView property is not null. If it is null, we simply return control to the calling method, as there is no window to Show, Hide, or ShowText upon.

In line #55, we use the dispatcher’s CheckAccess() method to determine if the current thread has access to the dispatcher, which means we can just issue a call directly upon the View. If we don’t have access, we must use the dispatcher to invoke the necessary command.

If the current thread has access to the dispatcher, line #76 is executed. If we don’t have access, the code block defined by lines #56-74 will be executed.

In line #57, we create a new thread. Lines #59-70 define the delegate this new thread will run. Line #72 sets the thread’s ApartmentState enum value, and line #73 starts the thread.

In the delegate this thread runs, we use the SplashScreenView’s dispatcher (line #61) to invoke a delegate (lines #64-67) that will update the SplashScreenView’s view model (line #66).

The same logic with regards to threads, dispatchers, delegates, and invoking is used in the SplashScreenHelper’s Hide() method.

Now let’s take a look at my app.xaml.cs Main() method. This is the entry point for my application, and therefore an excellent place to initialize my splash screen.

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using EasySplashScreen.Models;
using EasySplashScreen.Utilities;
using EasySplashScreen.ViewModels;
using EasySplashScreen.Views;
using Microsoft.Shell;

namespace EasySplashScreen
{
    public partial class App : ISingleInstanceApp
    {
        private const string Unique = "EasySplashScreen";

        [STAThread]
        public static void Main()
        {
            Thread thread = new Thread(
                new System.Threading.ThreadStart(
                    delegate()
                    {
                        SplashScreenHelper.SplashScreen = new SplashScreenView();
                        SplashScreenHelper.Show();
                        System.Windows.Threading.Dispatcher.Run();
                    }
                ));
            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;
            thread.Start();

            if (SingleInstance<App>.InitializeAsFirstInstance(Unique))
            {
                var application = new App();
                application.InitializeComponent();
                application.Run();

                // Allow single instance code to perform cleanup operations
                SingleInstance<App>.Cleanup();
            }
        }
    }
}

The code we’re interested in here is contained in lines #20-31. First, we create a new thread (line #20) and assign it a delegate (lines #22-27). This delegate creates a new instance of the SplashScreenView and sets it to the SplashScreenHelper’s SplashScreen property (line #24). Line #25 calls the view’s Show() method and line #26 starts the dispatcher. Lines #29 and 30 define the thread’s IsBackground and ApartmentState properties. Finally, line #31 starts the thread.

That’s all you need to implement a splash screen with updatable text. In order to set the status text on the splash screen, simply place a call to the SplashScreenHelper’s ShowText(string text) method, like so:

SplashScreenHelper.ShowText("Loading settings from database...");

You may be questioning why the SplashScreenHelper’s Show() method doesn’t include the same thread and dispatcher logic as the other two methods in the class. The reason is quite simple: I only intend to show my splash screen once, and the call to Show() the splash screen resides in the same delegate that creates the splash screen. Therefore, the same thread will be showing the view as the thread that created it… which allows us to directly manipulate the view.

And there you have it – a WPF splash screen with updatable status text utilizing the MVVM design pattern.

Note: since there are no special methods on the SplashScreenView being implemented, we really could use any window by just changing the type of the SplashScreen property on the SplashScreenHelper class from SplashScreenView to Window.

22 thoughts on “Easily create a WPF splash screen with status updates via MVVM

    • iskandar,

      Check out the article I just posted this morning on using the ISingleInstanceApp interface. Basically, you can ignore the references to ISingleInstanceApp for this splash screen article. If you’re interested in what ISingleInstanceApp does or how to implement it, check it out here.

  1. Hi,

    I am just start learning WPF, MVVM etc. so first of all, thanks for all your blogs and help.

    I have two questions regarding helper class.
    1. Why are you checking CheckAccess(), if this splash screen will never own main thread so it will never be possible to directly update window?
    2. Why are you invoking empty action after updating text?

    TIA
    Matjaž

    • Welcome! Both of those are excellent questions.

      1. The code as is never explicitly calls its own ShowText method, but it can. For this purpose, I prefer to CheckAccess to determine which dispatcher should invoke the code.
      2. The use of invoking the empty action is to update the UI. It shouldn’t be necessary, but in my practice I’ve found that without this line, the UI doesn’t update reliably. See this link for more information.

  2. BriBones

    Hi. This looks cool! However, I’m just moving from Perl on UNIX to C#, WPF and MVVM. Can your source be downloaded? I find that I learn more taking something that works, breaking it and putting it back together.

    • BriBones,

      Welcome to .NET! The code for this article is not available for direct download. However, you should be able to create the necessary files and simply copy/paste. You’re certainly welcome to the code, and if I can provide any insight into what it’s doing, don’t hesitate to ask!

      Thanks,

      Jonathan

  3. Nic

    Hello JKShay,
    I’m new to WPF and MVVM. Thanks for your article.
    I have a project with codes borrowed from your site, and got Splash Screen to come up but it does not disappear after the MainWindow loads behind it. When/where do I call the SplashScreenHelper.Hide method?
    Also, when/where do I call SplashScreenHelper.ShowText method after a long database load call?

    Thanks,
    Nic

    • Nic, you would want to call SplashScreenHelper.ShowText() before you place the long database call. This way your user is informed of what is happening at the time. Call SplashScreenHelper.Hide() at the end of your initialization code. If all that is happening during your SplashScreenHelper is the database call, it would be

      public void GetApplicationReady()
      {
          SplashScreenHelper.ShowText("Retrieving database-stored values");
          LongDatabaseCall();
          SplashScreenHelper.Hide();
      }
      
  4. Hello JKShay.
    Thanks for great article. I implemented it in my project, but I have a problem. Splash starts just once, on app startup. But when I’m trying to calling ‘ShowText’ it dowsn’t show me the splash window.
    Have you meet problem like mine?

    Thanks.

  5. Turgay

    Im getting below when debugging

    Error Type ‘SplashScreenExample.App’ already defines a member called ‘Main’ with the same parameter types

  6. Sai Prashad

    Thanks JKShay for the wonderful article.

    I am very new to WPF and MVVM. I have same kind of requirement.
    But I am unable to show main window and to hide the splash screen. Could you please help me how to show main window. where do I need to call the method to show. and to hide the splash screen where I need to write the code.

    Thank you

    • The ViewModelBase class you see referenced can be found here. It simply provides an implementation of the INotifyPropertyChanged interface which allows the UI to “automatically update” when underlying viewmodel values change.

  7. Jarrid

    Thanks for the info! Quick question, what’s the advantage of not having the splash screen window’s code be the “view model” instead of having it as a separate class?

Leave a Reply

Your email address will not be published. Required fields are marked *