MFC界面避免闪烁问题

本文是我在做MFC贪吃蛇时所学到的。大家可以通过我那篇MFC贪吃蛇的代码一起来理解更为好些。

我做的界面是每隔多少秒来刷新一次,虽然用了双缓冲,但是还要OnEraseBkgnd消息

函数的帮忙。因为每次调用OnPaint()或OnDraw()函数时,他会自动刷新背景哦,所以

界面还是有点闪烁的。而这个消息函数正好解决了这个问题,而这2方面的资料如下所

写的,都是我百度找的很好的资料。希望大叫一起交流学习。如有不好的地方,请见谅!

 

 

 MFC GDI双缓冲避免图形闪烁(转)

 
多数人认为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()得到裁剪区,然后在绘图时判断你的图形是否在这个区内,如果在就画,不
在就不画。
如果你的绘图过程不复杂,这样做可能对你的绘图效率不会有提高。以一个例子
来说明一些具体如何解决问题.
我想让一个区域动起来,如何解决窗口刷新时区域的闪烁。

void CJhkljklView::OnDraw(CDC* pDC)

{

 CJhkljklDoc* pDoc = GetDocument();

 ASSERT_VALID(pDoc);

 // TODO: add draw code for native data here

 int i;

 int x[20],y[20];

 CPen hPen;

 POINT w[5];

 

 x[0]=a/100+10;

 x[1]=a/100+30;


 x[2]=a/100+80;

 x[3]=a/100+30;

 x[4]=a/100+10;

 y[0]=10;

 y[1]=10;

 y[2]=25;

 y[3]=40;

 y[4]=40;

 for (i=0;i<5;i++)

 { w[i].x=x[i];

 w[i].y=y[i];

 }

 //CClientDC dc(this);

 //hPen=CreatePen(PS_SOLID,1,RGB(255,0,0));

 CRgn argn,Brgn;

 CBrush abrush(RGB(40,30,20));

 argn.CreatePolygonRgn(w, 5, 1);// point为CPoint数组,

 pDC->FillRgn(&argn, &abrush);

 abrush.DeleteObject();

}

void CJhkljklView::OnTimer(UINT nIDEvent)

{

 // TODO: Add your message handler code here and/or call default


 InvalidateRect(NULL,true);

 UpdateWindow();

 a+=100;

 CView::OnTimer(nIDEvent);

}

int CJhkljklView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

 if (CView::OnCreate(lpCreateStruct) == -1)

 return -1;

 

 // TODO: Add your specialized creation code here

 SetTimer(1,10,NULL);

 return 0;

}
利用定时器直接进行10毫秒的屏幕刷新,这样效果会出现不停的闪烁的情况.
解决方法利用双缓冲,首先触发WM_ERASEBKGND,然后修改返回TRUE;
定义变量:

CBitmap *m_pBitmapOldBackground ;

CBitmap m_bitmapBackground ;

CDC m_dcBackground;

//绘制背景

if(m_dcBackground.GetSafeHdc()== NULL|| (m_bitmapBackground.m_hObject == NULL))

 {


 m_dcBackground.CreateCompatibleDC(&dc);

 m_bitmapBackground.CreateCompatibleBitmap(&dc,rect.Width(),rect.Height()) ;

 m_pBitmapOldBackground = m_dcBackground.SelectObject(&m_
bitmapBackground) ;

 //DrawMeterBackground(&m_dcBackground, rect);

 CBrush brushFill, *pBrushOld;

 // 背景色黑色

 brushFill.DeleteObject();

 brushFill.CreateSolidBrush(RGB(255, 255, 255));

 pBrushOld = m_dcBackground.SelectObject(&brushFill);

 m_dcBackground.Rectangle(rect);

 m_dcBackground.SelectObject(pBrushOld);

 }

 memDC.BitBlt(0, 0, rect.Width(), rect.Height(),

 &m_dcBackground, 0, 0, SRCCOPY) ;

 //绘制图形

 int i;

 int x[20],y[20];

 CPen hPen;

 POINT w[5];

 

 x[0]=a/100+10;

 x[1]=a/100+30;

 x[2]=a/100+80;


 x[3]=a/100+30;

 x[4]=a/100+10;

 

 y[0]=10;

 y[1]=10;

 y[2]=25;

 y[3]=40;

 y[4]=40;

 

 

 

 for (i=0;i<5;i++)

 { w[i].x=x[i];

 w[i].y=y[i];

 }

 //CClientDC dc(this);

 //hPen=CreatePen(PS_SOLID,1,RGB(255,0,0));

 CRgn argn,Brgn;

 CBrush abrush(RGB(40,30,20));

 argn.CreatePolygonRgn(w, 5, 1);// point为CPoint数组,

 memDC.FillRgn(&argn, &abrush);

 abrush.DeleteObject();


}
这样编译运行程序就会出现屏幕不闪烁的情况了.

 

OnEraseBkgnd资料:

http://baike.baidu.com/view/3988473.htm 


界面避免闪烁二(转):

http://blog.csdn.net/r3000/article/details/5454262

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值