How to handle WM_ERASEBKGND to avoid flickering?

ALZ picture ALZ · Nov 5, 2013 · Viewed 7.9k times · Source

I have on a form some custom progress bars which are updated/refreshed twice per second and they are flickering.

TMyProgressBar = class(TCustomControl)

I inherited the control from TCustomControl, because I needed Handle and some TWinControl events. The controls (up to 64 items) are created dynamically and put on a ScrollBox. When progress is updated I first call InvalidateRect.

All painting work (a set of rectangles, DrawText, etc - inspired from here) are performed in a memory DC and then BitBlt-ed on the control's DC. It is anyway flickering, it seems like component dis-appears and re-appears. IMHO it is caused by background erasing.

In this flickering-free drawing advice it is written to handle WM_ERASEBKGND in the following way:

type
  TMyProgressBar = class(TCustomControl)
    procedure WMEraseBkGnd(var Message:TMessage); message WM_ERASEBKGND;

procedure TMyProgressBar.WMEraseBkGnd(var Message: TMessage);
begin
  Message.Result := 1;
end;

But in another component, by TMS (TAdvProgressBar), Result is set to 0 for the same message.

Now the Windows documentation states:

An application should return nonzero if it erases the background; otherwise, it should return zero.

I tested both variants (Result = 0, 1), and to my surprise both avoid flickering.

So now, what do I have to put in my Delphi code? What is the correct way?

Answer

Sertac Akyuz picture Sertac Akyuz · Nov 5, 2013

It doesn't matter. What matters is, as long as you don't call inherited, default window procedure will not erase the background. Since you're painting the whole surface of the control, you don't need default processing.

What changes when you return '0' or '1' (not '0') is that, when BeginPaint is called, the system sets the fErase member of the PAINTSTRUCT accordingly. When you return '0', it is set 'True', indicating that the background must be erased in the paint process. For '1', it is set 'False', indicating that no erasing is necessary. BeginPaint is called in TWinControl.PaintHandler. No one ever checks what fErase is, VCL only uses the device context BeginPaint returns, so what you return does not make any difference.

Still, I'd return '1', conceptually hinting that erasing have been taken care of.