Help with "Improving perceived WPF app startup performance with MEF and a Splash Screen".

Topics: Prism v4 - WPF 4
Aug 6, 2013 at 7:41 PM
Hi,

Need help trying to get my custom splash screen working based on this example from Channel 9 "Improving perceived WPF app startup performance with MEF and a Splash Screen".

I can't get any of the Storyboards to work nor display text in a TextBlock, creating the splash screen during InitializeModules() in my MefBootstrapper:
public class NatsarBootstrapper : MefBootstrapper
{
    #region Private Properties
    private IEventAggregator EventAggregator
    {
        get { return ServiceLocator.Current.GetInstance<IEventAggregator>(); }
    }
    #endregion

    protected override void InitializeModules()
    {
        IModuleManager manager = ServiceLocator.Current.GetInstance<IModuleManager>();

        Loading loading = this.Container.GetExportedValue<Loading>();
        Shell shellMain = this.Container.GetExportedValue<Shell>();

        loading.WindowStartupLocation = WindowStartupLocation.CenterScreen;
        loading.WindowState = WindowState.Normal;

        Application.Current.MainWindow = loading;
        Application.Current.MainWindow.Show();

        EventAggregator.GetEvent<MessageUpdateEvent>().Publish(new MessageUpdateEvent { Message = "Loading TurahStudyUIModule" });
        manager.LoadModule("TurahStudyUIModule");

        Application.Current.MainWindow.Hide();

        TurahStudyUIView view = ServiceLocator.Current.GetInstance<TurahStudyUIView>();

        shellMain.Height = 768;
        shellMain.Width = 1024;
        shellMain.WindowStartupLocation = WindowStartupLocation.CenterScreen;
        shellMain.WindowState = WindowState.Maximized;
        shellMain.Activate();

        Application.Current.MainWindow = shellMain;
        Application.Current.MainWindow.Content = view;
        Application.Current.MainWindow.Show();
    }

    protected override void ConfigureAggregateCatalog()
    {
        try
        {
            this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(NatsarBootstrapper).Assembly));
            this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(NatsarCommonModule).Assembly));
            this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(TurahStudyUIModule).Assembly));
        }
        catch (Exception msg)
        {
            .....
        }
    }

    protected override DependencyObject CreateShell()
    {
        DependencyObject shell = null;

        return shell;
    }
}
Loading.xaml Window:
<Window 
    x:Class="NatsarTurahStudyMaster.Views.Loading"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    AllowsTransparency="True" WindowStyle="None" >
    <Window.Background>
        <ImageBrush ImageSource="habakkuk-paleo-hebrew.png"  Stretch="UniformToFill"/>
    </Window.Background>
    <Window.Resources>
        <Storyboard  x:Key="InTransition">

                <!-- Create an animation that repeats indefinitely. -->
                <DoubleAnimation 
                  Storyboard.TargetName="foreverRepeatingRectangle" Storyboard.TargetProperty="(Rectangle.Width)" 
                  From="50" To="300" Duration="0:0:2" RepeatBehavior="Forever" />

                <!-- Create an animation that repeats for four seconds. As a result, the
                     animation repeats twice. -->
                <DoubleAnimation Storyboard.TargetName="fourSecondsRepeatingRectangle" Storyboard.TargetProperty="(Rectangle.Width)"
                  From="50" To="300" Duration="0:0:2" RepeatBehavior="0:0:4" />

                <!-- Create an animation that repeats twice. -->
                <DoubleAnimation Storyboard.TargetName="twiceRepeatingRectangle" Storyboard.TargetProperty="(Rectangle.Width)" 
                  From="50" To="300" Duration="0:0:2" RepeatBehavior="2x" />

                <!-- Create an animation that repeats 0.5 times. The resulting animation
                     plays for one second, half of its Duration. It animates from 50 to 150. -->
                <DoubleAnimation Storyboard.TargetName="halfRepeatingRectangle" Storyboard.TargetProperty="(Rectangle.Width)" 
                  From="50" To="300" Duration="0:0:2" RepeatBehavior="0.5x" />

                <!-- Create an animation that repeats for one second. The resulting animation
                     plays for one second, half of its Duration. It animates from 50 to 150. -->
                <DoubleAnimation Storyboard.TargetName="oneSecondRepeatingRectangle" Storyboard.TargetProperty="(Rectangle.Width)" 
                  From="50" To="300" Duration="0:0:2" RepeatBehavior="0:0:1" />
            </Storyboard>
    </Window.Resources>

    <Window.Triggers>
        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <BeginStoryboard Storyboard="{StaticResource InTransition}"/>
        </EventTrigger>
    </Window.Triggers>
    <Grid>
        <Border HorizontalAlignment="Stretch">
            <StackPanel Margin="20" >
                <TextBlock>RepeatBehavior="Forever"</TextBlock>
                <Rectangle Name="foreverRepeatingRectangle" Fill="#AA3333FF" Width="50" Height="20" HorizontalAlignment="Left" />
                <TextBlock Margin="0,20,0,0">RepeatBehavior="0:0:4"</TextBlock>
                <Rectangle Name="fourSecondsRepeatingRectangle" Fill="#AA3333FF" Width="50" Height="20" HorizontalAlignment="Left" />
                <TextBlock Margin="0,20,0,0">RepeatBehavior="2x"</TextBlock>
                <Rectangle Name="twiceRepeatingRectangle" Fill="#AA3333FF" Width="50" Height="20" HorizontalAlignment="Left" />
                <TextBlock Margin="0,20,0,0">RepeatBehavior="0.5x"</TextBlock>
                <Rectangle Name="halfRepeatingRectangle" Fill="#AA3333FF" Width="50" Height="20" HorizontalAlignment="Left" />
                <TextBlock Margin="0,20,0,0">RepeatBehavior="0:0:1"</TextBlock>
                <Rectangle Name="oneSecondRepeatingRectangle" Fill="#AA3333FF" Width="50" Height="20" HorizontalAlignment="Left" />
            </StackPanel>
        </Border>

        <TextBlock Text="{Binding Status}" Margin="10,0" Grid.Row="2" Grid.ColumnSpan="2" 
                   TextAlignment="Right">
            <TextBlock.Effect>
                <DropShadowEffect ShadowDepth="1" Color="#99ffffff"/>
            </TextBlock.Effect>
        </TextBlock>
    </Grid>
</Window>
[/code]
Loading.xaml.cs:
[Export(typeof(Loading))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class Loading : Window, IPartImportsSatisfiedNotification
{
    public Loading()
    {
        InitializeComponent();
    }

    [Import]
    public LoadingModel ViewModel
    {
        set
        {
            this.DataContext = value;
        }
        get
        {
            return DataContext as LoadingModel;
        }
    }

    /// <summary>
    /// Called when a part's imports have been satisfied and it is safe to use.
    /// </summary>
    public void OnImportsSatisfied()
    {
        // IPartImportsSatisfiedNotification is useful when you want to coordinate doing some work
        // with imported parts independent of when the UI is visible.
        Debug.WriteLine("Loading OnImportsSatisfied instantiation");
    }
}
LoadingModel.cs is where I'm just sending test text:
[Export(typeof(LoadingModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class LoadingModel : NotificationObject
{
    [ImportingConstructor]
    public LoadingModel(IEventAggregator eventAggregator)
    {
        Debug.WriteLine("LoadingModel instantiation");
        eventAggregator.GetEvent<MessageUpdateEvent>().Subscribe(e => UpdateMessage(e.Message));
    }

    private string _status;
    public string Status
    {
        get { return _status; }
        set
        { 
            _status = value; 
            RaisePropertyChanged(() => this.Status); 
        }
    }

    private void UpdateMessage(string message)
    {
        if (string.IsNullOrEmpty(message))
        {
            return;
        }

        int maxRecords = 1000;
        for (int x = 1; x < maxRecords; x++)
        {
            System.Threading.Thread.Sleep(10);
            Status = string.Format("{0}: {1} {2}",message ,Convert.ToInt32( ((decimal)x / (decimal)maxRecords) * 100), "...");
        }
    }
}
But, nothing is working?
Aug 6, 2013 at 11:08 PM
Well, I couldn't get it to work as I wanted, so I had to result to doing it this way:
protected override void InitializeModules()
{
    Shell shellMain = this.Container.GetExportedValue<Shell>();
 
    shellMain.InitializeMainWindow();
 
    TurahStudyUIView view = ServiceLocator.Current.GetInstance<TurahStudyUIView>();
 
    shellMain.Height = 768;
    shellMain.Width = 1024;
    shellMain.WindowStartupLocation = WindowStartupLocation.CenterScreen;
    shellMain.WindowState = WindowState.Maximized;
    shellMain.Activate();
 
    Application.Current.MainWindow = shellMain;
    Application.Current.MainWindow.Content = view;
    Application.Current.MainWindow.Activate();
}
and put the splash screen in my main window Shell.xaml.cs:
[Export(typeof(Shell))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class Shell : Window, IPartImportsSatisfiedNotification
{
    private readonly BackgroundWorker _compositionBackgroundWorker = new BackgroundWorker();
    Loading loading;
 
    public Shell()
    {
        InitializeComponent();
 
        _compositionBackgroundWorker.DoWork += CompositionBackgroundWorker_DoWork;
        _compositionBackgroundWorker.RunWorkerCompleted += CompositionBackgroundWorkerRunWorker_Completed;
    }
 
    public void InitializeMainWindow()
    {
        // Set the Window.Content to the "Loading UI" UserControl
        loading = ServiceLocator.Current.GetInstance<Loading>();
        loading.Show();
 
        _compositionBackgroundWorker.RunWorkerAsync();
    }
 
    private void CompositionBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        this.InitializeModules();
    }
 
    private void InitializeModules()
    {
        IModuleManager manager = ServiceLocator.Current.GetInstance<IModuleManager>();
 
        int maxRecords = 1000;
        for (int x = 1; x < maxRecords; x++)
        {
            System.Threading.Thread.Sleep(5);
            System.Windows.Application.Current.Dispatcher.Invoke((Action)(() => { loading.ViewModel.Status = string.Format("{0}: {1} {2}", "Loading", Convert.ToInt32(((decimal)x / (decimal)maxRecords) * 100), "..."); }));
        }
    }
 
    private void CompositionBackgroundWorkerRunWorker_Completed(object sender, RunWorkerCompletedEventArgs e)
    {
        loading.Hide();
        loading.Close();
        this.Show();
    }
 
    [Import]
    public ShellModel ViewModel
    {
        set
        {
            this.DataContext = value;
        }
        get
        {
            return DataContext as ShellModel;
        }
    }
 
    /// <summary>
    /// Called when a part's imports have been satisfied and it is safe to use.
    /// </summary>
    public void OnImportsSatisfied()
    {
        // IPartImportsSatisfiedNotification is useful when you want to coordinate doing some work
        // with imported parts independent of when the UI is visible.
        Debug.WriteLine("Shell OnImportsSatisfied instantiation");
    }
}
All development from here will be in TurahStudyUIView.
Aug 7, 2013 at 2:58 PM
Hi,

Based on the code snippets you provided, I couldn't find anything that could be causing the issue, since the code seems to be correct. Nevertheless, I couldn't reproduce your issue since I don't fully understand how your application is designed based on the example (Loading.xaml is defined as a Window where in the example it is a UserControl). Therefore, it would be useful if you can provide us the error messages you are receiving (if you are getting any) and a sample of your application so we can analyze deeper the root of your problem.

Nevertheless, I'm glad you found a workaround to make it work somehow.

Regards,

Federico Martinez
http://blogs.southworks.net/fmartinez