MFC(Microsoft Foundation Classes)是微软公司为Windows应用程序开发提供的一个类库,它是基于C++语言的应用框架,主要用于开发图形用户界面(GUI)应用程序。MFC实现了Windows API的大部分功能,并提供了更高层次的抽象,使开发者能够更轻松地创建Windows应用程序。本文将详细讲解MFC的结构、核心概念、主要类和典型应用场景,并深入探讨其底层原理及消息映射机制。
一、MFC的结构与核心概念
1. MFC的基本结构
MFC的结构包括以下几个主要部分:
- 应用程序框架:负责应用程序的初始化、主消息循环和清理工作。
- 文档/视图架构:用于实现基于文档的应用程序,将数据(文档)和用户界面(视图)分离。
- 用户界面类:用于创建和管理窗口、对话框、控件等用户界面元素。
- GDI类:封装了Windows图形设备接口(GDI),用于绘制图形和文本。
2. 核心概念
- 消息映射 :MFC使用消息映射机制将Windows消息路由到相应的类成员函数。通过宏
BEGIN_MESSAGE_MAP
和END_MESSAGE_MAP
定义消息映射。 - 运行时类信息(RTTI) :MFC使用
CRuntimeClass
实现运行时类型信息,支持动态创建对象和类型检查。 - 动态创建 :通过声明宏
DECLARE_DYNAMIC
和实现宏IMPLEMENT_DYNAMIC
,可以使类支持动态创建。
二、MFC的主要类
1. 应用程序类
- CWinApp :所有MFC应用程序的基类,负责应用程序的初始化和消息循环。每个MFC应用程序都需要从
CWinApp
派生一个类。
2. 窗口类
- CWnd:所有窗口类的基类,封装了窗口的创建、绘制、消息处理等功能。包括顶层窗口、子窗口、对话框等。
- CFrameWnd:单文档界面(SDI)和多文档界面(MDI)框架窗口的基类。
- CMDIFrameWnd:多文档界面框架窗口的基类,支持多个子窗口。
3. 文档/视图类
- CDocument:文档类的基类,负责数据的管理和存储。
- CView:视图类的基类,用于显示和交互文档的数据。
4. 控件类
- CButton:按钮控件类。
- CEdit:编辑框控件类。
- CListBox:列表框控件类。
- ComboBox:组合框控件类。
- CStatic:静态文本控件类。
5. 对话框类
- CDialog:对话框类的基类,用于创建模态和非模态对话框。
- CPropertySheet:属性表类,用于创建包含多个属性页的对话框。
- CPropertyPage:属性页类,用于定义属性表中的每个页。
6. GDI类
- CDC:设备上下文类,封装了绘图设备接口(GDI),用于绘制图形和文本。
- CPen:封装画笔对象,定义线条的样式、颜色和宽度。
- CBrush:封装画刷对象,定义填充区域的样式和颜色。
- CFont:封装字体对象,定义文本的字体样式和大小。
- CBitmap:封装位图对象,用于处理位图像。
三、典型应用场景
1. 基于对话框的应用程序
这是最简单的MFC应用程序类型。基于对话框的应用程序通常用于创建简单的用户界面。以下是创建一个基于对话框的MFC应用程序的基本步骤:
- 使用MFC应用程序向导创建一个新的MFC对话框应用程序。
- 在资源编辑器中设计对话框界面,添加控件(如按钮、编辑框等)。
- 在对话框类中添加控件变量和事件处理函数。
- 实现事件处理函数以响应用户交互。
2. 单文档界面(SDI)应用程序
单文档界面应用程序只能同时打开一个文档。以下是创建一个SDI应用程序的基本步骤:
- 使用MFC应用程序向导创建一个新的MFC SDI应用程序。
- 创建并实现
CDocument
派生类,用于管理文档数据。 - 创建并实现
CView
派生类,用于显示文档数据。 - 在
CView
派生类中实现绘制和用户交互逻辑。
3. 多文档界面(MDI)应用程序
多文档界面应用程序可以同时打开多个文档,每个文档在一个独立的子窗口中。以下是创建一个MDI应用程序的基本步骤:
- 使用MFC应用程序向导创建一个新的MFC MDI应用程序。
- 创建并实现
CDocument
派生类,用于管理文档数据。 - 创建并实现
CView
派生类,用于显示文档数据。 - 在
CView
派生类中实现绘制和用户交互逻辑。 - 在
CMainFrame
类中管理MDI子窗口的创建和销毁。
4. 使用控件创建复杂界面
MFC提供了丰富的控件类,可以使用这些控件创建复杂的用户界面。以下是一些常用控件及其应用场景:
- 按钮(CButton):用于触发操作。
- 编辑框(CEdit):用于输入和显示文本。
- 列表框(CListBox):用于显示项列表。
- 组合框(ComboBox):用于显示可编辑的下拉列表。
- 静态文本(CStatic):用于显示文本或图像。
- 树控件(CTreeCtrl):用于显示树状结构的数据。
- 列表控件(CListCtrl):用于显示带有列标题的数据表格。
四、实践示例
1. 基于对话框的应用程序示例
以下程序示例展示了一个简单的基于对话框的MFC应用程序,包含一个按钮和一个编辑框,详细分解如下:
1)定义应用程序类
首先,我们需要定义一个应用程序类。这个应用程序类通常会继承自CWinApp
,负责应用程序的初始化和运行。
cpp
// MyDialogApp.h
class CMyDialogApp : public CWinApp
{
public:
// 重写InitInstance方法,用于初始化应用程序
virtual BOOL InitInstance();
};
2)定义对话框类
接下来,我们定义一个对话框类,继承自CDialogEx
。这个类将代表我们的主对话框窗口。
cpp
// MyDialog.h
class CMyDialog : public CDialogEx
{
public:
// 构造函数,接受一个可选的父窗口指针
CMyDialog(CWnd* pParent = nullptr);
// 对话框资源ID,用于标识该对话框模板
enum { IDD = IDD_MYDIALOG_DIALOG };
protected:
// 用于在对话框和控件变量之间进行数据交换
virtual void DoDataExchange(CDataExchange* pDX);
protected:
// 声明消息映射宏
DECLARE_MESSAGE_MAP()
public:
// 处理"OK"按钮点击事件的函数
afx_msg void OnBnClickedOk();
// 编辑框控件变量,用于操作编辑框中的文本
CEdit m_edit;
};
3)实现应用程序类
在应用程序类中,我们需要实现InitInstance
方法,该方法是应用程序初始化的入口点。
cpp
// MyDialog.cpp
BOOL CMyDialogApp::InitInstance()
{
// 调用基类的初始化方法
CWinApp::InitInstance();
// 创建对话框实例
CMyDialog dlg;
// 设置主窗口指针为对话框
m_pMainWnd = &dlg;
// 显示模态对话框,并等待用户关闭该对话框
INT_PTR nResponse = dlg.DoModal();
// 返回FALSE以终止应用程序
return FALSE;
}
4) 实现对话框类
在对话框类中,我们需要实现构造函数、数据交换方法以及消息映射和事件处理函数。
cpp
// MyDialog.cpp
// 构造函数
CMyDialog::CMyDialog(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_MYDIALOG_DIALOG, pParent)
{
}
// 数据交换方法,用于在对话框控件和对应的成员变量之间进行数据传递
void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
// 调用基类的DoDataExchange方法
CDialogEx::DoDataExchange(pDX);
// 绑定编辑框控件变量
DDX_Control(pDX, IDC_EDIT1, m_edit);
}
// 消息映射宏,用于将消息路由到类成员函数
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
ON_BN_CLICKED(IDOK, &CMyDialog::OnBnClickedOk) // 绑定"OK"按钮点击事件
END_MESSAGE_MAP()
// 处理"OK"按钮点击事件的函数
void CMyDialog::OnBnClickedOk()
{
// 获取编辑框中的文本
CString text;
m_edit.GetWindowText(text);
// 显示获取到的文本
AfxMessageBox(text);
// 调用基类的OnOK方法,关闭对话框
CDialogEx::OnOK();
}
5)资源文件
最后,我们需要一个资源文件来定义对话框模板和控件,通常在资源编辑器中进行设计。以下是一个简单的资源文件内容示例:
cpp
// MyDialog.rc
IDD_MYDIALOG_DIALOG DIALOGEX 0, 0, 320, 200
STYLE DS_SETFONT | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "My Dialog"
FONT 8, "MS Sans Serif"
BEGIN
DEFPUSHBUTTON "OK",IDOK,209,179,50,14
PUSHBUTTON "Cancel",IDCANCEL,263,179,50,14
EDITTEXT IDC_EDIT1,10,10,300,14,ES_AUTOHSCROLL
END
以上就是一个基于MFC的简单对话框应用程序的完整过程。这个示例展示了如何定义应用程序类和对话框类,并通过消息映射机制处理用户交互。通过掌握这些基础知识,你可以进一步扩展和复杂化你的MFC应用程序。
2. 单文档界面(SDI)应用程序示例
以下代码展示了一个简单的SDI应用程序,包含一个文档和一个视图,详细分解如下:
1)定义应用程序类
首先,我们定义一个应用程序类,继承自CWinApp
。这个类负责应用程序的初始化和运行。
cpp
// MySDIApp.h
class CMySDIApp : public CWinApp
{
public:
// 重写InitInstance方法,用于初始化应用程序
virtual BOOL InitInstance();
};
2) 定义文档类
接下来,我们定义一个文档类,继承自CDocument
。这个类负责管理应用程序的数据。
cpp
// MyDocument.h
class CMyDocument : public CDocument
{
protected:
// 使用动态创建机制声明
DECLARE_DYNCREATE(CMyDocument)
public:
// 重写OnNewDocument方法,用于初始化新文档
virtual BOOL OnNewDocument();
};
3) 定义视图类
然后,我们定义一个视图类,继承自CView
。这个类负责显示文档数据。
cpp
// MyView.h
class CMyView : public CView
{
protected:
// 使用动态创建机制声明
DECLARE_DYNCREATE(CMyView)
public:
// 重写OnDraw方法,用于绘制视图内容
virtual void OnDraw(CDC* pDC);
};
4) 实现应用程序类
在应用程序类中,我们实现InitInstance
方法,该方法是应用程序初始化的入口点。
cpp
// MyApp.cpp
BOOL CMySDIApp::InitInstance()
{
// 调用基类的初始化方法
CWinApp::InitInstance();
// 创建单文档模板并添加到应用程序
AddDocTemplate(new CSingleDocTemplate(
IDR_MAINFRAME, // 主框架的资源ID
RUNTIME_CLASS(CMyDocument), // 文档类
RUNTIME_CLASS(CFrameWnd), // 框架窗口类
RUNTIME_CLASS(CMyView))); // 视图类
// 创建并打开新文档
OnFileNew();
// 返回TRUE以启动应用程序
return TRUE;
}
5)实现文档类
在文档类中,我们实现OnNewDocument
方法,用于初始化新文档。
cpp
BOOL CMyDocument::OnNewDocument()
{
// 调用基类的OnNewDocument方法
if (!CDocument::OnNewDocument())
return FALSE;
// 自定义文档初始化代码可在此处实现
return TRUE; // 返回TRUE表示成功
}
6) 实现视图类
在视图类中,我们实现OnDraw
方法,用于绘制视图内容。
cpp
void CMyView::OnDraw(CDC* pDC)
{
// 获取与视图关联的文档
CDocument* pDoc = GetDocument();
// 在视图中绘制文本
pDC->TextOut(10, 10, _T("Hello, MFC!"));
}
该示例演示了如何使用MFC创建一个简单的单文档界面(SDI)应用程序。应用程序类负责初始化框架和文档模板。文档类管理数据,视图类负责显示数据。通过掌握这些基础知识,你可以进一步扩展和复杂化你的MFC应用程序。
cpp
// MySDIApp.h
class CMySDIApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
// MyDocument.h
class CMyDocument : public CDocument
{
protected:
DECLARE_DYNCREATE(CMyDocument)
public:
virtual BOOL OnNewDocument();
};
// MyView.h
class CMyView : public CView
{
protected:
DECLARE_DYNCREATE(CMyView)
public:
virtual void OnDraw(CDC* pDC);
};
// MyApp.cpp
BOOL CMySDIApp::InitInstance()
{
CWinApp::InitInstance();
AddDocTemplate(new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CMyDocument),
RUNTIME_CLASS(CFrameWnd),
RUNTIME_CLASS(CMyView)));
OnFileNew();
return TRUE;
}
BOOL CMyDocument::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
return TRUE;
}
void CMyView::OnDraw(CDC* pDC)
{
CDocument* pDoc = GetDocument();
pDC->TextOut(10, 10, _T("Hello, MFC!"));
}
五、MFC底层原理
1. MFC的模块结构
MFC主要由三个动态链接库(DLL)组成:
- MFCxx.DLL:提供MFC库的核心功能。
- MSVCRxx.DLL:提供C运行时库。
- MSVCPxx.DLL:提供C++运行时库。
2. MFC的启动流程
MFC应用程序的启动流程可以分为以下几个步骤:
- 程序入口点 :MFC应用程序的入口点通常是
_tWinMain
,它负责初始化应用程序框架。 - 初始化应用程序对象 :
_tWinMain
函数会创建并调用CWinApp
派生类的对象并调用其InitInstance
方法。 - 消息循环 :
CWinApp
类的Run
方法启动应用程序的消息循环,用于处理Windows消息。 - 清理 :
CWinApp
类的ExitInstance
方法负责应用程序的清理工作。
3. 文档/视图架构的工作原理
MFC中的文档/视图架构通过消息映射和动态创建机制将数据和界面分离:
- 文档类(CDocument):负责存储和管理应用程序的数据。
- 视图类(CView):负责显示和交互数据。
- 框架窗口类(CFrameWnd):管理窗口及菜单、工具栏等界面元素。
文档和视图之间通过消息和指针相互通信,框架窗口协调文档和视图的交互。
六、消息映射机制
1. 消息处理的背景
在Windows编程中,消息是操作系统与应用程序之间通信的手段。应用程序通过消息处理函数响应用户输入、窗口事件等。MFC通过消息映射机制将消息分发到相应的类成员函数。
2. 消息映射的实现
MFC消息映射机制通过一系列宏和存储在类中的消息映射表实现。关键的宏包括:
- DECLARE_MESSAGE_MAP():在类声明中使用,声明消息映射表。
- BEGIN_MESSAGE_MAP(TheClass, BaseClass):在类实现中使用,开始消息映射表的定义。
- END_MESSAGE_MAP():在类实现中使用,结束消息映射表的定义。
- ON_WM_*():在消息映射表中使用,映射特定消息到类成员函数。
3. 消息映射的工作流程
以下是MFC消息映射的工作流程:
- 消息接收 :当Windows消息到达应用程序时,Windows将其传递给窗口过程函数
CWnd::WindowProc
。 - 消息分发 :
CWnd::WindowProc
调用CWnd::DispatchMessage
,它查找消息映射表,确定要调用的类成员函数。 - 消息处理:调用相应的类成员函数处理消息。
4. 消息映射的实现细节
在类中添加消息映射表:
cpp
class CMyWnd : public CWnd
{
protected:
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
在类实现中定义消息映射表:
cpp
BEGIN_MESSAGE_MAP(CMyWnd, CWnd)
ON_WM_PAINT()
END_MESSAGE_MAP()
void CMyWnd::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
}
5. 动态消息映射
MFC还支持动态添加消息处理函数,通过宏ON_COMMAND
、ON_UPDATE_COMMAND_UI
等实现。例如:
cpp
BEGIN_MESSAGE_MAP(CMyWnd, CWnd)
ON_COMMAND(ID_FILE_NEW, &CMyWnd::OnFileNew)
END_MESSAGE_MAP()
void CMyWnd::OnFileNew()
{
// Handle the ID_FILE_NEW command
}
6. 自定义消息
自定义消息可以用于应用程序内的自定义通信。定义自定义消息:
cpp
#define WM_MY_MESSAGE (WM_USER + 1)
BEGIN_MESSAGE_MAP(CMyWnd, CWnd)
ON_MESSAGE(WM_MY_MESSAGE, &CMyWnd::OnMyMessage)
END_MESSAGE_MAP()
LRESULT CMyWnd::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
// Handle custom message
return 0;
}
七、深入理解消息映射机制
1. MFC的消息路由
MFC不仅仅是简单的消息映射,它还实现了复杂的消息路由机制,使得消息可以在应用程序的不同部分之间传递。常见的消息路由包括:
- 命令消息:如菜单、工具栏按钮的点击事件。命令消息可以在视图、框架窗口、应用程序类之间传递。
- 通知消息:如控件发送的通知消息。通知消息一般从控件传递到其父窗口。
- 用户自定义消息:用户定义的消息,可以在任意窗口之间传递。
2. 反射消息
MFC支持控件的反射消息,即控件可以将消息反射回发送者。例如,按钮控件可以将BN_CLICKED消息反射到父窗口,父窗口可以处理这个消息。
cpp
BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
ON_BN_CLICKED(IDC_BUTTON1, &CMyDialog::OnButtonClicked)
END_MESSAGE_MAP()
void CMyDialog::OnButtonClicked()
{
// Handle button click
}
3. 消息预处理
MFC提供了消息预处理机制,允许在消息到达窗口过程之前进行处理。常见的方法包括PreTranslateMessage
和OnIdle
。
- PreTranslateMessage:可以在消息派发之前对消息进行预处理,常用于处理键盘消息。
- OnIdle:可以在应用程序空闲时进行处理,常用于后台任务。
cpp
BOOL CMyApp::PreTranslateMessage(MSG* pMsg)
{
// Custom message preprocessing
return CWinApp::PreTranslateMessage(pMsg);
}
八、总结
MFC是一个功能强大的C++应用框架,通过封装Windows API简化了Windows应用程序的开发。其核心架构包括应用程序框架、文档/视图架构、用户界面类和GDI类。MFC的消息映射机制通过宏和消息映射表实现,将Windows消息路由到相应的类成员函数,从而简化了消息处理过程。
通过深入理解MFC的结构、底层原理和消息映射机制,你可以更高效地开发复杂的Windows应用程序,并充分利用MFC提供的各种功能和特性。MFC虽然在现代开发中不如一些新的框架流行,但在Windows开发的历史和某些特定领域依然具有重要地位。