屏幕刷新问题解决方法总结

在MFC中经常遇到屏幕刷新的问题,下面是几种解决方法。 
1,刷新整个程序区域,有明显的闪烁情况 
    Invalidate( TRUE ); 
    UpdateWindow();  
2,刷新指定区域,该区域有闪烁情况,不过比上一种方法要好多了。 
    InvalidateRect( &m_rect, TRUE 0);

   UpdateWindow();  
3,还有一种方法

      InvalidateRect( &m_rect, TRUE 0);

      RedrawWindow( &m_rect, NULL, RDW_INTERNALPAINT |

                            RDW_INVALIDATE | RDW_UPDATENOW

                            | RDW_ERASE );

4, 最好的一种方法是使用 BitBlt 函数来拷贝需要刷新的区域。

5,最后,当然也可以直接使用 OnPaint() 放置在要刷新的位置。 

******************************************************************************************* 
******************************************************************************************* 
下面是引用别人的文章,我还没看;有空再看看... 
******************************************************************************************* 
无闪烁刷屏技术的实现 

在实现绘图的过程中,显示的图形总是会闪烁,笔者曾经被这个问题折磨了好久,通过向高手请教,搜索资料,问题一基本解决,现将文档整理出来以供大家参考. 
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、如何提高绘图的效率 
    实际上,在OnDraw(CDC *pDC)中绘制的图并不是所有都显示了的,例如:你在OnDraw中画了两个矩形,在一次重绘中虽然两个矩形的绘制函数都有执行,但是很有可能只有一个显示了,这是因为MFC本身为了提高重绘的效率设置了裁剪区。裁剪区的作用就是:只有在这个区内的绘图过程才会真正有效,在区外的是无效的,即使在区外执行了绘图函数也是不会显示的。因为多数情况下窗口重绘的产生大多是因为窗口部分被遮挡或者窗口有滚动发生,改变的区域并不是整个图形而只有一小部分,这一部分需要改变的就是pDC中的裁剪区了。因为显示(往内存或者显存都叫显示)比绘图过程的计算要费时得多,有了裁剪区后显示的就只是应该显示的部分,大大提高了显示效率。但是这个裁剪区是MFC设置的,它已经为我们提高了显示效率,在进行复杂图形的绘制时如何进一步提高效率呢?那就只有去掉在裁剪区外的绘图过程了。可以先用pDC->GetClipBox()得到裁剪区,然后在绘图时判断你的图形是否在这个区内,如果在就画,不在就不画。 
如果你的绘图过程不复杂,这样做可能对你的绘图效率不会有提高。 

///  无闪烁背景图绘制  // 
       程序设计: icemen (温冰)  树爱兵 
      Copyright (C) 2003.02.23. 
     你可无限制拷贝本段程序,但最好加上编程者,呵呵^_^ 
// 
BOOL CStrucView::OnEraseBkgnd(CDC* pDC) 
{        int nWidth; 
    int nHeight; 

    //CView::OnEraseBkgnd(pDC); 
    CStrucDoc* pDoc = GetDocument(); 
    ASSERT_VALID(pDoc); 
    CRect  rect; 
    GetWindowRect(&rect); 
    nWidth = rect.Width(); 
    nHeight= rect.Height(); 
     
    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)); 

/// 
   以上为画背景色                    // 
   以下为画背景图                    // 
///     
    GetClientRect(rect); 
    BITMAP bm; 
    CDC dcMem; 
    VERIFY(m_bmp.GetObject(sizeof(bm),(LPVOID)&bm)); 
    dcMem.CreateCompatibleDC(pDC); 
    CBitmap *pOldBMP =( CBitmap *)dcMem.SelectObject(&m_bmp); 
    MemDC.BitBlt( (rect.right - bm.bmWidth)/2, 
        (rect.bottom - bm.bmHeight)/2, 
        bm.bmWidth,bm.bmHeight,&dcMem,0,0,SRCCOPY); 
    dcMem.SelectObject(pOldBMP); 
/// 
   以上为画背景图                    // 
/// 
///  无闪烁背景图绘制  // 
       程序设计: icemen (温冰)  树爱兵 
      Copyright (C) 2003.02.23. 
     你可无限制拷贝本段程序,但最好加上编程者,呵呵^_^ 
// 

    pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY); 
   
    MemBitmap.DeleteObject(); 
    MemDC.DeleteDC(); 

    return TRUE; 

///  无闪烁背景图绘制  // 
       程序设计: icemen (温冰)  树爱兵 
      Copyright (C) 2003.02.23. 
     你可无限制拷贝本段程序,但最好加上编程者,呵呵^_^ 
// 

注:程序中m_bmp 为相应 的 StrucView.h中定义,为位图资源类 
protected: 
    CBitmap m_bmp; 
并应加上下面段,当然,你可在任何时候加入loadBitmap 子例程,也可加入文件资源,那由得你了!呵呵!^_^ 
CStrucView::CStrucView() 

    VERIFY(m_bmp.LoadBitmap(IDB_BITMAP3)); 


当前位置:  主页> 杂项技术> VC(MFC)>

解决Windows 程序界面闪烁问题的一些经验


 

原帖地址:http://blog.joycode.com/yaodong/archive/2004/11/26/39764.aspx

一般的windows 复杂的界面需要使用多层窗口而且要用贴图来美化,所以不可避免在窗口移动或者改变大小的时候出现闪烁。

先来谈谈闪烁产生的原因

原因一:
如果熟悉显卡原理的话,调用GDI函数向屏幕输出的时候并不是立刻就显示在屏幕上只是写到了显存里,而显卡每隔一段时间把显存的内容输出到屏幕上,这就是刷新周期。

一般显卡的刷新周期是 1/80秒左右,具体数字可以自己设置的。

这样问题就来了,一般画图都是先画背景色,然后再把内容画上去,如果这两次操作不在同一个刷新周期内完成,那么给人的视觉感受就是,先看到只有背景色的图像,然后看到画上内容的图像,这样就会感觉闪烁了。

解决方法:尽量快的输出图像,使输出在一个刷新周期内完成,如果输出内容很多比较慢,那么采用内存缓冲的方法,先把要输出的内容在内存准备好,然后一次输出到显存。要知道一次API调用一般可以在一个刷新周期内完成。

对于GDI,用创建内存DC的方法就可以了

原因二:

复杂的界面有多层窗口组成,当windows在窗口改变大小的时候是先重画父窗口,然后重画子窗口,子父窗口重画的过程一般无法在一个刷新周期内完成,所以会呈现闪烁。

我们知道父窗口上被子窗口挡住的部分其实没必要重画的

解决方法:给窗口加个风格 WS_CLIPCHILDREN ,这样父窗口上被子窗口挡住的部分就不会重画了。

如果同级窗口之间有重叠,那么需要再加上 WS_CLIPSIBLINGS 风格

原因三:

有时候需要在窗口上使用一些控件,比如IE,当你的窗口改变大小的时候IE会闪烁,即使你有了WS_CLIPCHILDREN也没用。原因在于窗口的类风格有CS_HREDRAW 或者 CS_VREDRAW,这两个风格表示窗口在宽度或者高度变化的时候重画,但是这样就会引起IE闪烁

解决方法:注册窗口类的时候不要使用这两个风格,如果窗口需要在改变大小的时候重画,那么可以在WM_SIZE的时候调用RedrawWindow。

原因四:

界面上窗口很多,而且改变大小时很多窗口都要移动和改变大小,如果使用MoveWindow或者SetWindowPos两个API来改变窗口的大小和位置,由于他们是等待窗口重画完成后才返回,所以过程很慢,这样视觉效果就可能会闪烁。

解决方法:

使用以下API来处理窗口移动,BeginDeferWindowPos, DeferWindowPos,EndDeferWindowPos先调用 BeginDeferWindowPos 设定需要移动的窗口的个数
使用DeferWindowPos,来移动窗口,这个API并不真的造成窗口移动EndDeferWindowPos 一次性完成所有窗口的大小和位置的改变。

有个地方要特别注意,要仔细计算清楚要移动多少个窗口,BeginDeferWindowPos设定的个数一定要和实际的个数一致,否则在Win9x下,如果实际移动的窗口数多于调用BeginDeferWindowPos时设定的个数,可能会造成系统崩溃。在Windows NT系列下不会有这样的问题。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值