How to handle WM_ERASEBKGND to avoid flickering?
Asked Answered
D

3

9

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?

Discant answered 5/11, 2013 at 17:12 Comment(3)
Return 1, you're, likely, not going to benefit from keeping the update region marked for erasing.Pleura
This is not really a cure all. There might be work that really needs to be done in WM_ERASEBACKGROUND. For sure if you paint nothing then you'll have no flicker.Headwater
There is a really great example of a custom TScrollBox that is tuned to combat flicker : https://mcmap.net/q/1178693/-performance-issues-re-sizing-large-amount-of-components-on-form-resizeMilissamilissent
P
8

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.

Pleura answered 5/11, 2013 at 20:2 Comment(4)
why 0 = TRUE and 1 = FALSE here - it's confusing me? :)Discant
@Discant - Return 'True' (1) to 'WM_ERASEBKGND', and you're saying that "OK, erasing done". Thus, then when 'BeginPaint' is called, the system says "Erase: No, you don't have to (False)". In other words, '0' is in response to the message. 'True' is in a completely different place.Pleura
Is it possible to call inherited from not overridden method (like procedure WMEraseBkGnd(var Message:TMessage); message WM_ERASEBKGND)?Discant
@Discant - Message handling is special in that regard in Delphi. See #7 here: docwiki.embarcadero.com/RADStudio/XE5/en/…Pleura
H
4

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.

Hyperaesthesia answered 5/11, 2013 at 21:6 Comment(1)
thanks for response and for your other progress bare idea. Now it's a little bit clearer for me - new to UI/Graphical development. In my case I am overwriting old state (view) of progress ant it doesn't make illusion of flickering, because background is no mo erased.Discant
J
2

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.

Jamin answered 6/11, 2013 at 0:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.