Delphi对Aero Glass和DoubleBuffered属性的支持 - 发生了什么,我们如何使用它们?

25
我对Delphi 2009/2010在Windows中支持Aero主题玻璃特性感到困惑,以及DoubleBuffered确切的含义以及它与Aero玻璃有什么关系。我发现DoubleBuffered不仅是VCL中的属性,也存在于.net WinForms中。起初我想知道它是否设置了一些由公共控件库使用的窗口样式位,或者其他什么。为什么要使用它,什么时候应该使用?
[更新:我应该说明我知道“双缓冲”是什么,作为减少闪烁的一般技术,我想知道的是,它为什么与在Windows Vista / Windows 7上呈现控件的Aero Glass面板有任何关系,特别是为什么按钮这种东西需要将双缓冲设置为true,才能在玻璃上工作?下面链接的博客文章似乎最具信息量。]
特别是,我对DoubleBuffered属性感到困惑,想知道它为什么存在,以及在表单和控件中的双缓冲属性与玻璃支持之间的关系。当您阅读像这篇C ++文章这样的文章时,您会发现没有提到双缓冲。
[更新2:以下内容包含一些事实错误,已进行修改:]
我发现一些C++开发者在讨论如何调用SetLayeredWindowAttributes来避免在经典Win32应用中启用DWM/Aero合成时引起的“黑色变为玻璃”闪烁问题。 但是下面的博客链接告诉我,这在Windows 7中不再适用,实际上仅在Vista中短暂适用,直到Microsoft阻止它。 [错误想法开始]我们不应该使用其他颜色,比如亮洋红色,并使其变成玻璃透明色吗?[错误想法结束]
何时应该设置DoubleBuffered和何时不应该设置DoubleBuffered的规则是什么?为什么首先要将DoubleBuffered添加到VCL中?设置后会在什么情况下引起问题?(似乎远程桌面是一个案例,但这是否是唯一的案例?)如果不设置,则我们将得到按钮文本渲染的故障,最可能是因为Delphi在Aero DWM中不改变默认的“将黑色呈现为玻璃”的方式。
据我所知,Aero Glass渲染基本上是以一种奇怪或难以理解的方式进行的[由Windows自身完成,而不是Delphi,后者仅包装了此功能],许多VCL源代码在2009/2010年的StdCtrls类中必须执行大量复杂的逻辑才能在Aero Glass上正确呈现内容,然而它仍然存在许多问题,看起来像是做错了,这可能是相关问题和qc问题背后的原因。 [更新3:在VCL中玻璃上的许多渲染故障都是在常见控件内部错误地进行渲染,似乎微软并不关心修复。简而言之,Delphi VCL代码修复无法解决古老的Windows公共控件库和现代[但古怪]的Aero Glass合成功能彼此不太喜欢且不特别配合的事实。感谢微软构建了如此高质量的技术并将其释放到世界上。]

如果这还不够有趣,那我们为什么要有ParentDoubleBuffered?

[7月30日更新:我认为这个问题很有趣,因为当你拥有一个大型现有的VCL框架时,通过Windows API解决此问题是一个非常困难的问题。]

4个回答

15

关于DoubleBuffer

.NET可能有一个同名同功能的组件,但Delphi是从头开始实现DoubleBuffer,我认为.NET也是这样做的。在实现过程中没有使用窗口样式位。

DoubleBuffer和Glass Aero

很简单:不要为位于Glass上的控件设置DoubleBuffer。要使DoubleBuffering起作用,必须能够初始化“缓冲区”-但对于Glass,应该用什么来初始化它?Windows标准控件(包括TButton)不需要DoubleBuffering。对于需要透明表面和双缓冲类似行为的新控件,可以使用分层窗口API。

让控件在Glass上工作

步骤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;

第二步,这应该是您的表单的OnPaint处理程序:

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;

步骤 3:设置GlassFrame.Enabled = True; 设置所有其他玻璃属性,在表单上添加控件,无论您喜欢它们放在哪里。可能在Glass上或任何其他地方。确保控件没有"DoubleBuffered = True"。就这样,享受吧。我已经测试了TButton、TCkBox和TEdit。

...编辑...

不幸的是,使用此方法,“Glass”被视为100%透明的表面,但它并不是-它看起来像玻璃,但它不像玻璃一样运作。100%透明度的问题在于,如果您单击该透明区域,则单击会传递到窗口后面的窗口。很可怕。

在撰写本文时,我相信没有API可以更改原始玻璃的默认黑色关键颜色(Google可以找到无数博客和论坛帖子,介绍如何对坐落在玻璃上的控件进行自定义绘制,并且在MSDN上的DWM函数列表中没有更改它的函数)。如果不更改默认的黑色,大多数控件无法正确呈现,因为它们使用clWindowText编写文本,而clWindowText是黑色的。几个论坛上提出的一个建议技巧是使用SetLayeredWindowAttributes API更改透明颜色。它很有效!一旦完成了这个过程,控件上的黑色文本就会显示出来,但不幸的是,玻璃不再是玻璃,玻璃看起来像玻璃,但行为像100%透明度。这基本上使此解决方案无效,并显示了Microsoft的双重标准:原始的BLACK并不表现得像100%透明度,但如果我们将其更改为更好的东西,则会表现得像100%透明度。

在我看来,在Glass上使用自定义控件的普遍想法是错误的。这可能是唯一可行的方法,但它是错误的,因为我们应该使用跨平台一致的控件:建议使用自定义控件会打开不一致、类似Winamp的应用程序的大门,在这些应用程序中,每个用户都必须重新创建满足其艺术想法的轮子。即使开发人员成功地复制了任何给定的Windows控件并让其在Glass上运行,"修复"也只是暂时的,需要为下一个Windows版本重新创建。更不用说可能需要为现有版本的Windows创建多个变体。

另一个解决方案是使用UpdateLayeredWindow的图层窗口。但是由于很多原因,这是个痛苦的事情。

对我来说这是条死路。但我会将此问题标记为"收藏",如果有更好的解决方案出现,我想知道。


然而,似乎 TMemo 仍然无法正常工作(至少不是没有双缓冲!),但这只是一个小问题,因为 TMemo 的特性集合只是 TRichEdit 特性的子集, 而 TRichEdit 可以 正常工作。 - Andreas Rejbrand
迄今为止最详细的答案。博客文章本身很棒,但这个答案在SO问题本身上是最清晰的。 - Warren P
只有我觉得使用这种方法 TLabel 看起来很差吗? - Uwe Raabe
@Uwa Rabee:是的,TLabelTStaticText 都看起来很糟糕。更糟糕的是,窗体似乎无法被点击! - Andreas Rejbrand
是的,一切都正确。已编辑答案以反映这一点。 - Cosmin Prund
+1 标签看起来不太好,但我一直在努力让按钮正常工作。请在我的问题中引用此答案,并我会接受它: https://dev59.com/k3A75IYBdhLWcg3wW3y8 - Cobus Kruger

7

1
很棒的文章。误解。当然。我只是在直觉上对一堆意外复杂性的想法进行了一些推测,这些复杂性我并没有设计,并且也不声称理解:Aero、合成、DWM等等。 - Warren P

3
DoubleBuffered是一种标准图形技术,用于减少闪烁。基本上,您在第二个画布上绘制窗体的新版本,然后将其与当前画布交换。即使绘图过程很慢,该交换也很快。这与Aero之间没有直接关系 - 您可以单独使用其中一个或两者都可以。Delphi中的双缓冲技术已经存在了很长时间,但现在我们每秒刷新屏幕的处理器周期更多,因此它变得不那么必要。这可能就是您为什么没有听说过它的原因。
双缓冲技术只应在需要时才使用 - 如果您的应用程序重新绘制屏幕时出现闪烁,请打开它并查看发生了什么。在这种情况下,您的第一选择应该是DisableUpdates/EnableUpdates(请参阅Delphi帮助)和Windows API LockWindowUpdate(同上)。
ParentDoubleBuffered,像大多数Parent...属性一样,告诉您此窗体是否将使用其父窗体的DoubleBuffered属性。这使您可以在应用程序主窗体上设置属性一次,并影响您创建的每个窗体。如果将该属性设置为false,则无效。
这是一个典型的情况,当你拥有向后兼容的代码时 - 里面有些东西今天很少用到,但仍然有效(有时仍然必要),尽管大多数人从不需要担心它们。对于大多数人来说,它们只是存在,你可以忽略它们,但对于我们中的一些人来说,它们是非常必要的(我们有几个非常复杂的表格,我们会以偶尔复杂的方式重新绘制它们以防止闪烁)。

3
LockWindowUpdate()与减少屏幕闪烁没有关系,完全、彻底和绝对错误将其用于此类目的。但是SO上已经有足够的讨论了。同样,在屏幕区域被多次绘制时,无论机器速度如何,闪烁总会出现,因此当没有桌面组合功能时,使用DoubleBuffered对于实现最佳绘图效果始终是必要的。 - mghie
我知道抽象地什么是双缓冲,但不确定为什么要使用它。最有帮助的是说“反应性使用”,即出现问题时打开以解决问题。然而,总会发生像在玻璃上一样的问题,因此不能纯粹地反应性使用。几乎需要一个流程图/决策树。关于玻璃/透明度/合成方面没有详细信息,请添加更多细节。 - Warren P

1

好的,我会尽量澄清一下。

首先,双缓冲是屏幕渲染时非常标准的技术。例如,在我的文本编辑器组件中,我经常使用它。想象一下,如果需要重新绘制文本。简单来说,我首先通过FillRect(ClientRect)清除整个客户端矩形,然后从第一个字符到最后一个可见字符逐行绘制。但这样看起来对最终用户来说很丑陋,因为在两个几乎相同的文本内容状态之间,显示器会有几毫秒左右的清空显示。

解决方案是将所有绘图操作都绘制到一个离屏位图上,当它完成时,只需将离屏位图绘制到屏幕上即可。然后,如果前一帧和新帧相同,则显示器不会发生任何变化,这与非双缓冲情况形成了鲜明的对比,在非双缓冲情况下,屏幕会显示一个空白的白色矩形片刻间,即出现闪烁。我总是为我的所有可视控件使用双缓冲,这确实大大提高了它们的质量和感觉。在具有几千兆字节内存(RAM)的现代系统上,增加的内存使用绝不是问题。而且在几乎所有情况下,缓冲区的交换速度都足够快(尽管需要复制相当多的像素)。
通常,当调整窗口大小时,您可以观察到缺少双缓冲的情况。有时它们会闪烁得厉害。值得注意的是,诸如OpenGL之类的技术本质上是双缓冲的(至少在大多数情况下)。

双缓冲与玻璃板没有特别的关系。实际上,大多数Delphi TWinControl的后代都具有DoubleBufferedParentDoubleBuffered属性(参考)。

ParentDoubleBuffered属性与DoubleBuffered的关系与ParentColorColorParentShowHintShowHintParentFontFont等属性的关系相同。它只是决定控件是否应该从其父窗口继承参数的值。

现在来回答关于玻璃的问题。嗯,广泛共识是,在VCL应用程序中,向玻璃板(或玻璃框架)添加控件通常很棘手。有人应该撰写 一篇长文 来讨论如何正确地完成这件事...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接