Flicker free drawing using memory DC

.
Download a sample file. The zip file is 17KB.

Flicker Free Updates Using CMemDC

Creating an MS-Windows application is often an absorbing task. Many issues are often postponed until near the end of development. One commonly postponed issue is removing flicker from screen redraws. This short tip explains how to do this using the CMemDC class with MFC. This class isn't a part of MFC, but after you've seen how useful it is you may add it to your MFC toolbox.

Exacerbating the Problem

One of the best ways to exacerbate flickering is to enable the "Show windows contents while dragging" mode found in the Plus! Tab of the Display properties in Windows 95. This mode causes your application to be redrawn while it is resized.

Windows NT also has a similar mode that may be set. If you enable this mode and your application flashes or flicker when resized this tip will probably help.

A Simple Solution

The reason your application is flickering is that the original image is being erased and then redrawn in quick succession when the application is resized. A common technique to reduce or eliminate this problem is to not erase the image, but rather, to draw to an off screen buffer and BitBliting the buffer to the screen. This eliminates the flashing caused by erasing, and the flashing cause by being able to see individual drawing operations on the screen. To disable erasing you need to intercept the WM_ERASEBKGND message and return FALSE.
BOOL CNoFlickerView::OnEraseBkgnd(CDC* pDC) 
{
	return FALSE;
}
To draw to an off screen buffer you need to create a memory CDC, draw to it, and BitBlit the result to the screen. You can do this by using the CMemDC class. This class is very small, just a few lines, but it makes handles most of the off screen drawing issues.
#ifndef _MEMDC_H_
#define _MEMDC_H_

//////////////////////////////////////////////////
// CMemDC - memory DC
//
// Author: Keith Rule
// Email:  keithr@europa.com
// Copyright 1996-1997, Keith Rule
//
// You may freely use or modify this code provided this
// Copyright is included in all derived versions.
//
// History - 10/3/97 Fixed scrolling bug.
//                   Added print support.
//
// This class implements a memory Device Context

class CMemDC : public CDC {
private:
	CBitmap m_bitmap; // Offscreen bitmap
	CBitmap* m_oldBitmap; // bitmap originally found in CMemDC
	CDC* m_pDC; // Saves CDC passed in constructor
	CRect m_rect; // Rectangle of drawing area.
	BOOL m_bMemDC; // TRUE if CDC really is a Memory DC.
public:
	CMemDC(CDC* pDC) : CDC(), m_oldBitmap(NULL), m_pDC(pDC)
	{
		ASSERT(m_pDC != NULL); // If you asserted here, you passed in a NULL CDC.
		
		m_bMemDC = !pDC->IsPrinting();
		
		if (m_bMemDC){
			// Create a Memory DC
			CreateCompatibleDC(pDC);
			pDC->GetClipBox(&m_rect);
			m_bitmap.CreateCompatibleBitmap(pDC, m_rect.Width(), m_rect.Height());
			m_oldBitmap = SelectObject(&m_bitmap);
			SetWindowOrg(m_rect.left, m_rect.top);
		} else {
			// Make a copy of the relevent parts of the current DC for printing
			m_bPrinting = pDC->m_bPrinting;
			m_hDC = pDC->m_hDC;
			m_hAttribDC = pDC->m_hAttribDC;
		}
	}
	
	~CMemDC()
	{
		if (m_bMemDC) {
			// Copy the offscreen bitmap onto the screen.
			m_pDC->BitBlt(m_rect.left, m_rect.top, m_rect.Width(), m_rect.Height(),
				this, m_rect.left, m_rect.top, SRCCOPY);
			//Swap back the original bitmap.
			SelectObject(m_oldBitmap);
		} else {
			// All we need to do is replace the DC with an illegal value,
			// this keeps us from accidently deleting the handles associated with
			// the CDC that was passed to the constructor.
			m_hDC = m_hAttribDC = NULL;
		}
	}
	
	// Allow usage as a pointer
	CMemDC* operator->() {return this;}
	
	// Allow usage as a pointer
	operator CMemDC*() {return this;}
};

#endif
To draw off screen and blit the result onto the screen you simply change the argument name from pDC to dc, and add a CMemDC local variable named pDC.
void CNoFlickerView::OnDraw(CDC* dc)
{
	CRect	rcBounds;
	GetClientRect(&rcBounds);
	CMemDC	pDC(dc, rcBounds);

	pDC->FillRect(rcBounds, CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH)));
	pDC->Ellipse(rcBounds);
}
This updated version of the OnDraw() function in conjunction with overriding the OnEraseBkgnd() function will eliminate the flicker in your application.

Caveats and Such

If you haven't guessed, this solution isn't a cure all. If your application is slow to draw it will still be slow when using this technique. However, for many situations, this simple class and technique will reduce or eliminate flickering with just a couple of minutes if implementation code.