相信很多人在做图形界面开发时,常常会遇到屏幕闪烁的情况,当然我也不例外。前段时间用vc++做了一个小游戏——五子棋,前期阶段主要做的是逻辑层面的编码,没有太注意屏幕闪烁的情况,到了后来实现悔棋功能时需要擦除已下过的棋子进行重绘,屏幕闪烁厉害,急需解决——有哪个玩家愿意玩屏幕老闪烁的游戏? 通常来说程序根据需要调用Invalidate(FALSE)使窗口客户区无效引起重绘,然后在窗口OnPaint函数(基于文档视图的程序则是OnDraw)中进行稳定绘图就行了。但是,我们在OnPaint中进行多重绘制(画背景、棋盘、棋子等),前后绘制的反差造成了闪烁现象。以前知道Java中解决屏幕闪烁问题是用双缓冲的方法,现在发现在vc++中也是可以这么做的。简单来说,双缓冲就是先把需要绘制的东西全部一口气画在内存中,最后把内存中的数据搬到屏幕上显示。 下面是双缓冲的代码实现例子: 点击(此处)折叠或打开 void C****Dlg::OnPaint() { if (IsIconic()) { //...... } else { //CDialog::OnPaint(); //不要调用这个 CPaintDC dc(this);//对话框的dc//通常CPaintDC用来响应WM_PAINT消息。 //CPaintDC是从CDC派生出来的:在构造时自动调用CWnd::BeginPaint,析构时调用CWnd::EndPaint。 RECT rect;// 客户区矩形 GetClientRect(&rect); // 使用双缓冲避免屏幕刷新时闪烁 CDC dcMem;// 内存dc CBitmap bmpMem; // 位图 dcMem.CreateCompatibleDC(NULL);// 创建兼容dc bmpMem.CreateCompatibleBitmap(&dc, rect.right-rect.left, rect.bottom-rect.top);//创建跟客户区域大小一样的(空)位图 // 把位图选到设备上下文环境中 CBitmap *pOld = dcMem.SelectObject(&bmpMem); // dcMem.FillSolidRect(&rect, RGB(255,255,255)); // 在此处将绘制内容全画到dcMem内存中,(即把之前使用CPaintDC绘制的dc换成dcMem即可) DrawTable(dcMem);//画棋盘 DrawChesses(dcMem); // 画棋子 //...... // 至此,内存中绘图完毕 // 从内存拷贝到设备dc dc.BitBlt(0, 0, rect.right - rect.left, rect.bottom - rect.top, &dcMem, 0, 0, SRCCOPY); dc.SelectObject(pOld); // 释放资源 bmpMem.DeleteObject(); dcMem.DeleteDC(); } } PS:屏幕闪烁问题虽然得到解决了,但是窗口上的按钮却还会闪(可能是因为使用图片按钮的缘故才那么明显),当然这个我也是无法容忍的。 默认情况窗口风格没有设置了WS_CLIPCHILDREN属性,所以父窗口刷新时子窗口也跟着刷新,于是产生按钮闪烁现象,于是我在游戏开始时给窗口加上WS_CLIPCHILDREN属性: ModifyStyle(0, WS_CLIPCHILDREN); 这样Invalidate 时按钮就不会闪烁了。 如果窗口加上了WS_CLIPCHILDREN属性,当需要切换背景图片时,按钮因为没有刷新所以会被盖住,直到(鼠标移到按钮上)重绘时才会显示出来。 解决方法: 1)添加BOOL类型的成员变量bgroundChanged,初始化为FALSE; 2)在切换背景图片前调用ModifyStyle(WS_CLIPCHILDREN, 0)去掉WS_CLIPCHILDREN属性,并把bgroundChanged设置为TRUE; 3)在OnPaint中最后增加 if (TRUE == bgroundChanged) { bgroundChg = FALSE; ModifyStyle(0, WS_CLIPCHILDREN); }