📄 13.2.2 onnewdocument函数的调用过程.txt
字号:
13.2.2 OnNewDocument函数的调用过程
前面已经提过, OnNewDocument函数是文件新建功能的一部分,当程序初始启动时,
或者单击【新建】菜单项时都会调用这个函数。根据前面的知识,对于新建菜单项来说,
应该有一个相应的菜单命令消息响应函数,但是 OnNewDocument函数并不是一个消息响
应函数,它只是一个虚函数。另外在 MFC内部,对于一个菜单项的单击事件来说,它所
! 提供的响应函数的名称是根据菜单项的 ID来设置的,【新建】菜单项的 D是 ID _FILE_NEW ,那
么它的响应函数应该是 OnFileNew。但在程序的文档类中,并没有看到 OnFileNew这样的消息响应
函数,那么我们就有理由相信这个 OnFileNew函数存在于应用程序的框架内部。并且当单击【文件\
新建】菜单项后,仍然是由 OnFileNew这个响应函数响应,然后该函数再去调用 OnNewDocument函
数。因为 OnNewDocument函数是虚函数,如果子类(本例中是 CGraphicDoc )中存在重写的这个函数,
就会调用子类中的这个虚函数,对本例来说,就是调用 CGraphicDoc类的 OnNewDocument函数。给
我们的感觉好像就是一旦单击【文件\新建】菜单命令,程序就调用的是 CGraphicDoc类的
OnNewDocument函数。为了验证我们上述这种猜想,可以在 Microsoft提供的 MFC源代码中找到
OnFileNew函数,看看它的实现过程。在 Visual Studio 6 .0安装日录\VC98\MFC\SRC目录下有一个
APPDLG.CPP文件,在该文件中可以看到 CWinApp类有一个成员函数 : OnFileNew,代码如例 13-6
所示。
例 13-6
void CWinApp : : OnFileNew ()
if (m_pDocManager != NULL)
m_pDocManager->OnFileNew() ;
那么这个 OnFileNew函数是否就是【文件-新建】菜单命令的响应函数呢?读者可以
在该函数名上单击鼠标右键,从弹出的快捷菜单中选择【 Go To Defmition Of OnFileNew>菜单项,
并从随后的对话框中选择 CWinApp类的 OnFileNew函数,即可打开 AFXW剧.H文件,并定位于
OnFileNew函数的声明处,代码如例 13-7所示。
例 13-7
protected:
// rnap to the following for file new/open
afx_msg void OnFileNew();
afx_msg
void OnFileOpen();
可以看到该函数前面有 afx_msg标识符,根据前面章节的知识,我们知道该标识符表明这个函数确
实是一个菜单命令消息响应函数。
在上述例 13-6所示 CWinApp类的 OnFileNew函数中,首先判断 m_pDocManager成员变量是否为空,
该成员变量的类型是 CDocManager指针类型,而 CDocManager对象是文档管理器。在 CDocManager
类内部有一个 CPtrList类型的指针链表 (m_templateList) , 维护了一系列的文档模板指针。当我
们在 CGraphicApp类的InitInstance函数中调用 AddDocTemplate函数加入一个文档模板指针时,实
际上就是加入到 CDocManager对象的这个指针链表中。
在上述例 13-6所示 CWinApp类的 OnFileNew函数中,如果判断 m_pDocManager成员变量不为空的
话,就调用文档管理器对象的 OnFileNew函数。该函数的定义位于 MFC源文件: DOCMGR.CPP文件中,
定义代码如例 13-8所示。
例 13-8
1. void CDocManager: :OnFileNew()
2. {
3. if (m_ternplateList.IsErnpty())
4. {
5. TRACEO("Error: no docurnent ternplates registered with CWinApp.\n. ) ;
6. AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);
7. return;
8. }
9. CDocTernplate* pTernplate = (CDocTernplate*)rn_ternplateList.GetHead();
10. if (rn_ternplateList.GetCount() > 1)
11. {
12. 11 rnore than one docurnent ternplate to choose frorn
13. 11 bring up dialog prornpting user
14. CNewTypeDlg dlg(&rn_ternplateList);
15. int nID = dlg.DoModa1();
16. if (n工 D IDOK)
17. pTernplate = dlg.rn-pSelectedTernplate;
18. else
19. return; // none -cancel operation
20. }
21 . ASSERT(pTemplate != NULL) ,
22 . ASSERT_KINDOF(CDocTemplate , pTemplate) ,
23 . pTemplate->OpenDocumentFile(NULL) ,
24. 11 if returns NULL , the user has already been alerted
25. }
读者先在如例13-8所示CDocManager类的OnFileNew函数开始位置处设置一个断点,然后阅读这段代
码,可以看到,在该函数中,首先判断成员变量m_templateList是否为空(第3行代码),该成员变量
的定义位于 .H文件中,代码如下所示。
11 Irnplementation
protected:
CPtrList rn_templateList ,
可以看到, m_templateList这个成员变量实际上是一个 CPtrList类型的变量,而CP位List就是一
个指针链表。也就是前面所说的,文档管理器利用这个指针链表来维护文档模板的
指针 。
如果判断m_templateList成员变量不为空,那么接下来,CDocManager类的OnFileNew函数就取出一
个文档模板的指针(第 9行代码),再判断一下文档模板链表中文档模板总数是否大于1c第10行代
码〉。对于Graphic程序来说,是一个单文档程序,并且在InitInstance函数中只调用了一次
AddDocTemplate函数,所以这个数量不大于 1。接下来CDocManager类的OnFileNew函数利用得到的
模板指针 CpTemplate)调用OpenDocumentFile函数(第 23行代码),我们也在此函数调用处设置一个
断点。
然后,我们在这个OpenDocumentFile函数上单击鼠标右键,选择【Go To Definition of
OpenDocumentFile】菜单命令,这将定位到CDocTemplate类的OpenDocumentFile函数定义处,代码
如下所示。
virtual CDocument* OpenDocumentFile(
LPCTSTR lpszPathName , BOOL bMakeVisible = TRUE) = 0;
可以看出, CDocTemplate类的 OpenDocumentFile是一个纯虚函数。因为本程序是一个单文档类型
的程序,使用的文档模板是从CDocTemplate派生的CSingleDocTemplate类,在调用时实际传递的是
CSingleDocTemplate对象指针,因此这里调用的 OpenDocumentFile函数实际上是
CSingleDocTemplate类的成员函数,该函数的定义位于如1FC源程序: DOCSINGL.CPP文件中,其定义
代码如例 13-9所示。
1JIJ 13-9
CDocument* CSingleDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName , BOOL bMakeVisible)
11 if lpszPathName NULL => create new file of this type
{
① CDocument* pDocument立即LL, CFrameWnd* pFrame = NULL , BOOL bCreated = FALSE , 11 =>
doc and frame created
492 I ~~~
BOOL bWasModified = FALSE ;
if (m-pOn1yDoc ! = NULL)
// already have a docurnent -re工n工t lt
pDocurnent = rn-pOn1yDOC ;
if ( ! pDocurnent->SaveModified())
return NULL ; 1/ leave the original one
pFrame = (CFrameWnd* )AfxGetMainwnd() ;
ASSERT(pFrame ! = NULL) ;
ASSERT_ KINDOF(CFrarneWnd, pFrarne) ;
ASSERT_ VALID(pFrarne) ;
else
// create a new docurnent
( pDocurnent = Cr eat eNewDocurnent() ; ASSERT(pFrarne NULL) ; // will be created below
bCreated = TRUE ;
if (pDocurnent NULL)
AfxMessageBox{AFX_IDP_FAILED_TO一CREATE_DOC) ; return NULL ;
}
ASSERT(pDocurnent m-pOnlyDoc) ;
if (pFrarne NULL)
ASSERT(bCreated) ;
// create frarne -set as rna工n document frame BOOL bAutoDelete = pDocurnent->m_bAutoDelete;
pDocument ->m_bAutoDelete = FALSE ;
// don't destroy if something goes wrong
( pFrarne = CreateNewFrame(pDocurnent,阳LL) ; pDocurnent->rn_bAutoDelete = bAutoDelete;
if (pFrame == NULL) {
AfxMessageBox(AFX_ IDP_ FAILED_TO_ CREATE_ DOC) ; delete pDocument ; // explicit delete
on error return NULL ;
工f (lpszPathName NULL)
.
" ‘ I 493
第13
11 create a new document
SetDefaultTitle(pDocument) ;
11 avoid creating temporary compound file when starting up inv工s工ble if (!bMakeVisible)
pDocument->m_bEmbedded = TRUE ;
( if (!pDocument->O口NewDocument() )
11 user has been alerted to what failed in OnNewDocument TRACEO( "CDocument: :OnNewDocument
returned FALSE. \ n n ) i if (bCreated)
pFrame->DestroyWindow(); 11 will destroy document return NULL ;
else
CWaitCursor wait ;
11 open an existing document bWasModified = pDocument->IsModified() ; pDocument一
>SetModifiedFlag(FALSE) ; 11 not d工rty for open
⑤ if (lpDocument->OnOpenDocument(lpszPathName))
11 user has been alerted to what fa工led in OnOpenDocument
TRACEO( "CDocument : :OnOpenDocument returned FALSE . \ n 11 ) ;
if (bCreated)
{
pFrame->DestroyWindow() ; 11 will destroy document
else if (!pDocument->IsModified())
11 original document is untouched pDocument->SetModifiedFlag(bWasModified) ;
else
11 we corrupted the original document SetDefaultTitle(pDocument) ;
if (!pDocument->OnNewDocument()) { TRACEO ( "Error: OnNewDocument failed after trying to
open a document -trying to continue . \ n n ) ; 11 assume we can continue
494 I ........
}
return NULL; // open failed
pDocurnent->SetPathName(lpszPathNarne) ;
CWinThread* pThread = AfxGetThread();
if (bCreated && pThread->rn-pMainWnd --NULL)
// set as rnain frarne (InitialUpdateFrarne will show the window) pThread->rn-pMainWnd =
pFrarne;
InitialUpdateFrame(pFrame, pDocurnent , bMakeVisible);
return pDocurnent;
读者先在如例 13-9所示OpenDocumentFile函数开始处设置一个断点,然后阅读该段代码,可以看到,
在此函数中,首先定义了一个文档类指针、框架类指针 ce位置处的代码),接下来调用了
CreateNewDocument函数ce位置处的代码),该函数用来创建-个文档类的对象。在此函数调用处也设
置一个断点。接下来,上述代码中又调用了 CreateNewFrame函数 ce位置处的代码,并在此函数调
用处设置一个断点),该函数将创建一个框架类对象,同时还将创建一个视类对象(读者可自行跟踪
这个 CreateNewFrame函数研究视类对象的创建)。也就是说,第一次启动程序时,在
OpenDocumentFile函数内部创建了文档类对象,同时还创建了框架类对象和视类对象。这就是MFC
提供的文档扁担
类结构的一个特点z每当有一份文档产生时,总是会产生一个文档类对象、
视类对象,它们三位一体来为这份文档服务。
接着, CSingleDocTemplate类的 OpenDocumentFi1e函数调用了 pDocument对象的 OnNewDocument
函数 ce位置处的代码,在此函数处设置一个断点),这是一个虚函数。在前面第三章已经提到,当
程序运行时,岛IFC框架n
A~lJ(:x.I:j:J A> M:i.且λ口咒lElVI、指针,或者是视类指针,它们都是指向派生类的指针。也就
是说,这时所获得的pDocument指针,井不是指向 CDocument这个基类的对象,而是指向 CGraphicDoc
类对象的指针,因此利用这个指针所调用的函数,都是派生类的函数。也就是说,这时实际上调用
的是 CGraphicDoc类的OnNewDocument函数。
读者可以调试运行Graphic程序,将会看到程序将按照如图 13.11所示的调用顺序执行。首先进入
CWinApp类的OnFileNew函数。 CWinApp这个类是从CCmdTarget类间接派生而来的,在前面章节中已
经提到,凡是从CCmdTarget类派生的类都可以接收命令消息,而菜单单击消息恰好就是命令捎息,
因此在CWinApp类中可以对【文件飞新建】菜单命令进行响应。
继续运行程序,将进入到CDocManager类的 OnFi1eNew函数。继续运行,程序将利用文档模板指针
pTemplate调用OpenDocumentFi1e函数。而pTemplate这个模板指针就是单文档模板
CCSingleDocTemplate类型)指针。文档模板管理文档类、框架类和视类对象,
"‘ I 495
第13
而它自身则是由文档管理器管理的。
,F 飞、
一--m_pDocManager->伽FileNewO
------pTemplate->句enDocumentFile(NULL)
----------Crea钮NewDocument
中
CreateNewFrame
+
pDocument->OnNewDocument
图 13.11程序启动时OnNewDocument函数的调用流程
继续运行程序,将进入到CSingleDocTemplate类的 OpenDocumentFile函数,单步执行代码,可以看
到该函数首先创建文档类的对象,之后创建框架类对象,同时会创建视类对象。当然,框架类对象
创建完成之后,还要把框架窗口创建出来,同样,在创建视类对象的同时,还要创建视类窗口。然
后, CSingleDocTemplate类的OpenDocumentFile函数利用文档指针(pDocument),调用
OnNewDocument函数。这时可以将鼠标移动到pDocument变量上,根据VC++提供的智能指示(如图
13.12所示)可以知道,这个指针实际上是指向 CGraphicDoc类对象的指针,因此就会调用到子类
CGraphicDoc的OnNewDocument函数。以下就是程序初始启动时, OnNewDocument函数被调用的流程。
。、
. if (lpszPathHa.e --阳LL)
也
f 11 crpatp a npw docul睡nt
SetDp句ultTitlp(pDOCUhent川
旬
,品
^ ~ 11剧。id creating te叩orat",!I co呻ound file础en stat-ti呵叩 inuisibl
气嗣
if (lbHakeUisible)
ι,
pDocu圃at-〉'-bE阴bpddpd -TRUE;
φf if. (1
))
.苦
R
、‘b { 11 user' 1,d5 üeell dxei teu也V 1IIIICI‘百.xxeu 1.' OnttewDocup世nt TRACEI("CDo
四.ent: :On""曲。cu.ent returnpd FALSE.\n"); if (bCreated)
、
- pFra.e-)DestroyWindow(); 11 .ill dpstroy document
〈收k
return "'LL;
}
F
图 13.12 pD∞ument指针当前值
在Graphic程序界面出现之后,选择【文件飞新建】菜单项,程序会再次依次进入 CWinApp类的
OnFileNew函数、CDocManager类的OnFileNew函数和CSingleDocTemplate
496 I胁'胁'
类的 OpenDocumentFile函数。这时,对于单文档类型的应用程序来说,它会重复地利用己创建的框
架类对象、文档类对象和视类对象。而如果是多文档类型的应用程序,则此时还是会去创建一个新
的文档类对象、框架类对象和视类对象。因为 Graphic程序是一个单文档类型的程序,并且此时文
档类对象、框架类对象和视类对象已经创建了,所以不需要再去创建它们了,也就是说,这时
CSingleDocTemplate类的 OpenDocumentFile函数中不再调用 CreateNewDocument函数和
CreateNewFrame函数,而是直接调用 pDocument对象的 OnNewDocument函数。以上就是当单击【文
件飞新建】菜单项时, OnNewDocument函数被调用的内部过程,如图 13.13所示。
,F『』
一一一-m_pD∞Manager->OnFileNew()
一一 --pTemplate护>OpenDocumentFile(NU
一一--pDocument->OnNewDocument
图 13.13单文档应用程序单击文件新建菜单项后 OnNewDocument函数的调用流程
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -