请问如何解决 paintbox 的闪烁问题(100分)

  • 主题发起人 主题发起人 kmask
  • 开始时间 开始时间
K

kmask

Unregistered / Unconfirmed
GUEST, unregistred user!
请问如何解决 paintbox 的闪烁问题

程序a中用到了一个 paintbox ,在上面画一个滚动的圆,如果程序a 在最前台就
没事,如果 程序a 被其他窗口(比如说:窗口b)遮到的话,把b拿开的时候,如果
程序a 中的paintbox 正在 repaint ,那么paintbox中的图形就会闪烁,并且被
窗口b 遮住的那部分图形就显示不出,知道下次 repaint 为止,请问各位有
解决的办法吗???

如何使 paintbox 在 repaint 的时候,就算被遮住也不会发生闪烁、图形消
失等问题???

万谢
 
在 OnActive 中 repaint
 
如果paintbox放在form上,则form的doublebuffered:=true;
如果是在panel上则panel的doublebufered:=true;
 
doublebuffer也不行,还有没有别的高见??
 
自己处理,遮住部分不画。
 
这种问题我也遇到过,可能与控件的特性有关系。
只有这种在上面才行。
例如,用bitblt,如果源对象被另一个对象(除了目标对象)遮住,就只能拷贝目标对象下面的东西。

好像还没有办法解决。
 
先画到一个Tbitmap里,再把Tbitmap整个画到Canvas上
 
三种方式:请先拷到其它地方阅读
Eliminate flicker when painting on a form or a TPaintBox control.
A lot of programmers complain about flicker when they draw on a TPaintBox control, when they draw on the form's Canvas, or when they paint on the Canvas 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.

Explanation of flicker
An example of flicker
Flicker in TControl and TGraphicControl objects
The wrong way to use TPaintBox or TImage
Removing flicker
Notes

Explanation of flicker:
Before you eliminate flicker, it's helpful to know what it is. Windows sends your program WM_PAINT 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 WM_PAINT message by calling the InvalidateRect API call.

Any window that responds to WM_PAINT messages must call the BeginPaint and EndPaint 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 TWinControl::PaintHandler in CONTROLS.PAS). Among other things, BeginPaint will send your window a WM_ERASEBKGND if the window is marked for erasing. The window will almost always be marked for erasing if Windows sent the WM_PAINT message on its own. If the WM_PAINT message was sent because you called InvalidateRect, then the window will be marked for erasing if the last argument to InvalidateRect was true.

The flicker that you see is caused by the default handler for the WM_ERASEBKGND message. Whenever the DefWindowProc receives a WM_ERASEBKGND 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:

Canvas->Brush->Color = Color;
Canvas->FillRect(ClientRect);

Realize that this handler executes when your window calls BeginPaint after receiving a WM_PAINT message. Your entire window will be erased before BeginPaint returns. Your window will remain erased until you paint over it. Generally, you paint the window in your WM_PAINT handler sometime after BeginPaint has been called. The flicker that you see is caused by BeginPaint erasing the background of your window just before your WM_PAINT handler paints the window. In fact, you can change the color of the flicker by changing the Color property of the form.

An example of flicker:
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 Color property to clBlue. Then modify the header and source files as shown below (create the OnClick and OnPaint handler's using the Object Inspector).

// Header file
#ifndef MAINFORMH
#define MAINFORMH
//---------------------------
#include <vcl/Classes.hpp>
#include <vcl/Controls.hpp>
#include <vcl/StdCtrls.hpp>
#include <vcl/Forms.hpp>
//---------------------------

class TForm1 : public TForm
{
__published: // IDE-managed Components
TButton *Button1;
void __fastcall Button1Click(TObject *Sender);
void __fastcall FormPaint(TObject *Sender);
private: // User declarations
void __fastcall WMEraseBkgnd(TWMEraseBkgnd &amp;Message);
void __fastcall WMPaint(TWMPaint &amp;Message);
public: // User declarations
__fastcall TForm1(TComponent* Owner);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_ERASEBKGND,TWMEraseBkgnd,WMEraseBkgnd)
MESSAGE_HANDLER(WM_PAINT, TWMPaint, WMPaint);
END_MESSAGE_MAP(TForm)
};


// CPP file
//-----------------------
#include <vcl/vcl.h>
#pragma hdrstop
#include "MAINFORM.h"
//-----------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//-----------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//-----------------------------------------
void __fastcall TForm1::WMEraseBkgnd(TWMEraseBkgnd &amp;Message)
{
OutputDebugString ("Inside WM_ERASEBKGND handler");
TForm::Dispatch(&amp;Message); // pass message to default handler
Sleep(5000); // delay so flicker becomes obvious
OutputDebugString ("Leaving WM_ERASEBKGND handler");
}
//-----------------------------------------
void __fastcall TForm1::WMPaint(TWMPaint &amp;Message)
{
OutputDebugString ("Just received WM_PAINT message. Before BeginPaint");
TForm::Dispatch(&amp;Message);
OutputDebugString ("Finished handling WM_PAINT message.");
}
//-----------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
OutputDebugString ("About to call invalidate");
::InvalidateRect(Handle,NULL,TRUE);
OutputDebugString ("Just returned from Invalidate call");
}
//-----------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
OutputDebugString ("Just entered form's OnPaint. BeginPaint already called.");
Canvas->Brush->Color = clBlack;
Canvas->FillRect(ClientRect);
OutputDebugString ("Leaving OnPaint handler.");
}

Run the program. Push the button a few times and observe how the background is erased before the OnPaint handler executes. Close the program and then look at the OutDbg1.TXT file. Locate the point where the first InvalidateRect call was made. You should see this sequence of events:

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.

Flicker in TControl and TGraphicControl objects:
The explanation of WM_ERASEBKGND only applies to descendents of TWinControl because only TWinControl descendents have window handles. TGraphicControl and TControl don't have window handles, and as such, they have no concept of DefWindowProc, BeginPaint, and WM_ERASEBKGND. However, descendent's of these two classes can still suffer from flicker (note that TPaintBox is derived from TGraphicControl). The key lies within the InvalidateControl method of TControl. This function calls InvalidateRect for the parent window:


procedure TControl.InvalidateControl(IsVisible, IsOpaque: Boolean);
begin
...
...
InvalidateRect(Parent.Handle, @Rect, not (IsOpaque or
(csOpaque in Parent.ControlStyle) or BackgroundClipped));

...
end

Parent->Handle is the HWND of the form or panel that the control is placed on. Rect is equal to the BoundsRect of the control. The InvalidateRect call is telling Windows to invalidate the region of the parent window where the control is located. Notice that since InvalidateRect is being called, the same WM_PAINT ==> BeginPaint ==> WM_ERASEBKGND sequence of events looms on the horizon. The last argument to InvalidateRect controls whether BeginPaint will send the WM_ERASEBKGND message. IsOpaque will be true if the control itself has the csOpaque control style. csOpaque 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 csOpaque is not set, InvalidRect is instructed to erase the background region of the control, which causes flicker.

The wrong way to use TPaintBox or TImage:
Now you know that flicker is caused by the WM_ERASEBKGND 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 PaintBox control. This code uses a PaintBox 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 TrackBar). If the water level has changed, the program updates the water level display in the PaintBox.

The program contains an OnPaint handler for the PaintBox control that completely paints the contents of the PaintBox based on the current water level. The timer event calls the VCL Repaint method when the water level changes.

//-----------------------------------------------------------------------
// To compile this code, place a TrackBar, a Timer, and a PaintBox control
// onto a form. The PaintBox control should be 200 pixels high. The TrackBar
// should range from -200 to 0 and should be a vertical TrackBar. Set the
// Timer interval to 10. Then create an OnTimer event and an OnPaint handler
// for the PaintBox control. Add an int member to the form class called
// WaterLevel.
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
// Note: The TrackBar ranges from -200 at the top to 0 at the bottom. The
// new water level is the trackbar position multiplied by -1. The -1
// inverts -200 into a positive 100. This way, the water levels
// range from 0 to 200.
int NewWaterLevel = TrackBar1->Position * -1;
if (NewWaterLevel != WaterLevel) // has the water level changed
{ // if so, store the new value
WaterLevel = NewWaterLevel; // and Repaint the control
PaintBox1->Repaint();
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::PaintBox1Paint(TObject *Sender)
{
// Competely repaint the PaintBox to display the correct water level
// Use a memory bitmap to do off screen drawing. Then draw the bitmap
// to the Canvas of the PaintBox.
//
Graphics::TBitmap *MemBitmap = new Graphics::TBitmap;
MemBitmap->Width = PaintBox1->Width;
MemBitmap->Height = PaintBox1->Height;

// Fill in the water tank with white.
MemBitmap->Canvas->Brush->Color = clWhite;
TRect rect = PaintBox1->ClientRect;
MemBitmap->Canvas->FillRect(rect);

// Now find the pixel that represents the water level. This code
// assumes that the paintbox is 200 pixels high, and that the max
// water level is 200. Your water level meter should be more robust.
// Fill in the water portion of the tank with blue.
rect.Top += 200 - (WaterLevel);
MemBitmap->Canvas->Brush->Color = clBlue;
MemBitmap->Canvas->FillRect(rect);

// Copy off screen image to the canvas
PaintBox1->Canvas->Draw(0,0,MemBitmap);
delete MemBitmap;
}

Removing the flicker:
Many programmers like to place all of their painting code in one location. In the previous example, the painting code resides in an OnPaint handler. Unfortunately, the PaintBox flickers whenever the water level changes because the Repaint call results in a call to the API InvalidateRect function. You could replace Repaint with Refresh or with a combination of Invalidate and Update, but the flicker would persist. (Note that early versions of the VCL help file claimed that Repaint and Refresh served different purposes. This is not true. Refresh simply calls Repaint, so the two are the same. Borland has updated the help files to reflect this).

There are several ways to eliminate the flicker in the PaintBox control.

Add a WM_ERASEBKND handler and block the erase action
Add csOpaque to the ControlStyle of the PaintBox.
Don't call Repaint, Refresh, or Invalidate.
Since the PaintBox is erased via a WM_ERASEBKGND message that is sent to its parent (in this case, the main form), you can intercept the WM_ERASEBKGND message. You can prevent flicker by not passing this message on for default processing. Unfortunately, trapping WM_ERASEBKGND might impact the appearance of other controls on the form.

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.

__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
WaterLevel = 0;
PaintBox1->ControlStyle = PaintBox1->ControlStyle << csOpaque;
}

Of the three choices listed, I prefer the last. The PaintBox control provides a Canvas 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 OnPaint handler. Whenever you need to update the screen, just do it, plain and simple. Don't ask Repaint to do it for you. By avoiding Repaint and its evil cohorts, you bypass the WM_PAINT ==> BeginPaint ==> WM_ERASEBKND 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.

void TForm1::PaintWaterLevel(void)
{
Graphics::TBitmap *MemBitmap = new Graphics::TBitmap;
MemBitmap->Width = PaintBox1->Width;
MemBitmap->Height = PaintBox1->Height;

MemBitmap->Canvas->Brush->Color = clWhite;
TRect rect = PaintBox1->ClientRect;
MemBitmap->Canvas->FillRect(rect);

rect.Top += 200 - (WaterLevel);
MemBitmap->Canvas->Brush->Color = clBlue;
MemBitmap->Canvas->FillRect(rect);

PaintBox1->Canvas->Draw(0,0,MemBitmap);
delete MemBitmap;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
int NewWaterLevel = TrackBar1->Position * -1;
if (NewWaterLevel != WaterLevel) // has the water level changed
{ // if so, store the new value
WaterLevel = NewWaterLevel; // and Repaint the control
PaintWaterLevel();
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::PaintBox1Paint(TObject *Sender)
{
PaintWaterLevel();
}

For this strategy to work, the PaintWaterLevel function must paint the entire client area of the PaintBox control. This is accomplished by creating an offscreen bitmap that matches the size of the PaintBox. The function first paints the background of the offscreen bitmap, and then paints the water level. Notice that you still need an OnPaint handler that will repaint the water level when the main window is uncovered from beneath another window on the desktop.

Note: If you are designing a component that is derived from TGraphicControl or TCustomControl, you can put your painting code inside the virtual Paint method of the class. The VCL calls Paint to allow the control to redraw itself. However, you can call Paint from your own code to update the control's appearance on the screen.

Note: The implementation of the PaintWaterLevel 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 WM_PAINT ==> BeginPaint ==> WM_ERASEBKGND sequence of events. Normally, this flicker isn't noticed by users, but if it is a problem in your program, consider using the csOpaque strategy.

 
接受答案了.
 
后退
顶部