支持加载进度的初始屏幕



我有以下引导程序类,使用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; }
}
}

这很好,运行良好,加载我导出的模块等。现在我想实现一个显示进度的初始屏幕(进度条和有关加载的导出等的信息),所以我不想要标准 WPFSplashScreen它只是一个静态图像。

现在我已经看到了带有外壳屏幕导体的自定义caliburn.micro启动画面,但对我来说,这不是awswer,只有在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

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

堆栈跟踪是

at System.Windows.Threading.Dispatcher.VerifyAccess() at System.Windows.DependencyObject.GetValue(DependencyProperty dp) at MahApps.Metro.Controls.MetroWindow.get_Flyouts() in d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\Controls\MetroWindow.cs:line 269 at MahApps.Metro.Controls.MetroWindow.ThemeManagerOnIsThemeChanged(Object sender, OnThemeChangedEventArgs e) in d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\Controls\MetroWindow.cs:line 962 at System.EventHandler1.Invoke(Object sender, TEventArgs e) at MahApps.Metro.Controls.SafeRaise.Raise[T](EventHandler1 eventToRaise, Object sender, T args) in d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\Controls\SafeRaise.cs:line 26 at MahApps.Metro.ThemeManager.OnThemeChanged(Accent newAccent, AppTheme newTheme) in d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\ThemeManager\ThemeManager.cs:line 591 at MahApps.Metro.ThemeManager.ChangeAppStyle(ResourceDictionary resources, Tuple'2 oldThemeInfo, Accent newAccent, AppTheme newTheme) in d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\ThemeManager\ThemeManager.cs:line 407 at MahApps.Metro.ThemeManager.ChangeAppStyle(Application app, Accent newAccent, AppTheme newTheme) in d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\ThemeManager\ThemeManager.cs:line 345 at Augur.Core.Themes.ThemeManager.SetCurrentTheme(String name) in F:\Camus\Augur\Src\Augur\Core\Themes\ThemeManager.cs:line 46 at Augur.Modules.Shell.ViewModels.ShellViewModel.OnViewLoaded(Object view) in F:\Camus\Augur\Src\Augur\Modules\Shell\ViewModels\ShellViewModel.cs:line 73 at Caliburn.Micro.XamlPlatformProvider.<>c__DisplayClass11_0.b__0(Object s, RoutedEventArgs e) at Caliburn.Micro.View.<>c__DisplayClass8_0.b__0(Object s, RoutedEventArgs e) at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised) at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args) at System.Windows.BroadcastEventHelper.BroadcastEvent(DependencyObject root, RoutedEvent routedEvent) at System.Windows.BroadcastEventHelper.BroadcastLoadedEvent(Object root) 在MS。Internal.LoadedOrUnloadedOperation.DoWork() at System.Windows.Media.MediaContext.FireLoadedPendingCallbacks() at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks() at System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object ressizeCompositionTarget) at System.Windows.Media.MediaContext.RenderMessageHandler(Object ressizeCompositionTarget) at System.Windows.Interop.HwndTarget.OnResize() at System.Windows.Interop.HwndTarget.HandleMessage(WindowMessage msg, IntPtr wparam, IntPtr lparam) at System.Windows.Interop.HwndSource.HwndTargetFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)在MS。Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) 在MS。Win32.HwndSubclass.DispatcherCallbackOperation(Object o) at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs) at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler) at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs) 在MS。Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, 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()));
}

最新更新