Wednesday, October 1, 2008

Splash Screen with Prism

The other day, I was working on the WPF application that I'm spending my spare time with. I've been trying to make improvements to startup time of the application's cold-start. With all the improvements done, I thought it'd be a good idea to add a splash screen and show the user about the loading progress. It is relatively easy to setup a splash screen for a WPF application and in fact, I've done that before. But in this application I'm using Prism framework, which if you do not already know, is a WPF Composite Application framework.
Prism itself has some initialization procedure which takes place when you run the bootstrap. Those are :

  1. Create Prism logger facility (ILoggerFacade implementation)
  2. Create the IoC container (Unity, Windsor, etc.)
  3. Register base services like Module Enumerator, Event Aggregator, Region Manager, etc.
  4. Configuration of Region Manager control for base controls like Selector, ContentControl etc.
  5. Creating the Shell Window
  6. Module initializations

...which you usually add other initializations like services, registering custom control region managers, loading resources, etc. and even if you don't, steps 5 and 6 could take long enough, specially if you have multiple Modules or a complex Shell window. So, all this tells me we need a splash window. I also needed to give some kind of feedback on the application initialization process and the splash window should be a WPF window so that I can benefit the power of WPF. Thanks to performance optimizations made in .NET FX 3.5 SP 1, it would not take much to load WPF related libraries in case of a cold-start, so using a WPF splash screen may not be such a bad idea. Also, to spice it up, we'll add an image for every modules we're loading to the splash screen.


Supporting the Splash Screen
To add the support of showing and hiding the splash screen, I've created an interface that needs to be implemented by our application. Our application needs to implement this interface :

/// <summary>
///
Interface that needs to be implemented by
/// WPF application to supporting splash screen.
/// </summary>
public interface ISplashApplication
{
/// <summary>
///
Shows the splash screen
/// </summary>
void ShowSplash();

/// <summary>
/// Hides the splash screen
/// </summary>
void HideSplash();

/// <summary>
///
Adds the module image to the splash screen
/// </summary>
/// <param name="image"></param>
void AddImage(BitmapImage image);

/// <summary>
///
Updates the message in splash screen
/// </summary>
/// <param name="message"></param>
void UpdateSplashMessage(string message);

/// <summary>
///
Sets the MainWindow of the application
/// </summary>
/// <param name="shell"
></param>
void SetMainWindow(Window shell);
}


Since bootstrapper actually loads our modules and initializes services, container, etc. we need to inject the reference of our ISplashApplication to the bootstrapper's constructor. This will let us update the splash screen messages when a work is done in bootstrapper.


Creating a Bootstrapper
I've been using Windsor container as my primary IoC container. I've also developed whole adapter which soon will be available on CompositeWPF Contrib project, which allows you work with Prism and Windsor.

If you're using Unity, you should have no problem. To show our splash screen, we'll override the Run method of the Bootstrapper, and first display the splash screen, and then let the base implementation do its job :

private ISplashApplication splashOwner;

public Bootstrapper(ISplashApplication splashOwner)
{
this.splashOwner = splashOwner;
}

public override void Run(bool useDefaultConfiguration)
{
this.splashOwner.ShowSplash();
this.UpdateMessage("Initializing bootstrapper...");

base.Run(useDefaultConfiguration);
this.UpdateMessage("B
ootstrapper sequence completed");
}


Loading Modules
Prism uses a default implementation of IModuleLoader to create and initialize IModule instances found by IModuleEnumerator. Since we need to update our splash screen before a module is created and initialized, we need to plug-in our own implementation of IModuleLoader which exposes events before initializing modules :

public class SplashModuleLoader : ModuleLoader
{
private readonly IContainerFacade container;

/// <summary>
///
Ctor
/// </summary>
/// <param name="containerFacade"></param>
/// <param name="loggerFacade"></param>
public SplashModuleLoader(IContainerFacade containerFacade, ILoggerFacade loggerFacade) : base(containerFacade, loggerFacade)
{
this.container = containerFacade;
}

/// <summary>
///
Creates an IModule using the container.
/// </summary>
/// <param n
ame="type"></param>
/// <returns></returns>
protected override IModule CreateModule(Type type)
{
IModule module = container.Resolve(type) as IModule;
OnModuleInitializing(type);
return module;
}

/// <summary>
///
Raises ModuleInitializing event when a
/// module is being initialized.
/// </summary>
/// <param name="type"></param>
protected virtual void OnModuleInitializing(Type type)
{
if(ModuleInitializing != null)
ModuleInitializing(this, new DataEventArgs<Type>(type));
}

/// <summary>
///
Event raised when a module is being initialized.
/// </summary>
public event EventHandler<DataEventArgs<Type>> ModuleInitializing;
}

...and we'll listen to this event in the bootstrapper and show the message / image for the IModule being loaded :

protected override void InitializeModules()
{
this.UpdateMessage("Initializing modules...");
this.SubscribeModuleLoading();

base.InitializeModules();
}

protected virtual void SubscribeModuleLoading()
{
SplashModuleLoader loader = (SplashModuleLoader) Container.Resolve(typeof (IModuleLoader));
loader.ModuleInitializing += LoadingModule;
}

private void LoadingModule(object sender, DataEventArgs<Type> e)
{
var moduleImage = e.Value.Name.GetImage();
if(moduleImage != null)
{
splashOwner.AddImage(moduleImage);
}

UpdateMessage("Loading module [" + e.Value.Name + "]");
}


Splash Screen

As said before the Splash Screen is actually a borderless WPF Window, displaying a image in the background plus bunch of Labels to display loading messages and a ListBox to show module images. To show module's images, I've used a horizontal ListBox with images added as list box items.

<Grid>
<Image Stretch="Fill" Name="splashImage" Source="Images\Splash.png" />
<TextBlock Name="lblMessage" Foreground="#FDBB13" TextAlignment="Left" Margin="20,0,20,88" Height="14" VerticalAlignment="Bottom" TextDecorations="None" />
<TextBlock Name="lblCopyright" Foreground="Black" Margin="20,0" Height="29" VerticalAlignment="Bottom" Style="{DynamicResource TextBlockFooterStyle}">
<Run>Copyright 2008-2009 http://www.hightech.ir. All rights reserved.</Run>
<LineBreak/>
<Run>This program is released under GPL license.</Run>
</TextBlock>
<ListBox Margin="5,0,5,38" Height="47" VerticalAlignment="Bottom" Name="ModuleList" BorderThickness="0" Background="Transparent" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" Focusable="False" IsHitTestVisible="False" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</
Grid>
...and when the splash window is displayed with some modules loaded, it should be something like this :

Hope you like it. You can download the source files from here. I'd appreciate your feedbacks and comments about it.

Updated: Download the source file here.
Submit this story to DotNetKicks Shout it

5 comments:

Anonymous said...

Absolutely love it, thanks.

Anonymous said...

Hi, is very nice splash screen sample.

Can I ask you, how I can re-use your thread dispacher implementation in other functionalities, how it could be useful?

Hadi Eskandari said...

Hi,

You can think of it as a mechanism just like Application.DoEvents() in WinForms world. You can pump application messages with it.

thanks for you feedback.

COP6727_SemanticSDLC said...

In the current drop of Prism. The UnityBootstrapper does no expose

Run
or
Run(bool)

as abstract, and therefore, rather painful to try to inject ANYTHING before the main shell comes up.

Marcelo Lopez Jr. said...

Within the latest drop of Prism, UnityExtensions.UnityBootstrapper does NOT have

Run()
or
Run(bool)

as abstract, so you have to resort to some pretty ridiculous mechanisms to side-step around them. Compound to it that
the default constructor for your UnityBootstrapper derived class, CANNOT be instantiated anything else other than the default constructor.

Loads of fun.