双缓冲绘图和窗口控件的绘制
---ATL ActiveX 窗口控件生成向导绘制代码OnDraw的一个错误
cheungmine
我们通常使用ATL COM组件,生成一个带窗口的ActiveX控件,然后希望在这个窗口中绘制我们的图像、图形等数据,然而ATL向导生成的代码中包含很多错误,下面是其自动向导生成的代码:
HRESULT OnDraw(ATL_DRAWINFO& di)
{ RECT& rc = *(RECT*)di.prcBounds; // 将剪辑区域设置为 di.prcBounds 指定的矩形 HRGN hRgnOld = NULL; if (GetClipRgn(di.hdcDraw, hRgnOld) != 1) hRgnOld = NULL; bool bSelectOldRgn = false; HRGN hRgnNew = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom); if (hRgnNew != NULL) { bSelectOldRgn = (SelectClipRgn(di.hdcDraw, hRgnNew) != ERROR); } Rectangle(di.hdcDraw, rc.left, rc.top, rc.right, rc.bottom); SetTextAlign(di.hdcDraw, TA_CENTER|TA_BASELINE); LPCTSTR pszText = _T("ATL 8.0 : Canvas");#ifndef _WIN32_WCE TextOut(di.hdcDraw, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2, pszText, lstrlen(pszText));#else ExtTextOut(di.hdcDraw, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2, ETO_OPAQUE, NULL, pszText, ATL::lstrlen(pszText), NULL);#endif if (bSelectOldRgn) SelectClipRgn(di.hdcDraw, hRgnOld); return S_OK; }
请注意这里面包含一个错误,改正之后的代码(红色字体):
HRESULT OnDraw(ATL_DRAWINFO& di)
{ RECT& rc = *(RECT*)di.prcBounds; // 将剪辑区域设置为 di.prcBounds 指定的矩形 HRGN hRgnOld = NULL; if (GetClipRgn(di.hdcDraw, hRgnOld) != 1) hRgnOld = NULL; bool bSelectOldRgn = false; HRGN hRgnNew = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom); if (hRgnNew != NULL) { bSelectOldRgn = (SelectClipRgn(di.hdcDraw, hRgnNew) != ERROR); } Rectangle(di.hdcDraw, rc.left, rc.top, rc.right, rc.bottom); SetTextAlign(di.hdcDraw, TA_CENTER|TA_BASELINE); LPCTSTR pszText = _T("ATL 8.0 : Canvas");#ifndef _WIN32_WCE TextOut(di.hdcDraw, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2, pszText, lstrlen(pszText));#else ExtTextOut(di.hdcDraw, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2, ETO_OPAQUE, NULL, pszText, ATL::lstrlen(pszText), NULL);#endif if (bSelectOldRgn) SelectClipRgn(di.hdcDraw, hRgnOld);
// 删除剪辑区域 ::DeleteObject(hRgnNew); // Add by cheungmine. MUST!!
return S_OK;
}
注意其中绿色的代码,你应该完全注释掉这种绘制的逻辑,而采用双缓冲。因此,ATL自动生成的OnDraw代码是不适合实际的绘图控件的。下面的代码是我更改之后的,增加了双缓冲机制:
void MyDrawCode (HDC hdc, RECT &rc)
{
Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom); SetTextAlign(di.hdcDraw, TA_CENTER|TA_BASELINE); LPCTSTR pszText = _T("ATL 8.0 : Canvas");#ifndef _WIN32_WCE TextOut(hdc, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2, pszText, lstrlen(pszText));#else ExtTextOut(hdc, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2, ETO_OPAQUE, NULL, pszText, ATL::lstrlen(pszText), NULL);#endif
}
void DbBufferDraw(HDC hdcDraw, RECT &rcClip) { HDC hMemDC = ::CreateCompatibleDC(hdcDraw); ATLASSERT(hMemDC); HBITMAP hBmpNew = ::CreateCompatibleBitmap(hdcDraw, WidthRect(rcClip), HeightRect(rcClip)); ATLASSERT(hBmpNew); HBITMAP hBmpOld = (HBITMAP) ::SelectObject(hMemDC, hBmpNew);
// 添加自己的绘制代码
MyDrawCode(hMemDC, rcClip); if (IsWindow()) { ::BitBlt ( hdcDraw, rcClip.left, rcClip.top, WidthRect(rcClip), HeightRect(rcClip), hMemDC, rcClip.left, rcClip.top, SRCCOPY ); } else { ::BitBlt ( hdcDraw, rcClip.left + m_rcPos.left, rcClip.top + m_rcPos.top, WidthRect(rcClip), HeightRect(rcClip), hMemDC, rcClip.left, rcClip.top, SRCCOPY ); } // 释放 hMemDC ::SelectObject(hMemDC, hBmpOld); ::DeleteObject(hBmpNew); ::DeleteDC(hMemDC); }
HRESULT OnDraw(ATL_DRAWINFO& di)
{ RECT& rc = *(RECT*)di.prcBounds; // 将剪辑区域设置为 di.prcBounds 指定的矩形 HRGN hRgnOld = NULL; if (GetClipRgn(di.hdcDraw, hRgnOld) != 1) hRgnOld = NULL; bool bSelectOldRgn = false; HRGN hRgnNew = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom); if (hRgnNew != NULL) { bSelectOldRgn = (SelectClipRgn(di.hdcDraw, hRgnNew) != ERROR); } // 双缓冲 DbBufferDraw(di.hdcDraw, rc); if (bSelectOldRgn) SelectClipRgn(di.hdcDraw, hRgnOld); // 删除剪辑区域 ::DeleteObject(hRgnNew); // Add by cheungmine. MUST!! return S_OK; }
按上面的修改,烦人的闪烁没了。另外,在OnDarw中,没必要把全部绘制代码放入 MyDrawCode 中。因为 MyDrawCode 如果执行时间较长,则 OnDraw会显得很慢。因此,光是双缓冲还不够,因为OnDraw被调用的时候,都是系统激发的,我们只需要把原来保存的绘制图片直接绘制到hMemDC中即可,也就是, MyDrawCode中不可以如本例所示的那样,放置实际绘制的代码,而是只把图片重新拷贝到hdc上即可,如:
void MyDrawCode (HDC hdc, RECT &rc)
{
m_BkgndMap.CopyTo(hdc, rc);
}
m_BkgndMap 可以是自己实现的Image或CImage等图像类。
因此,在一个基本的绘图系统中,至少需要3个缓冲层次:
第一层:控件窗口HDC(无窗口控件也是存在HDC的)
第二层:控件窗口HDC的兼容MemDC,即:HDC hMemDC = ::CreateCompatibleDC(hdcDraw);
第三层:后台图片HDC包装类: m_BkgndMap
关于如何创建这样的ActiveX 窗口控,请看我的相关文章:
from: