使用 Caliburn.Micro 的动画启动画面

Animated Splashscreen using Caliburn.Micro

我正在使用 Caliburn.Micro 创建一个应用程序。该应用程序在启动期间与 api 通信,这就是我需要向用户显示启动画面的原因。我创建了自己的动画启动画面作为 Window,它在 OnStartup 方法中从引导程序激活。 启动过程由启动画面视图模型管理。

当所有与启动相关的过程都完成后,我如何告诉引导程序关闭闪屏并激活另一个 window?

我考虑过发起一个事件,但我无法将引导程序订阅到 IEventaggregator。

我尝试在 ShellView 的 contentcontrol 中显示 Splashscreen,并在加载完成后切换到不同的虚拟机。这里的问题是启动画面应该显示在透明、无边框的 window 上,在创建 window 后无法更改。

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);
        }
    }

您应该为根视图使用 ShellViewModel 并用 "main" 视图替换初始屏幕视图,或者您可以等到初始屏幕显示后再显示根视图关闭:

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();
}

我假设有人想知道最终的解决方案:

首先,我使用 WindowManager 创建了一个 Splashscreen 的对话框,并让 SplashscreenViewModel 完成所有工作。

原来这种方法需要很长时间才能加载。因此,当我尝试执行时,Dailog 需要大约 8 秒的时间才能显示出来。这对我不耐烦的用户来说太长了。 我认为这是因为我使用 IoC 将大量依赖项注入 SplashscreenViewModel。

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

第二种方法是将 Splashscreen 创建为一个对话框,并使用 BackgroundWorker 来处理 Bootstrapper 中的所有计算和 api 内容。 虽然这工作得非常快,但我觉得必须有更好的方法。

第三个也是最后一个解决方案: Bootstrapper 调用 ShellViewModel。

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

在 OnInitialize 方法中,我创建了一个 BackgroundWorker,它执行所有长 运行 任务,同时使用 WindowManager 将 SplashScreen 显示为对话框。

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));
        }
    }

AnimatedSplashscreenViewModel 现在只需要一个依赖项,即 EventAggregator。我让它处理一个名为 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;
    }
}

在 ShellViewModel 的 InitializationCompleted 事件中,我发布了以下事件以关闭对话框:

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

现在这个最终方法比其他两个方法快得多。 启动可执行文件后立即显示启动画面。