【第25节】MFC编程:MFC入门的第一课

发布于:2025-03-26 ⋅ 阅读:(76) ⋅ 点赞:(0)

目录

前言

一、MFC历史

二、编写最简单的MFC程序

三、MFC中的对话框

四、MFC中的对话框类型

五、MFC中的消息映射【选读】


前言

        MFC 即 Microsoft Foundation Classes,是微软公司提供的一个类库。它以 C++ 类的形式封装了 Windows API,并且包含了大量的代码来简化 Windows 应用程序的开发过程。借助 MFC,开发人员能够更便捷地创建具有图形用户界面(GUI)的 Windows 应用程序,减少了直接使用 Windows API 进行编程的复杂性和工作量。

        学习 MFC,C++ 面向对象编程和 Windows SDK 是必备基础。当下,MFC 在客户端界面开发中热度减退。然而,它对梳理界面程序发展脉络意义重大,逆向解析老程序时也能派上用场。并且,不少新界面框架与 MFC 在设计思路、功能实现上有相通之处,掌握 MFC,学新框架能触类旁通,快速上手。

        MFC里的函数和SDK的API函数,很多名字看着差不多,功能也相仿,所以在学习时,从SDK过渡到MFC特别顺畅,没啥阻碍。就拿控件使用来说,MFC和SDK的方式基本是一样的。并且在编写MFC程序时,还可以直接调用SDK的API,这就使得MFC和SDK能很好地结合在一起,运用起来相当灵活。这种灵活性为开发者后续编写工具打下了基础,让开发者能够用相对少的代码量,就完成程序开发工作 。 

一、MFC历史

        Windows操作系统从一开始设计就引入了面向对象的理念,只是由于当时技术环境等条件的制约,这种面向对象的实现并不够完善。1989年的时候,Microsoft的程序员想把C++运用到Windows编程里,打算搞出一个能让Windows编程变得简单些的应用程序框架,他们给这个框架取名叫AFX(虽说AFX源自Application Framework,可奇怪的是这个词组里并没有“X”这个字母)。到了现在,AFX小组早就没了,1994年初开始,AFX这个名字也不再使用了。不过在Visual C++和MFC里,还到处都能看到AFX留下的痕迹,好多全局函数、结构还有宏的标识符,都带着AFX前缀。

        MFC也就是Microsoft Foundation Classes,翻译过来就是微软基础类库。它按照面向对象的思路,基本上把SDK里的所有对象都重新封装了,用起来更方便。靠着继承、虚函数还有消息映射这些技术,大大降低了Windows编程的难度。但因为C++标准到1998年才确定下来,而MFC在1992年就诞生了,所以MFC在整体设计上有一些缺陷。而且这些缺陷大多是设计和结构方面的问题,在后来的版本里也一直没得到解决。

二、编写最简单的MFC程序

想弄出一个简单的MFC程序,得满足下面这些条件:
1. 得把afxwin.h这个头文件包含进来。
2. 每个MFC项目都得有个从CWinApp派生出来的类的对象,不再需要WinMain函数了。
3. 一定要重写InitInstance函数。
4. 在InitInstance函数里,要创建窗口或者对话框,然后把窗口对象的指针,赋值给CWinApp的成员变量m_pMainWnd 。

示例程序:

#include <afxwin.h>

class CMyApp : public CWinApp
{
public:
    virtual BOOL InitInstance();
};

BOOL CMyApp::InitInstance()
{
    // 创建主窗口
    CFrameWnd* pMainWnd = new CFrameWnd;
    if (!pMainWnd)
    {
        return FALSE;
    }

    // 创建窗口,需要指定窗口类名、标题和样式等参数
    if (!pMainWnd->Create(NULL, _T("My MFC App")))
    {
        AfxMessageBox(_T("Failed to create main window!"));
        return FALSE;
    }

    // 显示窗口
    pMainWnd->ShowWindow(SW_SHOW);
    pMainWnd->UpdateWindow();

    // 将主窗口指针赋值给 m_pMainWnd
    m_pMainWnd = pMainWnd;

    return TRUE;
}

CMyApp theApp;

 

如果是直接创建的win32空项目程序,在vs中还是要做如下设置才能编译运行。

其实最简单的mfc程序还是如下:

#include <afxwin.h>
CWinApp theApp;

成功编译,但是运行没任何显示。


三、MFC中的对话框

        用Visual Studio(VS)软件借助MFC的向导来创建对话框程序,以vs2019为例

        用向导建对话框的时候,选基于对话框这个选项,然后直接点“生成的类”,就能看到马上要生成的类了。

        CDialogEx是CDialog类的升级版,它比CDialog多了一些功能,像能设置对话框的背景颜色还有背景图像。

一般CDialog或者CDialogEx的派生类有下面这些成员:
1. DoDataExchange是个虚函数,作用是让对话框控件和数据能相互交换。
2. OnInitDialog也是虚函数,它和对话框消息WM_INITDIALOG是一样的,能在这个函数里写对话框初始化的代码。
3. enum{IDD = xX},这是用来标记对话框资源的ID,这个ID和这个类是对应的。
4. m_hWnd是个成员变量,用来存对话框的窗口句柄。

一般CWinApp或者CWinAppEx的派生类有下面这些成员:
1. m_pMainWnd是成员变量,用来存主窗口的窗口句柄,这里存的其实就是主对话框的句柄。
2. InitInstance是虚函数,用来初始化程序,它最主要的作用就是用CWnd的派生类创建窗口,或者用Cdialog的派生类创建对话框。

四、MFC中的对话框类型

 模态对话框:

void CMyDialogApp::OnOpenModalDialog()
{
    CMyModalDialog dlg;
    dlg.DoModal();  // 模态对话框,阻塞程序
}

非模态对话框:

class CMyApp : public CWinApp
{
public:
    CMyNonModalDialog* m_pNonModalDlg;  // 非模态对话框对象作为成员变量

    void OnOpenNonModalDialog()
    {
        m_pNonModalDlg = new CMyNonModalDialog;
        m_pNonModalDlg->Create(IDD_NONMODAL_DIALOG, NULL);
        m_pNonModalDlg->ShowWindow(SW_SHOW);  // 非模态对话框,不阻塞程序
    }
};

五、MFC中的消息映射【选读】

        MFC窗口用的是同一个窗口处理流程,它通过消息映射把消息处理的过程给藏起来了。因为消息映射的语法不太好懂,所以刚开始理解消息映射的时候,可以先从它的实际应用入手。等编写MFC程序的经验多起来,再慢慢深入去弄懂它。

        下面先简单讲讲MFC的消息映射。MFC要实现消息映射,会在能响应消息的类里面,自动做好下面两方面的事:
        1. 消息映射的声明与实现:在类的定义,也就是头文件里,要加上声明消息映射的宏DELARE_MESSAGE_MAP。在类的实现,也就是源文件里,得靠BEGIN_MESSAGE_MAP和END_MESSAGE_MAP()来实现消息映射。
        2. 消息响应函数的声明与实现:用类向导添加消息响应函数的时候,函数的声明和实现代码会自动添加上。

代码如下:
声明部分:

//{{AFX_MSG
afx_msg void OnTimer(UINT nIDEvent);
afx_msg void OnPaint();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()

映射部分:

BEGIN_MESSAGE_MAP(CTestDialog,CDialog)
//{{AFX_MSG_MAP(CTestDialog)
ON_WM_TIMER()
ON_WM_PAINT()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

实现部分:

void CMFCD1g::OnPaint()
void CMFCDlg::OnTimer(UINT nIDEvent)
CDialog::OnTimer(nIDEvent);

        仅仅这些就能实现对消息处理的简化吗?当然,我们需要更深入探讨这几步。首先了解消息映射的声明和实现。

消息映射声明代码:

#define DECLARE_MESSAGE_MAP()/
protected:/
static const AFX_MSGMAP*PASCAL GetThisMessageMap();/// 获得当前类和基类的映射信息
virtual const AFX_MSGMAP*GetMessageMap()const;/    // 实际上调用了上一个函数

消息映射实现代码:

#define BEGIN_MESSAGE_MAP(theClass,baseClass)/
PTM_WARNING_DISABLE /
const AFX_MSGMAP*theClass::GetMessageMap()const/
{return GetThisMessageMap();}/
const AFX_MSGMAP*PASCAL theClass::GetThisMessageMap(O///获得自身函数映射表入口地址
{/
typedef theClass ThisClass;/                   //当前类
typedef baseClass TheBaseClass;/                  //基类
static const AFX_MSGMAP_ENTRY messageEntries[]=///当前类信息实体数组,记录了
{                                                 //该类所有的消息实体
//该行之所以空出来,是因为所有的消息都要写在这里
#define END_MESSAGE_MAP()/
{0,0,0,0,AfxSig_end,(AFX_PMSG)0}/
};/
static const AFX_MSGMAP messageMap =/    //映射消息的结束,也是消息实
//体的最后一个元素,标志结束
{&TheBaseClass::GetThisMessageMap,&_messageEntries[0]};/
return &messageMap;/                                //返回消息变量
}/
PTM_WARNING_RESTORE                              //pragma宏的处理,无关系

对于消息映射声明和实现,需特别说明四点:
        (1)静态变量:消息映射实体数组AFX_MSGMAP_ENTRY _messageEntries[]记录了当前类的所有消息映射。每个消息是一个数组成员,AFX_MSGMAP_ENTRY的定义如下:

struct AFX_MSGMAP_ENTRY {
    UINT nMessage;  //消息类型,比如WM_PAINT
    UINT nCode;     //控制消息(WM_COMMAND)或者通知消息(WM_NOTIFY)
    UINT nID;        //控制ID
    UINT nLastID;   //used for entries specifying a range of control id's
};    UINT_PTR  nSig; AFX_PMSGpfn;    //处理函数原型的类型 //处理函数的指针

        从上面的结构能看出来,每条映射有两部分内容。第一部分跟消息ID有关,包含前面四个域;第二部分和消息对应的执行函数有关,包含后面两个域。pfn是指向CCmdTarger成员函数的指针,函数指针的类型定义是:`typedef void(AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);`。

        当用一条或者多条消息映射条目来初始化消息映射数组的时候,各种不同类型的消息函数都会被转成既不接收参数,也不返回参数的类型。因为所有能做消息映射的类都是从CCmdTarge派生出来的,所以能进行这种转换。

        nSig是个标识变量,用来区分不同原型的消息处理函数,每个不同原型的消息处理函数都对应一个不同的nSig。在分发消息的时候,MFC内部会根据nSig把消息交给对应的成员函数去处理,其实就是根据nSig的值把pfn变回相应类型的消息处理函数然后执行。

        (2)静态变量方面:消息映射信息变量AFX_MSGMAP messageMap记录了当前类和基类的消息映射实体数组的入口地址。AFX_MSGMAP结构有对应的定义(文章里没给出具体的结构内容)。
        (3)消息映射实体数组情况:从BEGIN_MESSAGE_MAP和END_MESSAGE_MAP宏定义能知道,用户添加的消息映射实体会自动加到_messageEntries数组里,这样就完成了消息映射实体数组的初始化。
        (4)虚函数GetMessageMap作用:把它设成虚函数是为了实现多态,这样当前类和基类就能调用正确的消息映射实体数组。

        通过分析消息映射宏能明白,三个宏通过两个静态变量把类和基类、消息和对应的消息处理函数关联起来了。这种关联保证了消息处理按顺序来,先处理当前类的,再处理基类的,让消息能准确找到对应的函数。MFC通过消息循环把消息发到窗口,窗口类会调用相关函数去查找消息映射实体数组,先找当前类的,再找基类的。找到了就调用对应的消息处理函数,如果没找到,程序就会自动调用默认的处理函数。


网站公告

今日签到

点亮在社区的每一天
去签到