窗体闪烁的问题

今天老外报了个defect,跟窗体闪烁相关的,不过俺是看了好几遍也没察觉到,但是既然老外提出来了,还是要认真对待。一个如下图的窗体,由父窗体Pop-up和两个子窗体Title & TreeList组成,父窗体的边框由默认函数绘制,Title比较简单,画上一段文字和两个图标,TreeList部分使用了双缓冲绘制。设置了断点后,观察到pop-up边框和Title先绘制完成然后TreeList绘制之前,在Treelist那里是背景组成的一个框,然后在TreeList的backbuffer 绘制出来时整个TreeList显示出来,可能是这里造成的闪烁(还需要确认一下)。

问题的可能解决方法是对整个pop-up进行双缓冲绘制。

在网上搜索了一下,找到一片相当不错跟闪烁相关的文章,转载一下:

原文地址: http://www.cnblogs.com/loveshrek/archive/2007/03/23/685121.html

摘抄部分:

什么是闪烁

闪烁可以这样定义:当后面一幅图像以很快的速度画在前面一幅图像上时,在后面图像显示前,你可以很快看到前面那一个图像,这样的现象就是闪烁。我认为,闪烁会让使用者对程序很不满,原因是:如果用户接口编码如此糟糕,那么程序的其他部分呢,如何能相信数据的正确性呢?一个具有平滑,快速相应的程序会给用户带来信心,这个道理很简单。

程序出现闪烁可以由多种形式造成,最常见的原因是窗口大小发生改变时,其内容重画造成闪烁。

仅仅画一次

这是一个黄金法则,在任何计算机(Windows或者你使用的任何操作系统)上处理画法逻辑都需要遵循,即永远不要将同一像素画两次。一个懒惰的程序员常常不愿意在画法逻辑上投入过多精力,而是采用简单的处理逻辑。要避免闪烁,就需要确保不会出现重复绘制的情况发生。现在,WIndows和计算机还是很笨的,除非你给他们指令,否则他们不会做任何事情。如果闪烁的现象发生,那是因为你的程序刻意地多绘制了屏幕的某些区域造成的. 这个现象可能是因为一些明确的命令,或者一些被你忽视了的地方。如果程序有闪烁的现象出现,你需要你知道如何找到好的方案去解决这个问题。

如果还是不能解决

 有些时候,当你花了很多努力去考虑非常好的画法时,发现窗口还是会被全部刷新。这通常是由两个Window 类的属性造成的:CS_VREDRAWCS_HREDRAW。如果有其中一个标志被设置时,那么当窗口水平或者竖直方向有大小被改变时,其内容每次都会被重新刷新。所有,你需要关掉这两个标志,解决的唯一的方式是在创建窗体和窗体类被注册时,确保这两个属性不被设置。

WNDCLASSEX wc;
wc.cbSize  = sizeof(wc);
wc.style   = 0; /* CS_VREDRAW | CS_HREDRAW; */
...
RegisterClassEx(&wc);

上面的例子描述了当窗体类被注册时,这两个属性不被设置的实现方法。

有一点需要注意:如果主窗体有了这两个属性,即使子窗体没有重画标志,会导致所有子窗体在其大小被改变时会被重绘。可以通过以下方式避免这个情况发生:

剪切子窗体
有时,闪烁的原因是因为当重画时,父窗体没有剪切其子窗体区域。这样的结果导致,整个父窗口内容被重画,而子窗体又被显示在了上面(造成闪烁)。这个可以通过在父窗体上设置WS_CLIPCHILDREN 来解决。当这个标志被设置时,被子窗体占据的任何区域将会被排除在更新区域外。因此,即使你尝试在子窗体所在的位置上绘制(父窗口的内容),BeginPaint中的剪切区域也会阻止其绘制效果。
双缓冲和内存设备描述表(Memory Device Context, 简称Memory-DC)

常见的彻底避免闪烁的方法是使用双缓冲。其基本的思路是:将窗体的内容画在屏幕外的一个缓冲区内,然后,将该缓冲区的内容再传递到屏幕上(使用BilBlt函数)。这是一个非常好的减少闪烁的方法,但是经常被滥用,特别是当程序员并不真正地理解如何有效地绘制窗口时。

典型的双缓冲代码如下:

HDC          hdcMem;
HBITMAP      hbmMem;
HANDLE       hOld;
PAINTSTRUCT  ps;
HDC          hdc;
....
case WM_PAINT:
// Get DC for window
hdc = BeginPaint(hwnd, &ps);
// Create an off-screen DC for double-buffering
hdcMem = CreateCompatibleDC(hdc);
hbmMem = CreateCompatibleBitmap(hdc, win_width, win_height);
hOld   = SelectObject(hdcMem, hbmMem);
// Draw into hdcMem
// Transfer the off-screen DC to the screen
BitBlt(hdc, 0, 0, win_width, win_height, hdcMem, 0, 0, SRCCOPY);
// Free-up the off-screen DC
SelectObject(hdcMem, hOld);
DeleteObject(hbmMem);
DeleteDC    (hdcMem);
EndPaint(hwnd, &ps);
return 0; 

这个方法比较慢,因为在每次窗体需要重画的时候内存设备描述表(Memory-DC)都需要被重新创建。更有效的方法是,仅仅创建内存设备描述表(Memory-DC)一次,并使其足够大到能满足任何时候的整个窗体刷新。当程序结束时,再销毁这个内存设备描述表(Memory-DC)。这两种方法都存在对内存开销的问题,特别是如果内存设备描述表(Memory-DC)是针对真个屏幕的大小。双缓冲也需要两倍的时间去画。这是因为其第一次是在内存设备描述表(Memory-DC)上画,然后再使用BitBlt画回到屏幕上。当然,好的显卡会使BitBlt更快,但是仍然会耗CPU 时间。

如果程序需要显示相当复杂的信息,比如像网页,那么你应该使用内存设备描述表(Memory-DC)。比如IE,如果不使用双缓冲,是没有办法在绘制网页时不闪烁的。

没有必要将双缓冲技术用于整个窗体的绘制中。可以这样设想,窗口中仅仅有一个小部分包含了复杂的图形对象(比如半透明的位图或者其他)。你应该将内存设备描述表(Memory-DC)仅仅用于着一个小区域,其他区域使用常规的方法。 有时,通过仔细的思考,经常可以避免使用双缓冲而直接将结果画到屏幕上。只要你不破坏黄金法则,即“永远不要将一个像素画两次”,就可以防止闪烁的出现。

 

对于双缓冲,上面的代码是基本的算法,老外的提示了一个更方便的方法,就是用CreatEx创建窗体是设置Extend style为 WS_EX_COMPOSITED,然后就默认的双缓冲绘制了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值