2009-03-05 136 views
7

我正在尝试为长时间运行的操作显示一个请稍等的对话框。问题是因为这是单线程的,即使我告诉WaitScreen显示它永远不会。有没有一种方法可以改变屏幕的可见性并立即显示?我把Cursor调用作为一个例子。在我调用this.Cursor之后,光标立即更新。这正是我想要的行为。在WPF中显示“等待”屏幕

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

我是一样的,当我试图做到单线程。思考,让我们改变光标。但它从未真正奏效。 – Ray 2009-03-05 21:19:17

回答

15

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

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。

这真的是最简单的方法,而不是试图单线程去做。

+0

感谢您的回答。我找到了一种方法在UI线程上完成它(请参阅答案)。在一个完美的世界BackgroundWorker绝对是一种可行的方式。在这个时候,我只是没有那么多代码的改变。 – 2009-03-05 21:23:34

3

另一种选择是写你的长期运行程序的返回IEnumerable<double>指示进步的功能,只是说:

yield return 30; 

将表明的通过方式的30%,例如。然后,您可以使用WPF计时器在“背景”中执行它作为协同协同程序。

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

4

看看我的综合性这一非常敏感的话题的研究。如果您无法改善实际性能,您可以采取以下措施:

选项#1执行代码以同一方法同步显示等待消息,它与真正的任务相同。只要把此行一个漫长的过程之前:

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

它会在的调用()末进程的主线程调度未决的消息。

注意:选择Application.Current.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在单独的窗口中显示等待消息。

当您显示的不是一个简单的等待消息,而是一个动画时,它会派上用场。在我们等待另一个长时间渲染操作完成的同时渲染加载动画是一个问题。基本上,我们需要两个渲染线程。在单个窗口中不能有多个渲染线程,但可以将加载动画放入具有自己渲染线程的新窗口中,并使其看起来不是单独的窗口。

下载WpfLoadingOverlay.zipthis github(这是从第一个样本“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(); 
    } 
}