在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计时器在“背景”中执行它作为协作协程。
这里有一些细节描述,带有示例代码。