2009-03-05 177 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(); 
    } 
}