内存DC
在使用vc开发图形相关的应用程序时,常常需要使用MFC的CDC类直接把图形画在窗口上。这通常是通过响应windows的WM_PAINT消息实现的。如果要画的图形比较复杂,或者比较大,那么画图过程可能会造成窗口的闪烁。当窗口调整大小时,这种闪烁由为明显。 解决窗口闪烁问题的有效办法就是使用内存DC,也称为缓冲DC。在内存中准备一个和窗口DC相同属性的DC,在这个内存DC上执行画图操作。完成画图以后,把画图输出的内容整体复制到目标窗口DC上。因为画图操作不在窗口DC上进行,所以在画图的过程中窗口可以保持原来的内容。当画好的内容被复制到窗口DC时,因为复制操作执行的非常快,所以用户感觉窗口仿佛被立刻被画好,从而消除了从旧画面到白板再到新画面的闪烁现象。 生成内存DC主要用到以下四个函数: CreateCompatibleDC(CDC* pDC )。CDC类的成员函数,用于创建一个和pDC指向的DC兼容的内存DC。 CreateDiscardableBITmap( CDC* pDC, int nWidth, int nHeight)。CBitmap类的成员函数,用于按指定尺寸创建一个和pDC指向的DC兼容的位图。 SelectObject(CBitmap * pBitmap)。CDC类的成员函数,执行以后,所以在该DC上的图像输出都将被画到pBitmap指向的位图上。 BOOL BitBlt (int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop )。CDC类的成员函数,用于从源DC(pSrcDC)复制一个矩形的图象到当前DC中。 对于一个窗口,我们可以用下面的代码来创建内存DC,在内存DC上输出,并最终复制到窗口DC上。
void PaintWnd(CWnd *
pWnd) ...
{ CDC * pWndDC = pWnd -> GetWindowDC(); CRect WndRect = pWnd -> GetWindowRect(); CDC MemDC; CBitMap MemBitmap; MemDC.CreateCompatibleDC(pWndDC); //
利用屏幕DC
创建内存DC MemBitmap.CreateCompatibleBitmap( //
利用屏幕DC
创建兼容的位图 pWndDC, WndRect.Width(), WndRect.Height()); MemDC.SelectObject(&MemBitmap); // 让内存DC输出到位图 // 使用MemDC画图 // 。。。。。。 pWndDC -> BitBlt( // 从内存DC复制到窗口DC 0 , 0 , WndRect.Width(), WndRect.Height(), & MemDC, 0 , 0 , SRCCOPY); }
当然,实际的情况下,我们需要考虑的更多,因为内存DC、位图的创建都可能会失败。为了简化代码,笔者定义了一个类CMemoryDC,包装了内存DC创建过程中的出错处理,内存DC的事后清理等操作,并自动复制内存DC的内容到目标DC上。
声明CMemoryDC类的头文件MemoryDC.h如下:
#pragma once
#include
" Afxwin.h "
class
CMemoryDC ...
{ public : CMemoryDC(CDC * dc, RECT * rect, bool autoRender = false ); ~ CMemoryDC( void ); bool IsOK(); void Render(CDC * p_objectDC = NULL); CDC * GetMemoryDC(); operator CDC * (); private : bool m_bAutoRender; CRect m_DCRect; CDC * m_pOriginalDC; CDC m_MemoryDC; CBitmap m_MemoryBmp; }
;
类的实现文件CMemoryDC.cpp如下:
#include " .MemoryDC.h "
CMemoryDC::CMemoryDC(CDC
* dc, RECT * rect, bool
autoRender) ...
{ m_bAutoRender = autoRender; m_pOriginalDC = dc; if (dc == NULL || rect == NULL) return ; if ( ! m_MemoryDC.CreateCompatibleDC(dc)) return ; m_DCRect.SetRect(rect -> left, rect -> top, rect -> right, rect -> bottom); if ( ! m_MemoryBmp.CreateCompatibleBitmap(dc, m_DCRect.Width(), m_DCRect.Height())) return ; m_MemoryDC.SelectObject(m_MemoryBmp); }
CMemoryDC::
~ CMemoryDC( void
) ...
{ if (m_bAutoRender) Render(); if (m_MemoryDC.m_hDC != NULL) m_MemoryDC.DeleteDC(); if (m_MemoryBmp.m_hObject != NULL) m_MemoryBmp.DeleteObject(); }
bool
CMemoryDC::IsOK() ...
{ return m_MemoryDC.m_hDC != NULL && m_MemoryBmp.m_hObject != NULL; }
void CMemoryDC::Render(CDC *
p_objectDC) ...
{ if ( ! IsOK()) return ; CDC * pDC = (p_objectDC == NULL ? m_pOriginalDC : p_objectDC); CSize Size = m_MemoryDC.GetViewportExt() ; pDC -> BitBlt( m_DCRect.left, m_DCRect.top, m_DCRect.Width(), m_DCRect.Height(), & m_MemoryDC, 0 , 0 , SRCCOPY); }
CDC
*
CMemoryDC::GetMemoryDC() ...
{ return & m_MemoryDC; }
CMemoryDC::
operator CDC *
() ...
{ return & m_MemoryDC; }
使用这个类可以大大简化内存DC的创建操作。如果我们在窗口消息WM_PAINT的响应函数中使用内存DC,只要用如下这样简便的代码便可实现:
CRect Rect; GetClientRect(Rect); CPaintDC dc( this ); // device context for painting CMemoryDC MemDC( & dc, Rect, true ); if (MemDC.IsOK()) ... { // 使用MemDC画窗口 } // MemDC析构时会自动把图像复制到dc,无需其它操作
使用CMemoryDC创建内存DC防止窗口闪烁,编程的代码和不使用内存DC时相比,数量和复杂性几乎没有增加。