【问题标题】:Delphi support for Aero Glass and the DoubleBuffered property - what is going on and how do we use them?Delphi 对 Aero Glass 和 DoubleBuffered 属性的支持——发生了什么以及我们如何使用它们?
【发布时间】:2011-03-22 09:30:50
【问题描述】:

我对 Delphi 2009/2010 对 Windows 中 Aero Theme Glass 功能的支持、DoubleBuffered 的确切含义以及它与 Aero 玻璃的关系感到困惑。我发现 DoubleBuffered 不仅是 VCL 中的一个属性,它还在 .net WinForms 中找到。最初我想知道它是否设置了公共控件库使用的某种窗口样式位,或者什么。为什么要使用,什么时候使用?

[更新:我应该声明我知道“双缓冲”是什么,作为一种减少闪烁的通用技术,我想知道的是,为什么它与 Aero Glass 窗格上的渲染控件有任何关系Windows Vista/Windows 7,特别是为什么所有东西的按钮都需要设置双缓冲,才能在玻璃上工作?下面链接的博文似乎信息量最大。]

特别是对DoubleBuffered属性很困惑,想知道它为什么存在,它的玻璃支架与窗体和控件中的双缓冲属性的设置是什么关系。当您阅读C++ articles like this one 时,您会看到没有提到双缓冲。

[更新2:以下包含一些事实错误,并已进行修改:]

我发现一些 C++ 开发人员谈论他们如何调用 SetLayeredWindowAttributes 以避免在经典 Win32 应用程序中打开 DWM/Aero 合成导致的“黑色变成玻璃”故障 [但是下面的博客链接告诉我这在 Windows 7 中不再工作,实际上只是在 Vista 中短暂工作,直到微软阻止它]。 [Begin WRONG Idea] 我们不应该使用其他颜色,比如明亮的洋红色,然后把它变成玻璃透明色吗? [结束错误的想法]

什么时候应该设置和不设置 DoubleBuffered 的规则是什么?为什么首先将 DoubleBuffered 添加到 VCL 中?什么时候设置会出问题? (似乎远程桌面是一种情况,但这是唯一的情况吗?)当它没有设置时,我们会在按钮文本的渲染中出现故障,很可能是因为 Delphi 似乎没有更改默认的“将黑色渲染为玻璃" 在 Aero DWM 中。

在我看来,Aero Glass 渲染从根本上是以一种奇怪或难以理解的方式完成的 [由 Windows 本身完成,而不是由仅包含此功能的 Delphi 完成],而且 2009 年有大量内部 VCL 源代码/2010 在 StdCtrls 的类中必须做很多复杂的逻辑才能在 Aero Glass 上正确渲染东西,但它仍然有很多问题,在我看来它做错了,这可能是这个 related question, and qc issue. [更新 3:玻璃上的许多渲染故障,在 VCL 中,在通用控件中渲染完成错误,微软似乎并不关心修复。简而言之,Delphi VCL 代码修复无法解决这样一个事实,即古老的 Windows 通用控件库和现代 [但古怪的] Aero Glass 合成功能彼此不太喜欢,并且不能很好地协同工作。感谢 Microsoft 构建了如此高质量的技术并将其推向世界。]

如果还不够有趣的话;为什么我们有 ParentDoubleBuffered?

[7 月 30 日更新:这个问题对我来说很有趣,因为我认为它表明,当你有一个大型的现有 VCL 框架时,使用 Windows API 来解决这个问题是一个难题。]

【问题讨论】:

    标签: delphi aero composition dwm


    【解决方案1】:

    DoubleBuffered 是一个standard graphics technique 用于减少闪烁。基本上,您在第二个画布上绘制表单的新版本,然后将其换成当前的。即使绘图过程很慢,这种交换也很快。这与 Aero 之间没有直接关系 - 您可以单独使用其中一个或两个。双缓冲在 Delphi 中已经存在了很长时间,但现在我们每次屏幕刷新都有更多的处理器周期,这不太必要了。这可能就是你没有听说过它的原因。

    双缓冲是您应该只被动使用的东西 - 如果您在应用重新绘制屏幕时看到闪烁,请打开它并查看会发生什么。在这种情况下,尽管您的第一个办法是 DisableUpdates/EnableUpdates(请参阅 Delphi 帮助)和 Windows API LockWindowUpdate(同上)。

    ParentDoubleBuffered 与大多数 Parent... 属性一样,会告诉您此表单是否会使用其父级的 DoubleBuffered 属性。这使您可以在应用程序主窗体上设置一次属性,并使其影响您创建的每个窗体。或者不,如果你将该属性设置为 false。

    当您拥有向后兼容的代码时,这是一种典型的情况——其中有些东西现在很少使用但仍然有效(有时仍然是必需的),尽管大多数人从不需要担心它们。对于大多数人来说,它们就在那里,你可以忽略它们,但对于我们中的一些人来说,它们是非常必要的(我们有几个非常非常复杂的表格,我们偶尔会重新绘制复杂的图案以阻止闪烁)

    【讨论】:

    • LockWindowUpdate() 与减少闪烁无关,将其用于此类目的是完全、完全和完全错误的。但是已经在 SO 上对此进行了足够的讨论。同样,当屏幕的某个区域被多次绘制时,总会出现闪烁,无论机器的速度如何,因此当没有桌面合成处于活动状态时,DoubleBuffered 将始终是最佳绘图所必需的。
    • 您不应该为此使用 LockWindowUpdate。更多信息请参考blogs.msdn.com/b/oldnewthing/archive/2007/02/19/1716211.aspx,或google.com/…
    • 我知道抽象中的双缓冲是什么,但不知道为什么要使用它。说“被动使用”最有帮助,即打开以解决问题。然而,有些问题总是像在玻璃上一样发生,因此,不能纯粹被动地使用它。几乎需要一个流程图/决策树。此答案中没有关于玻璃/透明度/合成的任何内容 - 如果您对此了解更多,请添加详细信息。
    【解决方案2】:

    您的问题已提示a blog post from CR on Delphi Haven...

    查看堆栈溢出,我刚刚 注意到一个相当详细的问题 (真的,一组问题)关于 Aero 玻璃。

    【讨论】:

    • 很棒的文章。误解。当然。我只是凭直觉对一堆我没有设计的意外复杂性提出了一些想法,也没有声称自己理解:Aero、合成、DWM 等。
    【解决方案3】:

    好的,我会试着把事情理顺一下。

    首先,在屏幕渲染方面,双缓冲是一种非常标准的技术。我一直使用它,例如在我的文本编辑器组件中。想象一下文本需要重绘。非常简单地说,我首先清除整个客户端矩形,或多或少通过FillRect(ClientRect),然后我画线,从第一个可见到最后一个可见,每个从第一个字符到最后一个可见。但这对于最终用户来说会非常难看,在几乎相同文本内容的两种状态之间有几毫秒左右的时间来清除显示。

    解决方案是,对离屏位图进行所有绘制,并在完成后将离屏位图简单地绘制到屏幕上。然后,如果前一帧和新帧相同,则显示不会发生任何事情,这与非双缓冲情况形成鲜明对比,在非双缓冲情况下,屏幕将显示一个空白的白色矩形几毫秒,即屏幕会闪烁。我总是对我的视觉控件所有使用双缓冲,这确实极大地提高了它们的质量和感觉。在具有千兆字节内存 (RAM) 的现代系统上,内存使用量的增加绝不是问题。而且几乎在所有情况下,缓冲区的交换都足够快(尽管要复制的像素不少)。

    在调整窗口大小时,您通常会发现缺少双缓冲。有时它们只是闪烁为 h**l。有趣的是,OpenGL 等技术本质上是双缓冲的(至少在大多数情况下是这样)。

    因此,双缓冲与玻璃片无关。事实上,大多数德尔福TWinControl 的后代都具有DoubleBufferedParentDoubleBuffered 属性(reference)。

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

    现在关于玻璃的问题。好吧,众所周知,向一块玻璃(或玻璃框架)添加控件通常很尴尬,至少在 VCL 应用程序中是这样。有人应该写a long blog article 讨论如何正确地做到这一点......

    【讨论】:

      【解决方案4】:

      关于双缓冲

      .NET 可能有一个,它可能与 Delphi 的名称和用途相同,但 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;
      

      第 2 步,这应该是表单的 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 属性,将控件添加到表单中,无论您喜欢它们。可能在 Glass 或其他任何地方。确保控件没有“DoubleBuffered = True”。就是这样,享受。我用 TButton、TCkBox 和 TEdit 测试过。

      ...编辑...

      不幸的是,使用这种方法“玻璃”被视为 100% 透明的表面,它不是 - 它看起来像玻璃,但它的行为不像玻璃。 100% 透明度的问题是,如果您点击该透明区域,您的点击会转到窗口后面的窗口。太可怕了。

      在撰写本文时,我很确定没有 API 可以更改原始玻璃的默认 BLACK 键颜色(谷歌发现了无数关于如何使用自定义绘图来控制玻璃上的控件的博客和论坛帖子在the list of DWM functions on MSDN 中没有任何功能可以更改它)。在不更改默认黑色的情况下,大多数控件无法正确呈现,因为它们使用 clWindowText 编写文本,而这就是黑色。在几个论坛上发现的一个建议技巧是使用 SetLayeredWindowAttributes API 更改透明度颜色。它有效!一旦完成,控件上的黑色文本就会显示抛出,但不幸的是玻璃不再是玻璃,玻璃看起来像玻璃,但表现得像 100% 透明度。这几乎使该解决方案无效,并显示了 Microsoft 方面的双重标准:原始 BLACK 的行为不像 100% 透明,但如果我们将其更改为更好的东西,它的行为就像 100% 透明。

      在我看来,在 Glass 上使用自定义控件的普遍想法是错误的。这是唯一可行的方法,但它是错误的,因为我们应该使用跨平台一致的控件:建议自定义控件为不一致的、类似 winamp 的应用程序打开了大门,每个用户重新创建轮子适合它的艺术理念。即使开发人员设法忠实地重新创建任何给定的窗口控件并使其在玻璃上工作,“修复”也只是暂时的,需要为下一个版本的窗口重新创建。更不用说现有版本的 windows 应该有多种变体。

      另一种解决方案是使用带有 UpdateLayeredWindow 的分层窗口。但这很痛苦,原因有很多。

      这对我来说是个死胡同。但是我会给这个问题一个“最喜欢”的标志,如果有更好的东西出现,我想知道它。

      【讨论】:

      • 然而,似乎TMemo 仍然无法工作(至少 没有 双缓冲!),但这只是一个小问题,因为集合TMemo 的特征只是TRichEdit 的特征的一个子集,它确实有效。
      • 迄今为止最详细的答案。博客文章本身很棒,但这是 SO 问题本身最明确的答案。
      • 只是我还是 TLabel 用这种方法看起来真的很差?
      • @Uwa Rabee:是的,TLabels 和 TStaticTexts 看起来都很糟糕。更糟糕的是,表单似乎不可点击!
      • 是的,都是真的。编辑答案以反映这一点。
      猜你喜欢
      • 1970-01-01
      • 2015-02-15
      • 1970-01-01
      • 1970-01-01
      • 2014-05-23
      • 2016-12-18
      • 2014-10-22
      • 2011-10-12
      • 2010-12-22
      相关资源
      最近更新 更多