持之以恒
分类:
2009-08-26 21:32:53
//1.文档模板将用作文档、框架窗口和视图之间的连接
cmultidoctemplate* pdoctemplate;
pdoctemplate = new cmultidoctemplate(idr_mytype,
runtime_class(cmydoc),
runtime_class(cchildframe),
runtime_class(cmyview));
//关于RunTime_Class宏的问题以及MFC中使用RTTI技术我将在下篇文章中学习
adddoctemplate(pdoctemplate);
//2.分析标准外壳命令、dde、打开文件操作的命令行
ccommandlineinfo cmdinfo;
parsecommandline(cmdinfo);
// 调度在命令行中指定的命令。如果用 /regserver、/register、/unregserver 或 /unregister 启动应用程序,则返回 false。
if (!processshellcommand(cmdinfo))
return false;
//3.主窗口已初始化,因此显示它并对其进行更新
pmainframe->showwindow(m_ncmdshow);
pmainframe->updatewindow();
return true;
}
2.初始化文档模板
分析以下代码:
cmultidoctemplate* pdoctemplate;
pdoctemplate = new cmultidoctemplate(idr_mytype,
runtime_class(cmydoc),
runtime_class(cchildframe),
runtime_class(cmyview));
应用程序首先实例化一个cmultidoctemplate对象,此过程也是对cmultidoctemplate类数据成员的初始化过程。按调用次序列出了以下源代码:
cdoctemplate构造函数定义在: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。
cmultidoctemplate构造函数定义在: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;
if (!cdocmanager::bstaticinit)
loadtemplate();
}
看完以上代码后,来回过头看一看InitInstance函数将什么参数值传给了cmultidoctemplate的构造函数。
原来是一些runtime_class宏。以下是runtime_class宏的定义:
以下的宏定义在: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类对象进行管理。以下操作就是为了完成此工作。
以下函数定义在:src\mfc\appui2.cpp
void cwinapp::adddoctemplate(cdoctemplate* ptemplate)
{
if (m_pdocmanager == null)
m_pdocmanager = new cdocmanager;
m_pdocmanager->adddoctemplate(ptemplate);
}
以下函数定义在: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);
}
}
4.创建程序主框架窗口
应用程序实例化了一个cmainframe类对象,并调用loadframe函数加载窗口资源创建主框架窗口。以下是创建主框架窗口的流程。
创建窗口的主要代码是:pmainframe->loadframe(idr_mainframe); loadframe函数是mfc包装了窗口创
建过程的函数,在后面动态创建child窗口时,它还将披挂上阵(但稍有不同)(注意其实这一部分使我们关注的重点,不过现在要先熟悉一下相应的流程)。下面是它的源代码,让我们仔细分析一下:
以下函数定义在:src\mfc\winmdi.cpp
bool cmdiframewnd::loadframe(uint nidresource, dword dwdefaultstyle,cwnd* pparentwnd, ccreatecontext* pcontext)
{
if (!cframewnd::loadframe(nidresource, dwdefaultstyle,
pparentwnd, pcontext))
return false;
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,并将参数原封不动的传给它。
以下函数定义在: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);
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: 以下宏定义在:\src\mfc\afximpl.h
#define afxdeferregisterclass(fclass) afxenddeferregisterclass(fclass)
以下函数定义在: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));
wndcls.lpfnwndproc = defwindowproc;
wndcls.hinstance = afxgetinstancehandle();
wndcls.hcursor = afxdata.hcurarrow;
initcommoncontrolsex init;
init.dwsize = sizeof(init);
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;
}
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);
以下函数定义在: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;
cs.dwexstyle=~ws_ex_clientedge;
return true;
}
cmainframe::precreatewindow返回后,geticonwndclass函数调用getclassinfo函数重新收集cs信息
(此时的信息已是更改后的了),并调用afxregisterwndclass函数重新注册该窗口类(此窗口类为该应用程序的正式窗口类)。到此为止窗口
类注册完毕,以后程序还会调用一次cmainframe::precreatewindow,不过那此只是"过门不入"而已。
4.2主框架窗口创建开始
开始进入创建框架窗口的实质阶段。看loadframe函数做了什么?原来它调用了create函数。
以下函数定义在: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
hinstance hinst = afxfindresourcehandle(lpszmenuname, rt_menu);
if ((hmenu = ::loadmenu(hinst, lpszmenuname)) == null)
{
postncdestroy();
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))
{
if (hmenu != null)
destroymenu(hmenu);
return false;
}
return true;
}
简单地说cframewnd::create函数调用了基类的cwnd::createex;
以下函数定义在: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)
{
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;
cstring m_strfilename;
cstring m_strprintername;
cstring m_strdrivername;
cstring m_strportname;
~ccommandlineinfo();
};
再让我们来看看它的构造函数的实现:
//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
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()//部分源代码
{
assert_kindof(cdoctemplate, ptemplate);
ptemplate->opendocumentfile(null);
}
//in docmulti.cpp
cdocument* cmultidoctemplate::opendocumentfile(lpctstr lpszpathname,
bool bmakevisible)//部分源代码
{
cdocument* pdocument = createnewdocument();
bool bautodelete = pdocument->m_bautodelete;
pdocument->m_bautodelete = false;
cframewnd* pframe = createnewframe(pdocument, null);
pdocument->m_bautodelete = bautodelete;
if (lpszpathname == null)
{
setdefaulttitle(pdocument);
if (!bmakevisible)
pdocument->m_bembedded = true;
if (!pdocument->onnewdocument())//初始化数据
{
pframe->destroywindow();
return null;
}
m_nuntitledcount++;
}
else
{
// open an existing document
//...
}
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//注意这时候CRunTimeClass登场了
...//
cdocument* pdocument = (cdocument*)m_pdocclass->createobject();
...//
adddocument(pdocument);//将动态生成的文档对象的指针加入到应用程序的文档列表中
return pdocument;
}
cdocument* pdocument =
(cdocument*)m_pdocclass->createobject();这一句就是动态产生的核心,它借助于
cruntimeclass动态生成一个cdocument对象。
6.2.子窗口框架动态生成
cmultidoctemplate::opendocumentfile调用了createnewframe,这就是子窗口框架动态生成的主函数。
cframewnd* cdoctemplate::createnewframe(cdocument* pdoc, cframewnd* pother)//部分源代码
{
if (pdoc != null)
assert_valid(pdoc);
assert(m_nidresource != 0);
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)
{
return null;
}
assert_kindof(cframewnd, pframe);
if (!pframe->loadframe(m_nidresource,
ws_overlappedwindow | fws_addtotitle, // default frame styles
null, &context))//和原来函数的不同
{
return null;
}
//通过这两个函数完成了相应的主窗口的初始化,下面我们详细的分析一下
return pframe;
}
cframewnd* pframe =
(cframewnd*)m_pframeclass->createobject();这一句就是动态产生的核心,它借助于
cruntimeclass动态生成一个cframewnd对象。之后函数调用loadframe来创建子窗口。其过程与创建主框架窗口的过程大致相同,
但也有一些不同的地方,下面我就将说说这点不同。
6.3.子视图动态生成
瞪大眼睛仔细察看opendocumentfile的源代码,疑惑了,"怎么没有类cview* pview =createnewview();的代码?","那么子视图是如何生成的呢"下面我就为你详细解释一下吧!其实子视图动态生成函数被放到另一个地方了。让我们详细来看看吧。
其实,关键还是在loadframe,但与创建主窗口框架的那个loadframe不同的是传进了一个不同的参数&context,你回过头看看主窗口框架的那个loadframe,调用它时使用了默认参数,而那个默认参数值为null,
下面看看ccreatecontext结构。
//in afxext.h
struct ccreatecontext
{
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;
if (!oncreateclient(lpcs, pcontext))
{
return -1;
}
return 0;
}
// in winfrm.cpp
bool cframewnd::oncreateclient(lpcreatestruct, ccreatecontext* pcontext)
{
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)//部分源代码
{
cwnd* pview = (cwnd*)pcontext->m_pnewviewclass->createobject();
if (pview == null)
{
return null;
}
assert_kindof(cwnd, pview);
if (!pview->create(null, null, afx_ws_default_view,
crect(0,0,0,0), this, nid, pcontext))
{
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“三口组”,其生成过程与我上述讲的一般不二。