MFCcan自定义义消息函数时提示can't find the definition(implementation) of this function

MFC技术内幕系列之(二)---MFC文档视图结构内幕 - mfc-c++ - ITkeyowrd
MFC技术内幕系列之(二)---MFC文档视图结构内幕
////////////////////////////////////////////////////////////////////////////////////
/********* 文章系列:MFC技术内幕系列******
15:54 280人阅读
//////////////////////////////////////////////////////////////////////////////////// &&&&&&&&&&&&&&&&&&&& /********* 文章系列:MFC技术内幕系列***********/ &&&&&&&&&&&&&&&&&&&& /************MFC技术内幕系列之(二)***********/ &&&&&&&&&&&&&&&&&&&& /****&&& 文章题目:MFC文档视图结构内幕&&&&&&&&& *****/ &&&&&&&&&&&&&&&&&&&& /*&&&&&&&&&&&&&&&&&&&&&&&&&& Copyright(c)2002 bigwhite&&&&&&&&&&&& */ &&&&&&&&&&&&&&&&&&&& /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& All rights Reserved&&&&&&&&&&&&&&&&&& */ &&&&&&&&&&&&&&&&&&&& /*********关键字:MFC,文档视图结构************/ &&&&&&&&&&&&&&&&&&&& /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 时间:&&&&&&&&&&&&&&&&&& */ &&&&&&&&&&&&&&&&&&&& /*&&&& 注释:本文所涉及的程序源代码均在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* pDocT &pDocTemplate = new CMultiDocTemplate(IDR_MyTYPE, & RUNTIME_CLASS(CMyDoc), & RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架 & RUNTIME_CLASS(CMyView)); &AddDocTemplate(pDocTemplate); &// 创建主 MDI 框架窗口 &CMainFrame* pMainFrame = new CMainF &if (!pMainFrame-&LoadFrame(IDR_MAINFRAME)) & return FALSE; &m_pMainWnd = pMainF &// 仅当具有后缀时才调用 DragAcceptFiles &//& 在 MDI 应用程序中,这应在设置 m_pMainWnd 之后立即发生 &// 分析标准外壳命令、DDE、打开文件操作的命令行 &CCommandLineInfo cmdI &ParseCommandLine(cmdInfo); &// 调度在命令行中指定的命令。如果 &// 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。 &if (!ProcessShellCommand(cmdInfo)) & return FALSE; &// 主窗口已初始化,因此显示它并对其进行更新 &pMainFrame-&ShowWindow(m_nCmdShow); &pMainFrame-&UpdateWindow(); &return TRUE; &}
&&&&&&&&&&&&&&&&&&&&&& //////////////////////////////////////////// &&&&&&&&&&&&&&&&&&&&&& /*&&&& 2.初始化文档模板&&&&&& */ &&&&&&&&&&&&&&&&&&&&&& //////////////////////////////////////////// 分析以下代码: &&&&&&& CMultiDocTemplate* pDocT &pDocTemplate = new CMultiDocTemplate(IDR_MyTYPE, & RUNTIME_CLASS(CMyDoc), & RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架 & RUNTIME_CLASS(CMyView)); &&& 应用程序首先实例化一个CMultiDocTemplate对象,此过程也是对CMultiDocTemplate类数据成员的初始化过程。按调用次序我列出了以下源代码: &&& 注释1: CDocTemplate构造函数定义在:../Visual Studio.NET/vc7/atlmfc/src/mfc/doctempl.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 = nIDR &...// &&&&&&& m_pDocClass = pDocC &m_pFrameClass = pFrameC &m_pViewClass = pViewC &m_pOleFrameClass = NULL; &m_pOleViewClass = NULL; &&&&&&& ...// &} & 以上为CMultiDocTemplate类的基类CDocTemplate构造函数的部分源代码,该函数初始化了四个重要的成员 m_nIDResource,m_pDocClass,m_pFrameClass和m_pViewClass。 && 注释2: CMultiDocTemplate构造函数定义在:../Visual Studio.NET/vc7/atlmfc/src/mfc/docmulti.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.NET/vc7/atlmfc/include/afx.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.NET/vc7/atlmfc/src/mfc/appui2.cpp void CWinApp::AddDocTemplate(CDocTemplate* pTemplate)//将CMultiDocTemplate* pDocTemplate {&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //传给pTemplate &if (m_pDocManager == NULL) & m_pDocManager = new CDocM &m_pDocManager-&AddDocTemplate(pTemplate); } 注释2: 以下函数定义在:../Visual Studio.NET/vc7/atlmfc/src/mfc/docmgr.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.NET/vc7/atlmfc/src/mfc/winmdi.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./n&); &return TRUE; &} & CMDIFrameWnd::LoadFrame调用了其基类CFrameWnd的LoadFrame,并将参数原封不动的传给它。
& 注释2: 以下函数定义在:../Visual Studio.NET/vc7/atlmfc/src/mfc/winfrm.cpp &BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, &CWnd* pParentWnd, CCreateContext* pContext)&&&&&&&&& //部分源代码 &{ &...// &CString strFullS &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_strT &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.NET/vc7/atlmfc/src/mfc/afximpl.h &&&&&& #define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass) &&
&& 注释2: 以下函数定义在:../Visual Studio.NET/vc7/atlmfc/src/mfc/wincore.cpp &
& BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)//部分源代码 & { &// mask off all classes that are already registered &AFX_MODULE_STATE* pModuleState = AfxGetModuleState(); &fToRegister &= ~pModuleState-&m_fRegisteredC &if (fToRegister == 0) & return TRUE;
&LONG fRegisteredClasses = 0;
&// common initialization &WNDCLASS &memset(&wndcls, 0, sizeof(WNDCLASS));&& // start with NULL defaults &wndcls.lpfnWndProc = DefWindowP &wndcls.hInstance = AfxGetInstanceHandle(); &wndcls.hCursor = afxData.hcurA
&INITCOMMONCONTROLSEX &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 = _afxW & 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) == fToR && } && 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.NET/vc7/atlmfc/src/mfc/winfrm.cpp &
& LPCTSTR CFrameWnd::GetIconWndClass(DWORD dwDefaultStyle, UINT nIDResource)//部分源代码 &{ &...// &HICON hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(nIDResource)); &if (hIcon != NULL) &{ & CREATESTRUCT & memset(&cs, 0, sizeof(CREATESTRUCT)); & cs.style = dwDefaultS & PreCreateWindow(cs); && // will fill lpszClassName with default WNDCLASS name && // ignore instance handle from PreCreateWindow.
& WNDCLASS & 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 = _afxWndMDIF &} &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.NET/vc7/atlmfc/src/mfc/winfrm.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./n&); && PostNcDestroy();&&&&&&&&&&& // perhaps delete the C++ object && return FALSE; & } &}
&m_strTitle = lpszWindowN&&& // 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./n&); & if (hMenu != NULL) && DestroyMenu(hMenu); & return FALSE; &}
&return TRUE; & }
& 简单地说CFrameWnd::Create函数调用了基类的CWnd::CreateEx; & 注释2: 以下函数定义在:../Visual Studio.NET/vc7/atlmfc/src/mfc/wincore.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.dwExStyle = dwExS &cs.lpszClass = lpszClassN &cs.lpszName = lpszWindowN &cs.style = dwS &cs.x = &cs.y = &cs.cx = nW &cs.cy = nH &cs.hwndParent = hWndP &cs.hMenu = nIDorHM &cs.hInstance = AfxGetInstanceHandle(); &cs.lpCreateParams = lpP
&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 = _afxWndMDIF &} &return TRUE; &} && CWnd::CreateEx调用了CMainFrame::PreCreateWindow,但此次AfxDeferRegisterClass将不会被调用。也就是我上面所说的“过门不入”。
&&& CWnd::CreateEx函数还调用了AfxHookWindowCreate(this);后者是干什么的呢?其实它与消息映射和命令传递有关,我将在MFC技术内幕系列之(四)--《MFC消息映射与消息传递内幕》一文中详解。 &&
&& CWnd::CreateEx调用Win32API ::CreateWindowEx函数(传统的Win32API程序员一定不陌生这个函数), 就这样主框架窗口创建结束。
&&&&&&&&&&&&&&&&&&&&&& ////////////////////////////////////////////// &&&&&&&&&&&&&&&&&&&&&& /*&&&& 5.标准外壳命令解析&&&&& */ &&&&&&&&&&&&&&&&&&&&&& /////////////////////////////////////////////// &&& MFC向导制作的标准MDI应用程序启动时,应用程序会自动启动一个子窗口框架(实际上是一套文档模板),这是为何呢?下面我将详细讲解一下这个创建过程. &&& 其实这一过程也是在CMyWinApp::InitInstance()函数中完成的,看看下面代码:
&&&&&&& CCommandLineInfo cmdI &ParseCommandLine(cmdInfo); &&&&&&& if (!ProcessShellCommand(cmdInfo)) & return FALSE; && 函数首先实例化一个CCommandLineInfo类对象cmdInfo,让我们看看CCommandLineInfo是个什么东东?&
&//in afxwin.h &class CCommandLineInfo : public CObject//部分源代码 { &public: &// Sets default values &CCommandLineInfo(); &&&&&&& ...// &BOOL m_bShowS &BOOL m_bRunE &BOOL m_bRunA &enum { FileNew, FileOpen, FilePrint, FilePrintTo, FileDDE, AppRegister, & AppUnregister, FileNothing = -1 } m_nShellC
&// not valid for FileNew &CString m_strFileN
&// valid only for FilePrintTo &CString m_strPrinterN &CString m_strDriverN &CString m_strPortN
&~CCommandLineInfo(); &&&&&&& // Implementation &&&&&&& ...// &};
&再让我们来看看它的构造函数的实现:
//in appcore.cpp &CCommandLineInfo::CCommandLineInfo() &{ &m_bShowSplash = TRUE; &m_bRunEmbedded = FALSE; &m_bRunAutomated = FALSE; &m_nShellCommand = FileN &} &m_nShellCommand = FileN这一句对我们最重要;至于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; &
& // If we've been asked to open a file, call OpenDocumentFile()
&case CCommandLineInfo::FileOpen: & if (!OpenDocumentFile(rCmdInfo.m_strFileName)) && bResult = FALSE; & &&&&&&& case CCommandLineInfo::FilePrintTo: &case CCommandLineInfo::FilePrint: & ...// &&&&&&& case CCommandLineInfo::FileDDE: & ...// &case CCommandLineInfo::AppRegister: & ...// &&&&&&& case CCommandLineInfo::AppUnregister: & ...// &&&&&&&& } &return bR } && 挖掘源代码的确是了解MFC运行内幕的最好手段,大家一看源代码便知道如之何了。CCommandLineInfo构造函数中m_nShellCommand = FileN所以在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_pSelectedT & else &&&&&& // 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_bAutoD &pDocument-&m_bAutoDelete = FALSE;&& // don't destroy if something goes wrong &CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL); &pDocument-&m_bAutoDelete = bAutoD &...//
&if (lpszPathName == NULL) &{ & // create a new document - with default document name & SetDefaultTitle(pDocument);
& // avoid creating temporary compound file when starting up invisible & if (!bMakeVisible) && pDocument-&m_bEmbedded = TRUE;
& if (!pDocument-&OnNewDocument()) & { && // user has be alerted to what failed in OnNewDocument && TRACE(traceAppMsg, 0, &CDocument::OnNewDocument returned FALSE./n&); && pFrame-&DestroyWindow(); && return NULL; & }
& // it worked, now bump untitled count & m_nUntitledCount++; &} &else &{ & // open an existing document & CWaitC & if (!pDocument-&OnOpenDocument(lpszPathName)) & { && // user has be alerted to what failed in OnOpenDocument && TRACE(traceAppMsg, 0, &CDocument::OnOpenDocument returned FALSE./n&); && pFrame-&DestroyWindow(); && return NULL; & } & pDocument-&SetPathName(lpszPathName); &}
&InitialUpdateFrame(pFrame, pDocument, bMakeVisible); &return pD } &&&&&&&&&&&&&&&&&&&&&& ////////////////////////////////////////////// &&&&&&&&&&&&&&&&&&&&&& /*&&&& 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 pD &} & 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 &CCreateC &context.m_pCurrentFrame = pO &context.m_pCurrentDoc = pD &context.m_pNewViewClass = m_pViewC &context.m_pNewDocTemplate = &&&&&&& ...// &CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass-&CreateObject(); &if (pFrame == NULL) &{ & TRACE(traceAppMsg, 0, &Warning: Dynamic create of frame %hs failed./n&, && 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./n&); & // frame will be deleted in PostNcDestroy cleanup & return NULL; &}
&// it worked ! &return pF } && 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_pNewViewC // runtime class of view to create or NULL &CDocument* m_pCurrentD
&// for creating MDI children (CMDIChildWnd::LoadFrame) &CDocTemplate* m_pNewDocT
&// for sharing view/frame state from the original view/frame &CView* m_pLastV &CFrameWnd* m_pCurrentF // Implementation &CCreateContext(); }; & 而在CDocTemplate::CreateNewFrame中初始化了该结构如下: &&&&& CCreateC &context.m_pCurrentFrame = pO &context.m_pCurrentDoc = pD &context.m_pNewViewClass = m_pViewC &context.m_pNewDocTemplate = &&&&&& context.m_pNewViewClass = m_pViewC//关键的成员 & 下面看看这个创建的具体过程: &&&&& 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-&lpCreateP &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./n&); & 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./n&, && 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./n&); & return NULL;&&&&&&& // can't continue without a view &}
&...// &return pV } & CWnd* pView = (CWnd*)pContext-&m_pNewViewClass-&CreateObject();核心函数终于出现了。子视图动态生成完毕。
&&&&&&&&&&&&&&&&&&&&&& ///////////////////////////////////////// &&&&&&&&&&&&&&&&&&&&&& /*&&&&&&&& 7.收尾工作&&&&&&&&& */ &&&&&&&&&&&&&&&&&&&&&& ///////////////////////////////////////// && 至此,一套完整的Document/ChildFrame/View结构生成,此“三口组”共属同一套文档模板,如果你要定义另一套不同的文档模档需再定义另一组不同“三口组”(ChildFrame可以使用相同的)。并调用AddDocTemplate将该文档模板加入到应用程序的文档模板列表。比如:
&&&&&& CMultiDocTemplate* pOtherDocT &pOtherDocTemplate = new CMultiDocTemplate(IDR_MyOtherTYPE, & RUNTIME_CLASS(CMyOtherDoc), & RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架 & RUNTIME_CLASS(CMyOtherView)); &AddDocTemplate(pOtherDocTemplate);
“三口组”生成后程序调用ShowWindow,UpdateWindow将应用程序的主窗口展现在你眼前。
& 注释:当你在File菜单中选择new或在工具栏中单击“新建”时,应用程序将选择当前默认的文档模板并以它为基础动态生成 Document/ChildFrame/View“三口组”,其生成过程与我上述讲的一般不二。 &&&&&&&&&&&&&&&&&&&&&& //////////////////////////////////////// &&&&&&&&&&&&&&&&&&&&&& /*&&&&&& 8. &下集预告&&&&&&&& */ &&&&&&&&&&&&&&&&&&&&&& ////////////////////////////////////////
&& MFC技术内幕系列之(三)---《MFC执行期类型识别与动态创建技术内幕》,
//////////////////////////////////////////////////////////////////////////////////// &&&&&&&&&&&&&&&&&&&& /********* 文章系列:MFC技术内幕系列***********/ &&&&&&&&&&&&&&&&&&&& /************MFC技术内幕系列之(二)***********/ &&&&&&&&&&&&&&&&&&&& /****&&& 文章题目:MFC文档视图结构内幕&&&&&&&&& *****/ &&&&&&&&&&&&&&&&&&&& /*&&&&&&&&&&&&&&&&&&&&&&&&&& Copyright(c)2002 bigwhite&&&&&&&&&&&& */ &&&&&&&&&&&&&&&&&&&& /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& All rights Reserved&&&&&&&&&&&&&&&&&& */ &&&&&&&&&&&&&&&&&&&& /*********关键字:MFC,文档视图结构************/ &&&&&&&&&&&&&&&&&&&& /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 时间:&&&&&&&&&&&&&&&&&& */ &&&&&&&&&&&&&&&&&&&& /*&&&& 注释:本文所涉及的程序源代码均在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* pDocT &pDocTemplate = new CMultiDocTemplate(IDR_MyTYPE, & RUNTIME_CLASS(CMyDoc), & RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架 & RUNTIME_CLASS(CMyView)); &AddDocTemplate(pDocTemplate); &// 创建主 MDI 框架窗口 &CMainFrame* pMainFrame = new CMainF &if (!pMainFrame-&LoadFrame(IDR_MAINFRAME)) & return FALSE; &m_pMainWnd = pMainF &// 仅当具有后缀时才调用 DragAcceptFiles &//& 在 MDI 应用程序中,这应在设置 m_pMainWnd 之后立即发生 &// 分析标准外壳命令、DDE、打开文件操作的命令行 &CCommandLineInfo cmdI &ParseCommandLine(cmdInfo); &// 调度在命令行中指定的命令。如果 &// 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。 &if (!ProcessShellCommand(cmdInfo)) & return FALSE; &// 主窗口已初始化,因此显示它并对其进行更新 &pMainFrame-&ShowWindow(m_nCmdShow); &pMainFrame-&UpdateWindow(); &return TRUE; &}
&&&&&&&&&&&&&&&&&&&&&& //////////////////////////////////////////// &&&&&&&&&&&&&&&&&&&&&& /*&&&& 2.初始化文档模板&&&&&& */ &&&&&&&&&&&&&&&&&&&&&& //////////////////////////////////////////// 分析以下代码: &&&&&&& CMultiDocTemplate* pDocT &pDocTemplate = new CMultiDocTemplate(IDR_MyTYPE, & RUNTIME_CLASS(CMyDoc), & RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架 & RUNTIME_CLASS(CMyView)); &&& 应用程序首先实例化一个CMultiDocTemplate对象,此过程也是对CMultiDocTemplate类数据成员的初始化过程。按调用次序我列出了以下源代码: &&& 注释1: CDocTemplate构造函数定义在:../Visual Studio.NET/vc7/atlmfc/src/mfc/doctempl.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 = nIDR &...// &&&&&&& m_pDocClass = pDocC &m_pFrameClass = pFrameC &m_pViewClass = pViewC &m_pOleFrameClass = NULL; &m_pOleViewClass = NULL; &&&&&&& ...// &} & 以上为CMultiDocTemplate类的基类CDocTemplate构造函数的部分源代码,该函数初始化了四个重要的成员 m_nIDResource,m_pDocClass,m_pFrameClass和m_pViewClass。 && 注释2: CMultiDocTemplate构造函数定义在:../Visual Studio.NET/vc7/atlmfc/src/mfc/docmulti.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.NET/vc7/atlmfc/include/afx.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.NET/vc7/atlmfc/src/mfc/appui2.cpp void CWinApp::AddDocTemplate(CDocTemplate* pTemplate)//将CMultiDocTemplate* pDocTemplate {&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //传给pTemplate &if (m_pDocManager == NULL) & m_pDocManager = new CDocM &m_pDocManager-&AddDocTemplate(pTemplate); } 注释2: 以下函数定义在:../Visual Studio.NET/vc7/atlmfc/src/mfc/docmgr.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.NET/vc7/atlmfc/src/mfc/winmdi.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./n&); &return TRUE; &} & CMDIFrameWnd::LoadFrame调用了其基类CFrameWnd的LoadFrame,并将参数原封不动的传给它。
& 注释2: 以下函数定义在:../Visual Studio.NET/vc7/atlmfc/src/mfc/winfrm.cpp &BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, &CWnd* pParentWnd, CCreateContext* pContext)&&&&&&&&& //部分源代码 &{ &...// &CString strFullS &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_strT &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.NET/vc7/atlmfc/src/mfc/afximpl.h &&&&&& #define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass) &&
&& 注释2: 以下函数定义在:../Visual Studio.NET/vc7/atlmfc/src/mfc/wincore.cpp &
& BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)//部分源代码 & { &// mask off all classes that are already registered &AFX_MODULE_STATE* pModuleState = AfxGetModuleState(); &fToRegister &= ~pModuleState-&m_fRegisteredC &if (fToRegister == 0) & return TRUE;
&LONG fRegisteredClasses = 0;
&// common initialization &WNDCLASS &memset(&wndcls, 0, sizeof(WNDCLASS));&& // start with NULL defaults &wndcls.lpfnWndProc = DefWindowP &wndcls.hInstance = AfxGetInstanceHandle(); &wndcls.hCursor = afxData.hcurA
&INITCOMMONCONTROLSEX &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 = _afxW & 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) == fToR && } && 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.NET/vc7/atlmfc/src/mfc/winfrm.cpp &
& LPCTSTR CFrameWnd::GetIconWndClass(DWORD dwDefaultStyle, UINT nIDResource)//部分源代码 &{ &...// &HICON hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(nIDResource)); &if (hIcon != NULL) &{ & CREATESTRUCT & memset(&cs, 0, sizeof(CREATESTRUCT)); & cs.style = dwDefaultS & PreCreateWindow(cs); && // will fill lpszClassName with default WNDCLASS name && // ignore instance handle from PreCreateWindow.
& WNDCLASS & 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 = _afxWndMDIF &} &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.NET/vc7/atlmfc/src/mfc/winfrm.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./n&); && PostNcDestroy();&&&&&&&&&&& // perhaps delete the C++ object && return FALSE; & } &}
&m_strTitle = lpszWindowN&&& // 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./n&); & if (hMenu != NULL) && DestroyMenu(hMenu); & return FALSE; &}
&return TRUE; & }
& 简单地说CFrameWnd::Create函数调用了基类的CWnd::CreateEx; & 注释2: 以下函数定义在:../Visual Studio.NET/vc7/atlmfc/src/mfc/wincore.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.dwExStyle = dwExS &cs.lpszClass = lpszClassN &cs.lpszName = lpszWindowN &cs.style = dwS &cs.x = &cs.y = &cs.cx = nW &cs.cy = nH &cs.hwndParent = hWndP &cs.hMenu = nIDorHM &cs.hInstance = AfxGetInstanceHandle(); &cs.lpCreateParams = lpP
&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 = _afxWndMDIF &} &return TRUE; &} && CWnd::CreateEx调用了CMainFrame::PreCreateWindow,但此次AfxDeferRegisterClass将不会被调用。也就是我上面所说的“过门不入”。
&&& CWnd::CreateEx函数还调用了AfxHookWindowCreate(this);后者是干什么的呢?其实它与消息映射和命令传递有关,我将在MFC技术内幕系列之(四)--《MFC消息映射与消息传递内幕》一文中详解。 &&
&& CWnd::CreateEx调用Win32API ::CreateWindowEx函数(传统的Win32API程序员一定不陌生这个函数), 就这样主框架窗口创建结束。
&&&&&&&&&&&&&&&&&&&&&& ////////////////////////////////////////////// &&&&&&&&&&&&&&&&&&&&&& /*&&&& 5.标准外壳命令解析&&&&& */ &&&&&&&&&&&&&&&&&&&&&& /////////////////////////////////////////////// &&& MFC向导制作的标准MDI应用程序启动时,应用程序会自动启动一个子窗口框架(实际上是一套文档模板),这是为何呢?下面我将详细讲解一下这个创建过程. &&& 其实这一过程也是在CMyWinApp::InitInstance()函数中完成的,看看下面代码:
&&&&&&& CCommandLineInfo cmdI &ParseCommandLine(cmdInfo); &&&&&&& if (!ProcessShellCommand(cmdInfo)) & return FALSE; && 函数首先实例化一个CCommandLineInfo类对象cmdInfo,让我们看看CCommandLineInfo是个什么东东?&
&//in afxwin.h &class CCommandLineInfo : public CObject//部分源代码 { &public: &// Sets default values &CCommandLineInfo(); &&&&&&& ...// &BOOL m_bShowS &BOOL m_bRunE &BOOL m_bRunA &enum { FileNew, FileOpen, FilePrint, FilePrintTo, FileDDE, AppRegister, & AppUnregister, FileNothing = -1 } m_nShellC
&// not valid for FileNew &CString m_strFileN
&// valid only for FilePrintTo &CString m_strPrinterN &CString m_strDriverN &CString m_strPortN
&~CCommandLineInfo(); &&&&&&& // Implementation &&&&&&& ...// &};
&再让我们来看看它的构造函数的实现:
//in appcore.cpp &CCommandLineInfo::CCommandLineInfo() &{ &m_bShowSplash = TRUE; &m_bRunEmbedded = FALSE; &m_bRunAutomated = FALSE; &m_nShellCommand = FileN &} &m_nShellCommand = FileN这一句对我们最重要;至于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; &
& // If we've been asked to open a file, call OpenDocumentFile()
&case CCommandLineInfo::FileOpen: & if (!OpenDocumentFile(rCmdInfo.m_strFileName)) && bResult = FALSE; & &&&&&&& case CCommandLineInfo::FilePrintTo: &case CCommandLineInfo::FilePrint: & ...// &&&&&&& case CCommandLineInfo::FileDDE: & ...// &case CCommandLineInfo::AppRegister: & ...// &&&&&&& case CCommandLineInfo::AppUnregister: & ...// &&&&&&&& } &return bR } && 挖掘源代码的确是了解MFC运行内幕的最好手段,大家一看源代码便知道如之何了。CCommandLineInfo构造函数中m_nShellCommand = FileN所以在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_pSelectedT & else &&&&&& // 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_bAutoD &pDocument-&m_bAutoDelete = FALSE;&& // don't destroy if something goes wrong &CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL); &pDocument-&m_bAutoDelete = bAutoD &...//
&if (lpszPathName == NULL) &{ & // create a new document - with default document name & SetDefaultTitle(pDocument);
& // avoid creating temporary compound file when starting up invisible & if (!bMakeVisible) && pDocument-&m_bEmbedded = TRUE;
& if (!pDocument-&OnNewDocument()) & { && // user has be alerted to what failed in OnNewDocument && TRACE(traceAppMsg, 0, &CDocument::OnNewDocument returned FALSE./n&); && pFrame-&DestroyWindow(); && return NULL; & }
& // it worked, now bump untitled count & m_nUntitledCount++; &} &else &{ & // open an existing document & CWaitC & if (!pDocument-&OnOpenDocument(lpszPathName)) & { && // user has be alerted to what failed in OnOpenDocument && TRACE(traceAppMsg, 0, &CDocument::OnOpenDocument returned FALSE./n&); && pFrame-&DestroyWindow(); && return NULL; & } & pDocument-&SetPathName(lpszPathName); &}
&InitialUpdateFrame(pFrame, pDocument, bMakeVisible); &return pD } &&&&&&&&&&&&&&&&&&&&&& ////////////////////////////////////////////// &&&&&&&&&&&&&&&&&&&&&& /*&&&& 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 pD &} & 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 &CCreateC &context.m_pCurrentFrame = pO &context.m_pCurrentDoc = pD &context.m_pNewViewClass = m_pViewC &context.m_pNewDocTemplate = &&&&&&& ...// &CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass-&CreateObject(); &if (pFrame == NULL) &{ & TRACE(traceAppMsg, 0, &Warning: Dynamic create of frame %hs failed./n&, && 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./n&); & // frame will be deleted in PostNcDestroy cleanup & return NULL; &}
&// it worked ! &return pF } && 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_pNewViewC // runtime class of view to create or NULL &CDocument* m_pCurrentD
&// for creating MDI children (CMDIChildWnd::LoadFrame) &CDocTemplate* m_pNewDocT
&// for sharing view/frame state from the original view/frame &CView* m_pLastV &CFrameWnd* m_pCurrentF // Implementation &CCreateContext(); }; & 而在CDocTemplate::CreateNewFrame中初始化了该结构如下: &&&&& CCreateC &context.m_pCurrentFrame = pO &context.m_pCurrentDoc = pD &context.m_pNewViewClass = m_pViewC &context.m_pNewDocTemplate = &&&&&& context.m_pNewViewClass = m_pViewC//关键的成员 & 下面看看这个创建的具体过程: &&&&& 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-&lpCreateP &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./n&); & 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./n&, && 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./n&); & return NULL;&&&&&&& // can't continue without a view &}
&...// &return pV } & CWnd* pView = (CWnd*)pContext-&m_pNewViewClass-&CreateObject();核心函数终于出现了。子视图动态生成完毕。
&&&&&&&&&&&&&&&&&&&&&& ///////////////////////////////////////// &&&&&&&&&&&&&&&&&&&&&& /*&&&&&&&& 7.收尾工作&&&&&&&&& */ &&&&&&&&&&&&&&&&&&&&&& ///////////////////////////////////////// && 至此,一套完整的Document/ChildFrame/View结构生成,此“三口组”共属同一套文档模板,如果你要定义另一套不同的文档模档需再定义另一组不同“三口组”(ChildFrame可以使用相同的)。并调用AddDocTemplate将该文档模板加入到应用程序的文档模板列表。比如:
&&&&&& CMultiDocTemplate* pOtherDocT &pOtherDocTemplate = new CMultiDocTemplate(IDR_MyOtherTYPE, & RUNTIME_CLASS(CMyOtherDoc), & RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架 & RUNTIME_CLASS(CMyOtherView)); &AddDocTemplate(pOtherDocTemplate);
“三口组”生成后程序调用ShowWindow,UpdateWindow将应用程序的主窗口展现在你眼前。
& 注释:当你在File菜单中选择new或在工具栏中单击“新建”时,应用程序将选择当前默认的文档模板并以它为基础动态生成 Document/ChildFrame/View“三口组”,其生成过程与我上述讲的一般不二。 &&&&&&&&&&&&&&&&&&&&&& //////////////////////////////////////// &&&&&&&&&&&&&&&&&&&&&& /*&&&&&& 8. &下集预告&&&&&&&& */ &&&&&&&&&&&&&&&&&&&&&& ////////////////////////////////////////
&& MFC技术内幕系列之(三)---《MFC执行期类型识别与动态创建技术内幕》
推荐:引言:
侯捷老师在他那本著名的&深入浅出MFC&(第二版)的第六章中对比着传统的Win32API编程,详细讲解了MFC应用程序“生死因果”,而且侯捷老师还在&深入
15:54 280人阅读 评论(0)收藏 举报////////////////////////////////////////////////////////////////////////////////////
/********* 文章
相关阅读排行
相关内容推荐
请激活账号
为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。
您的注册邮箱:
如果您没有收到激活邮件,请注意检查垃圾箱。

我要回帖

更多关于 自定义函数 的文章

 

随机推荐