2021-04-12MFC00
请注意,本文编写于 595 天前,最后修改于 204 天前,其中某些信息可能已经过时。

目录


一、最小MFC

从空白的工程开始利用MFC的框架代码来创建出对话框

创建一个空白工程
Windows桌面向导

创建完空工程后,不需要写main函数,因为MFC的框架中有main函数(appmodul.cpp文件中的_tWinMain)
空工程

创建子类并继承MFC对话框类

c
# pragma once
# include <afxwin.h>	//CDialog头文件

class CMyDlg:public CDialog
{
};

创建子类并继承MFC对话框类

尝试编译运行会报错

c
# error:  Building MFC application with /MD[d] (CRT dll version) requires MFC shared dll version. Please # define _AFXDLL or do not use /MD[d]

需要设置项目属性->高级->MFC的使用,将原来的使用标准Windows库,改为在静态库中使用MFC,即可成功编译
设置项目属性

按F7编译,虽然成功但有一条警告,指的是Win32的版本没有定义,可忽略
F7编译

_WIN32_WINNT not defined. Defaulting to _WIN32_WINNT_MAXVER (see WinSDKVer.h)

新建一个新的用于参照的MFC对话框工程,并查看其framework.h文件时,可以看到一条用于忽略警告的宏

c
// 关闭 MFC 的一些常见且经常可放心忽略的隐藏警告消息
# define _AFX_ALL_WARNINGS

忽略警告

既然编译成功,那么F5尝试运行,啥都没有
因为我们还没使用我们新建的类,所以我们要用上CMyDlg类才可以

参照新建的MFC对话框工程,可以搜索发现

在实例化或者定义一个变量,定义完变量调用了一个成员函数DoModal()
DoModal
另外这个函数是在App类中的InitInstance中使用的,所以我们在空工程中也新建一个类就叫CMyApp
CMyApp.h

c
# pragma once
# include <afxwin.h>

class CMyApp:public CWinApp	//必须是public
{
public:
	virtual BOOL InitInstance();
};

CMyApp.cpp

c
# include "CMyApp.h"
# include "CMyDlg.h"

BOOL CMyApp::InitInstance()
{
    CMyDlg dlg;
    dlg.DoModal();
    return 0;
}

CMyApp

尝试编译并运行,引发异常

c
引发了异常: 读取访问权限冲突。
pThread 是 nullptr。

那么参照的MFC对话框工程的pThread会有值吗?去看看
有值,并且还是App类,接下来研究为什么会有值
pThread

找找pThread在哪里赋的值

AfxGetThread()函数进到main函数中执行了Get,那他什么时候Set或者赋值呢?这个时机肯定要比main函数要早,所以肯定是全局性的东西,查看用于参照的MFC对话框工程
发现App定义了一个变量,有全局变量,说明是在App类的构造函数里面做了赋值的操作
theApp

所以我们也在空工程的CMyApp.cpp中加上一条CMyApp myApp;
myApp

再次尝试编译并运行,弹出了一个断言,点击重试,看看是断在了哪里
断言

发现我们的m_lpszTemplateNamem_hDialogTemplatem_lpDialogTemplate全部都是空的,所以出现错误
引发断言的原因

查看上方的注释,可以判断出,构造的时候带资源模板,也就是我们没有创建对话框资源,所以添加一个Dialog资源
Dialog

创建完对话框之后,设置一下对话框ID为DLG_MY,方便后续使用,接下来去参照工程中看看如何使用,发现是在构造中使用的
构造带资源模板

我们也在创建的CMyDlg类中写一个
CMyDlg.h

c
# pragma once
# include <afxwin.h>	//CDialog头文件

class CMyDlg:public CDialog
{
public:
	CMyDlg(CDialog* pParent = nullptr);	// 标准构造函数
};

CMyDlg.cpp

c
# include "CMyDlg.h"
# include "resource.h"    //需要包含资源头文件,否则报错

CMyDlg::CMyDlg(CDialog* pParent):
	CDialog(DLG_MY, pParent)
{
}

构造中带模板

接下来再次尝试编译并运行,成功弹出来了

到此,一个最小的MFC就完成了,但是在过程中从参照工程中发现了下面一段代码

c
// 对话框数据
# ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_ABOUTBOX };
# endif

这段代码的功能是,可以快速的从类跳到类所对应的对话框资源上面
查看参照的MFC工程,右键就可以快速打开对应的对话框资源

所以我们也在自建的MFC工程中加上这段代码,就可以拥有这个功能了

c
// 对话框数据
# ifdef AFX_DESIGN_TIME
	enum { IDD = DLG_MY };//修改资源ID
# endif

二、MFC的消息响应

按钮消息
新建按钮并设置好ID后,鼠标移到按钮上直接双击
按钮消息

MFC已经帮我们写好了case,我们只需要填充内容即可

c
void CDlgDlg::OnBnClickedTest1()
{
	// TODO: 在此添加控件通知处理程序代码
	AfxMessageBox(L"测试1");
}

填写完编译并运行
测试1

常规添加消息的方式

右键添加事件处理程序

在 类列表中选择 这个消息最终在哪个类里处理就选择哪个类,并选择相对应的消息类型,我们依然使用按钮来演示,所以消息类型就选择BN_CLICKED,函数名一般保持默认即可。
点击确定即可创建
事件处理程序

BTN_TEST2

写完处理程序代码,后编译并运行
测试2

类向导
在按钮上右键选择类向导,在对象ID中选择按钮ID,在消息中选择对应的消息类型,点击添加处理程序,就会弹出 添加成员函数框,点击确定
类向导

创建完成后,可以在成员函数列表中,左键双击进入编辑,也可以点击右侧的编辑代码
类向导
添加实现

编译并运行
测试3

在我们添加消息响应的时候,VS除了帮我们加函数之外,还做了很多事情

在对话框头文件中,多了按钮的成员函数
头文件
cpp文件中,在消息映射表里有变化
消息映射表

所以VS帮我写的地方不多,我们能不能手动添加这些内容呢?

首先添加一个按钮,设置其ID为BTN_TEST4

修改对话框头文件,并添加实现
声明并实现

修改cpp文件中的BEGIN_MESSAGE_MAP
修改消息映射

写完,编译并运行
成功弹出

三、添加自定消息

在MFC中如果需要自定义消息宏,那么不能从1直接开始,要从0x400以上开始,0x400一下是系统留给自己的定义消息的范围

c
//# define WM_MYMSG 0x400+1
# define WM_MYMSG WM_USER + 1

ON_MESSAGE
指示函数将处理的用户定义消息。
语法

c
ON_MESSAGE(message, memberFxn)

参数

  • message
    • 消息 ID。
  • memberFxn
    • 消息映射消息处理程序函数的名称。 afx_msg LRESULT (CWnd::*)(WPARAM, LPARAM)

在头文件中添加自动消息宏

c
# define WM_MYMSG1 WM_USER + 1

在头文件中写入函数的声明,并在cpp文件中创建
声明并创建

接下来在cpp文件中,消息映射表添加
添加消息映射

在对话框中新建一个按钮
新建按钮
写入程序代码
写入程序代码

编译并运行
自动消息

四、消息映射表

消息映射表

第一个宏,带了两个参数,第一个参数:对话框类名,第二个参数:父类的类名

c
BEGIN_MESSAGE_MAP(CDlgDlg, CDialogEx)

展开之后

c
# define BEGIN_MESSAGE_MAP(theClass, baseClass) \
	PTM_WARNING_DISABLE \    //这个宏是把4867这个警告给关掉了
	const AFX_MSGMAP* theClass::GetMessageMap() const \
		{ return GetThisMessageMap(); } \
	const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
	{ \
		typedef theClass ThisClass;						   \
		typedef baseClass TheBaseClass;					   \
		__pragma(warning(push))							   \    //都是一些警告
		__pragma(warning(disable: 4640)) /* message maps can only be called by single threaded message pump */ \
		static const AFX_MSGMAP_ENTRY _messageEntries[] =  \
		{
		    //第一个
            ON_WM_SYSCOMMAND()
            //展开ON_WM_SYSCOMMAND
            # define ON_WM_SYSCOMMAND() \
            	{ WM_SYSCOMMAND, 0, 0, 0, AfxSig_vwl,(AFX_PMSG)(AFX_PMSGW) \
            		(static_cast< void (AFX_MSG_CALL CWnd::*)(UINT, LPARAM) > ( &ThisClass :: OnSysCommand)) },
            //第二个
		    ON_BN_CLICKED(BTN_TEST1, &CDlgDlg::OnBnClickedTest1)
            //展开ON_BN_CLICKED进去还是宏
            # define ON_BN_CLICKED(id, memberFxn) \
	            ON_CONTROL(BN_CLICKED, id, memberFxn)
	        //ON_CONTROL进去
	        # define ON_CONTROL(wNotifyCode, id, memberFxn) \
	            { WM_COMMAND, (WORD)wNotifyCode, (WORD)id, (WORD)id, AfxSigCmd_v, \
		            (static_cast< AFX_PMSG > (memberFxn)) },
            //第三个
            ON_MESSAGE(WM_MYMSG1, &CDlgDlg::OnMyMsg)
            //展开ON_MESSAGE
            # define ON_MESSAGE(message, memberFxn) \
	            { message, 0, 0, 0, AfxSig_lwl, \
            		(AFX_PMSG)(AFX_PMSGW) \
            		(static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > \
            		(memberFxn)) },
END_MESSAGE_MAP()
//展开END_MESSAGE_MAP
# define END_MESSAGE_MAP() \
		{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
	}; \
		__pragma(warning(pop))	\
		static const AFX_MSGMAP messageMap = \
		{ &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
		return &messageMap; \
	}								  \
	PTM_WARNING_RESTORE
c
(static_cast< AFX_PMSG > (memberFxn))
这段代码功能是把一个表达式转换为某种类型
比如
char* p = nullptr;
typdef int* PINT;
static_cast<PINT>(p); // ==>(PINT)p;

将上边展开的代码替换成我们的

c
	const AFX_MSGMAP* CDlgDlg::GetMessageMap() const
		{ return GetThisMessageMap(); }
	const AFX_MSGMAP* PASCAL CDlgDlg::GetThisMessageMap()
	{
		typedef CDlgDlg ThisClass;
		typedef CDialogEx TheBaseClass;
		static const AFX_MSGMAP_ENTRY _messageEntries[] =
		{
			//第一项
			{ WM_COMMAND, (WORD)BN_CLICKED, (WORD)BTN_TEST1, (WORD)BTN_TEST1, AfxSigCmd_v,
		            		(AFX_PMSG) ( &CDlgDlg::OnBnClickedTest1) },
			//第二项
			{ WM_SYSCOMMAND, 0, 0, 0, AfxSig_vwl, (AFX_PMSG) ( &ThisClass :: OnSysCommand)) },
			//第三项
		    { WM_MYMSG1, 0, 0, 0, AfxSig_lwl, (AFX_PMSG)(AFX_PMSGW) (AFX_PMSG) (&CDlgDlg::OnMyMsg) },
			{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
	};
		static const AFX_MSGMAP messageMap =
		{ &TheBaseClass::GetThisMessageMap, &_messageEntries[0] };
		return &messageMap;
	}

static const AFX_MSGMAP_ENTRY _messageEntries[]数组中每一项元素,实际上都是记录了消息相关的信息,以及相应消息的函数的地址还有这个函数该怎么调用
当消息来的时候,会从数组第一个参数开始向后遍历,看消息ID对不对,如果对就调后边的函数,具体怎么调就会看AfxSig_xxx是什么,每一个响应消息的类都有这么一张表

根据上边分析消息映射表的方式,在我们自建的最小MFC中尝试写一个消息映射表
首先在对话框头文件中添加宏DECLARE_MESSAGE_MAP()展开这个宏,可以看到是两个函数的声明

c
# define DECLARE_MESSAGE_MAP() \
protected: \
	static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
	virtual const AFX_MSGMAP* GetMessageMap() const; \

DECLARE_MESSAGE_MAP

修改完头文件,接下来在cpp中添加消息映射表

//消息映射表
const AFX_MSGMAP* CMyDlg::GetMessageMap() const
{
	return GetThisMessageMap();
}
const AFX_MSGMAP* PASCAL CMyDlg::GetThisMessageMap()
{
	typedef CMyDlg ThisClass;
	typedef CDialog TheBaseClass;
	static const AFX_MSGMAP_ENTRY _messageEntries[] =
	{
		//按钮BTN_TEST
		{ WM_COMMAND, (WORD)BN_CLICKED, (WORD)BTN_TEST, (WORD)BTN_TEST, AfxSigCmd_v,
						(AFX_PMSG)(&CMyDlg::OnBnClickedButton1) }
	};
	static const AFX_MSGMAP messageMap =
	{ &TheBaseClass::GetThisMessageMap, &_messageEntries[0] };
	return &messageMap;
}

消息映射表

添加完消息映射表后,编译并运行
测试

五、键盘消息

添加键盘消息可以,在对话框上右键 选择类向导,在虚函数中找到PreTranslateMessage添加函数并编辑代码
PreTranslateMessage

此时头文件中及cpp文件名中就会生成相应的声明及实现
声明
实现

我们可以添加相应的代码,如:
按下键盘A键, 弹出MessageBox
按下回车键, 窗口会关闭

c
BOOL CDlgDlg::PreTranslateMessage(MSG* pMsg)
{
	// TODO: 在此添加专用代码和/或调用基类
	switch (pMsg->message)      //指定了消息号,如 # define WM_KEYDOWN 0x0100
	{
	case WM_KEYDOWN:
	{
		switch (pMsg->wParam)	//判断键码
		{
		case 0x41:              //A键
		{
			AfxMessageBox(L"按下了A键");
			break;
		}
		case VK_RETURN:			//回车
		{
			EndDialog(0);
		}
		default:
			break;
		}
		break;
	}
	default:
		break;
	}
	return CDialogEx::PreTranslateMessage(pMsg);
}

关于自建工程中按下回车键,会关闭,没找到回车处理函数
窗口会关闭是因为默认焦点在按钮IDOK上面
默认焦点
此时敲击回车键,就会直接关闭窗口
OnOK

本文作者:Na1r

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!