MFC文档视图结构内幕_编程

/* 1.回顾"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.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 = 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.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)) 源代码中这样作是为了将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 cdocmanager; 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 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.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_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.net/vc7/atlmfc/src/mfc/winfrm.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.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 = 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./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; 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);后者是干什么的呢?其实它与消息映射和命令传递有关. 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 weve 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; // dont 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./n"); 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./n"); 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对象。 ////////////////////////////////////////////////// /* 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./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 couldnt create a frame./n"); // frame will be deleted in postncdestroy cleanup return null; } // it worked ! return pframe; } cframewnd* pframe = (cframewnd*)m_pframeclass->createobject();这一句就是动态产生的核心,它借助于cruntimeclass动态生成一个cdocument对象。之后函数调用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./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; // cant 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文档视图结构内幕_编程)