设置对话框背景颜色及背景图片可在OnCtlColor(),OnEraseBkgnd(),OnPaint()里设置,对话框初始化完毕,显示窗口时按顺序调用OnSize()>OnEraseBkgnd()>OnPaint()>OnCtlColor()。
OnEraseBkgnd()中默认调用基类(CFrameWnd)的OnEraseBkgnd(), 用窗口类中注册的Brush绘制背景。因此,绘制窗口背景时最好重写OnEraseBkgnd(),但后面不能调用基类的OnEraseBkgnd()。
同时,MSDN关 WM_ERASEBKGND消息的说明中有写道,DefWindowProc使用窗口类注册的背景Brush绘制背景,如果背景Brush是NULL,则需处理处理WM_ERASEBKGND消息。言外之意,处理WM_ERASEBKGND消息但不传给DefWindowProc就可以自己绘制背景,系统又不会重复绘制一遍。
When this member(背景画刷) is NULL, an application must paint its own background whenever it is requested to paint in its client area. To determine whether the background must be painted, an application can either process the WM_ERASEBKGND message or test the fErase member of the PAINTSTRUCT structure filled by the BeginPaint function.
方法如下:
- BOOL CMainWindow::OnEraseBkgnd(CDC* pDC)
- {
-
- CRect rc;
- GetClientRect(&rc);
- pDC->FillSolidRect(&rc , RGB(0,0,255) );
-
- return TRUE;
- }
BOOL CMainWindow::OnEraseBkgnd(CDC* pDC)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CRect rc;
GetClientRect(&rc);
pDC->FillSolidRect(&rc , RGB(0,0,255) );
//return CFrameWnd::OnEraseBkgnd(pDC);
return TRUE;
}
当然,也可以重写OnEraseBkgnd()直接返回TRUE,然后在OnPaint()中改变背景。
更直接的方法是修改窗口类,实现更改背景颜色
- BOOL CMainWindow::PreCreateWindow(CREATESTRUCT& cs)
- {
-
- if( CFrameWnd::PreCreateWindow(cs))
- {
-
- WNDCLASS wndclass;
-
- ::GetClassInfo(AfxGetInstanceHandle(),cs.lpszClass,&wndclass);
-
-
- wndclass.hbrBackground=CreateSolidBrush(RGB(0,100,100));
- wndclass.hbrBackground=m_BKBrush;
- wndclass.hbrBackground=*(new CBrush(RGB(25,25,0)));
-
- wndclass.lpszClassName = _T("newViewClassName ");
- VERIFY(AfxRegisterClass(&wndclass));
- cs.lpszClass=wndclass.lpszClassName;
- return TRUE;
- }
- return FALSE;
- }
BOOL CMainWindow::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: 在此添加专用代码和/或调用基类
if( CFrameWnd::PreCreateWindow(cs))
{
//改变窗口类
WNDCLASS wndclass;
::GetClassInfo(AfxGetInstanceHandle(),cs.lpszClass,&wndclass);
//wndclass.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
//wndclass.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
wndclass.hbrBackground=CreateSolidBrush(RGB(0,100,100));
wndclass.hbrBackground=m_BKBrush;//m_BKBrush不能为函数局部变量
wndclass.hbrBackground=*(new CBrush(RGB(25,25,0)));//最方便的方法
//wndclass.hCursor = AfxGetApp()-> LoadCursor(IDC_CURSOR1);
wndclass.lpszClassName = _T("newViewClassName ");
VERIFY(AfxRegisterClass(&wndclass));
cs.lpszClass=wndclass.lpszClassName;
return TRUE;
}
return FALSE;
}
若改变对话框大小,比如全屏显示ShowWindow(SW_SHOWMAXIMIZED);UpdateWindow();
其中 ShowWindow会调用OnSize()->OnEraseBkgnd(),
UpdateWindow();调用OnPaint()->OnCtlColor(),
若对话框中没有设置消息响应OnEraseBkgnd(),,则系统默认消息响应OnEraseBkgnd()会调用OnCtlColor()设置对话框背景(即替代OnEraseBkgnd())
对话框的背景设置可在OnCtlColor()中进行,因为OnCtlColor()一般会被多次调用,所以要想设置的CFont,CBrush等应在OnInitDialog中初始化,若要在OnCtlColor()中设置,在设置前先调用Detach就可以了,如下示例
- HBRUSH CDb3Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
- {
- if(pWnd->GetDlgCtrlID()==IDC_STATIC5)
- {
-
- m_font.CreatePointFont(300,"宋体");
- pDC->SelectObject(&m_font);
- m_font.Detach();
- pDC->SetBkMode(TRANSPARENT);
- return (HBRUSH)::GetStockObject(NULL_BRUSH);
- }
- }
HBRUSH CDb3Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
if(pWnd->GetDlgCtrlID()==IDC_STATIC5)
{
m_font.CreatePointFont(300,"宋体");
pDC->SelectObject(&m_font);
m_font.Detach();
pDC->SetBkMode(TRANSPARENT);
return (HBRUSH)::GetStockObject(NULL_BRUSH);
}
}
但是如果在OnCtlColor()在设置背景图片,则图片不会随对话框大小按比例缩放
所以可调用StretchBlt()函数设置,如下示例:
- void CDb3Dlg::OnPaint()
- {
- CClientDC cdc(this); CDC comdc;
- comdc.CreateCompatibleDC(&cdc);
- CBitmap bitmap;
- bitmap.LoadBitmap(IDB_BITMAP2);
- comdc.SelectObject(&bitmap);
- CRect rect;
- GetClientRect(rect);
- BITMAP bit;
- bitmap.GetBitmap(&bit);
- cdc.StretchBlt(0,0,rect.Width(),rect.Height(),&comdc,0,0,bit.bmWidth,bit.bmHeight,SRCCOPY);
- }
void CDb3Dlg::OnPaint()
{
CClientDC cdc(this); CDC comdc;
comdc.CreateCompatibleDC(&cdc);
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAP2);
comdc.SelectObject(&bitmap);
CRect rect;
GetClientRect(rect);
BITMAP bit;
bitmap.GetBitmap(&bit);
cdc.StretchBlt(0,0,rect.Width(),rect.Height(),&comdc,0,0,bit.bmWidth,bit.bmHeight,SRCCOPY);
}//全屏显示对话框背景图片(限bmp格式)
对于窗口程序,一般有个特点:窗口大部分的区域保持不变,只有不分区域需要重新绘制。如果将整个窗口全部刷新的画,就做了许多不必要的工作,因而,MFC采用了一套基于无效区的处理机制。在分析无效区处理之前,我们要明白一个现实,现在的机器还不够牛,如果够牛的话,我们干脆将整个窗口不断的重新绘制好了。事实上即使够牛也不行,对于一个单线程程序,通过一个while循环不断的刷新窗口,程序也无法相应其他消息(除非使用多线程),看来使用无效区的处理机制还是有其必然性的。
VC程序是基于消息机制的,你所做的任何操作,比如点击鼠标,拖动窗口,首先进入系统的消息队列。这里的系统消息队列包括多个程序的消息,系统再将消息发送给相应的程序。既然是队列,这就有一个先进先出的问题,屏幕上的无效区更新消息出现的频率就会特别高。比如当左上角更新的消息还没有处理,右下角更新的消息已经过来了。为了避免多次处理WM_PAINT消息,系统就将这些窗口更新消息合并到一条,只是将无效区范围变成包括这两次更新无效区范围在内的矩形区域。这样就减少了WM_PAINT消息的处理次数,提高了效率。
那么,在OnPaint消息处理函数中,又是怎样实现更新无效区的呢?首先,要明白MFC中所有绘图操作都是基于设备描述表(Device Context,简称DC)的,具体信息可参看任何一本VC教材。DC中包含了绘图设备的各种信息,对于屏幕绘图,其实就是有一块内存(显存),专门用来存放要显示到屏幕上的信息,显示器以85HZ的频率(我以前的显示器)将其内容刷新的屏幕上。这里就到了关键点,显示器的刷新是将显存中的内容完全更新到显示器上,不存在无效区处理的问题,那么,无效区的处理一定发生在DC的绘图处理上。事实确实如此,当程序调用OnPaint消息时,首先将无效区范围传递给DC,DC在进行绘图操作时,就只更新无效区范围内的信息,其他地方的不管,这就提高了效率。
现在你明白OnPaint的处理是怎么一回事了吧?这里还想说一下Invalidate和UpdateWindow的区别。Invalidate在消息队列中加入一条WM_PAINT消息,其无效区为整个客户区。而UpdateWindow直接发送一个WM_PAINT消息,其无效区范围就是消息队列中WM_PAINT消息(最多只有一条)的无效区。效果很明显,调用Invalidate之后,屏幕不一定马上更新,因为WM_PAINT消息不一定在队列头部,而调用UpdateWindow会使WM_PAINT消息马上执行的,绕过了消息队列。如果你调用Invalidate之后想马上更新屏幕,那就加上UpdateWindow()这条语句。