即便是基于MFC的應(yīng)用程序,建立窗口類也是會遵循如下的過程:
設(shè)計(jì)窗口類->注冊窗口類->生成窗口->顯示窗口->更新窗口->消息循環(huán)->消息路由到窗口過程函數(shù)處理。下面就剖析一下在MFC中是如何完成上述過程的。
(1)每個(gè)應(yīng)用程序都有且僅有一個(gè)應(yīng)用類的全局變量theApp,全局變量先于WinMain函數(shù)進(jìn)行處理。
(2)WinMain函數(shù)體在APPMODUL.CPP文件中,定義如下:
extern "C" int WINAPI
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
其中#define _tWinMain WinMain
(3)AfxWinMain函數(shù)體在WINMAIN.CPP文件中,里面有如下兩句話:
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
其實(shí)這里得到的這兩個(gè)指針都是指向全局的對象theApp的;
接下來有函數(shù)調(diào)用pThread->InitInstance(),根據(jù)多態(tài)性,會調(diào)用CXXXApp類中的InitInstance()函數(shù)。該函數(shù)很重要,在對該函數(shù)的調(diào)用中就會完成:設(shè)計(jì)窗口類->注冊窗口類->生成窗口->顯示窗口->更新窗口。
接下來,該函數(shù)中會繼續(xù)調(diào)用pThread->Run(),這就完成了:消息循環(huán)->消息路由到窗口過程函數(shù)處理。
(4)進(jìn)入CXXXApp::InitInstance()函數(shù)體中,對于單文檔應(yīng)用程序,調(diào)用ProcessShellCommand(cmdInfo),通過調(diào)用該函數(shù)就會完成:設(shè)計(jì)窗口類->注冊窗口類->生成窗口。
再接下來就會調(diào)用m_pMainWnd->ShowWindow(SW_SHOW);m_pMainWnd->UpdateWindow();這就完成了:顯示窗口->更新窗口。
(5)在函數(shù)CWinApp::ProcessShellCommand(CCommandLineInfo& rCmdInfo)中會進(jìn)入到如下的case分支:case CCommandLineInfo::FileNew:
if (!AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL))
(6)進(jìn)入函數(shù)CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo),調(diào)用_AfxDispatchCmdMsg(this, nID, nCode,
lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo);
(7)進(jìn)入函數(shù)AFXAPI _AfxDispatchCmdMsg(CCmdTarget* pTarget, UINT nID, int nCode,
AFX_PMSG pfn, void* pExtra, UINT nSig, AFX_CMDHANDLERINFO* pHandlerInfo),調(diào)用
case AfxSig_vv:
// normal command or control notification
ASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKED
ASSERT(pExtra == NULL);
(pTarget->*mmf.pfn_COMMAND)();
(8)進(jìn)入CWinApp::OnFileNew(),調(diào)用m_pDocManager->OnFileNew();這個(gè)函數(shù)很特殊,它本身是個(gè)消息響應(yīng)函數(shù),當(dāng)我們點(diǎn)擊ID為ID_FILE_NEW的菜單時(shí),會產(chǎn)生一個(gè)命令消息,由于命令消息可以被CCmdTarget類及其派生類來捕獲,而CWinApp是從CCmdTarget派生出來的,因此可以捕獲這個(gè)消息。當(dāng)應(yīng)用程序創(chuàng)建完成并成功顯示后,當(dāng)我們點(diǎn)擊文件菜單下的新建菜單項(xiàng)時(shí),就會首先進(jìn)入這個(gè)函數(shù),然后再依次執(zhí)行下去,最后就會執(zhí)行到pDocument->OnNewDocument()中,往往我們會對這個(gè)函數(shù)不解,不知道它為什么會響應(yīng)ID_FILE_NEW的命令消息,至此真相大白了。順便說一句,為什么程序在剛啟動的時(shí)候,我們并沒有點(diǎn)擊菜單項(xiàng),為什么會自動的產(chǎn)生這個(gè)消息呢?這是因?yàn)樵贑XXXXApp::InitInstance()函數(shù)中有“CCommandLineInfo cmdInfo;”這個(gè)類的構(gòu)造函數(shù)是這樣的:CCommandLineInfo::CCommandLineInfo()
{
m_bShowSplash = TRUE;
m_bRunEmbedded = FALSE;
m_bRunAutomated = FALSE;
m_nShellCommand = FileNew;
},因此就會在第(5)步驟的時(shí)候進(jìn)入到“case CCommandLineInfo::FileNew:”這個(gè)分支中,就相當(dāng)于產(chǎn)生了這樣一個(gè)FileNew的消息。同理對于ID為ID_FILE_OPEN(在CWinApp::OnFileOpen()中響應(yīng))、ID_FILE_SAVE(在CDocument::OnFileSave()中響應(yīng))等等在MFC向?qū)槲覀兩傻膯挝臋n類中找不到消息響應(yīng)的入口時(shí),其實(shí)都是在基類CWinApp或者CDocument類中進(jìn)行了響應(yīng)。對于CXXXXDoc::Serialize(CArchive& ar)函數(shù)也是通過ID_FILE_SAVE和ID_FILE_OPEN產(chǎn)生命令消息后就行響應(yīng)從而才調(diào)用該函數(shù)的。
(9)進(jìn)入CDocManager::OnFileNew(),CDocManager類有一個(gè)成員變量是CPtrList m_templateList;該變量保存了一個(gè)文檔模版鏈表指針,在CDocManager::OnFileNew()函數(shù)體中會調(diào)用CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();得到鏈表中的頭,也就是第一個(gè)文檔模版,后面就會用得到的這個(gè)指針去調(diào)用pTemplate->OpenDocumentFile(NULL);緊接著就會有一個(gè)判斷,用來確定該鏈表中是否只有一項(xiàng),如果鏈表中保存了多個(gè)文檔模版,則會彈出一個(gè)對話框,來讓我們選擇到底是使用哪一套文檔模版來構(gòu)建應(yīng)用程序,相信大家也都見到過這種情況吧。對了,還有一點(diǎn)要說明的是:pTemplate是一個(gè)CDocTemplate的指針,但接下來程序?yàn)槭裁磿M(jìn)入到CSingleDocTemplate::OpenDocumentFile的函數(shù)體內(nèi)呢,這是因?yàn)镃DocTemplate類中的OpenDocumentFile函數(shù)被定義為純虛函數(shù),而CSingleDocTemplate類又是從CDocTemplate類派生出來的,并且實(shí)現(xiàn)了該函數(shù),因此就會進(jìn)入到子類的函數(shù)體中了。
(10)進(jìn)入CDocument* CSingleDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,
BOOL bMakeVisible),先調(diào)用CreateNewDocument()創(chuàng)建文檔類,再調(diào)用pFrame = CreateNewFrame(pDocument, NULL);創(chuàng)建框架類和視圖類,從這里也可以看出MFC體系結(jié)構(gòu)中文檔、框架、視圖“三位一體”的模式,在這一個(gè)函數(shù)中同時(shí)創(chuàng)建三個(gè)類;再會調(diào)用pDocument->OnNewDocument();因此就會進(jìn)入到子類的文檔類中的pDocument->OnNewDocument()中了。
(11)進(jìn)入CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther),調(diào)用if (!pFrame->LoadFrame(m_nIDResource,
WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, // default frame styles
NULL, &context))
(12)進(jìn)入BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
CWnd* pParentWnd, CCreateContext* pContext),調(diào)用VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
(13)進(jìn)入BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister),該函數(shù)內(nèi)部就完成了:設(shè)計(jì)窗口類->注冊窗口類。MFC通過給我們提供好一些已經(jīng)訂制好的窗口類,我們不需要自己再設(shè)計(jì)窗口類,只需要到那些訂制好的窗口類“倉庫”中尋找一種適合我們需要的窗口類就可以了,然后通過AfxRegisterClass函數(shù)注冊窗口類。還需要說明的是,再后續(xù)的跟蹤過程中,我們會發(fā)現(xiàn)還會進(jìn)入到AfxEndDeferRegisterClass函數(shù)中進(jìn)行設(shè)計(jì)和注冊窗口類,這主要是因?yàn)閱挝臋n應(yīng)用程序比較特殊,它提前通過這樣的一種途徑進(jìn)行了窗口類的設(shè)計(jì)和注冊步驟,其實(shí)是應(yīng)該在BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)函數(shù)的調(diào)用中完成窗口類的設(shè)計(jì)和注冊的,這一點(diǎn)我們要清楚,也就是說設(shè)計(jì)和注冊窗口類的正宗發(fā)源地應(yīng)該是PreCreateWindow(CREATESTRUCT& cs)。此外,我們還會注意到在該函數(shù)體的前部分有一語句為“wndcls.lpfnWndProc = DefWindowProc;”因此所有窗口類的窗口過程函數(shù)都是DefWindowProc,這一點(diǎn)在后面的跟蹤中可以看到,每次生成窗口之后都會調(diào)用幾次DefWindowProc函數(shù)。也就是說MFC都是讓我們采用默認(rèn)的窗口過程函數(shù),這并不是說我們因此就不能使用自己的窗口過程函數(shù)實(shí)現(xiàn)個(gè)性化的消息處理了,MFC采用了一種基于消息映射的機(jī)制完成了消息個(gè)性化處理。
(14)回到BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
CWnd* pParentWnd, CCreateContext* pContext)中,調(diào)用LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
(15)進(jìn)入LPCTSTR CFrameWnd::GetIconWndClass(DWORD dwDefaultStyle, UINT nIDResource),調(diào)用PreCreateWindow(cs);
(16)進(jìn)入BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs),調(diào)用CFrameWnd::PreCreateWindow(cs)
(17)進(jìn)入BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs),調(diào)用VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));又一次設(shè)計(jì)和注冊窗口類
(18)回到BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
CWnd* pParentWnd, CCreateContext* pContext)中,調(diào)用if (!Create(lpszClass, lpszTitle, dwDefaultStyle, rectDefault, pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext))
(19)進(jìn)入BOOL CFrameWnd::Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
LPCTSTR lpszMenuName,
DWORD dwExStyle,
CCreateContext* pContext),調(diào)用if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext))
(20)BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam),調(diào)用if (!PreCreateWindow(cs))
,接下來調(diào)用HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);好了,終于讓我們找到生成窗口的地方了——函數(shù)::CreateWindowEx!
(21)進(jìn)入int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct),調(diào)用if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
(22)進(jìn)入int CFrameWnd::OnCreate(LPCREATESTRUCT lpcs),調(diào)用return OnCreateHelper(lpcs, pContext);
(23)進(jìn)入int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs, CCreateContext* pContext),調(diào)用if (CWnd::OnCreate(lpcs) == -1)
(24)進(jìn)入_AFXWIN_INLINE int CWnd::OnCreate(LPCREATESTRUCT),調(diào)用return (int)Default();
(25)進(jìn)入LRESULT CWnd::Default(),調(diào)用return DefWindowProc(pThreadState->m_lastSentMsg.message, pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam);
(26)進(jìn)入LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam),調(diào)用return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);
(27)回到int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs, CCreateContext* pContext),調(diào)用if (!OnCreateClient(lpcs, pContext))
(28)進(jìn)入BOOL CFrameWnd::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext),調(diào)用if (CreateView(pContext, AFX_IDW_PANE_FIRST) == NULL)
(29)進(jìn)入CWnd* CFrameWnd::CreateView(CCreateContext* pContext, UINT nID),調(diào)用if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0,0,0,0), this, nID, pContext))
(30)進(jìn)入BOOL CWnd::Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd, UINT nID,
CCreateContext* pContext),調(diào)用return CreateEx(0, lpszClassName, lpszWindowName,
dwStyle | WS_CHILD,
rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
pParentWnd->GetSafeHwnd(), (HMENU)nID, (LPVOID)pContext);
(31)進(jìn)入BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam),重復(fù)生成框架類CMainFrame的過程來生成CXXXView,因?yàn)樗彩且粋€(gè)窗口類,因此也需要進(jìn)行那一系列過程才能最終顯示更新出來。
調(diào)用的順序是這個(gè)樣子的:PreCreateWindow(cs)->BOOL CXXXView::PreCreateWindow(CREATESTRUCT& cs)->CView::PreCreateWindow(cs)->VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));->::CreateWindowEx(...)->CWnd::DefWindowProc->::CallWindowProc(...)->...->CXXXView::OnCreate->CView::OnCreate->CWnd::OnCreate->...
寫到這里,基本上就清楚了,中間的省略號表示的部分大多數(shù)都是在與窗口過程函數(shù)有關(guān)的,因?yàn)樵谏纱翱诘臅r(shí)候需要響應(yīng)一些消息,因此需要調(diào)用一些窗口過程函數(shù),每次在調(diào)用::CreateWindowEx(...)函數(shù)后都會調(diào)用一些窗口過程函數(shù),然后再去調(diào)用該窗口類對應(yīng)的OnCreate函數(shù),其實(shí)在調(diào)用OnCreate函數(shù)之前調(diào)用CreateWindowEx只是生成了一個(gè)窗口,至于這個(gè)窗口里面要放置些什么東西,以及該如何裝飾該窗口,則就需要由OnCreate來完成了,往往我們都會在OnCreate函數(shù)的后面(這樣做是為了不影響窗口本身應(yīng)該布置的格局)添加一些代碼,創(chuàng)建我們自己的東西,比如我們通常會在CMainFrame類的OnCreate函數(shù)后面放置一些Create代碼,來創(chuàng)建我們自己的可停靠的工具欄或者按鈕之類的東西,當(dāng)然我們也可以在CXXXView類的OnCreate函數(shù)的后面添加一些代碼,來創(chuàng)建我們需要的東西,比如按鈕之類的東西。在完成了從設(shè)計(jì)、注冊到生成窗口的過程之后,往往還需要顯示更新,有些時(shí)候,我們不必要每次都顯示的調(diào)用CWnd的ShowWindow和UpdateWindow兩個(gè)函數(shù),我們可以在創(chuàng)建的時(shí)候,給窗口風(fēng)格中添加WS_VISIBLE即可,因此有些時(shí)候會跟蹤不到ShowWindow和UpdateWindow兩個(gè)函數(shù)這兩個(gè)函數(shù),因?yàn)榇翱谠趧?chuàng)建的時(shí)候就可見了。
總的來說,先初始化應(yīng)用類,然后注冊生成框架類,然后再注冊生成視圖類,然后注冊生成視圖類OnCreate函數(shù)后面用戶添加的、用Create來準(zhǔn)備創(chuàng)建的窗口,然后再注冊生成框架類的OnCreate函數(shù)后面需要生成的m_wndToolBar、m_wndStatusBar以及我們自己添加的要?jiǎng)?chuàng)建的窗口類,最后在回到應(yīng)用類的初始化的函數(shù)體中,調(diào)用框架類的顯示和更新函數(shù),然后再進(jìn)入由框架類定義的窗口的消息循環(huán)中。
消息循環(huán)的過程是這個(gè)樣子的:
(1)調(diào)用int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)函數(shù)中的pThread->Run()
(2)進(jìn)入int CWinApp::Run(),調(diào)用return CWinThread::Run();
(3)進(jìn)入int CWinThread::Run(),調(diào)用if (!PumpMessage())
(4)進(jìn)入BOOL CWinThread::PumpMessage(),調(diào)用if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
(5)回到BOOL CWinThread::PumpMessage(),調(diào)用::TranslateMessage(&m_msgCur);::DispatchMessage(&m_msgCur);
(6)回到int CWinThread::Run(),調(diào)用while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
(7)再重復(fù)(4)-(6)的步驟
下面給出int CWinThread::Run()中消息循環(huán)的部分代碼:
do
{
// pump message, but quit on WM_QUIT
if (!PumpMessage())
return ExitInstance();
// reset "no idle" state after pumping "normal" message
if (IsIdleMessage(&m_msgCur))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
這段代碼其實(shí)本質(zhì)上與我們基于Win32 SDK手寫的代碼:
//消息循環(huán)
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
//簡單的說,函數(shù)TranslateMessage就是把WM_KEYDOWN和WM_KEYUP翻譯成WM_CHAR消息,沒有該函數(shù)就不能產(chǎn)生WM_CHAR消息。
TranslateMessage(&msg);
::DispatchMessage(&msg);
}
是一致的。