前面几章介绍了MFC的核心概念和思想,即介绍了MFC对Windows对象的封装方法和特点;MFC对象的动态创建、序列化;MFC消息映射机制。
现在,考查MFC的应用程序结构体系,即以文档-视为核心的编程模式。学习本章,应该弄清楚以下问题:
MFC中诸多MFC对象的关系:应用程序对象,文档对象,边框窗口对象,文档边框窗口对象,视对象,文档 模板 对象等。
MFC对象的创建和销毁:由什么对象创建或销毁什么对象,何时创建,何时销毁?
MFC提供了那些接口来支持其编程模式?
MFC对象的关系
创建关系

这里讨论应用程序、文档模板、边框窗口、视、文档等的创建关系。图5-1大略地表示了创建顺序,但表5-1更直接地显示了创建与被创建的关系。
表5-1 MFC对象的创建关系
创建者 | 被创建的对象 |
应用程序对象 | 文档模板 |
文档模板 | 文档 |
文档模板 | 边框窗口 |
边框窗口 | 视 |
交互作用关系
应用程序对象有一个文档模板列表,存放一个或多个文档模板对象;文档模板对象有一个打开文档列表,存放一个或多个已经打开的文档对象;文档对象有一个视列表,存放显示该文档数据的一个或多个视对象;还有一个指针指向创建该文档的文档模板对象;视有一个指向其关联文档的指针,视是一个子窗口,其父窗口是边框窗口(或者文档边框窗口);文档边框窗口有一个指向其当前活动视的指针;文档边框窗口是边框窗口的子窗口。
Windows 管理所有已经打开的窗口,把消息或事件发送给目标窗口。通常,命令消息发送给主边框窗口。

图5-2大略地表示了上述关系:
MFC提供了一些函数来维护这些关系。
表5-2列出了从一个对象得到相关对象的方法。
表5-2 从一个对象得到另一个对象的方法
本对象 | 要得到的对象 | 使用的成员函数 |
CDocument对象 | 视列表 | GetFirstViewPosition GetNextView |
文档模板 | GetDocTemplate | |
CView对象 | 文档对象 | GetDocument |
边框窗口 | GetParentFrame | |
CMDIChildWnd或 CFrameWnd对象 | 活动视 | GetActiveView |
活动视的文档 | GetActiveDocument | |
CMDIFrameWnd对象 | 活动文档边框窗口 | MDIGetActive |
表5-3 从一个对象通知另一个对象的方法:
本对象 | 要通知的对象/动作 | 使用的成员函数 |
CView对象 | 通知文档更新所有视 | CDocument::UpdateAllViews |
CDocument对象 | 更新一个视 | CView::OnUpdate |
CFrameWnd或 CMDIFrameWnd对象 | 通知一个视为活动视 | CView::OnActivateView |
设置一个视为活动视 | SetActivateView |
可以通过表5-2得到相关对象,再调用表5-3中相应的函数。例如:视在接受了新数据或者数据被修改之后,使用表5-2中的函数GetDocument得到关联文档对象,然后调用表5-3中的文档函数UpdateAllViews更新其他和文档对象关联的视。
在表5-2和表5-3中,CView对象指CView或派生类的实例;成员函数列中如果没有指定类属,就是第一列对象的类的成员函数。
MFC提供的接口
MFC编程就是把一些应用程序特有的东西填入MFC 框架 。MFC提供了两种填入的方法:一种就是使用前一章论述的消息映射,消息映射给应用程序的各种对象处理各种消息的机会;另一种就是使用虚拟函数,MFC在实现许多功能或者处理消息、事件的过程中,调用了虚拟函数来完成一些任务,这样就给了派生类覆盖这些虚拟函数实现特定处理的机会。
下面两节将列出两类接口,有两个目的:一是为了让读者获得整体印象,二是后文将涉及到或者讨论其中的许多函数时,不显得突兀。
虚拟函数接口
几乎每一个MFC类都定义和使用了虚拟成员函数, 程序员 可以在派生类中覆盖它们。一般,MFC提供了这些函数的缺省实现,所以覆盖函数应该调用基类的实现。这里给出一个MFC常用虚拟函数的总览表(见表5-4),更详细的信息或它们的缺省实现动作参见MFC文档。由于基类的虚拟函数被派生类继承,所以在派生类中不作重复说明。
覆盖基类的虚拟函数可以通过ClassWizard进行,不过,并非所有的函数都可以这样,有的必须手工加入函数声明和实现。
表5-4 常见MFC类的虚拟函数接口
类 | 虚拟函数 | 覆盖的目的和功能 |
CCmdTarget | OnCmdMsg | 发送、派发命令消息 |
OnFinalRelease | OLE用途,引用为0时作清理工作 | |
CWinThread | ExitInstance | 在线程退出时作清理工作 |
InitInstance | 在线程开始时作初始化 | |
OnIdle | 执行thread-specific idle-time处理 | |
PreTranslateMessage | 在消息送给Windows函数TranslateMessage and DispatchMessage.之前进行消息过滤 | |
IsIdleMessage | 检查是否是某个特别的消息 | |
ProcessWndProcException | 截获线程消息/命令处理中的例外 | |
ProcessMessageFilter | 线程消息过滤 | |
Run | 实现线程特定的消息循环 | |
CWinApp | HideApplication | 关闭所有的窗口之前隐藏应用程序 |
CloseAllDocument | 退出程序之前关闭所有文档 | |
转下页 |
续表 | ||
SaveModifiedDocument | 框架窗口关闭时用来保存文档 | |
DoMessageBox | 实现客户化的messagebox | |
DoWaitCursor | 关闭或打开等待光标 | |
OnDDeCommand | 响应DDE命令 | |
WinHelp | 调用WinHelp函数 | |
CWnd | WindowProc | 提供一个窗口过程 |
DefWindowProc | 为应用程序不处理的消息提供缺省处理 | |
PostNcDestroy | 在窗口销毁之后被消息处理函数OnNcDestroy调用 | |
OnNotify | 处理通知消息WM_NOTIFY | |
OnChildNotify | 父窗口调用它给控制子窗口一个机会来处理通知反射消息 | |
DoDataExchange | Updata调用它来进行对话框数据交换和验证 | |
CFrameWnd | GetMessageBar | 返回一个指向框架窗口的状态条的指针 |
OnCreateClient | 创建框架的客户窗口 | |
OnSetPreviewMode | 设置程序的主框架窗口进入或退出打印预览模式 | |
NegotiateBorderSpace | 协调边框窗口的边框空间的大小(OLE用途) | |
CMDIFrameWnd | CreateClient | 创建CMDIFrameWnd的MDICLIENT窗,被CWnd的消息处理函数OnCreate调用. |
转下页 |
续表 | ||
GetWindowMenuPopup | 返回窗口的弹出式菜单 | |
CDialog | OnInitDialog | 对话框窗口的初始化 |
OnSetFont | 设置对话框控制的文本字体 | |
OnOK | 模式对话框的OK按钮按下后进行的处理 | |
OnCancel | 模式对话框的CANCEL按钮按下后进行的处理 | |
CView | IsSelected | 测试是否有一个文档被选择(OLE支持) |
OnActivateView | 视窗口激活时调用 | |
OnActivateFrame | 当包含视窗口的框架窗口变成活动或非活动窗口时调用 | |
OnBeginPrinting | 打印工作开始时调用,用来分配GDI资源 | |
OnDraw | 用来屏幕显示、打印、打印预览文档内容 | |
OnEndPrinting | 打印工作结束时调用,释放GDI资源 | |
OnEndPrintPreview | 退出打印预览模式时调用 | |
OnPrepareDC | OnDraw或OnPrint之前调用,用来准备设备描述表 | |
OnPreparePrinting | 文档打印或者打印预览前调用,可用来初始化打印对话框 | |
OnPrint | 用来打印或打印预览文档 | |
OnUpdate | 用来通知一个视的关联文档内容已经变化 | |
CDocTemplate | MatchDocType | 确定文档类型和文档模板匹配时的可信程度 |
转下页 |
续表 | ||
CreateNewDocument | 创建一个新的文档 | |
CreateNewFrame | 创建一个包含文档和视的框架窗口 | |
InitialUpdateFrame | 初始化框架窗口,必要时使它可见 | |
SaveAllModified | 保存所有和模板相关的而且修改了的文档 | |
CloseAllDocuments | 关闭所有和模板相关的文档 | |
OpenDocumentFile | 打开指定路径的文件 | |
SetDefaultTitle | 设置文档窗口缺省显示的标题 | |
CDocument | CanCloseFrame | 在关闭显示该文档的边框窗口之前调用 |
DeleteContents | 用来清除文档的内容 | |
OnChangedViewList | 在与文档关联的视图被移走或新加入时调用 | |
OnCloseDocument | 用来关闭文档 | |
OnNewDocument | 用来创建新文档 | |
OnOpenDocument | 用来打开文档 | |
OnSaveDocument | 以来保存文档 | |
ReportSaveLoadException | 处理打开、保存文档操作失败时的例外 | |
GetFile | 返回一个指向Cfile对象的指针 | |
ReleaseFile | 释放一个文件以便其他应用程序可以使用 | |
SaveModified | 用来询问用户文档是否需要保存 | |
PreCloseFrame | 在框架窗口关闭之前调用 |
消息映射方法和标准命令消息
窗口对象可以响应以“WM_”为前缀的标准Windows消息,消息处理函数名称以“ON”为前缀。不同类型的Windows窗口处理的Windows消息是有所不同的,因此,不同类型的MFC窗口实现的消息处理函数也有所不同。例如,多文档边框窗口能处理WM_MDIACTIVATE消息,其他类型窗口就不能。程序员从一定的MFC窗口派生自己的窗口类,对感兴趣的消息,覆盖基类的消息处理函数,实现自己的消息处理函数。
所有的命令目标(CCmdTarger或导出类对象)可以响应命令消息,程序员可以指定应用程序对象、框架窗口对象、视对象或文档对象等来处理某条命令消息。一般地,尽量由与命令消息关系密切的对象来处理,例如隐藏/显示工具栏由框架窗口处理,打开文件由应用程序对象处理,数据变化的操作由文档对象处理。
对话框的控制子窗口可以响应各类通知消息。
对于命令消息,MFC实现了一系列标准命令消息处理函数。标准命令ID在afxres.h中定义。表5-5列出了MFC标准命令的实现,从ID或者函数名可以大致地看出该函数的目的、功用,具体的实现有的后续章节会讲解,详细参见MFC技术文档。
程序员可以自己来处理这些标准消息,也可以通过不同的类或从不同的类导出自己的类来处理这些消息,不过最好遵循MFC的缺省实现。比如处理ID_FILE_NEW命令,最好由CWinApp的派生类处理。
表5-5 标准命令消息处理函数
ID | 函数 | 实现函数的类 |
ID_FILE_NEW | OnFileNew | CWinApp |
ID_FILE_OPEN | OnFileOpen | CWinApp |
ID_FILE_CLOSE | OnFileClose | CDocument |
ID_FILE_SAVE | OnFileSave | CDocument |
ID_FILE_SAVE_AS | OnFileSaveAs | CDocument |
ID_FILE_SAVE_COPY_AS | OnFileSaveCopyAs | COleServerDoc |
ID_FILE_UPDATE | OnUpdateDocument | COleServerDoc |
ID_FILE_PAGE_SETUP | OnFilePrintSetup | CWinApp |
转下页 |
续表 | ||
ID_FILE_PRINT | OnFilePrint | CView |
ID_FILE_PRINT_PREVIEW | OnFilePrintPreview | CView |
ID_FILE_MRU_FILE1...FILE16 | OnUpdateRecentFileMenu | CWinApp |
ID_EDIT_CLEAR | CView没有实现, | |
ID_EDIT_CLEAR_ALL | 但是,如果有实现 | |
ID_EDIT_COPY | 函数,就是派生类 | |
ID_EDIT_CUT | CEditView的 | |
ID_EDIT_FIND | 实现函数 | |
ID_EDIT_PASTE_LINK | ||
ID_EDIT_PASTE_SPECIAL | ||
ID_EDIT_REPEAT | ||
ID_EDIT_REPLACE | ||
ID_EDIT_SELET_ALL | ||
ID_EDIT_UNDO | ||
ID_WINDOW_NEW | OnWindowNew | CMDIFrameWnd |
ID_WINDOW_ARRANGE | OnMDIWindowCmd | CMDIFrameWnd |
ID_WINDOW_CASCADE | ||
ID_WINDOW_TILE_HORZ | ||
ID_WINDOW_TILE_VERT | ||
ID_WINDOW_SPLIT | CSplitterWnd | |
ID_APP_ABOUT | ||
ID_APP_EXIT | OnAppExit | CWinApp |
ID_HELP_INDEX | OnHelpIndex | CWinApp |
ID_HELP_USING | OnHelpUsing | CWinApp |
ID_CONTEXT_HELP | OnContextHelp | CWinApp |
转下页 |
续表 | ||
ID_HELP | OnHelp | CWinApp |
ID_DEFAULT_HELP | OnHelpIndex | CWinApp |
ID_NEXT_PANE | OnNextPaneCmd | CSplitterWnd |
ID_PREV_PANE | OnNextPaneCmd | CSplitterWnd |
ID_OLE_INSERT_NEW | ||
ID_OLE_EDIT_LINKS | ||
ID_OLE_VERB_FIRST...LAST | ||
ID_VIEW_TOOLBAR | CFrameWnd | |
ID_VIEW_STATUS_BAR | CFrameWnd | |
ID_INDICATOR_CAPS ID_INDICATOR_NUM ID_INDICATOR_SCRL ID_INDICATOR_KANA | OnUpdateKeyIndicator | CFrameWnd |
MFC对象的创建过程
应用程序使用MFC的接口是把一些自己的特殊处理填入MFC框架,这些处理或者在应用程序启动和初始化的时候被调用,或者在程序启动之后和用户交互的过程中被调用,或者在程序退出和作清理工作的时候被调用。这三个阶段中,和用户交互阶段是各个程序自己的事情,自然都不一样,但是程序的启动和退出两个阶段是MFC框架所实现的,是MFC框架的一部分,各个程序都遵循同样的步骤和规则。显然,清楚MFC框架对这两个阶段的处理是很有必要的,它可以帮助深入理解MFC框架,更好地使用MFC框架,更有效地实现应用程序特定的处理。
MFC程序启动和初始化过程就是创建MFC对象和Windows对象、建立各种对象之间的关系、把窗口显示在屏幕上的过程,退出过程就是关闭窗口、销毁所创建的Windows对象和MFC对象的过程。所以,下面要讨论几种常用MFC对象的结构,它们是构成一个文档-视模式应用程序的重要部件。
应用程序中典型对象的结构
本节将主要分析应用程序对象、文档对象、文档模板等的数据结构。通过考察类的结构,特别是成员变量结构,弄清它的功能、目的以及和其他类的关系;另外,在后续有关分析中必定会提到这些成员变量,这里先作个说明,到时也不会显得突兀。
下面几节以表格的形式来描述各个类的成员变量。表格中,第一列打钩的表示是MFC类库文档有说明的;没打钩的在文档中没有说明,如果是public,则可以直接访问,但随着MFC版本的变化,以后MFC可能不支持这些成员;第二列是访问属性;第三列是成员变量名称;第四列是成员变量的数据类型;第五列是对成员变量的功能、用途的简要描述。
应用程序类的成员变量
应用程序对象的数据成员表由两部分组成,第一部分是CWinThread的成员变量,如表5-6所示,CWinApp继承了CWinThread的数据成员。第二部分是CWinApp自己定义的成员变量,如表5-7所示。
表5-6 CwinThread的成员变量
访问限制 | 变量名称 | 类型 | 解释 | |
√ | public | m_bAutoDelete | BOOL | 指定线程结束时是否销毁线程对象本身 |
√ | public | m_hThread | HANDLE | 当前线程的句柄 |
√ | public | m_nThreadID | UINT | 当前线程的ID |
√ | public | m_pMainWnd | CWnd* | 指向应用程序主窗口的指针 |
√ | public | m_pActiveWnd | CWnd* | 当OLE SERVER就地激活时指向客户程序主窗口的指针 |
public | m_msgCur | MSG | 当前消息(MSG结构) | |
public | m_pThreadParams | LPVOID | 传递给线程开始函数的参数 | |
public | m_pfnThreadProc | 函数指针1 | 线程开始函数,AFX_THREADPROC类型 | |
public | m_lpfnOleTermOrFreeLib | 函数指针2 | OLE用途,void (AFXAPI * fn)(BOOL,BOOL) | |
public | m_pMessageFilter | 指针 | OLE消息过滤,指向COleMessageFilter对象 | |
protected | m_ptCursorLast | CPoint | 最新鼠标位置 | |
protected | m_nMsgLast | UINT | 消息队列中最新接收到的消息 |
表5-7 CWinApp的成员变量
访问限制 | 变量名称 | 类型 | 解释 | |
√ | public | m_pszAppName | LPCTSTR | 应用程序名称 |
√ | public | m_hInstance | HINSTANCE | 标志应用程序当前实例句柄 |
√ | public | m_hPrevInstance | HINSTANCE | 32位程序设为空 |
√ | public | m_lpCmdLine | LPTSTR | 指向应用程序的命令行字符串 |
√ | public | m_nCmdShow | int | 指定窗口开始的显示方式 |
√ | public | m_bHelpMode | BOOL | 标识用户是否在上下文帮助模式 |
√ | public | m_pszExeName | LPCTSTR | 应用程序的模块名 |
√ | public | m_pszHelpFilePath | LPCTSTR | 应用程序的帮助文件名,缺省时同模块名 |
√ | public | m_pszProfileName | LPCTSTR | 应用程序的INI文件名,缺省时同应用程序名 |
√ | public | m_pszRegistryKey | LPCTSTR | Register入口,如果不指定,使用INI文件。 |
public | m_pDocManager; | CDocManager * | 指向一个文档模板管理器 | |
protected | m_hDevMode | HGLOBAL | 打印设备模式 | |
protected | m_hDevNames | HGLOBAL | 打印设备名称 | |
protected | m_dwPromptContext | DWORD | 被MESSAGE BOX覆盖的帮助上下文 | |
protected | m_nWaitCursorCount | int | 等待光标计数 | |
protected | m_hcurWaitCursorRestore | HCURSOR | 保存的光标,在等待光标之后恢复 | |
protected | m_pRecentFileList | 指针 | 指向CRecentFileList对象,最近打开的文件列表 | |
public | m_atomApp | ATOM | DDE用途 | |
public | m_atomSystemTopic | m_atomApp | DDE用途 | |
public | m_nNumPreviewPages | UINT | 缺省被打印的页面 | |
public | m_nSafetyPoolSize | size_t | 理想尺寸 | |
public | m_lpfnDaoTerm | 函数指针 | DAO初始化设置时使用 |
CDocument的成员变量
表5-8 文档对象的属性。
访问限制 | 变量名称 | 类型 | 解释 | |
protected | m_strTitle | CString | 文档标题 | |
protected | m_strPathName | CString | 文档路径 | |
protected | m_pDocTemplate | CDocTemplate* | 指向文档模板的指针 | |
protected | m_viewList | CPtrList | 关联的视窗口列表 | |
protected | m_bModified | BOOL | 文档是否有变化、需要存盘 | |
public | m_bAutoDelete | BOOL | 关联视都关闭时是否删除文档对象 | |
public | m_bEmbedded | BOOL | 文档是否由OLE创建 |
文档模板的属性
表5-9列出了文档模板的成员变量,5-10列出了单文档模板的成员变量,5-11列出了多文档模板的成员变量。单、多文档模板继承了文档模板的成员变量。
表5-9 文档模板的数据成员
访问限制 | 变量名称 | 类型 | 解释 | |
public | m_bAutoDelete | BOOL | ||
public | m_pAttachedFactory | CObject * | ||
public | m_hMenuInPlace | HMENU | 就地激活时,OLE客户程序的菜单 | |
public | m_hAccelInPlace | HACCEL | 就地激活时,OLE客户程序的快捷键 | |
public | m_hMenuEmbedding | HMENU | ||
public | m_hAccelEmbedding | HACCEL | ||
public | m_hMenuInPlaceServer | HMENU | ||
public | m_hAccelInPlaceServer | HACCEL | ||
protected | m_nIDResource | UINT | 框架、菜单、快捷键等的资源ID | |
protected | m_nIDServerResource | UINT | ||
public | m_nIDEmbeddingResource | UINT | ||
public | m_nIDContainerResource | UINT | ||
public | m_pDocClass | CRuntimeClass* | 指向文档类的动态创建信息 | |
public | m_pFrameClass | CRuntimeClass* | 指向框架类的动态创建信息 | |
public | m_pViewClass | CRuntimeClass* | 指向视类的动态创建信息,由字符串m_nIDResource描述 | |
public | m_pOleFrameClass | CRuntimeClass* | 指向OLD框架类的动态创建信息 | |
public | m_pOleViewClass | CRuntimeClass* | ||
public | m_strDocStrings | CString | 描述该文档类型的字符串 |
表5-10 单文档模板的成员变量
访问限制 | 变量名称 | 类型 | 解释 | |
protected | m_pOnlyDoc | CDocment* | 指向唯一的文档对象 |
表5-11 单文档模板的成员变量
访问限制 | 变量名称 | 类型 | 解释 | |
public | m_hMenuShared | HMENU | 该模板的MDI子窗口的菜单 | |
public | m_hAccelTable | HACCEL | 该模板的MDI子窗口的快捷键 | |
protected | m_docList | CPtrList | 该模板的文档列表 | |
protected | m_nUntitledCount | UINT | 用来生成文件名的数字,如”untitled0”的0。 |
WinMain入口函数
WinMain 流程
现在讨论MFC应用程序如何启动。
WinMain函数是MFC提供的应用程序入口。进入WinMain前,全局应用程序对象已经生成。WinMain流程如图5-3所示。图中,灰色框是对被调用的虚拟函数的注释,程序员可以或必须覆盖它以实现MFC要求的或用户希望的功能;大括号所包含的图示是相应函数流程的细化,有应用程序对象App的初始化、Run函数的实现、PumpMessage的流程,等等。
从图中可以看出:
(1)一些虚拟函数被调用的时机
对应用程序类(线程类)的InitIntance、ExitInstance、Run、ProcessMessageFilter、OnIdle、PreTranslateMessage来说,InitInstance在应用程序初始化时调用,ExitInstance在程序退出时调用,Run在程序初始化之后调用导致程序进入消息循环,ProcessMessageFilter、OnIdle、PreTranslateMessage在消息循环时被调用,分别用来过滤消息、进行Idle处理、让窗口预处理消息。
(2)应用程序对象的角色
首先,应用程序对象的成员函数InitInstance被WinMain调用。对程序员来说,它就是程序的入口点(真正的入口点是WinMain,但MFC向程序员隐藏了WinMain的存在)。由于MFC没有提供InitInstance的缺省实现,用户必须自己实现它。稍后将讨论该函数的实现。
其次,通过应用程序对象的Run函数,程序进入消息循环。实际上,消息循环的实现是通过CWinThread::Run来实现的,图中所示的是CWinThread::Run的实现,因为CWinApp没有覆盖Run的实现,程序员的应用程序类一般也不用覆盖该函数。
(3)Run所实现的消息循环
它调用PumpMessage来实现消息循环,如果没消息,则进行空闲(Idle)处理。如果是WM_QUIT消息,则调用ExitInstance后退出消息循环。
(4)CWinThread::PumpMessage
该函数在MFC函数文档里没有描述,但是MFC建议用户使用。它实现获取消息,转换(Translate)消息,发送消息的消息循环。在转换消息之前,调用虚拟函数PreTranslateMessage对消息进行预处理,该函数得到消息目的窗口对象之后,使用CWnd的WalkPreTranslateTree让目的窗口及其所有父窗口得到一个预处理当前消息的机会。关于消息预处理,见消息映射的有关章节。如果是WM_QUIT消息,PumpMessage返回FALSE;否则返回TRUE。
MFC空闲处理
MFC实现了一个Idle处理机制,就是在没有消息可以处理时,进行Idle处理。Idle处理的一个应用是更新用户接口对象的状态。更新用户接口状态的内容见消息映射的章节。
空闲处理由函数OnIdle完成,其原型为BOOL OnIdle(int)。参数的含义是当前空闲处理周期已经完成了多少次OnIdle调用,每个空闲处理周期的第一次调用,该参数设为0,每调用一次加1;返回值表示当前空闲处理周期是否继续调用OnIdle。
MFC的缺省实现里,CWinThread::OnIdle完成了工具栏等的状态更新。如果覆盖OnIdle,需要调用基类的实现。
在处理完一个消息或进入消息循环时,如果消息队列中没有消息要处理,则MFC开始一个新的空闲处理周期;
当OnIdle返回FASLE,或者消息队列中有消息要处理时,当前的空闲处理周期结束。
从图5-3中Run的流程上可以清楚的看到MFC空闲处理的情况。
本节描述了应用程序从InitInstance开始初始化、从Run进入消息循环的过程,下面将就SDI应用程序的例子描述该过程中创建各个所需MFC对象的流程。
SDI应用程序的对象创建
如前一节所述,程序从InitInstance开始。在SDI应用程序的InitInstance里,至少有以下语句:
//第一部分,创建文档模板对象并把它添加到应用程序的模板链表
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CTView));
AddDocTemplate(pDocTemplate);
//第二部分,动态创建文档、视、边框窗口等MFC对象和对应的Windows对象
//Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return FALSE;
//第三部分,返回TRUE,WinMain下一步调用Run开始消息循环,
//否则,终止程序
return TRUE;
对于第二部分,又可以分解成许多步骤。
下面将解释每一步。
文档模板的创建
第一步是创建文档模板。
文档模板的作用是动态创建其他MFC对象,它保存了要动态创建类的动态创建信息和该文档类型的资源ID。这些信息保存在文档模板的成员变量里:m_nIDResource(资源ID)、m_pDocClass(文档类动态创建信息)、m_pFrameClass(边框窗口类动态创建信息)、m_pViewClass(视类动态创建信息)。
资源ID包括菜单、像标、快捷键、字符串资源的ID,它们都使用同一个ID值,如IDR_MAINFRAME。其中,字符串资源描述了文档类型,由七个被“ ”分隔的子字符串组成,各个子串可以通过CDocTemplate的成员函数GetDocString(CString& rString, enum DocStringIndex index)来获取。DocStringIndex是CDocTemplate类定义的枚举变量以区分七个子串,描述如下(英文是枚举变量名称)。
WindowTitle 应用程序窗口的标题。仅仅对SDI程序指定。
DocName 用来构造缺省文档名的字符串。当用File菜单的菜单项new创建新文档时,缺省文档名由该字符串加一个数字构成。如果空,使用“unitled”。
FileNewName 文档类型的名称,在打开File New对话框时显示。
FilterName 匹配过滤字符串,在File Open对话框用来过滤要显示的文件。如果不指定,File Open对话框的文件类型(file style)不可访问。
FilterExt 该类型文档的扩展名。如果不指定,则不可访问对话框的文件类型(File Style)。
RegFileTypeId 文档类型在Windows 注册库中的存储标识。
RegFileTypeName 文档类型在Windows 注册库中的类型名称。
文档模板被应用程序对象创建和管理。应用程序类CWinApp有一个CDocManager类型的成员变量m_pDocManager,通过该变量来管理应用程序的文档模板列表,把一些相关的操作委派给CDocManager对象处理。
CDocManager使用CPtrList类型的m_templateList变量来存储文档模板,并提供了操作文档模板列表的系列函数。
从语句pDocTemplate = new CSingleDocTemplate(…)可以看出应用程序对象创建模板时传递一个资源ID和三个类的动态创建信息给它:
IDR_MAINFRAME,资源ID
RUNTIME_CLASS(CTDoc),文档类动态创建信息
RUNTIME_CLASS(CMainFrame),边框窗口类动态创建信息
RUNTIME_CLASS(CTView),视类动态创建信息
文档模板对象接收这些信息并把它们保存到对应的成员变量里头。然后AddDocTemplate实际调用m_pDocManager->AddDocTemplate,把创建的模板对象加入到文档模板管理器的模板列表中,也就是应用程序对象的文档模板列表中。
文件的创建或者打开
第二步是创建或者打开文件。
对于SDI程序,MFC对象的动态创建过程是在创建或者打开文件中发生的。但是为什么没有看到文件操作相关的语句呢?
CCommandLineInfo
首先,需要弄清楚类CcommandLineInfo,它是用来处理命令行信息的类,CWinApp::PareCommandLine调用CCommandLineInfo的成员函数ParseParm分析启动程序时的参数,把分析结果保存在CCommandLineInfo对象的成员变量里。CCommandLineInfo的定义如下:
class CCommandLineInfo : public CObject
{
BOOL m_bShowSplash;
BOOL m_bRunEmbedded;
BOOL m_bRunAutomated;
enum { FileNew, FileOpen, FilePrint, FilePrintTo, FileDDE,
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;
};
由上述定义可以看出,分析结果分几类:是否OLE激活;应该执行什么动作(FileNew、FileOpen等);传递的参数(打开或打印的文件名,打印设备、端口等)。
当命令行空时,执行FileNew命令。原因在于CCommandLineInfo的缺省构造函数:
CCommandLineInfo::CCommandLineInfo()
{
m_bShowSplash = TRUE;
m_bRunEmbedded = FALSE;
m_bRunAutomated = FALSE;
m_nShellCommand = FileNew;//指定了SHELL命令操作
}
缺省构造把应该执行的动作指定为FileNew。
处理命令行命令
其次,分析 CWinApp::ProcessShellCommand(CCommandLineInfo& rCmdInfo)的流程,它处理命令行的命令,流程如图5-3所示。

图5-4第三层表示根据命令类型进一步调用的函数,都是CWinApp或者派生类的成员函数。对于FILEDDE类型没有进一步的调用。
命令类型是FILENEW时,调用的函数就是标准命令ID_FILE_NEW对应的处理函数OnFileNew;命令类型是FILEOPEN时调用的函数是OpenDocumentFile,标准命令ID_FILE_OPEN的处理函数OnFileOpen的工作实际上就是由OpenDocumentFile完成的。函数FileNew、OpenDocumentFile导致了窗口、文档的创建。
OnFileNew
接着,分析 CWinApp::OnFileNew流程,如图5-5所示。

图5-5的说明:
应用程序对象得到文档模板管理器指针,调用文档模板管理器的成员函数OnFileNew(m_pDocManager->OnFileNew());模板管理器获取文档模板对象指针,调用文档模板对象的OpenDocumentFile 函数(pTemplate->OpenDocumentFile(NULL))。如果模板管理器发现有多个文档模板,就弹出一个对话框让用户选择文档模板。这里和后面的图解中类似于CWinApp::、CDocManager::、CDocTemplate::等的函数类属限制并不表示直接源码中有这样的限制,而是通过指针或者指针的动态约束可以认定调用了某个类的成员函数,其正确性仅仅限于本书图解的MFC的缺省实现。
如图5-5所示,程序员可以覆盖有关虚拟函数或命令处理函数:如果程序员在自己的应用程序类中覆盖了OnFileNew,则可以实现完全不同的处理流程;一般情况下,不会从文档模板类派生新类,如果派生的话,可以覆盖CDocTemplate的虚拟函数。
OnFileOpen
分析了 OnFileNew后,现在分析CWinApp::OnFileOpen(),其流程如图5-6所示。

CWinApp::OnFileOpen和OnFileNew类似,不过,第二步须得到一个要打开的文件的名称,第三步调用的是应用程序对象的OpenDocumentFile,而不是文档模板对象的该函数。
应用程序对象的OpenDocumentFile
分析应用程序的打开文档函数: CWinApp::OpenDocumentFile(LPCSTR name),其流程如图5-7所示。

应用程序对象把打开文件操作委托给文档模板管理器,后者又委托给文档模板对象来执行。如果是SDI程序,则委托给单文档对象;如果是MDI程序,则委托给多文档对象──这是由指针所指对象的实际类型决定的,因为该函数是一个虚拟函数。
文档模板的OpenDocumentFile
不论是FileNew还是FileOpen,最后的操作都归结到由文档模板来打开文件(文件名空则创建文件)。
CSingleDocTemplate::OpenDocumentFile(lpcstr name,BOOL visible)的流程见图5-8。有一点需要指出的是:创建了一个文档对象,并不等于打开了一个文档(件)或者创建了一个新文档(件)。
图5-8显示的流程大致可以描述如下:
如果已经有文档打开,则保存当前的文档;否则,文档对象还没有创建,需要创建一个新的文档对象。因为这时边框窗口还没有生成,所以还要创建边框窗口对象(MFC对象)和边框窗口。MFC边框窗口对象动态创建,HWND边框窗口由LoadFrame创建。MFC边框窗口被创建时,CFrameWnd的缺省构造函数被调用,它把正创建的对象(this所指)加入到模块-线程状态的边框窗口列表m_frameList之首。
边框窗口创建过程中由CreateView动态创建MFC视对象和HWND视窗口。
接着,如果没有指定要打开的文件名,则创建一个新的文件;否则,则打开文件,并使用序列化机制读入文件内容。
通过上述过程,动态地创建了MFC边框窗口对象、视对象、文档对象以及对应的Windows对象,并填写了有关对象的成员变量,建立起这些MFC对象的关系。
打开文件过程中所涉及的消息处理函数和虚拟函数
图5-8描述的整个过程中系列消息处理函数和虚拟函数被调用。例如:在Windwos边框窗口和视窗口被创建时会产生WM_CREATE等消息,导致OnCreate等消息处理函数的调用,CFrameWnd和CView都覆盖了该函数,所以在边框窗口和视窗口的创建中,同样的消息调用了不同的处理函数CFrameWnd::OnCreate和CView::OnCreate。
图5-8涉及的几个虚拟函数的流程分别由图5-9、图5-10图解。图5-9表示CDocument的OnNewDocument的流程;图5-10表示CDocument的OpenDocument的流程。这两个函数分别在创建新文档或者打开一个文档时被调用。从流程可以看出,对于OpenDocument函数,MFC的缺省实现主要用来设置修改标识、序列化读入打开文档的内容。图5-10显示了序列化的操作过程:
首先,使用文档对象打开或者创建的文件句柄创建一个用于读出数据的CArchive对象loadarchive;然后使用它通过Serialize进行序列化操作,完毕,CArchive对象被自动销毁,文件句柄被关闭。