MFC绘制图片闪烁详解

用MFC如何高效地绘图   
          显示图形如何避免闪烁,如何提高显示效率是问得比较多的问题。   
  而且多数人认为MFC的绘图函数效率很低,总是想寻求其它的解决方案。   
  MFC的绘图效率的确不高但也不差,而且它的绘图函数使用非常简单,   
  只要使用方法得当,再加上一些技巧,用MFC可以得到效率很高的绘图程序。   
  我想就我长期(呵呵当然也只有2年多)使用MFC绘图的经验谈谈   
  我的一些观点。   
  1、显示的图形为什么会闪烁?   
          我们的绘图过程大多放在OnDraw或者OnPaint函数中,OnDraw在进行屏   
  幕显示时是由OnPaint进行调用的。当窗口由于任何原因需要重绘时,   
  总是先用背景色将显示区清除,然后才调用OnPaint,而背景色往往与绘图内容   
  反差很大,这样在短时间内背景色与显示图形的交替出现,使得显示窗口看起来   
  在闪。如果将背景刷设置成NULL,这样无论怎样重绘图形都不会闪了。   
  当然,这样做会使得窗口的显示乱成一团,因为重绘时没有背景色对原来   
  绘制的图形进行清除,而又叠加上了新的图形。   
          有的人会说,闪烁是因为绘图的速度太慢或者显示的图形太复杂造成的,   
  其实这样说并不对,绘图的显示速度对闪烁的影响不是根本性的。   
  例如在OnDraw(CDC   *pDC)中这样写:   
    pDC->MoveTo(0,0);   
    pDC->LineTo(100,100);   
  这个绘图过程应该是非常简单、非常快了吧,但是拉动窗口变化时还是会看见   
  闪烁。其实从道理上讲,画图的过程越复杂越慢闪烁应该越少,因为绘图用的   
  时间与用背景清除屏幕所花的时间的比例越大人对闪烁的感觉会越不明显。   
  比如:清楚屏幕时间为1s绘图时间也是为1s,这样在10s内的连续重画中就要闪   
  烁5次;如果清楚屏幕时间为1s不变,而绘图时间为9s,这样10s内的连续重画   
  只会闪烁一次。这个也可以试验,在OnDraw(CDC   *pDC)中这样写:   
    for(int   i=0;i<100000;i++)   
    {   
      pDC->MoveTo(0,i);   
      pDC->LineTo(1000,i);   
    }   
  呵呵,程序有点变态,但是能说明问题。   
          说到这里可能又有人要说了,为什么一个简单图形看起来没有复杂图形那么   
  闪呢?这是因为复杂图形占的面积大,重画时造成的反差比较大,所以感觉上要   
  闪得厉害一些,但是闪烁频率要低。   
          那为什么动画的重画频率高,而看起来却不闪?这里,我就要再次强调了,   
  闪烁是什么?闪烁就是反差,反差越大,闪烁越厉害。因为动画的连续两个帧之间   
  的差异很小所以看起来不闪。如果不信,可以在动画的每一帧中间加一张纯白的帧,   
  不闪才怪呢。   
    
  2、如何避免闪烁   
          在知道图形显示闪烁的原因之后,对症下药就好办了。首先当然是去掉MFC   
  提供的背景绘制过程了。实现的方法很多,   
      *   可以在窗口形成时给窗口的注册类的背景刷付NULL   
      *   也可以在形成以后修改背景   
    static   CBrush   brush(RGB(255,0,0));   
    SetClassLong(this->m_hWnd,GCL_HBRBACKGROUND,(LONG)(HBRUSH)brush);   
      *   要简单也可以重载OnEraseBkgnd(CDC*   pDC)直接返回TRUE   
          这样背景没有了,结果图形显示的确不闪了,但是显示也象前面所说的一样,   
  变得一团乱。怎么办?这就要用到双缓存的方法了。双缓冲就是除了在屏幕上有   
  图形进行显示以外,在内存中也有图形在绘制。我们可以把要显示的图形先在内存中绘制好,然后再一次性的将内存中的图形按照一个点一个点地覆盖到屏幕上去(这个过程非常快,因为是非常规整的内存拷贝)。这样在内存中绘图时,随便用什么反差大的背景色进行清除都不会闪,因为看不见。当贴到屏幕上时,因为内存中最终的图形与屏幕显示图形差别很小(如果没有运动,当然就没有差别),这样看起来就不会闪。   
    
  3、如何实现双缓冲   
          首先给出实现的程序,然后再解释,同样是在OnDraw(CDC   *pDC)中:   
    CDC   MemDC;   //首先定义一个显示设备对象   
    CBitmap   MemBitmap;//定义一个位图对象   
    //随后建立与屏幕显示兼容的内存显示设备   
    MemDC.CreateCompatibleDC(NULL);   
    //这时还不能绘图,因为没有地方画   ^_^   
    //下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小   
    MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);   
        
    //将位图选入到内存显示设备中   
    //只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上   
    CBitmap   *pOldBit=MemDC.SelectObject(&MemBitmap);   
    //先用背景色将位图清除干净,这里我用的是白色作为背景   
    //你也可以用自己应该用的颜色   
    MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));   
    //绘图   
    MemDC.MoveTo(……);   
    MemDC.LineTo(……);   
      
    //将内存中的图拷贝到屏幕上进行显示   
    pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);   
    //绘图完成后的清理   
    MemBitmap.DeleteObject();   
    MemDC.DeleteDC();   
  上面的注释应该很详尽了,废话就不多说了。   
    
  4、如何提高绘图的效率   
          我主要做的是电力系统的网络图形的CAD软件,在一个窗口中往往要显示成千上万个电力元件,而每个元件又是由点、线、圆等基本图形构成。如果真要在一次重绘过程重画这么多元件,可想而知这个过程是非常漫长的。如果加上了图形的浏览功能,鼠标拖动图形滚动时需要进行大量的重绘,速度会慢得让用户将无法忍受。怎么办?只有再研究研究MFC的绘图过程了。   
          实际上,在OnDraw(CDC   *pDC)中绘制的图并不是所有都显示了的,例如:你   
  在OnDraw中画了两个矩形,在一次重绘中虽然两个矩形的绘制函数都有执行,但是很有可能只有一个显示了,这是因为MFC本身为了提高重绘的效率设置了裁剪区。裁剪区的作用就是:只有在这个区内的绘图过程才会真正有效,在区外的是无效的,即使在区外执行了绘图函数也是不会显示的。因为多数情况下窗口重绘的产生大多是因为窗口部分被遮挡或者窗口有滚动发生,改变的区域并不是整个图形而只有一小部分,这一部分需要改变的就是pDC中的裁剪区了。因为显示(往内存或者显存都叫显示)比绘图过程的计算要费时得多,有了裁剪区后显示的就只是应该显示的部分,大大提高了显示效率。但是这个裁剪区是MFC设置的,它已经为我们提高了显示效率,在进行复杂图形的绘制时如何进一步提高效率呢?那就只有去掉在裁剪区外的绘图过程了。可以先用pDC->GetClipBox()得到裁剪区,然后在绘图时判断你的图形是否在这个区内,如果在就画,不在就不画。   
  如果你的绘图过程不复杂,这样做可能对你的绘图效率不会有提高

==============================================================================

在OnEraseBkGnd中,如果你不调用原来缺省
的OnEraseBkGnd只是重画背景则不会有闪烁.而在OnPaint里面,
由于它隐含的调用了OnEraseBkGnd,而你又没有处理OnEraseBkGnd
函数,这时就和窗口缺省的背景刷相关了.缺省的
OnEraseBkGnd操作使用窗口的缺省背景刷刷新背景(一般情况
下是白刷),而随后你又自己重画背景造成屏幕闪动.
另外一个问题是OnEraseBkGnd不是每次都会被调用的.如果你
调用Invalidate的时候参数为TRUE,那么在OnPaint里面隐含
调用BeginPaint的时候就产生WM_ERASEBKGND消息,如果参数
是FALSE,则不会重刷背景.

所以解决方法有三个半:
1.用OnEraseBkGnd实现,不要调用原来的OnEraseBkGnd函数.
2.用OnPaint实现,同时重载OnEraseBkGnd,其中直接返回.
3.用OnPaint实现,创建窗口时设置背景刷为空
4.用OnPaint实现,但是要求刷新时用Invalidate(FALSE)这样
的函数.(不过这种情况下,窗口覆盖等造成的刷新还是要闪一
下,所以不是彻底的解决方法)
都挺简单的.
------------------------------------------------------
在MFC中 任何一个window组件的绘图 都是放在这两个member function中
在设定上 OnEraseBkgnd()是用来画底图的 而OnPaint()是用来画主要对象的
举例说明 一个按钮是灰色的 上面还有文字
则OnEraseBkgnd()所做的事就是把按钮画成灰色
而OnPaint()所做的事 就是画上文字

既然这两个member function都是用来画出组件的
那为何还要分OnPaint() 与 OnEraseBkgnd() 呢
其实OnPaint() 与 OnEraseBkgnd() 特性是有差的
1. OnEraseBkgnd()的要求是快速 在里面的绘图程序最好是不要太耗时间
因为 每当window组件有任何小变动 都会马上呼叫OnEraseBkgnd()
2. OnPaint() 是只有在程序有空闲的时候才会被呼叫
3. OnEraseBkgnd() 是在 OnPaint() 之前呼叫的
所以 OnPaint()被呼叫一次之前 可能会呼叫OnEraseBkgnd()好几次

如果我们是一个在做图形化使用者接口的人
常会需要把一张美美的图片设为我们dialog的底图
把绘图的程序代码放在OnPaint() 之中 可能会常碰到一些问题
比方说拖曳一个窗口在我们做的dialog上面一直移动
则dialog会变成灰色 直到动作停止才恢复
这是因为每次需要重绘的时候 程序都会马上呼叫OnEraseBkgnd()
OnEraseBkgnd()就把dialog画成灰色
而只有动作停止之后 程序才会呼叫OnPaint() 这时才会把我们要画的底图贴上去

这个问题的解法 比较差点的方法是把OnEraseBkgnd() 改写成不做事的function
如下所示
BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
}
以上本来是会呼叫CDialog::OnEraseBkgnd() 但是如果我们不呼叫的话
程序便不会画上灰色的底色了

比较好的做法是直接将绘图的程序从OnPaint()移到OnEraseBkgnd()来做
如下所示

// m_bmpBKGND 为一CBitmap对象 且事先早已加载我们的底图
// 底图的大小与我们的窗口client大小一致

BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
CRect rc;
GetUpdateRect(&rc);
CDC srcDC;
srcDC.CreateCompatibleDC(pDC);
srcDC.SelectObject(m_bmpBKGND);

pDC->BitBlt(rc.left,rc.top,rc.GetWidth(),
rc.GetHeight(),&srcDC,rc.left,rc.top,SRCCOPY);
return TRUE;
}

特别要注意的是 取得重画大小是使用GetUpdateRect() 而不是GetClientRect()
如果使用GetClientRect() 会把不该重画的地方重画

时间: 2024-10-10 16:24:07

MFC绘制图片闪烁详解的相关文章

常见图片格式详解

标明原作者信息 http://www.cnblogs.com/xiangism 做了几年有关图形.图像的工作,对图片格式算是小有经验,在此写成一文章总结下.虽然一开始并不想讲很理论的东西,但写完后发现几乎全是理论,细想一下关于图片格式的知识本身就是理论的东西,囧~~ 那就力求用最简单的方式将这些"理论"讲清楚吧. 常见的图片格式有bmp, jpg(jpeg), png, gif, webp等. 图像基本数据结构 要讲图片格式还先得从图像的基本数据结构说起.在计算机中, 图像是由一个个像

MFC下CSocket编程详解

MFC下CSocket编程详解 分类: C/C++2008-03-13 09:01 34465人阅读 评论(34) 收藏 举报 mfc编程socket服务器socketsstream MFC下CSocket编程详解: 1. 常用的函数和注意事项(详细的函数接口说明请查看MSDN): CSocket::Create 初始化(一般写服务器程序都不要用为好,用下面的 CSocket::Socket 初始化) CSocket::Socket初始化 CSocket::SetSockOpt 设置socket

ImageMagick之图片裁剪详解

ImageMagick之图片裁剪详解 imagemagick 的 convert 命令通过 crop 参数,可以把一幅大图片分成若干块大小一样的图片,同时也可以在大图上截取一块图片来.命令格式为 convert 原始图片 -crop widthxheight+x+y 目标图片 其中 widthxheight 是目标图片的尺寸,+x+y 是原始图片的坐标,这两组值至少要出现一组,也可以同时存在.另外该命令也可使用 gravity来重新定义坐标系统.关于更多 gravity 的信息,请参考:Imag

[MFC美化] SkinSharp使用详解2-SkinH.h函数介绍

SkinSharp功能强大,该皮肤库支持完全多种颜色改变等. 下面是静态链接库时的SkinH.h头文件: /*在Stdafx.h文件中加入如下语句 #include "SkinH.h" #pragma comment(lib, "Detours.lib") #pragma comment(lib, "SkinH_ST.lib") **************** 根据编译选项在链接选项中做如下设置 *********** 下表显示根据要使用的运行

[MFC美化] USkin使用详解-使用方法

该种皮肤库资料很少,用法与前面几种类似. 它主要有:USkin.dll ,USkin.lib,USkin.h和Sakura.msstyles这四个文件.皮肤格式是.u3.SkinBuilder是USkin界面换肤软件.u3文件制作环境 . 以下是摸索出来的用法,如有错误请指正. 一.使用方法 1. 将这四个文件拷贝至相应文件夹下..lib和.h放在工程文件夹,.dll和Sakura.msstyles放到debug文件夹下. 2. 在工程stdafx.h文件中加入USkin.h和USkin.lib

html5 Canvas绘制图形入门详解

html5,这个应该就不需要多作介绍了,只要是开发人员应该都不会陌生.html5是「新兴」的网页技术标准,目前,除IE8及其以下版本的IE浏览器之外,几乎所有主流浏览器(FireFox.Chrome.Opera.Safari.IE9+)都已经开始支持html5了.除此之外,在移动浏览器市场上,众多的移动浏览器也纷纷展开关于「html5的支持能力以及性能表现」的军备竞赛.html作为革命性的网页技术标准,再加上众多浏览器厂商或组织的鼎力支持,可以想见,html5将会成为未来网页技术的领头羊. ht

MFC消息映射机制详解

 MFC消息映射机制: 在每个能接收和处理消息的类中,定义一个消息与消息处理函数的映射表,即消息映射表.MFC有一个窗口句柄与C++对象指针的映射表,当窗口收到消息时,消息的第一个参数指明了该消息与哪个窗口句柄相关,通过映射表找到C++对象指针,然后将这个指针传递给应用程序框架窗口类的基类,基类调用WindowProc函数(在wincore.cpp文件中),这是一个虚函数,函数内部调用OnWndMsg函数,消息处理就是在这个函数内完成的,该函数也在wincore.cpp中.OnWndMsg函

常见图片格式详解系列(一) ----简介

常见图片格式介绍 信息时代,丰富多彩的世界,我们用图片来感知,来记忆,来存储.多姿多彩的图片格式,你是否了解其中奥妙呢.接下来的系列文章就要带大家详细解析常见图片格式. (一) BMP BMP格式,是windows的一种位图格式,同时也是一种未经过压缩的格式,所以占用的存储空间比较大,而且对于浏览器等网络应用并不支持,不过对于windows而言,可以搞一张bmp的图像来充当背景,相对比较清晰. (二)JPG jpeg格式是一种比较常见的图像格式,是一种经过JPEG算法压缩的图形格式,其占用存储空

常见图片格式详解系列(二)---BMP位图--

BMP BMP是windows的一种图片格式,其组织方式相对简单,一个简单表示bmp文件的头结构 (BITMAPFILEHEAER)+ 一个表示图片信息的结构(BITMAPINFOHEADER)+ 一个表示调色板的结构(可选).剩下的便是存储的每一个像素点对应的R,G,B值. BITMAPFILEHEADER WORD bfType:表示文件类型,该值必须是0x424D,即字符'BM',否则便不是BMP图片.DWORD bfSize:以字节为单位,表示整个图像文件的大小.如需要添加列表可继续从左