<原文发表于http://skywolfblog.blog.163.com/blog/static/1779021802012222114736392/>VC2008 sp1,向导生成的doc/view MDI应用程序,主线程继承自CWinAppEx。
菜单选择打开文件,首先是App响应ID_FILE_OPEN命令,从事件映射:
ON_COMMAND(ID_FILE_OPEN, &CWinAppEx::OnFileOpen)
可以看到执行的是CWinAppEx::OnFileOpen()。在CWinAppEx类里没找到OnFileOpen,应该直接继承CWinApp的OnFileOpen。找到代码如下:
void CWinApp::OnFileOpen()
{
ENSURE(m_pDocManager != NULL);
m_pDocManager->OnFileOpen();
}
可以看到里面又调用CDocManager的OnFileOpen()。在此处加个断点,作为跟踪的起点。用菜单打开一个文件开始跟踪。F11进入CDocManager::OnFileOpen(),看到先打开选择文件的对话框,选择文件得到文件名再调用CWinApp的OpenDocumentFile(),代码如下:
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
}进了CWinApp::OpenDocumentFile,我日,又转到CDocManager的OpenDocument()
CDocument* CWinApp::OpenDocumentFile(LPCTSTR lpszFileName)
{
ENSURE_VALID(m_pDocManager);
return m_pDocManager->OpenDocumentFile(lpszFileName);
}一个CDocManager可能注册了了多个template,比如说一个应用可以打开文本,也可以打开图片。CDocManager::OpenDocumentFile()先不知道打开的是什么文件,不知道是用哪一个template来操作。一个个尝试匹配,找到最合适的那个。细节暂时忽略,应该是按注册的扩展名。
CDocument* CDocManager::OpenDocumentFile(LPCTSTR lpszFileName)
{
if (lpszFileName == NULL)
{
AfxThrowInvalidArgException();
}
// find the highest 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);
return NULL; // We won't open the file. MFC requires paths with
             // 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::Confidence match;
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); // get first one
ASSERT_VALID(pView);
CFrameWnd* pFrame = pView->GetParentFrame(); if (pFrame == NULL)
TRACE(traceAppMsg, 0, "Error: Can not find a frame for document to 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 to activate.\n"); return pOpenDocument;
} if (pBestTemplate == NULL)
{
AfxMessageBox(AFX_IDP_FAILED_TO_OPEN_DOC);
return NULL;
} return pBestTemplate->OpenDocumentFile(szPath);
}
那个pOpenDocument没看明白,先不管它。知道进了CDocTemplate的OpenDocumentFile就行。我这里是MDI程序,进的是CMultiDocTemplate::OpenDocumentFileCDocument():
CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,
BOOL bMakeVisible)
{
CDocument* pDocument = CreateNewDocument();
if (pDocument == NULL)
{
TRACE(traceAppMsg, 0, "CDocTemplate::CreateNewDocument returned NULL.\n");
AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);
return NULL;
}
ASSERT_VALID(pDocument); BOOL bAutoDelete = pDocument->m_bAutoDelete;
pDocument->m_bAutoDelete = FALSE;   // don't destroy if something goes wrong
CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL);
pDocument->m_bAutoDelete = bAutoDelete;
if (pFrame == NULL)
{
AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);
delete pDocument;       // explicit delete on error
return NULL;
}
ASSERT_VALID(pFrame); if (lpszPathName == NULL)
{
// create a new document - with default document name
SetDefaultTitle(pDocument); // avoid creating temporary compound file when starting up invisible
if (!bMakeVisible)
pDocument->m_bEmbedded = TRUE; if (!pDocument->OnNewDocument())
{
// user has be alerted to what failed in OnNewDocument
TRACE(traceAppMsg, 0, "CDocument::OnNewDocument returned FALSE.\n");
pFrame->DestroyWindow();
return NULL;
} // it worked, now bump untitled count
m_nUntitledCount++;
}
else
{
// open an existing document
CWaitCursor wait;
if (!pDocument->OnOpenDocument(lpszPathName))
{
// user has be alerted to what failed in OnOpenDocument
TRACE(traceAppMsg, 0, "CDocument::OnOpenDocument returned FALSE.\n");
pFrame->DestroyWindow();
return NULL;
}
pDocument->SetPathName(lpszPathName);
} InitialUpdateFrame(pFrame, pDocument, bMakeVisible);
return pDocument;
}上面先分别用CreateNewDocument()和CreateNewFrame()创建了文档和视图的框架。暂时没有跟踪进去如何创建,反正知道这时候Doc和View(应该在Frame里面创建了)的实例都已经存在了。之后pDocument->OnOpenDocument()执行打开操作,再InitialUpdateFrame()更新窗口。
注意CDocument的OnOpenDocument() 缺省有一个重载,可能需用改写,因为不一定用它内置的CArchive串行化方法保存文件。void CDocTemplate::InitialUpdateFrame(CFrameWnd* pFrame, CDocument* pDoc,
BOOL bMakeVisible)
{
// just delagate to implementation in CFrameWnd
pFrame->InitialUpdateFrame(pDoc, bMakeVisible);
}进CFrameWnd::InitialUpdateFrame看看窗口咋初始化:
void CFrameWnd::InitialUpdateFrame(CDocument* pDoc, BOOL bMakeVisible)
{
// if the frame does not have an active view, set to first pane
CView* pView = NULL;
if (GetActiveView() == NULL)
{
CWnd* pWnd = GetDescendantWindow(AFX_IDW_PANE_FIRST, TRUE);
if (pWnd != NULL && pWnd->IsKindOf(RUNTIME_CLASS(CView)))
{
pView = (CView*)pWnd;
SetActiveView(pView, FALSE);
}
} if (bMakeVisible)
{
// send initial update to all views (and other controls) in the frame
SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE); // give view a chance to save the focus (CFormView needs this)
if (pView != NULL)
pView->OnActivateFrame(WA_INACTIVE, this); // finally, activate the frame
// (send the default show command unless the main desktop window)
int nCmdShow = -1;      // default
CWinApp* pApp = AfxGetApp();
if (pApp != NULL && pApp->m_pMainWnd == this)
{
nCmdShow = pApp->m_nCmdShow; // use the parameter from WinMain
pApp->m_nCmdShow = -1; // set to default after first time
}
ActivateFrame(nCmdShow);
if (pView != NULL)
pView->OnActivateView(TRUE, pView, pView);
} // update frame counts and frame title (may already have been visible)
if (pDoc != NULL)
pDoc->UpdateFrameCounts();
OnUpdateFrameTitle(TRUE);
}这里又发消息WM_INITIALUPDATE给里面的View窗口,这是我要关注的地方了,刚才开始就是不知道View怎么初始化,才发狠心跟踪代码,这里终于找到了。重载一下OnInitialUpdate()函数,这应该是做事的地方了。
void CMyView::OnInitialUpdate()
{
CView::OnInitialUpdate(); // TODO: 在此添加专用代码和/或调用基类
}瞟一眼:CView::OnInitialUpdate:
void CView::OnInitialUpdate()
{
OnUpdate(NULL, 0, NULL);        // initial update
}
还有啊,再瞟一眼CView::OnUpdate
void CView::OnUpdate(CView* pSender, LPARAM /*lHint*/, CObject* /*pHint*/)
{
ASSERT(pSender != this);
UNUSED(pSender);     // unused in release builds // invalidate the entire pane, erase background too
Invalidate(TRUE);
}
这回差不多到头了。Invalidate应该会引发WM_PAINT消息,后面该是OnPaint()的事了。思路大致清楚了。虽然这个Doc/View框架很复杂,就打开文件及显示内容这一部分来说,要关注的就是Doc在OnOpenDocument()读入数据,View在OnInitialUpdate()里初始化,OnPaint()里做显示。至于Doc/View里做内容修改与交互,以及文档的保存,下一步再研究。