使用双缓冲技术可以避免屏幕闪烁,绘制一个小球在屏幕中移动,每次小球移动,需要在窗口中擦除背景并且重新绘制小球,如果刷新速度过快,就会反复擦除与绘制图像造成屏幕闪烁。而使用双缓冲技术,在内存中创建一个缓冲区进行图像的绘制操作,绘制完成后,将缓冲区中的图像一次性绘制到窗口中中,就避免了窗口反复擦除和闪烁的现象
实现双缓冲
在窗体中定义三个变量并且在构造函数中初始化设置
在窗体的OnPaint函数中进行绘图
void CTESTBUFFDRAWDlg::OnPaint()
{
if (m_bIsBuffDraw)//使用双缓冲绘图
{
CDC *pDC;
CDC memDC;
CBitmap bitmap,*pOldBitmap;
CRect rect;
pDC = GetDC();
//获取窗口区域
GetClientRect(&rect);
int iWidth = rect.Width();
int iHeight = rect.Height();
//创建兼容dc(缓冲区绘图)
memDC.CreateCompatibleDC(pDC);
//建立绘制的目标图像
bitmap.CreateCompatibleBitmap(pDC, iWidth, iHeight);
pOldBitmap = memDC.SelectObject(&bitmap);
//设置画笔
CPen pen(PS_SOLID,2,RGB(255,255,255));
//设置画刷
CBrush brush(RGB(255,0,255));
CPen *pOldPen = memDC.SelectObject(&pen);
CBrush *pOldBrush = memDC.SelectObject(&brush);
//绘制黑色背景
memDC.FillSolidRect(rect,RGB(0,0,0));
//绘制小球
memDC.Ellipse(m_iBallPosX,100,m_iBallPosX+50,150);
//将缓冲区图像绘制到窗口
pDC->SetStretchBltMode(COLORONCOLOR);
pDC->BitBlt(0, 0, iWidth, iHeight, &memDC, 0, 0,SRCCOPY);
//释放资源
memDC.SelectObject(pOldPen);
memDC.SelectObject(pOldBrush);
pen.DeleteObject();
brush.DeleteObject();
memDC.DeleteDC();
bitmap.DeleteObject();
ReleaseDC(pDC);
}else{//非双缓冲绘图
CDC *pDC;
CRect rect;
pDC = GetDC();
GetClientRect(&rect);
//设置画笔
CPen pen(PS_SOLID,2,RGB(255,255,255));
CBrush brush(RGB(255,0,255));
CPen *pOldPen = pDC->SelectObject(&pen);
CBrush *pOldBrush = pDC->SelectObject(&brush);
//直接在窗口区域绘图
pDC->FillSolidRect(rect,RGB(0,0,0));
pDC->Ellipse(m_iBallPosX,100,m_iBallPosX+50,150);
//释放资源
pDC->SelectObject(pOldPen);
pDC->SelectObject(pOldBrush);
pen.DeleteObject();
brush.DeleteObject();
ReleaseDC(pDC);
}
//
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}
在OnEraseBkgnd消息响应函数中对于双缓冲绘图直接返回true,取消擦除背景的操作
BOOL CTESTBUFFDRAWDlg::OnEraseBkgnd(CDC* pDC)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
if (m_bIsBuffDraw)//如果是双缓冲绘图则不擦除背景
{
return true;
}else{//非双缓冲绘图正常擦除背景
return CDialog::OnEraseBkgnd(pDC);
}
}
这样双缓冲绘图就实现了。
实现小球移动
为了让小球移动,和绘图模式的切换,在设置几个按钮
小球开始移动按钮
开启和关闭双缓冲
void CTESTBUFFDRAWDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
m_bIsBuffDraw = true;
}
void CTESTBUFFDRAWDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
m_bIsBuffDraw = false;
}
在定时器函数中移动小球位置
void CTESTBUFFDRAWDlg::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
switch (nIDEvent)
{
case 1:
m_iBallPosX += m_iBallSpeed;
CRect rect;
GetClientRect(&rect);
if (m_iBallPosX>rect.Width())
{
m_iBallSpeed = -m_iBallSpeed;
}
if (m_iBallPosX<0)
{
m_iBallSpeed = -m_iBallSpeed;
}
Invalidate(true);//使窗口失效,意味着重新绘图刷新窗口显示
break;
}
CDialog::OnTimer(nIDEvent);
}
效果
关闭双缓冲后小球会有闪烁效果,由于gif捕捉的帧频率问题,导致闪烁的小球几乎看不到,而打开双缓冲后小球没有闪烁。gif能正常显示

Invalidate函数
对于Invalidate();函数,它的作用相当于它的参数bErase 决定了是否要在WM_PAINT消息前发送WM_ERASEBKGND
为true说明在重绘时调用OnEraseBkgnd擦除背景,而false说明在重绘时不调用OnEraseBkgnd擦除背景,因此对于双缓冲技术,OnEraseBkgnd中要禁止擦除背景,直接返回true,或者直接使用Invalidate(false)
而不使用双缓冲绘图,Invalidate(true)导致屏幕反复擦除而出现闪烁,如果使用Invalidate(false),绘制小球
//直接在窗口区域绘图
//pDC->FillSolidRect(rect,RGB(0,0,0));
pDC->Ellipse(m_iBallPosX,100,m_iBallPosX+50,150);
因为背景没有擦除,就会出现

的效果,而如果在绘制过程中手动擦除背景
就只会出现轻微的闪烁效果,这种闪烁是绘制背景和小球的时候出现,并且只会出现在小球区域
