📄 faq34.htm
字号:
<HTML>
<HEAD>
<TITLE>Eliminate flicker when painting on a form or a TPaintBox control.</TITLE>
<META NAME="Author" CONTENT="Harold Howe">
</HEAD>
<BODY BGCOLOR="WHITE">
<CENTER>
<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0 WIDTH="640">
<TR>
<TD>
<H3>
Eliminate flicker when painting on a form or a TPaintBox control.
</H3>
<P>
A lot of programmers complain about flicker when they draw on a <TT>TPaintBox</TT>
control, when they draw on the form's <TT>Canvas</TT>, or when they paint on the
<TT>Canvas</TT> of a control that they have written. One newsgroup post
contained a subject line that read: "TPaintBox wicked flicker, can it be
stopped." The answer is yes, and the solution is simple, once you understand
what is going on.
<UL>
<LI><A HREF="#explanation">Explanation of flicker</A>
<LI><A HREF="#example" >An example of flicker</A>
<LI><A HREF="#tcontrol" >Flicker in TControl and TGraphicControl objects</A>
<LI><A HREF="#tpaintbox" >The wrong way to use TPaintBox or TImage</A>
<LI><A HREF="#remove" >Removing flicker</A>
<LI><A HREF="#notes" >Notes</A>
</UL>
</P>
<H3>
<A NAME="explanation">Explanation of flicker:</A>
</H3>
<P>
Before you eliminate flicker, it's helpful to know what it is. Windows sends
your program <TT>WM_PAINT</TT> messages to notify you that some part of
the screen needs to be repainted. This happens when your program first starts,
when you restore or maximize the program, and when the program is uncovered from
beneath another program. You can also ask Windows to send a <TT>WM_PAINT</TT>
message by calling the <TT>InvalidateRect</TT> API call.
</P>
<P>
Any window that responds to <TT>WM_PAINT</TT> messages must call the <TT>BeginPaint</TT> and
<TT>EndPaint</TT> API functions. These API functions might sound foreign to you if you've
been programming with OWL, the VCL, or MFC because these frameworks call the
functions for you (see <TT>TWinControl::PaintHandler</TT> in <TT>CONTROLS.PAS</TT>).
Among other things, <TT>BeginPaint</TT> will send your window a <TT>WM_ERASEBKGND</TT> if the
window is marked for erasing. The window will almost always be marked for erasing if
Windows sent the <TT>WM_PAINT</TT> message on its own. If the <TT>WM_PAINT</TT> message
was sent because you called <TT>InvalidateRect</TT>, then the window will be marked for
erasing if the last argument to <TT>InvalidateRect</TT> was true.
</P>
<P>
The flicker that you see is caused by the default handler for the <TT>WM_ERASEBKGND</TT>
message. Whenever the <TT>DefWindowProc</TT> receives a <TT>WM_ERASEBKGND</TT> message, it
erases the contents of the window by filling the window with the background color of
the form. The default handler is is equivalent to executing this code:
</P>
<pre>
Canvas<b>-></b>Brush<b>-></b>Color <b>=</b> Color<b>;</b>
Canvas<b>-></b>FillRect<b>(</b>ClientRect<b>)</b><b>;</b>
</pre>
<P>
Realize that this handler executes when your window calls <TT>BeginPaint</TT> after
receiving a <TT>WM_PAINT</TT> message. Your entire window will be erased before
<TT>BeginPaint</TT> returns. Your window will remain erased until you paint over
it. Generally, you paint the window in your <TT>WM_PAINT</TT> handler sometime
after <TT>BeginPaint</TT> has been called. The flicker that you see is caused by
<TT>BeginPaint</TT> erasing the background of your window just before your
<TT>WM_PAINT</TT> handler paints the window. In fact, you can change the color
of the flicker by changing the <TT>Color</TT> property of the form.
</P>
<H3>
<A NAME="example">An example of flicker:</A>
</H3>
<P>
To demonstrate the flicker phenomena, it's beneficial to slow down the message
handling process so the flicker becomes more apparent. The following code
example does just that. Create a new project and and add a button to the main
form (put it near the bottom). Set the form's <TT>Color</TT> property to <TT>clBlue</TT>.
Then modify the header and source files as shown below (create the <TT>OnClick</TT>
and <TT>OnPaint</TT> handler's using the Object Inspector).
</P>
<pre>
<font color="navy">// Header file</font>
<font color="green">#ifndef MAINFORMH</font>
<font color="green">#define MAINFORMH</font>
<font color="navy">//---------------------------</font>
<font color="green">#include <vcl\Classes.hpp></font>
<font color="green">#include <vcl\Controls.hpp></font>
<font color="green">#include <vcl\StdCtrls.hpp></font>
<font color="green">#include <vcl\Forms.hpp></font>
<font color="navy">//---------------------------</font>
<b>class</b> TForm1 <b>:</b> <b>public</b> TForm
<b>{</b>
<b>__published</b><b>:</b> <font color="navy">// IDE-managed Components</font>
TButton <b>*</b>Button1<b>;</b>
<b>void</b> <b>__fastcall</b> Button1Click<b>(</b>TObject <b>*</b>Sender<b>)</b><b>;</b>
<b>void</b> <b>__fastcall</b> FormPaint<b>(</b>TObject <b>*</b>Sender<b>)</b><b>;</b>
<b>private</b><b>:</b> <font color="navy">// User declarations</font>
<b>void</b> <b>__fastcall</b> WMEraseBkgnd<b>(</b>TWMEraseBkgnd <b>&</b>Message<b>)</b><b>;</b>
<b>void</b> <b>__fastcall</b> WMPaint<b>(</b>TWMPaint <b>&</b>Message<b>)</b><b>;</b>
<b>public</b><b>:</b> <font color="navy">// User declarations</font>
<b>__fastcall</b> TForm1<b>(</b>TComponent<b>*</b> Owner<b>)</b><b>;</b>
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER<b>(</b>WM_ERASEBKGND<b>,</b>TWMEraseBkgnd<b>,</b>WMEraseBkgnd<b>)</b>
MESSAGE_HANDLER<b>(</b>WM_PAINT<b>,</b> TWMPaint<b>,</b> WMPaint<b>)</b><b>;</b>
END_MESSAGE_MAP<b>(</b>TForm<b>)</b>
<b>}</b><b>;</b>
<font color="navy">// CPP file</font>
<font color="navy">//-----------------------</font>
<font color="green">#include <vcl\vcl.h></font>
<font color="green">#pragma hdrstop</font>
<font color="green">#include "MAINFORM.h"</font>
<font color="navy">//-----------------------</font>
<font color="green">#pragma resource "*.dfm"</font>
TForm1 <b>*</b>Form1<b>;</b>
<font color="navy">//-----------------------</font>
<b>__fastcall</b> TForm1<b>:</b><b>:</b>TForm1<b>(</b>TComponent<b>*</b> Owner<b>)</b>
<b>:</b> TForm<b>(</b>Owner<b>)</b>
<b>{</b>
<b>}</b>
<font color="navy">//-----------------------------------------</font>
<b>void</b> <b>__fastcall</b> TForm1<b>:</b><b>:</b>WMEraseBkgnd<b>(</b>TWMEraseBkgnd <b>&</b>Message<b>)</b>
<b>{</b>
OutputDebugString <b>(</b><font color="blue">"Inside WM_ERASEBKGND handler"</font><b>)</b><b>;</b>
TForm<b>:</b><b>:</b>Dispatch<b>(</b><b>&</b>Message<b>)</b><b>;</b> <font color="navy">// pass message to default handler</font>
Sleep<b>(</b><font color="blue">5000</font><b>)</b><b>;</b> <font color="navy">// delay so flicker becomes obvious</font>
OutputDebugString <b>(</b><font color="blue">"Leaving WM_ERASEBKGND handler"</font><b>)</b><b>;</b>
<b>}</b>
<font color="navy">//-----------------------------------------</font>
<b>void</b> <b>__fastcall</b> TForm1<b>:</b><b>:</b>WMPaint<b>(</b>TWMPaint <b>&</b>Message<b>)</b>
<b>{</b>
OutputDebugString <b>(</b><font color="blue">"Just received WM_PAINT message. Before BeginPaint"</font><b>)</b><b>;</b>
TForm<b>:</b><b>:</b>Dispatch<b>(</b><b>&</b>Message<b>)</b><b>;</b>
OutputDebugString <b>(</b><font color="blue">"Finished handling WM_PAINT message."</font><b>)</b><b>;</b>
<b>}</b>
<font color="navy">//-----------------------------------------</font>
<b>void</b> <b>__fastcall</b> TForm1<b>:</b><b>:</b>Button1Click<b>(</b>TObject <b>*</b>Sender<b>)</b>
<b>{</b>
OutputDebugString <b>(</b><font color="blue">"About to call invalidate"</font><b>)</b><b>;</b>
<b>:</b><b>:</b>InvalidateRect<b>(</b>Handle<b>,</b>NULL<b>,</b>TRUE<b>)</b><b>;</b>
OutputDebugString <b>(</b><font color="blue">"Just returned from Invalidate call"</font><b>)</b><b>;</b>
<b>}</b>
<font color="navy">//-----------------------------------------</font>
<b>void</b> <b>__fastcall</b> TForm1<b>:</b><b>:</b>FormPaint<b>(</b>TObject <b>*</b>Sender<b>)</b>
<b>{</b>
OutputDebugString <b>(</b><font color="blue">"Just entered form's OnPaint. BeginPaint already called."</font><b>)</b><b>;</b>
Canvas<b>-></b>Brush<b>-></b>Color <b>=</b> clBlack<b>;</b>
Canvas<b>-></b>FillRect<b>(</b>ClientRect<b>)</b><b>;</b>
OutputDebugString <b>(</b><font color="blue">"Leaving OnPaint handler."</font><b>)</b><b>;</b>
<b>}</b>
</pre>
<P>
Run the program. Push the button a few times and observe how the background
is erased before the <TT>OnPaint</TT> handler executes. Close the program and then
look at the OutDbg1.TXT file. Locate the point where the first <TT>InvalidateRect</TT>
call was made. You should see this sequence of events:
</P>
<PRE>
About to call invalidate
Just returned from Invalidate call
Just received WM_PAINT message. Before BeginPaint
Inside WM_ERASEBKGND handler
Leaving WM_ERASEBKGND handler
Just entered form's OnPaint. BeginPaint already called.
Leaving OnPaint handler.
Finished handling WM_PAINT message.
</PRE>
<H3>
<A NAME="tcontrol">Flicker in <TT>TControl</TT> and <TT>TGraphicControl</TT> objects:</A>
</H3>
<P>
The explanation of <TT>WM_ERASEBKGND</TT> only applies to descendents of <TT>TWinControl</TT>
because only <TT>TWinControl</TT> descendents have window handles. <TT>TGraphicControl</TT>
and <TT>TControl</TT> don't have window handles, and as such, they have no concept of
<TT>DefWindowProc</TT>, <TT>BeginPaint</TT>, and <TT>WM_ERASEBKGND</TT>. However,
descendent's of these two classes can still suffer from flicker (note that <TT>TPaintBox</TT>
is derived from <TT>TGraphicControl</TT>). The key lies within the <TT>InvalidateControl</TT>
method of TControl. This function calls <TT>InvalidateRect</TT> for the parent window:
</P>
<PRE>
procedure TControl.InvalidateControl(IsVisible, IsOpaque: Boolean);
begin
...
...
InvalidateRect(Parent.Handle, @Rect, not (IsOpaque or
(csOpaque in Parent.ControlStyle) or BackgroundClipped));
...
end
</PRE>
<P>
<TT>Parent->Handle</TT> is the <TT>HWND</TT> of the form or panel that the control
is placed on. <TT>Rect</TT> is equal to the <TT>BoundsRect</TT> of the control.
The <TT>InvalidateRect</TT> call is telling Windows to invalidate the region of
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -