MFC双缓冲绘图技术的使用和测试例子

⌚Time: 2022-12-24 16:50:24

👨‍💻Author: Jack Ge

使用双缓冲技术可以避免屏幕闪烁,绘制一个小球在屏幕中移动,每次小球移动,需要在窗口中擦除背景并且重新绘制小球,如果刷新速度过快,就会反复擦除与绘制图像造成屏幕闪烁。而使用双缓冲技术,在内存中创建一个缓冲区进行图像的绘制操作,绘制完成后,将缓冲区中的图像一次性绘制到窗口中中,就避免了窗口反复擦除和闪烁的现象

实现双缓冲

在窗体中定义三个变量并且在构造函数中初始化设置


    int m_iBallPosX;//小球位置

    int m_iBallSpeed;//小球速度

    bool m_bIsBuffDraw;//是否开启双缓冲

在窗体的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::OnBnClickedButton3()

{

    // TODO: 在此添加控件通知处理程序代码

    SetTimer(1,1,NULL);

}

开启和关闭双缓冲




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);

因为背景没有擦除,就会出现

的效果,而如果在绘制过程中手动擦除背景


        //直接在窗口区域绘图

        pDC->FillSolidRect(rect,RGB(0,0,0));

        pDC->Ellipse(m_iBallPosX,100,m_iBallPosX+50,150);

就只会出现轻微的闪烁效果,这种闪烁是绘制背景和小球的时候出现,并且只会出现在小球区域