2009-07-17 8 views
12

Tengo aquí una aplicación heredada que tiene unos pocos bucles "que requieren mucho tiempo" que se activan como resultado de varias interacciones del usuario. El código que consume tiempo actualiza periódicamente algo en la pantalla con información de progreso (generalmente una etiqueta) y luego, aparentemente persuade que la actualización visual ocurra allí y, a continuación, el código llama a Application.ProcessMessages (argh!).¿Obtener ventana para actualizar (etc) sin llamar a Application.ProcessMessages?

Todos sabemos ahora qué tipo de problemas puede presentar esto en una aplicación GUI (ser caritativa, era un momento más inocente en aquel entonces) y estamos seguros de que, de vez en cuando, conseguimos que los usuarios logren lo imposible con el programa porque están haciendo clic en los controles mientras el programa está "ocupado".

¿Cuál es la mejor manera de actualizar periódicamente las imágenes del formulario sin asumir otros eventos/mensajes, etc.?

Mis pensamientos fueron a cualquiera;
- desactivar todos los controles antes de hacer nada que consume tiempo, y dejando las llamadas'... ProcessMessages en el lugar para 'forzar' el refresco, o
- encontrar otra manera de refrescar periódicamente un control

Puedo hacer lo primero, pero me dejó pensando: ¿hay una mejor solución?

código de ejemplo de legacy;

 
i:=0; 
while FJobToBeDone do 
begin 
    DoStepOfLoop; 
    inc(i); 
    if i mod 100 = 0 then 
    begin 
    UpdateLabelsEtc; 
    Application.ProcessMessages; 
    end; 
end; 

Ya puedo escuchar todos los desmayos, en la parte posterior. :-)

Respuesta

18

Si llama a Update() en los controles después de haber cambiado las propiedades, los obligará a volver a dibujar. Otra forma es llamar a Repaint() en lugar de Actualizar(), lo que implica una llamada a Update().

Es posible que tenga que llamar Update() en los controles de los padres o los marcos, así, pero esto podría permitir a eliminar los ProcessMessages() llamada por completo.

+0

el clavo mghie , ¡Gracias! Lo dejé caer en un par de lugares clave de muestra y le di una prueba rápida; las actualizaciones de la pantalla suceden bien, pero los controles no generan eventos 'nuevos' mientras el formulario está ocupado. Si combino esto junto con el bloqueo explícito del formulario cuando el trabajo comienza, tengo la esperanza de que esto evitará que los eventos deshonestos lleguen a su fin. ¡Muchas gracias! – robsoft

8

La solución que uso para las actualizaciones largas es haciendo los cálculos en un hilo separado. De esta manera, el hilo principal se mantiene muy receptivo. Una vez que el hilo está listo, envía un mensaje de Windows al hilo principal, lo que indica que el hilo principal puede procesar los resultados.

Esto tiene otros inconvenientes, aunque severos. En primer lugar, mientras el otro subproceso está activo, deberá desactivar algunos controles porque podrían reiniciar el subproceso nuevamente. Un segundo inconveniente es que su código necesita convertirse en hilo seguro. Esto puede ser un verdadero desafío a veces. Si está trabajando con código heredado, es muy probable que su código no sea seguro para subprocesos. Finalmente, el código de subprocesos múltiples es más difícil de depurar y debe ser realizado por desarrolladores experimentados.

Pero la gran ventaja de la multi-threading es que su aplicación se mantiene receptiva y el usuario puede continuar haciendo otras cosas hasta que el hilo termine. Básicamente, está traduciendo un método sincrónico en una función asíncrona. Y el hilo puede disparar varios mensajes que indican ciertos controles para actualizar sus propios datos, que se actualizarían de inmediato, sobre la marcha. (Y en el momento en que desea que se actualicen).)

He usado esta técnica muchas veces, porque creo que es mucho mejor. (Pero también más complejo.)

+0

Gracias, Taller Alex - Creo que avanzar es lo "correcto" para intentar hacer, particularmente donde podría haber un largo período de inactividad. Sin embargo, en este caso particular, la 'Actualización' de mghie parece ir a la deriva y reemplazar completamente la llamada a ProcessMessages. – robsoft

+0

Aunque Alex dio una buena explicación, carece de un puntero sobre cómo empezar: Delphi tiene una plantilla de unidad de subprocesos en el diálogo 'agregar nuevo', pero en esencia todo se reduce a crear una clase derivada de TThread, anulando el Ejecutar método –

+0

Stijn proporciona una buena manera de usar hilos en Delphi. Básicamente, la clase TThread envuelve la API de Windows y proporciona una clase base donde puede compartir datos. Personalmente, prefiero utilizar la API sin procesar, creando un procedimiento de subproceso independiente que llama a un método específico de mi clase de lógica de negocios nuevamente. (O un método de su forma principal/secundaria). Pero debido a la complejidad, ni siquiera me atrevería a usar esta técnica con el código heredado, a menos que lo reescriba desde cero. (El código heredado a menudo no es seguro para subprocesos.) –

1

La técnica que está buscando se llama enhebrado. Es una técnica diffucult en la programación. El código debe manejarse con cuidado, porque es más difícil de depurar. Ya sea que vaya con threading o no, definitivamente debe deshabilitar los controles que los usuarios deben nometer con (me refiero a los controles que pueden afectar el proceso en curso). Debe considerar el uso acciones para activar/desactivar los botones, etc ...

+0

Gracias por este Codervish. Debo admitir que no he usado mucho las listas de acción en el pasado, pero al observarlas más de cerca, me parece una forma bastante clara de controlar todo el lado de las cosas habilitado/eventos. ¡Aclamaciones! – robsoft

1

En lugar de desactivar los controles, tenemos una var boolean en forma fBusy, a continuación, simplemente comprobar esto cuando el usuario presiona un botón, nosotros lo introdujo por los motivos exactos que menciona, los usuarios presionan los botones mientras esperan que se ejecute el código de ejecución prolongada (da miedo lo familiar que es la muestra de su código).

por lo que terminan con algo como

procedure OnClick(Sender:TObejct); 
begin 
    if (FBusy) then 
    begin 
     ShowMessage('Wait for it!!'); 
     Exit; 
    end 
    else FBusy := True; 
    try 
     //long running code 
    finally 
     FBusy := False; 
    end; 
end; 

Su impotente para recordar al rap el código de larga data en un try-finally en el caso de las salidas o excepción, como se puede terminar con una forma eso no funcionará.

Según lo sugerido, usamos hilos si es para código que no afectará los datos, por ejemplo ejecutando un informe o análisis de datos, pero algunas cosas no son opciones, digamos si estamos actualizando 20,000 registros de productos, entonces no No queremos que nadie intente vender o alterar los registros a mitad de camino, por lo que debemos bloquear la aplicación hasta que se complete.

+0

Gracias por esto, este enfoque es la forma en que he tendido a hacerlo yo mismo en otras aplicaciones. Creo que trataré de usar un hilo para código de larga ejecución en la próxima aplicación que escriba, ya que parece algo con lo que debería estar cómodo. :-) – robsoft

3

No llame a Application.Processmessages, esto es lento y puede generar recursividad ilimitada. Por ejemplo, para actualizar todo en Panel1 sin película, podemos utilizar este método:

procedure TForm1.ForceRepaint; 
var 
    Cache: TBitmap; 
    DC: HDC; 
begin 
    Cache := TBitmap.Create; 
    Cache.SetSize(Panel1.Width, Panel1.Height); 
    Cache.Canvas.Lock; 
    DC := GetDC(Panel1.Handle); 
    try 
    Panel1.PaintTo(Cache.Canvas.Handle, 0, 0); 
    BitBlt(DC, 0, 0, Panel1.Width, Panel1.Height, Cache.Canvas.Handle, 0, 0, SRCCOPY); 
    finally 
    ReleaseDC(Panel1.Handle, DC); 
    Cache.Canvas.Unlock; 
    Cache.Free; 
    end; 
end; 

Para un mejor rendimiento, el mapa de bits caché debe ser creado en un primer momento y libre cuando el proceso ha terminado

Cuestiones relacionadas