文檔/視圖結(jié)構(gòu)是MFC中最有特色而又有難度的部分,在這當(dāng)中涉及了應(yīng)用、文檔模板、文檔、視圖、MDI框架窗口、MDI子窗口等不同的對(duì)象,如果不了解 這些部分之間如何關(guān)聯(lián)的話,就可能犯錯(cuò)誤,也就很難編出有水平的文檔/視圖程序。比如我在初學(xué)VC編程的時(shí)候,為應(yīng)用程序添加了兩個(gè)文檔模板,兩個(gè)模板公 用一個(gè)文檔類,只是視圖不一樣,期望當(dāng)一個(gè)模板的文檔的視圖改變了文檔后,調(diào)用UpdateAllViews后也能更新另一個(gè)文檔模板的視圖,結(jié)果當(dāng)然是 不行的,原因就是對(duì)MFC的文檔/視圖結(jié)構(gòu)沒(méi)有深入的了解,了解的最好方法就是閱讀一下MFC的源代碼。下面就是我的筆記:
(一)應(yīng)用程序?qū)ο笈c文檔模板之間的聯(lián)系:
首先,在應(yīng)用程序?qū)ο笾杏幸粋€(gè)CDocManager指針類型的共有數(shù)據(jù)成員m_pDocManager,在CDocManager中維護(hù)一個(gè) CPtrList類型的鏈表:m_tempateList,它是一個(gè)保護(hù)成員。InitInstance函數(shù)中調(diào)用CWinApp:: AddDocTemplate函數(shù),實(shí)際上是調(diào)用m_pDocManager的AddDocTemplate函數(shù)向鏈表m_templateList添加 模板指針。CWinApp提供了GetFirstDocTemplatePosition和GetNextDocTemplate函數(shù)實(shí)現(xiàn)對(duì) m_templateList鏈表的訪問(wèn)(實(shí)際上是調(diào)用了CDocManager的相關(guān)函數(shù))。
在文件操作方面CWinApp提供的最常用的功能是文件的新建(OnFileNew)和打開(kāi)(OnFileOpen),它也是調(diào)用CDocManager 類的同名函數(shù)。對(duì)于新建,一般的時(shí)候在只有一個(gè)文檔模板的時(shí)候,它新建一個(gè)空白的文件;如果有多個(gè)文檔模板的時(shí)候,它會(huì)出現(xiàn)一個(gè)對(duì)話框提示選擇文檔類型。 它的源代碼如下:
void CDocManager::OnFileNew()
{
if (m_templateList.IsEmpty())
{
.......
return;
}
//取第一個(gè)文檔模板的指針
CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();
if (m_templateList.GetCount() > 1)
{
// 如果多于一個(gè)文檔模板,出現(xiàn)對(duì)話框提示用戶去選擇
CNewTypeDlg dlg(&m_templateList);
int nID = dlg.DoModal();
if (nID == IDOK)
pTemplate = dlg.m_pSelectedTemplate;
else
return; // none - cancel operation
}
......
//參數(shù)為NULL的時(shí)候OpenDocument File會(huì)新建一個(gè)文件
pTemplate->OpenDocumentFile(NULL);
}
打開(kāi)文件:
void CDocManager::OnFileOpen()
{
// 出現(xiàn)打開(kāi)文件對(duì)話框
CString newName;
if (!DoPromptFileName(newName, AFX_IDS_OPENFILE,
OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, TRUE, NULL))
return; // open cancelled
AfxGetApp()->OpenDocumentFile(newName); //實(shí)際也是調(diào)用文檔模板的同名函數(shù)
}
(二)文檔模板與文檔之間的聯(lián)系:
從上面看出應(yīng)用程序?qū)ο髮?duì)文件的新建和打開(kāi)是依靠文檔模板的OpenDocumentFile函數(shù)實(shí)現(xiàn)的。MFC的模板類是用來(lái)聯(lián)系文檔類、視類和框架類的,在它的構(gòu)造函數(shù)就需要這三者的信息:
CDocTemplate ( UINT nIDResource, CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass );
構(gòu)造函數(shù)利用后三個(gè)參數(shù)為它的三個(gè)CruntimeClass*類型的保護(hù)成員賦值:
m_pDocClass = pDocClass;
m_pFrameClass = pFrameClass;
m_pViewClass = pViewClass;
文檔模板分為單文檔模板和多文檔模板兩種,這兩個(gè)模板的實(shí)現(xiàn)是不同的,除了上面的三個(gè)成員,內(nèi)部有彼此不相同的但是很重要的成員變量。對(duì)于多文檔模板: CPtrList m_docList;,單文檔模板:CDocument* m_pOnlyDoc;。它們都有一個(gè)成員函數(shù)AddDocument,分別各自的成員進(jìn)行賦值操作,而在它們的父類的CDocTemplate中則是為 它所添加的文檔的m_pDocTemplate變量賦值為模板自己的地址:
void CDocTemplate::AddDocument(CDocument* pDoc)
{
ASSERT_VALID(pDoc);
ASSERT(pDoc->m_pDocTemplate == NULL);
pDoc->m_pDocTemplate = this;
}
由于單文檔模板只能擁有一個(gè)文檔,所以它只是維護(hù)一個(gè)指向自己所擁有的模板的指針:m_pOnlyDoc,AddDocument函數(shù)就是要為這個(gè)成員賦值:
void CSingleDocTemplate::AddDocument(CDocument* pDoc)
{
......
CDocTemplate::AddDocument(pDoc);
m_pOnlyDoc = pDoc;
}
由于多文檔模板可以擁有多個(gè)文檔,所以它要維護(hù)的是包含它所打開(kāi)的所有文檔的指針的鏈表,所以它的AddDocument的實(shí)現(xiàn)為:
void CMultiDocTemplate::AddDocument(CDocument* pDoc)
{
......
CDocTemplate::AddDocument(pDoc);
m_docList..AddTail(pDoc);
}
模板通過(guò)m_pOnlyDoc(單文檔)或記住了自己所擁有的所有的模板的指針,并通過(guò)GetFirstDocPosition和GetNextDoc函 數(shù)可以實(shí)現(xiàn)對(duì)它所擁有的文檔的訪問(wèn),同時(shí)使文檔記住了自己所屬文檔模板的指針,同時(shí)文檔提供了GetDocTemplate()函數(shù)可以取得它所屬的模 板。
對(duì)AddDocument函數(shù)的調(diào)用主要是發(fā)生在另一個(gè)成員函數(shù)CreateNewDocument里,它的作用是創(chuàng)建一個(gè)新的文檔:
CDocument* CDocTemplate::CreateNewDocument()
{
if (m_pDocClass == NULL)
{
……
}
CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();
……
AddDocument(pDocument);
return pDocument;
}
CreateNewDocument函數(shù)主要利用文檔類的運(yùn)行時(shí)指針的函數(shù)CreateObject創(chuàng)建一個(gè)新文檔對(duì)象,并利用AddDocument將其指針賦給相關(guān)的成員,留做以后使用。
在應(yīng)用程序的OnFileNew和OnFileOpen函數(shù)都使用了模板的OpenDocumentFile函數(shù),而且在實(shí)際編程的時(shí)候也大都使用這個(gè)函 數(shù)。在MSDN的文檔說(shuō)這個(gè)函數(shù)當(dāng)參數(shù)不為NULL的時(shí)候打開(kāi)文件,否則就用上面所說(shuō)的CreateNewDocument函數(shù)創(chuàng)建一個(gè)新文檔,那么它是 如何實(shí)現(xiàn)的呢?
CDocument* CSingleDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,
BOOL bMakeVisible)
{
CDocument* pDocument = NULL;
CFrameWnd* pFrame = NULL;
BOOL bCreated = FALSE; // => doc and frame created
BOOL bWasModified = FALSE;
//如果已經(jīng)有打開(kāi)的文檔,就會(huì)詢問(wèn)否保存文件
if (m_pOnlyDoc != NULL)
{
pDocument = m_pOnlyDoc;
if (!pDocument->SaveModified())
return NULL;
pFrame = (CFrameWnd*)AfxGetMainWnd();
......
}
//創(chuàng)建新文件
else
{
pDocument = CreateNewDocument();
ASSERT(pFrame == NULL);
bCreated = TRUE;
}
......
//如果第一次創(chuàng)建文檔則也要?jiǎng)?chuàng)建框架窗口。
if (pFrame == NULL)
{
ASSERT(bCreated);
// create frame - set as main document frame
BOOL bAutoDelete = pDocument->m_bAutoDelete;
pDocument->m_bAutoDelete = FALSE;
pFrame = CreateNewFrame(pDocument, NULL);
pDocument->m_bAutoDelete = bAutoDelete;
......
}
if (lpszPathName == NULL)
{
// 為新文檔設(shè)置默認(rèn)標(biāo)題
SetDefaultTitle(pDocument);
……
//一般的時(shí)候重載OnNewDocument初始化一些數(shù)據(jù),如果返回FALSE,表示初始化失//敗,銷毀窗口。
if (!pDocument->OnNewDocument())
{
......
if (bCreated)
pFrame->DestroyWindow(); // will destroy document
return NULL;
}
}
else
{
CWaitCursor wait;
// open an existing document
bWasModified = pDocument->IsModified();
pDocument->SetModifiedFlag(FALSE);
//OnOpenDocument函數(shù)重新初始化文檔對(duì)象
if (!pDocument->OnOpenDocument(lpszPathName))
{
if (bCreated)
{
//新建文檔的情況
pFrame->DestroyWindow();
}
else if (!pDocument->IsModified())
{
// 文檔沒(méi)有被修改,恢復(fù)原來(lái)文檔的修改標(biāo)志
pDocument->SetModifiedFlag(bWasModified);
}
else
{
// 修改了原始的文檔
SetDefaultTitle(pDocument);
if (!pDocument->OnNewDocument())
{
TRACE0("Error: OnNewDocument failed after trying to open a document - trying to continue.\n");
}
}
return NULL; // open failed
}
pDocument->SetPathName(lpszPathName);
}
CWinThread* pThread = AfxGetThread();
if (bCreated && pThread->m_pMainWnd == NULL)
{
pThread->m_pMainWnd = pFrame;
}
InitialUpdateFrame(pFrame, pDocument, bMakeVisible);
return pDocument;
}
以下是多文檔模板的OpenDocumentFile的實(shí)現(xiàn)
CDocument* CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,
BOOL bMakeVisible)
{
//新建一個(gè)文檔對(duì)象
CDocument* pDocument = CreateNewDocument();
……
BOOL bAutoDelete = pDocument->m_bAutoDelete;
pDocument->m_bAutoDelete = FALSE;
CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL);
pDocument->m_bAutoDelete = bAutoDelete;
……
if (lpszPathName == NULL)
//當(dāng)是新建的時(shí)候
{
SetDefaultTitle(pDocument);
// avoid creating temporary compound file when starting up invisible
if (!bMakeVisible)
pDocument->m_bEmbedded = TRUE;
if (!pDocument->OnNewDocument())
{
pFrame->DestroyWindow();
return NULL;
}
m_nUntitledCount++;
}
else
{
// 打開(kāi)一個(gè)已經(jīng)存在的文件
CWaitCursor wait;
if (!pDocument->OnOpenDocument(lpszPathName))
{
// user has be alerted to what failed in OnOpenDocument
TRACE0("CDocument::OnOpenDocument returned FALSE.\n");
pFrame->DestroyWindow();
return NULL;
}
pDocument->SetPathName(lpszPathName);
}
InitialUpdateFrame(pFrame, pDocument, bMakeVisible);
return pDocument;
}
從上面看出模板類的OpenDocumentFile函數(shù)里,利用CreateNewDocument對(duì)象使文檔對(duì)象與模板對(duì)象建立了聯(lián)系,利用了CreateNewFrame函數(shù)使框架窗口與文檔、視圖、模板發(fā)生了聯(lián)系: