MFC中的窗口绘图问题
1.闪屏
如果按照常规的绘图过程,即每做完一个绘图操作,就马上进行窗口刷新,当绘图过程比较密集的时候,由于没绘制一次就刷新一次窗口,而刷新窗口是需要一定延时的,这就导致了在上一次的刷新还没有完成的时候,这一次的绘图又到来了,这就是常见的闪屏现象(screen flicker)
2.双缓冲
要避免闪屏的问题,只需要对每次绘图都进行窗口刷新,而是对所有的绘图完成之后再进行一次刷新,这既提高了效率,有避免了闪屏的问题。
绘图的实质是将绘制的图形数据写入到一个内存块,然后计算机将这个内存块送到图像显示设备进行显示。默认的绘图过程也是如此,只不过它是每将一次的绘图数据写入到内存,就直接将这块内存发送到显示设备。而现在我们要做的是,将一个周期的内的绘图数据全部写入到同一个内存块中,这个周期完成之后,再将其送到显示设备。
代码的大致流程如下:
CBitmap memBmp;
CDC memDC;
memDC.CreateCompatibleDC(NULL);
CDC *pDC=this->GetDC();
CRect rect;
m_chartBoard.GetWindowRect(rect);
this->ScreenToClient(rect);
memBmp.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());
CBitmap *pOldBmp=memDC.SelectObject(&memBmp);
COLORREF testColor=RGB(236,233,216);
memDC.FillSolidRect(rect,testColor);
//TODO:在这里使用memDC完成绘图操作
pDC->BitBlt(0,0,rect.Width(),rect.Height(),&memDC,0,0,SRCCOPY);
memBmp.DeleteObject();
memDC.DeleteDC();
同时要重写窗口类的afx_msg BOOL OnEraseBkgnd(CDC* pDC)函数
BOOL CPlayBackDlg::OnEraseBkgnd(CDC* pDC)
{
//return CDialog::OnEraseBkgnd(pDC);
return TRUE;
}
从上面的代码可以看出,双缓冲使用一个位图对象CBitmap memBmp 作为绘图数据的存储对象,CBitmap *pOldBmp=memDC.SelectObject(&memBmp);将绘图操作的对象memDC和绘图数据的存储对象memBmp关联起来,在后续过程中使用memDC进行的绘图操作的绘图数据都会存储到memBmp中,它实质上是一个位图图像,最后用
pDC->BitBlt(0,0,rect.Width(),rect.Height(),&memDC,0,0,SRCCOPY);
将这个位图图像应用到设备环境中
3.局部重绘
假如一个窗口上既有需要进行重复绘图的区域,又有一些不需要进行重复绘图的区域,如果再每个绘图周期都没有区别的对整个窗口进行重绘,这是很没有必要的,这里可以只对需要进行重复绘图的局部区域进行重绘。
进行局部重绘可以通过CDC类函数
BOOL BitBlt(
int x,
int y,
int nWidth,
int nHeight,
CDC* pSrcDC,
int xSrc,
int ySrc,
DWORD dwRop
);
里的第1到4个参数进行指定,这四个参数指定了一个矩形,程序会将内存中的数据应用到这个设备所在环境的这个矩形上,实质上就是将绘图后生成的图像拷贝到窗口的指定区域进行显示。
指定了这4个参数后,确实是可以实现局部重绘的功能了,但是对于那些在这个区域之外的界面在整个窗口丢失焦点(如,被其他程序窗口覆盖后又获得焦点)的情况,它们不会被重新绘制。这个问题的原因是我们在OnEraseBkgnd函数中没有区别的只返回一个TRUE,把它自带的这句代码
return CDialog::OnEraseBkgnd(pDC);
给注释掉了,函数
afx_msg BOOL OnEraseBkgnd(
CDC* pDC
);
的功能是准备一个失效的区域进行重绘,重绘的过程是使用窗口类的背景画刷进行,它的功能是通知程序进行整个窗口的重绘。所以在这种情况下,我们需要调用CDialog::OnEraseBkgnd(pDC)进行整个窗口重绘。
大致的操作流程是:
<1>将所有的绘图操作全部放到OnPaint函数当中
void CPlayBackDlg::OnPaint()
{
if(m_bChartDataInit)DrawChart();
CDialog::OnPaint();
}
<2>在OnEraseBkgnd里对绘图操作的情况进行区分
BOOL CPlayBackDlg::OnEraseBkgnd(CDC* pDC)
{
if(m_bDrawChart)
return TRUE;
else
return CDialog::OnEraseBkgnd(pDC);
}