调用Invalidate()造成屏幕闪动的问题,请教... 我在屏幕上载入了几幅图片(把JPG图片放在了橡皮筋类的框中,实现图片的拖动及放大缩小),然后在鼠标左键点击的响应函数中调用了Invalidate()函数对窗口进行刷新,结果就造成了闪动,请指教一下解决的思路,谢谢... 解决方案 » 免费领取超大流量手机卡,每月29元包185G流量+100分钟通话, 中国电信官方发货 显示图形如何避免闪烁 显示图形如何避免闪烁,如何提高显示效率是问得比较多的问题。而且多数人认为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()得到裁剪区,然后在绘图时判断你的图形是否在这个区内,如果在就画,不在就不画。如果你的绘图过程不复杂,这样做可能对你的绘图效率不会有提高。 你可以写一个控件类,继承于CWnd,将其与你要操作的图片关联起来,然后在你的对话框类的OnLButtonDown(UINT nFlags, CPoint point)函数中动态创建一个“控件”,之后就可以使用鼠标对其拖动,伸缩了,好比在资源编辑中对按钮等控件那样操作。 在用MoveWindow的前后各InvalidateRect一下,用橡皮筋类来获取“控件”区域。我是这样做的,当然是会有点儿闪烁的,不过还行吧。 解决闪烁通常有三种方法:父窗口设置WS_CLIPCHILDREN风格;控件响应WM_ERASEBKGND消息直接返回TRUE;控件使用双缓冲,根据实际需要来选择。如果控件绘制速度较慢,是没法彻底解决闪烁问题的。 使用双缓冲吧 记住在OnDraw或Onpait中直接绘图在你鼠标Down和Up的时候都不要使用Invalidate。实在必要的可以加内存绘图。一般只需要在鼠标移动的过程当中使用内存绘图就行了。 同意楼上的说法,我前几天也遇到这个问题,多亏CSDN上的朋友帮助,使用双缓冲就不闪了.如果LZ要源码的话,我可以发给你。 不知道你清楚Invalidate()的含意没.调用此函数全客户区都会刷新 当然会有闪动.你在拉伸时使用InvalidateRect()来控制只刷新图片区域 这样只会刷新图片的区域 进行时实的显示图片.也可以在拉伸完成一次调用Invalidate()来刷新 这样就在拉伸图片时不能时实看到图片变化的大小. 大致如楼上所言1、建立一个类变量:m_bNeedErase,根据需要设定其值,通常没有图片显示时,为真(调用默认背景擦除),否则为假(关闭默认背景擦除)2、处理类似:OnEraseBkgnd(CDC* pDC)方法里面的代码大概这样: if(!m_bNeedErase) return FALSE; else return CView::OnEraseBkgnd(pDC);3、在绘制方法中使用内存DC绘制,如下是一个PicView的简单绘制代码,供参考:void CSPicView::OnDraw(CDC* pDC){ CRect rect; this->GetClientRect(&rect); static CBrush brush(RGB(255,255,255)); if(!m_bReady) { pDC->FillRect(&rect,&brush); m_bNeedErase = TRUE; return ; } if(m_pRecPicPage->m_pImage == NULL) { pDC->FillRect(&rect,&brush); m_bNeedErase = TRUE; return ; } m_bNeedErase= FALSE; CBitmap memBitmap; CDC memDC; memDC.CreateCompatibleDC(pDC); memBitmap.CreateCompatibleBitmap(pDC,rect.right,rect.bottom); CBitmap* pOldBmp= memDC.SelectObject(&memBitmap); memDC.FillRect(&rect,&brush); m_pRecPicPage->m_pImage->Draw(memDC.GetSafeHdc(),-m_x+5,-m_y+5,m_lWidth-m_x+5,m_lHeight-m_y+5); pDC->BitBlt(rect.left,rect.top,rect.right,rect.bottom,&memDC,0,0,SRCCOPY); memDC.SelectObject(pOldBmp); memDC.DeleteDC(); memBitmap.DeleteObject();} TO9楼:请发给我学习下,谢谢,我的邮箱是:[email protected] 要看Invalidate()写在哪里,如果是写在onpaint 里,当然会一直闪啊,因为Invalidate()本来就是调用onpaint ,所以一直循环的重画啊,所以不要写在onpaint 里,写在buttonup 或 buttondown 里 OnEraseBkgnd(CDC* pDC)return TRUE;先屏蔽背景刷新,然后再双缓冲CPoint ptCenter; CRect rect,ellipseRect; GetClientRect(&rect); ptCenter = rect.CenterPoint(); CDC dcMem; //用于缓冲作图的内存DC CBitmap bmp; //内存中承载临时图象的位图 dcMem.CreateCompatibleDC(pDC); //依附窗口DC创建兼容内存DC bmp.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());//创建兼容位图 dcMem.SelectObject(&bmp); //将位图选择进内存DC //按原来背景填充客户区,不然会是黑色 dcMem.FillSolidRect(rect,pDC->GetBkColor()); for(int i=20;i>0;i--) //在内存DC上做同样的同心圆图象 { ellipseRect.SetRect(ptCenter,ptCenter); ellipseRect.InflateRect(i*10,i*10); dcMem.Ellipse(ellipseRect); } pDC->BitBlt(0,0,rect.Width(),rect.Height(), &dcMem,0,0,SRCCOPY);//将内存DC上的图象拷贝到前台 dcMem.DeleteDC(); //删除DC bm.DeleteObject(); //删除位图 OnEraseBkgnd(CDC* pDC)return TRUE; 我用了CFormView 派生类,没有OnEraseBkgnd(CDC* pDC)函数怎么办 客户端4个线程同时向服务端发信息,服务端刚开始正常,过不一分钟WSASend时,返回错误997,为什么? 怎么学好计算机? 怎样在单文档中显示对话框? 一个编译错误! 怎么样将一段(MPEG2)视频刻录成Dvd光盘,可以在Dvd机中播放。 关于属性页的问题,高分相送,解决就给分!!!! test 关于RTTI? CGI、ISAPI程序使用临时文件时,内存空间应控制在多大合适?给出理由。 求教--如何让程序运行得更快? . 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()得到裁剪区,然后在绘图时判断你的图形是否在这个区内,如果在就画,不在就不画。
如果你的绘图过程不复杂,这样做可能对你的绘图效率不会有提高。
我前几天也遇到这个问题,多亏CSDN上的朋友帮助,使用双缓冲就不闪了.
如果LZ要源码的话,我可以发给你。
你在拉伸时使用InvalidateRect()来控制只刷新图片区域 这样只会刷新图片的区域 进行时实的显示图片.
也可以在拉伸完成一次调用Invalidate()来刷新 这样就在拉伸图片时不能时实看到图片变化的大小.
1、建立一个类变量:m_bNeedErase,根据需要设定其值,通常没有图片显示时,为真(调用默认背景擦除),否则为假(关闭默认背景擦除)
2、处理类似:OnEraseBkgnd(CDC* pDC)方法
里面的代码大概这样:
if(!m_bNeedErase) return FALSE;
else return CView::OnEraseBkgnd(pDC);3、在绘制方法中使用内存DC绘制,如下是一个PicView的简单绘制代码,供参考:
void CSPicView::OnDraw(CDC* pDC)
{
CRect rect;
this->GetClientRect(&rect);
static CBrush brush(RGB(255,255,255)); if(!m_bReady)
{
pDC->FillRect(&rect,&brush);
m_bNeedErase = TRUE;
return ;
}
if(m_pRecPicPage->m_pImage == NULL)
{
pDC->FillRect(&rect,&brush);
m_bNeedErase = TRUE;
return ;
} m_bNeedErase= FALSE;
CBitmap memBitmap;
CDC memDC;
memDC.CreateCompatibleDC(pDC);
memBitmap.CreateCompatibleBitmap(pDC,rect.right,rect.bottom);
CBitmap* pOldBmp= memDC.SelectObject(&memBitmap);
memDC.FillRect(&rect,&brush);
m_pRecPicPage->m_pImage->Draw(memDC.GetSafeHdc(),-m_x+5,-m_y+5,m_lWidth-m_x+5,m_lHeight-m_y+5);
pDC->BitBlt(rect.left,rect.top,rect.right,rect.bottom,&memDC,0,0,SRCCOPY);
memDC.SelectObject(pOldBmp);
memDC.DeleteDC();
memBitmap.DeleteObject();}
return TRUE;
先屏蔽背景刷新,然后再双缓冲CPoint ptCenter;
CRect rect,ellipseRect;
GetClientRect(&rect);
ptCenter = rect.CenterPoint();
CDC dcMem; //用于缓冲作图的内存DC
CBitmap bmp; //内存中承载临时图象的位图
dcMem.CreateCompatibleDC(pDC); //依附窗口DC创建兼容内存DC
bmp.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());//创建兼容位图
dcMem.SelectObject(&bmp); //将位图选择进内存DC
//按原来背景填充客户区,不然会是黑色
dcMem.FillSolidRect(rect,pDC->GetBkColor());
for(int i=20;i>0;i--) //在内存DC上做同样的同心圆图象
{
ellipseRect.SetRect(ptCenter,ptCenter);
ellipseRect.InflateRect(i*10,i*10);
dcMem.Ellipse(ellipseRect);
}
pDC->BitBlt(0,0,rect.Width(),rect.Height(),
&dcMem,0,0,SRCCOPY);//将内存DC上的图象拷贝到前台
dcMem.DeleteDC(); //删除DC
bm.DeleteObject(); //删除位图
return TRUE;