I've got a legacy app here that has a few 'time-consuming' loops that get fired off as a result of various user interaction. The time-consuming code periodically updates something on the screen with progress information (typically a label) and then, seemingly to persuade the visual refresh to happen there-and-then, the code calls Application.ProcessMessages (argh!).
We all know now what kind of trouble this can introduce to a GUI app (being charitable, it was a more innocent time back then) and we're finding that sure as eggs, from time to time we get users achieving the impossible with the program because they're clicking on controls while the program is 'busy'.
What's the best way of periodically refreshing the visuals of the form without taking-on other events/messages etc?
My thoughts were to either;
- disable all of the controls before doing anything time-consuming, and leaving the '...ProcessMessages' calls in place to 'force' the refresh, or
- find another way to refresh a control periodically
I can do the former but it left me wondering - is there a better solution?
example code from legacy;
i:=0; while FJobToBeDone do begin DoStepOfLoop; inc(i); if i mod 100 = 0 then begin UpdateLabelsEtc; Application.ProcessMessages; end; end;
I can already hear you all fainting, at the back. :-)
If you call Update() on the controls after you have changed properties you will force them to redraw. Another way is to call Repaint() instead of Refresh(), which implies a call to Update().
You may need to call Update() on parent controls or frames as well, but this could allow you to eliminate the ProcessMessages() call completely.
The solution I use for long updates is by doing the calculations in a separate thread. That way, the main thread stays very responsive. Once the thread is done, it sends a Windows message to the main thread, indicating the main thread can process the results.
This has some other, severe drawbacks though. First of all, while the other thread is active, you'll have to disable a few controls because they might restart the thread again. A second drawback is that your code needs to become thread-safe. This can be a real challenge sometimes. If you're working with legacy code, it's very likely that your code won't be thread-safe. Finally, multi-threaded code is harder to debug and should be done by experienced developers.
But the big advantage of multi-threading is that your application stays responsive and the user can just continue to do some other things until the thread is done. Basically, you're translating a synchronous method into an asynchronous function. And the thread can fire several messages indicating certain controls to refresh their own data, which would be updated immediately, on the fly. (And at the moment when you want them to be updated.)
I've used this technique myself quite a few times, because I think it's much better. (But also more complex.)
Do not call Application.Processmessages, This is slow and might generate unlimited recursion. For example, to update everything in Panel1 without flick, we can use this method:
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;
For better performance, the cache bitmap should be created at first and free when the process has finished
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With