我在表单上有一些自定义进度条,每秒更新/刷新两次并且它们闪烁。
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代码中加入什么?什么是正确的方法?
答案 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
处理程序中调用PAINTSTRUCT
时BeginPaint
WM_PAINT
成员的初始化方式。如果您的绘制处理程序忽略该成员,那么WM_ERASEBKND
返回的内容并不重要。
通过绘画一次而不是两次来避免闪烁。如果您使用WM_ERASEBKGND
中的颜色填充该区域,然后稍后在WM_PAINT
中对其进行blit,则会出现闪烁现象。如果你在WM_ERASEBKGND
中没有画画而只是WM_PAINT
中的blit,你就不会闪烁。唯一的技巧是使你的blit覆盖整个无效区域并初始化像素。