这一章开始进入MFC的世界了,以一个Wizard生成的单文档类型工程为例,将MFC程序的基本框架进行了剖析,让我们看到了WIN32的影子。
新建立的单文档类型工程Test,包括如下几个类(继承关系也一并列出):
CTestApp----从CWinApp---CWinThread---CCmdTarget---Cobject继承而来,代表应用本身
CAboutDlg----CDialog---CWnd---CCmdTarget---Cobject,帮助对话框
CMainFrame----CFrameWnd---CWnd---CCmdTarget---Cobject 框架类,主窗口(窗口、菜单、工具栏、状态栏)
CTestView---CView---CWnd---CCmdTarget---Cobject 视类,用于数据的显示,外部输入的响应
CTestDoc---CDocument---CCmdTarget---Cobject 数据,在MFC里Document就是数据的意思,MVC的思想是将数据本身与数据的显示分离开来,CView用于数据的显示。CFrameWnd\CView\CDocument组成了文档三人组,在多文档应用里,每打开一个文档即会创建一个三人组。
在工程里,我们没法直接找到WinMain的身影,这是因为这部分对于所有APP都是一样的,FrameWork当然代劳之,在连接阶段将完成WinMain的加入。下面是该MFC程序的生死流程:
在工程里,我们发现有一个很重要的全局变量CTestApp theApp,全局变量的构造必然在main函数执行之前,在其基类CWinApp的构造函数里有如下这句:
CWinApp::CWinApp()
{
......
AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();
pModuleState->m_pCurrentWinApp = this;
}
这里便将我们定义的对象CTestApp交给了FrameWork了。
在WinMain中我们会看到如下代码(为了便于理解,稍做了修改)
int AFXAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLint, int nCmdShow)
{
......
CWinApp *pApp = AfxGetApp(); //Get the point of theApp object
pApp->InitInstance(); //InitInstance is virtual func, so
return(pApp->Run());
}
注意到虚函数InitInstance,我们之前WIN32程序里的一系列动作(设计窗口类、注册、创建窗口、显示、更新、消息循环)都蕴含其中。
在InitInstance中有如下关键代码:
BOOL CTestApp::InitInstance()
{
<span style="white-space:pre"> </span>......
// Register the application's document templates. Document templates
// serve as the connection between documents, frame windows and views.
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CTestView));
AddDocTemplate(pDocTemplate);
// The one and only window has been initialized, so show and update it.
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}
这里使用了MFC的“动态创建”技术来创建三人组对象,并使用CSingleDocTemple管理三者。有了窗口类对象才有窗口一说,切勿本末倒置。接下来进入“设计、注册窗口类以及创建窗口”的环节,在第一章WIN32编程里,我们不是还需要指定窗口类名称么,这里怎么没有传递该信息进去?对于单文档类型这种已知的窗口类型,MFC当然知道。
m_pMainWnd指向的是主窗口对象,View窗口是其子窗口。
pApp->Run其实就是我们的消息循环了。
若我们未对主窗口、视类窗口的窗口过程函数进行修改,那么MFC会调用其默认的。但实际情况,我们程序员的主要职责便是改写窗口过程函数。
至此,这便是一个MFC程序的典型框架。
接下来看一些细枝末节的东西:
(1)在CTestApp中响应来自菜单ID_APP_ABOUT的命令消息,所有继承自CCmdTarget的类均可以响应命令消息。
(2)在CTestDoc中有一个Serialize的虚函数,该函数用于文档的“读写”,这里的读写是针对对象级的,读取时可以使用MFC的动态创建技术实现对象的恢复。
本章的最后举了一个在主窗口、视类窗口显示一个按钮的例子,用于说明主窗口的工作区以及主窗口、视类窗口之间的关系。CButton m_btn是谁的成员不重要,重要的是调用其Create时,使用的父窗口是谁。
int CTestView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: Add your specialized creation code here
m_btn.Create("按钮",WS_CHILD | BS_DEFPUSHBUTTON,CRect(0,0,100,100),GetParent(),123);
// m_btn.Create("按钮",WS_CHILD | BS_DEFPUSHBUTTON,CRect(0,0,100,100),this,123);
m_btn.ShowWindow(SW_SHOWNORMAL);
return 0;
}