C# 在 WPF 中显示“等待”屏幕
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/616629/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Display "Wait" screen in WPF
提问by Shaun Bowe
I am trying to display a please wait dialog for a long running operation. The problem is since this is single threaded even though I tell the WaitScreen to display it never does. Is there a way I can change the visibility of that screen and make it display immediately? I included the Cursor call as an example. Right after I call this.Cursor, the cursor is updated immediately. This is exactly the behavior I want.
我正在尝试为长时间运行的操作显示请稍候对话框。问题是因为这是单线程的,即使我告诉 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 is just a Grid with a Z-index of 99 that I hide and show.
WaitScreen 只是一个我隐藏和显示的 Z-index 为 99 的网格。
update: I really don't want to use a background worker unless I have to. There are a number of places in the code where this start and stop will occur.
更新:除非必须,我真的不想使用后台工作人员。代码中有很多地方会发生这种开始和停止。
采纳答案by Shaun Bowe
I found a way! Thanks to this thread.
我找到了一个方法!感谢这个线程。
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);
}
That function needs to be called right before the long running operation. That will then Force the UI thread to update.
该函数需要在长时间运行的操作之前调用。这将强制 UI 线程更新。
回答by Ray
Doing it single threaded really is going to be a pain, and it'll never work as you'd like. The window will eventually go black in WPF, and the program will change to "Not Responding".
做单线程真的会很痛苦,而且它永远不会像你想要的那样工作。窗口最终会在 WPF 中变黑,并且程序将更改为“无响应”。
I would recommending using a BackgroundWorker to do your long running task.
我建议使用 BackgroundWorker 来完成您的长时间运行任务。
It's not that complicated. Something like this would work.
没那么复杂。像这样的事情会起作用。
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();
}
You can then look at the ProgressChanged event to display a progress if you like (remember to set WorkerReportsProgress to true). You can also pass a parameter to RunWorkerAsync if your DoWork methods needs an object (available in e.Argument).
然后,如果您愿意,您可以查看 ProgressChanged 事件以显示进度(记住将 WorkerReportsProgress 设置为 true)。如果您的 DoWork 方法需要一个对象(在 e.Argument 中可用),您还可以将参数传递给 RunWorkerAsync。
This really is the simplest way, rather than trying to do it singled threaded.
这确实是最简单的方法,而不是尝试单线程化。
回答by Daniel Earwicker
Another option is to write your long-running routine as a function that returns IEnumerable<double>
to indicate progress, and just say:
另一种选择是将长时间运行的例程编写为返回IEnumerable<double>
以指示进度的函数,只需说:
yield return 30;
That would indicate 30% of the way through, for example. You can then use a WPF timer to execute it in the "background" as a co-operative coroutine.
例如,这表示已经完成了 30%。然后,您可以使用 WPF 计时器在“后台”作为协作协程执行它。
回答by Alex Klaus
Check out my comprehensive research of this very delicate topic. If there's nothing you can do to improve the actual performance, you have the following options to display a waiting message:
查看我对这个非常微妙的主题的综合研究。如果您无法提高实际性能,您可以选择以下选项来显示等待消息:
Option #1Execute a code to display a waiting message synchronously in the same method which does the real task. Just put this line before a lengthy process:
选项 #1执行代码以在执行实际任务的相同方法中同步显示等待消息。只需将这一行放在一个漫长的过程之前:
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => { /* Your code to display a waiting message */ }));
It'll process pending messages on the main dispatcher thread at the end of the Invoke().
它将在Invoke()结束时处理主调度程序线程上的挂起消息。
Note:Reason for selecting Application.Current.Dispatcher but Dispatcher.CurrentDispatcher is explained here.
注意:选择 Application.Current.Dispatcher 但 Dispatcher.CurrentDispatcher 的原因在这里解释。
Option #2Display a “Wait” screen and update UI (process pending messages).
选项 #2显示“等待”屏幕并更新 UI(处理待处理消息)。
To do it WinFormsdevelopers executed Application.DoEventsmethod. WPF offers two alternatives to achieve similar results:
为此,WinForms开发人员执行了Application.DoEvents方法。WPF 提供了两种替代方法来实现类似的结果:
Option #2.1With using DispatcherFrame class.
选项 #2.1使用DispatcherFrame 类。
Check a bit bulky example from MSDN:
从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;
}
Option #2.2Invoke an empty Action
选项 #2.2调用空操作
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, (Action)(() => { }));
See discussions which one (2.1 or 2.2) is better here. IMHO option #1 is still better than #2.
请参阅此处讨论哪一个(2.1 或 2.2)更好。恕我直言,选项 #1 仍然比 #2 好。
Option #3Display a waiting message in a separate window.
选项 #3在单独的窗口中显示等待消息。
It comes in handy when you display not a simple waiting message, but an animation. Rendering a loading animation at the same time that we are waiting for another long rendering operation to complete is a problem. Basically, we need two rendering threads. You can't have multiple rendering threads in a single window, but you can put your loading animation in a new window with its own rendering thread and make it look like it's not a separate window.
当您显示的不是简单的等待消息而是动画时,它会派上用场。在等待另一个长时间渲染操作完成的同时渲染加载动画是一个问题。基本上,我们需要两个渲染线程。您不能在单个窗口中拥有多个渲染线程,但是您可以将加载动画放置在具有自己的渲染线程的新窗口中,并使其看起来不是一个单独的窗口。
Download WpfLoadingOverlay.zipfrom this github(it was a sample from article "WPF Responsiveness: Asynchronous Loading Animations During Rendering", but I can't find it on the Web anymore) or have a look at the main idea below:
从这个 github下载WpfLoadingOverlay.zip(它是文章“ WPF Responsiveness: Asynchronous Loading Animations during Rendering”的示例,但我在网上找不到了)或者看看下面的主要思想:
public partial class LoadingOverlayWindow : Window
{
/// <summary>
/// Launches a loading window in its own UI thread and positions it over <c>overlayedElement</c>.
/// </summary>
/// <param name="overlayedElement"> An element for overlaying by the waiting form/message </param>
/// <returns> A reference to the created window </returns>
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();
}
}