MFC框架如何创建的过程
2011-07-15 23:45
471 查看
温故而知新,学习MFC框架如何创建的过程
很久没有使用MFC了,以至于都忘记MFC框架复杂的窗口、文档、视的创建过程了。下面我们跟踪一个MFC MDI的应用程序,来温习或学习一下。使用AppWizard创建一个MDI应用程序,我创建的应用程序叫MDITest,这样MFC生成了如下的类:
类名 | 作用 |
CMDITestApp | 派生于CWinApp的应用程序类。 |
CMainFrame | 派生于CMDIFrameWnd的MDI框架窗口类。 |
CMDITestDoc | 派生于CDocument的文档类。 |
CChildFrame | 派生于CMDIChildWnd的MDI子窗口类。 |
CMDITestView | 派生于CView的文档显示类。 |
CMainFrame (Menu, Toolbar …)
|
我们从这里开始:
// CMDITestApp 初始化 BOOL CMDITestApp::InitInstance() |
整个InitInstance代码如下:
BOOL CMDITestApp::InitInstance() { InitCommonControls(); // 这里删减了大量注释和错误处理 CWinApp::InitInstance(); AfxOleInit(); AfxEnableControlContainer(); SetRegistryKey(_T("应用程序向导生成的本地应用程序")); LoadStdProfileSettings(4); // 加载标准 INI 文件选项(包括 MRU) TRACE("Before CMultiDocTemplate\n"); // 注册应用程序的文档模板。文档模板 // 将用作文档、框架窗口和视图之间的连接 CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate(IDR_MDITestTYPE, RUNTIME_CLASS(CMDITestDoc), RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架 RUNTIME_CLASS(CMDITestView)); if (!pDocTemplate) return FALSE; TRACE("Before AddDocTemplate\n"); AddDocTemplate(pDocTemplate); // 创建主 MDI 框架窗口 TRACE("Before new CMainFrame\n"); CMainFrame* pMainFrame = new CMainFrame; TRACE("Before pMainFrame->LoadFrame\n"); if (!pMainFrame || !pMainFrame->LoadFrame(IDR_MAINFRAME)) return FALSE; m_pMainWnd = pMainFrame; TRACE("Before ParseCommandLine\n"); CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); // 调度在命令行中指定的命令。如果 // 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。 TRACE("Before ProcessShellCommand\n"); if (!ProcessShellCommand(cmdInfo)) return FALSE; TRACE("Before pMainFrame->ShowWindow\n"); // 主窗口已初始化,因此显示它并对其进行更新 pMainFrame->ShowWindow(m_nCmdShow); TRACE("Before pMainFrame->UpdateWindow\n"); pMainFrame->UpdateWindow(); return TRUE;} |
忽略掉开始的乱七八糟的初始化,从CMultiDocTemplate开始:
CMultiDocTemplate* pDocTemplate = new CMultiDocTemplate(IDR_MDITestTYPE, RUNTIME_CLASS(CMDITestDoc), RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架 RUNTIME_CLASS(CMDITestView)); AddDocTemplate(pDocTemplate);(作了一点点简化) |
然后通过AddDocTemplate函数将新创建的文档模板添加到模板管理器之中(我们以后再研究模板管理器)。
然后创建主框架窗口CMainFrame:
CMainFrame* pMainFrame = new CMainFrame; if (!pMainFrame || !pMainFrame->LoadFrame(IDR_MAINFRAME)) return FALSE; |
处理命令行,在这里第一个空文档被建立出来:
CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); // 调度在命令行中指定的命令。如果用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。 if (!ProcessShellCommand(cmdInfo)) // ß 这里创建出初始空文档 return FALSE; |
最后,显示主窗口:
pMainFrame->ShowWindow(m_nCmdShow); pMainFrame->UpdateWindow(); |
上面遗留了三个待研究的分支,让我们现在去研究它们:
1、 CDocTemplate
2、 CFrameWnd::LoadFrame
3、 CWnd::ProcessShellCommand
研究CDocTemplate
我们的例子中是构造了一个CMultiDocTemplate,它是从CDocTemplate派生而来,所以我们主要研究CDocTemplate。
CDocTemplate的几个关键属性列表如下:
CRuntimeClass* m_pDocClass; // class for creating new documents CRuntimeClass* m_pFrameClass; // class for creating new frames CRuntimeClass* m_pViewClass; // class for creating new views |
m_pDocClass | 表示文档类类型,在此例子中就是CMDITestDoc |
m_pFrameClass | 表示容纳View窗口的框架窗口类类型,此例中为CChildFrame |
m_pViewClass | 表示显示文档的View视类类型,此例中为CMDITestView |
一会还会看到CDocTemplate的创建文档、框架、视的过程,放在ProcessShellCommand中研究。
研究LoadFrame
让我们继续研究CFrameWnd::LoadFrame是怎么运作的。使用的方法是跟踪进入。。。
BOOL CMDIFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext){ // 调用基类 CFrameWnd 的 LoadFrame, pContext 在创建主窗口时 = NULL // pParentWnd = NULL 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.\n"); return TRUE;} |
BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext){ // only do this once ASSERT_VALID_IDR(nIDResource); // nIDResource = 128, IDR_MAINFRAME ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource); m_nIDHelp = nIDResource; // ID for help context (+HID_BASE_RESOURCE) CString strFullString; if (strFullString.LoadString(nIDResource)) // = “MDITest” AfxExtractSubString(m_strTitle, strFullString, 0); // 取得第一个子串 VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG)); // attempt to create the window // GetIconWndClass 会调用 virtual PreCreateWindow 函数,别处也会调用,从而 // 使得子类的PreCreateWindow 将被调用多次 LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource); CString strTitle = m_strTitle; // 调用 CFrameWnd::Create() 实际创建出窗口。 // 注意:在这里将给 CMainFrame 发送 WM_CREATE 等多个消息。触发 CMainFrame 的 // OnCreate 处理等。 if (!Create(lpszClass, strTitle, dwDefaultStyle, rectDefault, pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext)) { return FALSE; // will self destruct on failure normally } // save the default menu handle, 好像 CMDIFrameWnd 也保存了一次? ASSERT(m_hWnd != NULL); m_hMenuDefault = ::GetMenu(m_hWnd); // load accelerator resource LoadAccelTable(MAKEINTRESOURCE(nIDResource)); // WM_INITIALUPDATE 是 MFC 发明的消息,参见后面的说明。 if (pContext == NULL) // send initial update SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE); return TRUE;} |
WM_INITIALUPDATE This message is sent by the document template to all descendants of a frame window when it is safe for them to do their initial update. It maps to a call to CView::OnInitialUpdate but can be used in other CWnd-derived classes for other one-shot updating.
|
1、 注册窗口类(AfxDeferRegisterClass)
2、 实际创建窗口(Create)
3、 处理菜单、快捷键,发送WM_INITIALUPDATE消息给所有子窗口。实际将在CView中处理此消息。(例如:在ToolBar上面放一个FormView,可能就能收到这个消息并处利?)
至此,CMainFrame已经成功创建,菜单已经装载,工具条、状态行等已经在CMainFrame::OnCreate中创建。让我们接着研究第一个子窗口是怎么被创建出来的,该过程和CMainFrame::LoadFrame比起来就不那么直接了。
研究CWnd::ProcessShellCommand
第一个MDI子窗口是从这里面建立出来的,这实在是缺乏直观性。不过MFC就是这样,没办法。
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: // 忽略 case CCommandLineInfo::FilePrintTo: // 忽略 case CCommandLineInfo::FilePrint: case CCommandLineInfo::FileDDE: case CCommandLineInfo::AppRegister: case CCommandLineInfo::AppUnregister: } return bResult;} |
注意:实际进入到了AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL)之中。
AfxGetApp()实际返回了CMDITestApp的唯一实例,它从CWinApp – CWinThread – CCmdTarget – CObject 派生而来。我们没有重载OnCmdMsg,所以进入到CCmdTarget的OnCmdMsg处理中。为了研究,我们删减了一些代码。
BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo){ // 这里删减了一些代码 // determine the message number and code (packed into nCode) const AFX_MSGMAP* pMessageMap; const AFX_MSGMAP_ENTRY* lpEntry; UINT nMsg = 0; // 这里删减了一些代码,处理后 nMsg = WM_COMMAND // 为了简化,删减了一些断言等。以下循环用于查找处理此消息的入口。 for (pMessageMap = GetMessageMap(); pMessageMap->pfnGetBaseMap != NULL; pMessageMap = (*pMessageMap->pfnGetBaseMap)()) { lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID); if (lpEntry != NULL) { // 找到了消息处理项入口,分发此消息。 return _AfxDispatchCmdMsg(this, nID, nCode, lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo); } } return FALSE; // 未找到则不处理} |
AFX_STATIC BOOL AFXAPI _AfxDispatchCmdMsg(CCmdTarget* pTarget, UINT nID, int nCode, AFX_PMSG pfn, void* pExtra, UINT_PTR nSig, AFX_CMDHANDLERINFO* pHandlerInfo) // return TRUE to stop routing{ union MessageMapFunctions mmf; mmf.pfn = pfn; BOOL bResult = TRUE; // default is ok if (pHandlerInfo != NULL) { // just fill in the information, don't do it pHandlerInfo->pTarget = pTarget; pHandlerInfo->pmf = mmf.pfn; return TRUE; } switch (nSig) { case AfxSigCmd_v: // normal command or control notification ASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKED ASSERT(pExtra == NULL); (pTarget->*mmf.pfnCmd_v_v)(); // ß 实际调用 pTarget 指向的这个成员函数 break; // 下面还有大量的多种 AfxSigCmd_xxx,忽略掉它们。 default: // illegal ASSERT(FALSE); return 0; break; } return bResult;} |
void CWinApp::OnFileNew() { if (m_pDocManager != NULL) m_pDocManager->OnFileNew();} |
void CDocManager::OnFileNew() { if (m_templateList.IsEmpty()) // 提示没有模板并返回 CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead(); // 第一个 if (m_templateList.GetCount() > 1) // 弹出一个对话框(很难看的)提示用户选择一个文档模板 // 在这个例子里面,pTemplate 就是 CMDITestApp::InitInstance() 里面创建的那个模板 pTemplate->OpenDocumentFile(NULL);} |
> mfc71d.dll!CDocManager::OnFileNew() 行852 C++ mfc71d.dll!CWinApp::OnFileNew() 行25 C++ mfc71d.dll!_AfxDispatchCmdMsg(CCmdTarget * pTarget=0x0042cae8, unsigned int nID=57600, int nCode=0, void (void)* pfn=0x0041153c, void * pExtra=0x00000000, unsigned int nSig=53, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000) 行89 C++ mfc71d.dll!CCmdTarget::OnCmdMsg(unsigned int nID=57600, int nCode=0, void * pExtra=0x00000000, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000) 行396 + 0x27 C++ mfc71d.dll!CWinApp::ProcessShellCommand(CCommandLineInfo & rCmdInfo={...}) 行27 + 0x1e C++ MDITest.exe!CMDITestApp::InitInstance() 行101 + 0xc C++ |
CMultiDocTemplate::OpenDocumentFile 又是很多很多代码,让我们选择一些。
CDocument* CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName, BOOL bMakeVisible){ // 以下代码删减了验证、断言部分 CDocument* pDocument = CreateNewDocument(); // 创建文档对象 CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL); // 创建框架窗口 if (lpszPathName == NULL) { pDocument->OnNewDocument(); // 初始化文档 } else // 打开已有文档 InitialUpdateFrame(pFrame, pDocument, bMakeVisible); return pDocument;} |
CDocument* CDocTemplate::CreateNewDocument() { // default implementation constructs one from CRuntimeClass if (m_pDocClass == NULL) // 错误提示啦 // CRuntimeClass* m_pDocClass -> CreateObject 实例化文档类。 // 在此例子中既是 CMDITestDoc CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject(); AddDocument(pDocument); // 添加到模板里的文档列表,MultiDocTemplate 保存此一文档 return pDocument;} |
class CMDITestDoc : public CDocument {protected: // 仅从序列化创建 CMDITestDoc(); // 被保护的构造函数 DECLARE_DYNCREATE(CMDITestDoc) // 支持从 CRuntimeClass 信息中创建。 |
CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther) { // create a frame wired to the specified document CCreateContext context; // 这个 CreateContext 传递到 LoadFrame 中 context.m_pCurrentFrame = pOther; // 此例中 = NULL context.m_pCurrentDoc = pDoc; // = 刚才创建的文档 context.m_pNewViewClass = m_pViewClass; // 显示此文档的视类的类型 context.m_pNewDocTemplate = this; if (m_pFrameClass == NULL) // 提示错误并返回 // 利用 CRuntimeClass 信息创建框架窗口对象,此例中为 CChildFrame CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject(); // 这里,我们又看到了 LoadFrame , 参考前面的 LoadFrame 吧 // 在这里面,View 窗口也被产生出来。参考 TRACE 输出。 pFrame->LoadFrame(m_nIDResource, WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, // default frame styles NULL, &context); return pFrame;} |
BOOL CMDITestDoc::OnNewDocument() { TRACE("CMDITestDoc::OnNewDocument() entry\n"); if (!CDocument::OnNewDocument()) return FALSE; // TODO: 在此添加重新初始化代码 // (SDI 文档将重用该文档) return TRUE;} |
void CDocTemplate::InitialUpdateFrame(CFrameWnd* pFrame, CDocument* pDoc, BOOL bMakeVisible){ // just delagate to implementation in CFrameWnd pFrame->InitialUpdateFrame(pDoc, bMakeVisible);} |
// 主窗口已初始化,因此显示它并对其进行更新 pMainFrame->ShowWindow(m_nCmdShow); pMainFrame->UpdateWindow(); |
Before CMultiDocTemplate Before AddDocTemplateBefore new CMainFrameCMainFrame::CMainFrame()Before pMainFrame->LoadFrameCMainFrame::PreCreateWindow entry // 注意:PreCreateWindow 被两次调用CMainFrame::PreCreateWindow entryCMainFrame::OnCreate entry before CMDIFrameWnd::OnCreateCMainFrame::OnCreate before m_wndToolBar.CreateExCMainFrame::OnCreate before m_wndStatusBar.CreateBefore ParseCommandLineBefore ProcessShellCommandCMDITestDoc::CMDITestDoc() // 文档对象被创建CChildFrame::CChildFrame() // 子框架窗口被创建CChildFrame::PreCreateWindow entry CChildFrame::PreCreateWindow entryCChildFrame::PreCreateWindow entryCMDITestView::CMDITestView() entry // 子框架窗口的 OnCreate 中创建了 View 窗口CMDITestView::PreCreateWindow entryCMDITestDoc::OnNewDocument() entryBefore pMainFrame->ShowWindowBefore pMainFrame->UpdateWindow // 退出时的 TRACECMDITestView::~CMDITestView()CChildFrame::~CChildFrame()CMDITestDoc::~CMDITestDoc()CMainFrame::~CMainFrame() |
下一篇:第一部分. ArgoUML指南 热门文章1.C 语言基础教程(我的C之旅开始了)[八]
2.C 语言基础教程(我的C之旅开始了)[五]
3.C 语言基础教程(我的C之旅开始了)[十]
4.C 语言基础教程(我的C之旅开始了)[九]
5.C 语言基础教程(我的C之旅开始了)[四]
6.tc编译的dos程序和vc编译的win32控制台程序...
7.C 语言基础教程(我的C之旅开始了)[七]
8.C 语言基础教程(我的C之旅开始了)[三]
9.Delegate比较全面的例子
10.BerkeleyDB 在 C 中的范例 CURD
11.VC常用快捷键
12.Cell报表控件成功应用于汽车零配件销售企业...
13.C 语言基础教程(一)颜色加亮
14.笑解 API 函数(转)
15.汉字编码的编程问题
16.VC20个小技巧
17.换肤技术的一点心得:置换控件的背景颜色与...
18.VC WinExec打开指定程序或者文件的方法
19.vc树形控件中点击右键建立一个弹出式菜单
20.VC++网络编程
推荐信息[商业源码]湖南某软件公司商业POS通系统(Delphi)
[商业源码]某医院HIS管理系统(Delphi)
[商业源码]惠特ERP管理系统(VB)
[商业源码]某指纹考勤管理系统(Delphi)
[商业源码]某市公路运输管理系统(.NET)
[商业源码]某大型商业连锁系统(PB)
[商业源码]某网络营销软件(VB)
[商业源码]新中大财务管理系统(PB)
[商业源码]SUNSHINE医院管理系统(PB)
[商业源码]大型WEB进销存系统(ASP)
[商业源码]某企业大型ERP系统(VB)
[商业源码]大型企业内部管理系统(VB)
[商业源码]伯乐人力资源管理系统2000(VB)
[商业源码]邮件管理与群发系统(VB)
[商业源码]报表设计系统2000(VB)
温故而知新,学习MFC框架如何创建的过程
发表:不详 阅读:次 关键字: 字体:[大 中 小]很久没有使用MFC了,以至于都忘记MFC框架复杂的窗口、文档、视的创建过程了。下面我们跟踪一个MFC MDI的应用程序,来温习或学习一下。
使用AppWizard创建一个MDI应用程序,我创建的应用程序叫MDITest,这样MFC生成了如下的类:
类名 | 作用 |
CMDITestApp | 派生于CWinApp的应用程序类。 |
CMainFrame | 派生于CMDIFrameWnd的MDI框架窗口类。 |
CMDITestDoc | 派生于CDocument的文档类。 |
CChildFrame | 派生于CMDIChildWnd的MDI子窗口类。 |
CMDITestView | 派生于CView的文档显示类。 |
CMainFrame (Menu, Toolbar …)
|
我们从这里开始:
// CMDITestApp 初始化 BOOL CMDITestApp::InitInstance() |
整个InitInstance代码如下:
BOOL CMDITestApp::InitInstance() { InitCommonControls(); // 这里删减了大量注释和错误处理 CWinApp::InitInstance(); AfxOleInit(); AfxEnableControlContainer(); SetRegistryKey(_T("应用程序向导生成的本地应用程序")); LoadStdProfileSettings(4); // 加载标准 INI 文件选项(包括 MRU) TRACE("Before CMultiDocTemplate\n"); // 注册应用程序的文档模板。文档模板 // 将用作文档、框架窗口和视图之间的连接 CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate(IDR_MDITestTYPE, RUNTIME_CLASS(CMDITestDoc), RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架 RUNTIME_CLASS(CMDITestView)); if (!pDocTemplate) return FALSE; TRACE("Before AddDocTemplate\n"); AddDocTemplate(pDocTemplate); // 创建主 MDI 框架窗口 TRACE("Before new CMainFrame\n"); CMainFrame* pMainFrame = new CMainFrame; TRACE("Before pMainFrame->LoadFrame\n"); if (!pMainFrame || !pMainFrame->LoadFrame(IDR_MAINFRAME)) return FALSE; m_pMainWnd = pMainFrame; TRACE("Before ParseCommandLine\n"); CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); // 调度在命令行中指定的命令。如果 // 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。 TRACE("Before ProcessShellCommand\n"); if (!ProcessShellCommand(cmdInfo)) return FALSE; TRACE("Before pMainFrame->ShowWindow\n"); // 主窗口已初始化,因此显示它并对其进行更新 pMainFrame->ShowWindow(m_nCmdShow); TRACE("Before pMainFrame->UpdateWindow\n"); pMainFrame->UpdateWindow(); return TRUE;} |
忽略掉开始的乱七八糟的初始化,从CMultiDocTemplate开始:
CMultiDocTemplate* pDocTemplate = new CMultiDocTemplate(IDR_MDITestTYPE, RUNTIME_CLASS(CMDITestDoc), RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架 RUNTIME_CLASS(CMDITestView)); AddDocTemplate(pDocTemplate);(作了一点点简化) |
然后通过AddDocTemplate函数将新创建的文档模板添加到模板管理器之中(我们以后再研究模板管理器)。
然后创建主框架窗口CMainFrame:
CMainFrame* pMainFrame = new CMainFrame; if (!pMainFrame || !pMainFrame->LoadFrame(IDR_MAINFRAME)) return FALSE; |
处理命令行,在这里第一个空文档被建立出来:
CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); // 调度在命令行中指定的命令。如果用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。 if (!ProcessShellCommand(cmdInfo)) // ß 这里创建出初始空文档 return FALSE; |
最后,显示主窗口:
pMainFrame->ShowWindow(m_nCmdShow); pMainFrame->UpdateWindow(); |
上面遗留了三个待研究的分支,让我们现在去研究它们:
1、 CDocTemplate
2、 CFrameWnd::LoadFrame
3、 CWnd::ProcessShellCommand
研究CDocTemplate
我们的例子中是构造了一个CMultiDocTemplate,它是从CDocTemplate派生而来,所以我们主要研究CDocTemplate。
CDocTemplate的几个关键属性列表如下:
CRuntimeClass* m_pDocClass; // class for creating new documents CRuntimeClass* m_pFrameClass; // class for creating new frames CRuntimeClass* m_pViewClass; // class for creating new views |
m_pDocClass | 表示文档类类型,在此例子中就是CMDITestDoc |
m_pFrameClass | 表示容纳View窗口的框架窗口类类型,此例中为CChildFrame |
m_pViewClass | 表示显示文档的View视类类型,此例中为CMDITestView |
一会还会看到CDocTemplate的创建文档、框架、视的过程,放在ProcessShellCommand中研究。
研究LoadFrame
让我们继续研究CFrameWnd::LoadFrame是怎么运作的。使用的方法是跟踪进入。。。
BOOL CMDIFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext){ // 调用基类 CFrameWnd 的 LoadFrame, pContext 在创建主窗口时 = NULL // pParentWnd = NULL 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.\n"); return TRUE;} |
BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext){ // only do this once ASSERT_VALID_IDR(nIDResource); // nIDResource = 128, IDR_MAINFRAME ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource); m_nIDHelp = nIDResource; // ID for help context (+HID_BASE_RESOURCE) CString strFullString; if (strFullString.LoadString(nIDResource)) // = “MDITest” AfxExtractSubString(m_strTitle, strFullString, 0); // 取得第一个子串 VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG)); // attempt to create the window // GetIconWndClass 会调用 virtual PreCreateWindow 函数,别处也会调用,从而 // 使得子类的PreCreateWindow 将被调用多次 LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource); CString strTitle = m_strTitle; // 调用 CFrameWnd::Create() 实际创建出窗口。 // 注意:在这里将给 CMainFrame 发送 WM_CREATE 等多个消息。触发 CMainFrame 的 // OnCreate 处理等。 if (!Create(lpszClass, strTitle, dwDefaultStyle, rectDefault, pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext)) { return FALSE; // will self destruct on failure normally } // save the default menu handle, 好像 CMDIFrameWnd 也保存了一次? ASSERT(m_hWnd != NULL); m_hMenuDefault = ::GetMenu(m_hWnd); // load accelerator resource LoadAccelTable(MAKEINTRESOURCE(nIDResource)); // WM_INITIALUPDATE 是 MFC 发明的消息,参见后面的说明。 if (pContext == NULL) // send initial update SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE); return TRUE;} |
WM_INITIALUPDATE This message is sent by the document template to all descendants of a frame window when it is safe for them to do their initial update. It maps to a call to CView::OnInitialUpdate but can be used in other CWnd-derived classes for other one-shot updating.
|
1、 注册窗口类(AfxDeferRegisterClass)
2、 实际创建窗口(Create)
3、 处理菜单、快捷键,发送WM_INITIALUPDATE消息给所有子窗口。实际将在CView中处理此消息。(例如:在ToolBar上面放一个FormView,可能就能收到这个消息并处利?)
至此,CMainFrame已经成功创建,菜单已经装载,工具条、状态行等已经在CMainFrame::OnCreate中创建。让我们接着研究第一个子窗口是怎么被创建出来的,该过程和CMainFrame::LoadFrame比起来就不那么直接了。
研究CWnd::ProcessShellCommand
第一个MDI子窗口是从这里面建立出来的,这实在是缺乏直观性。不过MFC就是这样,没办法。
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: // 忽略 case CCommandLineInfo::FilePrintTo: // 忽略 case CCommandLineInfo::FilePrint: case CCommandLineInfo::FileDDE: case CCommandLineInfo::AppRegister: case CCommandLineInfo::AppUnregister: } return bResult;} |
注意:实际进入到了AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL)之中。
AfxGetApp()实际返回了CMDITestApp的唯一实例,它从CWinApp – CWinThread – CCmdTarget – CObject 派生而来。我们没有重载OnCmdMsg,所以进入到CCmdTarget的OnCmdMsg处理中。为了研究,我们删减了一些代码。
BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo){ // 这里删减了一些代码 // determine the message number and code (packed into nCode) const AFX_MSGMAP* pMessageMap; const AFX_MSGMAP_ENTRY* lpEntry; UINT nMsg = 0; // 这里删减了一些代码,处理后 nMsg = WM_COMMAND // 为了简化,删减了一些断言等。以下循环用于查找处理此消息的入口。 for (pMessageMap = GetMessageMap(); pMessageMap->pfnGetBaseMap != NULL; pMessageMap = (*pMessageMap->pfnGetBaseMap)()) { lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID); if (lpEntry != NULL) { // 找到了消息处理项入口,分发此消息。 return _AfxDispatchCmdMsg(this, nID, nCode, lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo); } } return FALSE; // 未找到则不处理} |
AFX_STATIC BOOL AFXAPI _AfxDispatchCmdMsg(CCmdTarget* pTarget, UINT nID, int nCode, AFX_PMSG pfn, void* pExtra, UINT_PTR nSig, AFX_CMDHANDLERINFO* pHandlerInfo) // return TRUE to stop routing{ union MessageMapFunctions mmf; mmf.pfn = pfn; BOOL bResult = TRUE; // default is ok if (pHandlerInfo != NULL) { // just fill in the information, don't do it pHandlerInfo->pTarget = pTarget; pHandlerInfo->pmf = mmf.pfn; return TRUE; } switch (nSig) { case AfxSigCmd_v: // normal command or control notification ASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKED ASSERT(pExtra == NULL); (pTarget->*mmf.pfnCmd_v_v)(); // ß 实际调用 pTarget 指向的这个成员函数 break; // 下面还有大量的多种 AfxSigCmd_xxx,忽略掉它们。 default: // illegal ASSERT(FALSE); return 0; break; } return bResult;} |
void CWinApp::OnFileNew() { if (m_pDocManager != NULL) m_pDocManager->OnFileNew();} |
void CDocManager::OnFileNew() { if (m_templateList.IsEmpty()) // 提示没有模板并返回 CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead(); // 第一个 if (m_templateList.GetCount() > 1) // 弹出一个对话框(很难看的)提示用户选择一个文档模板 // 在这个例子里面,pTemplate 就是 CMDITestApp::InitInstance() 里面创建的那个模板 pTemplate->OpenDocumentFile(NULL);} |
> mfc71d.dll!CDocManager::OnFileNew() 行852 C++ mfc71d.dll!CWinApp::OnFileNew() 行25 C++ mfc71d.dll!_AfxDispatchCmdMsg(CCmdTarget * pTarget=0x0042cae8, unsigned int nID=57600, int nCode=0, void (void)* pfn=0x0041153c, void * pExtra=0x00000000, unsigned int nSig=53, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000) 行89 C++ mfc71d.dll!CCmdTarget::OnCmdMsg(unsigned int nID=57600, int nCode=0, void * pExtra=0x00000000, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000) 行396 + 0x27 C++ mfc71d.dll!CWinApp::ProcessShellCommand(CCommandLineInfo & rCmdInfo={...}) 行27 + 0x1e C++ MDITest.exe!CMDITestApp::InitInstance() 行101 + 0xc C++ |
CMultiDocTemplate::OpenDocumentFile 又是很多很多代码,让我们选择一些。
CDocument* CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName, BOOL bMakeVisible){ // 以下代码删减了验证、断言部分 CDocument* pDocument = CreateNewDocument(); // 创建文档对象 CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL); // 创建框架窗口 if (lpszPathName == NULL) { pDocument->OnNewDocument(); // 初始化文档 } else // 打开已有文档 InitialUpdateFrame(pFrame, pDocument, bMakeVisible); return pDocument;} |
CDocument* CDocTemplate::CreateNewDocument() { // default implementation constructs one from CRuntimeClass if (m_pDocClass == NULL) // 错误提示啦 // CRuntimeClass* m_pDocClass -> CreateObject 实例化文档类。 // 在此例子中既是 CMDITestDoc CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject(); AddDocument(pDocument); // 添加到模板里的文档列表,MultiDocTemplate 保存此一文档 return pDocument;} |
class CMDITestDoc : public CDocument {protected: // 仅从序列化创建 CMDITestDoc(); // 被保护的构造函数 DECLARE_DYNCREATE(CMDITestDoc) // 支持从 CRuntimeClass 信息中创建。 |
CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther) { // create a frame wired to the specified document CCreateContext context; // 这个 CreateContext 传递到 LoadFrame 中 context.m_pCurrentFrame = pOther; // 此例中 = NULL context.m_pCurrentDoc = pDoc; // = 刚才创建的文档 context.m_pNewViewClass = m_pViewClass; // 显示此文档的视类的类型 context.m_pNewDocTemplate = this; if (m_pFrameClass == NULL) // 提示错误并返回 // 利用 CRuntimeClass 信息创建框架窗口对象,此例中为 CChildFrame CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject(); // 这里,我们又看到了 LoadFrame , 参考前面的 LoadFrame 吧 // 在这里面,View 窗口也被产生出来。参考 TRACE 输出。 pFrame->LoadFrame(m_nIDResource, WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, // default frame styles NULL, &context); return pFrame;} |
BOOL CMDITestDoc::OnNewDocument() { TRACE("CMDITestDoc::OnNewDocument() entry\n"); if (!CDocument::OnNewDocument()) return FALSE; // TODO: 在此添加重新初始化代码 // (SDI 文档将重用该文档) return TRUE;} |
void CDocTemplate::InitialUpdateFrame(CFrameWnd* pFrame, CDocument* pDoc, BOOL bMakeVisible){ // just delagate to implementation in CFrameWnd pFrame->InitialUpdateFrame(pDoc, bMakeVisible);} |
// 主窗口已初始化,因此显示它并对其进行更新 pMainFrame->ShowWindow(m_nCmdShow); pMainFrame->UpdateWindow(); |
Before CMultiDocTemplate Before AddDocTemplateBefore new CMainFrameCMainFrame::CMainFrame()Before pMainFrame->LoadFrameCMainFrame::PreCreateWindow entry // 注意:PreCreateWindow 被两次调用CMainFrame::PreCreateWindow entryCMainFrame::OnCreate entry before CMDIFrameWnd::OnCreateCMainFrame::OnCreate before m_wndToolBar.CreateExCMainFrame::OnCreate before m_wndStatusBar.CreateBefore ParseCommandLineBefore ProcessShellCommandCMDITestDoc::CMDITestDoc() // 文档对象被创建CChildFrame::CChildFrame() // 子框架窗口被创建CChildFrame::PreCreateWindow entry CChildFrame::PreCreateWindow entryCChildFrame::PreCreateWindow entryCMDITestView::CMDITestView() entry // 子框架窗口的 OnCreate 中创建了 View 窗口CMDITestView::PreCreateWindow entryCMDITestDoc::OnNewDocument() entryBefore pMainFrame->ShowWindowBefore pMainFrame->UpdateWindow // 退出时的 TRACECMDITestView::~CMDITestView()CChildFrame::~CChildFrame()CMDITestDoc::~CMDITestDoc()CMainFrame::~CMainFrame() |
相关文章推荐
- 温故而知新,学习MFC框架如何创建的过程
- 学习MFC框架如何创建的过程
- 温故而知新,学习MFC框架如何创建的过程
- 温故而知新,学习MFC框架如何创建的过程
- 温故而知新,学习MFC框架如何创建的过程
- 学习MFC框架如何创建的过程
- 温故而知新,学习MFC框架如何创建的过程
- 学习MFC框架如何创建的过程
- 学习MFC框架如何创建的过程
- 学习MFC框架如何创建的过程
- 温故而知新,学习MFC框架如何创建的过程
- 温故而知新,学习MFC框架如何创建的过程
- 温故而知新,学习MFC框架如何创建的过程
- 温故而知新,学习MFC框架如何创建的过程
- mfc学习之主框架窗口创建过程
- MFC框架创建过程--一个颇为标准的模板
- MFC框架的创建过程
- MFC中如何安全的创建和关闭线程
- MFC如何在VS2008中创建弹出式菜单?
- MFC应用程序中处理消息的顺序,创建窗口的过程关闭窗口的顺序(非模态窗口),打开模式对话框的函数调用顺序