⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 faq34.htm

📁 C++builder学习资料C++builder
💻 HTM
📖 第 1 页 / 共 2 页
字号:
the parent window where the control is located. Notice that since <TT>InvalidateRect</TT>

is being called, the same WM_PAINT ==> BeginPaint ==> WM_ERASEBKGND sequence of

events looms on the horizon. The last argument to <TT>InvalidateRect</TT> controls

whether <TT>BeginPaint</TT> will send the <TT>WM_ERASEBKGND</TT> message. <TT>IsOpaque</TT>

will be true if the control itself has the <TT>csOpaque</TT> control style. <TT>csOpaque</TT>

means that a control completely paints its client area. By default, controls do not

contain this style. The logic above tells the Windows not to erase the region if

either the form or the control completely paint themselves. Since <TT>csOpaque</TT>

is not set, <TT>InvalidRect</TT> is instructed to erase the background region of

the control, which causes flicker.

</P>

<H3>

<A NAME="tpaintbox">The wrong way to use <TT>TPaintBox</TT> or <TT>TImage</TT>:

</H3>

<P>

Now you know that flicker is caused by the <TT>WM_ERASEBKGND</TT> message.

However, it may be difficult to track down why this message is being sent to

your program. Imagine that you have a program that needs to frequently update

the display. The following code example shows one way of updating the screen via

a <TT>PaintBox</TT> control. This code uses a <TT>PaintBox</TT> control to show

the water level in an imaginary storage tank. The program has a timer that reads

the water level (in a real system, it would read an I/O port, but in our test

program it reads the value from a <TT>TrackBar</TT>). If the water level has

changed, the program updates the water level display in the <TT>PaintBox</TT>.

</P>

<P>

The program contains an <TT>OnPaint</TT> handler for the <TT>PaintBox</TT> control

that completely paints the contents of the <TT>PaintBox</TT> based on the current

water level. The timer event calls the VCL <TT>Repaint</TT> method when the

water level changes.

</P>

<pre>

<font color="navy">//-----------------------------------------------------------------------</font>

<font color="navy">// To compile this code, place a TrackBar, a Timer, and a PaintBox control</font>

<font color="navy">// onto a form. The PaintBox control should be 200 pixels high. The TrackBar</font>

<font color="navy">// should range from -200 to 0 and should be a vertical TrackBar. Set the</font>

<font color="navy">// Timer interval to 10. Then create an OnTimer event and an OnPaint handler</font>

<font color="navy">// for the PaintBox control. Add an int member to the form class called</font>

<font color="navy">// WaterLevel.</font>

<b>void</b> <b>__fastcall</b> TForm1<b>:</b><b>:</b>Timer1Timer<b>(</b>TObject <b>*</b>Sender<b>)</b>

<b>{</b>

    <font color="navy">// Note: The TrackBar ranges from -200 at the top to 0 at the bottom. The</font>

    <font color="navy">//       new water level is the trackbar position multiplied by -1. The -1</font>

    <font color="navy">//       inverts -200 into a positive 100. This way, the water levels</font>

    <font color="navy">//       range from 0 to 200.</font>

    <b>int</b> NewWaterLevel <b>=</b> TrackBar1<b>-></b>Position <b>*</b> <b>-</b><font color="blue">1</font><b>;</b>

    <b>if</b> <b>(</b>NewWaterLevel <b>!=</b> WaterLevel<b>)</b>              <font color="navy">// has the water level changed</font>

    <b>{</b>                                             <font color="navy">// if so, store the new value</font>

        WaterLevel <b>=</b> NewWaterLevel<b>;</b>               <font color="navy">// and Repaint the control</font>

        PaintBox1<b>-></b>Repaint<b>(</b><b>)</b><b>;</b>

    <b>}</b>

<b>}</b>

<font color="navy">//---------------------------------------------------------------------------</font>

<b>void</b> <b>__fastcall</b> TForm1<b>:</b><b>:</b>PaintBox1Paint<b>(</b>TObject <b>*</b>Sender<b>)</b>

<b>{</b>

    <font color="navy">// Competely repaint the PaintBox to display the correct water level</font>

    <font color="navy">// Use a memory bitmap to do off screen drawing. Then draw the bitmap</font>

    <font color="navy">// to the Canvas of the PaintBox.</font>

    <font color="navy">//</font>

    Graphics<b>:</b><b>:</b>TBitmap <b>*</b>MemBitmap <b>=</b> <b>new</b> Graphics<b>:</b><b>:</b>TBitmap<b>;</b>

    MemBitmap<b>-></b>Width  <b>=</b> PaintBox1<b>-></b>Width<b>;</b>

    MemBitmap<b>-></b>Height <b>=</b> PaintBox1<b>-></b>Height<b>;</b>



    <font color="navy">// Fill in the water tank with white.</font>

    MemBitmap<b>-></b>Canvas<b>-></b>Brush<b>-></b>Color <b>=</b> clWhite<b>;</b>

    TRect rect <b>=</b> PaintBox1<b>-></b>ClientRect<b>;</b>

    MemBitmap<b>-></b>Canvas<b>-></b>FillRect<b>(</b>rect<b>)</b><b>;</b>



    <font color="navy">// Now find the pixel that represents the water level. This code</font>

    <font color="navy">// assumes that the paintbox is 200 pixels high, and that the max</font>

    <font color="navy">// water level is 200. Your water level meter should be more robust.</font>

    <font color="navy">// Fill in the water portion of the tank with blue.</font>

    rect<b>.</b>Top <b>+</b><b>=</b> <font color="blue">200</font> <b>-</b> <b>(</b>WaterLevel<b>)</b><b>;</b>

    MemBitmap<b>-></b>Canvas<b>-></b>Brush<b>-></b>Color <b>=</b> clBlue<b>;</b>

    MemBitmap<b>-></b>Canvas<b>-></b>FillRect<b>(</b>rect<b>)</b><b>;</b>



    <font color="navy">// Copy off screen image to the canvas</font>

    PaintBox1<b>-></b>Canvas<b>-></b>Draw<b>(</b><font color="blue">0</font><b>,</b><font color="blue">0</font><b>,</b>MemBitmap<b>)</b><b>;</b>

    <b>delete</b> MemBitmap<b>;</b>

<b>}</b>

</pre>

<H3>

<A NAME="remove">Removing the flicker:</A>

</H3>

<P>

Many programmers like to place all of their painting code in one location. In

the previous example, the painting code resides in an <TT>OnPaint</TT> handler.

Unfortunately, the <TT>PaintBox</TT> flickers whenever the water level changes

because the <TT>Repaint</TT> call results in a call to the API <TT>InvalidateRect</TT>

function. You could replace <TT>Repaint</TT> with <TT>Refresh</TT> or with a

combination of <TT>Invalidate</TT> and <TT>Update</TT>, but the flicker would

persist. (Note that early versions of the VCL help file claimed that <TT>Repaint</TT>

and <TT>Refresh</TT> served different purposes. This is not true. <TT>Refresh</TT>

simply calls <TT>Repaint</TT>, so the two are the same. Borland has updated the

help files to reflect this).

</P>

<P>

There are several ways to eliminate the flicker in the <TT>PaintBox</TT> control.

<UL>

<LI> Add a <TT>WM_ERASEBKND</TT> handler and block the erase action

<LI> Add <TT>csOpaque</TT> to the <TT>ControlStyle</TT> of the <TT>PaintBox</TT>.

<LI> Don't call <TT>Repaint</TT>, <TT>Refresh</TT>, or <TT>Invalidate</TT>.

</UL>

Since the <TT>PaintBox</TT> is erased via a <TT>WM_ERASEBKGND</TT> message that

is sent to its parent (in this case, the main form), you can intercept the

<TT>WM_ERASEBKGND</TT> message. You can prevent flicker by not passing this

message on for default processing. Unfortunately, trapping <TT>WM_ERASEBKGND</TT>

might impact the appearance of other controls on the form.

</P>

<P>

To use the csOpaqe method, add this line of code to the form's constructor. Do

this for all controls that need to be painted frequently.

</P>

<pre>

<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>

    WaterLevel <b>=</b> <font color="blue">0</font><b>;</b>

    PaintBox1<b>-></b>ControlStyle <b>=</b> PaintBox1<b>-></b>ControlStyle <b><<</b> csOpaque<b>;</b>

<b>}</b>

</pre>

<P>

Of the three choices listed, I prefer the last. The <TT>PaintBox</TT> control

provides a <TT>Canvas</TT> property that allows you to paint on the control

whenever you feel like it. There is no reason why you have to put all of your

painting code inside of an <TT>OnPaint</TT> handler. Whenever you need to update

the screen, just do it, plain and simple. Don't ask <TT>Repaint</TT> to do it

for you. By avoiding <TT>Repaint</TT> and its evil cohorts, you bypass the

<TT>WM_PAINT ==> BeginPaint ==> WM_ERASEBKND</TT> chain of events

that causes the flicker in the first place. Additionally, you significantly

reduce the amount of code that executes. Here is a modified version of the water

level program that avoids flicker by moving the painting code into a reusable

function.

</P>

<pre>

<b>void</b> TForm1<b>:</b><b>:</b>PaintWaterLevel<b>(</b><b>void</b><b>)</b>

<b>{</b>

    Graphics<b>:</b><b>:</b>TBitmap <b>*</b>MemBitmap <b>=</b> <b>new</b> Graphics<b>:</b><b>:</b>TBitmap<b>;</b>

    MemBitmap<b>-></b>Width  <b>=</b> PaintBox1<b>-></b>Width<b>;</b>

    MemBitmap<b>-></b>Height <b>=</b> PaintBox1<b>-></b>Height<b>;</b>



    MemBitmap<b>-></b>Canvas<b>-></b>Brush<b>-></b>Color <b>=</b> clWhite<b>;</b>

    TRect rect <b>=</b> PaintBox1<b>-></b>ClientRect<b>;</b>

    MemBitmap<b>-></b>Canvas<b>-></b>FillRect<b>(</b>rect<b>)</b><b>;</b>



    rect<b>.</b>Top <b>+</b><b>=</b> <font color="blue">200</font> <b>-</b> <b>(</b>WaterLevel<b>)</b><b>;</b>

    MemBitmap<b>-></b>Canvas<b>-></b>Brush<b>-></b>Color <b>=</b> clBlue<b>;</b>

    MemBitmap<b>-></b>Canvas<b>-></b>FillRect<b>(</b>rect<b>)</b><b>;</b>



    PaintBox1<b>-></b>Canvas<b>-></b>Draw<b>(</b><font color="blue">0</font><b>,</b><font color="blue">0</font><b>,</b>MemBitmap<b>)</b><b>;</b>

    <b>delete</b> MemBitmap<b>;</b>

<b>}</b>

<font color="navy">//---------------------------------------------------------------------------</font>

<b>void</b> <b>__fastcall</b> TForm1<b>:</b><b>:</b>Timer1Timer<b>(</b>TObject <b>*</b>Sender<b>)</b>

<b>{</b>

    <b>int</b> NewWaterLevel <b>=</b> TrackBar1<b>-></b>Position <b>*</b> <b>-</b><font color="blue">1</font><b>;</b>

    <b>if</b> <b>(</b>NewWaterLevel <b>!=</b> WaterLevel<b>)</b>              <font color="navy">// has the water level changed</font>

    <b>{</b>                                             <font color="navy">// if so, store the new value</font>

        WaterLevel <b>=</b> NewWaterLevel<b>;</b>               <font color="navy">// and Repaint the control</font>

        PaintWaterLevel<b>(</b><b>)</b><b>;</b>

    <b>}</b>

<b>}</b>

<font color="navy">//---------------------------------------------------------------------------</font>

<b>void</b> <b>__fastcall</b> TForm1<b>:</b><b>:</b>PaintBox1Paint<b>(</b>TObject <b>*</b>Sender<b>)</b>

<b>{</b>

    PaintWaterLevel<b>(</b><b>)</b><b>;</b>

<b>}</b>

</pre>

<P>

For this strategy to work, the <TT>PaintWaterLevel</TT> function must paint the

entire client area of the <TT>PaintBox</TT> control. This is accomplished by

creating an offscreen bitmap that matches the size of the <TT>PaintBox</TT>.

The function first paints the background of the offscreen bitmap, and then

paints the water level. Notice that you still need an <TT>OnPaint</TT> handler

that will repaint the water level when the main window is uncovered from beneath

another window on the desktop.

</P>

<P>

<A NAME="notes"></A>

<B>Note:</B> If you are designing a component that is derived from

<TT>TGraphicControl</TT> or <TT>TCustomControl</TT>, you can put your painting

code inside the virtual <TT>Paint</TT> method of the class. The VCL calls

<TT>Paint</TT> to allow the control to redraw itself. However, you can call

<TT>Paint</TT> from your own code to update the control's appearance on the

screen.

</P>

<P>

<B>Note:</B> The implementation of the <TT>PaintWaterLevel</TT> function eliminates

flicker caused by updates from code. It does not remove flicker that is caused

when the program is uncovered from beneath another window, because this action

still causes the <TT>WM_PAINT ==> BeginPaint ==> WM_ERASEBKGND</TT> sequence of events.

Normally, this flicker isn't noticed by users, but if it is a problem in your

program, consider using the <TT>csOpaque</TT> strategy.

</P>





</TD> </TR>



</TABLE>

</CENTER>

</BODY>

</HTML>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -