【问题标题】:Display "Wait" screen in WPF在 WPF 中显示“等待”屏幕
【发布时间】:2009-03-05 21:08:38
【问题描述】:

我正在尝试为长时间运行的操作显示一个请稍候对话框。问题是因为这是单线程的,即使我告诉 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 的网格,我将其隐藏和显示。

更新:我真的不想使用后台工作人员,除非我必须这样做。代码中有很多地方会发生这种开始和停止。

【问题讨论】:

  • 我在尝试单线程时也是如此。思考,让我们改变光标。但它从来没有真正奏效过。

标签: c# .net wpf multithreading xaml


【解决方案1】:

做单线程真的会很痛苦,而且永远不会像你想要的那样工作。窗口最终会在 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。

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

【讨论】:

  • 感谢您的回答。我找到了一种在 UI 线程上执行此操作的方法(请参阅答案)。在一个完美的世界中,BackgroundWorker 绝对是要走的路。我只是现在没有更改那么多代码的奢侈。
【解决方案2】:

我找到了方法!感谢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 线程更新。

【讨论】:

    【解决方案3】:

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

    选项#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();
        }
    }
    

    【讨论】:

      【解决方案4】:

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

      yield return 30;
      

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

      It's described in some detail here, with sample code.

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-01-26
        • 1970-01-01
        • 2019-11-02
        • 1970-01-01
        • 2011-01-08
        • 1970-01-01
        相关资源
        最近更新 更多