怎么解决屏幕闪烁?

maxorang 2003-10-15 08:39:49
加精
在vc中实现绘图功能的时候屏幕经常会出现闪烁的现象,我花了很长时间都没有解决;
最近在vckbase的在线杂志中看到下面的介绍可以解决闪屏的问题!
************************************************************
我们知道,在VC++的文档、视结构中,CView的OnDraw函数用于实现绝大部分图形绘制的工作。如果用户改变窗口尺寸,或者显示隐藏的区域,OnDraw函数都将被调用来重画窗口。并且,当程序文档中的数据发生改变时,一般必须通过调用视图的Invalidate(或InvalidateRect)成员函数来通知Windows所发生的改变,对Invalidate的调用也会触发对OnDraw函数的调用。正因为OnDraw函数被频繁调用,所以在其执行时,每次都刷新填充一次视图客户区域,便会使屏幕不稳定,产生闪烁现象。
笔者通过对VC++应用程序框架结构和Windows消息映射系统的仔细研究,找到另外一种改变视图背景的方法,其执行效果比上述两种方法都好。其实在程序调用OnDraw函数之前,会触发一个Windows消息:WM_ERASEBKGND,以擦除视图刷新区域。在缺省情况下,Windows系统使用视图窗口注册时窗口类中的成员hbrBackground所描述的画刷来擦除屏幕,这一般会将屏幕刷新成COLOR_WINDOW所对应的颜色。因此,在OnDraw函数中设置背景颜色的执行过程是这样的:先将屏幕刷新成COLOR_WINDOW所对应的颜色,接着又在OnDraw函数中填充其他颜色,这正是产生屏幕闪烁的根本原因。
解决问题
通过上述分析,我们应将视图背景颜色填充移到Windows消息:WM_ERASEBKGND所对应的消息映射函数中,而不是在OnDraw函数中 。我们可以通过下列步骤实现这一过程:在文档类中增加一成员变量m_viewBkColor保存当前背景颜色,同时增加两个成员函数GetViewBkColor和SetViewBkColor对其进行读写操作。这样做的好处是可以对m_viewBkColor成员进行序列化,将其和文档联系在一起,打开某一文档时,其背景将和上一次程序操作该文档时的背景保持一致。在视图类中为视图的Windows消息WM_ERASEBKGND增加消息映射函数OnEraseBkgnd,代码如下:

BOOL CTestView::OnEraseBkgnd(CDC* pDC)
{
CRect rect;
CBrush brush;
brush.CreateSolidBrush(GetDocument()->GetViewBkColor());
pDC->GetClipBox(rect);
pDC->FillRect(rect,&brush);
return true;
}
在该函数中不需要对客户区域矩形进行设备坐标到逻辑坐标的转换,并且Windows在调用该函数时会自动进行裁剪区域的计算,使得需要刷新的屏幕面积达到最小。这样我们可以在程序中通过设计下列菜单函数轻松地改变视图背景的颜色,而且运行效果相当令人满意。
void CTestView::OnChangeViewBkcolor()
{
CColorDialog cdlg;
if(cdlg.DoModal()==IDOK)
{
GetDocument()->SetViewBkColor
(cdlg.GetColor());
InvalidateRect(NULL);
}
}
*******************************************************
我现在的问题是:这个成员m_viewBkColor跟成员函数GetViewBkColor和SetViewBkColor函数应该怎么写啊?不知道大虾们还有没有更好的办法?
...全文
4317 42 打赏 收藏 转发到动态 举报
写回复
用AI写文章
42 条回复
切换为时间正序
请发表友善的回复…
发表回复
seagis 2003-11-30
  • 打赏
  • 举报
回复
up
maxorang 2003-11-25
  • 打赏
  • 举报
回复
大家介绍的很多方法都是指绘图直接在OnDraw中绘制的,但是我绘图的时候是通过类模板来做的,在OnDraw()只有一个函数就是pDoc->Draw(pDC, 0, 0,BColor);,无论我把这个函数放在什么地方都不对还是会出现闪屏的现象,这是什么原因呢?
maxorang 2003-11-24
  • 打赏
  • 举报
回复
添加OnEraseBackGround函数,返回TRUE
感觉刷新没有能同步赶上,就是说它保存的是上一屏,没能即时的把当前屏绘制出来!
1998810 2003-11-24
  • 打赏
  • 举报
回复
我曾经写过一个中国象棋,一开始在拖动棋子时也是闪,后来用双缓存的方法可以了.不知道和你的
问题是不是差不多

MFC如何高效地绘图

[ 作者: TouchMe 添加时间: 2001-12-25 8:21:34 ]




显示图形如何避免闪烁,如何提高显示效率是问得比较多的问题。
而且多数人认为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()得到裁剪区,然后在绘图时判断你的图形是否在这个区内,如果在就画,不在就不画。
如果你的绘图过程不复杂,这样做可能对你的绘图效率不会有提高。
maxorang 2003-11-20
  • 打赏
  • 举报
回复
多谢wqs6(竹山) ,不过第一篇讲的就是我题目中的内容
dfyang 2003-11-20
  • 打赏
  • 举报
回复
添加OnEraseBackGround函数,返回TRUE
一个傻冒 2003-11-20
  • 打赏
  • 举报
回复
只要取消绘制背景。然后在ondraw函数中,将所需要的图像都画在一幅位图上,然后bitblt肯定行。一定没有闪烁。
wqs6 2003-11-18
  • 打赏
  • 举报
回复
第二篇错了
应该是
http://www.inlio.com/indexhui.asp?ID=212
还有
http://www.ccw.com.cn/htm/app/aprog/01_5_31_4.asp
wqs6 2003-11-18
  • 打赏
  • 举报
回复
看看这篇文章
http://www.ccw.com.cn/htm/app/aprog/01_2_12_4.asp
这篇也不错
http://www.cnemb.com/print.php?sid=149
maxorang 2003-11-18
  • 打赏
  • 举报
回复
ydfivy(花生薄脆饼)
在这个OnEraseBkgnd中间是不是真的能解决这个问题呢?
怎么一直都没有人回答在问题如何解决成员m_viewBkColor跟成员函数GetViewBkColor和SetViewBkColor函数应该怎么写啊?的问题呢?
一个傻冒 2003-11-16
  • 打赏
  • 举报
回复
这个问题我也碰到过.也没有得到很好的解决.我也曾想过按照DX中的方法,建立两个缓冲.然后刷向主屏.可是没有用.后来我只好,设置一些状态.比如,在程序刚初始时,在OnEraseBkgnd绘制背景.然后,设置运行时的状态.这时把背景和前景的任务全部交给OnDraw.画一屏后,bitblt.
虽然当需要位图很多时,开销很大,不过,效果还可以说得过去.
maxorang 2003-11-14
  • 打赏
  • 举报
回复
return 0
对矢量图形能达到那样的效果吗?
yellowwolf 2003-11-14
  • 打赏
  • 举报
回复
我这个问题几个月都没有解决郁闷。 我的是滚动显示的且用的是一个自己的类中stretch这个函数, 不知道如何实际, 谁能帮我啊。
yellowwolf 2003-11-14
  • 打赏
  • 举报
回复
好。。
wangzi163 2003-11-13
  • 打赏
  • 举报
回复
sorry
fanoble 2003-11-13
  • 打赏
  • 举报
回复
我把WM_ERASEBKGND给拦截了,然后return 0 :)
maxorang 2003-11-13
  • 打赏
  • 举报
回复
醉大饿极:提供的方法对于不超过视图范围内的位图是可以的,但是如果有滚动条就不行了,而且对矢量图形的支持也是有问题的!
magicprogrammer 2003-11-07
  • 打赏
  • 举报
回复
创建一个后台设备,在OnDraw中将它显示出来就行了。记住在更新世不要查除原有图像
baojian88888 2003-11-07
  • 打赏
  • 举报
回复
在注册窗口类时,
把 RegisterClassEx 函数的参数WNDCLASSEX.style 设置为0
这样在WM_SIZE时,就不会触发WM_PAINT消息

WNDCLASSEX.hbrBackground 也设置为 0
就可以避免 WM_ERASEBKGND 消息重画背景

还有一个,CreateWindowEx时注意 WS_CLIPCHILDREN 风格

用创建内存位图的方法确实是一个重要解决刷新问题的方法,有些时候也不得不用这种方法

但位图较大时,开销太大,对于配置较好的机器看不出任何问题,但在配置很差的机器上测试就可以看出差别了

呵呵,个人经验
HongHuer 2003-11-07
  • 打赏
  • 举报
回复
能不能实现,大家在应该去实践一下。

行就是行,不行就是不行。



不要想当然。
加载更多回复(22)

19,469

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 图形处理/算法
社区管理员
  • 图形处理/算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧