繁体   English   中英

启动画面支持加载进度

[英]Splash Screen that Supports Loading Progress

我有以下将Caliburn Micro用于我的MVVM框架的引导程序类

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初始屏幕,但对我而言,这不是问题,只有在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 ,然后再请高级人员就如何进行最佳处理提出建议。

谢谢你的时间。


部分解决方案:我的引导程序类中的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与消息

调用线程无法访问该对象,因为其他线程拥有它。

堆栈跟踪为

在d:\\ projects \\ git \\ MahApps.Metro \\ src \\中的MahApps.Metro.Controls.MetroWindow.get_Flyouts()中的System.Windows.DependencyObject.GetValue(DependencyProperty dp)中的System.Windows.Threading.Dispatcher.VerifyAccess()中MahApps.Metro.Controls.MetroWindow.cs上的MahApps.Metro \\ MahApps.Metro.Shared \\ Controls \\ MetroWindow.cs:线269在d:\\ projects \\ git \\ MahApps.Metro \\ src \\ MahApps .Metro \\ MahApps.Metro.Shared \\ Controls \\ MetroWindow.cs:System.EventHandler 1.Invoke(Object sender, TEventArgs e) at MahApps.Metro.Controls.SafeRaise.Raise[T](EventHandler线962 1.Invoke(Object sender, TEventArgs e) at MahApps.Metro.Controls.SafeRaise.Raise[T](EventHandler 1 eventToRaise,Object 1.Invoke(Object sender, TEventArgs e) at MahApps.Metro.Controls.SafeRaise.Raise[T](EventHandler 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(ResourceDic d,\\ projects \\ git \\ MahApps.Metro \\ src \\ MahApps.Metro \\ MahApps.Metro.Shared \\ ThemeManager \\ ThemeManager \\ ThemeManager.cs:第407行上的dtuary`2资源,元组newOldThemeInfo,Accent newAccent,AppTheme newTheme)。 .ThemeManager.ChangeAppStyle(Application app,d:\\ projects \\ git \\ MahApps.Metro \\ src \\ MahApps.Metro \\ MahApps.Metro.Shared \\ ThemeManager \\ ThemeManager.cs:line 345在d:\\ projects \\ git \\ MahApps.Metro.Shared .Themes.ThemeManager.SetCurrentTheme(String name)in F:\\ Camus \\ Augur \\ Src \\ Augur \\ Core \\ Themes \\ ThemeManager.cs:F 46中Augur.Modules.Shell.ViewModels.ShellViewModel.OnViewLoaded(Object view)的第46行:: Camus \\ Augur \\ Src \\ Augur \\ Modules \\ Shell \\ ViewModels \\ ShellViewModel.cs:Caliburn.Micro.XamlPlatformProvider上的第73行。<> c__DisplayClass11_0.b__0(Object s,RoutedEventArgs e)在Caliburn.Micro.View。<> System.Windows.UI.UIElement.RaiseEventImpl(Dep)上的c__DisplayClass8_0.b__0(Object s,RoutedEventArgs e)在System.Windows.EventRoute.InvokeHandlersImpl(Object source,RoutedEventArgs args,Boolean reRaised) endencyObject发送者,位于System.Windows.BroadcastEventHelper.BroadcastEvent(DependencyObject根,RoutedEvent routedEvent),位于System.Windows.BroadcastEventHelper.BroadcastLoadedEvent(对象根),位于System.Windows.Media的MS.Internal.LoadedOrUnloadedOperation.DoWork()。 System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()处于System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)处于System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget)处于System.Windows。 System.Windows.Interop.HwndTarget.HandleMessage处的Interop.HwndTarget.OnResize()(System.Windows.Interop.HwndSource.HwndTargetFilterMessage(IntPtr hwnd,Int32 msg,IntPtr lParam,IntPtr,IntPtr lParam,IntPtr lParam) MS.Win32.HwndSubclass.Di上的MS.Win32.HwndWrapper.WndProc(IntPtr hwnd,Int32 msg,IntPtr wParam,IntPtr lParam,Boolean&处理) System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source,Delegate callback,Object args,Int32 numArgs,Delegate catchHandler的委托回调,Object args,Int32 numArgs) ),位于MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd,Int32 msg,IntPtr lParam,IntPtr InParent,IntPtr lParam,IntPtr lParam,

我试图更改代码以使用应用程序调度程序并存储任务调度程序,并将其与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);

有任何想法吗?

为什么不在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()));
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM