2009-03-05 31 views
7

Estoy tratando de mostrar un diálogo de espera por favor para una operación de larga ejecución. El problema es que esto tiene un solo hilo aunque le digo a WaitScreen que nunca lo muestre. ¿Hay alguna manera de que pueda cambiar la visibilidad de esa pantalla y hacer que se muestre de inmediato? Incluí la llamada del Cursor como un ejemplo. Justo después de llamar a esto. Cursor, el cursor se actualiza inmediatamente. Este es exactamente el comportamiento que quiero.Mostrar la pantalla "Esperar" en 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 es solo una Cuadrícula con un índice Z de 99 que oculto y muestro.

actualización: Realmente no quiero utilizar un trabajador de fondo a menos que sea necesario. Hay una serie de lugares en el código donde se producirá este inicio y finalización.

+0

Yo era el mismo cuando estaba tratando de hacerlo de rosca simple. Pensando, vamos a cambiar el cursor. Pero nunca funcionó realmente. – Ray

Respuesta

15

¡Encontré una manera! Gracias a 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); 
} 

Esa función debe llamarse justo antes de la operación de larga ejecución. Eso forzará la actualización del hilo de la interfaz de usuario.

17

Hacerlo con una sola hebra realmente va a ser un problema, y ​​nunca funcionará como te gustaría. La ventana eventualmente se pondrá negra en WPF, y el programa cambiará a "No responde".

Recomendaría utilizar un BackgroundWorker para realizar su tarea de larga ejecución.

No es tan complicado. Algo como esto funcionaría.

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

A continuación, puede mirar en el caso ProgressChanged para mostrar un progreso si te gusta (no olvide colocar WorkerReportsProgress en true). También puede pasar un parámetro a RunWorkerAsync si sus métodos DoWork necesitan un objeto (disponible en e.Argument).

Esto realmente es la forma más simple, en lugar de tratar de hacerlo individualmente enhebrado.

+0

Gracias por la respuesta. Encontré una manera de hacerlo en el hilo de UI (Ver respuesta). En un mundo perfecto, BackgroundWorker es definitivamente el camino a seguir. Simplemente no puedo darme el lujo de cambiar tanto código en este momento. –

3

Otra opción es escribir su rutina de larga duración como una función que devuelve IEnumerable<double> para indicar el progreso, y sólo decir:

yield return 30; 

que indicarían el 30% del camino a través, por ejemplo. A continuación, puede usar un temporizador WPF para ejecutarlo en el "fondo" como una corutina cooperativa.

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

4

Compruebe hacia fuera mi exhaustiva investigación de este tema muy delicado. Si no hay nada que puede hacer para mejorar el rendimiento real, tiene las siguientes opciones para mostrar un mensaje en espera:

Opción # 1 ejecutar un código para mostrar un mensaje de espera de forma sincrónica en el mismo método que hace la verdadera tarea . Sólo hay que poner esta línea antes de un largo proceso:

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

que va a procesar los mensajes pendientes en el hilo principal despachador al final de la Invoke().

Nota: Motivo para seleccionar Application.Current.Dispatcher but Dispatcher.CurrentDispatcher se explica here.

Opción n. ° 2 Muestre una pantalla "Esperar" y actualice la IU (proceso de mensajes pendientes).

Para hacerlo WinForms developers ejecutado Application.DoEvents method. WPF ofrece dos alternativas para lograr resultados similares:

Opción # 2.1 Con el uso de DispatcherFrame class.

Comprobar un poco voluminosos ejemplo de 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; 
} 

Opción # 2.2 invocar una acción vacía

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

Ver discusiones que uno (2.1 o 2.2) es mejor here. En mi humilde opinión, la opción n. ° 1 es mejor que la n. ° 2.

Opción # 3 Mostrar un mensaje de espera en una ventana separada.

Resulta útil cuando muestra no un simple mensaje de espera, sino una animación. Presentar una animación de carga al mismo tiempo que estamos esperando que se complete otra operación de representación larga es un problema. Básicamente, necesitamos dos hilos de renderizado. No puede tener múltiples subprocesos de representación en una sola ventana, pero puede colocar su animación de carga en una nueva ventana con su propio subproceso y hacer que parezca que no es una ventana separada.

Descargar WpfLoadingOverlay.zip de this github (que era una muestra del artículo "WPF de respuesta: asincrónicos Cargando Animaciones durante la representación", pero no lo puede encontrar en la web ya no) o echar un vistazo a la idea principal a continuación:

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