Delphi support for Aero Glass and the DoubleBuffered property – what is going on and how do we use them

aerocompositiondelphidwm

I am confused by Delphi 2009/2010 support for the Aero Theme Glass features in Windows, and by what, exactly DoubleBuffered means, and what it has to do with Aero glass. I have found that DoubleBuffered is not only a property in the VCL, it also is found in .net WinForms. Initially I wondered if it set some kind of window style bit used by the common controls library, or what. Why is it used, and when should it be used?

[Update: I should state that I know what "double-buffering" is, as a general technique for reduction of flicker, what I wondered is, why does it have ANYTHING to do with rendering controls on an Aero Glass pane in Windows Vista/Windows 7, and in particular why would a BUTTON of all things need to have double-buffering set true, to work over glass?. The blog post linked below seems most informative.]

In particular, I am confused by the DoubleBuffered property, and I want to know, why it exists, and what its relationship between the glass support and the double-buffered property in a form and a control are set. When you read C++ articles like this one you see that there is no mention of double buffering.

[Update2: The following contained some factual errors, and has been amended:]

I found some C++ developers talking about how they can call SetLayeredWindowAttributes to avoid the "black becomes glass" glitch that DWM/Aero compositing causes when you switch it on in your classic Win32 app [however the blog link below tells me that this no longer works in Windows 7, and actually only briefly worked in Vista, until Microsoft blocked it]. [Begin WRONG Idea] Shouldn't we use some other color, like bright magenta and make that turn into the glass transparency color? [End WRONG Idea]

What are the rules for when DoubleBuffered should be set and not set, and why was DoubleBuffered added to the VCL in the first place? When will it cause problems when set? (It appears remote desktop is one case, but is that the only case?) and when it is not set, we get glitched out rendering of button text, most likely because it appears that Delphi does not change the default "render black as glass" in the Aero DWM.

It seems to me that Aero Glass rendering is being done fundamentally in an odd or hard to understand way [by Windows itself, not by Delphi, which merely wraps this functionality], and that a lot of internal VCL source code in 2009/2010 in classes in StdCtrls has to do a lot of complex logic to render stuff correctly on Aero Glass, and yet it's still got lots of problems and looks to me like it's done wrong, and that this might be behind this related question, and qc issue. [Update3: A lot of rendering glitches on glass, in the VCL are rendering done wrong inside common controls, which it seems, Microsoft doesn't care about fixing. In short, Delphi VCL code fixes can't fix the fact that the ancient Windows common controls library and the modern [but quirky] Aero Glass compositing feature don't like each other much and don't particularly work well together. Thank you Microsoft for building such a high quality technology and unleashing it on the world.]

And if it wasn't fun enough yet; Why do we have ParentDoubleBuffered?

[Update July 30: This question is interesting to me because I think it shows that working on the Windows API to solve this problem, when you have a large existing VCL framework, is a hard hard problem.]

Best Answer

About DoubleBuffer

.NET may have one, and it may have the same name and the same purpose as Delphi's one, but Delphi is implementing DoubleBuffer from ground-up and I assume .NET does the same. No window style bits are used implementing this.

DoubleBuffer and Glass Aero

Fairly simple: Don't set DoubleBuffer for controls that sit on Glass. For DoubleBuffering to work one has to be able to initialize the "Buffer" - but what to initialize it with for Glass? DoubleBuffering is not required for Windows standard controls (including TButton). For new controls that need both transparent surfaces and doublebuffer-like behaviour, one can use the Layered windows api's.

Getting controls to work on Glass

Step 1:

TForm1 = class(TForm)
...
protected
  procedure CreateWindowHandle(const Params: TCreateParams); override;
...
end;

procedure TForm15.CreateWindowHandle(const Params: TCreateParams);
begin
  inherited;
  SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) or WS_EX_LAYERED);
  SetLayeredWindowAttributes(Handle, RGB(60, 60, 60), 0, LWA_COLORKEY);
end;

Step 2, this should be your form's OnPaint handler:

procedure TForm15.FormPaint(Sender: TObject);
var rClientRect:TRect;
begin
  if GlassFrame.Enabled then
  begin
    rClientRect := ClientRect;

    Canvas.Brush.Color := RGB(60, 60, 60);
    Canvas.Brush.Style := bsSolid;
    Canvas.FillRect(rClientRect);

    if not GlassFrame.SheetOfGlass then
    begin
      rClientRect.Top := rClientRect.Top + GlassFrame.Top;
      rClientRect.Left := rClientRect.Left + GlassFrame.Left;
      rClientRect.Right := rClientRect.Right - GlassFrame.Right;
      rClientRect.Bottom := rClientRect.Bottom - GlassFrame.Bottom;
      Canvas.Brush.Color := clBtnFace;
      Canvas.FillRect(rClientRect);
    end;
  end;
end;

Step 3: Set GlassFrame.Enabled = True; Set all other Glass properties, add controls to the form, wherever you like them. May be on Glass or anywhere else. Make sure the controls don't have "DoubleBuffered = True". That's it, enjoy. I've tested with TButton, TCkBox and TEdit.

... EDIT ...

Unfortunately using this method "Glass" is treated as an 100% transparent surface, and it's not - it looks like glass, but it doesn't behave like glass. The problem with 100% transparency is, if you click on that transparent area, your click goes to the window behind your window. Horrible.

At the time of this writing I'm pretty sure there's no API to change the default BLACK key color for the original glass (google finds countless blog and forum posts on how you need to use custom drawing for controls that sit on glass and there's no function to change that in the list of DWM functions on MSDN). Without changing the default BLACK color most controls can't render properly because they write text using clWindowText and that's BLACK. One suggested trick found on several forums is to change the transparency color using the SetLayeredWindowAttributes API. And it works! Once that's done black text on controls shows throw, but unfortunately glass is no longer glass, glass looks like glass but behaves like 100% transparency. This pretty much invalidates this solution and shows an double standard on Microsoft's side: The original BLACK does not behave like 100% transparency yet if we change it to something better it does behave like 100% transparency.

In my opinion the common thinking of using custom-controls on Glass is wrong. It's the only thing that might work, but it's wrong, because we're supposed to use controls that are consistent across the platform: suggesting custom controls opens the door to inconsistent, winamp-like applications, where each user re-creates the wheel to suit it's artistic ideas. Even if an developer manages to faithfully recreate any given windows control and make it work on glass, the "fix" is only temporary and needs to be re-created for the next version of windows. Not to mention one should probably have multiple variants for the existing versions of windows.

The other solution is to use Layered windows with UpdateLayeredWindow. But that's a PAIN for sooo many reasons.

This is an dead end for me. But I'll give the question an "favorite" flag, if something better shows up I'd like to know about it.

Related Topic