一个APP可以有多个文档模板,一个文档模板可以有多个文档(Document),一个Document可以有多个View。在程序。要在程序中添加新的文档模板可以如下所示:
CSingleDocTemplate*pDocTemplate; pDocTemplate = newCSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS(CmfcArchiveDoc), RUNTIME_CLASS(CMainFrame), // 主 SDI 框架窗口 RUNTIME_CLASS(CmfcArchiveView)); if(!pDocTemplate) returnFALSE; AddDocTemplate(pDocTemplate); CSingleDocTemplate* pDocNew; pDocNew = newCSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS(CmfcArchiveDoc), RUNTIME_CLASS(CMainFrame), // 主 SDI 框架窗口 RUNTIME_CLASS(CmfcArchiveView)); if (pDocNew == NULL) returnFALSE; AddDocTemplate(pDocNew);
1 程序启动
在程序初始化或者点击菜单新建的时候都会调用OnNewDocument函数。具体流程如下所示:
<1>CWinApp::OnFileNew()//appdlg.cpp
void CWinApp::OnFileNew() { if(m_pDocManager != NULL) m_pDocManager->OnFileNew(); }
<2>CDocManager::OnFileNew() //docmgr.cpp
void CDocManager::OnFileNew() { if(m_templateList.IsEmpty()) { TRACE(traceAppMsg, 0, "Error: no document templates registered withCWinApp.\n"); AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC); return; } CDocTemplate* pTemplate =(CDocTemplate*)m_templateList.GetHead(); if(m_templateList.GetCount() > 1) { //more than one document template to choose from //bring up dialog prompting user CNewTypeDlgdlg(&m_templateList); INT_PTR nID = dlg.DoModal(); if(nID == IDOK) pTemplate =dlg.m_pSelectedTemplate; else return; // none - cancel operation } ASSERT(pTemplate != NULL); ASSERT_KINDOF(CDocTemplate, pTemplate); pTemplate->OpenDocumentFile(NULL); //if returns NULL, the user has already been alerted }
从这个函数可以看到首次运行程序的时候吧,程序会从成员变量m_templateList这里获取程序的模板中的列表,并且弹出对话框供user选择使用哪一个模板
最后在pTemplate->OpenDocumentFile(NULL)调用OpenDocumentFlle函数,因为这个文档模板是单文档的 ,所以pTemplate是CSingleTemplate类型的。
<3> CSingleDocTemplate::OpenDocumentFile(LPCTSTRlpszPathName, BOOL bMakeVisible)
CDocument*CSingleDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName, BOOL bMakeVisible) { returnOpenDocumentFile(lpszPathName, TRUE, bMakeVisible); }
<4> CSingleDocTemplate::OpenDocumentFile(LPCTSTRlpszPathName, BOOL bAddToMRU, BOOL bMakeVisible)
这个函数时主要的函数
CDocument*CSingleDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName, BOOL bAddToMRU, BOOLbMakeVisible) { CDocument* pDocument = NULL; CFrameWnd* pFrame = NULL; BOOL bCreated = FALSE; // => docand frame created BOOL bWasModified = FALSE; if(m_pOnlyDoc != NULL) { //already have a document - reinit it pDocument = m_pOnlyDoc; if(!pDocument->SaveModified()) { // set a flag to indicate that the document being openedshould not // be removed from the MRU list, if it was being openedfrom there g_bRemoveFromMRU =FALSE; return NULL; // leave the original one } pFrame =(CFrameWnd*)AfxGetMainWnd(); ASSERT(pFrame != NULL); ASSERT_KINDOF(CFrameWnd,pFrame); ASSERT_VALID(pFrame); } else { //create a new document pDocument = CreateNewDocument(); ASSERT(pFrame == NULL); // will becreated below bCreated = TRUE; } if(pDocument == NULL) { AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC); returnNULL; } ASSERT(pDocument == m_pOnlyDoc); if(pFrame == NULL) { ASSERT(bCreated); //create frame - set as main document frame BOOL bAutoDelete =pDocument->m_bAutoDelete; pDocument->m_bAutoDelete =FALSE; // don't destroy if something goes wrong pFrame =CreateNewFrame(pDocument, NULL); pDocument->m_bAutoDelete =bAutoDelete; if(pFrame == NULL) { AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC); delete pDocument; // explicitdelete on error return NULL; } } if(lpszPathName == NULL) { //create a new document SetDefaultTitle(pDocument); //avoid creating temporary compound file when starting up invisible if(!bMakeVisible) pDocument->m_bEmbedded= TRUE; if (!pDocument->OnNewDocument()) { // user has been alerted to what failed in OnNewDocument TRACE(traceAppMsg,0, "CDocument::OnNewDocument returnedFALSE.\n"); if (bCreated) pFrame->DestroyWindow(); // will destroydocument return NULL; } } else { CWaitCursor wait; //open an existing document bWasModified =pDocument->IsModified(); pDocument->SetModifiedFlag(FALSE); // not dirty foropen if (!pDocument->OnOpenDocument(lpszPathName)) { // user has been alerted to what failed in OnOpenDocument TRACE(traceAppMsg,0, "CDocument::OnOpenDocument returnedFALSE.\n"); if (bCreated) { pFrame->DestroyWindow(); // will destroydocument } else if(!pDocument->IsModified()) { // original document is untouched pDocument->SetModifiedFlag(bWasModified); } else { // we corrupted the original document SetDefaultTitle(pDocument); if (!pDocument->OnNewDocument()) { TRACE(traceAppMsg,0, "Error: OnNewDocument failed after trying" "to open a document - trying to continue.\n"); // assume we can continue } } return NULL; // open failed } pDocument->SetPathName(lpszPathName,bAddToMRU); pDocument->OnDocumentEvent(CDocument::onAfterOpenDocument); } CWinThread* pThread = AfxGetThread(); ASSERT(pThread); if(bCreated && pThread->m_pMainWnd == NULL) { //set as main frame (InitialUpdateFrame will show the window) pThread->m_pMainWnd =pFrame; } InitialUpdateFrame(pFrame, pDocument,bMakeVisible); returnpDocument; }
因为是第一次打开程序,所以会新建一个CDocument,以及MainFrame,最后就会调用我们自己写的OnNewDocument。
<5> CmfcArchiveDoc::OnNewDocument()
BOOLCmfcArchiveDoc::OnNewDocument() { if(!CDocument::OnNewDocument()) returnFALSE; // TODO: 在此添加重新初始化代码 // (SDI 文档将重用该文档) returnTRUE; }
在这个函数里面会调用CDocument::OnNewDocument
总结:
<1>CWinApp::OnFileNew()//appdlg.cpp
<2>CDocManager::OnFileNew() //docmgr.cpp
<3>CSingleDocTemplate::OpenDocumentFile(LPCTSTRlpszPathName, BOOL bMakeVisible)
<4>CSingleDocTemplate::OpenDocumentFile(LPCTSTRlpszPathName, BOOL bAddToMRU, BOOL bMakeVisible)
<5>CmfcArchiveDoc::OnNewDocument()
2 文件新建
文件新建的流程跟程序初次打开流程一样,只不过在CSingleDocTemplate::OpenDocumentFile(LPCTSTRlpszPathName, BOOL bAddToMRU, BOOL bMakeVisible)
中函数执行的内容可能不同,如果打开的是同一个文档模板的话,那么就不会再去创建文档,FrameWindow。否则的话就要新建新的模板。
3 文件打开
文件打开以及文件关闭会执行的命令是ID_FILE_OPEN,ID_FILE_SAVE。这两个命令都是跟文档串行化有关的,一个是存档,一个载入。
<1> CWinApp::OnFileOpen()
void CWinApp::OnFileOpen() { ENSURE(m_pDocManager != NULL); m_pDocManager->OnFileOpen(); }
<2> CDocManager::OnFileOpen()
void CDocManager::OnFileOpen() { // prompt theuser (with all document templates) CString newName; if(!DoPromptFileName(newName, AFX_IDS_OPENFILE, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, TRUE, NULL)) return;// open cancelled AfxGetApp()->OpenDocumentFile(newName); //if returns NULL, the user has already been alerted }
其中DoPromptFileName函数会弹出一个对话框提示选择要打开的文件,并且在选定后会保存文件的名字和路径。
<3> CWinApp::OpenDocumentFile(LPCTSTR lpszFileName)
CDocument*CWinApp::OpenDocumentFile(LPCTSTR lpszFileName) { ENSURE_VALID(m_pDocManager); returnm_pDocManager->OpenDocumentFile(lpszFileName); }
<4> CDocManager::OpenDocumentFile(LPCTSTRlpszFileName)
CDocument*CDocManager::OpenDocumentFile(LPCTSTR lpszFileName) { returnOpenDocumentFile(lpszFileName, TRUE); }
<5>CDocManager::OpenDocumentFile(LPCTSTR lpszFileName, BOOL bAddToMRU)
CDocument*CDocManager::OpenDocumentFile(LPCTSTR lpszFileName, BOOL bAddToMRU) { if(lpszFileName == NULL) { AfxThrowInvalidArgException(); } // find thehighest confidence POSITION pos =m_templateList.GetHeadPosition(); CDocTemplate::Confidence bestMatch =CDocTemplate::noAttempt; CDocTemplate* pBestTemplate = NULL; CDocument* pOpenDocument = NULL; TCHAR szPath[_MAX_PATH]; ASSERT(lstrlen(lpszFileName) <_countof(szPath)); TCHAR szTemp[_MAX_PATH]; if(lpszFileName[0] == '\"') ++lpszFileName; Checked::tcsncpy_s(szTemp,_countof(szTemp), lpszFileName, _TRUNCATE); LPTSTR lpszLast = _tcsrchr(szTemp, '\"'); if(lpszLast != NULL) *lpszLast = 0; if(AfxFullPath(szPath, szTemp) == FALSE ) { ASSERT(FALSE); returnNULL; // We won't open the file. MFC requires pathswith //length < _MAX_PATH } TCHAR szLinkName[_MAX_PATH]; if(AfxResolveShortcut(AfxGetMainWnd(), szPath, szLinkName, _MAX_PATH)) Checked::tcscpy_s(szPath,_countof(szPath), szLinkName); while(pos != NULL) { CDocTemplate* pTemplate =(CDocTemplate*)m_templateList.GetNext(pos); ASSERT_KINDOF(CDocTemplate,pTemplate); CDocTemplate::Confidencematch; ASSERT(pOpenDocument ==NULL); match =pTemplate->MatchDocType(szPath, pOpenDocument); if(match > bestMatch) { bestMatch = match; pBestTemplate =pTemplate; } if(match == CDocTemplate::yesAlreadyOpen) break; // stop here } if (pOpenDocument != NULL) { POSITION posOpenDoc =pOpenDocument->GetFirstViewPosition(); if(posOpenDoc != NULL) { CView* pView =pOpenDocument->GetNextView(posOpenDoc); // getfirst one ASSERT_VALID(pView); CFrameWnd* pFrame =pView->GetParentFrame(); if (pFrame == NULL) TRACE(traceAppMsg,0, "Error: Can not find a frame for documentto activate.\n"); else { pFrame->ActivateFrame(); if (pFrame->GetParent() != NULL) { CFrameWnd*pAppFrame; if (pFrame != (pAppFrame =(CFrameWnd*)AfxGetApp()->m_pMainWnd)) { ASSERT_KINDOF(CFrameWnd,pAppFrame); pAppFrame->ActivateFrame(); } } } } else TRACE(traceAppMsg,0, "Error: Can not find a view for document toactivate.\n"); returnpOpenDocument; } if(pBestTemplate == NULL) { AfxMessageBox(AFX_IDP_FAILED_TO_OPEN_DOC); returnNULL; } return pBestTemplate->OpenDocumentFile(szPath,bAddToMRU, TRUE); }
程序会判断pDocument是否是NULL,是的话就会调用pBestTemplate->OpenDocumentFile(szPath,bAddToMRU, TRUE);
第一次打开一个文件的时候,必然会调用这个函数的。
<6> CSingleDocTemplate::OpenDocumentFile(LPCTSTRlpszPathName, BOOL bAddToMRU, BOOL bMakeVisible)
这个函数和程序启动以及新建操作的函数一样,关键是调用的部分不用。它调用的部分是:
lpszPathName!= NULL 的部分
else { CWaitCursor wait; //open an existing document bWasModified =pDocument->IsModified(); pDocument->SetModifiedFlag(FALSE); // not dirty foropen if (!pDocument->OnOpenDocument(lpszPathName)) { // user has been alerted to what failed in OnOpenDocument TRACE(traceAppMsg,0, "CDocument::OnOpenDocument returnedFALSE.\n"); if (bCreated) { pFrame->DestroyWindow(); // will destroydocument } else if(!pDocument->IsModified()) { // original document is untouched pDocument->SetModifiedFlag(bWasModified); } else { // we corrupted the original document SetDefaultTitle(pDocument); if (!pDocument->OnNewDocument()) { TRACE(traceAppMsg,0, "Error: OnNewDocument failed after trying" "to open a document - trying to continue.\n"); // assume we can continue } } return NULL; // open failed }
<7> CDocument::OnOpenDocument(LPCTSTR lpszPathName)
Serialize(loadArchive); // load me
OnOpenDocument函数中会调用Serialize()函数。这个Serialize函数是个虚函数,从而会调用的是CmfcArchiveDoc::Serialize(CArchive& ar)
总结:
<1> CWinApp::OnFileOpen()
<2> CDocManager::OnFileOpen()
<3> CWinApp::OpenDocumentFile(LPCTSTR lpszFileName)
<4> CDocManager::OpenDocumentFile(LPCTSTRlpszFileName)
<5>CDocManager::OpenDocumentFile(LPCTSTR lpszFileName, BOOL bAddToMRU)
<6> CSingleDocTemplate::OpenDocumentFile(LPCTSTRlpszPathName, BOOL bAddToMRU, BOOL bMakeVisible)
<7> CDocument::OnOpenDocument(LPCTSTR lpszPathName)
其中要是注意的是如果已经打开了一个文档并且没有关闭,那么在再次打开的时候不会调用序列化函数,原因是当已经打开了一个文档,再次打开它的时候,CDocManager::OnFileOpen()这个函数中的pDocument已经有值了,所以不会再去打开了。
4 文件保存
<1> void CDocument::OnFileSave() //doccore.cpp
<2> BOOL CDocument::DoFileSave() //doccore.cpp
<3> BOOL CDocument::DoSave(LPCTSTR lpszPathName,BOOL bReplace) //doccore.cpp
<4> BOOL CDocument::OnSaveDocument(LPCTSTRlpszPathName) //doccore.cpp
<5> void CmfcArchiveDoc::Serialize(CArchive& ar)
这个就是文件的保存流程。
MFC File相关命令流程分析