支持加载进度的启动画面

时间:2017-04-20 23:31:11

标签: c# wpf mvvm mef caliburn.micro

我使用Caliburn Micro为我的MVVM框架提供了以下bootstrapper类

public class Bootstrapper : BootstrapperBase
{
    private List<Assembly> priorityAssemblies;

    public Bootstrapper()
    {
        PreInitialize();
        Initialize();
    }

    protected virtual void PreInitialize() { }

    protected override void Configure()
    {
        var directoryCatalog = new DirectoryCatalog(@"./");
        AssemblySource.Instance.AddRange(
             directoryCatalog.Parts
                  .Select(part => ReflectionModelServices.GetPartType(part).Value.Assembly)
                  .Where(assembly => !AssemblySource.Instance.Contains(assembly)));

        priorityAssemblies = SelectAssemblies().ToList();
        var priorityCatalog = new AggregateCatalog(priorityAssemblies.Select(x => new AssemblyCatalog(x)));
        var priorityProvider = new CatalogExportProvider(priorityCatalog);

        // Now get all other assemblies (excluding the priority assemblies).
        var mainCatalog = new AggregateCatalog(
            AssemblySource.Instance
                .Where(assembly => !priorityAssemblies.Contains(assembly))
                .Select(x => new AssemblyCatalog(x)));
        var mainProvider = new CatalogExportProvider(mainCatalog);

        Container = new CompositionContainer(priorityProvider, mainProvider);
        priorityProvider.SourceProvider = Container;
        mainProvider.SourceProvider = Container;

        var batch = new CompositionBatch();

        BindServices(batch);
        batch.AddExportedValue(mainCatalog);

        Container.Compose(batch);
    }

    protected virtual void BindServices(CompositionBatch batch)
    {
        batch.AddExportedValue<IWindowManager>(new WindowManager());
        batch.AddExportedValue<IEventAggregator>(new EventAggregator());
        batch.AddExportedValue(Container);
        batch.AddExportedValue(this);
    }

    protected override object GetInstance(Type serviceType, string key)
    {
        String contract = String.IsNullOrEmpty(key) ?
            AttributedModelServices.GetContractName(serviceType) :
            key;
        var exports = Container.GetExports<object>(contract);

        if (exports.Any())
            return exports.First().Value;

        throw new Exception(
            String.Format("Could not locate any instances of contract {0}.", contract));
    }

    protected override IEnumerable<object> GetAllInstances(Type serviceType)
    {
        return Container.GetExportedValues<object>(
            AttributedModelServices.GetContractName(serviceType));
    }

    protected override void BuildUp(object instance)
    {
        Container.SatisfyImportsOnce(instance);
    }

    protected override void OnStartup(object sender, StartupEventArgs suea)
    {
        base.OnStartup(sender, suea);
        DisplayRootViewFor<IMainWindow>();
    }

    protected override IEnumerable<Assembly> SelectAssemblies()
    {
        return new[] { Assembly.GetEntryAssembly() };
    }

    protected CompositionContainer Container { get; set; }

    internal IList<Assembly> PriorityAssemblies
    {
        get { return priorityAssemblies; }
    }
}

这很好,效果很好,加载我导出的模块等。现在我想实现一个显示进度的启动画面(进度条和加载的导出信息等),所以我不想要标准WPF SplashScreen,它只是一个静态图像。

现在我已经看到了Custom caliburn.micro splashscreen with shell screen conductor,但对我而言,这不是问题,只有在OnStartup之后才会加载MEF导出。

所以,我添加了以下SplashScreenManager

[Export(typeof(ISplashScreenManager))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class SplashScreenManager : ISplashScreenManager
{
    private IWindowManager windowManager;
    private ISplashScreen splashScreen;

    private Thread splashThread;
    private Dispatcher splashDispacher;

    [ImportingConstructor]
    public SplashScreenManager(IWindowManager windowManager, ISplashScreen splashScreen)
    {
        if (windowManager == null)
            throw new ArgumentNullException("windowManager cannot be null");

        if (splashScreen == null)
            throw new ArgumentNullException("splashScreen cannot be null");

        this.windowManager = windowManager;
        this.splashScreen = splashScreen;
    }

    public void ShowSplashScreen()
    {
        splashDispacher = null;
        if (splashThread == null)
        {
            splashThread = new Thread(new ThreadStart(DoShowSplashScreen));
            splashThread.SetApartmentState(ApartmentState.STA);
            splashThread.IsBackground = true;
            splashThread.Start();
            Log.Trace("Splash screen thread started");
        }
    }

    private void DoShowSplashScreen()
    {
        splashDispacher = Dispatcher.CurrentDispatcher;
        SynchronizationContext.SetSynchronizationContext(
            new DispatcherSynchronizationContext(splashDispacher));

        splashScreen.Closed += (s, e) =>
            splashDispacher.BeginInvokeShutdown(DispatcherPriority.Background);
        Application.Current.Dispatcher.BeginInvoke(
            new System.Action(delegate { windowManager.ShowWindow(splashScreen); }));

        Dispatcher.Run();
        Log.Trace("Splash screen shown and dispatcher started");
    }

    public void CloseSplashScreen()
    {
        if (splashDispacher != null)
        {
            splashDispacher.BeginInvoke(
                new System.Action(delegate { splashScreen.Close(); }));
            Log.Trace("Splash screen close requested");
        }
    }

    public ISplashScreen SplashScreen
    {
        get { return splashScreen; }
    }
}

尝试使用Caliburn IWindowManager在后​​台线程上显示启动画面。 ISplashScreenManager

的地方
public interface ISplashScreenManager
{
    void ShowSplashScreen();

    void CloseSplashScreen();

    ISplashScreen SplashScreen { get; }
}

然后我们有ISplashScreen[ViewModel]实现

[Export(typeof(ISplashScreen))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class SplashScreenViewModel : Screen, ISplashScreen, ISupportProgress
{
    // Code to provide updates to the view etc. 
}

此时ISplashScreen为空标记界面。

所以我的问题是如何正确地命令SplashScreenViewModel的调用,以便在模块加载到引导程序GetInstance方法时显示启动画面?

我尝试过像

这样的事情
protected override void OnStartup(object sender, StartupEventArgs suea)
{
    var splashManager = Container.GetExportedValue<ISplashScreenManager>();
    var windowManager = IoC.Get<IWindowManager>();
    windowManager.ShowWindow(splashManager.SplashScreen);

    base.OnStartup(sender, suea);
    DisplayRootViewFor<IMainWindow>();

    splashManager.SplashScreen.TryClose();
}

但这会立即关闭启动画面,并且不会使用我的多线程代码来显示SplashScreenManager中的启动画面。

我愿意大量修改代码来做我想做的事情,但我现在似乎可以得到正确的组合。我想避免深入思考线程和使用ManualResetEvent s,然后再向大家询问有关如何最好地进行的建议。

感谢您的时间。

部分解决方案:我现在在我的bootstrapper类中的OnStartup方法中有以下代码

protected override void OnStartup(object sender, StartupEventArgs suea)
{
    splashScreenManager = Container.GetExportedValue<ISplashScreenManager>();
    splashScreenManager.ShowSplashScreen();

    Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
    Application.Current.MainWindow = null;

    base.OnStartup(sender, suea);
    DisplayRootViewFor<IMainWindow>();

    // I have also tried this.
    Application.Current.Dispatcher.Invoke(() => DisplayRootViewFor<IMainWindow>());

    splashScreenManager.CloseSplashScreen();
}

SplashScreenManager类是

[Export(typeof(ISplashScreenManager))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class SplashScreenManager : ISplashScreenManager
{
    private IWindowManager windowManager;
    private ISplashScreenViewModel splashScreen;

    private Thread splashThread;
    private Dispatcher splashDispacher;

    public void ShowSplashScreen()
    {
        splashDispacher = null;
        if (splashThread == null)
        {
            splashThread = new Thread(new ThreadStart(DoShowSplashScreen));
            splashThread.SetApartmentState(ApartmentState.STA);
            splashThread.IsBackground = true;
            splashThread.Name = "SplashThread"; 
            splashThread.Start();
            Log.Trace("Splash screen thread started");
        }
    }

    private void DoShowSplashScreen()
    {
        // Get the splash vm on the splashThread.
        splashScreen = IoC.Get<ISplashScreenViewModel>();

        splashDispacher = Dispatcher.CurrentDispatcher;
        SynchronizationContext.SetSynchronizationContext(
            new DispatcherSynchronizationContext(splashDispacher));

        splashScreen.Closed += (s, e) =>
            splashDispacher.BeginInvokeShutdown(DispatcherPriority.Background);
        splashScreen.Show();

        Dispatcher.Run();
        Log.Trace("Splash screen shown and dispatcher started");
    }

    public void CloseSplashScreen()
    {
        if (splashDispacher != null)
        {
            splashScreen.Close();
            Log.Trace("Splash screen close requested");
        }
    }

    public ISplashScreenViewModel SplashScreen
    {
        get { return splashScreen; }
    }
}

现在显示带有不确定进度条的启动画面(尚未连接消息),看起来像

现在,问题是,当我们点击

DisplayRootViewFor<IMainWindow>();

它会抛出InvalidOperationException消息

  

调用线程无法访问此对象,因为另一个线程拥有它。

堆栈跟踪

  System.Windows.Threading.Dispatcher.VerifyAccess()上的

     在System.Windows.DependencyObject.GetValue(DependencyProperty dp)      位于d:\ projects \ git \ MahApps.Metro \ src \ MahApps.Metro \ MahApps.Metro.Shared \ Controls \ MetroWindow.cs中的MahApps.Metro.Controls.MetroWindow.get_Flyouts():第269行      在MahApps.Metro.Controls.MetroWindow.ThemeManagerOnIsThemeChanged(Object sender,OnThemeChangedEventArgs e)中的d:\ projects \ git \ MahApps.Metro \ src \ MahApps.Metro \ MahApps.Metro.Shared \ Controls \ MetroWindow.cs:第962行      在System.EventHandler 1.Invoke(Object sender, TEventArgs e) at MahApps.Metro.Controls.SafeRaise.Raise[T](EventHandler 1 eventToRaise,Object sender,T args)在d:\ projects \ git \ MahApps.Metro \ src \ MahApps.Metro \ MahApps.Metro.Shared \ Controls \ SafeRaise.cs:第26行      在MahApps.Metro.ThemeManager.OnThemeChanged(Accent newAccent,AppTheme newTheme)中的d:\ projects \ git \ MahApps.Metro \ src \ MahApps.Metro \ MahApps.Metro.Shared \ ThemeManager \ ThemeManager.cs:第591行      在MahApps.Metro.ThemeManager.ChangeAppStyle(ResourceDictionary资源,Tuple`2 oldThemeInfo,Accent newAccent,AppTheme newTheme)中的d:\ projects \ git \ MahApps.Metro \ src \ MahApps.Metro \ MahApps.Metro.Shared \ ThemeManager \ ThemeManager .cs:第407行      在MahApps.Metro.ThemeManager.ChangeAppStyle(应用程序应用程序,Accent newAccent,AppTheme newTheme)中的d:\ projects \ git \ MahApps.Metro \ src \ MahApps.Metro \ MahApps.Metro.Shared \ ThemeManager \ ThemeManager.cs:第345行      在Augur.Core.Themes.ThemeManager.SetCurrentTheme(String name)在F:\ Camus \ Augur \ Src \ Augur \ Core \ Themes \ ThemeManager.cs:第46行      在F:\ Camus \ Augur \ Src \ Augur \ Modules \ Shell \ ViewModels \ ShellViewModel.cs中的Augur.Modules.Shell.ViewModels.ShellViewModel.OnViewLoaded(对象视图):第73行      在Caliburn.Micro.XamlPlatformProvider。&lt;&gt; c__DisplayClass11_0.b__0(对象s,RoutedEventArgs e)      在Caliburn.Micro.View。&lt;&gt; c__DisplayClass8_0.b__0(对象s,RoutedEventArgs e)      at System.Windows.EventRoute.InvokeHandlersImpl(Object source,RoutedEventArgs args,Boolean reRaised)      在System.Windows.UIElement.RaiseEventImpl(DependencyObject sender,RoutedEventArgs args)      在System.Windows.BroadcastEventHelper.BroadcastEvent(DependencyObject root,RoutedEvent routedEvent)      在System.Windows.BroadcastEventHelper.BroadcastLoadedEvent(Object root)      在MS.Internal.LoadedOrUnloadedOperation.DoWork()      在System.Windows.Media.MediaContext.FireLoadedPendingCallbacks()      在System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()      在System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)      在System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget)      在System.Windows.Interop.HwndTarget.OnResize()      在System.Windows.Interop.HwndTarget.HandleMessage(WindowMessage msg,IntPtr wparam,IntPtr lparam)      在System.Windows.Interop.HwndSource.HwndTargetFilterMessage(IntPtr hwnd,Int32 msg,IntPtr wParam,IntPtr lParam,Boolean&amp; handling)      在MS.Win32.HwndWrapper.WndProc(IntPtr hwnd,Int32 msg,IntPtr wParam,IntPtr lParam,Boolean&amp; handling)      在MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)      在System.Windows.Threading.ExceptionWrapper.InternalRealCall(委托回调,对象args,Int32 numArgs)      在System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source,Delegate callback,Object args,Int32 numArgs,Delegate catchHandler)      在System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority,TimeSpan timeout,Delegate方法,Object args,Int32 numArgs)      在MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd,Int32 msg,IntPtr wParam,IntPtr lParam)

我试图更改代码以使用Application调度程序并存储任务调度程序并将其与Task一起使用以返回Gui线程。我不知道为什么我会丢失线程上下文,我做错了什么以及如何解决它?

尝试修复

splashScreenManager = Container.GetExportedValue<ISplashScreenManager>();
splashScreenManager.ShowSplashScreen();

Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
Application.Current.MainWindow = null;

base.OnStartup(sender, suea);
Application.Current.Dispatcher.Invoke(() => DisplayRootViewFor<IMainWindow>()); // Still throws the same exception.

splashScreenManager = Container.GetExportedValue<ISplashScreenManager>();
splashScreenManager.ShowSplashScreen();

Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
Application.Current.MainWindow = null;

base.OnStartup(sender, suea);
Application.Current.Dispatcher.BeginInvoke(
    new System.Action(delegate { DisplayRootViewFor<IMainWindow>(); })); // Still throws the same exception.

TaskScheduler guiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

splashScreenManager = Container.GetExportedValue<ISplashScreenManager>();
splashScreenManager.ShowSplashScreen();

Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
Application.Current.MainWindow = null;

Task.Factory.StartNew(() =>
{
    base.OnStartup(sender, suea);
    DisplayRootViewFor<IMainWindow>();
}, CancellationToken.None, 
   TaskCreationOptions.None, 
   guiScheduler);

有什么想法吗?

1 个答案:

答案 0 :(得分:1)

为什么不在后台线程中显示启动画面窗口,首先在OnStartup方法中执行,然后在初始化完成后关闭它?:

protected override async void OnStartup(object sender, StartupEventArgs suea)
{
    Application.Current.ShutdownMode = ShutdownMode.OnLastWindowClose;
    Window splashScreenWindow = null;
    Thread splashScreenWindowThread = new Thread(new ThreadStart(() =>
    {
        SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));
        splashScreenWindow = new Window();
        splashScreenWindow.Content = new ProgressBar() { IsIndeterminate = true };
        splashScreenWindow.Closed += (ss, es) => Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
        splashScreenWindow.Show();
        Dispatcher.Run();
    }));
    splashScreenWindowThread.SetApartmentState(ApartmentState.STA);
    splashScreenWindowThread.IsBackground = true;
    splashScreenWindowThread.Start();

    base.OnStartup(sender, suea);
    //...
    splashScreenWindow.Dispatcher.BeginInvoke(new Action(() => splashScreenWindow.Close()));
}
相关问题