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

来源:互联网 发布:淘宝贷款条件 编辑:程序博客网 时间:2024/05/20 20:19
1.回顾Initinstance函数
首先回顾一下CMyWinapp::Initinstance()函数,并将里面与文档视图结构有关的代码深入探讨一下:
bool CMyWinapp::Initinstance()
//只列出了与文档视图结构相关的源代码
{

    //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“三口组”,其生成过程与我上述讲的一般不二。
原创粉丝点击