Forzar actualización de la GUI desde UI Thread

En WinForms, ¿cómo forzar una actualización de UI inmediata desde el hilo de UI?

Lo que estoy haciendo es aproximadamente:

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

El texto de la etiqueta no se establece en “Espere …” antes de la operación.

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

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

Se puede obtener una mejor comprensión leyendo una pregunta similar: ¿Por qué no controlará la actualización / actualización a mitad de proceso?

Por último, para el registro, pude actualizar mi etiqueta 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.

Llame a 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.

label.Invalidate llamada. 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 la Actualización lo obliga a actualizar en ese lugar específico del código. Desde MSDN :

El método Invalidate rige lo que se pinta o se repinta. 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.

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 ya se ha mencionado, las operaciones de larga ejecución deben realizarse mediante un hilo, que puede ser un trabajador de fondo, un hilo explícito, un hilo del grupo de hilos o (desde .Net 4.0) una tarea: Stackoverflow 570537: update-label-while-processing-in-windows-forms , para que la UI se mantenga receptiva.

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

Creé 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 estaba pasando por alto el código (usando F10) y viendo lo que sucedió. Y después de leer este artículo Multithreading en 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 junto a ella, no debe tener que cambiar entre la ventana de Visual Studio para la depuración y su ventana de la aplicación para ver qué sucede. Inicie la aplicación, establezca un punto de interrupción en label1->Text ... , coloque la ventana de la aplicación junto a la ventana VS y coloque el cursor del mouse sobre la ventana 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 lo paso SIN mover el mouse, se establece el nuevo texto y la etiqueta se actualiza 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é configurado y se llame a la función update (), pero la UI no se actualiza / repinta 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 todo el formulario

  3. Agregar Application::DoEvents(); le da a la UI 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: ¡ trate de evitar DoEvents ()! , mejor uso de threading (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 usa la función WINAPI. También el artículo de MSDN sobre el método System.Windows.Forms Control.Update se refiere a la función WMAPI WM_PAINT. El artículo de MSDN sobre 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.

<> chiste> Conclusión: así que solo tienes que evitar que el usuario use el mouse 😉 <> / joke>

puedes intentar esto

 using System.Windows.Forms; // u need this to include. MethodInvoker updateIt = delegate { this.label1.Text = "Started..."; }; this.label1.BeginInvoke(updateIt); 

Mira si funciona

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

 label.Text = "Please Wait..."; Task task = Task.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.

Es muy tentador querer “arreglar” esto 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 los eventos.

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

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

Traté de disminuir el valor y la pantalla se actualizó incluso en modo de depuración, pero eso no funcionaría para establecer progressBar.Value en progressBar.Maximum , porque no se puede establecer el valor de la barra de progreso por encima del máximo, así que primero configuré el progressBar.Value para progressBar.Maximum - 1, luego establezca progressBar.Maxiumum en igual 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 parece necesitar Invalidate() , Refresh() , Update() ni hacer nada en la barra de progreso o en su contenedor del Panel o en el Formulario principal.

Tuve el mismo problema con la propiedad Enabled y descubrí una first chance exception planteada porque no es segura 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!