在WPF中显示“等待”屏幕

我正在尝试为长时间运行的操作显示请等待对话框。 问题是因为这是单线程,即使我告诉WaitScreen显示它永远不会。 有没有办法可以改变屏幕的可见性并立即显示? 我将Cursor调用作为示例包含在内。 在我调用this.Cursor之后,光标立即更新。 这正是我想要的行为。

private void Button_Click(object sender, RoutedEventArgs e) { this.Cursor = System.Windows.Input.Cursors.Pen; WaitScreen.Visibility = Visibility.Visible; // Do something long here for (Int32 i = 0; i < 100000000; i++) { String s = i.ToString(); } WaitScreen.Visibility = Visibility.Collapsed; this.Cursor = System.Windows.Input.Cursors.Arrow; } 

WaitScreen只是一个Z-index为99的网格,我隐藏并显示。

更新:我真的不想使用后台工作者,除非我必须这样做。 代码中有许多地方会出现这种启动和停止。

做单线程确实会很痛苦,而且它永远不会像你想的那样工作。 窗口最终会在WPF中变黑,程序将变为“无响应”。

我建议使用BackgroundWorker来完成长时间运行的任务。

这并不复杂。 像这样的东西会起作用。

 private void DoWork(object sender, DoWorkEventArgs e) { //Do the long running process } private void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { //Hide your wait dialog } private void StartWork() { //Show your wait dialog BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += DoWork; worker.RunWorkerCompleted += WorkerCompleted; worker.RunWorkerAsync(); } 

然后,您可以查看ProgressChanged事件以显示进度(如果您愿意,请记住将WorkerReportsProgress设置为true)。 如果您的DoWork方法需要一个对象(在e.Argument中可用),您还可以将参数传递给RunWorkerAsync。

这实际上是最简单的方法,而不是尝试单挑线程。

我找到了一个方法! 感谢这个post 。

 public static void ForceUIToUpdate() { DispatcherFrame frame = new DispatcherFrame(); Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Render, new DispatcherOperationCallback(delegate(object parameter) { frame.Continue = false; return null; }), null); Dispatcher.PushFrame(frame); } 

该函数需要在长时间运行之前调用。 然后,这将强制UI线程更新。

看看我对这个非常微妙的话题的全面研究。 如果您无法提高实际性能,则可以使用以下选项显示等待消息:

选项#1执行代码以与执行实际任务相同的方法同步显示等待消息。 只需将这一行放在一个漫长的过程之前:

 Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => { /* Your code to display a waiting message */ })); 

它将在Invoke()结束时处理主调度程序线程上的挂起消息。

注意: 此处说明选择Application.Current.Dispatcher但Dispatcher.CurrentDispatcher的原因。

选项#2显示“等待”屏幕并更新UI(处理待处理消息)。

为此, WinForms开发人员执行了Application.DoEvents方法。 WPF提供两种替代方案来实现类似的结果:

选项#2.1使用DispatcherFrame类 。

从MSDN中检查一下有点笨重的例子:

 [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] public void DoEvents() { DispatcherFrame frame = new DispatcherFrame(); Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame); Dispatcher.PushFrame(frame); } public object ExitFrame(object f) { ((DispatcherFrame)f).Continue = false; return null; } 

选项#2.2调用空Action

 Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, (Action)(() => { })); 

请参阅讨论哪一个(2.1或2.2) 在这里更好。 恕我直言选项#1仍然优于#2。

选项#3在单独的窗口中显示等待消息。

当您显示的不是简单的等待消息,而是动画时,它会派上用场。 在我们等待另一个长渲染操作完成的同时渲染加载动画是一个问题。 基本上,我们需要两个渲染线程。 您不能在一个窗口中拥有多个渲染线程,但是您可以将加载动画放在具有自己的渲染线程的新窗口中,并使其看起来不像是一个单独的窗口。

从这个github下载WpfLoadingOverlay.zip (这是文章“ WPF响应:渲染过程中的异步加载动画 ”中的一个示例,但我不能在Web上找到它)或者看看下面的主要想法:

 public partial class LoadingOverlayWindow : Window { ///  /// Launches a loading window in its own UI thread and positions it over overlayedElement. ///  ///  An element for overlaying by the waiting form/message  ///  A reference to the created window  public static LoadingOverlayWindow CreateAsync(FrameworkElement overlayedElement) { // Get the coordinates where the loading overlay should be shown var locationFromScreen = overlayedElement.PointToScreen(new Point(0, 0)); // Launch window in its own thread with a specific size and position var windowThread = new Thread(() => { var window = new LoadingOverlayWindow { Left = locationFromScreen.X, Top = locationFromScreen.Y, Width = overlayedElement.ActualWidth, Height = overlayedElement.ActualHeight }; window.Show(); window.Closed += window.OnWindowClosed; Dispatcher.Run(); }); windowThread.SetApartmentState(ApartmentState.STA); windowThread.Start(); // Wait until the new thread has created the window while (windowLauncher.Window == null) {} // The window has been created, so return a reference to it return windowLauncher.Window; } public LoadingOverlayWindow() { InitializeComponent(); } private void OnWindowClosed(object sender, EventArgs args) { Dispatcher.InvokeShutdown(); } } 

另一种选择是将长时间运行的例程编写为返回IEnumerable以指示进度的函数,然后说:

 yield return 30; 

例如,这将表明30%的通过率。 然后,您可以使用WPF计时器在“背景”中执行它作为协作协程。

这里有一些细节描述,带有示例代码。