2
votes

My control is a TCustomControl descendant where all the contents is painted with GDI+ in the overridden Paint method.

Everything is fine when

DoubleBuffered := True;
ParentBackground := False;

and I erase control's background in Paint method with

g := TGPGraphics.Create(Canvas.Handle);
g.Clear(MakeColor(70, 70, 70));

Now I would like to make a transparent background in the areas where I am not painting.

So, I commented the g.Clear out and made

ParentBackground := True;

in constructor.

When runtime themes are off it is enough to set DoubleBuffered of parent control to True in order to avoid flickering, but with runtime themes this does not help any more.

Below is an excerpt from TWinControl code with a marked line that causes flickering:

procedure TWinControl.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
  if StyleServices.Enabled and Assigned(Parent) and (csParentBackground in FControlStyle) then
  begin
    { Get the parent to draw its background into the control's background. }
    if Parent.DoubleBuffered then
      PerformEraseBackground(Self, Message.DC) //It flickers here!!!!!
    else
      StyleServices.DrawParentBackground(Handle, Message.DC, nil, False);
  end
  else
  begin
    { Only erase background if we're not doublebuffering or painting to memory. }
    if not FDoubleBuffered or
{$IF DEFINED(CLR)}
       (Message.OriginalMessage.WParam = Message.OriginalMessage.LParam) then
{$ELSE}
       (TMessage(Message).wParam = WPARAM(TMessage(Message).lParam)) then
{$ENDIF}
      FillRect(Message.DC, ClientRect, FBrush.Handle);
  end;
  Message.Result := 1;
end;

Are there any solutions to that?

1
Why do you have DoubleBuffered set to True? - David Heffernan
I think it is likely possible to resolve the flickering without setting DoubleBuffered to True. That's been the route I have always taken to resolve flickering VCL controls. - David Heffernan
@DavidHeffernan: But status bars and list view controls often require double buffering, or you get horrible visual artefacts (at least on most Swedish PCs I have seen). - Andreas Rejbrand
@AndreasRejbrand list view and status bar are indeed exceptions to the rule. - David Heffernan
So you can change the behavior by handling WM_ERASEBKGND yourself, no? - Sertac Akyuz

1 Answers

2
votes

There is an error in TWinControl.WMEraseBkgnd method. It should always skip erasing background for double buffered controls when control is not painting in memory.

You can override WMEraseBkgnd behavior in your own control, or patch TWinControl.WMEraseBkgnd to apply following fix for all controls.

  TMyControl = class(TCustomControl)
  protected
  ...
    procedure WMEraseBkgnd(var Message: TWmEraseBkgnd); message WM_ERASEBKGND;
  ...

procedure TMyControl.WMEraseBkgnd(var Message: TWmEraseBkgnd);
begin
{ Only erase background if we're not doublebuffering or painting to memory. }
  if not FDoubleBuffered or
{$IF DEFINED(CLR)}
    (Message.OriginalMessage.WParam = Message.OriginalMessage.LParam) then
{$ELSE}
    (TMessage(Message).WParam = WParam(TMessage(Message).LParam)) then
{$ENDIF}
    begin
      if StyleServices.Enabled and Assigned(Parent) and (csParentBackground in ControlStyle) then
        begin
          if Parent.DoubleBuffered then
            PerformEraseBackground(Self, Message.DC)
          else
            StyleServices.DrawParentBackground(Handle, Message.DC, nil, False);
        end
      else
        FillRect(Message.DC, ClientRect, Brush.Handle);
    end;
  Message.Result := 1;
end;

Issue is reported as RSP-24415