mfc双缓存技术+滚动条的画图问题

16 篇文章 0 订阅

转自:http://blog.csdn.net/ke_yang/article/details/5417643

VC中的绘图有个比较棘手的问题是闪烁,双缓存是解决此类问题的一种方法,但是在系统绘图中,由于可能要加载滚动条,响应鼠标拖动等事件,导致传统的双缓存方法不一定适用,本文提出了一种解决方法能够用统一的框架内实现滚动条,鼠标图型拖动,视口转换以及双缓存绘图.

关键字:双缓存,滚动条,鼠标拖动,VC,视口转换

  炫丽的软件效果能增强用户体验,用绘图方法展示动人效果就成为了必不可少的一个环节,VC提供了非常丰富的绘图API函数库,例如GDI+接口,但是用过这些接口函数的开发人员应该知道,有个非常头疼的问题是闪烁.如何解决这个问题,双缓存是一个非常好的办法.
1.双缓存原理介绍
  在VC中进行绘图过程处理时,如果图形刷新很快,经常出现图形闪烁的现象。利用先在内存绘制,然后拷贝到屏幕的办法可以消除屏幕闪烁,具体的方法是先在内存中创建一个与设备兼容的内存设备上下文,也就是开辟一快内存区来作为显示区域,然后在这个内存区进行绘制图形。在绘制完成后利用BitBlt函数把内存的图形直接拷贝到屏幕上即可。
2.双缓存绘图实现
 前面简单的介绍了VC下双缓存的原理,在实现这一环节,我们的主要工作是将上面的想法实现.当面我们所用到的MFC应用程序是基于对话框,所以代码我们当前要放在OnPaint函数里.
void CTestScrollDlg::OnPaint() 
{
         CPaintDC dc(this);     
         int nWidth = 1000;
         int nHeight = 1000;

         //随后建立与屏幕显示兼容的内存显示设备 
         CDC MemDC; //首先定义一个显示设备对象
         CBitmap MemBitmap;//定义一个位图对象
         MemDC.CreateCompatibleDC(NULL);
         //这时还不能绘图,因为没有地方画
         //下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小
         MemBitmap.CreateCompatibleBitmap(&dc,nWidth,nHeight); 
         //将位图选入到内存显示设备中
         //只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上
         MemDC.SelectObject(&MemBitmap);
         //先用背景色将位图清除干净,这里我用的是白色作为背景
         //你也可以用自己应该用的颜色
         MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255)); 
         //绘图
         MemDC.Ellipse(m_nEclipseRect); 
         //将内存中的图拷贝到屏幕上进行显示
         dc.SetViewportOrg(-m_nHScrollPos,-m_nVScrollPos);
         dc.BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);
         //绘图完成后的清理
         MemBitmap.DeleteObject();
         MemDC.DeleteDC();
         CDialog::OnPaint();
} 


  这里面有一点要着重讲一下.几个变量的定义: 
1.nEclipseRect这是个矩形框,CRect类型,用于表示一个椭圆的四个值 
2.m_nHScrollPos是个int类型,用之于水平滚动条,表现当前X轴坐标值. 
3.m_nVScrollPos是个int类型,用之于垂直滚动条,表现当前的Y轴坐标值 
  在本程序中,由于绘图的面积可能会比较大,我们采用了滚动条机制.下面列出来滚动条代码. 
void CTestScrollDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
     // TODO: Add your message handler code here and/or call default
     SCROLLINFO scrollinfo;  
     GetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);  
//     this->Invalidate(false);
     //InvalidateRect(NULL,TRUE); 
     switch (nSBCode)  
     {  
         case SB_LEFT:  
             ScrollWindow((scrollinfo.nPos-scrollinfo.nMin)*10,0);  
             scrollinfo.nPos = scrollinfo.nMin;  
             SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);  
             break;  
         case SB_RIGHT:  
             ScrollWindow((scrollinfo.nPos-scrollinfo.nMax)*10,0);  
             scrollinfo.nPos = scrollinfo.nMax;  
             SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);  
             break;  
         case SB_LINELEFT:  
             scrollinfo.nPos -= 1;  
             if (scrollinfo.nPos)
             {  
                 scrollinfo.nPos = scrollinfo.nMin;  
                 break;  
             }  
             SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);  
             ScrollWindow(10,0);  
             break;  
         case SB_LINERIGHT:  
             scrollinfo.nPos += 1;  
             if (scrollinfo.nPos>scrollinfo.nMax)  
             {  
                 scrollinfo.nPos = scrollinfo.nMax;  
                 break;  
             }  
             SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);  
             ScrollWindow(-10,0);  
             break;  
         case SB_PAGELEFT:  
             scrollinfo.nPos -= 5;  
             if (scrollinfo.nPos)
             {  
                 scrollinfo.nPos = scrollinfo.nMin;  
                 break;  
             }  
             SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);  
             ScrollWindow(10*5,0);  
             break;  
         case SB_PAGERIGHT:  
             scrollinfo.nPos += 5;  
             if (scrollinfo.nPos>scrollinfo.nMax)  
             {  
                 scrollinfo.nPos = scrollinfo.nMax;  
                 break;  
             }  
             SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);  
             ScrollWindow(-10*5,0);  
             break;  
         case SB_THUMBPOSITION:  
             break;  
         case SB_THUMBTRACK:  
             ScrollWindow((scrollinfo.nPos-nPos)*10,0);  
             scrollinfo.nPos = nPos;  
             SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);  
             break;  
         case SB_ENDSCROLL:  
             break;  
     }  
     m_nHScrollPos = scrollinfo.nPos*10;
     CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
} 


void CTestScrollDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
     // TODO: Add your message handler code here and/or call default
     SCROLLINFO scrollinfo;  
     GetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);  
//     this->Invalidate(false);
     //InvalidateRect(NULL,TRUE);   
     switch (nSBCode)  
     {  
         case SB_BOTTOM:  
             ScrollWindow(0,(scrollinfo.nPos-scrollinfo.nMax)*10);  
             scrollinfo.nPos = scrollinfo.nMax;  
             SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);  
             break;  
         case SB_TOP:  
             ScrollWindow(0,(scrollinfo.nPos-scrollinfo.nMin)*10);  
             scrollinfo.nPos = scrollinfo.nMin;  
             SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);  
             break;  
         case SB_LINEUP:  
             scrollinfo.nPos -= 1;  
             if (scrollinfo.nPos)
             {  
                 scrollinfo.nPos = scrollinfo.nMin;  
                 break;  
             }  
             SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);  
             ScrollWindow(0,10);  
             break;  
         case SB_LINEDOWN:  
             scrollinfo.nPos += 1;  
             if (scrollinfo.nPos>scrollinfo.nMax)  
             {  
                 scrollinfo.nPos = scrollinfo.nMax;  
                 break;  
             }  
             SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);  
             ScrollWindow(0,-10);  
             break;  
         case SB_PAGEUP:  
             scrollinfo.nPos -= 5;  
             if (scrollinfo.nPos)
             {  
                 scrollinfo.nPos = scrollinfo.nMin;  
                 break;  
             }  
             SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);  
             ScrollWindow(0,10*5);  
             break;  
         case SB_PAGEDOWN:  
             scrollinfo.nPos += 5;  
             if (scrollinfo.nPos>scrollinfo.nMax)  
             {  
                 scrollinfo.nPos = scrollinfo.nMax;  
                 break;  
             }  
             SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);  
             ScrollWindow(0,-10*5);  
             break;  
         case SB_ENDSCROLL:  
             // MessageBox("SB_ENDSCROLL");  
             break;  
         case SB_THUMBPOSITION:  
             // ScrollWindow(0,(scrollinfo.nPos-nPos)*10);  
             // scrollinfo.nPos = nPos;  
             // SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);  
             break;  
         case SB_THUMBTRACK:  
             ScrollWindow(0,(scrollinfo.nPos-nPos)*10);  
             scrollinfo.nPos = nPos;  
             SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);  
             break;  
     }
     m_nVScrollPos = scrollinfo.nPos*10;
     CDialog::OnVScroll(nSBCode, nPos, pScrollBar);
} 


 这段代码通用性比较强,可以直接粘到应用程序中使用.但是只要注意,由于在我们的应用程序中运行到了视口切换的概念,这是本地坐标系跟世界坐标系之间的切换,前面提到了二个变量用来标记当前坐标系的视口值.具体原理若不理解,请参考视口转换的原理. 
3.鼠标拖动事件实现
  前面的代码运行出来的效果,是一个对话框,然后在(0,0,200,200)的位置上出现一个椭圆,下面的工作,是想通过鼠标拖动这个椭圆,即实现鼠标的智能拖动.下面我们在对话框中添加鼠标拖动事件.
void CTestScrollDlg::OnMouseMove(UINT nFlags, CPoint point) 
{
     // TODO: Add your message handler code here and/or call default
     //dc.SetViewportOrg(-m_nHScrollPos,-m_nVScrollPos);
     //     dc.Ellipse(m_nEclipseRect);
     int cx = point.x-m_nOrgPoint.x;
     int cy = point.y - m_nOrgPoint.y;
     m_nOrgPoint = point;
     point.x += m_nHScrollPos;
     point.y += m_nVScrollPos;
     CString str;
     str.Format("x= %d,y=%d",point.x,point.y);
     this->SetWindowText(str);
     // 判断当前的点是否在矩形框中,同时是否按下鼠标左键
     if(nFlags&MK_LBUTTON && m_nEclipseRect.PtInRect(point))
     {
         m_nEclipseRect.bottom += cy;
         m_nEclipseRect.top += cy;
         m_nEclipseRect.right += cx;
         m_nEclipseRect.left += cx;
         this->Invalidate();
     }
     CDialog::OnMouseMove(nFlags, point);
} 


  这里面有个变量m_nOrgPoint,是个类变量,用之于记录上一次的坐标值. 
4.再闪烁问题解决
  不过我们运行这个效果图后,会发现又出现了闪烁,这个是什么问题呢,我们不是已经添加了双缓存了吗.
 其实这个问题的产生的原因是因为我们在鼠标拖动时,也进行了Invalidate函数,而这个时候是不需要进行背景重绘,这个问题的解决是通过添加OnEraseBkgnd函数.
 在当前对话框中通过添加函数向导里,在Filter里选择Window.然后先中EraseBkgnd事件.
BOOL CTestScrollDlg::OnEraseBkgnd(CDC* pDC) 
{
     // TODO: Add your message handler code here and/or call default
     return true;
     return CDialog::OnEraseBkgnd(pDC);
} 


 再运行一下,就会发现闪烁不见了. 
5.总结

 双缓存绘图的问题几乎困扰过每一个开发者,虽然参考网上信息,能解决这一问题,但是在添加鼠标事件,添加滚动条事件,如何通过视口切换的方法,显示一张大图,目前并没有统一的解决办法,在实际工作,在统一解决这个问题时,也花了一些功夫,在解决后,整理了一下思路,以供开发人员参考.


---------------------------------------------------------------------------

       自己的公司,朋友们关注一下 中海岸信息技术有限公司

        公司网址:www.icss.cc

        中海岸建站:www.cseashore.asia



  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值