1
votes

I'm creating an application using Caliburn.Micro. The application communicates with an api during startup which is why I need to show a Splashscreen to the users. I've created my own animated Splashscreen as a Window which is activated from bootstrapper in the OnStartup method. The startup process is managed by the splashscreens viewmodel.

When all startup related processes are finished how do I tell the bootstrapper to close the splashscreen and activate another window?

I thought about raising an event but I cannot subscribe the bootstrapper to the IEventaggregator.

I tried displaying the Splashscreen inside of a contentcontrol in the ShellView and just switch to a different vm after the loading is done. The problem here is that the splash should be displayed on a transparent, borderless window which cannot be changed after the window is created.

public class Bootstrapper : BootstrapperBase
    {
        private SimpleContainer _container = new SimpleContainer();

        public Bootstrapper()
        {
            Initialize();
        }

        protected override void Configure()
        {
            _container
                .Singleton<IWindowManager, WindowManager>()
                .Singleton<IEventAggregator, EventAggregator>();

            GetType().Assembly.GetTypes()
                .Where(type => type.IsClass)
                .Where(type => type.Name.EndsWith("ViewModel"))
                .ToList()
                .ForEach(viewModelType => _container.RegisterPerRequest(
                    viewModelType, viewModelType.ToString(), viewModelType));
        }

        protected override void OnStartup(object sender, StartupEventArgs e)
        {
            FrameworkElement.LanguageProperty.OverrideMetadata(
                typeof(FrameworkElement),
                new FrameworkPropertyMetadata(
                    XmlLanguage.GetLanguage(
                        CultureInfo.CurrentCulture.IetfLanguageTag
                        )
                    )
                );
            DisplayRootViewFor<AnimatedSplashViewModel>();
            //DisplayRootViewFor<ShellViewModel>();
        }


        protected override object GetInstance(Type service, string key)
        {
            return _container.GetInstance(service, key);
        }

        protected override IEnumerable<object> GetAllInstances(Type service)
        {
            return _container.GetAllInstances(service);
        }

        protected override void BuildUp(object instance)
        {
            _container.BuildUp(instance);
        }
    }
 public class AnimatedSplashViewModel : Screen
    {
        private IEventAggregator _events;

        private string _splashMessage;

        public string SplashMessage
        {
            get { return _splashMessage; }
            set
            {
                _splashMessage = value;
                NotifyOfPropertyChange(() => SplashMessage);
            }
        }

        public AnimatedSplashViewModel(IEventAggregator events)
        {
            _events = events;
            SplashMessage = "Please wait";

            // Simulation of long tasks
            var worker = new BackgroundWorker();
            worker.DoWork += Worker_DoWork;
            worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
            worker.RunWorkerAsync();


        }

        private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            _events.PublishOnUIThread(new SplashFinishedEvent());
        }

        private void Worker_DoWork(object sender, DoWorkEventArgs e)
        {
            Thread.Sleep(10000);
        }
    }
2

2 Answers

1
votes

You should either use a ShellViewModel for the root view and replace the splash screen view with a "main" view, or you could just wait to display the root view until the splash screen has been closed:

protected override void OnStartup(object sender, StartupEventArgs e)
{
    Application.ShutdownMode = ShutdownMode.OnExplicitShutdown;

    var windowManager = IoC.Get<IWindowManager>();
    var eventAggregator = IoC.Get<IEventAggregator>();
    windowManager.ShowDialog(new AnimatedSplashViewModel(eventAggregator));
    DisplayRootViewFor(typeof(ShellViewModel));
}

...
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    TryClose();
}
1
votes

I case anyone wondering about the final solution:

First I used the WindowManager to create a Dialog of the Splashscreen and let the SplashscreenViewModel do all the work.

Turned out this approach takes ages to load. So when I tried to execute it took around 8 seconds for the Dailog to show up. This is far too long for my impatient users. I think this was because I used IoC to inject alot of dependecies into the SplashscreenViewModel.

windowManager.ShowDialog(new AnimatedSplashViewModel(locationEndpoint, userEndpoint, applicationEndpoint, adUser, clientInfo, locationInfo, loggedInUser));

Second approach was to create the Splashscreen as a dialog and use a BackgroundWorker for all the computing and api stuff inside the Bootstrapper. While this worked quite fast I felt that there must be a better approach.

Third and final solution: The Bootstrapper calls the ShellViewModel.

 public Bootstrapper()
    {
        Initialize();
        DisplayRootViewFor<ShellViewModel>();
    }

In the OnInitialize method I've created a BackgroundWorker which executes all the long running tasks while displaying the SplashScreen as a Dialog using the WindowManager.

protected override void OnInitialize()
    {
        var windowManager = new WindowManager();
        using (BackgroundWorker bw = new BackgroundWorker())
        {
            bw.DoWork += InitializeApplication;
            bw.RunWorkerCompleted += InitializationCompleted;
            bw.RunWorkerAsync();
            windowManager.ShowDialog(new AnimatedSplashViewModel(_events));
        }
    }

The AnimatedSplashscreenViewModel now only requires one dependency which is the EventAggregator. I let it handle a custom Event named SplashMessageChangedEvent.

public class SplashMessageChangedEvent
{
    public string Content { get; set; }
    public bool CloseDialog { get; set; } = false;

    public SplashMessageChangedEvent(string content)
    {
        Content = content;
    }

    public SplashMessageChangedEvent(bool closeDialog)
    {
        CloseDialog = closeDialog;
    }
}

In the InitializationCompleted Event in the ShellViewModel I publish the following event to close the Dialog:

private void InitializationCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        _events.PublishOnUIThread(new SplashMessageChangedEvent(true));
    }

Now this final approach is much faster than the other two. The Splashscreen is shown instantly after starting the executable.