MFC OnEraseBkgnd浅析

转载

在使用VC进行绘图时,最郁闷的就是屏幕闪烁问题。有时只能使用双缓冲去解决,当然更多的时候是去忍受。有没有什么这种的方法?有,那就是重载OnEraseBkgnd()函数。该函数控制重绘图的背景刷新,默认将窗口用系统背景颜色填充,即用白色将窗口刷一遍。这就产生的严重的闪烁效果,有时看到屏幕白的一条一闪而过,非常不爽。如果将该函数返回值设为TRUE,即不进行背景重绘,虽然屏幕的刷新仍然会闪烁,但是就不会像原来那么严重了。因为窗口的颜色通常与白色反差加大,所以闪烁比较严重。而不进行背景重绘,由于大部分区域新画的颜色与旧颜色相同,就不会有严重的闪烁效果了。这样做有没有什么不好的后果呢?通常情况是没有的,因为一般都是执行整个窗口的重绘,即使用白色刷了背景,也是没有任何作用的。除非你刷新整个屏幕,但只重绘一小部分,这种情况可能么?有可能,比如你通过点击一个按钮来刷新整个窗口,重新绘制的有效区域要比刷新之前小,这些不重叠的区域在重载OnEraseBkgnd()函数之前会变作白色,而重载之后会保持原来的样子。

下面说两种重载OnEraseBkgnd()函数的方法:

1)手工重载

在要重载的类(通常为CView的子类)声明部分(.h文件)添加虚函数的声明:

virtual BOOL OnEraseBkgnd(CDC* pDC);

注意添加在//{{AFX_VIRTUAL(CMyView)和//}}AFX_VIRTUAL之间。

在类的定义文件(.cpp)中的MESSAFE_MAP部分,//{{AFX_MSG_MAP(CMyView)和 //}}AFX_MSG_MAP之间添加ON_WM_ERASEBKGND()
然后在函数体添加:

BOOL CMyView::OnEraseBkgnd(CDC* pDC)
{
    return TRUE;
}

这样就搞定了。

2)使用Classwizard

或者使用Classwizard来进行,省去了写代码。通过菜单View->ClassWizard(或者ctrl+W)->Class    name中选你的view。然后在Class Info选项卡中,将Message Filter设为Child Window。重新切换到Message Map选项卡,在Messages中找到WM_ERASEBKGND,双击就添加成功了。点击右边的Edit Code,将函数体改为return TRUE,搞定。这种方法不需要记忆,简单省事,强烈推荐。


在OnEraseBkGnd中,如果你不调用原来缺省的OnEraseBkGnd只是重画背景则不会有闪烁。而在OnPaint里面,
由于它隐含的调用了OnEraseBkGnd,而你又没有处理OnEraseBkGnd函数,这时就和窗口缺省的背景刷相关了。缺省的OnEraseBkGnd操作使用窗口的缺省背景刷刷新背景(一般情况下是白刷),而随后你又自己重画背景造成屏幕闪动。
另外一个问题是OnEraseBkGnd不是每次都会被调用的。如果你调用Invalidate的时候参数为TRUE,那么在OnPaint里面隐含调用BeginPaint的时候就产生WM_ERASEBKGND消息,如果参数是FALSE,则不会重刷背景。

所以解决方法有三个半:
1.用OnEraseBkGnd实现,不要调用原来的OnEraseBkGnd函数
2.用OnPaint实现,同时重载OnEraseBkGnd,其中直接返回
3.用OnPaint实现,创建窗口时设置背景刷为空
4.用OnPaint实现,但是要求刷新时用Invalidate(FALSE)这样的函数(不过这种情况下,窗口覆盖等造成的刷新还是要闪一 下,所以不是彻底的解决方法) 
都挺简单的
------------------------------------------------------ 
MFC中 任何一个window组件的绘图 都是放在这两个member function中在设定上 OnEraseBkgnd()是用来画底图的OnPaint()是用来画主要对象的举例说明 一个按钮是灰色的 上面还有文字则OnEraseBkgnd()所做的事就是把按钮画成灰色而OnPaint()所做的事 就是画上文字 
既然这两个member function都是用来画出组件的那为何还要分OnPaint() 与 OnEraseBkgnd() 呢 
其实OnPaint() 与 OnEraseBkgnd() 特性是有差的 
1. OnEraseBkgnd()的要求是快速 在里面的绘图程序最好是不要太耗时间因为 每当window组件有任何小变动 都会马上呼叫OnEraseBkgnd() 
2. OnPaint() 是只有在程序有空闲的时候才会被呼叫 
3. OnEraseBkgnd() 是在 OnPaint() 之前呼叫的,所以 OnPaint()被呼叫一次之前 可能会呼叫OnEraseBkgnd()好几次。

如果我们是一个在做图形化使用者接口的人常会需要把一张美美的图片设为我们dialog的底图,把绘图的程序代码放在OnPaint() 之中 可能会常碰到一些问题。比方说拖曳一个窗口在我们做的dialog上面一直移动,则dialog会变成灰色 直到动作停止才恢复,这是因为每次需要重绘的时候 程序都会马上呼叫OnEraseBkgnd() ,OnEraseBkgnd()就把dialog画成灰色,而只有动作停止之后 程序才会呼叫OnPaint() 这时才会把我们要画的底图贴上去。

这个问题的解法 比较差点的方法是把OnEraseBkgnd() 改写成不做事的function 
如下所示 

BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
    return TRUE;
}

以上本来是会呼叫CDialog::OnEraseBkgnd() 但是如果我们不呼叫的话,程序便不会画上灰色的底色了。

比较好的做法是直接将绘图的程序从OnPaint()移到OnEraseBkgnd()来做,如下所示:

// m_bmpBKGND 为一CBitmap对象 且事先早已加载我们的底图
// 底图的大小与我们的窗口client大小一致
BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
	CRect rc;
	GetUpdateRect(&rc);
	CDC srcDC;
	srcDC.CreateCompatibleDC(pDC);
	srcDC.SelectObject(m_bmpBKGND);

	pDC->BitBlt(rc.left,rc.top,rc.GetWidth(),
		rc.GetHeight(),&srcDC,rc.left,rc.top,SRCCOPY);
	return TRUE;
} 

特别要注意的是 取得重画大小是使用GetUpdateRect() 而不是GetClientRect() ,如果使用GetClientRect() 会把不该重画的地方重画。

应该是这样的吧,当Windows确定客户区需要重绘时,它首先发送WM_ERASEBKGND消息给窗口过程,由WM_ERASEBKGND消息的默认处理用白色画刷刷除背景,然后再发送WM_PAINT消息给窗口过程,由WM_PAINT消息的响应程序负责绘画客户区内容。或者说,当Windows确定客户区需要重绘时,它分别发送WM_ERASEBKGND和WM_PAINT消息,由这两个消息的响应程序分别负责刷除背景和重画客户内容。

至于闪烁的问题,是由于刷除背景以后,在WM_PAINT未执行完成之前,windows已把视频卡的缓存输出到屏幕上了。

如何擦除之前的背景图片?
我明白问题所在了,你的问题是当窗口大小变化时,原来的背景没有被清除,造成图片重叠显示,你上面的代码没有问题,写在OnEraseBkgnd()中还是写在OnPaint()中的结果都是一样的,最主要的原因是,当我们改变窗口大小时,触发WM_SIZE消息,而默认的OnSize在内部调用Invalidate时用的参数是:Invalidate(FLASE);也就是不刷新背景的。所以原来的背景刷不掉
解决的方法是处理WM_SIZE,在OnSize()中,调用:Invalidate(TRUE);强制刷新背景就行了。

BOOL CDisplayBmpBackGroundDlg::OnEraseBkgnd(CDC* pDC)   //增加擦除背景函数
{
   CRect rect;
   GetClientRect(&rect);
   CBitmap m_pBmp;
   BITMAP bm;
   CDC dcMem;
   m_pBmp.LoadBitmap(IDB_BITMAP1);
   m_pBmp.GetBitmap(&bm);//得到位图尺寸
   dcMem.CreateCompatibleDC(pDC);
   CBitmap* pOldBitmap = dcMem.SelectObject(&m_pBmp);
   pDC->SetStretchBltMode(COLORONCOLOR);//这个模式不设置的话会导致图片严重失真
   pDC->StretchBlt(0, 0, rect.Width(), rect.Height(), &dcMem, 0, 0, bm.bmWidth,bm.bmHeight,SRCCOPY);
   dcMem.SelectObject(pOldBitmap);
   return TRUE;
}

2、来我们让STATIC控件透明吧 

HBRUSH CDisplayBmpBackGroundDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	if(nCtlColor == CTLCOLOR_DLG || nCtlColor == CTLCOLOR_BTN || nCtlColor ==  CTLCOLOR_STATIC)
    {
        pDC->SetBkMode(TRANSPARENT);
    }     
    return  HBRUSH(GetStockObject(HOLLOW_BRUSH));
}

4、这就要对该控件或者对话框处理了,要刷出新字体来。

CWnd* pWnd = GetDlgItem(IDC_STATIC1);
pWnd->SetWindowText("guohaiyang.blog.163.com");//需要修改的控件标题
pWnd ->GetParent()->InvalidateRect(CRect(0,0,100,50),TRUE);
pWnd ->GetParent()->InvalidateRect(CRect(0,0,100,50),TRUE);
this->RedrawWindow();

MFC OnEraseBkgnd

分享到:
评论加载中,请稍后...
创APP如搭积木 - 创意无限,梦想即时!
回到顶部