Delphi support for Aero Glass and the DoubleBuffered property - what is going on and how do we use them?
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, a开发者_运维技巧nd 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.]
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.
Your question has prompted a blog post from CR on Delphi Haven...
Looking on Stack Overflow, I’ve just noticed a rather detailed question (really, set of questions) about Aero glass.
DoubleBuffered is a standard graphics technique used to reduce flicker. Basically, you draw a new version of your form on a second canvas then swap it for the current one. That swap is fast, even if the drawing process is slow. There's no direct relationship between that and Aero - you can use either or both independently. Double buffering has been around very a very long time in Delphi, but now we have a lot more processor cycles per screen refresh it's less necessary. Which may be why you haven't heard of it.
Double buffering is something you should only use reactively - if you see flicker when your app is repainting the screen turn it on and see what happens. In that case though your first recourse would be DisableUpdates/EnableUpdates (see the Delphi help for those) and the Windows API LockWindowUpdate (ditto).
ParentDoubleBuffered, like most Parent... properties, tells you whether this form will use the DoubleBuffered property from its parent. This lets you set the property once on your app main form and have it affect every form you create. Or not, if you set that property to false.
This is a typical situation when you have backward compatible code - there's things in there that are rarely used today but still work (and are sometimes still necessary), even though most people never need to worry about them. For most people they're just there and you can ignore them, but for some of us they're desperately necessary (we have a couple of really, really complex forms that we redraw in occasionally complex patterns to stop the flickering)
OK, I will try to straighten things out a bit.
First of all, double-buffering is a very standard technique when it comes to on-screen rendering. I use it all the time, for instance in my text editor component. Imagine the text needs to be redrawn. Very simply put, I first clear the entire client rect, more or less by FillRect(ClientRect)
, then I draw the lines, from the first to the last visible, each from the first character to the last visible. But this will look very ugly to the end user, having a few milliseconds or so with a cleared display, between two states of almost the same textual content.
The solution, is to do all drawing to an off-screen bitmap, and simply draw the off-screen bitmap onto the screen when it is complete. Then, if the preveous and the new frame are identical, there will happen nothing with the display, in great contrast to the non-double-buffered case, where the screen will display an empty, white rectangle for a few milliseconds, i.e. the screen will flicker. I always use double-buffering for all my visual controls, and this really improves the quality and feel of them, vastly. On a modern system with gigabytes of memory (RAM), the increased memory usage is by no means a problem. And in almost all cases, the swapping of the buffers is more than fast enough (although there are quite a few pixels to copy).
Often you can observe the lack of double-buffering when resizing windows. Sometimes they just flicker as h**l. It might also be interesting to notice that technologies such as OpenGL are inherently double-buffered (in most cases, at least).
So double-buffering has nothing in particlar to do with sheets of glass. In fact most decendants of the Delphi TWinControl
has the DoubleBuffered
and ParentDoubleBuffered
properties (reference).
The ParentDoubleBuffered
property is ralated to DoubleBuffered
in the same way as ParentColor
is related to Color
, ParentShowHint
to ShowHint
, ParentFont
to Font
, etc. It simply decides whether or not the control should inherit the value of the parameter from its parent window.
Now to the question about the glass. Well, it is very common knowledge that it in general is awkward to add controls to a sheet of glass (or a glass frame), at least in a VCL application. Someone should write a long blog article discussing how to do that properly...
精彩评论