MFC單文檔/視圖結構窮追猛打
目錄
第一章找回WinMain函數 2
第二章InitApplication()函數 2
第三章InitInstance ()函數 3
第一節:new CSingleDocTemplate 3
第二節:AddDocTemplate(pDocTemplate); 5
第三節:ProcessShellCommand(cmdInfo) 5
第一段AfxGetApp()->OnCmdMsg(…) 6
第二段OnFileNew(); 6
第四節 :m_pMainWnd->ShowWindow(SW_SHOW); ///顯示窗口 8
第五節 :m_pMainWnd-> UpdateWindow (); ///重畫窗口 8
第四章pApp->Run函數 8
第五章總結 8
本文將針對一個單文檔來描述MFC的文檔/視圖結構,他直接打開MFC的源代碼進行分析,在分析過程中去掉了無關的部分。所以第一步就是要創建一個稱為First得工程,文檔類型是單文檔,下文將圍繞這個工程來講的。
第一章找回WinMain函數
首先在VC的安裝路徑中找到WINMAIN.CPP文件,AfxWinMain函數就是VC編譯器的入口,去掉一些不重要的東西后得到如下得程序。
int AFXAPI AfxWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,LPTSTR lpCmdLine, int nCmdShow)
{
int nReturnCode = -1;
CWinApp* pApp = AfxGetApp(); ///獲取應用程序類的指針與文檔視圖無關。
AfxWinInit(hInstance, hPrevInstance,lpCmdLine,nCmdShow); ///與文檔視圖無關。
pApp->InitApplication(); ///初始化應用程序詳見下文。
pApp->InitInstance(); ///最重要下面祥述
nReturnCode = pApp->Run();///消息循環直到應用程序被關閉。與文檔視圖無關。
AfxWinTerm(); ///與文檔視圖無關。
return nReturnCode;///整個應用結束。
}
下面分節講述。
第二章InitApplication()函數
在文件Appcore.cpp文件中InitApplication如下:
BOOL CWinApp::InitApplication()
{
if (CDocManager::pStaticDocManager != NULL) ///這段和我們關系不大,暫時不理它
{
if (m_pDocManager == NULL)
m_pDocManager = CDocManager::pStaticDocManager;
CDocManager::pStaticDocManager = NULL;
}
if (m_pDocManager != NULL)
m_pDocManager->AddDocTemplate(NULL);
else
CDocManager::bStaticInit = FALSE; /*我們的程序將會執行到這句,表示文檔類用動態方式創建,也就是說用RUNTIME_CLASS來創建的(詳見下文)。*/
return TRUE;
}
說明:CDocManager類是一個不公開的類,他主要用來管理多文檔模板對象的,對于單文檔只有一個文檔模板這個類不是很重要,但還是用它管理文檔模板的;本文只在相關之處作說明。bStaticInit是它的一個靜態成員。
第三章InitInstance ()函數
它一般被重載,在First工程中,InitInstance中和文檔視圖類有關的程序有下面的一些:
CSingleDocTemplate* pDocTemplate; ///定義指針
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CFirstDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CFirstView)); ///這條語句的作用見第一段
AddDocTemplate(pDocTemplate);
CCommandLineInfo cmdInfo; ///定義一個對象
ParseCommandLine(cmdInfo); ///解析命令行并發送參數,與文檔視圖無關
if (!ProcessShellCommand(cmdInfo)) ///這是最重要的詳見的三段
return FALSE;
m_pMainWnd->ShowWindow(SW_SHOW); ///顯示窗口
m_pMainWnd->UpdateWindow();
第一節:new CSingleDocTemplate
new CSingleDocTemplate其實就是創建一個CSingleDocTemplate對象并調用他的構造函數,要講清楚這句話,首先必須明白RUNTIME_CLASS結構,RUNTIME_CLASS結構定義如下:
#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
于是這句話展開后如下(關于符合##的具體意義參見MSDN):
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
(CRuntimeClass*)(&CFirstDoc::classCFirstDoc),
(CRuntimeClass*)(&CMainFrame::classCMainFrame),
(CRuntimeClass*)(&CFirstView::classCFirstView));
這時我們會發現CfirstDoc,CmainFrame,CfirstView各會多冒出一個靜態成員出來,它究竟在哪里呢?查看這三個類的定義我們會發現每個類定義都有一個宏DECLARE_DYNCREATE,在實現文件中有另外一個宏IMPLEMENT_DYNCREATE。下面把這些宏的展開情況列出如下:
(1)文檔類
DECLARE_DYNCREATE(CFirstDoc) 展開后為:
public:
static const CRuntimeClass classCFirstDoc; ///這就是上面所說的那個靜態成員
virtual CRuntimeClass* GetRuntimeClass() const;
static CObject* PASCAL CreateObject();
IMPLEMENT_DYNCREATE(CFirstDoc, CDocument) 展開后為:
CObject* PASCAL CFirstDoc::CreateObject()
{ return new CFirstDoc; }
const CRuntimeClass CFirstDoc::classCFirstDoc= {
"CFirstDoc", sizeof(class CFirstDoc), 0xFFFF, CFirstDoc::CreateObject, (CRu
ntimeClass*)(&CDocument::classCDocument), NULL };
CRuntimeClass* CFirstDoc::GetRuntimeClass() const
{ return (CRuntimeClass*)(&CFirstDoc::classCFirstDoc); }
(2)視圖類
DECLARE_DYNCREATE(CFirstView) 展開后為:
public:
static const CRuntimeClass classCFirstView;
virtual CRuntimeClass* GetRuntimeClass() const;
static CObject* PASCAL CreateObject();
IMPLEMENT_DYNCREATE(CFirstView, CView) 展開后為:
CObject* PASCAL CFirstView::CreateObject()
{ return new CFirstView; }
const CRuntimeClass CFirstView::classCFirstView = {
" CFirstView ", sizeof(class CFirstView), 0xFFFF, CFirstView::CreateObject, (CRu
ntimeClass*)(&CView::classCView), NULL };
CRuntimeClass* CFirstView::GetRuntimeClass() const
{ return (CRuntimeClass*)(& CFirstView::classCFirstView); }
(3)主框架類
DECLARE_DYNCREATE(CMainFrame) 展開后為:
public:
static const CRuntimeClass classCMainFrame;
virtual CRuntimeClass* GetRuntimeClass() const;
static CObject* PASCAL CreateObject();
IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd) 展開后為:
CObject* PASCAL CMainFrame::CreateObject()
{ return new CMainFrame; }
const CRuntimeClass CMainFrame::classCMainFrame = {
"CMainFrame", sizeof(class CMainFrame), 0xFFFF, CMainFrame::CreateObject, (CRu
ntimeClass*)(& CFrameWnd::classCFrameWnd), NULL };
CRuntimeClass* CMainFrame::GetRuntimeClass() const
{ return (CRuntimeClass*)(&CMainFrame::classCMainFrame); }
這些宏首先在你的定義文件中定義一個以class+類名為名字的靜態變量。然后定義一個返回這個靜態變量的函數GetRuntimeClass并在實現文件中實現之。最后定義并實現一個創建對象的函數CreateObject。
好了,我們現在就來看看CSingleDocTemplate對象的構造函數做了些什么:下面是他的實現
CSingleDocTemplate::CSingleDocTemplate(UINT nIDResource,
CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass,
CRuntimeClass* pViewClass)
: CDocTemplate(nIDResource, pDocClass, pFrameClass, pViewClass)
{
m_pOnlyDoc = NULL;
}
它主要是初始化他的父類CDocTemplate,CDocTemplate做了些什么?如下:
{
<1>把資源ID及文檔、框架、視圖的三個CRuntimeClass結構的靜態成員的地址(也就是傳進來的四個參數) 保存了起來。
<2>LoadTemplate();///為指定的文檔模板對象裝載資源
}
至此這個new語句總算講完了。
第二節:AddDocTemplate(pDocTemplate);
下面是他的實現函數
void CWinApp::AddDocTemplate(CDocTemplate* pTemplate)
{
if (m_pDocManager == NULL)
m_pDocManager = new CDocManager; /*創建一個文檔管理類,對于單文檔常常只有一個文檔模板用CDocManager類并不是很重要,我們可以忽略這句*/
m_pDocManager->AddDocTemplate(pTemplate); /*把文檔模板指針保存在CDocManager實例中由CDocManager去管理他,我們只要知道有這個事就行了*/
}
第三節:ProcessShellCommand(cmdInfo)
這個函數發送的一個命令就是FileNew其他的暫時不管他,其實所有的文檔類,框架類,視圖類都是在這個函數中創建的,在第一段中已經講到應用程序類將上三個類的CRuntimeClass一個靜態成員地址保存起來了,關于CRuntimeClass結構的具體功能參照MSDN。
ProcessShellCommand描述如下:
BOOL CWinApp::ProcessShellCommand(CCommandLineInfo& rCmdInfo)
{
BOOL bResult = TRUE;
switch (rCmdInfo.m_nShellCommand)
{
case CCommandLineInfo::FileNew:
if (!AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL))
OnFileNew();
if (m_pMainWnd == NULL)
bResult = FALSE;
break;
case CCommandLineInfo::FileOpen:
if (!OpenDocumentFile(rCmdInfo.m_strFileName))
bResult = FALSE;
break;
……………………………….///還有很多消息處理略去
}
return bResult;
}
第一段AfxGetApp()->OnCmdMsg(…)
AfxGetApp()->OnCmdMsg主要是派發命令消息,跟我們關系最大的消息是菜單命令ID_FILE_NEW,下面講述他的處理函數OnFileNew
第二段OnFileNew();
OnFileNew只是簡單的調用文檔管理對象的同名函數,他的描述如下:
void CWinApp::OnFileNew()
{
if (m_pDocManager != NULL)
m_pDocManager->OnFileNew();///只是調用CDocManager::OnFileNew
}
文檔管理類的OnFileNew去掉一些沒用的東西后實現如下:
void CDocManager::OnFileNew()
{
CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();/*獲得第一個文檔模板,對于單文檔只有一個文檔模板*/
if (m_templateList.GetCount() > 1)///對單文檔這段沒用
{
CNewTypeDlg dlg(&m_templateList);
int nID = dlg.DoModal();
if (nID == IDOK)
pTemplate = dlg.m_pSelectedTemplate;
else
return; /// none - cancel operation
}
pTemplate->OpenDocumentFile(NULL);/*其實是調用CSingleDocTemplate::OpenDocumentFile函數下面祥述這里,因為在CDocTemplate 類中這個函數是一個純虛函數*/
}
下面簡述pTemplate->OpenDocumentFile(NULL);這個函數,
這是最重要的一個函數,他執行了如下6個步驟:
<1>pDocument = CreateNewDocument();///創建一個新文檔
{
CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();/*這是才真正的創建文檔對象,這里創建的文檔對象就是上面RUNTIME_CLASS(CFirstDoc),中的對象這是MFC開發人員想出來的一種運行時創建對象的方法,應該說比較不錯的,不知道C++Builder的是怎樣構造的。*/
AddDocument(pDocument); ///把創建的對象指針記錄下來,以后還要用的。
}
<2> pFrame = CreateNewFrame(pDocument, NULL); /*根據新文檔指針創建新文檔框架,這個比較重要下一段描述*/
<3>SetDefaultTitle(pDocument); ///設置缺省標題,不重要的
<4>pDocument->OnNewDocument() ///清除老文檔內容
{
DeleteContents(); ///刪除原來的內容而不管是否存盤
SetModifiedFlag(FALSE); ///清除曾經修改過的標記
}
<5>pThread->m_pMainWnd = pFrame; ///框架窗口作為的指針作為主窗口的指針
<.6>InitialUpdateFrame(pFrame, pDocument, bMakeVisible); /*使框架中的視圖窗口接受到OnInitialUpdat的調用,并作原始更新。*/
至此第一段的OnFileNew函數總算講完。下面講<2> CreateNewFrame函數
CreateNewFrame是CDocTemplate的成員函數如下3個步驟:
<2.1>CCreateContext context; ///創建CcreateContext對象.也就是我們重載LoadFrame函數的那個CcreateContext的來源*/
context.m_pCurrentFrame = pOther; ///保存當前框架,一般為NULL
context.m_pCurrentDoc = pDoc; ///保存文檔對象指針
context.m_pNewViewClass = m_pViewClass; ///保存視圖對象指針
context.m_pNewDocTemplate = this; ///保存文檔模板的指針。
<2.2>CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject(); ///真正創建框架的對象
<2.3>pFrame->LoadFrame(m_nIDResource,WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE,
NULL, &context) ///加載資源,這是最重要的,下面講述
{
<2.3.1> GetIconWndClass(dwDefaultStyle, nIDResource) ///加載資源并注冊窗口
{
AfxFindResourceHandle
::LoadIcon ///加載光標資源
CREATESTRUCT cs; ///創建一個CREATESTRUCT結構實例
PreCreateWindow(cs); /*調用窗口預創建函數,這個函數我們可以重載的
根據cs調用 AfxReGISterWndClass注冊框架窗口*/
}
<2.3.2> Create(…) ///創建框架窗口,后面祥述,此處暫時不講。
<2.3.3> m_hMenuDefault = ::GetMenu(m_hWnd); ///獲取并保存菜單句柄
<2.3.4> LoadAccelTable(MAKEINTRESOURCE(nIDResource)); ///裝載加速鍵表
}
下面講Create函數,如下4個步驟:
<2.3.2.1>hMenu = ::LoadMenu(hInst, lpszMenuName); ///加載菜單
<2.3.2.2>CREATESTRUCT cs; ///創建一個CREATESTRUCT結構實例
cs.lpCreateParams = lpParam; ///(此時的lpParam就是<2.1>建的context對象指針)
<2.3.2.3> PreCreateWindow(cs); ///調用窗口預創建函數,這個函數我們可以重載的,又調用一次
<2.3.2.4>調用API函數::CreateWindowEx真正創建框架窗口,創建過程中會發出WM_CREATE和WM_NCCREATE等消息。
下面講接收WM_CREATE消息的消息處理函數CMainFrame::OnCreate,其他消息不重要
<2.3.2.3.1>CMainFrame::OnCreate最重要的是調用CFrameWnd::OnCreateHelper函數如下:
{
<1>CWnd::OnCreate(lpcs); ///調用缺省的
<2>調用可重載函數OnCreateClient(lpcs,pContext)函數, 在OnCreateClient函數內只是調用CreateView(pContext, AFX_IDW_PANE_FIRST)函數;
CreateView函數步驟如下:
{
<2.1> pView=pContext->m_pNewViewClass->CreateObject(); /*創建視圖對象*/
<2.2>pView->Create(…);/*調用視圖對象的Create函數真正創建視圖,其更詳細的動作可以不理他,但要知道這里會發生很多事情(跟創建框架窗口類似的);其他一些不重要的動作可以不理他;*/
}
<3>PostMessage(WM_SETMESSAGESTRING, AFX_IDS_IDLEMESSAGE);/*發一個WM_SETMESSAGESTRING消息初始化消息串*/
<4>RecalcLayout(); ///重定控制欄等無關動作
}
<2.3.2.3.2> m_wndToolBar.CreateEx; ///創建工具欄
<2.3.2.3.3> LoadToolBar; /停靠工具欄等無關動作
至此WM_CREATE消息處理講完,同時第三段的ProcessShellCommand函數也結束了
第四節 :m_pMainWnd->ShowWindow(SW_SHOW); ///顯示窗口
第五節 :m_pMainWnd-> UpdateWindow (); ///重畫窗口
至此第二節InitInstance ()函數講完,
第四章pApp->Run函數
Run函數處理消息循環直到程序推出,已經有很多書籍講到他的過程,本文不描述他。
第五章總結
從上面的描述中可以看出,文檔/視圖結構的關鍵在CRuntimeClass結構,通過CRuntimeClass結構實現動態創建對象。