学懂C++(五十六): 深入理解MFC框架、底层原理及消息映射机制

MFC(Microsoft Foundation Classes)是微软公司为Windows应用程序开发提供的一个类库,它是基于C++语言的应用框架,主要用于开发图形用户界面(GUI)应用程序。MFC实现了Windows API的大部分功能,并提供了更高层次的抽象,使开发者能够更轻松地创建Windows应用程序。本文将详细讲解MFC的结构、核心概念、主要类和典型应用场景,并深入探讨其底层原理及消息映射机制。

一、MFC的结构与核心概念

1. MFC的基本结构

MFC的结构包括以下几个主要部分:

  • 应用程序框架:负责应用程序的初始化、主消息循环和清理工作。
  • 文档/视图架构:用于实现基于文档的应用程序,将数据(文档)和用户界面(视图)分离。
  • 用户界面类:用于创建和管理窗口、对话框、控件等用户界面元素。
  • GDI类:封装了Windows图形设备接口(GDI),用于绘制图形和文本。
2. 核心概念
  • 消息映射:MFC使用消息映射机制将Windows消息路由到相应的类成员函数。通过宏BEGIN_MESSAGE_MAPEND_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应用程序的基本步骤:

  1. 使用MFC应用程序向导创建一个新的MFC对话框应用程序。
  2. 在资源编辑器中设计对话框界面,添加控件(如按钮、编辑框等)。
  3. 在对话框类中添加控件变量和事件处理函数。
  4. 实现事件处理函数以响应用户交互。
2. 单文档界面(SDI)应用程序

单文档界面应用程序只能同时打开一个文档。以下是创建一个SDI应用程序的基本步骤:

  1. 使用MFC应用程序向导创建一个新的MFC SDI应用程序。
  2. 创建并实现CDocument派生类,用于管理文档数据。
  3. 创建并实现CView派生类,用于显示文档数据。
  4. CView派生类中实现绘制和用户交互逻辑。
3. 多文档界面(MDI)应用程序

多文档界面应用程序可以同时打开多个文档,每个文档在一个独立的子窗口中。以下是创建一个MDI应用程序的基本步骤:

  1. 使用MFC应用程序向导创建一个新的MFC MDI应用程序。
  2. 创建并实现CDocument派生类,用于管理文档数据。
  3. 创建并实现CView派生类,用于显示文档数据。
  4. CView派生类中实现绘制和用户交互逻辑。
  5. CMainFrame类中管理MDI子窗口的创建和销毁。
4. 使用控件创建复杂界面

MFC提供了丰富的控件类,可以使用这些控件创建复杂的用户界面。以下是一些常用控件及其应用场景:

  • 按钮(CButton):用于触发操作。
  • 编辑框(CEdit):用于输入和显示文本。
  • 列表框(CListBox):用于显示项列表。
  • 组合框(ComboBox):用于显示可编辑的下拉列表。
  • 静态文本(CStatic):用于显示文本或图像。
  • 树控件(CTreeCtrl):用于显示树状结构的数据。
  • 列表控件(CListCtrl):用于显示带有列标题的数据表格。

四、实践示例

1. 基于对话框的应用程序示例

以下程序示例展示了一个简单的基于对话框的MFC应用程序,包含一个按钮和一个编辑框,详细分解如下:

1)定义应用程序类

首先,我们需要定义一个应用程序类。这个应用程序类通常会继承自CWinApp,负责应用程序的初始化和运行。

// MyDialogApp.h
class CMyDialogApp : public CWinApp
{
public:
    // 重写InitInstance方法,用于初始化应用程序
    virtual BOOL InitInstance();
};

2)定义对话框类

接下来,我们定义一个对话框类,继承自CDialogEx。这个类将代表我们的主对话框窗口。

// 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方法,该方法是应用程序初始化的入口点。

// MyDialog.cpp
BOOL CMyDialogApp::InitInstance()
{
    // 调用基类的初始化方法
    CWinApp::InitInstance();

    // 创建对话框实例
    CMyDialog dlg;

    // 设置主窗口指针为对话框
    m_pMainWnd = &dlg;

    // 显示模态对话框,并等待用户关闭该对话框
    INT_PTR nResponse = dlg.DoModal();

    // 返回FALSE以终止应用程序
    return FALSE;
}

4) 实现对话框类

在对话框类中,我们需要实现构造函数、数据交换方法以及消息映射和事件处理函数。

// 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)资源文件

最后,我们需要一个资源文件来定义对话框模板和控件,通常在资源编辑器中进行设计。以下是一个简单的资源文件内容示例:

// 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。这个类负责应用程序的初始化和运行。

// MySDIApp.h
class CMySDIApp : public CWinApp
{
public:
    // 重写InitInstance方法,用于初始化应用程序
    virtual BOOL InitInstance();
};

2) 定义文档类

接下来,我们定义一个文档类,继承自CDocument。这个类负责管理应用程序的数据。

// MyDocument.h
class CMyDocument : public CDocument
{
protected:
    // 使用动态创建机制声明
    DECLARE_DYNCREATE(CMyDocument)

public:
    // 重写OnNewDocument方法,用于初始化新文档
    virtual BOOL OnNewDocument();
};

 

3) 定义视图类

然后,我们定义一个视图类,继承自CView。这个类负责显示文档数据。

// MyView.h
class CMyView : public CView
{
protected:
    // 使用动态创建机制声明
    DECLARE_DYNCREATE(CMyView)

public:
    // 重写OnDraw方法,用于绘制视图内容
    virtual void OnDraw(CDC* pDC);
};

4) 实现应用程序类

在应用程序类中,我们实现InitInstance方法,该方法是应用程序初始化的入口点。

// 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方法,用于初始化新文档。

BOOL CMyDocument::OnNewDocument()
{
    // 调用基类的OnNewDocument方法
    if (!CDocument::OnNewDocument())
        return FALSE;

    // 自定义文档初始化代码可在此处实现

    return TRUE; // 返回TRUE表示成功
}

6) 实现视图类

在视图类中,我们实现OnDraw方法,用于绘制视图内容。

void CMyView::OnDraw(CDC* pDC)
{
    // 获取与视图关联的文档
    CDocument* pDoc = GetDocument();

    // 在视图中绘制文本
    pDC->TextOut(10, 10, _T("Hello, MFC!"));
}

 该示例演示了如何使用MFC创建一个简单的单文档界面(SDI)应用程序。应用程序类负责初始化框架和文档模板。文档类管理数据,视图类负责显示数据。通过掌握这些基础知识,你可以进一步扩展和复杂化你的MFC应用程序。

// 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应用程序的启动流程可以分为以下几个步骤:

  1. 程序入口点:MFC应用程序的入口点通常是_tWinMain,它负责初始化应用程序框架。
  2. 初始化应用程序对象_tWinMain函数会创建并调用CWinApp派生类的对象并调用其InitInstance方法。
  3. 消息循环CWinApp类的Run方法启动应用程序的消息循环,用于处理Windows消息。
  4. 清理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消息映射的工作流程:

  1. 消息接收:当Windows消息到达应用程序时,Windows将其传递给窗口过程函数CWnd::WindowProc
  2. 消息分发CWnd::WindowProc调用CWnd::DispatchMessage,它查找消息映射表,确定要调用的类成员函数。
  3. 消息处理:调用相应的类成员函数处理消息。
4. 消息映射的实现细节

在类中添加消息映射表:

class CMyWnd : public CWnd
{
protected:
    afx_msg void OnPaint();
    DECLARE_MESSAGE_MAP()
};

在类实现中定义消息映射表:

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_COMMANDON_UPDATE_COMMAND_UI等实现。例如:

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. 自定义消息

自定义消息可以用于应用程序内的自定义通信。定义自定义消息:

#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消息反射到父窗口,父窗口可以处理这个消息。

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
    ON_BN_CLICKED(IDC_BUTTON1, &CMyDialog::OnButtonClicked)
END_MESSAGE_MAP()

void CMyDialog::OnButtonClicked()
{
    // Handle button click
}
3. 消息预处理

MFC提供了消息预处理机制,允许在消息到达窗口过程之前进行处理。常见的方法包括PreTranslateMessageOnIdle

  • PreTranslateMessage:可以在消息派发之前对消息进行预处理,常用于处理键盘消息。
  • OnIdle:可以在应用程序空闲时进行处理,常用于后台任务。
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开发的历史和某些特定领域依然具有重要地位。

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿享天开

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值