Frame、View、Document的关系,非常重要,它们的确不是孤立的,而是互相联系的。
如果是SDI程序,CMainFrame调用GetActiveView获得CView,而CView可以调用GetDocument获得CDocument。如果已知CDocument,可以使用GetFirstViewPosition获得第一个相关的CView,然后使用GetNextView获得下一个的CView。之所以这么做,是因为MFC允许一个CDocument拥有多个CView。
如果是MDI程序,稍微复杂一些。你不能直接调用GetActiveView,而需要先调用CMainFrame::MDIGetActive获得当前的CMDIChildWnd,而这个CMDIChildWnd相当于SDI中的CMainFrame
1、框架窗口
框架窗口为应用程序的用户界面提供结构框架,它是应用程序的主窗口,负责管理其包容的窗口,一个应用程序的最顶层的框架窗口是应用程序启动时创建的第一个窗口。
MFC提供三种类型的框架窗口:单文档窗口,多文档窗口(MDI),对话框。在AppWizard的第一个对话框中,就提供了选项,让用户选择应用程序是基于单文档、多文档还是对话框的。MFC单文档窗口一次只能打开一个文档框架窗口,而MDI应用程序运行时,在应用程序的一个实例中打开多个文档框架窗口,这些窗口称作子窗口(Child Window)。这些文档可以是同一类型的,也可以是不同类型的。如Visual Studio就可以打开资源文件窗口和源程序窗口等不同类型的窗口。此时,激活不同类型的MDI子窗口,菜单也将相应变化。
MFC提供了三个类CFrameWnd、CMDIFrameWnd、CMDIChildWnd和CDialog 分别用于支持单文档窗口、多文档窗口和对话框。
CFrameWnd
用于SDI框架窗口,形成单个文档及其视的边框。框架窗口既是应用程序的主框架窗口,也是当前文档对应的视图的边框。
CMDIFrameWnd
用于MDI应用程序的主框架窗口。主框架窗口是所有MDI文档窗口的容器,并与它们共享菜单条。MDI框架窗口是出现在桌面中的顶层窗口。
CMDIChildWnd
用于在MDI主框架窗口中显示打开的各个文档。每个文档及其视都有一个MDI子框架窗口,子框架窗口包含在MDI主框架窗口中。子框架窗口看起来类似一般的框架边框窗口,但它是包含在主框架窗口中,而不是位于桌面的,并且为主窗口所裁剪。而且MDI子窗口没有自己的菜单,它与主MDI框架窗口共享菜单。
CDialog
对话框是一种特殊类型的窗口,它的边框一般不可以调整,而且内部包含一些控件窗口。有关对话框作为主窗口的技术可以参见下一章。
要生成一个单文档窗口,主窗口就必须从CFrameWnd派生;要生成一个多文档窗口,主窗口就必须从CMDIFrameWnd派生,而且其中的子窗口必须从CMDIChildWnd派生出来;而基于对话框的窗口程序就要从CDialog派生出主窗口类。
子窗口
子窗口就是具有WS_CHILD风格的窗口,且一定有一个父窗口。所有的控件都是子窗口。子窗口可以没有边框。子窗口被完全限制在父窗口内部。
父窗口
父窗口就是拥有子窗口的窗口。
弹出式窗口
具有WS_POPUP风格,它可以没有父窗口。这种窗口几乎什么都没有,可看作一个矩形区域。
2、窗口的创建
窗口的创建分为两步:第一步是用new创建一个C++的窗口对象,但是此时只是初始化窗口的数据成员,并没有真正创建窗口(这一点与一般的对象有所不同)。
2.1、第一步:创建一个C++对象,其中CMainFrame是从CFrameWnd派生的对象。
CMainFrame* pMyFrame=new CMainFrame();//用new操作符创建窗口对象
或
CMainFrame MyFrame;//定义一个窗口对象,自动调用其构造函数
2.2、第二步是创建窗口。CFrameWnd的Create成员函数把窗口给做出来,并将其HWND保存在C++对象的公共数据成员m_hWnd中。
//第二步:创建窗口
pMyFrame->Create(NULL,“My Frame Window”);
或
MyFrame.Create(NULL,“My Frame Window”);
Create函数的原形如下:
BOOL Create( LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle = WS_OVERLAPPEDWINDOW, const RECT& rect = rectDefault, CWnd* pParentWnd = NULL, LPCTSTR lpszMenuName = NULL, DWORD dwExStyle = 0, CCreateContext* pContext = NULL );
Create函数第一个参数为窗口注册类名,它指定了窗口的图标和类风格。这里我们使用NULL做为其值,表明使用缺省属性。第二个参数为窗口标题。其余几个参数指定了窗口的风格、大小、父窗口、菜单名等。
这个函数看起来比较复杂,对于CFrameWnd派生出来的窗口,我们可以使用LoadFrame从资源文件中创建窗口,它只需要一个参数。
pMyFrame->LoadFrame(IDR_FRAME);
LoadFrame使用该参数从资源中获取许多默认值,包括主边框窗口的标题、图标、菜单、加速键等。但是,在使用LoadFrame时,必须确保标题字符串、图标、菜单、加速键等资源使用同一个ID标识符(在本例中,我们使用IDR_FRAME)。
提示:在Hello程序的InitInstance中我们看不到创建窗口的过程。实际上,在
pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(CHelloDoc),RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CHelloView));
AddDocTemplate(pDocTemplate);
程序片段中,我们看到,CSingleDocTemplate构造函数的第二个参数就是IDR_MAINFRAME。在构造函数内部,已经通过调用m_pMainWnd->LoadFrame(IDR_MAINFRAME),完成了应用程序主窗口的创建过程。
在InitInstance中,创建完窗口后,窗口调用ShowWindow成员函数来显示窗口。ShowWindow带一个参数,指示窗口以何种方式显示(最大化、最小化或一般)。缺省方式为SW_SHOW,但实际上我们经常希望应用程序启动时窗口最大化,此时可以将该参数该为SW_SHOWMAXMIZED,即调用
m_pMainWnd->ShowWindow(SW_SHOWMAXIMIZED);
在MainFrm.cpp中,我们还看到CMainFrame类有一个OnCreate方法。OnCreate成员函数定义如清单3.3。当调用Create或CreateEx时,操作系统会向窗口发送一条WM_CREATE消息。这一函数就是用来响应WM_CREATE消息的。
清单3.3 OnCreate成员函数定义
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{if (CFrameWnd::OnCreate(lpCreateStruct) == -1)return -1;
if (!m_wndToolBar.Create(this) ||!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar/n");
return -1; // fail to create
}
if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))){
TRACE0("Failed to create status bar/n");
return -1; // fail to create
}
// TODO: Remove this if you don't want tool tips or a resizeable toolbar
m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
// TODO: Delete these three lines if you don't want the toolbar to
// be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
return 0;
}
在OnCreate函数中,首先调用CFrameWnd的缺省处理方法OnCreate完成窗口创建工作。后面是应用程序主窗口的特定工作,在上面程序中,创建了工具条和状态栏(有关工具条和状态栏编程参见下一章有关内容)。可以在此处加入一些初始化工作,如从INI文件中载入设置,显示Splash Window(启动画面)等。
3 、注册窗口
在传统的Windows C程序中,送给一个窗口的所有消息是在它的窗口函数中处理的。把一个窗口同它的窗口函数联系起来的过程称为注册窗口类。注册窗口包括对窗口指定一个窗口函数(给出窗口函数的指针)以及设定窗口的光标、背景刷子等内容。一个注册窗口类可以被多个窗口共享。注册窗口通过调用API函数RegisterClass来完成。
在MFC下,框架提供了缺省的自动窗口注册过程。框架仍然使用传统的注册类,而且提供了几个标准的注册类,它们在标准的应用程序初始化函数中注册。调用AfxRegisterWndClass全局函数就可以注册附加的窗口类,然后把已经注册的类传给CWnd的Create成员函数。用户可以定制自己的注册过程,以提供一些附加的特性。比如设置窗口的图标、背景、光标等。下面是注册窗口的例子。
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
UINT ClassStyle=CS_VREDRAW|CS_HREDRAW;
cs.style=cs.style&(~FWS_ADDTOTITLE);
cs.lpszClass = AfxRegisterWndClass(ClassStyle,
AfxGetApp()->LoadStandardCursor(IDC_ARROW), (HBRUSH)(COLOR_WINDOW+1),//for brush
AfxGetApp()->LoadIcon(IDR_MAINFRAME));
return TRUE;
}
注册窗口在CFrameWnd的PreCreateWnd方法中完成。从成员函数名字PreCreateWindow中就可以看出来,注册窗口的工作必须在调用Create函数创建窗口之前完成。其中cs.style=cs.style&(~FWS_ADDTOTITLE)指定窗口标题风格,关闭自动添加文档标题的功能。AfxRegisterWndClass指定窗口使用箭头光标、背景刷子使用比窗口颜色标号大一的颜色、图标使用IDR_MAINFRAME标识符指定的图标(当然也可以使用其它图标)。用上面的程序段替换Hello程序MainFrm.cpp中的PreCreateWindow成员函数定义,并重新编译和运行程序。此时,窗口标题变成了Hello,原来令人讨厌的“Untitled-”没有了,因为窗口风格中关闭自动添加当前文件名的风格。
4、 关闭和销毁窗口
框架窗口不仅维护窗口的创建,还管理着窗口的关闭和销毁过程。关闭窗口时,操作系统依次向被关闭的窗口发送WM_CLOSE和WM_DESTROY消息。WM_CLOSE消息的缺省处理函数OnClose将调用DestroyWindow,来销毁窗口;最后,框架调用窗口的析构函数作清理工作并删除C++窗口对象。
不要使用C++的delete操作符来销毁框架窗口,而应当采用CWnd的DestroyWindow成员函数来销毁。DestroyWindow首先删除子窗口,再删除窗口本身。若窗口以变量方式产生(即在堆栈上分配内存),该窗口对象会被自动清除。若对象是用new操作符创建的(也就是在堆上分配内存的),则需要用户自己处理。有关DestroyWindow问题在第五章对话框技术中还要作进一步解释。
OnClose()常用功能:保存窗口的一些状态、工具条状态,提示保存未保存的数据等等。
void CMainFrame::OnClose()
{
SaveBarState( "MyDockState" );//保存工具条状态
CFrameWnd::OnClose();
}
5、 窗口激活
活动窗口必定是一个没有父窗口的顶层窗口,包括框架窗口和对话框。当顶层窗口被激活时,Windows向窗口发送WM_ACTIVATE消息,对此消息的缺省处理是将活动窗口设为有输入焦点。
输入焦点用于表示哪个窗口有资格接收键盘输入消息。带有输入焦点的窗口或是一个活动窗口,或者是该活动窗口的子窗口。当一个顶层窗口获得输入焦点时,Windows向该窗口发送WM_SETFOCUS消息,此窗口可将输入焦点重定位到它的子窗口上。子窗口不会自动获得输入焦点。失去输入焦点的窗口会收到WM_KILLFOCUS消息。当子窗口拥有输入焦点时,父窗口就不会处理键盘输入了。