问题描述:
如上图所示,这是用MFC创建的多文档程序,可以看到主框架的背景区是灰色的,如果我要在这块区域贴一张图片,应该怎么办呢?最容易想到的是在CMainFrame的OnPaint中对背景进行更改,代码如下:
(为了简便,这里改为设置窗口背景色,其实这和贴图的原理差不多)
void CMainFrame::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: 在此处添加消息处理程序代码
RECT rc;
GetClientRect(&rc);
dc.FillRect(&rc,&CBrush(RGB(0,255,0)));
}
但运行之后你会发现并没有达到期望的效果。
下面我们来分析为什么会这样。
多文档窗口之主框架窗口的创建过程:
1.
在CMyTestApp::InitInstance中调用了
CMainFrame* pMainFrame = new CMainFrame;
pMainFrame->LoadFrame(IDR_MAINFRAME)
2.
在CMDIFrameWnd::LoadFrame中调用了
CFrameWnd::LoadFrame(nIDResource, dwDefaultStyle,pParentWnd, pContext)
在CFrameWnd::LoadFrame中含有
Create( lpszClass, strTitle, dwDefaultStyle, rectDefault,
pParentWnd, ATL_MAKEINTRESOURCE(nIDResource), 0L, pContext)
3.
CFrameWnd::Create中调用了
CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext)
该函数的rect、pParentWnd、pContext等参数是没有值的,这会调用CWnd的CreateEx方法,创建的窗口句柄存在m_hWnd成员变量中
之后便进入到CFrameWnd::OnCreate,然后再返回到本函数中
该函数执行完后返回到第2步
4.
CMainFrame::OnCreate
这是个消息响应函数,响应的是WM_CREATE消息,这里面调用了
CMDIFrameWnd::OnCreate(lpCreateStruct)
5.
CFrameWnd::OnCreate调用了
OnCreateHelper(lpcs, pContext)
lpcs中包含客户区、类名、标题、菜单等数据
6.
CFrameWnd::OnCreateHelper调用了
CWnd::OnCreate(lpcs)
然后又调用了
OnCreateClient(lpcs, pContext)
7.
CMDIFrameWnd::OnCreateClient中
CMenu* pMenu=GetMenu();
CreateClient(lpcs, pMenu)
CMDIFrameWnd中含有m_hWnd和m_hWndMDIClient成员变量,前者是在第3步时创建的CWnd窗口
8.
CMDIFrameWnd::CreateClient中的部分代码
ASSERT(m_hWnd != NULL);
ASSERT(m_hWndMDIClient == NULL);
m_hWndMDIClient = ::AfxCtxCreateWindowEx(dwExStyle, _T(“mdiclient”), NULL,
dwStyle, 0, 0, 0, 0, m_hWnd, (HMENU)AFX_IDW_PANE_FIRST,
AfxGetInstanceHandle(), (LPVOID)&ccs)
9.返回到第3步
在上面的分析过程中,通过第8步,可以看到,在主窗口框架中又建立了一个子窗口,该窗口的类型为”mdiclient”,该窗口的句柄为m_hWndMDIClient。也就是说,程序在创建完主框架窗口后,又在该窗口内部创建了一个客户区窗口,该客户区窗口就是我们看到的图中灰色区域的部分,其实也是我们要改变背景的部分,如果没有这个客户区窗口,那我们前面改变主框架窗口背景的代码是有效果的,但当该窗口创建后(该窗口的背景色为灰色),就又把之前改变了背景色的区域给挡住了。
通过上面的分析,我们知道了,只要改变m_hWndMDIClient窗口的背景色就可以了,那具体该怎么做呢?
先说一种比较笨的办法,就是在CxxxxView的OnDraw、OnMoving、OnSizing等等消息响应函数中每次都对上面的客户区窗口设置背景色,具体做法为:
在CxxxxApp中先创建一个成员变量HWND m_hMdiClient(构造函数中初始化为NULL)。然后修改
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
theApp.m_hMdiClient=m_hWndMDIClient; //记录下客户区窗口句柄
……
}
最后在CxxxxView的OnDraw等方法中加上这样的代码:
if(theApp.m_hMdiClient!=NULL)
{
RECT rc;
GetClientRect(theApp.m_hMdiClient,&rc);
HDC hdc=::GetDC(theApp.m_hMdiClient);
HBRUSH hbr=CreateSolidBrush(RGB(0,255,0));
::FillRect(hdc,&rc,hbr);
}
经过这样的修改,感觉上应该是可以了,每当我的子窗口触发重绘等操作时,就把客户区重新填充一下背景,但实际运行你会很失望,程序一创建时,客户区还是灰色的,当触发了重绘操作时,的确客户区变为我想要的绿色了,但每次拖动窗口,就会留下大片的阴影,效果不是一般的差。
有没有什么好的办法呢,其实上面的做法都是在客户区窗口自我绘制完后,又人为的在客户区窗口中重新绘制内容,这是治标不治本的做法,当客户区窗口每次更新时,都默认采用自己的灰色填充窗口背景,这是即时进行的,而我们添加的改变背景的代码则常常不能即时进行,所以,最根本的做法,就是在客户区窗口收到WM_PAINT这样的消息时,不再用默认的灰色填充窗口背景,而是改用我们希望的背景色或图片,写到这里,答案应该是呼之欲出了,那就是定制客户区窗口的默认窗口过程。于是,改动如下:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
theApp.m_hMDIWnd=m_hWndMDIClient;
lpfnOldProc = (WNDPROC)SetWindowLong(m_hWndMDIClient,GWL_WNDPROC,(DWORD)SCWndProc);
……
}
SCWndProc是CMainFrame的静态成员函数:
LRESULT CALLBACK CMainFrame::SCWndProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
RECT rc;
HBRUSH br;
HDC dc;
switch (wMsg) {
case WM_PAINT:
::GetClientRect(hWnd,&rc);
br=CreateSolidBrush(RGB(0,255,0));
dc=::GetDC(hWnd);
FillRect(dc,&rc,br);
break;
}
return CallWindowProc (lpfnOldProc, hWnd, wMsg, wParam, lParam);
}
别忘了程序关闭前,换回原来的窗口过程:
void CMainFrame::OnDestroy()
{
SetWindowLong(m_hWndMDIClient,GWL_WNDPROC,(DWORD)lpfnOldProc);
CMDIFrameWnd::OnDestroy();
}
这样基本可以了,但程序再一开始启动的时候,窗口背景还是灰色的,这是因为客户区才创建时,我们还没有为其指定新的窗口过程,补救的办法也简单,就是在CxxxxApp::InitInstance()的最后pMainFrame->UpdateWindow();代码之后,加上下面的代码:
RECT rc;
GetClientRect(m_hWndMDIClient,&rc);
HDC hdc=::GetDC(m_hWndMDIClient);
HBRUSH hbr=CreateSolidBrush(RGB(0,255,0));
::FillRect(hdc,&rc,hbr);
好了,基本上这样就差不多了,最后贴一张最终的效果图: