/* 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文档视图结构内幕_编程
最新推荐文章于 2025-08-17 13:03:11 发布