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:
- a string property to store the status text, and
- 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.
Leave a Reply