在WPF中显示“等待”屏幕

xtfmy6hx  于 2023-01-02  发布在  其他
关注(0)|答案(5)|浏览(185)

我试图为一个长时间运行的操作显示一个请等待对话框。问题是因为这是单线程的,即使我告诉等待屏幕显示它也不会显示。有没有办法可以改变屏幕的可见性并使它立即显示?我包括了Cursor调用作为一个例子。在我调用这个。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索引为99的网格,我可以隐藏和显示它。
我真的不想使用一个后台工作者,除非我不得不这样做。在代码中有很多地方会发生这种启动和停止。

7cwmlq89

7cwmlq891#

在单线程环境下运行真的会很痛苦,而且它永远不会像你希望的那样工作。在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传递参数。
这确实是最简单的方法,而不是尝试单线程化。

xghobddn

xghobddn2#

我找到办法了!多亏了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线程更新。

laximzn5

laximzn53#

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

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

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

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

  • 注:* 选择应用程序.当前.调度程序而不是调度程序.当前调度程序的原因将在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();
    }
}
bis0qfac

bis0qfac4#

另一种选择是将长时间运行的例程编写为一个函数,该函数返回IEnumerable<double>以指示进度,并且只需说:

yield return 30;

例如,这将指示30%的进程,然后您可以使用WPF计时器在“后台”作为协作协程来执行它。
It's described in some detail here, with sample code.

njthzxwz

njthzxwz5#

简短回答

简化前面的答案,您可以只创建一个类似这样的任务,只需对代码进行非常小的更改。

private void Button_Click(object sender, RoutedEventArgs e)
{
  this.Cursor = System.Windows.Input.Cursors.Pen;
  WaitScreen.Visibility = Visibility.Visible;

  Task.Factory.StartnNew(()=>{
      // Do something long here
      for (Int32 i = 0; i < 100000000; i++)
      {
        String s = i.ToString();
      }
  }).ContinueWith(()=>{
      WaitScreen.Visibility = Visibility.Collapsed;
      this.Cursor = System.Windows.Input.Cursors.Arrow; 
  }, TaskScheduler.FromCurrentSynchronizationContext());
}

可扩展的答案

如果你想让它更具伸缩性,你可以创建一个RunLongTask(Action action)方法:

private void RunLongTask(Action action)
{
  IsBusy = true;

  Task.Run(action).ContinueWith(()=>{//similar to Task.Factory.StartNew...
      IsBusy = false;
  }, TaskScheduler.FromCurrentSynchronizationContext());
}

在这里你可以把IsBusy绑定到你的窗口控件属性上,比如IsEnabled或者Visibility属性(为了可见性,你需要一个转换器,我会在答案的最后添加)

<Grid Name="OverlayGrid" Visibility={Binding IsBusy, Converter={local:InverseBoolVisibilityCollapseConverter}}">...</Grid>
<!-- where local is defined at the Window element, referring to the namespace where you created the converter code -->

然后,您可以这样使用它:

RunLongTask(SomeParameterlessMethodName);

RunLongTask(()=>{
    //long
    //long
    //long
});

使用的转换器

[ValueConversion(typeof(bool), typeof(Visibility))]
class BoolVisibilityCollapseConverter : MarkupExtension, IValueConverter
{
    //convert from source to target
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        bool v = (bool)value;
        if (v)
            return Visibility.Visible;
        else
            return Visibility.Collapsed;
    }

    //convert from target to source
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new InvalidOperationException("BoolVisibilityHideConverter is intended to be bound one way from source to target");
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }
}

[ValueConversion(typeof(bool), typeof(Visibility))]
class InverseBoolVisibilityCollapseConverter : MarkupExtension, IValueConverter
{
    //convert from source to target
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        bool v = !(bool)value;
        if (v)
            return Visibility.Visible;
        else
            return Visibility.Collapsed;
    }

    //convert from target to source
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new InvalidOperationException("InverseBoolVisibilityCollapseConverter is intended to be bound one way from source to target");
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }
}

相关问题