如何处理 WM_ERASEBKGND 避免闪烁?

Posted

技术标签:

【中文标题】如何处理 WM_ERASEBKGND 避免闪烁?【英文标题】:How to handle WM_ERASEBKGND to avoid flickering? 【发布时间】: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 示例,它针对闪烁进行了调整:***.com/a/16569324/327083 【参考方案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 用初始化的像素覆盖整个无效区域。

【讨论】:

以上是关于如何处理 WM_ERASEBKGND 避免闪烁?的主要内容,如果未能解决你的问题,请参考以下文章

winform绘制闪烁 前台有个线程隔一秒给控件赋值,控件就开始闪烁,如何处理

如何处理 terraform 进程崩溃并避免重试时资源泄漏?

注销后谷歌如何处理后退按钮?

如何处理以设置间隔卸载组件以避免内存泄漏

避免在 React 中由对象字面量引起的重新渲染:如何处理对象中的变量?

Flux - 如何处理多个商店更新相同的视图?