剖析MFC的文檔視圖結構
////////////////////////////////////////////////////////////////////////////////////
/********* 文章系列:MFC技術內幕系列***********/
/************MFC技術內幕系列之(二)***********/
/**** 文章題目:MFC文檔視圖結構內幕 *****/
/* Copyright(c)2002 bigwhite */
/* All rights Reserved */
/*********關鍵字:MFC,文檔視圖結構************/
/* 時間:2002.7.23 */
/* 註釋:本文所涉及的程序源代碼均在Microsoft */
/ Visual Studio.Net Enterprise Architect Edition /
/* 開發工具包提供的源代碼中 */
//////////////////////////////////////////////////////////////////////////////////////////////////////////
引 言:侯捷老師的"深入淺出MFC"一書的第8章中有“"Document/View"是MFC的基石。”一說,可以看出文檔視圖結構在MFC Framework中的地位是多麼的重要。本文將以一個標準MFC應用程序嚮導作成的MDI程序爲例,來和大家一起詳細挖掘文檔視圖結構的內幕。
正文:
/////////////////////////////////////////////
/* 1.回顧"InitInstance函數" */
/////////////////////////////////////////////
在 我的《MFC應用程序“生死因果”內幕》一文中,當談到CMyWinApp::InitInstance()時,我只是粗略的講了介紹了一下各個函數的功 能,而忽略了很多細節,這裏讓我們在回顧一下CMyWinApp::InitInstance()函數,並將裏面與文檔視圖結構有關的代碼深入探討一下:
BOOL CMyApp::InitInstance()//只列出了與文檔視圖結構相關的源代碼
{
//...
//文檔模板將用作文檔、框架窗口和視圖之間的連接
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(IDR_MyTYPE,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CChildFrame), // 自定義 MDI 子框架
RUNTIME_CLASS(CMyView));
AddDocTemplate(pDocTemplate);
// 創建主 MDI 框架窗口
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
// 僅當具有後綴時才調用 DragAcceptFiles
// 在 MDI 應用程序中,這應在設置 m_pMainWnd 之後立即發生
// 分析標準外殼命令、DDE、打開文件操作的命令行
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// 調度在命令行中指定的命令。如果
// 用 /RegServer、/Register、/Unregserver 或 /Unregister 啓動應用程序,則返回 FALSE。
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// 主窗口已初始化,因此顯示它並對其進行更新
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
return TRUE;
}
////////////////////////////////////////////
/* 2.初始化文檔模板 */
////////////////////////////////////////////
分析以下代碼:
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(IDR_MyTYPE,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CChildFrame), // 自定義 MDI 子框架
RUNTIME_CLASS(CMyView));
應用程序首先實例化一個CMultiDocTemplate對象,此過程也是對CMultiDocTemplate類數據成員的初始化過程。按調用次序我列出了以下源代碼:
註釋1: CDocTemplate構造函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcdoctempl.cpp
CDocTemplate::CDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass,
CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass)//部分源代碼
{
ASSERT_VALID_IDR(nIDResource);
ASSERT(pDocClass == NULL ||
pDocClass->IsDerivedFrom(RUNTIME_CLASS(CDocument)));
ASSERT(pFrameClass == NULL ||
pFrameClass->IsDerivedFrom(RUNTIME_CLASS(CFrameWnd)));
ASSERT(pViewClass == NULL ||
pViewClass->IsDerivedFrom(RUNTIME_CLASS(CView)));
m_nIDResource = nIDResource;
...//
m_pDocClass = pDocClass;
m_pFrameClass = pFrameClass;
m_pViewClass = pViewClass;
m_pOleFrameClass = NULL;
m_pOleViewClass = NULL;
...//
}
以上爲CMultiDocTemplate類的基類CDocTemplate構造函數的部分源代碼,該函數初始化了四個重要的成員
m_nIDResource,m_pDocClass,m_pFrameClass和m_pViewClass。
註釋2: CMultiDocTemplate構造函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcdocmulti.cpp
CMultiDocTemplate::CMultiDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass,
CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass)
: CDocTemplate(nIDResource, pDocClass, pFrameClass, pViewClass)
{
ASSERT(m_docList.IsEmpty());
m_hMenuShared = NULL;
m_hAccelTable = NULL;
m_nUntitledCount = 0; // start at 1
// load resources in constructor if not statically allocated
if (!CDocManager::bStaticInit)
LoadTemplate();
}
看完以上代碼後,來回過頭看一看InitInstance函數將什麼參數值傳給了CMultiDocTemplate的構造函數。
原來是一些RUNTIME_CLASS宏。以下是RUNTIME_CLASS宏的定義:
註釋3: 以下的宏定義在:..Visual Studio.NETvc7atlmfcincludeafx.h中
#define RUNTIME_CLASS(class_name) _RUNTIME_CLASS(class_name)
#define _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
這個地方是個難點,這將涉及到MFC的另一個重要技術---"執行期類型識別"。此項技術我將在
MFC 技術內幕系列之(三)---《MFC執行期類型識別與動態創建技術內幕》中詳細講解。回到眼前來,源代碼中這樣作是爲了將CMyDoc, CChildFrame,CMyView各類中的static CRuntimeClass class##class_name地址賦予CMultiDocTemplate類的各CRuntimeClass*指針成員m_pDocClass, m_pFrameClass和m_pViewClass,這位以後的動態創建Document/Frame/View"三口組"打下了基礎。
///////////////////////////////////////////
/* 3.文檔模板列隊 */
///////////////////////////////////////////
文 檔模板初始化結束後,InitInstance函數調用了CWinApp::AddDocTemplate(pDocTemplate)函數,其主要目的 是將以初始化後的那個文檔模板加入到文檔模板鏈表中,並由CDocManager類對象進行管理。以下操作就是爲了完成此工作。
註釋1: 以下函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcappui2.cpp
void CWinApp::AddDocTemplate(CDocTemplate* pTemplate)//將CMultiDocTemplate* pDocTemplate
{ //傳給pTemplate
if (m_pDocManager == NULL)
m_pDocManager = new CDocManager;
m_pDocManager->AddDocTemplate(pTemplate);
}
註釋2: 以下函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcdocmgr.cpp
CDocManager::CDocManager()
{
}//目前是一個空函數;
void CDocManager::AddDocTemplate(CDocTemplate* pTemplate)//部分源代碼
{
if (pTemplate == NULL)
{
...//
}
else
{
ASSERT_VALID(pTemplate);
ASSERT(m_templateList.Find(pTemplate, NULL) == NULL);// must not be in list
pTemplate->LoadTemplate();
m_templateList.AddTail(pTemplate);//CPtrList m_templateList is a member //of CDocManager
}
}
///////////////////////////////////////////////
/* 4.創建程序主框架窗口 */
///////////////////////////////////////////////
應用程序實例化了一個CMainFrame類對象,並調用LoadFrame函數加載窗口資源創建主框架窗口。以下是創建主框架窗口的流程。
創 建窗口的主要代碼是:pMainFrame->LoadFrame(IDR_MAINFRAME);LoadFrame函數是MFC包裝了窗口創建 過程的函數,在後面動態創建Child窗口時,它還將披掛上陣(但稍有不同)。下面是它的源代碼,讓我們仔細分析一下:
註釋1: 以下函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcwinmdi.cpp
BOOL CMDIFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
CWnd* pParentWnd, CCreateContext* pContext)
{
if (!CFrameWnd::LoadFrame(nIDResource, dwDefaultStyle,
pParentWnd, pContext))
return FALSE;
// save menu to use when no active MDI child window is present
ASSERT(m_hWnd != NULL);
m_hMenuDefault = ::GetMenu(m_hWnd);
if (m_hMenuDefault == NULL)
TRACE(traceAppMsg, 0, "Warning: CMDIFrameWnd without a default menu.
");
return TRUE;
}
CMDIFrameWnd::LoadFrame調用了其基類CFrameWnd的LoadFrame,並將參數原封不動的傳給它。
註釋2: 以下函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcwinfrm.cpp
BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
CWnd* pParentWnd, CCreateContext* pContext) //部分源代碼
{
...//
CString strFullString;
if (strFullString.LoadString(nIDResource))
AfxExtractSubString(m_strTitle, strFullString, 0); // first sub-string
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
// attempt to create the window
LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
CString strTitle = m_strTitle;
if (!Create(lpszClass, strTitle, dwDefaultStyle, rectDefault,
pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext))
{
return FALSE; // will self destruct on failure normally
}
...//
if (pContext == NULL) // send initial update
SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);
return TRUE;
}
//////////////////////////////////////////////////////
/* 4.1註冊應用程序主框架窗口類 */
//////////////////////////////////////////////////////
在 傳統的Win32API編程中,創建窗口一般步驟是定義窗口類,註冊窗口類,並調用::CreateWindow函數來創建。前面說過LoadFrame 函數封裝了MFC創建窗口的過程,那麼也就是說LoadFrame函數將負責定義窗口類,註冊窗口類等瑣碎工作。下面我們就通過挖掘源代碼來看看 LoadFrame函數是如何完成這些工作的。
LoadFrame首先調用AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG),
註釋1: 以下宏定義在:..Visual Studio.NETvc7atlmfcsrcmfcafximpl.h
#define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)
註釋2: 以下函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcwincore.cpp
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)//部分源代碼
{
// mask off all classes that are already registered
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
fToRegister &= ~pModuleState->m_fRegisteredClasses;
if (fToRegister == 0)
return TRUE;
LONG fRegisteredClasses = 0;
// common initialization
WNDCLASS wndcls;
memset(&wndcls, 0, sizeof(WNDCLASS)); // start with NULL defaults
wndcls.lpfnWndProc = DefWindowProc;
wndcls.hInstance = AfxGetInstanceHandle();
wndcls.hCursor = afxData.hcurArrow;
INITCOMMONCONTROLSEX init;
init.dwSize = sizeof(init);
// work to register classes as specified by fToRegister, populate fRegisteredClasses as we go
if (fToRegister & AFX_WND_REG)
{
// Child windows - no brush, no icon, safest default class styles
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.lpszClassName = _afxWnd;
if (AfxRegisterClass(&wndcls))
fRegisteredClasses |= AFX_WND_REG;
}
...//
if (fToRegister & AFX_WNDMDIFRAME_REG)
{
// MDI Frame window (also used for splitter window)
wndcls.style = CS_DBLCLKS;
wndcls.hbrBackground = NULL;
if (_AfxRegisterWithIcon(&wndcls, _afxWndMDIFrame, AFX_IDI_STD_MDIFRAME))
fRegisteredClasses |= AFX_WNDMDIFRAME_REG;
}
if (fToRegister & AFX_WNDFRAMEORVIEW_REG)
{
// SDI Frame or MDI Child windows or views - normal colors
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
if (_AfxRegisterWithIcon(&wndcls, _afxWndFrameOrView, AFX_IDI_STD_FRAME))
fRegisteredClasses |= AFX_WNDFRAMEORVIEW_REG;
}
...//
// must have registered at least as mamy classes as requested
return (fToRegister & fRegisteredClasses) == fToRegister;
}
MFC預定義了若干個“窗口類模板”,比如"AFX_WNDMDIFRAME_REG","AFX_WNDFRAMEORVIEW_REG"等,MFC在
LoadFrame 函數中調用AfxEndDeferRegisterClass函數爲你的應用程序預註冊了適當的窗口類。本例中預註冊的窗口類爲 AFX_WNDFRAMEORVIEW_REG。(注意是預註冊,如果你在後面更改了CREATESTRUCT結構的域成員,MFC還會根據你的更改重新 爲你的應用程序正式註冊新的窗口類,稍候會有詳細敘述)
預註冊完窗口類,MFC將判斷你是否想更改窗口類的各參數。若你更改了,則MFC會重新註冊新類;否則源預註冊的窗口類就將成爲正式的窗口類。下面我們來看看MFC的判斷過程:此判斷過程由GetIconWndClass開始
LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
註釋3: 以下函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcwinfrm.cpp
LPCTSTR CFrameWnd::GetIconWndClass(DWORD dwDefaultStyle, UINT nIDResource)//部分源代碼
{
...//
HICON hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(nIDResource));
if (hIcon != NULL)
{
CREATESTRUCT cs;
memset(&cs, 0, sizeof(CREATESTRUCT));
cs.style = dwDefaultStyle;
PreCreateWindow(cs);
// will fill lpszClassName with default WNDCLASS name
// ignore instance handle from PreCreateWindow.
WNDCLASS wndcls;
if (cs.lpszClass != NULL &&
GetClassInfo(AfxGetInstanceHandle(), cs.lpszClass, &wndcls) &&
wndcls.hIcon != hIcon)
{
// register a very similar WNDCLASS
return AfxRegisterWndClass(wndcls.style,
wndcls.hCursor, wndcls.hbrBackground, hIcon);
}
}
return NULL; // just use the default
}
GetIconWndClass 函數將調用CMainFrame::PreCreateWindow(CREATESTRUCT& cs)來看看應用程序是否修改了CREATESTRUCT結構的域成員。CMainFrame::PreCreateWindow調用 CMDIFrameWnd::PreCreateWindow(CREATESTRUCT& cs),後者的代碼如下:
BOOL CMDIFrameWnd::PreCreateWindow(CREATESTRUCT& cs)//in winmdi.cpp
{
if (cs.lpszClass == NULL)
{
VERIFY(AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG));
cs.lpszClass = _afxWndMDIFrame;
}
return TRUE;
}
MFC將爲應用程序註冊AFX_WNDMDIFRAME_REG預定義窗口類,並設置cs.lpszClass = _afxWndMDIFrame。
在應用程序的代碼中我更改了cs結構:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CMDIFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: 在此處通過修改 CREATESTRUCT cs 來修改窗口類或
// 樣式
cs.dwExStyle=~WS_EX_CLIENTEDGE;
return TRUE;
}
CMainFrame:: PreCreateWindow返回後,GetIconWndClass函數調用GetClassInfo函數重新收集cs信息(此時的信息已是更改後的 了),並調用AfxRegisterWndClass函數重新註冊該窗口類(此窗口類爲該應用程序的正式窗口類)。到此爲止窗口類註冊完畢,以後程序還會 調用一次CMainFrame::PreCreateWindow,不過那此只是"過門不如"而已。
/////////////////////////////////////////////////
/* 4.2主框架窗口創建開始 */
/////////////////////////////////////////////////
開始進入創建框架窗口的實質階段。看LoadFrame函數做了什麼?原來它調用了Create函數。
註釋1: 以下函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcwinfrm.cpp
BOOL CFrameWnd::Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
LPCTSTR lpszMenuName,
DWORD dwExStyle,
CCreateContext* pContext)
{
HMENU hMenu = NULL;
if (lpszMenuName != NULL)
{
// load in a menu that will get destroyed when window gets destroyed
HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, RT_MENU);
if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL)
{
TRACE(traceAppMsg, 0, "Warning: failed to load menu for CFrameWnd.
");
PostNcDestroy(); // perhaps delete the C++ object
return FALSE;
}
}
m_strTitle = lpszWindowName; // save title for later
if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext))
{
TRACE(traceAppMsg, 0, "Warning: failed to create CFrameWnd.
");
if (hMenu != NULL)
DestroyMenu(hMenu);
return FALSE;
}
return TRUE;
}
簡單地說CFrameWnd::Create函數調用了基類的CWnd::CreateEx;
註釋2: 以下函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcwincore.cpp
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)//部分源代碼
{
// allow modification of several common create parameters
CREATESTRUCT cs;
cs.dwExStyle = dwExStyle;
cs.lpszClass = lpszClassName;
cs.lpszName = lpszWindowName;
cs.style = dwStyle;
cs.x = x;
cs.y = y;
cs.cx = nWidth;
cs.cy = nHeight;
cs.hwndParent = hWndParent;
cs.hMenu = nIDorHMenu;
cs.hInstance = AfxGetInstanceHandle();
cs.lpCreateParams = lpParam;
if (!PreCreateWindow(cs))
{
PostNcDestroy();
return FALSE;
}
AfxHookWindowCreate(this);
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);
...//
return TRUE;
}
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CMDIFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: 在此處通過修改 CREATESTRUCT cs 來修改窗口類或
// 樣式
cs.dwExStyle=~WS_EX_CLIENTEDGE;
return TRUE;
}
BOOL CMDIFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL)
{
VERIFY(AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG));
cs.lpszClass = _afxWndMDIFrame;
}
return TRUE;
}
CWnd::CreateEx調用了CMainFrame::PreCreateWindow,但此次AfxDeferRegisterClass將不會被調用。也就是我上面所說的“過門不入”。
CWnd::CreateEx函數還調用了AfxHookWindowCreate(this);後者是幹什麼的呢?其實它與消息映射和命令傳遞有關,我將在MFC技術內幕系列之(四)--《MFC消息映射與消息傳遞內幕》一文中詳解。
CWnd::CreateEx調用Win32API ::CreateWindowEx函數(傳統的Win32API程序員一定不陌生這個函數),
就這樣主框架窗口創建結束。
//////////////////////////////////////////////
/* 5.標準外殼命令解析 */
///////////////////////////////////////////////
MFC嚮導製作的標準MDI應用程序啓動時,應用程序會自動啓動一個子窗口框架(實際上是一套文檔模板),這是爲何呢?下面我將詳細講解一下這個創建過程.
其實這一過程也是在CMyWinApp::InitInstance()函數中完成的,看看下面代碼:
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
if (!ProcessShellCommand(cmdInfo))
return FALSE;
函數首先實例化一個CCommandLineInfo類對象cmdInfo,讓我們看看CCommandLineInfo是個什麼東東?
//in afxwin.h
class CCommandLineInfo : public CObject//部分源代碼
{
public:
// Sets default values
CCommandLineInfo();
...//
BOOL m_bShowSplash;
BOOL m_bRunEmbedded;
BOOL m_bRunAutomated;
enum { FileNew, FileOpen, FilePrint, FilePrintTo, FileDDE, AppRegister,
AppUnregister, FileNothing = -1 } m_nShellCommand;
// not valid for FileNew
CString m_strFileName;
// valid only for FilePrintTo
CString m_strPrinterName;
CString m_strDriverName;
CString m_strPortName;
~CCommandLineInfo();
// Implementation
...//
};
再讓我們來看看它的構造函數的實現:
//in appcore.cpp
CCommandLineInfo::CCommandLineInfo()
{
m_bShowSplash = TRUE;
m_bRunEmbedded = FALSE;
m_bRunAutomated = FALSE;
m_nShellCommand = FileNew;
}
m_nShellCommand = FileNew;這一句對我們最重要;至於CWinApp::ParseCommandLine我想用MFC文檔中的一句話來解釋:
Call this member function to parse the command line and send the parameters, one at a time, to CCommandLineInfo::ParseParam.
下面我們來看看外殼命令解析的主角:CWinApp::ProcessShellCommand
//in appui2.cpp
//DDE and ShellExecute support
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;
// If we've been asked to open a file, call OpenDocumentFile()
case CCommandLineInfo::FileOpen:
if (!OpenDocumentFile(rCmdInfo.m_strFileName))
bResult = FALSE;
break;
case CCommandLineInfo::FilePrintTo:
case CCommandLineInfo::FilePrint:
...//
case CCommandLineInfo::FileDDE:
...//
case CCommandLineInfo::AppRegister:
...//
case CCommandLineInfo::AppUnregister:
...//
}
return bResult;
}
挖 掘源代碼的確是瞭解MFC運行內幕的最好手段,大家一看源代碼便知道如之何了。CCommandLineInfo構造函數中 m_nShellCommand = FileNew;所以在ProcessShellCommand中對應的代碼自然就一目瞭然了:CWinApp::OnFileNew()被調用了。
//////////////////////////////////////////////////
/* 6.一套文檔/視圖即將誕生 */
//////////////////////////////////////////////////
上文說CWinApp::OnFileNew()被調用了,那麼就讓我來看看其代碼吧!
//in appdlg.cpp
void CWinApp::OnFileNew()
{
if (m_pDocManager != NULL)
m_pDocManager->OnFileNew();
}
//in docmgr.cpp
void CDocManager::OnFileNew()//部分源代碼
{
...//
CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();
if (m_templateList.GetCount() > 1)
{
// more than one document template to choose from
// bring up dialog prompting user
CNewTypeDlg dlg(&m_templateList);
INT_PTR nID = dlg.DoModal();
if (nID == IDOK)
pTemplate = dlg.m_pSelectedTemplate;
else
return; // none - cancel operation
}
ASSERT(pTemplate != NULL);
ASSERT_KINDOF(CDocTemplate, pTemplate);
pTemplate->OpenDocumentFile(NULL);
// if returns NULL, the user has already been alerted
}
//in docmulti.cpp
CDocument* CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,
BOOL bMakeVisible)//部分源代碼
{
CDocument* pDocument = CreateNewDocument();
...//
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 (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.
");
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.
");
pFrame->DestroyWindow();
return NULL;
}
pDocument->SetPathName(lpszPathName);
}
InitialUpdateFrame(pFrame, pDocument, bMakeVisible);
return pDocument;
}
//////////////////////////////////////////////
/* 6.1.子文檔動態生成 */
//////////////////////////////////////////////
CMultiDocTemplate::OpenDocumentFile調用了CreateNewDocument(),這就是子文檔動態生成的主函數。
//in doctempl.cpp
CDocument* CDocTemplate::CreateNewDocument()//部分源代碼
{
// default implementation constructs one from CRuntimeClass
...//
CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();
...//
AddDocument(pDocument);//將動態生成的文檔對象的指針加入到應用程序的文檔列表中
return pDocument;
}
CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();這一句就是動態產生的核心,它藉助於 CRuntimeClass動態生成一個CDocument對象。其動態生成的奧祕我將在MFC技術內幕系列之(三)---
《MFC執行期類型識別與動態創建技術內幕》一文中詳解。
//////////////////////////////////////////////////
/* 6.2.子窗口框架動態生成 */
/////////////////////////////////////////////////
CMultiDocTemplate::OpenDocumentFile調用了CreateNewFrame,這就是子窗口框架動態生成的主函數。
// Default frame creation
CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther)//部分源代碼
{
if (pDoc != NULL)
ASSERT_VALID(pDoc);
// create a frame wired to the specified document
ASSERT(m_nIDResource != 0); // must have a resource ID to load from
CCreateContext context;
context.m_pCurrentFrame = pOther;
context.m_pCurrentDoc = pDoc;
context.m_pNewViewClass = m_pViewClass;
context.m_pNewDocTemplate = this;
...//
CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();
if (pFrame == NULL)
{
TRACE(traceAppMsg, 0, "Warning: Dynamic create of frame %hs failed.
",
m_pFrameClass->m_lpszClassName);
return NULL;
}
ASSERT_KINDOF(CFrameWnd, pFrame);
...//
// create new from resource
if (!pFrame->LoadFrame(m_nIDResource,
WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, // default frame styles
NULL, &context))
{
TRACE(traceAppMsg, 0, "Warning: CDocTemplate couldn't create a frame.
");
// frame will be deleted in PostNcDestroy cleanup
return NULL;
}
// it worked !
return pFrame;
}
CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();這一句就是動態產生的核心,它藉助於 CRuntimeClass動態生成一個CDocument對象。其動態生成的奧祕我將在MFC技術內幕系列之(三)---
《MFC執行期類型識別與動態創建技術內幕》一文中詳解。之後函數調用LoadFrame來創建子窗口。其過程與創建主框架窗口的過程大致相同,但也有一些不同的地方,下面我就將說說這點不同。
//////////////////////////////////////////////
/* 6.3.子視圖動態生成 */
//////////////////////////////////////////////
瞪大眼睛仔細察看OpenDocumentFile的源代碼,疑惑了,"怎麼沒有類似CView* pView =CreateNewView();
的代碼?","那麼子視圖是如何生成的呢"下面我就爲你詳細解釋一下吧!其實子視圖動態生成函數被放到另一個地方了。讓我們詳細來看看吧。
其實,關鍵還是在LoadFrame,但與創建主窗口框架的那個LoadFrame不同的是傳進了一個不同的參數
&context,你回過頭看看主窗口框架的那個LoadFrame,調用它時使用了默認參數,而那個默認參數值爲NULL,
下面看看CCreateContext 結構。
//in afxext.h
struct CCreateContext // Creation information structure
// All fields are optional and may be NULL
{
// for creating new views
CRuntimeClass* m_pNewViewClass; // runtime class of view to create or NULL
CDocument* m_pCurrentDoc;
// for creating MDI children (CMDIChildWnd::LoadFrame)
CDocTemplate* m_pNewDocTemplate;
// for sharing view/frame state from the original view/frame
CView* m_pLastView;
CFrameWnd* m_pCurrentFrame;
// Implementation
CCreateContext();
};
而在CDocTemplate::CreateNewFrame中初始化了該結構如下:
CCreateContext context;
context.m_pCurrentFrame = pOther;
context.m_pCurrentDoc = pDoc;
context.m_pNewViewClass = m_pViewClass;
context.m_pNewDocTemplate = this;
context.m_pNewViewClass = m_pViewClass;//關鍵的成員
下面看看這個創建的具體過程:
LoadFrame(...,&context)-->CFrameWnd::Create(...,&context)--> CWnd::CreateEx(...,&context)
-->::CreateWindowEx
::CreateWindowEx API函數將產生WM_CREATE消息,並將&context傳遞之,CMainFrame::OnCreate將響應消息,並引起一系列的函數調用,看下面:
//in mainfrm.cpp
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
...//
return 0;
}
// in winfrm.cpp
int CFrameWnd::OnCreate(LPCREATESTRUCT lpcs)
{
CCreateContext* pContext = (CCreateContext*)lpcs->lpCreateParams;
return OnCreateHelper(lpcs, pContext);
}
// in winfrm.cpp
int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs, CCreateContext* pContext)//部分源代碼
{
if (CWnd::OnCreate(lpcs) == -1)
return -1;
// create special children first
if (!OnCreateClient(lpcs, pContext))
{
TRACE(traceAppMsg, 0, "Failed to create client pane/view for frame.
");
return -1;
}
...//
return 0; // create ok
}
// in winfrm.cpp
BOOL CFrameWnd::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext)
{
// default create client will create a view if asked for it
if (pContext != NULL && pContext->m_pNewViewClass != NULL)
{
if (CreateView(pContext, AFX_IDW_PANE_FIRST) == NULL)
return FALSE;
}
return TRUE;
}
// in winfrm.cpp
CWnd* CFrameWnd::CreateView(CCreateContext* pContext, UINT nID)//部分源代碼
{
...//
// Note: can be a CWnd with PostNcDestroy self cleanup
CWnd* pView = (CWnd*)pContext->m_pNewViewClass->CreateObject();
if (pView == NULL)
{
TRACE(traceAppMsg, 0, "Warning: Dynamic create of view type %hs failed.
",
pContext->m_pNewViewClass->m_lpszClassName);
return NULL;
}
ASSERT_KINDOF(CWnd, pView);
// views are always created with a border!
if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
CRect(0,0,0,0), this, nID, pContext))
{
TRACE(traceAppMsg, 0, "Warning: could not create view for frame.
");
return NULL; // can't continue without a view
}
...//
return pView;
}
CWnd* pView = (CWnd*)pContext->m_pNewViewClass->CreateObject();核心函數終於出現了。子視圖動態生成完畢。
/////////////////////////////////////////
/* 7.收尾工作 */
/////////////////////////////////////////
至 此,一套完整的Document/ChildFrame/View結構生成,此“三口組”共屬同一套文檔模板,如果你要定義另一套不同的文檔模檔需再定義 另一組不同“三口組”(ChildFrame可以使用相同的)。並調用AddDocTemplate將該文檔模板加入到應用程序的文檔模板列表。比如:
CMultiDocTemplate* pOtherDocTemplate;
pOtherDocTemplate = new CMultiDocTemplate(IDR_MyOtherTYPE,
RUNTIME_CLASS(CMyOtherDoc),
RUNTIME_CLASS(CChildFrame), // 自定義 MDI 子框架
RUNTIME_CLASS(CMyOtherView));
AddDocTemplate(pOtherDocTemplate);
“三口組”生成後程序調用ShowWindow,UpdateWindow將應用程序的主窗口展現在你眼前。
註釋:當你在File菜單中選擇new或在工具欄中單擊“新建”時,應用程序將選擇當前默認的文檔模板並以它爲基礎動態生成 Document/ChildFrame/View“三口組”,其生成過程與我上述講的一般不二。
/********* 文章系列:MFC技術內幕系列***********/
/************MFC技術內幕系列之(二)***********/
/**** 文章題目:MFC文檔視圖結構內幕 *****/
/* Copyright(c)2002 bigwhite */
/* All rights Reserved */
/*********關鍵字:MFC,文檔視圖結構************/
/* 時間:2002.7.23 */
/* 註釋:本文所涉及的程序源代碼均在Microsoft */
/ Visual Studio.Net Enterprise Architect Edition /
/* 開發工具包提供的源代碼中 */
//////////////////////////////////////////////////////////////////////////////////////////////////////////
引 言:侯捷老師的"深入淺出MFC"一書的第8章中有“"Document/View"是MFC的基石。”一說,可以看出文檔視圖結構在MFC Framework中的地位是多麼的重要。本文將以一個標準MFC應用程序嚮導作成的MDI程序爲例,來和大家一起詳細挖掘文檔視圖結構的內幕。
正文:
/////////////////////////////////////////////
/* 1.回顧"InitInstance函數" */
/////////////////////////////////////////////
在 我的《MFC應用程序“生死因果”內幕》一文中,當談到CMyWinApp::InitInstance()時,我只是粗略的講了介紹了一下各個函數的功 能,而忽略了很多細節,這裏讓我們在回顧一下CMyWinApp::InitInstance()函數,並將裏面與文檔視圖結構有關的代碼深入探討一下:
BOOL CMyApp::InitInstance()//只列出了與文檔視圖結構相關的源代碼
{
//...
//文檔模板將用作文檔、框架窗口和視圖之間的連接
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(IDR_MyTYPE,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CChildFrame), // 自定義 MDI 子框架
RUNTIME_CLASS(CMyView));
AddDocTemplate(pDocTemplate);
// 創建主 MDI 框架窗口
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
// 僅當具有後綴時才調用 DragAcceptFiles
// 在 MDI 應用程序中,這應在設置 m_pMainWnd 之後立即發生
// 分析標準外殼命令、DDE、打開文件操作的命令行
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// 調度在命令行中指定的命令。如果
// 用 /RegServer、/Register、/Unregserver 或 /Unregister 啓動應用程序,則返回 FALSE。
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// 主窗口已初始化,因此顯示它並對其進行更新
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
return TRUE;
}
////////////////////////////////////////////
/* 2.初始化文檔模板 */
////////////////////////////////////////////
分析以下代碼:
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(IDR_MyTYPE,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CChildFrame), // 自定義 MDI 子框架
RUNTIME_CLASS(CMyView));
應用程序首先實例化一個CMultiDocTemplate對象,此過程也是對CMultiDocTemplate類數據成員的初始化過程。按調用次序我列出了以下源代碼:
註釋1: CDocTemplate構造函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcdoctempl.cpp
CDocTemplate::CDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass,
CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass)//部分源代碼
{
ASSERT_VALID_IDR(nIDResource);
ASSERT(pDocClass == NULL ||
pDocClass->IsDerivedFrom(RUNTIME_CLASS(CDocument)));
ASSERT(pFrameClass == NULL ||
pFrameClass->IsDerivedFrom(RUNTIME_CLASS(CFrameWnd)));
ASSERT(pViewClass == NULL ||
pViewClass->IsDerivedFrom(RUNTIME_CLASS(CView)));
m_nIDResource = nIDResource;
...//
m_pDocClass = pDocClass;
m_pFrameClass = pFrameClass;
m_pViewClass = pViewClass;
m_pOleFrameClass = NULL;
m_pOleViewClass = NULL;
...//
}
以上爲CMultiDocTemplate類的基類CDocTemplate構造函數的部分源代碼,該函數初始化了四個重要的成員
m_nIDResource,m_pDocClass,m_pFrameClass和m_pViewClass。
註釋2: CMultiDocTemplate構造函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcdocmulti.cpp
CMultiDocTemplate::CMultiDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass,
CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass)
: CDocTemplate(nIDResource, pDocClass, pFrameClass, pViewClass)
{
ASSERT(m_docList.IsEmpty());
m_hMenuShared = NULL;
m_hAccelTable = NULL;
m_nUntitledCount = 0; // start at 1
// load resources in constructor if not statically allocated
if (!CDocManager::bStaticInit)
LoadTemplate();
}
看完以上代碼後,來回過頭看一看InitInstance函數將什麼參數值傳給了CMultiDocTemplate的構造函數。
原來是一些RUNTIME_CLASS宏。以下是RUNTIME_CLASS宏的定義:
註釋3: 以下的宏定義在:..Visual Studio.NETvc7atlmfcincludeafx.h中
#define RUNTIME_CLASS(class_name) _RUNTIME_CLASS(class_name)
#define _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
這個地方是個難點,這將涉及到MFC的另一個重要技術---"執行期類型識別"。此項技術我將在
MFC 技術內幕系列之(三)---《MFC執行期類型識別與動態創建技術內幕》中詳細講解。回到眼前來,源代碼中這樣作是爲了將CMyDoc, CChildFrame,CMyView各類中的static CRuntimeClass class##class_name地址賦予CMultiDocTemplate類的各CRuntimeClass*指針成員m_pDocClass, m_pFrameClass和m_pViewClass,這位以後的動態創建Document/Frame/View"三口組"打下了基礎。
///////////////////////////////////////////
/* 3.文檔模板列隊 */
///////////////////////////////////////////
文 檔模板初始化結束後,InitInstance函數調用了CWinApp::AddDocTemplate(pDocTemplate)函數,其主要目的 是將以初始化後的那個文檔模板加入到文檔模板鏈表中,並由CDocManager類對象進行管理。以下操作就是爲了完成此工作。
註釋1: 以下函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcappui2.cpp
void CWinApp::AddDocTemplate(CDocTemplate* pTemplate)//將CMultiDocTemplate* pDocTemplate
{ //傳給pTemplate
if (m_pDocManager == NULL)
m_pDocManager = new CDocManager;
m_pDocManager->AddDocTemplate(pTemplate);
}
註釋2: 以下函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcdocmgr.cpp
CDocManager::CDocManager()
{
}//目前是一個空函數;
void CDocManager::AddDocTemplate(CDocTemplate* pTemplate)//部分源代碼
{
if (pTemplate == NULL)
{
...//
}
else
{
ASSERT_VALID(pTemplate);
ASSERT(m_templateList.Find(pTemplate, NULL) == NULL);// must not be in list
pTemplate->LoadTemplate();
m_templateList.AddTail(pTemplate);//CPtrList m_templateList is a member //of CDocManager
}
}
///////////////////////////////////////////////
/* 4.創建程序主框架窗口 */
///////////////////////////////////////////////
應用程序實例化了一個CMainFrame類對象,並調用LoadFrame函數加載窗口資源創建主框架窗口。以下是創建主框架窗口的流程。
創 建窗口的主要代碼是:pMainFrame->LoadFrame(IDR_MAINFRAME);LoadFrame函數是MFC包裝了窗口創建 過程的函數,在後面動態創建Child窗口時,它還將披掛上陣(但稍有不同)。下面是它的源代碼,讓我們仔細分析一下:
註釋1: 以下函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcwinmdi.cpp
BOOL CMDIFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
CWnd* pParentWnd, CCreateContext* pContext)
{
if (!CFrameWnd::LoadFrame(nIDResource, dwDefaultStyle,
pParentWnd, pContext))
return FALSE;
// save menu to use when no active MDI child window is present
ASSERT(m_hWnd != NULL);
m_hMenuDefault = ::GetMenu(m_hWnd);
if (m_hMenuDefault == NULL)
TRACE(traceAppMsg, 0, "Warning: CMDIFrameWnd without a default menu.
");
return TRUE;
}
CMDIFrameWnd::LoadFrame調用了其基類CFrameWnd的LoadFrame,並將參數原封不動的傳給它。
註釋2: 以下函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcwinfrm.cpp
BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
CWnd* pParentWnd, CCreateContext* pContext) //部分源代碼
{
...//
CString strFullString;
if (strFullString.LoadString(nIDResource))
AfxExtractSubString(m_strTitle, strFullString, 0); // first sub-string
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
// attempt to create the window
LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
CString strTitle = m_strTitle;
if (!Create(lpszClass, strTitle, dwDefaultStyle, rectDefault,
pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext))
{
return FALSE; // will self destruct on failure normally
}
...//
if (pContext == NULL) // send initial update
SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);
return TRUE;
}
//////////////////////////////////////////////////////
/* 4.1註冊應用程序主框架窗口類 */
//////////////////////////////////////////////////////
在 傳統的Win32API編程中,創建窗口一般步驟是定義窗口類,註冊窗口類,並調用::CreateWindow函數來創建。前面說過LoadFrame 函數封裝了MFC創建窗口的過程,那麼也就是說LoadFrame函數將負責定義窗口類,註冊窗口類等瑣碎工作。下面我們就通過挖掘源代碼來看看 LoadFrame函數是如何完成這些工作的。
LoadFrame首先調用AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG),
註釋1: 以下宏定義在:..Visual Studio.NETvc7atlmfcsrcmfcafximpl.h
#define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)
註釋2: 以下函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcwincore.cpp
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)//部分源代碼
{
// mask off all classes that are already registered
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
fToRegister &= ~pModuleState->m_fRegisteredClasses;
if (fToRegister == 0)
return TRUE;
LONG fRegisteredClasses = 0;
// common initialization
WNDCLASS wndcls;
memset(&wndcls, 0, sizeof(WNDCLASS)); // start with NULL defaults
wndcls.lpfnWndProc = DefWindowProc;
wndcls.hInstance = AfxGetInstanceHandle();
wndcls.hCursor = afxData.hcurArrow;
INITCOMMONCONTROLSEX init;
init.dwSize = sizeof(init);
// work to register classes as specified by fToRegister, populate fRegisteredClasses as we go
if (fToRegister & AFX_WND_REG)
{
// Child windows - no brush, no icon, safest default class styles
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.lpszClassName = _afxWnd;
if (AfxRegisterClass(&wndcls))
fRegisteredClasses |= AFX_WND_REG;
}
...//
if (fToRegister & AFX_WNDMDIFRAME_REG)
{
// MDI Frame window (also used for splitter window)
wndcls.style = CS_DBLCLKS;
wndcls.hbrBackground = NULL;
if (_AfxRegisterWithIcon(&wndcls, _afxWndMDIFrame, AFX_IDI_STD_MDIFRAME))
fRegisteredClasses |= AFX_WNDMDIFRAME_REG;
}
if (fToRegister & AFX_WNDFRAMEORVIEW_REG)
{
// SDI Frame or MDI Child windows or views - normal colors
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
if (_AfxRegisterWithIcon(&wndcls, _afxWndFrameOrView, AFX_IDI_STD_FRAME))
fRegisteredClasses |= AFX_WNDFRAMEORVIEW_REG;
}
...//
// must have registered at least as mamy classes as requested
return (fToRegister & fRegisteredClasses) == fToRegister;
}
MFC預定義了若干個“窗口類模板”,比如"AFX_WNDMDIFRAME_REG","AFX_WNDFRAMEORVIEW_REG"等,MFC在
LoadFrame 函數中調用AfxEndDeferRegisterClass函數爲你的應用程序預註冊了適當的窗口類。本例中預註冊的窗口類爲 AFX_WNDFRAMEORVIEW_REG。(注意是預註冊,如果你在後面更改了CREATESTRUCT結構的域成員,MFC還會根據你的更改重新 爲你的應用程序正式註冊新的窗口類,稍候會有詳細敘述)
預註冊完窗口類,MFC將判斷你是否想更改窗口類的各參數。若你更改了,則MFC會重新註冊新類;否則源預註冊的窗口類就將成爲正式的窗口類。下面我們來看看MFC的判斷過程:此判斷過程由GetIconWndClass開始
LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
註釋3: 以下函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcwinfrm.cpp
LPCTSTR CFrameWnd::GetIconWndClass(DWORD dwDefaultStyle, UINT nIDResource)//部分源代碼
{
...//
HICON hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(nIDResource));
if (hIcon != NULL)
{
CREATESTRUCT cs;
memset(&cs, 0, sizeof(CREATESTRUCT));
cs.style = dwDefaultStyle;
PreCreateWindow(cs);
// will fill lpszClassName with default WNDCLASS name
// ignore instance handle from PreCreateWindow.
WNDCLASS wndcls;
if (cs.lpszClass != NULL &&
GetClassInfo(AfxGetInstanceHandle(), cs.lpszClass, &wndcls) &&
wndcls.hIcon != hIcon)
{
// register a very similar WNDCLASS
return AfxRegisterWndClass(wndcls.style,
wndcls.hCursor, wndcls.hbrBackground, hIcon);
}
}
return NULL; // just use the default
}
GetIconWndClass 函數將調用CMainFrame::PreCreateWindow(CREATESTRUCT& cs)來看看應用程序是否修改了CREATESTRUCT結構的域成員。CMainFrame::PreCreateWindow調用 CMDIFrameWnd::PreCreateWindow(CREATESTRUCT& cs),後者的代碼如下:
BOOL CMDIFrameWnd::PreCreateWindow(CREATESTRUCT& cs)//in winmdi.cpp
{
if (cs.lpszClass == NULL)
{
VERIFY(AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG));
cs.lpszClass = _afxWndMDIFrame;
}
return TRUE;
}
MFC將爲應用程序註冊AFX_WNDMDIFRAME_REG預定義窗口類,並設置cs.lpszClass = _afxWndMDIFrame。
在應用程序的代碼中我更改了cs結構:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CMDIFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: 在此處通過修改 CREATESTRUCT cs 來修改窗口類或
// 樣式
cs.dwExStyle=~WS_EX_CLIENTEDGE;
return TRUE;
}
CMainFrame:: PreCreateWindow返回後,GetIconWndClass函數調用GetClassInfo函數重新收集cs信息(此時的信息已是更改後的 了),並調用AfxRegisterWndClass函數重新註冊該窗口類(此窗口類爲該應用程序的正式窗口類)。到此爲止窗口類註冊完畢,以後程序還會 調用一次CMainFrame::PreCreateWindow,不過那此只是"過門不如"而已。
/////////////////////////////////////////////////
/* 4.2主框架窗口創建開始 */
/////////////////////////////////////////////////
開始進入創建框架窗口的實質階段。看LoadFrame函數做了什麼?原來它調用了Create函數。
註釋1: 以下函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcwinfrm.cpp
BOOL CFrameWnd::Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
LPCTSTR lpszMenuName,
DWORD dwExStyle,
CCreateContext* pContext)
{
HMENU hMenu = NULL;
if (lpszMenuName != NULL)
{
// load in a menu that will get destroyed when window gets destroyed
HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, RT_MENU);
if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL)
{
TRACE(traceAppMsg, 0, "Warning: failed to load menu for CFrameWnd.
");
PostNcDestroy(); // perhaps delete the C++ object
return FALSE;
}
}
m_strTitle = lpszWindowName; // save title for later
if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext))
{
TRACE(traceAppMsg, 0, "Warning: failed to create CFrameWnd.
");
if (hMenu != NULL)
DestroyMenu(hMenu);
return FALSE;
}
return TRUE;
}
簡單地說CFrameWnd::Create函數調用了基類的CWnd::CreateEx;
註釋2: 以下函數定義在:..Visual Studio.NETvc7atlmfcsrcmfcwincore.cpp
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)//部分源代碼
{
// allow modification of several common create parameters
CREATESTRUCT cs;
cs.dwExStyle = dwExStyle;
cs.lpszClass = lpszClassName;
cs.lpszName = lpszWindowName;
cs.style = dwStyle;
cs.x = x;
cs.y = y;
cs.cx = nWidth;
cs.cy = nHeight;
cs.hwndParent = hWndParent;
cs.hMenu = nIDorHMenu;
cs.hInstance = AfxGetInstanceHandle();
cs.lpCreateParams = lpParam;
if (!PreCreateWindow(cs))
{
PostNcDestroy();
return FALSE;
}
AfxHookWindowCreate(this);
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);
...//
return TRUE;
}
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CMDIFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: 在此處通過修改 CREATESTRUCT cs 來修改窗口類或
// 樣式
cs.dwExStyle=~WS_EX_CLIENTEDGE;
return TRUE;
}
BOOL CMDIFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL)
{
VERIFY(AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG));
cs.lpszClass = _afxWndMDIFrame;
}
return TRUE;
}
CWnd::CreateEx調用了CMainFrame::PreCreateWindow,但此次AfxDeferRegisterClass將不會被調用。也就是我上面所說的“過門不入”。
CWnd::CreateEx函數還調用了AfxHookWindowCreate(this);後者是幹什麼的呢?其實它與消息映射和命令傳遞有關,我將在MFC技術內幕系列之(四)--《MFC消息映射與消息傳遞內幕》一文中詳解。
CWnd::CreateEx調用Win32API ::CreateWindowEx函數(傳統的Win32API程序員一定不陌生這個函數),
就這樣主框架窗口創建結束。
//////////////////////////////////////////////
/* 5.標準外殼命令解析 */
///////////////////////////////////////////////
MFC嚮導製作的標準MDI應用程序啓動時,應用程序會自動啓動一個子窗口框架(實際上是一套文檔模板),這是爲何呢?下面我將詳細講解一下這個創建過程.
其實這一過程也是在CMyWinApp::InitInstance()函數中完成的,看看下面代碼:
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
if (!ProcessShellCommand(cmdInfo))
return FALSE;
函數首先實例化一個CCommandLineInfo類對象cmdInfo,讓我們看看CCommandLineInfo是個什麼東東?
//in afxwin.h
class CCommandLineInfo : public CObject//部分源代碼
{
public:
// Sets default values
CCommandLineInfo();
...//
BOOL m_bShowSplash;
BOOL m_bRunEmbedded;
BOOL m_bRunAutomated;
enum { FileNew, FileOpen, FilePrint, FilePrintTo, FileDDE, AppRegister,
AppUnregister, FileNothing = -1 } m_nShellCommand;
// not valid for FileNew
CString m_strFileName;
// valid only for FilePrintTo
CString m_strPrinterName;
CString m_strDriverName;
CString m_strPortName;
~CCommandLineInfo();
// Implementation
...//
};
再讓我們來看看它的構造函數的實現:
//in appcore.cpp
CCommandLineInfo::CCommandLineInfo()
{
m_bShowSplash = TRUE;
m_bRunEmbedded = FALSE;
m_bRunAutomated = FALSE;
m_nShellCommand = FileNew;
}
m_nShellCommand = FileNew;這一句對我們最重要;至於CWinApp::ParseCommandLine我想用MFC文檔中的一句話來解釋:
Call this member function to parse the command line and send the parameters, one at a time, to CCommandLineInfo::ParseParam.
下面我們來看看外殼命令解析的主角:CWinApp::ProcessShellCommand
//in appui2.cpp
//DDE and ShellExecute support
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;
// If we've been asked to open a file, call OpenDocumentFile()
case CCommandLineInfo::FileOpen:
if (!OpenDocumentFile(rCmdInfo.m_strFileName))
bResult = FALSE;
break;
case CCommandLineInfo::FilePrintTo:
case CCommandLineInfo::FilePrint:
...//
case CCommandLineInfo::FileDDE:
...//
case CCommandLineInfo::AppRegister:
...//
case CCommandLineInfo::AppUnregister:
...//
}
return bResult;
}
挖 掘源代碼的確是瞭解MFC運行內幕的最好手段,大家一看源代碼便知道如之何了。CCommandLineInfo構造函數中 m_nShellCommand = FileNew;所以在ProcessShellCommand中對應的代碼自然就一目瞭然了:CWinApp::OnFileNew()被調用了。
//////////////////////////////////////////////////
/* 6.一套文檔/視圖即將誕生 */
//////////////////////////////////////////////////
上文說CWinApp::OnFileNew()被調用了,那麼就讓我來看看其代碼吧!
//in appdlg.cpp
void CWinApp::OnFileNew()
{
if (m_pDocManager != NULL)
m_pDocManager->OnFileNew();
}
//in docmgr.cpp
void CDocManager::OnFileNew()//部分源代碼
{
...//
CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();
if (m_templateList.GetCount() > 1)
{
// more than one document template to choose from
// bring up dialog prompting user
CNewTypeDlg dlg(&m_templateList);
INT_PTR nID = dlg.DoModal();
if (nID == IDOK)
pTemplate = dlg.m_pSelectedTemplate;
else
return; // none - cancel operation
}
ASSERT(pTemplate != NULL);
ASSERT_KINDOF(CDocTemplate, pTemplate);
pTemplate->OpenDocumentFile(NULL);
// if returns NULL, the user has already been alerted
}
//in docmulti.cpp
CDocument* CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,
BOOL bMakeVisible)//部分源代碼
{
CDocument* pDocument = CreateNewDocument();
...//
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 (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.
");
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.
");
pFrame->DestroyWindow();
return NULL;
}
pDocument->SetPathName(lpszPathName);
}
InitialUpdateFrame(pFrame, pDocument, bMakeVisible);
return pDocument;
}
//////////////////////////////////////////////
/* 6.1.子文檔動態生成 */
//////////////////////////////////////////////
CMultiDocTemplate::OpenDocumentFile調用了CreateNewDocument(),這就是子文檔動態生成的主函數。
//in doctempl.cpp
CDocument* CDocTemplate::CreateNewDocument()//部分源代碼
{
// default implementation constructs one from CRuntimeClass
...//
CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();
...//
AddDocument(pDocument);//將動態生成的文檔對象的指針加入到應用程序的文檔列表中
return pDocument;
}
CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();這一句就是動態產生的核心,它藉助於 CRuntimeClass動態生成一個CDocument對象。其動態生成的奧祕我將在MFC技術內幕系列之(三)---
《MFC執行期類型識別與動態創建技術內幕》一文中詳解。
//////////////////////////////////////////////////
/* 6.2.子窗口框架動態生成 */
/////////////////////////////////////////////////
CMultiDocTemplate::OpenDocumentFile調用了CreateNewFrame,這就是子窗口框架動態生成的主函數。
// Default frame creation
CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther)//部分源代碼
{
if (pDoc != NULL)
ASSERT_VALID(pDoc);
// create a frame wired to the specified document
ASSERT(m_nIDResource != 0); // must have a resource ID to load from
CCreateContext context;
context.m_pCurrentFrame = pOther;
context.m_pCurrentDoc = pDoc;
context.m_pNewViewClass = m_pViewClass;
context.m_pNewDocTemplate = this;
...//
CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();
if (pFrame == NULL)
{
TRACE(traceAppMsg, 0, "Warning: Dynamic create of frame %hs failed.
",
m_pFrameClass->m_lpszClassName);
return NULL;
}
ASSERT_KINDOF(CFrameWnd, pFrame);
...//
// create new from resource
if (!pFrame->LoadFrame(m_nIDResource,
WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, // default frame styles
NULL, &context))
{
TRACE(traceAppMsg, 0, "Warning: CDocTemplate couldn't create a frame.
");
// frame will be deleted in PostNcDestroy cleanup
return NULL;
}
// it worked !
return pFrame;
}
CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();這一句就是動態產生的核心,它藉助於 CRuntimeClass動態生成一個CDocument對象。其動態生成的奧祕我將在MFC技術內幕系列之(三)---
《MFC執行期類型識別與動態創建技術內幕》一文中詳解。之後函數調用LoadFrame來創建子窗口。其過程與創建主框架窗口的過程大致相同,但也有一些不同的地方,下面我就將說說這點不同。
//////////////////////////////////////////////
/* 6.3.子視圖動態生成 */
//////////////////////////////////////////////
瞪大眼睛仔細察看OpenDocumentFile的源代碼,疑惑了,"怎麼沒有類似CView* pView =CreateNewView();
的代碼?","那麼子視圖是如何生成的呢"下面我就爲你詳細解釋一下吧!其實子視圖動態生成函數被放到另一個地方了。讓我們詳細來看看吧。
其實,關鍵還是在LoadFrame,但與創建主窗口框架的那個LoadFrame不同的是傳進了一個不同的參數
&context,你回過頭看看主窗口框架的那個LoadFrame,調用它時使用了默認參數,而那個默認參數值爲NULL,
下面看看CCreateContext 結構。
//in afxext.h
struct CCreateContext // Creation information structure
// All fields are optional and may be NULL
{
// for creating new views
CRuntimeClass* m_pNewViewClass; // runtime class of view to create or NULL
CDocument* m_pCurrentDoc;
// for creating MDI children (CMDIChildWnd::LoadFrame)
CDocTemplate* m_pNewDocTemplate;
// for sharing view/frame state from the original view/frame
CView* m_pLastView;
CFrameWnd* m_pCurrentFrame;
// Implementation
CCreateContext();
};
而在CDocTemplate::CreateNewFrame中初始化了該結構如下:
CCreateContext context;
context.m_pCurrentFrame = pOther;
context.m_pCurrentDoc = pDoc;
context.m_pNewViewClass = m_pViewClass;
context.m_pNewDocTemplate = this;
context.m_pNewViewClass = m_pViewClass;//關鍵的成員
下面看看這個創建的具體過程:
LoadFrame(...,&context)-->CFrameWnd::Create(...,&context)--> CWnd::CreateEx(...,&context)
-->::CreateWindowEx
::CreateWindowEx API函數將產生WM_CREATE消息,並將&context傳遞之,CMainFrame::OnCreate將響應消息,並引起一系列的函數調用,看下面:
//in mainfrm.cpp
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
...//
return 0;
}
// in winfrm.cpp
int CFrameWnd::OnCreate(LPCREATESTRUCT lpcs)
{
CCreateContext* pContext = (CCreateContext*)lpcs->lpCreateParams;
return OnCreateHelper(lpcs, pContext);
}
// in winfrm.cpp
int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs, CCreateContext* pContext)//部分源代碼
{
if (CWnd::OnCreate(lpcs) == -1)
return -1;
// create special children first
if (!OnCreateClient(lpcs, pContext))
{
TRACE(traceAppMsg, 0, "Failed to create client pane/view for frame.
");
return -1;
}
...//
return 0; // create ok
}
// in winfrm.cpp
BOOL CFrameWnd::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext)
{
// default create client will create a view if asked for it
if (pContext != NULL && pContext->m_pNewViewClass != NULL)
{
if (CreateView(pContext, AFX_IDW_PANE_FIRST) == NULL)
return FALSE;
}
return TRUE;
}
// in winfrm.cpp
CWnd* CFrameWnd::CreateView(CCreateContext* pContext, UINT nID)//部分源代碼
{
...//
// Note: can be a CWnd with PostNcDestroy self cleanup
CWnd* pView = (CWnd*)pContext->m_pNewViewClass->CreateObject();
if (pView == NULL)
{
TRACE(traceAppMsg, 0, "Warning: Dynamic create of view type %hs failed.
",
pContext->m_pNewViewClass->m_lpszClassName);
return NULL;
}
ASSERT_KINDOF(CWnd, pView);
// views are always created with a border!
if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
CRect(0,0,0,0), this, nID, pContext))
{
TRACE(traceAppMsg, 0, "Warning: could not create view for frame.
");
return NULL; // can't continue without a view
}
...//
return pView;
}
CWnd* pView = (CWnd*)pContext->m_pNewViewClass->CreateObject();核心函數終於出現了。子視圖動態生成完畢。
/////////////////////////////////////////
/* 7.收尾工作 */
/////////////////////////////////////////
至 此,一套完整的Document/ChildFrame/View結構生成,此“三口組”共屬同一套文檔模板,如果你要定義另一套不同的文檔模檔需再定義 另一組不同“三口組”(ChildFrame可以使用相同的)。並調用AddDocTemplate將該文檔模板加入到應用程序的文檔模板列表。比如:
CMultiDocTemplate* pOtherDocTemplate;
pOtherDocTemplate = new CMultiDocTemplate(IDR_MyOtherTYPE,
RUNTIME_CLASS(CMyOtherDoc),
RUNTIME_CLASS(CChildFrame), // 自定義 MDI 子框架
RUNTIME_CLASS(CMyOtherView));
AddDocTemplate(pOtherDocTemplate);
“三口組”生成後程序調用ShowWindow,UpdateWindow將應用程序的主窗口展現在你眼前。
註釋:當你在File菜單中選擇new或在工具欄中單擊“新建”時,應用程序將選擇當前默認的文檔模板並以它爲基礎動態生成 Document/ChildFrame/View“三口組”,其生成過程與我上述講的一般不二。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.