如何处理WM_ERASEBKGND以避免闪烁?

时间:2013-11-05 17:12:16

标签: delphi winapi delphi-7 gdi flicker

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

TMyProgressBar = class(TCustomControl)

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

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

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代码中加入什么?什么是正确的方法?

3 个答案:

答案 0 :(得分:7)

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

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

尽管如此,我还是回归'1',从概念上暗示已经完成了擦除工作。

答案 1 :(得分:4)

当背景未被(完全)擦除时,您应该返回0,并且当要考虑擦除背景时,您应该返回另一个值,然后返回0。这是你必须遵守的惯例。

更重要的是不要在此消息处理程序 1)中调用inherited,这将调用继承的消息处理程序,并最终调用默认的Windows过程,该过程将使用在创建时给予窗口的画笔,如果有的话。

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

请记住,在这种情况下“擦除”也意味着“画画”,但这超出了我的想法。


1) Inherited与虚拟方法相比,消息处理程序的工作方式略有不同。虽然它的含义相同 - 继承链中的第一个处理程序将被调用 - 其声明中没有override指令,并且无法添加方法名称。

答案 2 :(得分:2)

WM_ERASEBKND的返回值只是确定在后续fErase处理程序中调用PAINTSTRUCTBeginPaint WM_PAINT成员的初始化方式。如果您的绘制处理程序忽略该成员,那么WM_ERASEBKND返回的内容并不重要。

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