EVC MFC绘图容易出现的问题总结

来源:互联网 发布:oracle 高级sql语句 编辑:程序博客网 时间:2024/05/24 01:28

      最近做了一个基于wince5.0平台的监控软件(包含大量的绘图操作),使用evc4.0 MFC开发实现。由于刚开始做,绘图方面的经验较少,碰到了不少问题,在此做一下总结,以此为鉴。在此之前首先感谢那些在各论坛提供此类问题解决方法的前辈们,本项目碰到的问题能得以顺利解决,多亏他们的回帖。

      一、在绘图时使用::GetDC和::DeleteDC代替CWnd::GetDC和CWnd::DeleteDC

      使用CWnd::GetDC和CWnd::Delete进行绘图时存在一个bug,每刷新一次会有一个4K的内存泄露,使用::GetDC和::DeleteDC这两个API函数就不会出现此问题。

    二、::GetDC和::DeleteDC 与::BeginPaint和 ::EndPaint的区别

       (此段引用自:http://hi.baidu.com/deton/blog)

          在EVC中绘制位图比较方便,有不少现成的函数可供调用,我们所要注意的只是BeginPaint()或GetDC()的使用即可.
         因为代码比较简单,所以不做更多解释.

         这是消息循环函数:
         LRESULT CALLBACK MainWndProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)
         {
             ......
           
             switch(wMsg)
             {
                 case WM_PAINT:
                         OnPaintMainWnd(hWnd,wMsg,wParam,lParam);
                         break;
               
                 ......               
           
             }
             return DefWindowProc(hWnd,wMsg,wParam,lParam);
           
             ......
           
         }
       
         响应WM_PAINT消息的函数,在这里进行位图的绘制:
         LRESULT OnPaintMainWnd(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)
         {
             PAINTSTRUCT ps;
             HDC hdc = BeginPaint(hWnd,&ps);
             //Create a DC that matches the device
             HDC hdcMem = CreateCompatibleDC(hdc);
             //Load the bitmap
             HANDLE hBmp= LoadImage(g_hInst_MainWnd,MAKEINTRESOURCE(IDB_MAINWND),IMAGE_BITMAP,0,0,0);
             //Select the bitmap into to the compatible device context
             HGDIOBJ hOldSel = SelectObject(hdcMem,hBmp);
             //Get the bitmap dimensions from the bitmap
             BITMAP bmp;
             GetObject(hBmp,sizeof(BITMAP),&bmp);
             //Get the window area
             RECT rc;
             GetClientRect(hWnd,&rc);
             //Copy the bitmap image from the memory DC to the screen DC
             BitBlt(hdc,rc.left,rc.top,bmp.bmWidth,bmp.bmHeight,hdcMem,0,0,SRCCOPY);
             //Restore original bitmap selection and destroy the memory DC
             SelectObject(hdcMem,hOldSel);   
             DeleteDC(hdcMem);
             EndPaint(hWnd,&ps);
             return 0;
         }

         我们都知道BeginPaint()和EndPaint()需要配套使用,并且这两个函数也只能用在WM_PAINT消息的相应函数当中.如果我们在 WM_PAINT的响应函数中将以上两个绘制函数相应替换为GetDC()和ReleaseDC()会有什么结果呢?
         即:
         HDC hdc = BeginPaint(hWnd,&ps);     -->    HDC hdc = GetDC(hWnd);
         EndPaint(hWnd,&ps);                 -->    ReleaseDC(hWnd,hdc);
       
         编译并运行程序,我们发现窗口一片空白,好像没有绘制位图.但其实不尽然,我们采用单步调试,可以发现其实位图已经绘制出来,只不过又被背景颜色抹掉了. 由此可知,如果需要使用GetDC(),我们对消息循环函数必须要加上对WM_ERASEBKGND的处理:
         LRESULT CALLBACK MainWndProc(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam)
         {
             switch(wMsg)
             {
                 case WM_PAINT:
                         OnPaintMainWnd(hWnd,wMsg,wParam,lParam);
                         break;
                 case WM_ERASEBKGND   
                         return 0;           
             }
             return DefWindowProc(hWnd,wMsg,wParam,lParam);
         }
         只要系统不对WM_ERASEBKGND进行默认处理,我们用GetDC()替代BeginPaint()就可以正常使用.
       
         至此我们可以看出BeginPaint(),EndPaint()和GetDC(),ReleaseDC()的区别.前一对只能用在WM_PAINT响应函数中,并且绘制背景时不会被抹掉;后一对随处可用,但如果用在WM_PAINT响应函数中,那么接下来将会被WM_ERASEBKGND消息的响应函数的背景绘制给抹掉。BeginPaint() 和EndPaint() 可以删除消息队列中的WM_PAINT消息,并使无效区域有效。GetDC()和ReleaseDC()并不删除也不能使无效区域有效,因此当程序跳出 WM_PAINT 时 ,无效区域仍然存在。系统就回不断发送WM_PAINT消息,于是程序不断处理WM_PAINT消息。 相当于BeginPaint、EndPaint会告诉GDI内部,这个窗口需要重画的地方已经重画了,这样WM_PAINT处理完返回给系统后,系统不会再重发WM_PAINT,而GetDC没有告诉系统这个窗口需要重画的地方已经画过,在你把程序返回给系统后,系统一直以为通知你的重画命令你还没有乖乖的执行或者执行出错,所以在消息空闲时,它还会不断地发WM_PAINT催促你画,导致一些优先级低的消息无法被执行。   如果此时存在时钟调用,则OnTimer时钟事件处理函数就会无法调用执行,时钟失效。

   三、SelectObject选择对象后,必须及时恢复,否则会导致内存泄露

    HDC hMemDC;
    HBITMAP hMemBitmap;
    PAINTSTRUCT ps;
    HDC hdc= ::BeginPaint(m_hWnd, &ps);
    GetClientRect(&rcPaint);

   HBITMAP   pOldmap;

   hMemDC = CreateCompatibleDC(hdc);
   hMemBitmap = CreateCompatibleBitmap(hdc,rcPaint.right-rcPaint.left,rcPaint.bottom-rcPaint.top);
   pOldmap =(HBITMAP)SelectObject(hMemDC,hMemBitmap);

   BitBlt(hdc,rcPaint.left,rcPaint.top,rcPaint.right,rcPaint.bottom,hMemDC,0,0,SRCCOPY);
   SelectObject(hMemDC,pOldmap);
   DeleteObject (hMemBitmap);
   DeleteDC (hMemDC);
   ::EndPaint(m_hWnd, &ps);

  上述代码段中如果不包含:SelectObject(hMemDC,pOldmap)就会导致内存严重泄露,另外还要注意DeleteObject (hMemBitmap)和DeleteDC (hMemDC)两句的先后顺序,DeleteDC 必须放在DeleteObject (hMemBitmap)后,否则导致DeleteDC 调用失败,也会引起内存泄露。

  四、慎用WM_ERASEBKGND              

     MFC下在调用Invalidate(TRUE)或者在自窗口的打开关闭时会触发WM_ERASEBKGND事件,如果调用此事件后不作处理采用默认方式,则会出现界面闪烁问题,如果直接retuen TRUE的话,虽然会解决屏幕闪烁问题,但仍会出现内存泄露问题(本项目中开始使用Invalidate()的方式刷新界面,大约1分半的时间出现一个4K的内存泄露),后来使用Invalidate(FALSE),然后在MFC中将WM_ERASEBKGND消息响应函数删除,就不会出现内存泄露了。即使这样仍存在着一个问题,就是在打开子窗口和子窗口关闭时会出现背景界面重绘,出现白屏闪烁一下,经过研究后来在DefWindowProc中加入

 if (message == WM_ERASEBKGND)
 {
  return TRUE;
 }

将WM_ERASEBKGND消息屏蔽即可解决屏幕闪烁的问题。

以上是根据我的第一个evc项目遇到的一些问题的总结,希望对刚开始学习evc绘图的朋友有些帮助 

原创粉丝点击