zoukankan      html  css  js  c++  java
  • 【MFC】VC界面绘制双缓存

    VC界面绘制双缓存

    转载请注明原文网址: 

    http://www.cnblogs.com/xianyunhe/archive/2011/11/20/2255811.html

    1、闪屏的问题
    在GDI的绘图系统中,每调用一次区域绘图操作,如FillRect、BitBlt等,图形显示系统就会在屏幕中对指定的区域进行一次刷新操作。如果频繁的进行区域绘制操作的操作的话,我们就会发现,屏幕会出现闪屏。
    使用下面的代码对闪屏的问题进行测试,在XP系统闪屏尤其严重,在Win7系统,闪屏问题有所改善。Win7系统在绘制效率上有所提升。

    void CDoubleBufferView::DrawDirect(CDC* pDC)
    {
        ASSERT_VALID(pDC);
     
        /*用渐变色粉刷背景*/
        CRect rect(0,0,0,0);
        CSize szTotal = GetTotalSize();
        for (int i = 0; i <= szTotal.cy; i++)
        {
            rect.left = 0;
            rect.right = szTotal.cx;
            rect.top = i*10;
            rect.bottom = (i+1)*10;
            pDC->FillSolidRect(&rect, RGB((i*10)%255,0,0));
        }
     
        /*绘制图片*/
        CDC dcPic;
        dcPic.CreateCompatibleDC(pDC);
        CBitmap bmpImport;
        bmpImport.LoadBitmap(IDB_BITMAP1);
        CBitmap* pOldBmp = dcPic.SelectObject(&bmpImport);
        BITMAP bitmap;
        bmpImport.GetBitmap(&bitmap);
        int iWidth = bitmap.bmWidth;
        int iHeight = bitmap.bmHeight;
        pDC->BitBlt(0, 0, iWidth, iHeight,
            &dcPic, 0, 0, SRCCOPY);
        dcPic.SelectObject(pOldBmp);
     
        /*释放资源*/
        bmpImport.DeleteObject();
        dcPic.DeleteDC();
    }

    2、双缓存
    产生闪屏的原因是类似于多进程之间的通信问题,每次DC的绘图操作,都要把相关的显示数据发送到显卡,显卡处理后,在显示器上显示。借鉴提升多线程之间的通信效率的解决方法,可通过减少与显卡之间的交互次数来提升绘制的效率。这也就是双缓存的思路。双缓存的原理是先把更新操作中所有绘制数据先写入内存,然后再调用BitBlt或StretchBlt一次性的把所有数据发送到显卡中。
    用一个比喻来说,没有用双缓存就像老师讲课时在黑板上使用粉笔写板书,学生能看到老师写板书的整个过程,如果把黑板看做是一个屏幕的话,老师在写板书的过程,就是一个闪屏的过程。
    使用了双缓存,就像老师采用了多媒体教学,老师就可以提前在家把板书用PPT做好,上课时只要一页一页的翻PPT就可以了,学生们是看不到PPT制作的过程,也就会有闪屏的问题了。
    可采用了如下代码来实现双缓存。

    void CDoubleBufferView::DrawWithBufferInefficient(CDC* pDC)
    {
        ASSERT_VALID(pDC);
     
        /*创建内存DC*/
        CDC dcMemory;
        dcMemory.CreateCompatibleDC(pDC);
        dcMemory.SetBkColor(pDC->GetBkColor());
         
        /*设置内存DC的画板,大小整个窗口一样*/
        CSize szTotal = GetTotalSize();
        CBitmap bmpMemory;
        bmpMemory.CreateCompatibleBitmap(pDC,
            szTotal.cx, szTotal.cy);
        dcMemory.SelectObject(&bmpMemory);
         
        /*设置内存DC的起始点*/
        dcMemory.SetViewportOrg(0, 0);
         
        /*粉刷背景*/
        dcMemory.FillSolidRect(0, 0, szTotal.cx, szTotal.cy, pDC->GetBkColor());
         
        DrawDirect(&dcMemory);
         
        /*把内存DC复制到输入DC中*/
        pDC->BitBlt(0, 0, szTotal.cx, szTotal.cy,
            &dcMemory, 0, 0, SRCCOPY);
         
        /*释放资源*/
        bmpMemory.DeleteObject();
        dcMemory.DeleteDC();
    }

    3、取消擦除背景
    当我们使用了上面的双缓存技术后,我们看到闪屏问题有所缓解,但是有些操作仍然会导致闪屏,比如在有滚动条的视图窗口拖动滚动条时。为什么会产生这样的原因呢?先来分析一下界面的绘制原理。
    当我们需要对窗口绘制时,可调用UpdateWindow、RedrawWindow、Invalidate或InvalidateRect等函数。通过查看这些函数的MSDN中得知,有些窗口绘制函数中,有一个是否擦除背景的选项。如果要擦除背景,那一次绘制就要进行两部操作,也就是要跟显卡交互两次,一是擦除背景,一是重新绘制图形,那双缓存的作用就失去了。
    如果我们再绘制图形的时候,自己对背景进行一次粉刷,也就没有必要再使用擦除背景,同时也能保证双缓存的效果。
    取消擦除北京的方法主要有两种:
    (1)取消重绘时的擦除选项。
    如使用Invalidate(FALSE)。
    这种方法虽然有效,但是需要对所有的重绘函数进行处理,难以完全取消擦除背景。
    (2)截断擦除消息。
    背景的擦除是通过WM_ERASEBKGND消息来完成。于是,我们只要截获了该消息,就能彻底取消擦除背景。
    可在窗口类中为WM_ERASEBKGND提供消息响应函数,然后直接返回TRUE。

    BOOL CDoubleBufferView::OnEraseBkgnd(CDC* pDC)
    {
        // TODO: Add your message handler code here and/or call default
         
        return TRUE;
        //return CScrollView::OnEraseBkgnd(pDC);
    }

    4、绘制效率的提升
    在刷新界面的时候,刷新的区域越小,刷新效率更高,因此,在刷新界面的时候,我们应该尽量较少不必要的刷新。操作系统也会对界面的刷新操作进行优化,如拉动滚动条的时候,并不是对整个界面进行刷新,而只是对已经无效的区域中换上新的图形,然后再在屏幕调整图形区域在界面上的位置。因此,就有一个裁剪区域的概念,在重绘的过程中,只有裁剪区域需要重绘。因此,我们在双缓存中,也只需对裁剪区域重绘。可通过CDC::GetClipBox来获得裁剪区域的大小。
    因此,对双缓存的优化代码如下所示:

    void CDoubleBufferView::DrawWithBufferEfficient(CDC* pDC)
    {
        ASSERT_VALID(pDC);
     
        /*创建内存DC*/
        CDC dcMemory;
        dcMemory.CreateCompatibleDC(pDC);
        dcMemory.SetBkColor(pDC->GetBkColor());
         
        /*设置内存DC的画板,大小与输入DC的裁剪区域一样*/
        /*只对裁剪区域进行重新绘制*/
        CRect rectClip(0,0,0,0);
        pDC->GetClipBox(&rectClip);
        CBitmap bmpMemory;
        bmpMemory.CreateCompatibleBitmap(pDC,
            rectClip.Width(), rectClip.Height());
        dcMemory.SelectObject(&bmpMemory);
         
        /*设置内存DC的起始点*/
        dcMemory.SetViewportOrg(-1*rectClip.left, -1*rectClip.top);
     
        /*粉刷背景*/
        dcMemory.FillSolidRect(&rectClip, pDC->GetBkColor());
     
        DrawDirect(&dcMemory);
     
        /*把内存DC复制到输入DC中*/
        pDC->BitBlt(rectClip.left, rectClip.top, rectClip.Width(), rectClip.Height(),
            &dcMemory, rectClip.left, rectClip.top, SRCCOPY);
         
        /*释放资源*/
        bmpMemory.DeleteObject();
        dcMemory.DeleteDC();
    }

     5、工程源代码下载

    https://files.cnblogs.com/xianyunhe/DoubleBuffer.rar

    如果问题解决起来不妥或者有更好的解决办法,麻烦请告知,帮助曾经和你一样的入门者,谢谢。
  • 相关阅读:
    【转】Senior Data Structure · 浅谈线段树(Segment Tree)
    1260:【例9.4】拦截导弹(Noip1999)
    1255:迷宫问题
    1253:抓住那头牛
    1254:走出迷宫
    POJ 3692 幼儿园做游戏 最大团 模板题
    POJ 1466 大学谈恋爱 二分匹配变形 最大独立集
    POJ 3422 矩阵取数 最小费用流拆点+负边
    POJ 2195 一人一房 最小费用流 建图 水题
    POJ 3068 运送危险化学品 最小费用流 模板题
  • 原文地址:https://www.cnblogs.com/ourran/p/4610437.html
Copyright © 2011-2022 走看看