MFC是一个编程框架
MFC中的各种类结合起来构成了一个应用程序框架,它的目的就是让程序员在此基础上来建立Windows下的应用程序。MFC框架定义了应用程序的轮廓,并提供了用户接口的标准实现方法。AppWizard可以用来生成初步的框架文件。资源编辑器用于帮助直观的设计用户接口。ClassWizard用来协助添加代码到框架文件,最后,通过类库实现了应用程序特定的逻辑。
MFC提供了一个Windows应用程序开发模式,对程序的控制主要是由MFC框架完成的。而且MFC也完成了大部分的功能,预定义或实现了许多事件和消息处理。框架或者由其本身处理事件,不依赖程序员的代码,或者调用程序员的代码来处理应用程序特定的事件。
1.S DI生成
1.步骤dxq2009
首先,打开VC++6.0开发环境,然后,选择”File”菜单中的“New”子菜单,在弹出的对话框中选择“MFC AppWizard(exe)”项并在“Progect name”编辑框中输入合适的工程名字Simple1,如图,它的意思是创建一个基于MFC的应用,接着进入正式的创建过程,MFC 应用程序的创建过程有6步(基于对话框)或者6步(SDI或者MDI),下面首先介绍SDI 应用的创建过程。
(1)第一步用于选择应用的结构以及语言等。如图1,首先确定应用是否需要Doc/View Architecture Support支持,因为不使用该结构的应用不支持从磁盘文件打开文档,也没有派生于类CWnd的窗口客户区。上面3个单选按钮用于确定创建的应用类型,
包括单文档,多文档,对话框,这里选择第一个。然后从资源列表框选择应用所使用的语言种类,单击“Next”。
图1
(2)第二步为用用程序选择4项数据库支持选项之一:如图2.如果选择了数据库支持,那么单击“Data Source”按钮,选择外部的数据库表项,一般按默认即可,单击“Next”。
图2
(3)第三步选择希望包含在应用中的复合文档支持项,同时判定是否启用标准的ActiveX 资源,以及是否为应用的菜单条添加额外的自动化命令等,如图4,一般安默认,单击“Next”
图4
(4)第四步用于选择应用所需的基本用户接口特征,以及所想使用的工具栏类型,如图5,如果想要修改应用所使用的文件名和扩展名,或者想要调整应用的用户接口和框架风格,就单击“Advanced”,然后修改,一般默认,单击“Next”。
图5
(5)第五步设置工程的风格,Explorer风格的应用类似于资源管理器,标准MFC风格带有文件视图区域,还要判定是否希望应用向导在源文件中生成注释,最后选择MFC库时动态链接还是静态链接,如图6单击“Next”。
图6
(6)第六步可以更改由应用向导提供的默认类型,基类,头文件和实现文件名,对于视图,还可以更改它的基类,如图7,一般默认,单击”Finish”,在弹出的工程信息对话框中点击“OK”即结束应用的创建过程。
图7
2.MFC工程的成员类及全局对象
应用向导可以自动地生成MFC应用的各个C++类,另外,还能自动的生成一个类APP的全局对象theApp,如图8下面做简要说明。
图8
1.应用类及全局对象(CCExcmpleApp)
应用类封装了Windows应用的初始化,运行以及终止的全过程。对于每一个基于框架的应用,它必须有一个且只能有一个派生于CWinApp的类对象。这个对象是全局对象,因此它在创建任何窗口前首先被构造。类CWinApp提供了几个关键的可重载的虚成员函数,他们是InitInstance,Run,ExitInstance以及OnIdle等。而且,在程序中可以随时调用全局函数AfxGetApp,以便获得CWinApp类对象的指针。
2.文档类(CCExcmpleDoc)
文档类实际上是一种数据结构,该类实现了对这种结构的封装以利于管理,通常,它不但包含应用中所需的数据,而且也包含了处理这些数据的方法,另外,文档类还可以为应用提供与其存储的数据相关的服务。
3.视图类(CCExcmpleView)
该类占有框架窗口的客户区,主要负责显示文档数据,也为文档对象和用户之间提供了用以交互的可视接口,另外,也完成了与文档打印相关的操作,通常,一般的绘制操作都是在该类中完成,因此有时也称视图类窗口为“绘制窗口”。
4.框架类(CMainFrame)
框架类表示应用程序的主框架窗口,其主要作用是响应标准的窗口消息,不过,它通常先将消息按照一定的次序传递给视图类以及文档类等其他命令处理类,另外,它还为视图类提供可视化的边框,同时也包括标题栏,一些标准的窗口组件等。
5.“关于”对话框类(CAboutDlg)
该类封装了用于显示软件版本,版权等相关信息的“关于”对话框,通常不需要对它进行任
何的编程。而只需要使用对话框资源编辑器对对话框模板进行简单的编辑即可。
3.源文件结构:
应用向导生成的应用程序具有很多原始的功能,例如:打开文件对话框等,而且还可以使用
类向导向某个类添加成员函数或者成员变量,而且类向导可以将添加的成员安排在何时得位
置。应用向导和类向导时怎么样实现这些自动功能呢?下面先浏览一下CCExcmpleView的
头文件:
// CExcmpleView.h : interface of the CCExcmpleView class
//
/////////////////////////////////////////////////////////////////////////////
#if !defined(AFX_CEXCMPLEVIEW_H__4FEB3544_9956_4E4C_93C9_35D40796D187__INCLUDED_) #define AFX_CEXCMPLEVIEW_H__4FEB3544_9956_4E4C_93C9_35D40796D187__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif// _MSC_VER > 1000
class CCExcmpleView : public CView
{
//Constructors
protected: //create from serialization only
CCExcmpleView();
DECLARE_DYNCREATE(CCExcmpleView)
//Attributes
public:
CCExcmpleDoc* GetDocument();
//Operations
public:
//Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CCExcmpleView)
public:
virtual void OnDraw(CDC* pDC); // overridden to draw this view
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CCExcmpleView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
// Generated message map functions
protected:
//{{AFX_MSG(CCExcmpleView)
// NOTE - the ClassWizard will add and remove member functions here.
// DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
#ifndef _DEBUG // debug version in CExcmpleView.cpp
inline CCExcmpleDoc* CCExcmpleView::GetDocument()
{ return (CCExcmpleDoc*)m_pDocument; }
#endif
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif
// !defined(AFX_CEXCMPLEVIEW_H__4FEB3544_9956_4E4C_93C9_35D40796D187__INCLUDED_)
该代码的作用是声明CCExcmpleView类,但是这个声明包含在一个“#iif…#define…#endif”
结构内,其目的时保证编译时此文件只被包含一次。下面介绍下头文件的组成部分。主要由
注释块,访问类型以及分界符。
1.注释块:用“//”引导的绿色部分
Constructors块:构造块,用于声明该类的C++构造函数,以及所需的各种初始化函数。Attributes块: 共性或属性快,用于包含对象的共性或属性,
Operations块:操作块,用于包含成员函数,可以通过对象调用这些函数,以使该对象执行
需要的任务或操作,
Overridables块:重载块,该块用于包含虚函数,当需要更改基类的行为时,可以在派生类
中重载这些函数。
Implementation块;实现块,是MFC类声明中最重要的部分。实现块包括所有的实现信息,
包括成员变量和成员函数。
2.访问类型。
Public ,protected,private
3.分界符:
从上面的代码可以看到如“//{{AFX_MSG(CCExcmpleView)”等的标识符,类向导使用几种特殊的分界符,用以区分向导生成的代码和用户输入的代码,这些格式化的分界符以注释的形式出现在代码中。如下所示:
“DECLARE_DYNCREA TE(CCExcmpleView)”是MFC为支持该类的动态创建而提供的宏。
4.应用程序类:MFC程序的启动过程:终止过程
1.全局对象的产生:
全局对象在名为Global的文件夹中,此时只有一个theApp,从C++的学习中可以了解到,当操作系统将程序加载并激活时,全局对象将首先获得配置,因此其构造函数将首先被执行,也即时说它比WinMain更早,下来看这个构造函数到底做了什么。
CCExcmpleApp theApp;
从程序中看到,它自己的构造函数只是完成用户自定义的变量的初始化,而运行环境的初始化是在它的基类中完成的,它的基类时CWinApp,它的构造函数定义如下:
{//参数时Windows使用的应用名称
if (lpszAppName != NULL)
m_pszAppName = _tcsdup(lpszAppName);
else
m_pszAppName = NULL;
// initialize CWinThread state初始化CWinThread state状态
AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();
AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
ASSERT(AfxGetThread() == NULL);
pThreadState->m_pCurrentWinThread = this;
ASSERT(AfxGetThread() == this);
m_hThread = ::GetCurrentThread();
m_nThreadID = ::GetCurrentThreadId();
// initialize CWinApp state初始化CWinApp状态
ASSERT(afxCurrentWinApp == NULL);
// only one CWinApp object please存储theApp对象的指针
pModuleState->m_pCurrentWinApp = this;
ASSERT(AfxGetApp() == this);
// in non-running state until WinMain直到运行WinMain时,才是真正意义上的运行状态m_hInstance = NULL;
m_pszHelpFilePath = NULL;// 应用程序帮助文件的路径
m_pszProfileName = NULL;
m_pszRegistryKey = NULL;
m_pszExeName = NULL;
m_pRecentFileList = NULL;
m_pDocManager = NULL;
m_atomApp = m_atomSystemTopic = NULL;
m_lpCmdLine = NULL;
m_pCmdInfo = NULL;
// initialize wait cursor state
m_nWaitCursorCount = 0;
m_hcurWaitCursorRestore = NULL;
// initialize current printer state
m_hDevMode = NULL;
m_hDevNames = NULL;
m_nNumPreviewPages = 0; // not specified (defaults to 1)
// initialize DAO state
m_lpfnDaoTerm = NULL; // will be set if AfxDaoInit called
CWinApp的基类时CWinThread,CWinThread表示具有一个或多个线程的应用程序的主执行线程。
从上面代码可以看到,此函数主要时用来对线程和全局对象的初始化,同时保存theApp对象的指针,这样WinMain可以通过该指针调用它的成员函数,用以初始化和执行该应用.
2.应用程序入口-WinMain函数以及主框架创建
全局对象生成后,系统根据配置的CRT DLL(C-Runtime DLL,C运行时动态链接库)对WinMain函数进行调用,这些工作是由系统自动完成的,WinMain函数的定义如下:
_tWinMain中的_t是为了支持Unicode而定义的映射宏。
从代码里面看到,WinMain只是对另外一个函数AfxWinMain的简单调用,下面看AfxWinMain函数的实现过程。如下:
// App global initializations (rare) APP全局初始化dxq
if (pApp != NULL && !pApp->InitApplication())
goto InitFailure;
// Perform specific initializations
if (!pThread->InitInstance())
{
if (pThread->m_pMainWnd != NULL)
{
TRACE0("Warning: Destroying non-NULL m_pMainWnd\n");
pThread->m_pMainWnd->DestroyWindow();
}
nReturnCode = pThread->ExitInstance();
goto InitFailure;
}
nReturnCode = pThread->Run(); //dxq 进入Run状态
InitFailure:
#ifdef _DEBUG
// Check for missing AfxLockTempMap calls
if (AfxGetModuleThreadState()->m_nTempMapLock != 0)
{
TRACE1("Warning: Temp map lock count non-zero (%ld).\n",
AfxGetModuleThreadState()->m_nTempMapLock);
}
AfxLockTempMaps();
AfxUnlockTempMaps(-1);
#endif
AfxWinTerm(); //dxq终止应用运行并注销环境
return nReturnCode;
}
从代码看出,该函数主要调用了4个关键的函数:AfxWinInit(),InitApplication(),InitInstance(),Run(),
在此就不详细的介绍这几个函数,有兴趣的学生可以自己区看看。
AfxWinInit():-初始化MFC环境,
主要完成两个任务:其1:初始化全局对象的数据成员,其2,初始化线程指定的数据。
InitApplication():内部管理
InitInstance():应用的初始化
讲解:每当应用程序启动一个新的实例时,WinMain就调用InitInstance,从概念上讲,应用
的初始化过程可分为两个部分,程序第一次运行时,将进行应用级的初始化,当程序的一个副本或“实例”运行时,它将进行实例的初始化。此函数时虚函数,而且在CWinApp中是一个空函数,因此,在派生类中必须对此函数进行重载,应用向导会完成这个任务。下面是它的函数体:
BOOL CCExcmpleApp::InitInstance()
{
。。。。。。。
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CCExcmpleDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CCExcmpleView));
AddDocTemplate(pDocTemplate);
//dxq 解析命令行为标准的外壳命令
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// 发送指定的命令
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// 惟一的窗口已经被初始化,显示并更新
m_pMainWnd->ShowWindow(SW_SHOW);
//更新窗口发送WM_PAINT 消息
m_pMainWnd->UpdateWindow();
return TRUE;
}
主框架创建
此函数完成了MFC程序的大部分任务,如命令解析,框架,视图乃至文档的生成等,但是,这些过程对开发人员来说时不透明的,他们被MFC封装起来。
在应用程序开始执行时,由APP类辅助解析命令行,执行下面两行代码,接着执行ProcessShellCommand函数,,在这个函数里面调用了一系列封装的函数,主要完成创建一个主框架窗口,学员可以进入函数了解一下。再次略去。至此,主框架就创建好了,主框架的显示以及更新也在InitInstance函数中。在主框架产生之际会发出WM_CREATE消息,因此
return -1;
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FL YBY | CBRS_SIZE_DYNAMIC) ||
!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: 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;
}
工具栏和状态栏分别由CToolBar和CStatusBar创建,两个对象属于主窗口。为了拦截WM_CREATE,首先需要在MessageMap中设定“映射项目”;
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
// NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code !
ON_WM_CREATE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
ON_WM_CREATE这个宏表示,只要WM_CREATE发生,OnCreate就会调用,就会创建工具栏和状态栏。
至此,程序就已经启动起来了,启动完毕后,程序进入挂起状态。
3.对象的创建
SDI应用通常包括几个重要的对象:文档模板,文档,框架窗口以及视图等。应用对象负责创建文档模板,而文档模板负责创建文档和框架窗口,框架窗口负责创建视图对象,其先后顺序为:文档模板->文档->框架窗口->视图。
对象创建完成之后,
4.程序的挂起:
Run():程序挂起。
Run()函数通过消息循环,检查消息队列中是否有需要处理的消息,如果有消息需要处理,则Run()就获取-翻译—分发它,如果没有任何消息需要处理,则Run调用OnIdle以便执行用户或框架需要完成的空闲时间处理如果没有任何消息,也没有任何可执行的空闲处理,则应用程序一直等待消息产生,应用也就被挂起。
下面时函数定义:
该函数没有做处理,主要调用基类的CWinThread::Run();
int CWinThread::Run()
{
ASSERT_V ALID(this);
// for tracking the idle time state
BOOL bIdle = TRUE;
LONG lIdleCount = 0;
// dxq 获取并分发消息直到收到一个WM_QUIT消息
for (;;)
{
// dxq 第一阶段:检查是否可以在空闲做一些工作
while (bIdle &&
!::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)) {
// 当为空闲时调用OnIdle
if (!OnIdle(lIdleCount++))
bIdle = FALSE; // assume "no idle" state
}
// 第二阶段:当可以从队列中得到消息时提取消息
do
{
// 提取消息当为WM_QUIT时退出
if (!PumpMessage())
return ExitInstance();
// 提取正常消息,重置空闲状态
if (IsIdleMessage(&m_msgCur))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
}
ASSERT(FALSE); // not reachable
通过该函数的实现,可以理解消息循环的过程,和SCK不太一样,它是封装在MFC下的,实际上,它在PumpMessage函数的定义中,学员下去可以自己看看CWinThread中的PumpMessage函数。这个函数需哎哟首先从消息队列中提取消息,接着调用PreTranslateMessage虚函数过滤窗口消息。此处不详细讲解啦。
5.MFC的终止过程
应用处挂起状态时,如果不小心单击了“关闭”按钮,或者用键盘或鼠标从系菜单中选择关闭,这时,系统都会给窗口过程发送一个WM_SYSCOMMAND消息,窗口过程将这个消息传给默认的窗口过程,而默认的窗口过程会给窗口过程发送一个WM_CLOSE消息来响应,窗口过程再次将它传给默认的窗口过程,默认窗口过程调用DestroyWindow来响应这个WM_CLOSE消息,此后,DestroyWindow将导致Windows给窗口过程发送一个WM_DESTROY消息,此消息导致窗口过程再调用PostQuitMessage,将一个WM_QUIT消息置入消息队列中,以此来响应此消息,Run函数收到WM_QUIT消息后,会结束内部的消息循环,然后调用ExitInstance()函数,最后回到AfxWinMain(),执行AfxWinTerm,以此来终止程序的运行。
6.MFC程序流程小结
1. Windows将用户程序装入内存。
2. 构造全局对象theApp,在程序被装入时,所有全局对象都会立刻被创建。
3. Windows调用全局函数WinMain,它是类库的惟一实例
4. WinMain里面只调用函数AfxWinMain,
5. AfxWinMain执行AfxWininit,调用AfxinitThred,接着
6. AfxWinMain执行InitApplication,然后执行Initinstance,Initinstance是CWinApp的虚函数,在此改写。
7. InitInstance函数里面启动文档的装入以及主要框架和视图显示处理过程。
8 .在这里new 一个CMyFrameWnd ,CMyFrameWnd构造函数调用Create产生主窗口
9. InitInstance 执行ShowWindow,UpdateWindow,发出WM_PAINT
10. WinMain调用theApp的Run函数,它启动窗口消息和命令消息的传递处理过程。11:单击file/close,则发出WM_CLOSE
12:CMainFrame交默认处理
13:调用::DestroyWindow发出WM_DESTROY
14 :默认处理调用::postQuitMessage 发出WM_QUIT
15: CWinapp::Run收到WM_QUIT结束内部循环,调用ExitInsance(若CCExcmpleApp改写Exitinstance,则调用CCExcmpleApp::ExitInstance;
16. ExitInstance函数负责完成应用程序结束之前的清除工作。
17 . ExitInstance函数返回时,Run函数也返回了,MFC完成了一些清除工作,Windows 终止应用程序
18. 回到AfxWinMain,执行AfxWinTerm,程序结束!!
5.文档/视图类
文档视图类以及主框架之间的关系
文档视图较好的实现了数据显示和数据操作的分离,具体的说,用户对数据所做的任何改变的都是由文档类负责管理的,而视图通过调用此接口,以实现对数据的访问和更新。
框架窗口,文档,视图他们之间的关系如下图所示:
从上图可以看到,视图占据了框架窗口的客户区,框架窗口只是相当于视图的容器。这样,即使直接在框架窗口的客户区内执行绘制操作,在屏幕上也不会由任何的输出信息,输出被视图所覆盖。必须通过视图显示应用输出。
注意:文档至少应有一个相关的视图,相反,视图只能与一个文档向关联。CCExcmpleDoc类的基类为CDocument类,CDocument类为用户定义的文档类提供了基本的功能,框架通过使用CDocument提供的接口来操作文档,用户通过与文档相关联的CView 对象来与之交互。
CCExcmpleView类的基类为CView,CView类为用户定义的视图类CCExcmpleView提供了
基本功能,视图是数据的用户窗口,为用户提供了文档数据的可视显示,它在窗口中显示文档的内容,视图还给用户提供了一个与文档中的数据交互的界面,它把用户的输入转换为对文档中的数据的操作。每个文档都会与一个或多个视图相关联,甚至可以与多个不同的视图相关联。
视图类里面的几个重要函数:
解析:每个视图对象只有一个文档与其相关联,用户可以通过该视图对象的成员函数GetDocument获取与其关联的文档,然后,就可以在视图类中对文档类的公有成员函数及成员变量进行访问。
解析:在视图与文档关联后,在视图显示之前,或者当用户选择了“File|New”或“File|Open”时,框架就会调用此虚函数,它调用基类的OnInitialUpdate();函数。
它的功能主要包括:读取文档数据,然后对视图对象的数据成员或控制进行更新,以便反映文档的便哈,还可以使视图的部分客户区无效,就有WM_PAINT消息产生,从而触发对函
数OnDraw的调用,利用更新后的文档数据对窗口进行重绘。
OnDraw函数:此函数为虚函数,必须在派生类中重载此函数,它主要由框架来调用,以呈现文档数据,依据其参数不同,可以分别完成屏幕显示,打印以及打印预览等任务,在MFC 应用程序中,几乎所有的绘制操作都是在OnDraw中完成的(鼠标绘制除外)。