我使用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);
有什么想法吗?
答案 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()));
}