在程序启动的过程会逐步创建程序相关的窗口,整个流程的起点是CWinApp类别或者其子类里面的InitInstance函数里面,由于这个函数式一个虚函数,所以实际是调用CWinApp类别的子类当中实现的函数。在InitInstance函数里面,首先保存FRAME、VIEW和DOC的RUNTIME_CLASS信息,然后通过函数ParseCommandLine扫描在启动的时候设置的一些命令行消息,当然如果没有相应的消息,就会使用默认的消息。你可以利用下列代码验证下,传递给程序的命令行是什么:
LPWSTR* ptr;
CCommandLineInfo cmdInfo;
int num;
ptr=CommandLineToArgvW(GetCommandLineW(),&num);
CFile file(TEXT("qq.txt"),CFile::modeWrite|CFile::modeCreate);
for (int i=0;i<num;i++)
{
file.Write(ptr[i],lstrlenW(ptr[i])+1);
}
如果没有命令行传递给程序的话,ParseCommandLine就不会执行,一切都是使用在CCommandLineInfo类别构造函数当中使用的默认命令。其中最主要的一句是:
m_nShellCommand = FileNew;
正是这一句使得在程序启动的时候进行主窗口的创建,当然创建过程是由ProcessShellCommand中进行的。这个函数是一个很大的switch…case结构,switch的选择规则是CCommandLineInfo的m_nShellCommand参数,由于上面的m_nShellCommand等于FileNew,所以整个函数会转入窗口的创建流程。
if (!AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL))
OnFileNew();
上面的两句真正起作用的是第二句,第一句是将消息发送到消息循环当中去处理。但是由于现在除了CWinThread类别及其子类已经创建出来之外,其他的类别还没有创建;而这个类别并没有对ID_FILE_NEW进行拦截。所以执行的是第二句。当然,如果你拦截了这个消息,并进行相应的处理也可以在消息处理函数当中进行处理。而OnFileNew仅仅只是调用CDocManager类别的OnFileNew函数。由于之前在执行下面的代码的时候已经将RUNTIME_CLASS结构体当中的信息传递给CDocManager当中。所以在CDocManager类在OnFileNew函数当中有足够的消息来创建窗口,并将窗口和文档关联起来。
不过在创建之前,CDocManager会首先让用户选择一下,到底使用哪一个文档末班类别,这个文档模板类别是通过AddDocTemplate函数传递进去的CDocTemplate类指针。这个类有两个字符一个是单文档模板CSingleDocTemplate,另一个是CMutilDocTemplate类。这两个类别的最大的区别是在CSingleDocTemplate类当中是以一个CDocument指针成员变量m_pOnlyDoc存放文档类,而在CMutilDocTemplate类当中是以一个文档类列表CPtrList存放文档的。这一点可以在CDocTemplate类相应的成员函数OpenDocumentFile里面得到验证。由于OpenDocumentFile函数是一个虚拟函数,在CDocManager类的OnFileNew函数当中实际上是调用CDocTemplate类的子类成员函数。OpenDocumentFile函数的定义如下,在OnFileNew当中传递进来的的文档路径为空。
virtual CDocument* OpenDocumentFile(
LPCTSTR lpszPathName, BOOL bMakeVisible = TRUE);
OpenDocTemplate函数的实现是在单文档模板类或者多文档模板类当中实现的,这个函数总的过程分为两个,第一个是打开已有的文档,第二个是创建新文档。但是这两个过程交织在一起,每一个过程又包含三个步骤,创建文档,创建主窗口,设置相应的属性。
第一部分,如果文档成员指针不为空,则表明是打开文档,所以需要保存原有的文档修改数据,然后只需要获得当前的主窗口就可以了,否则就需要利用之前保存的RUNTIME_CLASS信息new一个新的文档类,同时将文档加入到m_pOnlyDoc当中,或者多文档模板类的CPtrList列表当中。不过此时还只是新建一个文档类,而没有将相应的文档数据和这个类别关联起来。 if (m_pOnlyDoc != NULL)//
{
pDocument = m_pOnlyDoc;
if (!pDocument->SaveModified())
return NULL;
pFrame = (CFrameWnd*)AfxGetMainWnd();
}
else
{
pDocument = CreateNewDocument();//
bCreated = TRUE;
}
接下来是根据pFrame是否为空进行判断是否创建主窗口,主窗口的创建相对文档创建多一个步骤,除了利用RUNTIME_CLASS结构体new一个窗口出来之外,还会调用窗口的LoadFrame函数进行win32窗口的创建HWND。
if (pFrame == NULL)
{
BOOL bAutoDelete = pDocument->m_bAutoDelete;
pDocument->m_bAutoDelete = FALSE;
pFrame = CreateNewFrame(pDocument, NULL);
pDocument->m_bAutoDelete = bAutoDelete;
if (pFrame == NULL)
{
AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);
delete pDocument; // explicit delete on error
return NULL;
}
}
到这一步,窗口的创建完成一部分,接下来需要做的是在相应的类别里面进行消息拦截处理,相应的消息处理之后才算一个完整的窗口创建过程。不过这些消息大多有默认的处理调用,所以即使不进行自定义的拦截也可以比较正常的窗口的显示。