我正在尝试为长时间运行的操作显示请等待对话框。问题是因为这是单线程,即使我告诉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的网格,我隐藏并显示。
更新:我真的不想使用后台工作者,除非我必须这样做。代码中有许多地方会出现这种启动和停止。
答案 0 :(得分:17)
单线程做它真的会很痛苦,它永远不会像你想的那样工作。该窗口最终将在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。
这实际上是最简单的方法,而不是单独尝试单线程。
答案 1 :(得分:16)
我找到了办法!感谢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);
}
需要在长时间运行之前调用该函数。那将强制UI线程更新。
答案 2 :(得分:4)
查看我对这个非常微妙主题的全面研究。如果您无法改善实际性能,则可以使用以下选项显示等待消息:
选项#1 执行代码以与执行实际任务相同的方法同步显示等待消息。只需将这一行放在一个漫长的过程之前:
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => { /* Your code to display a waiting message */ }));
它将在 Invoke()结束时处理主调度程序线程上的待处理消息。
注意:选择Application.Current.Dispatcher的原因,但解释了Dispatcher.CurrentDispatcher here。
选项#2 显示“等待”屏幕并更新UI(处理待处理消息)。
要做 WinForms 开发人员执行Application.DoEvents方法。 WPF提供了两种替代方案来实现类似的结果:
选项#2.1 使用DispatcherFrame class。
从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 调用空操作
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, (Action)(() => { }));
参见讨论哪一个(2.1或2.2)更好here。恕我直言选项#1仍然优于#2。
选项#3 在单独的窗口中显示等待消息。
当您显示的不是简单的等待消息,而是动画时,它会派上用场。在我们等待另一个长渲染操作完成的同时渲染加载动画是一个问题。基本上,我们需要两个渲染线程。您不能在一个窗口中拥有多个渲染线程,但是您可以使用自己的渲染线程将加载动画放在一个新窗口中,并使其看起来不像是一个单独的窗口。
从this github下载 WpfLoadingOverlay.zip (这是文章“ WPF响应:呈现期间的异步加载动画”的示例,但我不能在网上找到它)或者看看下面的主要想法:
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();
}
}
答案 3 :(得分:3)
另一种选择是将长时间运行的例程编写为返回IEnumerable<double>
以指示进度的函数,然后说:
yield return 30;
例如,这将表明30%的通过时间。然后,您可以使用WPF计时器在“背景”中执行它作为合作协程。