Bootstrap

GDI+ 双缓冲实现和局部刷新技术

GDI+双缓冲技术:
早前曾为此问题在CSDN发帖求助( GDI+ 如何使用双缓冲绘制图像),得到了一个GDI+下较可行的方法,虽然绘制效果比直接绘制要好一些,不过还不能跟GDI的双缓冲方式比肩。

现在,我终于找到了一个理想的实现方式,效果与GDI的实现不相上下,代码如下:

/*C++ code*/
RECT rc;
GetClientRect(g_hwnd,&rc);
Bitmap bmp(int(rc.right),int(rc.bottom));

Graphics bmpGraphics(&bmp);
bmpGraphics.SetSmoothingMode(SmoothingModeAntiAlias);

/*Drawing on bitmap*/
SolidBrush bkBrush(Color(0,0,0));
bmpGraphics.FillRectangle(&bkBrush,0,0,rc.right,rc.bottom);

/*Drawing on DC*/
Graphics graphics(hdc);
/*Important! Create a CacheBitmap object for quick drawing*/
CachedBitmap cachedBmp(&bmp,&graphics);
graphics.DrawCachedBitmap(&cachedBmp,0,0);

以上的绘制代码最区别于网络上其他GDI+实现的一处就是,在最后添加了一个CacheBitmap对象用于快速绘制。

CacheBitmap是一个包含了bmp全部象素,并且针对graphics所关联的DC做过特别优化的位图对象。这点可以从其构造参数上看到。 

关于双缓冲的实现还有一点十分关键,虽然它不属于双缓冲实现的核心。如果绘制需要经常的重绘背景,则需要自己拦截WM_ERASEBKGND消息,并在处理函数中什么也不做,即此消息发生时不重画背景,背景的重画在WM_PAINT中全权控制。

如,WM_ERASEBKGND消息处理的实现
void OnEraseBkGnd(HDC hdc)
{
//do nothing
}


附:GDI的双缓冲实现
RECT rc;
GetClientRect(hwnd,&rc);
HDC hMemDc = CreateCompatibleDC(hdc);
HBITMAP hBmp = CreateCompatibleBitmap(hdc,rc.right,rc.bottom);
HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDc,hBmp);
//在此使用hMemDc进行 GDI 绘制
BitBlt(hdc,0,0,rc.right,rc.bottom,hMemDc,0,0,SRCCOPY);
SelectObject(hMemDc,hOldBmp);
DeleteObject(hBmp);
DeleteObject(hMemDc);


前言:GDI+很好用,但要将所有图像混合,必须要用到UpdateLayeredWindow,但UpdateLayeredWindow注定每次刷新都要刷新整个窗体,也就是说,哪怕我们只是改变的只是一个像素,也都要重新绘制整个窗体,就没有像GDI中那样有区域裁剪的功能,真无语了,搜遍整个网络也没找到UpdateLayeredWindow的局部刷新方案,最后想到在MFC中是可以使用GDI+的,而MFC中的刷新方案就是局部刷新的,这说明,利用GDI的函数bitblt或alphaBlend是可以整合GDI+的,最后尝试了一下,成功了,下面分享给大家。

本文摘要:这个工程是在第一篇《WIN32界面开发之一:初试加载背景界面》的基础上讲解的,所有请大家先看看第一篇,这篇内容实现的效果是,建立一个背景图案,在图案上模拟一个按钮,按钮原是红色块,当点击按钮的时候,变成绿色色块,鼠标弹起时,还原为红色色块。

正常状态:

占击状态:

思想:首先,在第一次创建工程时,我们在在兼容DC绘图,并把这个DC保存起来,当下次重绘时,重新创建一个兼容DC,用AlphaBlend将我们保存的原兼容DC上的内容复制到当前的兼容DC上,然后再在要更新的区域重新画图就是了,这里我们只需要更新按钮的区域。

一、几个全局变量

[cpp]  view plain copy
  1. HDC hdcBKMemory=NULL;//兼容DC  
  2. HBITMAP hBKBitmap=NULL;//兼容DC上的画布  
  3. HGDIOBJ hBKBitmapOld=NULL;//被选出的原兼容DC上的默认画布  
二、背景兼容DC的初次创建与释放

我们先看创建的代码:

[cpp]  view plain copy
  1. HDC hDC = ::GetDC(m_hWnd);  
  2.   
  3. if (hdcBKMemory==NULL)  
  4. {  
  5.     hdcBKMemory = CreateCompatibleDC(hDC);  
  6.     //创建背景画布  
  7.     BITMAPINFOHEADER stBmpInfoHeader = { 0 };     
  8.     int nBytesPerLine = ((sizeWindow.cx * 32 + 31) & (~31)) >> 3;  
  9.     stBmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);     
  10.     stBmpInfoHeader.biWidth = sizeWindow.cx;     
  11.     stBmpInfoHeader.biHeight = sizeWindow.cy;     
  12.     stBmpInfoHeader.biPlanes = 1;     
  13.     stBmpInfoHeader.biBitCount = 32;     
  14.     stBmpInfoHeader.biCompression = BI_RGB;     
  15.     stBmpInfoHeader.biClrUsed = 0;     
  16.     stBmpInfoHeader.biSizeImage = nBytesPerLine * sizeWindow.cy;     
  17.     PVOID pvBits = NULL;     
  18.     hBKBitmap = ::CreateDIBSection(NULL, (PBITMAPINFO)&stBmpInfoHeader, DIB_RGB_COLORS, &pvBits, NULL, 0);  
  19.     assert(hBKBitmap != NULL);  
  20.     hBKBitmapOld = ::SelectObject( hdcBKMemory, hBKBitmap);  
  21.   
  22.     //gdi+画图  
  23.     Gdiplus::Graphics graph(hdcBKMemory);  
  24.     graph.SetSmoothingMode(Gdiplus::SmoothingModeNone);  
  25.     graph.DrawImage(pImage, 0, 0, sizeWindow.cx, sizeWindow.cy);  
  26.     graph.FillRectangle(&SolidBrush(Color::Green),10,10,25,25);  
  27.   
  28.     graph.FillRectangle(&SolidBrush(Color::Red),100,50,30,30);//模拟按钮  
  29.   
  30.     graph.ReleaseHDC(hdcBKMemory);  
  31. }  
讲解:上面的代码比较简单,首先是用CreateDIBSection创建一个设备相关位置(DDB),然后将位图选入到背景兼容DC(hdcBKMemory)中,后面就是用GDI+在这个DC上画图。
再看释放:
因为我们要一直用它,直到销毁,所以我们在WM_DESTROY消息中销毁这些变量:
[cpp]  view plain copy
  1. case WM_DESTROY:  
  2.     PostQuitMessage(100);  
  3.   
  4.     delete pImage;  
  5.     ::SelectObject( hdcBKMemory, hBKBitmapOld); //不要把默认的位图选回来,如果选回来的话,我们新建的位图就被替换掉了,当然我们上面画的东东也就没有了  
  6.     ::DeleteObject(hBKBitmapOld);  
  7.     ::DeleteObject(hBKBitmap);   
  8.     ::DeleteDC(hdcBKMemory);  
  9.     break;  
三、新建一个兼容DC与画布,将背景复制到上面,并且在当前画布上画上按钮正常状态
先看代码:

[cpp]  view plain copy
  1. HDC hdcEnd = CreateCompatibleDC(hDC);//新建兼容DC  
  2. BITMAPINFOHEADER stBmpInfoHeader2 = { 0 };     
  3. int nBytesPerLine2 = ((sizeWindow.cx * 32 + 31) & (~31)) >> 3;  
  4. stBmpInfoHeader2.biSize = sizeof(BITMAPINFOHEADER);     
  5. stBmpInfoHeader2.biWidth = sizeWindow.cx;     
  6. stBmpInfoHeader2.biHeight = sizeWindow.cy;     
  7. stBmpInfoHeader2.biPlanes = 1;     
  8. stBmpInfoHeader2.biBitCount = 32;     
  9. stBmpInfoHeader2.biCompression = BI_RGB;     
  10. stBmpInfoHeader2.biClrUsed = 0;     
  11. stBmpInfoHeader2.biSizeImage = nBytesPerLine2 * sizeWindow.cy;     
  12. PVOID pvBits2 = NULL;     
  13. HBITMAP hbmpMem2 = ::CreateDIBSection(NULL, (PBITMAPINFO)&stBmpInfoHeader2, DIB_RGB_COLORS, &pvBits2, NULL, 0);//新建画布  
  14.   
  15. HGDIOBJ hEndBitmapOld=SelectObject(hdcEnd,hbmpMem2);  
  16. POINT ptSrc = { 0, 0};  
  17. BLENDFUNCTION blendFunc;  
  18. blendFunc.BlendOp = 0;  
  19. blendFunc.BlendFlags = 0;  
  20. blendFunc.AlphaFormat = 1;  
  21. blendFunc.SourceConstantAlpha = 255;//AC_SRC_ALPHA  
  22. ::AlphaBlend(hdcEnd,0,0,sizeWindow.cx,sizeWindow.cy,hdcBKMemory,0,0,sizeWindow.cx,sizeWindow.cy,blendFunc);//将背景复制到新画布上  
  23.   
  24. Graphics graph2(hdcEnd);  
  25. if (inBtnRect)//这个变量下面讲解  
  26. {  
  27.     graph2.FillRectangle(&SolidBrush(Color::Green),100,50,30,30);//按钮按下状态  
  28. }else{  
  29.     graph2.FillRectangle(&SolidBrush(Color::Red),100,50,30,30);//按钮正常状态  
  30. }  
  31.   
  32. POINT ptWinPos = { rcWindow.left, rcWindow.top };  
  33.   
  34. //UpdateLayeredWindow  
  35. HMODULE hFuncInst = LoadLibrary(_T("User32.DLL"));  
  36. typedef BOOL (WINAPI *MYFUNC)(HWNDHDC, POINT*, SIZE*, HDC, POINT*, COLORREF, BLENDFUNCTION*, DWORD);            
  37. MYFUNC UpdateLayeredWindow;  
  38. UpdateLayeredWindow = (MYFUNC)::GetProcAddress(hFuncInst, "UpdateLayeredWindow");  
  39. //不会发送 WM_SIZE和WM_MOVE消息  
  40. if(!UpdateLayeredWindow(m_hWnd, hDC, &ptWinPos, &sizeWindow, hdcEnd, &ptSrc, 0, &blendFunc, ULW_ALPHA))  
  41. {  
  42.     assert(L"UpdateLayeredWindow 调用失败");  
  43.     TCHAR tmp[255] = {_T('\0')};  
  44. }//使用UpdateLayeredWindow更新到当前窗体上  
  45. //释放资源  
  46. graph2.ReleaseHDC(hdcEnd);  
  47. SelectObject(hdcEnd,hEndBitmapOld);  
  48.   
  49. ::DeleteObject(hFuncInst);  
  50. ::DeleteObject(hEndBitmapOld);  
  51. ::DeleteObject(hbmpMem2);  
  52. ::DeleteDC(hdcEnd);  
  53.   
  54. ::DeleteDC(hDC);  

讲解:这段代码看起来比较长,但主要是这么个流程:
1、创建新的兼容DC
2、创建画布并将此画布选到当前的兼容DC中,准备做画
3、应用AlphaBlend将原背景复制到当前画布中,这时当前的兼容DC就具有了背景图案
4、在原背景图案上画上按钮
5、最后利用UpdateLayeredWindow将最终的图案复制到当前的窗体上

运行后的软件图案是这样的:

4、添加按钮及响应

1、添加当前按钮状态的变量(全局变量)

[cpp]  view plain copy
  1. bool inBtnRect=false;//存储当前按钮是否是被按下,FALSE是没被按下,TRUE是按下  
2、在WM_LBUTTONDOWN中添加以下代码
[cpp]  view plain copy
  1. case WM_LBUTTONDOWN:  
  2.     {  
  3.         POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };  
  4.   
  5.         RectF btnRectf(100,50,30,30);  
  6.         if (btnRectf.Contains(pt.x,pt.y))  
  7.         {  
  8.             inBtnRect=true;  
  9.             SendMessage(hwnd,WM_PAINT,NULL,NULL);  
  10.             SetCapture(hwnd);  
  11.         }  
  12.     }  
  13.     break;  
讲解:
1、首先用GET_X_LPARAM和GET_Y_LPARAM获取当前鼠标在窗体中的点击位置;
2、如果点击位置在按钮区域内的话,就刷新窗体,并且设置焦点

3、在WM_LBUTTONUP添加代码
[cpp]  view plain copy
  1. case WM_LBUTTONUP:  
  2.     {  
  3.         if (inBtnRect)  
  4.         {  
  5.             ReleaseCapture();  
  6.             inBtnRect=false;  
  7.             SendMessage(hwnd,WM_PAINT,NULL,NULL);  
  8.         }     
  9.     }  
  10.     break;  
讲解:主要是释放焦点,把inBtnRect设置成FALE,并且重绘窗体;


与往常一样,源码地址:http://download.csdn.net/detail/harvic880925/5770565
转载请标明出处哦:http://blog.csdn.net/harvic880925/article/details/9355741


声明:软件所用图片来于网络,感谢金山影音漂亮的界面图片


;