在创建工作完成之后,进行初始化,使用文档对象的数据来更新视和显示窗口。
至此,本节描述了MFC的SDI程序从分析命令行到创建或打开文件的处理过程,文档对象已经动态创建。总结如下:
命令行分析→应用程序的FileNew→文档模板的OpenDocumentFile(NULL)→文档的OnNewDocument
命令行分析→应用程序的FileOPen→文档模板的OpenDocumentFile(filename)→文档的OpenDocument
边框窗口对象、视对象的动态创建和对应 Windows对象的创建从LoadFrame开始,这些将在下一节论述。
SDI边框窗口的创建
第三步是创建SDI边框窗口。
图5-8已经分析了创建SDI边框窗口的时机和创建方法,下面,从LoadFrame开始分析整个窗口创建过程。
CFrameWnd::LoadFrame
CFrameWnd::LoadFrame的流程如图5-11所示,其原型如下:
BOOL CFrameWnd::LoadFrame(UINT nIDResource,
DWORD dwDefaultStyle,
CWnd* pParentWnd,
CCreateContext* pContext)
第一个参数是和该框架相关的资源ID,包括字符串、快捷键、菜单、像标等;
第二个参数指定框架窗口的“窗口类”和窗口风格;此处创建SDI窗口时和缺省值相同,为WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE;
第三个参数指定框架窗口的父窗口,此处和缺省值相同,为NULL;
第四个参数指定创建的上下文,如图5-8所示由CreateNewFrame生成了该变量并传递给LoadFrame。其缺省值为NULL。
创建上下文结构的定义:
struct CCreateContext
{
CRuntimeClass* m_pNewViewClass; //View的动态创建信息
CDocument*m_pCurrentDoc;//指向一文档对象,将和新创建视关联
//用来创建MDI子窗口的信息(C MDIChildFrame::LoadFrame使用)
CDocTemplate* m_pNewDocTemplate;
// for sharing view/frame state from the original view/frame
CView* m_pLastView;
CFrameWnd* m_pCurrentFrame;
};
这里,传递给LoadFrame的CCreateContext变量是:
(视的动态创建信息,新创建的文档对象,当前文档模板,NULL,NULL)。
其中,“新创建的文档对象”就是图 5-8中创建的那个文档对象。从此图中还可以看到,LoadFrame被CreateNewFrame调用,CreateNewFrame是文档模板的成员函数,被文档模板的成员函数OpenDocumentFile所调用,所以,LoadFrame间接地被文档模板调用,“当前文档模板”就是调用它的模板对象。顺便指出,对SDI程序来说是这样的,对MDI程序有所不同。“视的动态创建信息”也是文档模板传递过来的。
对图5-11的说明:
在创建边框窗口之前,先注册“窗口类”。LoadFrame注册了两个“窗口类”,一个为边框窗口,一个为视窗口。关于“窗口类”注册,见2.2.1节。
注册窗口类之后,创建边框窗口,并加载资源。创建边框窗口使用了CFrameWnd的Create虚拟函数,最终调用::CreateEx创建窗口。::CreateEx有11个参数,其最后一个参数就是文档模板传递给LoadFrame的CCreateContext类型的指针,该指针将被传递给窗口过程,进一步由Windows传递给OnCreate函数。顺便指出,创建完毕的边框窗口的窗口过程是统一的MFC窗口过程。
创建边框窗口时,发送消息WM_NCCREATE和WM_CREATE,导致对应的消息处理函数OnNcCreate和OnCreate被调用。CWnd提供了OnNcCreate处理非客户区创建消息,CFrameWnd没有处理该消息,但是提供了OnCreate处理消息WM_CREATE。OnCreate将创建视对象和视窗口。
CFrameWnd::OnCreate
按创建工作的进度,现在要讨论边框窗口创建消息(WM_CREATE)的处理了,处理函数是CFrameWnd的OnCreate,其原型如下:
int CFrameWnd::OnCreate(LPCREATESTRUCT lpcs)
其中,参数指向一个CreateStruct结构(关于CreateStruct的描述见4.4.1节),它包含了窗口创建参数的副本,也就是说CreaeEx窗口创建函数的11个参数被对应地复制到该结构的11个域,例如它的第一个成员就可以转换成CCreateContext类型的指针。
函数OnCreate处理WM_CREATE消息,它从lpcs指向的结构中分离出lpCreateParams并把它转换成为CCreateContext类型的指针pContext,然后,调用OnCreateHelp(lpcs,pContext),把创建工作委派给它完成。
CFrameWnd::OnCreateHelp的原型如下,流程见图5-11。
int CFrameWnd::OnCreateHelp(LPCREATESTRUCT lpcs,
CCreateContext* pContext)
说明:由于CFrameWnd覆盖了消息处理函数OnCreate来处理WM_CREATE消息,所以CWnd就失去了处理该消息的机会,除非CFrameWnd::OnCreate主动调用基类的该消息处理函数。图5-11展示了对CWnd::OnCreate的调用。
在边框窗口被创建之后,调用虚拟函数OnCreateClient(lpcs,pContext),它的缺省实现将创建视对象和视窗口。
最后,在状态栏显示“Ready”字样,调用RecalcLayout安排工具栏等的位置。关于WM_SETMESSAGESTRING消息和RecalcLayout函数,见工具栏有关13.2.3节。
到此,SDI的边框窗口已经被创建。下一节将描述视的创建。
视的创建
第四步,创建视。
如前一节所述,若CFrameWnd::OnCreateClient(lpcs,pContext)判断pContext包含了视的动态创建信息,则调用函数CreateView创建视对象和视窗口。CreateView的原型如下,其流程如图5-13所示。
CWnd * CFrameWnd::CreateView(CCreateContext* pContext, UINT nID)
其中:
第一个参数是创建上下文;
第二个参数是创建视 (子窗口)的ID,缺省是AFX_IDW_PANE_FIRST,这里等同缺省值。
说明:
CreateView调用了CWnd的Create函数创建HWND视窗口,视的子窗口ID是AFX_IDW_PANE_FIRST,父窗口是创建它的边框窗口。创建视窗口时的WM_CREATE、WM_NCCREATE消息分别被CView、CWnd的相关消息处理函数处理。处理情况如图5-13所述,这里不作进一步的讨论。
到此,文档对象、边框窗口对象、视窗口对象已经创建,文件已经打开或者创建,边框窗口、视窗口已经创建。现在,是显示和定位窗口、显示文档数据的时候了,这些通过调用CFrameWnd的虚拟函数InitialUpdateFrame完成,如图5-8所示。
窗口初始化
这是第五步,初始化边框窗口、视窗口等。
InitialUpdateFrame的原型如下:
void CFrameWnd::InitialUpdateFrame(CDocument* pDoc, BOOL bMakeVisible)
其中:
第一个参数是当前的文档对象;
第二个参数表示边框窗口是否应该可见并激活。
该函数是在文档模板的OpenDocumentFile中调用的,传递过来的第一个参数是刚才创建的文档,第二个参数是TRUE,见图5-8。
InitialUpdateFrame的处理过程参见图5-14,解释如下:
首先,如果当前边框窗口没有活动视,则获取ID为AFX_IDW_PANE_FIRST的视pView。如果该视不存在,则pView=NULL;否则(pView!=NULL),调用成员函数SetActiveView(pView,FALSE)把它设置为活动视,参数2为FALSE表示并不通知它成为活动视(见图5-14)。
然后,如果InitialUpdateFrame的参数bMakeVisible为TRUE,则给所有边框窗口的视发送WM_INITIALUPDATE消息,通知它们在显示之前使用文档数据来初始化视。这导致视类的虚拟函数OnInitUpdate被调用,该函数又调用OnUpdate来完成初始化。其他子窗口(如状态栏、工具栏)也将收到WM_INITIALUPDATE消息,导致它们更新状态。
其三,调用pView->OnActivateFrame(WA_INACTIVE,this)给活动视(若存在的话)一个机会来保存当前焦点。这里,解释这个函数:
void CView::OnActivateFrame( UINT nState,CFrameWnd* pFrameWnd );
其中,参数1取值为WA_INACTIVE/WA_ACTIVE/WA_CLICKACTIVE,具体见消息WM_ACTIVE的解释;参数2指向被激活的框架窗口。
视对象通过该虚拟函数在它所属的边框窗口被激活或者失去激活时作一些特别的处理,例如,CFormView用它来保存或者恢复输入焦点控制。
其四,在OnActivateFrame之后,调用成员函数ActivateFrame激活框架窗口。这个过程将产生一个消息WM_ACTIVE(处理该消息的过程在下一节作解释),它导致OnActiveTopLevel和OnActive被调用。接着,如果活动视非空,则调用成员函数OnActivateView激活它。
至此,参数bMakeVisible为TRUE时显示窗口的处理完毕。
最后,如果参数pDoc非空,则更新边框窗口计数,更新边框窗口的标题。更新边框窗口计数是为了在一个文档对应多个视的情况下,给显示同一文档的不同文档边框窗口编号,编号从1开始,保存在边框窗口的成员变量m_nWindow里。例如有两个边框对应一个文档tt1,则它们的标题分别为“tt1:1”、“tt1:2”。如果只有一个文档只对应一个边框窗口,则成员变量m_nWindow等于-1,标题不含编号,如“tt1”。
当然,对于SDI应用程序,不存在一个文档对应多个视的情况。上述情况是针对MDI应用程序而言的。SDI应用程序执行该过程时,相当于MDI程序的一个特例。
图 5-14涉及的一些函数由图5-15、5-15图解。
图5-14中的函数SetActiveView的图解如图5-15所示,其原型如下,:
void CFrameWnd::SetActiveView(CView * pViewNew, BOOL bNotify = TRUE)
其中:
参数1指向被设置的视对象,若非视类型的对象,则为NULL;
参数 2表示是否通知被设置的视。
图5-15中的变量m_pViewActive是CFrameWnd的成员变量,用来保存边框窗口的活动视。
图5-15中的流程可以概括为:Deactivate当前视(m_pViewActive非空时);设置当前活动视;若参数bNotify为TRUE,通知pViewNew被激活。
图5-14中的函数ActivateFrame图解如图5-16所示,其原型如下,:
void CFrameWnd::ActivateFrame(UINT nCmdShow)
参数nCmdShow用来传递给CWnd::ShowWindow,指定显示窗口的方式。参数缺省为1,图5-14调用时设置为-1。
该函数用来激活(Activate)和恢复(Restore)边框窗口,使得它对用户可见可用。在初始化、OLE事件、DDE事件等需要显示边框窗口的地方调用。图5-16表示的MFC缺省实现是激活边框窗口并把它放到顶层。
程序员可以覆盖该虚拟函数ActivateFrame来控制边框窗口怎样被激活。
图5-16中的函数BringToTop是CFrameWnd内部使用的成员函数(protected)。它调用::BringWindowToTop把窗口放到Z轴上的顶层。
至此,边框窗口初始化的过程已经描述清楚,视的初始化见下一节。
视的初始化
第六步,在边框窗口初始化之后,初始化视。
如图5-14所示,视、工具条窗口处理消息WM_INITAILUPDATE(MFC内部消息),完成初始化。这里只讨论视的消息处理函数,其原型如下:
void CView::OnInitialUpdate()
图5-14对该函数的注释说明了该函数的特殊之处。其缺省实现是调用OnUpdate(NULL, 0, NULL)更新视。可以覆盖OnInitialUpdate实现自己的初始化。
OnUpdate是一个虚拟函数,其原型如下:
void CView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
其中:
参数1指向修改文档数据的视;若更新所有的视,设为NULL;
参数2是一个包含了修改信息的long型变量;
参数3指向一个包含修改信息的对象(从CObject派生的对象)。
参数2、参数3是在文档更新对应视的时候传递过来的。
该函数用来更新显示视窗口,反映文档的变化,在MFC中,它为函数CView::OnInitialUpdate和CDocument::UpdateAllViews所调用。其缺省实现是使整个客户区无效。在下一次收到WM_PAINT消息时,重绘无效区。
工具条的初始化见讨论第13章。
激活边框窗口(处理WM_ACTIVE)
第七步,在窗口初始化完成之后,激活并显示出来。
下面讨论边框窗口激活时的处理(对WM_ACTIVE的处理)。
WM_ACTIVE的消息参数
wParam的低阶word指示窗口是被激活还是失去激活:WA_ACTIVE,被鼠标点击以外的方法激活;WA_CLICKACTIVE,由鼠标点击激活;WA_INACTIVE,失去激活;
wParam的高阶word指示窗口是否被最小化;非零表示最小化;
lPararm表示将激活的窗口句柄(WA_INACTIVE),或者将失去激活的窗口句柄(WA_CLICKACTIVE、WA_ACTIVE)。
在标准Windows消息处理的章节中,曾指出处理WM_ACTIVE消息时,先要调用一个函数_AfxHandleActivate,此函数的原型如下:
static void AFXAPI _AfxHandleActivate(CWnd* pWnd,
WPARAM nState,CWnd* pWndOther)
其中:
参数1是接收消息的窗口;
参数2是窗口状态,为WM_ACTIVE的消息参数wParam;
参数3是WM_ACTIVE的消息参数lParam表示的窗口。
_AfxHandleActivate是MFC内部使用的函数,声明和实现均在WinCore.CPP文件中。实现如下:
如果pWnd指向的窗口不是子窗口,而且pWnd和pWndOther窗口的顶级父窗口(TopLevelParent)不是同一窗口,则发送MFC定义的消息WM_ACTIVATETOPLEVEL给pWnd的顶级窗口,消息参数wParam是nState,消息参数lParam指向一个长度为二的数组,数组里存放pWnd和pWndOther所指窗口的句柄。否则,_AfxHandleActivate不作什么。
从这里可以看出:只有顶层的主边框窗口能处理WM_ACTIVE消息,事实上,Windows系统只会给顶层的非子窗口发送WM_ACTIVE消息。
WM_ACTIVATETOPLEVEL消息的处理
CWnd及派生类CFrameWnd实现了对WM_ACTIVATETOPLEVEL消息的处理,分别解释如下:
消息处理函数CWnd::OnActivateTopLevel如果失去激活,则取消工具栏的提示(TOOLTIP)。
消息处理函数CFrameWnd::OnActivateTopLevel调用CWnd的OnActivateTopLevel;如果接收WM_ACTIVE消息的窗口是线程主窗口,则使得其活动的视窗口变成非活动的(OnActive(FALSE, pActiveView,pActiveView)。
从这里可以知道,在顶层窗口接收到WM_ACTIVE消息后,MFC会进行一些固定的处理,然后才调用WM_ACTIVE消息处理函数。
WM_ACTIVE消息的处理
在_AfxHandleActivate和WM_ACTIVATETOPLEVEL消息处理完之后,才是对WM_ACTIVE的处理。CWnd和CFrameWnd都实现了消息处理。
CWnd的消息处理函数:
void CWnd::OnActive(UINT nState, CWnd* pWndOther, BOOL bMinimized)
其中:
参数1取值为WA_INACTIVE/WA_ACTIVE/WA_CLICKACTIVE;
参数2指向激活或者失去激活的窗口,具体同WM_ACTIVE消息;
参数3表示是否最小化。
此函数的实现是调用Default(),作缺省处理。
CFrameWnd的消息处理函数:
void CFrameWnd::OnActive(UINT nState,CWnd* pWndOther, BOOL bMinimized)
首先调用CWnd::OnActivate。
如果活动视非空,消息是WA_ACTIVE/WA_CLICKACTIVE,并且不是最小化,则重新激活当前视,调用了以下函数:
pActiveView->OnActiveView(TRUE,pActiveView,pActiveView);
并且,如果活动视非空,通知它边框窗口状态的变化(激活/失去激活),调用以下函数:
pActiveView->OnActivateFrame(nState, this)。
SDI流程的回顾
从InitialInstance开始,首先应用程序对象创建文档模板,文档模板创建文档对象、打开或创建文件;然后,文档模板创建边框窗口对象和边框窗口;接着边框窗口对象创建视对象和视窗口。这些创建是以应用程序的文档模板为中心进行的。在创建这些MFC对象的同时,建立了它们之间的关系。创建这些之后,进行初始化,激活主边框窗口,把边框窗口、视窗口显示出来。
这样,一个SDI应用程序就完成了启动过程,等待着用户的交互或者输入。
5.3.4节将在SDI程序启动流程的基础之上,介绍MDI应用程序的启动流程。两者的相同之处可以这样类比:创建SDI边框窗口、视、文档的过程和创建MDI文档边框窗口、视、文档的过程类似。不同之处主要表现在:主边框窗口的创建不一样;MDI有文档边框窗口的创建,SDI没有;SDI只能一个文档、一个视;MDI可能多文档、多个视。
MDI程序的对象创建
MDI应用程序对象的InitialInstance函数一般含有以下代码:
//第一部分:创建和添加模板
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_TTTYPE,
RUNTIME_CLASS(CTtDoc),
RUNTIME_CLASS(CChildFrame),//custom MDI child frame
RUNTIME_CLASS(CTtView));
AddDocTemplate(pDocTemplate);
//第二部分:创建MFC框架窗口对象和Windows主边框窗口
// 创建主MDI边框窗口
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
//第三部分:处理命令行,命令行空则执行OnFileNew创建新文档
//分析命令行
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// 处理命令行命令
if (!ProcessShellCommand(cmdInfo))
return FALSE;
第四部分:显示和更新主框架窗口
// 主窗口已被初始化,现在显示和更新主窗口
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
SDI应用程序对象的InitialInstance和SDI应用程序对象的InitialInstance比较,有以下的相同和不同之处。相同之处在于:
创建和添加模板;处理命令行。
不同之处在于:
创建的模板类型不同。SDI使用单文档模板,边框窗口类从CFrameWnd派生;MDI使用多文档模板,边框窗口类从CMDIChildWnd派生.
主窗口类型不同。SDI的是从CFrameWnd派生的类;MDI的是从CMDIFrameWnd派生的类。
主框架窗口的创建方式不同。SDI在创建或者打开文档时动态创建主窗口对象,然后加载主窗口(LoadFrame)并初始化;MDI使用第二部分的语句来创建动态主窗口对象和加载主窗口,第四部分语句显示、更新主窗口。
命令行处理的用途不一样。SDI一定要有命令行处理部分的代码,因为它导致了主窗口的创建;MDI可以去掉这部分代码,因为它的主窗口的创建、显示等由第二、四部分的语句来处理。
有别于SDI的主窗口加载过程
和SDI应用程序一样,MDI应用程序使用LoadFrame加载主边框窗口,但因为LoadFrame的虚拟属性,所以MDI调用了CMDIFrameWnd的LoadFrame函数,而不是CFrameWnd的LoadFrame。
LoadFrame的参数1指定了资源ID,其余几个参数取缺省值。和SDI相比,第四个创建上下文参数为NULL,因为MDI主窗口不需要文档、视等的动态创建信息。
图 5-17图解了CMdiFrameWnd::LoadFrame的流程:
首先,用同样的参数调用基类CFrameWnd的LoadFrame,其流程如图5-11所示,但由于参数4表示的创建上下文为空,所以,CFrameWnd::LoadFrame在加载了菜单和快捷键之后,给所有子窗口发送WM_INITUPDATE消息。
另外,WM_CREATE消息怎样处理呢?由于CMDIFrameWnd没有覆盖OnCreate,所以还是由基类CFrameWnd::OnCreate处理。但是它调用虚拟函数OnCreateClient(见图5-12)时,由于CMDIFrameWnd覆盖了该函数,所以动态约束的结果是CMDIFrameWnd::OnCreateClient被调用,它和基类的OnCreateClient不同,后者CreateView创建MFC视对象和视窗口,前者调用虚拟函数CreateClient创建MDI客户窗口。MDI客户窗口负责创建和管理MDI子窗口。
CreateClient是CMDIFrameWnd的虚拟函数,其原型如下:
BOOL CMDIFrameWnd::CreateClient(
LPCREATESTRUCT lpCreateStruct, CMenu* pWindowMenu);
该函数主要用来创建MDI客户区窗口。它使用Windows系统预定义的“mdiclient”窗口类来创建客户区窗口,保存该窗口句柄在CMDIFrameWnd的成员变量m_hWndMDIClient中。调用::CreateWindowEx创建客户窗口时传递给它的窗口创建数据参数(第11个参数)是一个CLIENTCREATESTRUCT结构类型的参数,该结构指定了一个菜单和一个子窗口ID:
typedef struct tagCLIENTCREATESTRUCT{
HMENU hWindowMenu;
UINT idFirstChild;
}CLIENTCREATESTRUCT;
hWindowMenu表示主边框窗口菜单栏上的“Windows弹出菜单项”的句柄。MDICLIENT类客户窗口在创建MDI子窗口时,把每一个子窗口的标题加在这个弹出菜单的底部。idFirstChild是第一个被创建的MDI子窗口的ID号,第二个MDI子窗口ID号为idFirstChild+1,依此类推。
这里,hWindowMenu的指定不是必须的,程序员可以在MDI子窗口激活时进行菜单的处理;idFirstChild的值是AFX_IDM_FIRST_MDICHILD。
综合地讲,CMDIFrameWnd::LoadFrame完成创建MDI主边框窗口和MDI客户区窗口的工作。
创建了MDI边框窗口和客户区窗口之后,接着是处理WM_INITUPDATE消息,进行初始化。但是按SDI应用程序的讨论顺序,下一节先讨论MDI子窗口的创建。
MDI子窗口、视、文档的创建
和SDI应用程序类似,MDI应用程序通过文档模板来动态创建MDI子窗口、视、文档对象。不同之处在于:这里使用了多文档模板,调用的是CMDIChildWnd(或派生类)的消息处理函数和虚拟函数,如果它覆盖了CFrameWnd的有关函数的话。
还是以处理标准命令消息ID_FILE_NEW的OnFileNew为例。
表示OnFileNew的图5-5、表示OnFileOpen的图5-6在多文档应用程序中仍然适用,但表示OpenDocumentFile的图5-8有所不同,其第三步中地单文档模板应当换成多文档模板,关于这一点,参阅图5-8的说明。
(1)多文档模板的OpenDocumentFile
MDI的OpenDocumentFile的原型如下:
CDocument* CMultiDocTemplate::OpenDocumentFile(
LPCTSTR lpszPathName, BOOL bMakeVisible);
它的原型和单文档模板的该函数原型一样,但处理流程比图5-8要简单些:
第一,不用检查是否已经打开了文档;
第二,不用判断是否需要创建框架窗口或者文档对象,因为不论新建还是打开文档都需要创建新的文档框架窗口(MDI子窗口)和文档对象。
除了这两点,其他处理步骤基本相同,调用同样名字的函数来创建文档对象和MDI子窗口。虽然是名字相同的函数,但是参数的值可能有异,又由于C++的虚拟机制和MFC消息映射机制,这些函数可能来自不同层次类的成员函数,因而导致有不同的处理过程和结果,即SDI创建了CFrameWnd类型的对象和边框窗口;MDI则创建了CMDIChildWnd类型的对象和边框窗口。不同之处解释如下:
(2)CMDIChildWnd的虚拟函数LoadFrame
CMDIChildWnd::LoadFrame代替了图5-8中的CFrameWnd::LoadFrame,两者流程大致相同,可以参见图5-11。但是它们用来创建窗口的函数不同。前者调用了函数CMDIChildWnd::Create(参数1…参数6);后者调用了CFrameWnd::Create(参数1…参数7)。
这两个窗口创建函数,虽然都是虚拟函数,但是有很多不同之处:
前者是CMDIChildWnd定义的虚拟函数,后者是CWnd定义的虚拟函数;
前者在参数中指定了父窗口,即主创建窗口,后者的父窗口参数为NULL;
前者指定了WS_CHILD风格,创建的是子窗口,后者创建一个顶层窗口;
前者给客户窗口m_hWndMDIClient(CMDIFrameWnd的成员变量)发送WM_MDICREATE消息让客户窗口来创建MDI子窗口(主边框窗口的子窗口是客户窗口,客户窗口的子窗口是MDI子窗口),后者调用::CreateEx函数来创建边框窗口;
前者的窗口创建数据是指向MDICREATESTRUCT结构的指针,该结构的最后一个域存放一个指向CCreateContext结构的指针,后者是指向CCreateContext结构的指针。
MDICREATESTRUCT结构的定义如下:
typedef struct tagMDICREATESTRUCT { // mdic
LPCTSTR szClass;
LPCTSTR szTitle;
HANDLE hOwner;
int x;
int y;
int cx;
int cy;
DWORD style;
LPARAM lParam;
}MDICREATESTRUCT;
该结构的用处和CREATESTRUCT类似,只是它仅用于MDI子窗口的创建上,用来保存创建MDI子窗口时的窗口创建数据。域lParam保存一个指向CCreateContext结构的指针。
WM_CREATE的处理函数不同
创建MDI子窗口时发送的WM_CREATE消息由CMDIChildWnd的成员函数OnCreate(LPCREATESTRUCT lpCreateStruct)处理。
OnCreate函数仅仅从lpCreateStruct指向的数据中取出窗口创建数据,即指向MDICREATESTRUCT结构的指针,并从该结构得到指向CCreateContext结构的指针pContext,然后调用虚拟函数OnCreateHelper(lpCreateStruct,pContext)。
此处动态约束的结果是调用了CFrameWnd的成员函数OnCreateHelper。SDI应用程序的OnCreate也调用了CFrameWnd::OnCreateHelper,所以后面的处理(创建视等)可参见SDI的流程了。
待MDI子窗口、视、文档对象创建完毕,多文档模板的OpenDocumentFile也调用InitialUpdateFrame来进行初始化。
MDI子窗口的初始化和窗口的激活
(1)MDI子窗口的初始化
完成了 MDI子窗口、视、文档的创建之后,多文档模板的OpenDocumenFile调用边框窗口的虚拟函数InitialUpdateFrame进行初始化,该函数流程参见图5-14。不过,这里this指针指向CMDIChildWnd对象,由于C++虚拟函数的动态约束,初始化过程调用了CMDIChildWnd的ActivateFrame函数(不是CFrameWnd的ActivateFrame),来显示MDI子窗口,更新菜单等等,见图5-18。
图5-18的说明:
第一,调用基类CFrameWnd的ActivateFrame显示窗口时,由于当前窗口是文档边框窗口,所以没有发送WM_ACTIVATE消息,而是发送消息WM_MDIACTIVATE。
第二,由于Windows不处理MDI子窗口的激活,所以必须由MFC或者程序员来完成。当一个激活的MDI子窗口被隐藏后从可见变成不可见,但它仍然是活动的,这时需要把下一文档边框窗口激活以便用户看到的就是激活的窗口。在没有其他文档边框窗口时,则把该隐藏的文档边框窗口标记为“伪失去激活”。当一个文档边框窗口从不可见变成可见时,检查变量m_bPseudoInactive,若真则该窗口从Windows角度看仍然是激活的,只需要调用OnMDIActivate把它改成“MFC激活”。OnMDIActivate把变量m_bPseudoInactive的值改变为FALSE。
至此,MDI子窗口初始化调用描述完毕。初始化将导致MDI窗口被显示、激活。下面讨论MDI子窗口的激活。
(2)MDI子窗口的激活
通过给客户窗口发送消息WM_MDIACTIVATE来激活文档边框窗口。客户窗口发送WM_MDIACTIVATE消息给将被激活或者取消激活的MDI子窗口(文档边框窗口),这些子窗口调用消息处理函数OnMDIActivate响应该消息WM_MDIACTIVATE。关于MDI消息,见表5-12。
用户转向一个子窗口(包括文档边框窗口)导致它的顶层(TOP LEVEL)边框窗口收到WM_ACTIVATE消息而被激活,子窗口是文档边框窗口的话将收到WM_MDIACTIVATE消息。
但是,一个边框窗口被其他方式激活时,它的文档边框窗口不会收到WM_MDIACTIVATE消息,而是最近一次被激活的文档边框窗口收到WM_NCACTIVATE消息。该消息由CWnd::Default缺省处理,用来重绘文档边框窗口的标题栏、边框等等。
MDI子窗口用OnMDIActiveate函数处理WM_MDIACTIVATE消息。其原型如下:
void CMDIChildWnd::OnMDIActivate( BOOL bActivate,
CWnd* pActivateWnd,CWnd* pDeactivateWnd );
其中:
参数1表示是激活(TRUE),还是失去激活(FALSE);
参数2表示将被激活的MDI子窗口;
参数3表示将被失去激活的MDI子窗口;
简单地说,该函数把m_bPseudoInactive的值改变为FALSE,调用成员函数OnActivateView通知失去激活的子窗口的视它将失去激活,调用成员函数OnActivateView通知激活子窗口的视它将被激活。
至于MDI主边框窗口,它还是响应WM_ACTIVATE消息而被激活或相反。CMDIFrameWnd没有提供该消息的处理函数,它调用基类CFrameWnd的处理函数OnActivate。
现在,MDI应用程序的启动过程描述完毕。
表5-12 MDI消息
消息 | 说明 |
WM_MDIACTIVATE | 激活MDI Child窗口 |
WM_MDICASCADE | CASCADE排列MDI Child窗口 |
WM_MDICREATE | 创建MDI Child窗口 |
WM_MDIDESTROY | 销毁MDI Child窗口 |
WM_MDIGETACTIVE | 得到活动的MDI Child窗口 |
WM_MDIICONARRANGE | 安排最小化了的MDI Child窗口 |
WM_MDIMAXIMIZE | MDI Child窗口最大化 |
WM_MDINEXT | 激活Z轴顺序的下一MDI Child窗口 |
WM_MDIREFRESHMENU | 根据当前MDI Child窗口更新菜单 |
WM_MDIRESTORE | 恢复MDI Child窗口 |
WM_MDISETMENU | 根据当前MDI Child窗口设置菜单 |
WM_MDITITLE | TITLE安排MDI Child窗口 |