作者:北京工业大学计算机学院网络学科部 胡击
在使用VC6.0/5.0的AppWizard生成MDI应用的时候,我们发现MDI主窗口的客户区背景千篇一律的是深灰的。VC6.0/5.0并没有提供修改其背景色的方法。甚至使用SDK编程也没有好的方法修改背景色。以至于微软的产品如Office也是灰蒙蒙的背景。那么,有没有办法将背景设置为自己喜欢的颜色呢?
笔者在学习过程中摸索出一套随意改变客户区窗口颜色的方法。利用这套方法,可以将客户区窗口设为256色背景甚至设为BITMAP位图以至于动画等等。大大地增强了程序的多媒体效果。
先介绍对MDI客户窗口编程的基本原理。
一、MDI客户窗口
一个MDI应用的主框架窗口包含一个特殊的子窗口称为MDICLIENT窗口。MDICLIENT窗口负责管理主框架窗口的客户区。MDICLIENT窗口本身有自己的子窗口即由CMDIChildWnd派生的文档窗口,也就是MDI子窗口。MDI主框架窗口负责管理MDICLIENT子窗口。当控制条(菜单条,状态条等)发生变化时,MDI主框架窗口重新配置MDICLIENT窗口。MDICLIENT子窗口负责管理全部的MDI子窗口。父窗口负责将某些命令传递到子窗口。因此,消息队列发向MDI子窗口的消息由MDICLIENT窗口负责传递,发向MDICLIENT窗口和MDI子窗口的消息由主框架窗口负责传递。这样,我们可以在主框架窗口截获关于MDICLIENT窗口的重画消息然后加入自己设计的代码。
二、MDI客户窗口编程方法
对MDI客户窗口编程有一定的难度。原因是MDIFrameWnd的客户区完全被MDICLIENT窗口覆盖掉了。这样,MDI主窗口类MDIFrameWnd的背景色和光标都不起作用。同时,微软并不支持将MDICLIENT窗口作为子类,MDICLIENT窗口只能使用标准的背景色和光标。所以,对MDI客户窗口编程不能象对普通窗口那样简单地重载WM_PAINT的消息处理函数。
改变MDI客户窗口背景的方法有两种。
使用CMDIFrameWnd::CreateClient 函数:
CreateClient( LPCREATESTRUCT lpCreateStruct, CMenu* pWindowMenu );
参数lpCreateStruct是指向CREATESTRUCT 结构的指针。在CREATESTRUCT 结构中lpszClass项指向窗口类WNDCLASS结构。通过改变WNDCLASS结构中的HbrBackground项和hCursor项可以更改MDICLIENT窗口的背景刷和光标。由于该函数创建新的MDICLIENT窗口对象,必须在重载的主窗口的OnCreate成员函数中调用。该方法比较复杂,必须手动创建MDI客户窗口,不能利用AppWizard自动提供的功能。而且,只能使用Windows95有限的16色背景刷。本文采用第二种方法。
在主框架窗口的消息队列中截获发向MDI客户窗口的WM_PAINT消息并向主框架窗口发送一条标志消息。在这条标志消息的处理函数中对MDI客户窗口进行操作。该方法比较简捷,但有几点值得注意的地方。
首先,如何截获MDI客户窗口WM_PAINT消息。MFC提供了PreTranslateMessage(MSG* pMsg) 函数。它在消息发送到TranslateMessage 和DispatchMessage 函数以前预先解释消息。可以重载该函数截获MDI客户窗口WM_PAINT消息:
BOOL PreTranslateMessage(MSG* pMsg)
{
if(pMsg->hwnd==m_hWndMDIClient && pMsg->message==WM_PAINT)
PostMessage(WM_PAINT);
return CMDIFrameWnd::PreTranslateMessage(pMsg);
}
其次,为简单起见,这里将标志消息设为MDI主窗口的WM_PAINT。在MDI主窗口WM_PAINT的消息处理函数中增加重画MDI客户窗口的代码。读者也可以自定义消息,不过麻烦一点。
最后,由于对MDI客户窗口的操作都是在主窗口完成的。如何在主窗口中获得MDI客户窗口的设备描述表呢。其实,在MDI主窗口类中有MDI客户窗口成员m_hWndMDIClient。按如下方法得到客户窗口的设备描述表
dc.m_hDC=::GetDC(this->m_hWndMDIClient);
然后就可以对客户窗口进行操作了。
三、实例
将客户窗口设为256色背景。
使用AppWizard生成MDI应用TEST。
在TEST.CPP中的函数,增加如下代码:
BOOL CTestApp::InitInstance()
{
AfxEnableControlContainer();
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
SetRegistryKey(_T("Local AppWizard-Generated Applications"));
LoadStdProfileSettings(); // Load standard INI file options (including MRU)
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_TESTTYPE,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CTestView));
AddDocTemplate(pDocTemplate);
// create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// The main window has been initialized, so show and update it.
pMainFrame->ShowWindow(m_nCmdShow);
//******增加代码头******
AfxGetMainWnd()->PostMessage(WM_PAINT);
//******增加代码尾******
pMainFrame->UpdateWindow();
return TRUE;
}
保证程序一开始就更新客户窗口。
使用ClassWard在CmainFrame类中加入PreTranslateMessage消息处理函数入如下:
BOOL PreTranslateMessage(MSG* pMsg)
{ //******增加代码头******
if(pMsg->hwnd==m_hWndMDIClient && pMsg->message==WM_PAINT)
PostMessage(WM_PAINT);
//******增加代码尾******
return CMDIFrameWnd::PreTranslateMessage(pMsg);
}
在主窗口的WMPAINT消息处理函数中加入:
void CMainFrame::OnPaint()
{
//******增加代码头******
CMDIFrameWnd::OnPaint();
CRect rc;
CDC dc;
dc.m_hDC=::GetDC(this->m_hWndMDIClient);
CBrush br(RGB(120,200,40));//256色刷子
dc.SelectObject(&br);
dc.GetClientRect(&rc); // 这句话在VC7.0中好像没有,需要查
dc.PatBlt(rc.left,rc.top,rc.Width(),rc.Height(),PATCOPY);
ReleaseDC(&dc);
//******增加代码尾******
}
将客户窗口设为Bitmap位图。
1,2,3同例一。
4.在资源中加入自己喜欢的位图并设为IDB_BITMAP1。在主窗口类加入Cbitmap类成员m_bmp。并在CMDIFrameWnd::OnCreate()函数末尾初始化:
m_bmp.LoadBitmap(IDB_BITMAP1);
5.在主窗口的WM_PAINT消息处理函数中加入:
void CMainFrame::OnPaint()
{
//******增加代码头******
CMDIFrameWnd::OnPaint();
CRect rc,memrc;
CDC dc,memdc;
dc.m_hDC=::GetDC(this->m_hWndMDIClient);
memdc.CreateCompatibleDC(&dc);
memdc.SelectObject(&m_bmp);
GetClientRect(&rc) ;
dc.BitBlt(rc.top,rc.left,rc.Width(),rc.Height()
,&memdc,rc.top,rc.left,SRCCOPY);
ReleaseDC(&memdc);
ReleaseDC(&dc);
//******增加代码尾******
}
对深入MDI客户窗口编程一文的改进和补充
向海
看了贵报刊载的深入MDI客户窗口编程一文,颇有兴趣,实际操作了一次,觉得很不错 整个窗口如图一(略)所示:
不过,美中不足的是,当我用鼠标拖动MDI子窗口时,出现了图二(略)所示情况:
背景怎麽给抹掉了?但当我把整个框架窗口最小化,再还原它的时候,客户窗口 的背景又恢复正常了,怎麽会是这个样子呢? 是VC6.0/5.0出错了吗?不是的, 这是由於我们在拖动MDI子窗口时,客户窗口没有及时重绘的结果 那麽,该怎样 去修正呢?当用鼠标拖动子窗口时,我们应该让当前活动子窗口给主框架窗口发 一个重绘客户窗口背景的消息,使用ClassWard在CChildFrame中加入OnMove消息 处理函数如下:
void CChildFrame::OnMove(int x, int y)
{ CMDIChildWnd::OnMove(x, y); //新增代码
CMDIFrameWnd* p_MFrame=GetMDIFrame( );
P_MFrame- >PostMessage(WM_PAINT) ; //新增代码 }
再编译、运行,刚才的现象没有啦,一切都好了,无论怎麽拖动MDI子窗口,背景都不会有给抹掉的现象了。
可是,又有问题了,当我把鼠标放在MDI子窗口的边框上,并拖动边框缩小MDI子窗口的尺寸时,出现了图三所示情况:
同样的道理,当MDI子窗口的尺寸改变时,我们也得向主框架窗口发一条重绘背景的消息 使用ClassWard在CChildFrame中加入OnSize消息处理函数如下:
void CChildFrame::OnSize(UINT nType, int cx, int cy) { CMDIChildWnd::OnSize(nType, cx, cy); //新增代码
CMDIFrameWnd* p_MFrame=GetMDIFrame( );
P_ MFrame - >PostMessage(WM_PAINT) ; //新增代码 }
再编译、运行,全正常了。
CString strDisplay;
strDisplay.Format("HoleXPosion:%d,HoleYPos:%d",HoleXPosion,HoleYPos);
AfxMessageBox(strDisplay);