1 Frame和View概述
Frame窗体实际就是一个Overlapped窗口(WS_OVERLAPPEDWINDOW组合风格),它可以包含菜单、工具条和标题栏。MFC使用CFrameWnd封装了Frame窗体,并提供了它更多的特性,比如:可dockable的control bar、dde会话等。进一步,MFC中提供了CMDIFrameWnd类和CMDIChildWnd类,对多个Frame窗口进行管理,它是对MDI系列API的封装,这是我们常说的MDI框架中的两个重要窗口类。对应的,SDI框架中只有一个Frame窗体,因此应用则直接从CFrameWnd继承即可。
View是MFC中一个非常重要的子窗口,默认的View风格为AFX_WS_DEFAULT_VIEW(WS_CHILD | WS_VISIBLE | WS_BORDER)。View的基本类为CView,MFC提供了多种派生类,比如:处理滚动消息的CScrollView、可以定义UI资源的CFormView、各种控件的包装的CCtrlView基类,以及由此派生的相应的控件View,比如:CListView、CTreeView等。所有的View都有一个很重要的特性,就是它的父窗口只能是CFrameWnd及其子类的对象。
2 SDI
MFC提供的一种Doc-View框架,它包含三个基本元素:View、Document和Frame Window,其中,View从CView或其子类继承、Document从CDocument或其子类继承、Frame window从CFrameWnd或其子类继承。既然是单文档视图,那么就只有一个Frame窗口,其中也只包含一个文档和视图。(说明:视图可以动态创建,因此可以改变到其它视图。另外,视图既然是窗口,当然还可以切分成子视图,此时实际上又引入了一个Frame窗口,即:CSplitterFrameWnd,因为视图的父窗口只能是不是CFrameWnd及其子类的对象)
MFC使用CSingleDocTemplate来将这些这三种元素绑定在一起,这里实际是使用了RUNTIMECLASS宏,将各个类运行时信息(主要是类名,父类名)等记录在了文档模板中。程序中的文档模板都注册到CDocManager对象中,由它来管理,该对象是CWinApp的一个私有成员。
为了提供runtime信息,可以使用DECLARE_DYNAMIC和IMPLEMENT_DYNAMIC宏。以这对宏提供的信息为基础,MFC建立了它自己的RTTI机制(Runtime Type Identification),并提供了例如:ISKINDOF等宏。对于上述几种类,为了能够由框架来创建它们,还至少需要提供一个类似工厂的特性,在MFC中,封装了另外一对宏,即:DECLARE_DYNCREATE和IMPLEMENT_DYNCRATE,它们除了提供前面宏的功能外,还提供了一个CreateObject方法,即:创建相应的对象实例。此时,创建的实例均使用的是无参数的构造函数。
在SDI创建过程中,最核心的方法是CDocTemplate的OpenDocumentFile方法,该方法为虚方法,对于CSingleDocTemplate而言,其序列如下:
1. 判断当前是否已经打开一个文档,如果是,那么处理保存相关情形;否则,调用CreateNewDocument方法创建一个新文档,该方法主要是调用CreateObject创建相应的文档对象,然后将文档对象的m_pDocTemplate成员指向当前doc template实例。
2. 如果是第一次创建(Frame窗口为空),那么将调用CreateNewFrame方法要创建Frame窗口。该方法主要做了两件事情:
a. 调用CreateObject,创建Frame实例
b. 调用该实例的LoadFrame方法,从资源中创建该窗体,这里包括:菜单和加速键资源。
c. 在创建Frame窗体之后,CFrameWnd的OnCreate方法将会创建Frame窗口的子窗口,比如:相关联的视图。该基类中只是调用CreateView方法创建View。当然,用户可以重载OnCreateClient方法,创建其它的子窗口。此时,注意缺省的(注册到文档模板)View的资源ID为AFX_IDW_PANE_FIRST(最多可以有256个pane,这一范围的ID还被用于Frame窗口内的各种control bar)。CreateView方法主要是先通过CreateObject创建相应的View实例,然后再创建该实例的窗口。
d. Lay out子窗口(RecalcLayout)
3. 判断是新创建一个空文档,还是打开一个已有文档
4. 调用InitialUpdateFrame方法初始化视图,此时,基本步骤如下:
a. 如果当前没有active view,那么调用SetActiveView设置当前view。该方法将先deactivate当前的active view,然后再设置新的active view(相应View的OnActiveView被调用)。
b. 向Frame窗口的所有子窗口发送WM_INITUPDATE消息,其中,CView类提供了OnInitUpdate方法响应该消息。
c. 如果符合步骤a的条件,那么会调用该View的OnActivateFrame虚方法,以便View能够有机会设置焦点,Form View需要这个机会。CView基类缺省不做任何事情。
d. 激活Frame,即:将其显示在最上方。注意此时,调用了BringToTop方法,它封装了如下两个API方法:
GetLastActivePopup: 获得指定窗口中当前活动的pop up子窗口
BringWindowToTop:将指定窗口显示在最上方。如果该窗口为一个子窗口,那么其top-level父窗口将被显示在最上方。
e. 激活View
f. 给Document类机会更新Frame的Title