MFC编程(转)
微软基础类库(MFC:Microsoft Foundation Class)是微软为Windows程序员提供的一个面向对象的Windows编程接口,它大大简化了Windows编程工作。使用MFC类库的好处是:首先,MFC提供了一个标准化的结构,这样开发人员不必从头设计创建和管理一个标准Windows应用程序所需的程序,而是“站在巨人肩膀上”,从一个比较高的起点编程,故节省了大量的时间;其次,它提供了大量的代码,指导用户编程时实现某些技术和功能。MFC库充分利用了Microsoft开发人员多年开发Windows程序的经验,并可以将这些经验融入到你自己开发的应用程序中去。
对用户来说,用MFC开发的最终应用程序具有标准的、熟悉的Windows界面,这样的应用程序易学易用;另外,新的应用程序还能立即支持所有标准Windows特性,而且是用普通的、明确定义的形式。事实上,也就是在Windows应用程序界面基础上定义了一种新的标准——MFC标准。
为了更好的理解MFC,我们有必要了解一下MFC的历史。
2.4.1 MFC历史
开始,Microsoft建立了一个AFX小组,AFX代表Application Framework,即应用程序框架。据说创建该小组原意是为了发布一个Borland C++的OWL的竞争性产品,因为那时侯Borland 公司的应用程序框架OWL(object Windows Language)已经做的相当成功。AFX小组象OWL那样,提出了一个高度抽象Windows API的一个类库。
他们采用自顶向下的设计方法,逐步将对象抽象出来,并施加到Windows上。然后,他们试着花了几个月时间用这个类库来编写应用程序,结果发现这个类库偏离Windows API实在太远,过分抽象并没有太大的实用性,相反大大降低了应用程序的效率。
于是,他们干脆放弃了整个AFX类库,对类库进行重新设计。这次,他们采用了自底向上的方法,从已有的Windows API着手,将类建立在Windows API对象基础上,设计出后来成为MFC1.0的一个类库。但是,你现在仍然可以看到AFX时期的痕迹,许多源程序文件有afx前缀,如afxabort.cpp,afxmem.cpp。MFC延用了许多AFX类库的宏,因此我们经常会看到以AFX开头的宏。
AFX小组实际上做了两件工作:MFC类库和对MFC的IDE支持(即资源编译器和操作向导)。在1994年4月份之后,AFX的名字停止使用,该小组成员成为Visual C++开发组的一部分,即现在的MFC小组。
MFC1.0版于1992年同Microsoft C/C++7.0同时发布。它提供了对Windows API简单的抽象和封装,还没有我们现在常用的文档/视结构特性。但它引入了CObject,通过CArchive的持续化和其他一些MFC中仍然使用的特性,从而奠定了MFC的基础。
MFC2.0在MFC1.0基础上增加了文档/视结构框架、OLE1.0类、消息映射和公用对话框类,废弃了1.0版中的CModalDialog类并将它的功能移入到CDialog中,并增加了工具条、对话条、分割视窗的支持。MFC2.1随同Visual C++ 1.1 for NT发布,它把MFC2.0移植到了Win32上。MFC2.5随同Visual C++1.5一起发布,它引入了OLE 2.0和ODBC类。它是最后的官方的16位发行版,于93年12月发布。目前,在开发16位Windows程序时,Visual C++1.5和MFC 2.5仍然有大量的用户。随后的MFC2.51、2.52纠正了MFC.25中的一些错误,增加了标签式对话框、WinSock和MAPI(Microsoft 电子邮件应用程序接口)支持。MFC3.1同Visual C++2.1一起于1995年1月份发布,它引入了Windows95公共控件(包括动画、热键、图象列表、工具条提示等等)。MFC4.0于1995年12月份同Visual C++4.0一起发布。Microsoft直接从Visual C++2.0一下子跳过一个版本号,升级到了4.0,以保持MFC版本号和Visual C++版本号的一致性,但这种一致性又在Visual C++5.0中打破了。在MFC4.0中增加了CSynchronize,CMutex,CEvent,CMultiLock,CShellNew以更好的支持多线程以及Windows 95的其他一些特性。Visual C++还引入了Component Gallery(组件画廊)、STL支持和大量的新特性。MFC4.1最重要的特性是支持Win32s。许多MFC开发者一直都在使用该版本。MFC4.1修正了4.0的一些错误并增加了Internet特性。MFC4.2增加了ISAPI和OCX容器支持。
MFC4.21于1997年3月19日同Visual C++5.0一起发布,它是目前最新和最完善的MFC版本。它只增加了对微软的IntelliMouse(智能鼠标器)的支持。现在MFC版本号又不与Visual C++匹配了。
MFC发行版列表如下:
MFC Release MSVC Release 16位或32位 备注
1.0 16 简单的 封装Windows
2.0 1.0 16 增加了文档/视结构
2.1 1.1 for NT 32 第一个NT的发行版
2.5 1.5 16 OLE/ODBC,最后一个
16位版本
2.51 2.0 16 修正错误
2.52 2.1 16 增加标签式对话框
2.52b 2.2 16
2.5c 4.0 16
3.0 2.0 32 标签式对话框、可停泊工具条
3.1 2.1 32 Winsock/MAPI, Windows公共控 制
3.2 2.2 32
4.0 4.0 32 Win 95, 线程类, OCX 容器
4.1 4.1 32 sweeper (WinInet) classes
**以上是最后支持Win32s的版本
4.2 4.2 32 修正错误, ISAPI classes
4.2b internet dl 32 修正错误
4.21 5.0 32 IntelliMouse™ support.
2.4.2 MFC类库概念和组成
类库是一个可以在应用中使用的相互关联的C++类的集合。类库有些随编译器提供,如Borland C++ Turbo Vision等;有的是由其他软件公司销售,如用于数据库开发的CodeBase;有的则是由用户自己开发的。比如图象处理类库完成图象显示、格式转换、量化等;串行通信类库用于支持串行口输入输出。有些情况下用户可以直接利用类库中包含的类定义应用程序所需的变量,有时则需要从类库所提供的类中派生出新的类,这依赖于类库的设计和具体的应用程序。
Microsoft提供了一个基础类库MFC,其中包含用来开发C++和C++ Windows应用程序的一组类。基础类库的核心是以C++形式封装了大部分的Windows API。类库表示窗口、对话框、设备上下文、公共GDI对象如画笔、调色板、控制框和其他标准的Windows部件。这些类提供了一个面向Windows中结构的简单的C++成员函数的接口。
MFC可分为两个主要部分:(1)基础类(2)宏和全程函数。
MFC基础类
MFC中的类按功能来分可划分为以下几类:
基类
应用程序框架类
应用程序类
命令相关类
文档/视类
线程类
可视对象类
窗口类
视类
对话框类
属性表
控制类
菜单类
设备描述表
绘画对象类
通用类
文件
诊断
异常
收集
模板收集
其他支持类
OLE2类
OLE基类
OLE可视编辑包装程序类
OLE 可视编辑服务器程序类
OLE数据传输类
OLE对话框类
其他OLE类
数据库类
宏和全局函数
若某个函数或变量不是某个类的一个成员,那么它是一个全程函数或变量。
Microsoft基本宏和全程函数提供以下功能:
数据类型
运行时刻对象类型服务
诊断服务
异常处理
CString格式化及信息框显示
消息映射
应用消息和管理
对象连接和嵌入(OLE)服务
标准命令和Windows IDs
约定:全程函数以“Afx”为前缀,所有全程变量都是以“afx”为前缀,宏不带任何特别前缀,但是全部大写。
常见的全局函数和宏有:AfxGetApp,AfxGetMainWnd,AfxMessageBox,DEBUG_NEW等,我们会在后面的章节中用到并对它们进行介绍。
从继承关系来看,又可将MFC中的类分成两大类:大多数的MFC类是从CObject继承下来;另外一些类则不是从CObject类继承下来,这些类包括:字符串类CString,日期时间类CTime,矩形类CRect,点CPoint等,它们提供程序辅助功能。
由于MFC中大部分类是从CObject继承下来的,CObject类描述了几乎所有的MFC中其他类的一些公共特性,因此我们有必要理解CObject类。
我们首先查看一下CObject类的定义,CObject类定义如下清单2.1所示:
清单2.1CObject类的定义
// class CObject is the root of all compliant objects
class CObject
{
public:
// Object model (types, destruction, allocation)
virtual CRuntimeClass* GetRuntimeClass() const;
virtual ~CObject(); // virtual destructors are necessary
// Diagnostic allocations
void* PASCAL operator new(size_t nSize);
void* PASCAL operator new(size_t, void* p);
void PASCAL operator delete(void* p);
#if defined(_DEBUG) && !defined(_AFX_NO_DEBUG_CRT)
// for file name/line number tracking using DEBUG_NEW
void* PASCAL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
#endif
// Disable the copy constructor and assignment by default so you will get
// compiler errors instead of unexpected behaviour if you pass objects
// by value or assign objects.
protected:
CObject();
private:
CObject(const CObject& objectSrc); // no implementation
void operator=(const CObject& objectSrc); // no implementation
// Attributes
public:
BOOL IsSerializable() const;
BOOL IsKindOf(const CRuntimeClass* pClass) const;
// Overridables
virtual void Serialize(CArchive& ar);
// Diagnostic Support
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
// Implementation
public:
static const AFX_DATA CRuntimeClass classCObject;
#ifdef _AFXDLL
static CRuntimeClass* PASCAL _GetBaseClass();
#endif
};
CObject类为派生类提供了下述服务:
对象诊断
MFC提供了许多诊断特性,它可以:
输出对象内部信息:CDumpContext类与CObject的成员函数Dump配合,用于在调试程序时输出对象内部数据。
对象有效性检查:重载基类的AssertValid成员函数,可以为派生类的对象提供有效性检查。
运行时访问类的信息:
MFC提供了一个非常有用的特性,它可以进行运行时的类型检查。如果从CObject派生出一个类,并使用了以下三个宏(IMPLEMENT_DYNAMIC,IMPLEMENT_ DYNCREATE或IMPLEMENT_SERIAL)之一,就可以:
运行时访问类名
安全可靠的把通用的CObject指针转化为派生类的指针
比如,我们定义一个主窗口类
CMyFrame:public CFrameWnd
{
......
}
然后我们使用这个类:
CMyFrame *pFrame=(CMyFrame*)AfxGetMainWnd();
pFrame->DoSomeOperation();
AfxGetMainWnd是一个全局函数,返回指向应用程序的主窗口的指针,类型为CWnd*,因此我们必须对它进行强制类型转换,但我们如何知道是否转换成功了呢?我们可以使用CObject的IsKindOf()成员函数检查pFrame的类型,用法如下:
ASSERT(pFrame->IsKindOf(RUN_TIMECLASS(CMyFrame)));
将上一语句插入到pFrame-> DoSomeOperation()之前,就可以在运行时作类型检查,当类型检查失败时,引发一个断言(ASSERT),中断程序执行。
对象持续性
通过与非CObject派生的档案类CArchive相结合,提供将多个不同对象以二进制形式保存到磁盘文件(Serilization)中以及根据磁盘文件中的对象状态数据在内存中重建对象(Deserilization )的功能。
然而,MFC不仅仅是一个类库,它还提供了一层建立在Windows API的C++封装上的附加应用程序框架。该框架提供了Windows程序需要的多数公共用户界面。
所谓应用程序框架指的是为了生成一般的应用所必须的各种软组件的集成。应用框架是类库的一种超集。一般的类库只是一种可以用来嵌入任何程序中的、提供某些特定功能(如图象处理、串行通信)的孤立的类的集合,但应用框架却定义了应用程序的结构,它的类既相互独立,又相互依赖,形成一个统一的整体,可以用来构造大多数应用程序。中国用户熟悉的Borland C++的DOS下的Turbo Vision和Windows下OWL(Object Windows Language)都是应用框架的例子。
下面我们举个具体的例子来说明MFC所提供的应用程序框架,程序如清单2.2。
清单2.2应用程序框架示例
#include<afxwin.h>
//derived an application class
class CMinMFCApp:public CWinApp
{
public:
BOOL InitInstance();
};
//Derive the main window class
class CMainWindow:public CFrameWnd
{
public:
CMainWindow();
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CMainWindow,CFrameWnd)
END_MESSAGE_MAP()
/*CMinMFCApp Member Functions*/
BOOL CMinMFCApp::InitInstance()
{
m_pMainWnd=new CMainWindow();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
/*CMainWindow member functions*/
CMainWindow::CMainWindow()//constructor
{
Create(NULL,
"Min MFC Application",
WS_OVERLAPPEDWINDOW,
rectDefault,
NULL,
NULL);
}
/*an instance of type CMinMFCApp*/
CMinMFCApp ThisApp;
清单2.2程序段定义了一个最小的MFC应用程序所需的框架程序。其中声明了CMinMFCApp类,它是从应用程序类CWinApp中派生下来的;和窗口CMainWindow类,它是从框架窗口CFrameWnd类派生出来。我们还用CMinMFCApp定义了一个全局对象ThisApp。读者也许会问,为什么没有WinMain函数?因为MFC已经把它封装起来了。在程序运行时,MFC应用程序首先调用由框架提供的标准的WinMain函数。在WinMain函数中,首先初始化由CMinMFCApp定义的唯一的实例,然后调用CMinMFCApp继承CWinApp的Run成员函数,进入消息循环。退出时调用CWinApp的ExitInstance函数。
由上面的说明可以看到,应用程序框架不仅提供了构建应用程序所需要的类(CWinApp,CFrameWnd等),还定义了程序的基本执行结构。所有的应用程序都在这个基本结构基础上完成不同的功能。
MFC除了定义程序执行结构之外,还定义了三种基本的主窗口模型:单文档窗口,多文档窗口和对话框作为主窗口。
Visual C++提供了两个重要的工具,用于支持应用程序框架,它们就是前面提到AppWizard和ClassWizard。AppWizard用于在应用程序框架基础上迅速生成用户的应用程序基本结构。ClassWizard用于维护这种应用程序结构。
2.4.3 MFC的优点
Microsoft MFC具有以下不同于其它类库的优势:
完全支持Windows所有的函数、控件、消息、GDI基本图形函数,菜单及对话框。类的设计以及同API函数的结合相当合理。
使用与传统的Windows API同样的命名规则,即匈牙利命名法。
进行消息处理时,不使用易产生错误的switch/case语句,所有消息映射到类的成员函数,这种直接消息到方法的映射对所有的消息都适用。它通过宏来实现消息到成员函数的映射,而且这些函数不必是虚拟的成员函数,这样不需要为消息映射函数生成一个很大的虚拟函数表(V表),节省内存。
通过发送有关对象信息到文件的能力提供更好的判定支持,也可确认成员变量。
支持异常错误的处理,减少了程序出错的机会
运行时确定数据对象的类型。这允许实例化时动态操作各域
有较少的代码和较快的速度。MFC库只增加了少于40k的目标代码,效率只比传统的C Windows程序低5%。
可以利用与MFC紧密结合的AppWizard和ClassWizard等工具快速开发出功能强大的应用程序。
另外,在使用MFC时还允许混合使用传统的函数调用。
我们着重讲解一下MFC对消息的管理,这是编写MFC消息处理程序的基础。
2.4.4 MFC对消息的管理
Windows消息的管理包括消息发送和处理。为了支持消息发送机制,MFC提供了三个函数:SendMessage、PostMessage和SendDlgItemMessage。而消息处理则相对来说显得复杂一些。MFC采用了一种新的机制取代C语言编程时对Windows消息的Switch/Case分支,简化了Windows编程,使程序可读性、可维护性大大提高。
MFC对消息的处理
MFC不使用用C语言编写Windows程序时用的易产生错误的switch/case语句,而采用一种消息映射机制来决定如何处理特定的消息。这种消息映射机制包括一组宏,用于标识消息处理函数、映射类成员函数和对应的消息等。其中,用afx_msg放在函数返回类型前面,用以标记它是一个消息处理成员函数。类若至少包含了一个消息处理函数,那么还需要加上一个DECLARE_MESSAGE_MAP()宏,该宏对程序执行部分所定义的消息映射进行初始化。清单2.3演示了消息处理函数的例子:
清单2.3 消息处理函数例子
class CMainFrame:CFrameWnd{
public:
CMainFrame();
protected:
//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnEditCopy();
afx_msg void OnClose();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
成员函数OnCreate,OnEditCopy,OnClose分别用来处理消息WM_CREATE、ID_EDIT_COPY和WM_CLOSE。其中,WM_CREATE和WM_CLOSE是系统预定义消息,包含在Windows.h中。而ID_EDIT_COPY是菜单Edit->Copy的标识,也就是用户选择Edit->Copy菜单项时产生的消息,一般在资源文件头文件中定义。在类的实现部分给出这三个成员函数的定义,以及特殊的消息映射宏。上面的例子的消息映射宏定义如下:
BEGIN_MESSAGE_MAP(CMainFrame,CFrameWnd)
ON_WM_CREATE()
ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
ON_WM_CLOSE()
END_MESSAGE_MAP()
消息映射宏由BEGIN_MESSAGE_MAP() 和END_MESSAGE_MAP()。其中,BEGIN_MESSAGE_MAP宏包含两个参数CMainFrame类和CFrameWnd,分别代表当前定义的类和它的父类。在BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()之间,包含了主窗口要处理的各个Windows消息的入口。在本例中,包含三个消息。其中ON_ WM_CREATE被用来指定缺省的成员函数OnCreate与WM_CREATE相对应。在MFC中,包含了大量的预定义消息映射宏,用来指定各种成员函数与各种形如WM_XXXX消息相对应。如ON_WM_CLOSE宏指定了WM_CLOSE消息的处理成员函数为OnClose。这时侯,只需要写出要处理的消息就够了,不必再写出处理函数。消息映射宏ON_COMMAND则被用来将菜单项和用户自定义的命令同它们的处理成员函数联系起来。在上例中,用户选择Edit->Copy菜单项时,系统执行OnEditCopy()函数。ON_COMMAND宏的一般定义形式如下:
ON_COMMAND(command,command_function)
其中,command为菜单消息或用户自定义消息,command_function为消息处理函数。MFC允许用户自定义消息,常量WM_USER和第一个消息值相对应,用户必须为自己的消息定义相对于WM_USER的偏移值,偏移范围在0~0x3FFF之间,这对绝大多数程序来说都是够用的。用户可以利用#define语句直接定义自己的消息:
#define WM_USER1 (WM_USER+0)
#define WM_USER2 (WM_USER+1)
#define WM_USER3 (WM_USER+2)
下表列出了Windows95中Windows消息值的范围。
常 量 | 值 | 消息值范围 | 意 义 |
WM_USER | 0x0400 | 0x0000-0x03FF | Windows消息 |
0x0400-0x7FFF | 用户自定义的消息 | ||
0x8000-0xBFFF | Windows保留值 | ||
0xC000-0xFFFF | 供应用使用的字符串消息 |
为了说明如何使用用户自定义消息,我们看一个例子,见程序清单2.4:
清单2.4 使用用户自定义消息
#include<afxwin.h>
#define CM_APPLE (WM_USER+0)
#define CM_ORANGE (WM_USER+1)
class CMainFrame:CFrameWnd{
public:
CMainFrame();
protected:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnClose();
//handle user select apple
afx_msg LRESULT CMApple(WPARAM wParam, LPARAM lParam);
//handle user select orange
afx_msg LRESULT CMOrange(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
};
相应的消息映射如下:
BEGIN_MESSAGE_MAP(CMainFrame,CFrameWnd)
ON_WM_CREATE()
ON_MESSAGE(CM_APPLE, CMApple)
ON_MESSAGE(CM_ORANGE,CMOrange)
ON_WM_CLOSE()
END_MESSAGE_MAP()
第一个ON_MESSAGE宏用于指定 CM_APPLE 命令消息的处理成员函数为CMApple ,而第二个ON_MESSAGE宏用于指定CM_ORANGE命令消息的处理函数为CMOrange。
消息的发送
Windows应用程序允许应用程序向自己发送消息、向其他应用程序发送消息,甚至可以向Windows操作系统本身发送消息(比如要求关闭操作系统或重新启动操作系统)。Windows提供了三个API函数用于发送消息,这三个函数是:SendMessage、PostMessage和SendDlgItemMessage。
SendMessage用于向窗口发送消息,该函数说明如下:
LRESULT SendMessage(
HWND hWnd, //消息要发往的窗口的句柄
UINT Msg, //要发送的消息
WPARAM wParam, //消息的第一个参数
LPARAM lParam //消息的第二个参数
);
其中,hWnd为接收消息窗口的句柄,参数Msg指定发送的消息,参数wParam和lParam依赖于消息Msg。该函数调用目标窗口的窗口函数,直到目标窗口处理完该消息才返回。
PostMessage函数同SendMessage类似,它把消息放在指定窗口创建的线程的消息队列中,然后不等消息处理完就返回,而不象SendMessage那样必须等到消息处理完毕才返回。目标窗口通过GetMessage或PeekMessage从消息队列中取出并处理。PostMessage函数说明如下:
BOOL PostMessage(
HWND hWnd, //消息发往的窗口
UINT Msg, //要发送的消息
WPARAM wParam, //消息的第一个参数
LPARAM lParam //消息的第二个参数
);
其中,参数hWnd为接收消息的窗口的句柄,参数Msg指定所发送的消息,参数wParam和lParam依赖于消息Msg。
SendDlgItemMessage函数用于向对话框的某个控制发送消息,函数声明如下:
LONG SendDlgItemMessage(
HWND hDlg, //对话框句柄
int nIDDlgItem, //对话框控件的ID
UINT Msg, //要发送的消息
WPARAM wParam, //消息的第一个参数
LPARAM lParam //消息的第二个参数
);
其中,hDlg为包含目标控制的对话框的窗口句柄,参数nIDDlgItem为接收消息的对话框控制的整数标识符,参数Msg指定了所发送的消息,参数wParam和lParam提供附加的特定消息的信息。
MFC将这三个函数封装为CWnd类的成员函数,隐藏了窗口句柄和对话框句柄。这三个成员函数用于向本窗口发送消息,函数的说明如下:
LRESULT SendMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );BOOL PostMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );LRESULT SendDlgItemMessage( int nID, UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );
2.4.5学习MFC的方法
首先要对Windows API有一定的了解,否则无法深入学习MFC。至少要知道Windows对程序员来说意味着什么,它能完成什么工作,它的一些常用数据结构等。
另一点是不要过分依赖于Wizards。Wizards能做许多工作,但同时掩饰了太多的细节。应当看看AppWizard和ClassWizard为你所做的工作。在mainfrm.cpp中运行调试器来观察一下MFC运行的流程。除非你理解了生成的代码的含义,否则无法了解程序是如何运行。
还有很重要的一点就是要学会抽象的把握问题,不求甚解。许多人一开始学习Visual C++就试图了解整个MFC类库,实际上那几乎是不可能的。一般的学习方法是,先大体上对MFC有个了解,知道它的概念、组成、基本约定等。从最简单的类入手,由浅入深,循序渐进、日积月累的学习。一开始使用MFC提供的类时,只需要知道它的一些常用的方法、外部接口,不必要去了解它的细节和内部实现,把它当做一个模块或者说黑盒子来用,这就是一种抽象的学习方法。在学到一定程度时,再可以深入研究,采用继承的方法对原有的类的行为进行修改和扩充,派生出自己所需的类。
学习MFC,最重要的一点是理解和使用MFC类库,而不是记忆。