?? 13.3.2 mfc框架對serialize函數的調用過程.txt
字號:
13.3.2 MFC框架對Serialize函數的調用過程
另外,在Graphic程序運行后,當單擊【文件_保存】和【文件_打開】菜單項時都會出現一個對話
框。但是在CGraphicDoc類中并沒有看到相應的菜單命令響應函數,因此我們可以推斷這些命令響應
函數一定也是在MFC框架內部實現的。我們可以跟蹤【文件_打開】命令的消息響應函數,同時查看
MFC框架對CGraphicDoc類的Serialize函數的調用過程。
同樣,在Microsoft提供的MFC源文件: APPDLG.CPP中,可以看到OnFileOpen也是CWinApp的一個成員
函數。井且按照上面查看CWinApp類的OnFileNew函數定義的方法一樣,可以發現這個OnFileOpen函
數的聲明前面有 afx_msg標識符,說明這個函數確實是菜單命令消息響應函數。該函數的實現代碼
如例 13-13所示。
例 13-13
void CWinApp: :OnFileOpen()
{
ASSERT(m_pDocManager != NULL);
m_pDocManager->OnFileOpen();
可以看到, CWinApp類的 OnFileOpen函數接著調用了文檔管理器的 OnFileOpen函數,該函數的定
義位于 MFC源文件: DOCMGR. CPP文件中,定義代碼如例 13-14所示。
例3-14
void CDocManager: :OnFileOpen() .
// prompt the user (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
可以看到,在 CDocManager類的 OnFileOpen函數中調用了一個函數: DoPrompt FileName,從其函
數名大概可以猜到這是一個顯示"文件打開/保存"對話框的函數(讀者先在此函數調用處設置一個斷
點)。該函數的實現代碼如例 13-15所示。
例 13-15
BOOL CDocManager : : DoPromptFileName (CString& fileName , UINT nIDSTitle, DWORD lFlags ,
BOOL bOpenFileDialog, CDocTemplate* pTemplate) { CFileDialog dlgFile(bOpenFileDialog);
CString title;
VERIFY(title . LoadString(nIDSTitle)) ;
dlgFile.m_ofn.Flags 1= lFlags ;
CString strFilter;
CString strDefault;
if (pTemplate != NULL)
{
ASSERT_ VALID(pTemplate) ;
_AfxAppendFilterSuffix(strFilter, dlgFile . m一ofn, pTemplate , &str Default) ;
else
11 do for all doc template
POSITION pos = m_templateList.GetHeadPosition();
BOOL bFirst = TRUE ;
while (pos != NULL)
{ CDocTemplate* pTemplate = (CDocTemplate*) m_templateList. GetNext
500 I如......
(pos) ; _Af xAppendFilterSuffix(strF工 lter, dlgFile.m_ofn, pTemplate, bFirst ?
&strDefault : NULL); bFirst = FALSE;
.
1/ append the "*.*" all files filter
CString allFilter;
VER工 FY(allFilter.LoadString(AFX_IDS_ALLFILTER));
strFilter += al工 Filter;
strFilter += (TCHAR)'\0'; // next string please
strFilter += _T("*.*");
strFilter += (TCHAR) , \0' ; / / last str工ng
dlgFile.m_ofn.nMaxCustFilter++;
dlgFile.m一ofn.lpstrFilter = strFilter; dlgFile.m_ofn.lpstrTitle = title;
dlgFile.m_ofn.lpstrFile = fileName.GetBuffer(_MAX_PATH);
int nResult = dlgFile.DoModal();
fileName.ReleaseBuffer();
return nResult工 DOK;
可以看到,在 CDocManager類的 DoPromptFileName函數中構造了一個 CFileDialog對象: dlgFile.
由此我們可以知道原來 MFC框架也是利用 CFileDialog這個類來顯示"文件打開"對話框的。
讓我們回到上述如例 13-14所示 CDocManager類的 OnFileOpen函數,可以看到,接下來,該函數調
用 AfxGetApp函數獲得應用程序 (CWinApp)類對象指針,并利用此指針調用 CWinApp對象的
OpenDocumentFile函數。我們在此行代碼處也設置一個斷點。而 CWinApp類的 OpenDocumentFile
定義位于 AppUl.cpp文件中,代碼如例 13-16所示。
例 13-16
CDocument* CWinApp: :OpenDocumentFile(LPCTSTR lpszFileName)
{
ASSERT(m-pDocManager != NULL);
return m-pDocManager->OpenDocumentFile(lpszFileName);
可以看到. CWinApp類的 OpenDocumentFile函數實際上就是去調用 CDocManager類的
OpenDocumentFile函數。后者位于 DOCMGR.CPP文件中,代碼如例 13-17所示。
CDocument* CDocManager::OpenDocumentFile(LPCTSTR lpszFileName) {
11 find the highest confidence
POSITION pos = m_templateList . GetHeadPos工 tion() ;
CDocTemplate ::Confidence bestMatch = CDocTemplate : : noAttempt ;
CDocTemplate* pBestTemplate = NULL ;
( CDocument* pOpenDocument =陽LL ;
TCHAR szPath[_MAX_ PATH] ;
ASSERT(lstrlen(lpszFileName) < _countof(szPath)) ;
TCHAR szTernp[_MAX_ PATH] ;
if (lpszFileNarne[O] , \ 11
I )
++lpszFileName ;
lstrcpyn(szTemp , lpszFileNarne, _MAX_PATH) ;
LPTSTR lpszLast = _tcsrchr(szTemp , \ " , ) ;
if (lpszLast ! = NULL)
*lpszLast = 0 ; AfxFullPath (szPath , szTemp) ; TCHAR szLinkName[_MAX_PATH] ; if
(AfxResolveShortcut(AfxGetMainWnd() , szPath, szL工nkNarne, MAX
PATH) ) lstrcpy(szPath, szLinkName) ;
while (pos ! = NULL)
CDocTemplate* pTemplate = (CDocTemplate*)m_ternplateList .GetNext (pos) ;
ASSERT_KINDOF(CDocTernplate , pTemplate) ;
CDocTemplate : : Confidence match;
ASSERT( p OpenDocument NULL) ;
( rnatch = pTernpl ate->MatchDocType (szPath, pOpenDocument);
if (match > bestMatch)
bestMatch = match;
pBestTernplate = pTernplate ;
}
if (match CDocTemplate : : yesAlreadyOpen) break; /1 stop here
if (pOpenDocument != NULL)
{ POSITION pos = pOpenDocument->GetFirstViewPosition() ; if (pos ! = NULL)
{
CView* pView =pOpenDocument->GetNextView(pos) ; 11 get first one
ASSERT_ VALID(pView) ;
CFrarneWnd* pFrarne = pView->GetParentFrarne() ;
if (pFrarne != NULL)
pFrame->ActivateFrame() ; else TRACEO ("Error: Can not find a frame for document to
activate.
\n" ) ;
CFrameWnd* pAppFrame;
if (pFrame != (pAppFrame = (CFrameWnd*)AfxGetApp()-> m-pMainWnd))
{
ASSERT_ KINDOF(CFrameWnd, pAppFrame); pAppFrame->ActivateFrame() ;
}
else
TRACEO ( "Error : Can not f工 nda飛riew for document to acti vate . \n ;
u )
( return pOpenDocument;
if (pBestTemplate NULL)
{
AfxMessageBox(AFX_IDP_FA ILED_T。一 OPEN_DOC) ; return NULL;
@D return pBestTemplate->OpenDocumentFile(szPath);
讀者先在 CDocManager類的 OpenDocumentFile函數開始處設置一個斷點。然后閱讀此函數的實現代
碼,可以看到,在此函數中定義了一個 CDocument類型的指針變量: pOpenDocument (.位置處的代
碼),并在后面有一個函數調用: MatchDocType (.位置處的代碼),該函數將 pOpenDocument指針變
量作為參數傳遞進去了。我們在此行代碼設置一個斷點。在該代碼的最后利用文檔模板的指針調用
OpenDocumentFile (.位置處的代碼)。因為本程序是一個單文檔類型的程序,所以這里實際上調用
的是單文檔模板類 ( CSingleDocTemplate )的 OpenDocumentFile函數。該函數的代碼可參見本章
前面的內容,可以看到在該函數中,對 IpszPathName變量進行判斷的 else子句塊下面調用了文檔
對象的 OnOpenDocument函數(上面 CSingleDocTemplate類 OpenDocumentFile函數的 .位置處的代
碼)。這個函數的定義位于 DocCore.cpp文件中,代碼如例 13-18所示。
例 13-18
BOOL CDocument: :OnOpenDocument(LPCTSTR lpszPathName)
if (IsModified()) TRACEO ( "Warning : OnOpenDocument replaces an unsaved document . \ n
11 ) i
CFileException fe ; ( CFile* pFile = GetFile(lpszPathName ,
" ‘ I 503
第13
CFile::rnodeReadICFile::shareDenyWrite, &fe);
if (pFile NULL)
{
ReportSaveLoadExcept工O且(lpszPathNarne, &fe , FALSE , AFX_IDP_FAILED_TO一OPEN_DOC) ;
return FALSE;
DeleteContents() ;
SetModifiedFlag(); // dirty during de-serialize
( CArchive loadArchive(pFile , CArchive ::load CArchive::bNoFlushOn
Delete) ; loadArchive.rn-pDocurnent = this; loadArchive . rn_bForceFlat = FALSE; TRY {
CWaitCursor wait;
if (pFile->GetLength() != 0)
③ Serialize(loadArchive); // load rne
loadArchive . Close();
ReleaseFile(pFile , FALSE);
}
CATCH_ALL(e)
{
ReleaseF工le (pFile , TRUE);
DeleteContents() ; // rernove fa工led contents
TRY
ReportSaveLoadException(lpszPathNarne , e ,
FALSE , AFX_IDP_FAILED_TO_OPEN_DOC); } END TRY DELETE_EXCEPTION(e) ; return FALSE;
END CATCH ALL
SetModifiedFlag(FALSE); // start off with unrnod工fied
return TRUE;
這時己經進入到文檔基類 (CDocument)中了。在CDocument類的OnOpenDocument函數中,根據得到的
文件名,構造一個 CFile對象 ce位置處的代碼),然后利用此對象指針構造一個CArchive對象c(.
位置處的代碼),隨后有一個Serialize函數的調用 ce位
置處的代碼〉。可以在此調用處設置一個斷點。
調試運行 Graphic程序,當程序界面出現后,選擇【文件飛打開】菜單項,程序進入 CWinApp類的
OnFileOpen函數,該函數將調用CDocManager類的OnFi1eOpen函數。繼續運行,程序進入CDocManager
類的DoPromptFileName函數,該函數的作用就是彈出一個文件打開(或保存)對話框。繼續運行,程
序就會彈出一個文件打開對話框,從中選擇一個文件,例如 Graphic.txt文件并打開。然后程序調
用 CWinApp類的 OpenDocmentFile函數。繼續運行,程序回到CDocManager類的OpenDocmentFile函
數。繼續運行,程序進入 CSingleD∞Template類的OpenDocumentFile函數。在此函數中,調用文檔
對象的 OnOpenDocument函數。因此,程序進入到CDocument類的OnOpenDocument函數,在該函數中,
首先構造CFile對象,接著構造CArchive對象,之后調用Serialize函數,這是一個虛函數,根據多
態性原則,它調用就是子類: CGraphicDoc類的Serialize函數。繼續運行,就可以看到程序進入了
CGraphicDoc類的Serialize函數。因為此時是從文件加載數據,所以進入該函數中的else分支。繼
續運行程序,即可從彈出的消息框中看到讀取的數據。上述過程如圖 13.14所示。
文件打開菜單命令
圖 13.14 文件打開菜單命令響應過程
剛才我們看到一個現象,就是當保存數據后,在不關閉程序窗口的情況下,再次打開同一個文件時,
程序并不會進入Serialize函數中。讀者可以再次調試運行Graphic程序,
先單擊【文件_保存】菜單項,并選擇一個文件,例如Graphic.txt文件保存數據。然后單擊【文件
_打開】菜單項,這時程序會依次進入CWinApp類的OnFileOpen函數、 CDocManager類的OnFileOpen
函數,通過DoPrompFileName函數調用顯示文件打開對話框。選擇我們剛才所保存的文件:
Graphic.txt,程序進入到 CWinApp類的 OpenDocumentFile函數,然后是CDocManager類的
OpenDocumentFile函數。讀者要注意了,這里非常關鍵!在此函數中,我們單步運行程序,一直運行
到MatchDocType函數調用處。當執行完這個函數調用后,可以發現pOpenDocument這個指針有值了,
也就是說,程序發現這個文件己經與先前的一個文檔對象相關聯了,即獲得了先前的文檔對象指針:
CGraphicDoc類型的指針(如圖 13.15所示)。因為此時 pOpenDocument指針不為空了,那么
CDocManager類的 OpenDocumentFile函數將直接返回 pOpenDocument這個文檔類指針(如例 13-17
所示 CDocManager類 OpenDocumentFile函數實現代碼中@位置處的代碼).這個函數的執行就結束
了,它并沒有調用 CSingleDocTemplate類的 OpenDocumentFile函數,也就沒有調用CDocument函數
中的Serialize函數,當然其子類: CGraphicDoc中的Serialize函數也就沒有被調用。這就是上面在
保存數據之后再次打開同一個文件時,看不到讀取的數據消息框的原因。
圖 13.15 pOpenDocument指針已經指向CGraphicDoc類的對象
讀者可以試驗一下,如果在保存數據之后,再打開另一個文件,這時程序就會彈出消息框,顯示讀
取到的數據,說明這時調用了CGraphicDoc類的Serialize函數。這就說明文檔對象與文件是相關聯
的。一旦換了另一個文件,文檔對象會將它的數據清空,然后將新文件中的數據與這個對象相關聯。
注意在這個過程中,文檔對象是同一個文檔對象。因為對單文檔類型的程序來說,每次只能寫一份
文檔,因此從效率上考慮,沒有必要構造多個文檔對象。但是對多文檔程序來說,每打開一個文件
都會構造一個新的文檔對象。一定要注意,對單文檔來說,文檔對象本身并不會銷毀,它只是將數
據清空,然后再與一個新的
文件相關聯。
上面的調試過程比較復雜,讀者只要記住.(文件保存和【文件打開】萊單項的命令響應畫數都是在
CWinApp類中提供的。CWinApp類有一個成員變量:m_pDocManager. 是指向CDocManager對象的指針,
也就是說, CWinApp負責管理文檔管理器,而后者有
: m_templateList.
里文檔模板,而后者又是用來管理文檔類、框架類和視類的,始終讓這三個對象三位一體,一起為
文檔服務。所以在上述調試過程中,我們可以看到都是由 CWinApp對象轉到 CDocManager對象,再
轉到CSingleDocTemplate對象,如果牽扯到CDocument對象,就轉到文檔對象的函數中。讀者只要掌
握了這一線索,不管函數調用如何跳來跳去,都會很清楚地知道它們之間的調用邏輯。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -