[英]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
线9621.Invoke(Object sender, TEventArgs e) at MahApps.Metro.Controls.SafeRaise.Raise[T](EventHandler
1 eventToRaise,Object1.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.