⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 13.2.2 onnewdocument函数的调用过程.txt

📁 网上第一本以TXT格式的VC++深入详解孙鑫的书.全文全以TXT格式,并每一章节都分了目录,清晰易读
💻 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 + -