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?
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.
You should return 0 when the background is not (completely) erased, and you should return another value then 0 when background is to be considered erased. This is the convention by which you have to hold.
More important is not to call inherited
within this message handler 1), which would call the inherited message handlers, and eventually the default Windows procedure which would paint the device context with the brush that is given to the window at creation time, if any.
Now, in practice, and especially in this example of your custom control, it does not really matter which value you return, because you are the only one performing the erasing task. But consider designing a base control class or deployment of the control: you might want to indicate to descendants or users of your control that the background is not totally validated. That's what Message.Result = 0
means. You could also send back Message.Result = ebLeftSide
which would indicate that in the current state of the control, only the left side (whatever that might mean) is "erased".
Remember that "erasing" in this context also means "drawing", but that is beyond the question I think.
1)Inherited
works a bit different for message handlers in comparison to virtual methods. Although it means the same - the first handler in the inheritance chain will be called - there is no override directive in its declaration, and the method name cannot be added.
The return value of WM_ERASEBKND
just determines how the fErase
member of the PAINTSTRUCT
gets initialized when you call BeginPaint
in the subsequent WM_PAINT
handler. If your paint handler ignores that member, then it doesn't really matter what WM_ERASEBKND
returns.
The flicker is avoided by painting once instead of twice. If you fill the region with a color in WM_ERASEBKGND
and then blit over it a moment later in WM_PAINT
, you'll get flicker. If you do no painting in WM_ERASEBKGND
and just blit in WM_PAINT
, you won't flicker. The only trick is making your blit covers the entire invalidated area with initialized pixels.
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