【问题标题】:How to handle WM_ERASEBKGND to avoid flickering?如何处理 WM_ERASEBKGND 避免闪烁?
【发布时间】:2013-11-16 16:03:31
【问题描述】:

我在表单上有一些自定义进度条,它们每秒更新/刷新两次,并且它们在闪烁。

TMyProgressBar = class(TCustomControl)

我从TCustomControl 继承了控件,因为我需要Handle 和一些TWinControl 事件。控件(最多 64 个项目)是动态创建的并放在 ScrollBox 上。当进度更新时,我首先致电InvalidateRect

所有绘画工作(一组矩形、DrawText 等 - 灵感来自 here)在内存 DC 中执行,然后在控件的 DC 上执行BitBlt-ed。无论如何它都在闪烁,似乎组件消失并重新出现。恕我直言,这是由背景擦除引起的。

this flickering-free drawing advice 中,它被写入以下列方式处理WM_ERASEBKGND

type
  TMyProgressBar = class(TCustomControl)
    procedure WMEraseBkGnd(var Message:TMessage); message WM_ERASEBKGND;

procedure TMyProgressBar.WMEraseBkGnd(var Message: TMessage);
begin
  Message.Result := 1;
end;

但在另一个组件中,通过 TMS (TAdvProgressBar),Result 被设置为 0 用于相同的消息。

现在the Windows documentation 表示:

如果清除背景,应用程序应该返回非零值; 否则,它应该返回零。

我测试了两种变体(结果 = 0、1),令我惊讶的是,它们都避免了闪烁。

那么现在,我必须在我的 Delphi 代码中添加什么?正确的方法是什么?

【问题讨论】:

  • 返回 1,您可能不会受益于将更新区域标记为擦除。
  • 这并不是真正的万能药。在WM_ERASEBACKGROUND 中可能确实需要完成一些工作。当然,如果你什么都不画,那么你就不会闪烁。
  • 有一个非常棒的自定义TScrollBox 示例,它针对闪烁进行了调整:stackoverflow.com/a/16569324/327083

标签: delphi winapi delphi-7 gdi flicker


【解决方案1】:

没关系。重要的是,只要您不调用inherited,默认窗口程序就不会擦除背景。由于您正在绘制控件的整个表面,因此不需要默认处理。

当您返回“0”或“1”(不是“0”)时,会发生什么变化,当调用BeginPaint 时,系统会相应地设置PAINTSTRUCTfErase 成员。返回“0”时,设置为“True”,表示在绘制过程中必须擦除背景。对于'1',设置为'False',表示不需要擦除。 BeginPaintTWinControl.PaintHandler 中调用。没有人检查过fErase 是什么,VCL 只使用设备上下文BeginPaint 返回,所以你返回的内容没有任何区别。

不过,我还是会返回“1”,从概念上暗示已处理了擦除操作。

【讨论】:

  • 这里为什么 0 = TRUE 和 1 = FALSE - 这让我很困惑? :)
  • @ALZ - 将 'True' (1) 返回到 'WM_ERASEBKGND',你说的是“好的,擦除完成”。因此,当调用“BeginPaint”时,系统会显示“擦除:不,您不必(假)”。换句话说,“0”是对消息的响应。 'True' 完全不同。
  • 是否可以从非overridden 方法调用inherited(如procedure WMEraseBkGnd(var Message:TMessage); message WM_ERASEBKGND)?
  • @ALZ - 在 Delphi 中,消息处理在这方面是特殊的。参见#7:docwiki.embarcadero.com/RADStudio/XE5/en/…
【解决方案2】:

当背景未被(完全)擦除时,您应该返回 0,当背景被视为已擦除时,您应该返回另一个值,然后是 0。这是您必须遵守的约定。

更重要的是不要在此消息处理程序 1) 中调用inherited,这将调用继承的消息处理程序,并最终调用默认的 Windows 过程,该过程将使用刷子绘制设备上下文在创建时提供给窗口,如果有的话。

现在,在实践中,尤其是在您的自定义控件的这个示例中,您返回哪个值并不重要,因为您是唯一执行擦除任务的人。但是请考虑设计一个基本控件类或控件的部署:您可能希望向您的控件的后代或用户表明背景未完全验证。这就是Message.Result = 0 的意思。您还可以发回Message.Result = ebLeftSide,这表明在控件的当前状态下,只有左侧(无论这可能意味着什么)被“擦除”。

请记住,在这种情况下,“擦除”也意味着“绘图”,但这超出了我认为的问题范围。


1)Inherited 与虚拟方法相比,消息处理程序的工作方式略有不同。虽然意思一样——将调用继承链中的第一个处理程序——其声明中没有覆盖指令,并且不能添加方法名称。

【讨论】:

  • 感谢您的回复和您的其他进展。现在对我来说更清楚了 - UI/图形开发的新手。在我的情况下,我正在覆盖进度蚂蚁的旧状态(视图),它不会产生闪烁的错觉,因为背景没有被擦除。
【解决方案3】:

WM_ERASEBKND 的返回值决定了当您在后续的WM_PAINT 处理程序中调用BeginPaint 时,PAINTSTRUCTfErase 成员如何被初始化。如果您的绘制处理程序忽略了该成员,那么WM_ERASEBKND 返回的内容并不重要。

通过绘制一次而不是两次来避免闪烁。如果你用WM_ERASEBKGND 中的颜色填充该区域,然后稍后在WM_PAINT 中对它进行blit,你会得到闪烁。如果你没有在WM_ERASEBKGND 中进行绘画,而只是在WM_PAINT 中进行blit,则不会闪烁。唯一的技巧是让你的 blit 用初始化的像素覆盖整个无效区域。

【讨论】:

    猜你喜欢
    • 2010-09-14
    • 1970-01-01
    • 2023-04-02
    • 1970-01-01
    • 2013-05-02
    • 2016-06-30
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多