IF you are only displaying an image as a splash screen, then there is a better way:
How to add a splash screen - WPF .NET Framework | Microsoft Learn[
^]
However, if you are wanting a more dynamic solution, like displaying loading messages &/or a progress bar, then it is more involved. Here is an example stripped out of a live WPF app.
1. We need a
DispatcherHelper
class to help with multi-threading:
public static class DispatcherHelper
{
public static Dispatcher? UIDispatcher { get; private set; }
public static void CheckBeginInvokeOnUI(Action? action)
{
if (action == null)
return;
CheckDispatcher();
if (UIDispatcher != null && UIDispatcher.CheckAccess())
action();
else
UIDispatcher?.BeginInvoke(action);
}
private static void CheckDispatcher()
{
if (UIDispatcher == null)
{
StringBuilder stringBuilder = new("The DispatcherHelper is not initialized.");
stringBuilder.AppendLine();
stringBuilder.Append("Call DispatcherHelper.Initialize() in the static App constructor.");
throw new InvalidOperationException(stringBuilder.ToString());
}
}
public static DispatcherOperation? RunAsync(Action action)
{
CheckDispatcher();
return UIDispatcher?.BeginInvoke(action);
}
public static void Initialize()
{
if (UIDispatcher != null && UIDispatcher.Thread.IsAlive)
return;
UIDispatcher = Dispatcher.CurrentDispatcher;
}
public static void Reset() => UIDispatcher = null!;
}
2. Next we need a Splash Window:
<Window x:Class="WpfDynamicSplashScreen.SplashScreenWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="SplashScreenWindow"
Height="100" Width="300" WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text ="SPLASHSCREEN"
FontSize="36"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<ProgressBar IsIndeterminate="True" Grid.Row="1" Height="10" />
</Grid>
</Window>
and the code-behind for the
DispatcherHelper
initialization:
public partial class SplashScreenWindow
{
public SplashScreenWindow()
{
InitializeComponent();
DispatcherHelper.Initialize();
}
}
3. Now we can add the bootstrapping code to:
a. Show the Splash Window
b. Do early work needed
c. Show the Main Window
d. Handle the shutting down process
As we are managing the lifecycle of the app, there is some manual work required. The bare minimum is below:
internal class Bootstrap
{
[STAThread]
internal static void Main()
{
App app = new App { ShutdownMode = ShutdownMode.OnExplicitShutdown };
SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext());
Bootstrap bs = new();
bs.ExitRequested += (sender, e) =>
{
app.Shutdown();
};
Task programStart = bs.StartAsync();
Task exTask = HandleExceptions(programStart, app);
app.Run();
}
private static async Task HandleExceptions(Task task, Application wpfApplication)
{
try
{
await Task.Yield();
await task;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
wpfApplication.Shutdown();
}
}
public async Task StartAsync()
{
EventHandler windowClosed = (sender, e) => {
Debug.WriteLine($"{nameof(sender)} requested widow closed");
CloseRequested(sender!, e);
};
SplashScreenWindow splashScreen = new SplashScreenWindow();
splashScreen.Closed += windowClosed;
splashScreen.Show();
await Task.Delay(3000).ConfigureAwait(false);
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
MainWindow mainUI = new MainWindow();
mainUI.Closed += windowClosed;
mainUI.Show();
splashScreen.Owner = mainUI;
splashScreen.Closed -= windowClosed;
splashScreen.Close();
});
}
public event EventHandler<EventArgs> ExitRequested;
private void CloseRequested(object sender, EventArgs e)
=> OnExitRequested(EventArgs.Empty);
protected virtual void OnExitRequested(EventArgs e)
=> ExitRequested(this, e);
}
This solution is designed to work with MVVM, however I have commented out key parts. However, if you are using MVVM, then you will need to use the following interface on the
MainWindowViewModel
:
internal interface IViewModelBootStrap
{
event EventHandler<EventArgs> CloseRequested;
void RequestClose();
Task InitializeAsync();
}
If you are using Mvvm, it would look something like:
public class MainWindowViewModel : ViewModelBase, IViewModelBootStrap
{
public event EventHandler<EventArgs> CloseRequested;
public Task InitializeAsync()
{
}
public void RequestClose()
{
CloseRequested.Invoke(this, EventArgs.Empty);
}
}
4. Now, as we are handling the lifecycle of the app, we need to remove the default startup and point to our
BootStrap.Main
.
First, Remove the MainWindow from
App.Xaml
:
<Application x:Class="WpfDynamicSplashScreen.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
</Application.Resources>
</Application>
Now go to the application's Properties (right-click app in solution explorer and choose "Properties") and change "startup" to out
BootStrap
class.
Now run the app and the Splashscreen will show for 3 seconds with an indeterminate progress bar, then will close and show the MainWindow.
You will have no memory leaks.