MFC File相關命令流程分析
一個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)
這個就是檔案的儲存流程。