2009-09-01 13 views
60

En WinForms, ¿cómo fuerzo una actualización de UI inmediata del hilo de UI?Forzar la actualización de GUI desde UI Thread

Lo que estoy haciendo es más o menos:

label.Text = "Please Wait..." 
try 
{ 
    SomewhatLongRunningOperation(); 
} 
catch(Exception e) 
{ 
    label.Text = "Error: " + e.Message; 
    return; 
} 
label.Text = "Success!"; 

texto de la etiqueta no quede ajustado en "Esperar ..." antes de la operación.

Lo resolví usando otro hilo para la operación, pero se pone peludo y me gustaría simplificar el código.

+3

Ejecutar 'SomewhatLongRunningOperation()' en otro hilo es la respuesta correcta aquí. No debe atar el hilo de la interfaz de usuario para nada que no afecte directamente a la IU. En cuanto a la simplificación del código, es muy posible que puedas simplificar el uso de ese otro hilo. – cHao

Respuesta

80

Al principio me preguntaba por qué el OP no había marcado ya una de las respuestas como la respuesta, pero después de intentarlo y todavía no funciona, profundicé un poco y descubrí que hay mucho más sobre este tema que Primero lo había supuesto.

Una mejor comprensión se puede ganar mediante la lectura de una pregunta similar: Why won't control update/refresh mid-process

Por último, para que conste, yo era capaz de obtener mi sello para actualizar haciendo lo siguiente:

private void SetStatus(string status) 
{ 
    lblStatus.Text = status; 
    lblStatus.Invalidate(); 
    lblStatus.Update(); 
    lblStatus.Refresh(); 
    Application.DoEvents(); 
} 

Aunque por lo que entiendo, esto está lejos de ser un enfoque elegante y correcto para hacerlo. Es un truco que puede funcionar o no según lo ocupado que esté el hilo.

+0

Gracias, realmente funciona. Reestructuré el código para evitar esto, pero tu solución (aunque sea chistosa) es más elegante. – dbkk

+0

¡Iba a NUTS con eso! Pfiew !!! Pensé que una llamada invalidaría ...Nunca habría pensado empacar todas estas llamadas solo para obtener una etiqueta actualizada. Haky pero funciona –

+23

'Refresh()' es equivalente a 'Invalidate()' seguido de 'Update()', por lo que en realidad lo está haciendo dos veces allí. –

11

Llame al Application.DoEvents() después de configurar la etiqueta, pero debe hacer todo el trabajo en una secuencia separada en su lugar, para que el usuario pueda cerrar la ventana.

+2

+1, pero es para aplicaciones simples, es completamente válido para mantener las cosas simples al no utilizar el hilo de trabajo en segundo plano. Simplemente ponga el cursor en una llamada de reloj de arena Application.DoEvents. – Ash

+3

No, no lo es, ya que SomeWhatLongRunningOperation está bloqueando toda la aplicación. ¿Te gustan los programas que no responden? ¡Yo no! – Scoregraphic

+1

No, simplemente no me gustan las aplicaciones simples que complican demasiado y que simplemente no requieren la sobrecarga de varios hilos. Windows es un sistema operativo multitarea, simplemente inicie la tarea y realice otro trabajo en otra aplicación. – Ash

12

Llamar label.Invalidate y luego label.Update() - por lo general, la actualización solo se produce después de salir de la función actual, pero llamar a Update lo obliga a actualizar en ese lugar específico del código. De MSDN:

El método de invalidación gobierna lo que se pintado o repintado. El método de actualización rige cuando ocurre la pintura o el repintado. Si usa los métodos Invalidar y Actualizar juntos en lugar de llamar a Refrescar, lo que se vuelve a pintar depende de la sobrecarga de Invalidación que use. El método Update simplemente obliga a pintar el control de inmediato, pero el método Invalidate rige lo que se pinta cuando se llama al método Update.

+2

Y entonces, como puede ver, 'label.Invalidate()' (sin argumentos) seguido de 'label.Update()' es equivalente a 'label.Refresh()'. –

1

Es muy tentador querer "arreglarlo" y forzar una actualización de UI, pero la mejor solución es hacer esto en un hilo de fondo y no atar el hilo de UI, para que pueda responder a eventos .

+0

Overkill, para una aplicación simple, es completamente válido forzar una actualización de UI. Los botones minimizar/maximizar aún funcionan, solo funcionan en otra cosa. ¿Por qué introducir problemas de comunicación entre hilos? – Ash

+0

No lo llamaría excesivo para una aplicación simple, solo exagerado si la operación es muy corta. Sin embargo, lo llamó "SomewhatLongRunningOperation". Y dado que las personas generalmente solo necesitan hacer esto si la UI estará atada durante un período de tiempo algo largo, ¿por qué bloquear la IU? ¡Para eso es la clase BackgroundWorker! No puede ser mucho más fácil que eso. –

3

puede probar esta

using System.Windows.Forms; // u need this to include. 

MethodInvoker updateIt = delegate 
       { 
        this.label1.Text = "Started..."; 
       }; 
this.label1.BeginInvoke(updateIt); 

ver si funciona.

2

Después de actualizar la interfaz de usuario, iniciar una tarea a realizar con la operación de larga ejecución:

label.Text = "Please Wait..."; 

Task<string> task = Task<string>.Factory.StartNew(() => 
{ 
    try 
    { 
     SomewhatLongRunningOperation(); 
     return "Success!"; 
    } 
    catch (Exception e) 
    { 
     return "Error: " + e.Message; 
    } 
}); 
Task UITask = task.ContinueWith((ret) => 
{ 
    label.Text = ret.Result; 
}, TaskScheduler.FromCurrentSynchronizationContext()); 

Esto funciona en .NET 3.5 y posterior.

+0

Esto funcionó para mí, gracias @pixelgrease – Rob

4

Acabo de tropezar con el mismo problema y encontré información interesante y quería poner mi granito de arena y agregarlo aquí.

En primer lugar, como otros ya han mencionado, las operaciones de larga duración deben hacerse mediante un hilo, que puede ser un trabajador de fondo, un hilo explícito, un hilo del threadpool o (desde .Net 4.0) una tarea : Stackoverflow 570537: update-label-while-processing-in-windows-forms, para que la interfaz de usuario se mantenga receptiva.

Pero para tareas cortas no hay una necesidad real de enhebrar, aunque no duele, por supuesto.

He creado un WinForm con un botón y una etiqueta para analizar este problema:

System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) 
{ 
    label1->Text = "Start 1"; 
    label1->Update(); 
    System::Threading::Thread::Sleep(5000); // do other work 
} 

Mi análisis fue pasando por encima del código (utilizando F10) y ver lo que pasó. Y después de leer este artículo Multithreading in WinForms he encontrado algo interesante. El artículo dice en la parte inferior de la primera página, que el subproceso de la interfaz de usuario no puede volver a pintar la interfaz de usuario hasta que la función ejecutada actualmente finalice y la ventana esté marcada por Windows como "no responde" en su lugar después de un tiempo. También he notado eso en mi solicitud de prueba desde arriba mientras lo paso, pero solo en ciertos casos.

(Para la siguiente prueba es importante no tener Visual Studio configurado en pantalla completa, debe poder ver la ventana de su pequeña aplicación al mismo tiempo al lado, No debe tener que cambiar entre la ventana de Visual Studio para la depuración y la ventana de aplicación para ver qué pasa. iniciar la aplicación, establecer un punto de interrupción en label1->Text ..., poner la ventana de la aplicación junto a la ventana VS y coloque el cursor del ratón sobre la ventana de VS.)

  1. Cuando hago clic una vez en VS después del inicio de la aplicación (para poner los focos allí y permitir el paso) y páselo SIN mover el mouse, se establece el nuevo texto y se actualiza la etiqueta en la función de actualización(). Esto significa que la IU está repintada obviamente.

  2. Cuando paso sobre la primera línea, muevo el mouse alrededor y hago clic en algún lugar, luego paso más, es probable que el nuevo texto esté activado y se llame a la función update(), pero la UI no se actualiza/repintado y el texto anterior permanece allí hasta que finaliza la función button1_click(). ¡En lugar de repintar, la ventana está marcada como "no receptiva"! Tampoco ayuda agregar this->Update(); para actualizar el formulario completo.

  3. Agregar Application::DoEvents(); le da a la interfaz de usuario la oportunidad de actualizar/volver a pintar. De todos modos, debes tener cuidado de que el usuario no pueda presionar botones ni realizar otras operaciones en la interfaz de usuario que no están permitidas. Por lo tanto: Try to avoid DoEvents()!, mejor uso de subprocesos (que creo que es bastante simple en .Net).
    Pero (@Jagd, 2 de abril de 2010 a las 19:25) puede omitir .refresh() y .invalidate().

Mis explicaciones son las siguientes: AFAIK winform todavía utiliza la función WINAPI. También MSDN article about System.Windows.Forms Control.Update method se refiere a la función WMAPI WM_PAINT. El MSDN article about WM_PAINT indica en su primera oración que el comando WM_PAINT solo es enviado por el sistema cuando la cola de mensajes está vacía. Pero como la cola de mensajes ya está llena en el segundo caso, no se envía y, por lo tanto, la etiqueta y el formulario de solicitud no se vuelven a pintar.

<> broma> Conclusión: por lo que sólo tiene que mantener al usuario utilizar el ratón ;-) <>/broma>

0

que tenía el mismo problema con la propiedad Enabled y descubrí un first chance exception elevó debido es no seguro para subprocesos. Encontré la solución sobre "Cómo actualizar la GUI de otro hilo en C#?" aquí https://stackoverflow.com/a/661706/1529139 ¡Y funciona!

1

Creo que tengo la respuesta, destilada de lo anterior y un poco de experimentación.

progressBar.Value = progressBar.Maximum - 1; 
progressBar.Maximum = progressBar.Value; 

he intentado disminuir el valor y la pantalla actualizada, incluso en modo de depuración, pero eso no quiere trabajar para establecer progressBar.Value a progressBar.Maximum, porque no se puede establecer el valor de la barra de progreso por encima del máximo, por lo que primero defina el progressBar.Value a progressBar.Maximum - 1, luego configure progressBar.Maxiumum en igual a progressBar.Valu e. Dicen que hay más de una forma de matar a un gato. A veces me gustaría matar a Bill Gates o quien sea que sea ahora: o).

Con este resultado, ni siquiera parecen necesitar Invalidate(), Refresh(), Update(), o hacer cualquier cosa a la barra de progreso o su recipiente panel o la forma de los padres.

Cuestiones relacionadas