Flicker Free Windows and Drawing
What is it?
Whenever a window is resized using the
Win32 Full Window Drag option the content of the window is redrawn as the window is
stretched or shrunk, or if you have a timer event that updates some portion of your
display using InvalidateRect(..) or similar then chances are that on every update your
window will flicker. The content of the window will flicker if the drawing code draws in
the same place more than once using two different colours. This can mean something as
simple as using FillRect(...) to destroy the previous content of a screen area and using
TextOut(...) to update text. Any window that does this to the window is going to flicker.
How to Cure Flicker in Everyday Life
The first step to removing flicker is to eliminate all sources of flicker that aren't your fault.
Make sure WM_ERASEBKGND is handled
correctly, this probably means doing nothing but setting the return value to 1 because all
of your actual drawing code takes place in your WM_PAINT handler. By having a simple
handler for this message you can eliminate a large portion of the usual flicker associated
with a window.
In dialog boxes that are re-sizeable make sure the style WS_CLIPCHILDREN is added, this will remove child controls from the update
region generated for the dialog box.
If your window scrolls try using ScrollWindow(...) instead of simply updating your internal data and then invalidating the entire window.
Use an off screen bitmap to do your drawing on and then transfer that to your main window.
An Example
Take the following example code from a standard MFC CView derived OnDraw(...)
CRect rcClient;
GetClientRect( rcClient );
pDC->FillSolidRect( rcClient, GetSysColor( COLOR_3DFACE ) );
pDC->TextOut( rcClient.right - 100, rcClient.bottom - 100, _T("hello") );
All it does is fill the client area with a colour and then perform a text out. Using this simple code will flicker like crazy when the window is resized.
To eliminate the flicker from this example simply respond to the message WM_ERASEBKGND and return 1.
If that doesn't work, and it probably will not work for most non-trivial applications you will have to consider drawing to an off
screen bitmap. You must consider the areas you will use an off screen bitmap very
carefully, the size of the bitmap will have a big impact on the speed of your window
update and remember that some people might have 1600x1200x32bit so a bitmap of your
application maximised could be 8mb or larger. The following code added to a CView derived
window class WM_PAINT handler will add off screen drawing to your application reasonably
painlessly:
void CYourView::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect rcPaint = dc.m_ps.rcPaint;
//
// Create a bitmap just big enough to hold the changes, then we
// create a device context to draw on, then we bring the two
// together so we have something to draw on.
CBitmap bmp;
bmp.CreateCompatibleBitmap( &dc, rcPaint.Width(), rcPaint.Height() );
CDC newDC;
newDC.CreateCompatibleDC( &dc );
CBitmap *pOld = newDC.SelectObject( &bmp );
//
// Set the origin of the device context so that our bitmap
// appears where the changes need to be!
newDC.SetViewportOrg( -rcPaint.left, -rcPaint.top );
//
// Now we do the client stuff, this should work for most people
OnPrepareDC( &newDC );
OnDraw( &newDC );
//
// Now that we have some drawn data on our bitmap we can simply copy
// it to the invalid portion of our window and deselect our bitmap
VERIFY( dc.BitBlt( rcPaint.left, rcPaint.top
, rcPaint.Width(), rcPaint.Height()
, &newDC, rcPaint.left, rcPaint.top, SRCCOPY ) );
SelectObject( newDC, pOld );
}