2009-03-05 40 views
7

Tôi đang cố gắng hiển thị hộp thoại chờ cho một hoạt động chạy dài. Vấn đề là vì đây là chuỗi đơn mặc dù tôi nói với WaitScreen để hiển thị nó không bao giờ. Có cách nào tôi có thể thay đổi khả năng hiển thị của màn hình đó và làm cho màn hình hiển thị ngay lập tức không? Tôi đã bao gồm cuộc gọi Cursor làm ví dụ. Ngay sau khi tôi gọi this.Cursor, con trỏ được cập nhật ngay lập tức. Đây chính là hành vi tôi muốn.Hiển thị màn hình "Chờ" trong 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 chỉ là một lưới có chỉ số Z là 99 mà tôi ẩn và hiển thị.

cập nhật: Tôi thực sự không muốn sử dụng nhân viên nền trừ khi tôi phải làm như vậy. Có một số vị trí trong mã nơi bắt đầu và dừng này sẽ xảy ra.

+0

Tôi cũng giống như khi tôi cố gắng làm đơn luồng. Suy nghĩ, cho phép chỉ cần thay đổi con trỏ. Nhưng nó không bao giờ thực sự hiệu quả. – Ray

Trả lời

15

Tôi đã tìm được cách! Cảm ơn 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); 
} 

Chức năng đó cần phải được gọi ngay trước hoạt động dài. Sau đó, sẽ buộc chuỗi giao diện người dùng cập nhật.

17

Làm cho nó đơn luồng thực sự sẽ là một nỗi đau, và nó sẽ không bao giờ hoạt động như bạn muốn. Cửa sổ cuối cùng sẽ chuyển sang màu đen trong WPF, và chương trình sẽ thay đổi thành "Not Responding".

Tôi sẽ đề xuất sử dụng một BackgroundWorker để thực hiện tác vụ dài của bạn.

Nó không phức tạp. Một cái gì đó như thế này sẽ làm việc.

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

Sau đó, bạn có thể xem sự kiện ProgressChanged để hiển thị tiến trình nếu bạn muốn (nhớ đặt WorkerReportsProgress thành true). Bạn cũng có thể chuyển một tham số tới RunWorkerAsync nếu các phương thức DoWork của bạn cần một đối tượng (có sẵn trong e.Argument).

Đây thực sự là cách đơn giản nhất, thay vì cố gắng thực hiện nó đơn luồng.

+0

Cảm ơn câu trả lời. Tôi tìm thấy một cách để làm điều đó trên thread UI (Xem câu trả lời). Trong một thế giới hoàn hảo, BackgroundWorker chắc chắn là con đường để đi. Tôi chỉ không có sự sang trọng của việc thay đổi nhiều mã vào thời điểm này. –

3

Một lựa chọn khác là viết thường xuyên lâu dài của bạn như là một hàm trả về IEnumerable<double> để chỉ ra sự tiến bộ, và chỉ nói:

yield return 30; 

Điều đó sẽ cho thấy 30% các cách thức thông qua, ví dụ. Sau đó bạn có thể sử dụng một bộ đếm thời gian WPF để thực hiện nó trong "nền" như một coroutine hợp tác.

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

4

Kiểm tra nghiên cứu toàn diện của tôi về chủ đề rất nhạy cảm này. Nếu bạn không thể làm gì để cải thiện hiệu suất thực tế, bạn có các tùy chọn sau để hiển thị thông báo chờ:

Tùy chọn # 1 Thực thi mã để hiển thị đồng thời tin nhắn chờ trong cùng một phương thức thực hiện tác vụ thực . Chỉ cần đặt dòng này trước khi một quá trình dài:

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

Nó sẽ xử lý thư đang chờ xử trên thread phối chính ở phần cuối của các Invoke().

Lưu ý: Lý do chọn Application.Current.Dispatcher nhưng Dispatcher.CurrentDispatcher được giải thích here.

Tùy chọn # 2 Hiển thị màn hình "Chờ" và cập nhật giao diện người dùng (quá trình đang chờ xử lý tin nhắn).

Để làm điều đó WinForms nhà phát triển thực hiện phương thức Application.DoEvents. WPF cung cấp hai lựa chọn thay thế để đạt được kết quả tương tự:

Tùy chọn # 2.1 Khi sử dụng DispatcherFrame class.

Kiểm tra một chút cồng kềnh ví dụ từ 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; 
} 

Lựa chọn # 2.2 Gọi một hành động trống

Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, (Action)(() => { })); 

Xem thảo luận cái nào (2.1 hoặc 2.2) là tốt hơn here. Tùy chọn IMHO # 1 vẫn tốt hơn # 2.

Tùy chọn # 3 Hiển thị thông báo chờ trong cửa sổ riêng.

Có ích khi bạn hiển thị không phải là tin nhắn chờ đơn giản, mà là một hoạt ảnh. Hiển thị hoạt ảnh tải cùng một lúc mà chúng tôi đang đợi một hoạt động hiển thị dài khác hoàn thành là một vấn đề. Về cơ bản, chúng ta cần hai chuỗi hiển thị. Bạn không thể có nhiều chuỗi hiển thị trong một cửa sổ, nhưng bạn có thể đặt hoạt ảnh tải trong cửa sổ mới với chuỗi hiển thị của chính nó và làm cho nó trông giống như nó không phải là một cửa sổ riêng biệt.

Tải WpfLoadingOverlay.zip từ this github (đó là một mẫu từ bài viết "WPF đáp ứng: không đồng bộ tải Animations Trong Rendering", nhưng tôi không thể tìm thấy nó trên web nữa) hoặc có một cái nhìn tại ý tưởng chính bên dưới:

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(); 
    } 
} 
Các vấn đề liên quan