五二电子网 管理员组

文章数量: 1493

相信很多人在做图形界面开发时,常常会遇到屏幕闪烁的情况,当然我也不例外。前段时间用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, p);//创建跟客户区域大小一样的(空)位图

// 把位图选到设备上下文环境中

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 - p, &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);
}

本文标签: 闪烁 屏幕 需要 按钮