9

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

TMyProgressBar = class(TCustomControl)

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

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

这个无闪烁的绘图建议中,它被写入以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为相同的消息。

现在Windows 文档指出:

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

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

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

4

3 回答 3

8

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

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

尽管如此,我还是会返回“1”,从概念上暗示已经处理了擦除。

于 2013-11-05T20:02:53.757 回答
4

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

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

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

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


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

于 2013-11-05T21:06:02.117 回答
2

just的返回值WM_ERASEBKND决定了在后续处理程序中调用时如何初始化fErase成员。如果您的绘图处理程序忽略该成员,那么返回什么并不重要。PAINTSTRUCTBeginPaintWM_PAINTWM_ERASEBKND

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

于 2013-11-06T00:09:19.507 回答