应用场景是:绘制一个运动小球,在大屏幕滚动,屏幕背景图也是一个自定义绘制的(填充色,线条和文字等组成)。如图所示。红色是小球,在大背景里动态移动,而背景是静态的,静止不动。
1、MFC克服C++窗体重绘时的闪烁问题,用到的技巧是双缓冲。双缓冲原理网上的文章好多,这里不赘述。
//---------------------------------MFC双缓冲//--------------------------------- void CViewImage::OnPaint() { CPaintDC dc(this); // device context for painting // TODO: 在此处添加消息处理程序代码 // 不为绘图消息调用 CFormView::OnPaint() OnPrepareDC(&dc); OnDraw(&dc); //调用了OnDraw } void CViewImage::OnDraw(CDC *pDC) { CDC dcMemory; //图形重绘,双缓冲防止闪屏 CRect rect; GetClientRect(&rect); CBitmap bmp; dcMemory.CreateCompatibleDC(pDC); bmp.CreateCompatibleBitmap(pDC, rect.right, rect.bottom); CBitmap *pOldBitmap = dcMemory.SelectObject(&bmp); dcMemory.SetBkMode(TRANSPARENT); DrawBackgroundImage(&dcMemory);//绘制自定义背景图 DrawBall(&dcMemory);//绘制小球 pDC->BitBlt(rect.left, rect.top, rect.right, rect.bottom, &dcMemory, 0, 0, SRCCOPY); dcMemory.SelectObject(pOldBitmap); dcMemory.DeleteDC(); bmp.DeleteObject(); } BOOL CViewImage::OnEraseBkgnd(CDC* pDC) { //Invalidate(FALSE)不擦除背景,直接画,它只会向消息队列中添加了WM_PAINT消息。 //Invalidate(TRUE)擦除背景,它会向消息队列中添加了WM_ERASEBKGND和WM_PAINT两个消息。 //可见:Invalidate(FALSE)不会清空之前所画图像。 //如果你想用Invalidate(TRUE)来实现Invalidate(FALSE)一样的效果, //你可以添加对WM_ERASEBKGND消息响应的函数, //修改OnEraseBkgnd函数的返回值为:return TRUE; //返回值return TRUE就是不擦除背景,此时Invalidate(TRUE)与Invalidate(FALSE)的效果是一样的。 return TRUE;//禁止背景重绘; true表示已处理背景刷新,false表示需要在OnPaint里处理 //return CFormView::OnEraseBkgnd(pDC); }
2、本文要解决的是提升绘图效率,不要让背景频繁擦除。本方法可以认为是MFC三缓冲绘图。(#^.^#)
在上面的源码OnDraw函数中,每次都需要绘制自定义背景图和小球图。每隔50毫秒通过Invalidate函数发送WM_PAINT消息刷新屏幕。但实际运行过程中发现,每次绘制自定义背景太占CPU(4%,4核CPU),有什么更好的办法吗?
办法就是不要每次都重绘背景图,因为它是静止不动的。于是想到将背景预先绘制到一个内存设备区域,然后在OnDraw中将该内存设备环境中的内容复制到当前的内存设备环境中。
方法1:画在已知的内存型bmp
//---------------------------------方法1:--------------------------------- 定义类的成员变量 CDC dcMemory;//图形重绘,双缓冲防止闪屏 CBitmap bmp; CBitmap *pOldBitmap; void CViewImage::initBackgroundImage(CDC* pDC) { if (!bFirst) { return; } CRect rect; GetClientRect(&rect); dcMemory.CreateCompatibleDC(pDC); bmp.CreateCompatibleBitmap(pDC, rect.right, rect.bottom); pOldBitmap = dcMemory.SelectObject(&bmp); dcMemory.SetBkMode(TRANSPARENT); //dcMemory.FillSolidRect(0, 0, rect.right, rect.bottom, RGB(255, 255, 255));//默认是黑色 DrawBackgroundImage(&dcMemory);//绘制自定义背景图 } void CViewImage::destroyBackgroundImage() { dcMemory.SelectObject(pOldBitmap); dcMemory.DeleteDC(); bmp.DeleteObject(); } void CViewImage::OnDraw(CDC* pDC)//方法1:画在已知的内存型bmp { initBackgroundImage(CDC* pDC);//使用布尔量控制只绘制一次,但是遇到OnSize消息时,背景图需要重绘 CRect rect; GetClientRect(&rect); CDC dcCompatible; dcCompatible.CreateCompatibleDC(pDC); pOldBitmap = dcCompatible.SelectObject(&bmp); DrawBall(&dcCompatible);//绘制小球 pDC->BitBlt(rect.left, rect.top, rect.right, rect.bottom, &dcCompatible, 0, 0, SRCCOPY); dcCompatible.SelectObject(pOldBitmap); dcCompatible.DeleteDC(); }
方法2:一个CDC拷贝到另一个CDC
//---------------------------------方法2:--------------------------------- 定义类的成员变量 CDC dcMemory1; CBitmap bmp1; CBitmap *pOldBitmap1; void CViewImage::initBackgroundImage(CDC* pDC) { if (!bFirst) { return; } CRect rect; GetClientRect(&rect); dcMemory1.CreateCompatibleDC(pDC); bmp1.CreateCompatibleBitmap(pDC, rect.right, rect.bottom); pOldBitmap1 = dcMemory1.SelectObject(&bmp1); dcMemory1.SetBkMode(TRANSPARENT); //dcMemory1.FillSolidRect(0, 0, rect.right, rect.bottom, RGB(255, 255, 255));//默认是黑色 DrawBackgroundImage(&dcMemory1);//绘制自定义背景图 } void CViewImage::destroyBackgroundImage() { dcMemory1.SelectObject(pOldBitmap1); dcMemory1.DeleteDC(); bmp1.DeleteObject(); } void CViewImage::OnDraw(CDC* pDC) { initBackgroundImage(CDC* pDC);//使用布尔量控制只绘制一次,但是遇到OnSize消息时,背景图需要重绘 //方法2:一个CDC拷贝到另一个CDC CDC dcMemory2; dcMemory2.CreateCompatibleDC(&dcMemory1); CBitmap bmp2; bmp2.CreateCompatibleBitmap(&dcMemory1, rect.right, rect.bottom); CBitmap *pOldBitmap2 = dcMemory2.SelectObject(&bmp2); dcMemory2.SetBkMode(TRANSPARENT); dcMemory2.BitBlt(rect.left, rect.top, rect.right, rect.bottom, &dcMemory1, 0, 0, SRCCOPY);//CDC拷贝 DrawBall(&dcMemory2);//绘制小球 pDC->BitBlt(rect.left, rect.top, rect.right, rect.bottom, &dcMemory2, 0, 0, SRCCOPY);//绘制在客户区 dcMemory2.SelectObject(pOldBitmap2); dcMemory2.DeleteDC(); bmp2.DeleteObject(); }
推荐使用方法2!!
---题外话---
//---------------------------------题外话:外部加载位图并显示---------------------------------
void CViewImage::OnDraw(CDC* pDC) { CBitmap bitmap; bitmap.LoadBitmap(IDB_BITMAP_DEBUG_TEACH); BITMAP bmp1; bitmap.GetBitmap(&bmp1); CDC dcCompatible; dcCompatible.CreateCompatibleDC(pDC); pOldBitmap = dcCompatible.SelectObject(&bitmap); dcCompatible.MoveTo(1, 1); dcCompatible.LineTo(40, 50); pDC->BitBlt(rect.left, rect.top, rect.right, rect.bottom, &dcCompatible, 0, 0, SRCCOPY); dcCompatible.SelectObject(pOldBitmap); dcCompatible.DeleteDC(); bitmap.DeleteObject(); }
---
参考文献:
https://www.cnblogs.com/renyuan/p/3474802.html
https://bbs.csdn.net/topics/390929456
https://blog.csdn.net/ooyyee11/article/details/7600625
https://www.cnblogs.com/lujin49/p/4704795.html