从空白的工程开始利用MFC的框架代码来创建出对话框
创建一个空白工程
创建完空工程后,不需要写main函数
,因为MFC的框架中有main函数
(appmodul.cpp文件中的_tWinMain)
创建子类并继承MFC对话框类
c# pragma once # include <afxwin.h> //CDialog头文件 class CMyDlg:public CDialog { };
尝试编译运行会报错
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的版本没有定义,可忽略
_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()
另外这个函数是在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; }
尝试编译并运行,引发异常
c引发了异常: 读取访问权限冲突。 pThread 是 nullptr。
那么参照的MFC对话框工程的pThread
会有值吗?去看看
有值,并且还是App类,接下来研究为什么会有值
找找pThread
在哪里赋的值
AfxGetThread()
函数进到main函数中执行了Get,那他什么时候Set或者赋值呢?这个时机肯定要比main
函数要早,所以肯定是全局性的东西,查看用于参照的MFC对话框工程
发现App定义了一个变量,有全局变量,说明是在App类的构造函数里面做了赋值的操作
所以我们也在空工程的CMyApp.cpp
中加上一条CMyApp myApp;
再次尝试编译并运行,弹出了一个断言,点击重试,看看是断在了哪里
发现我们的m_lpszTemplateName
、m_hDialogTemplate
、m_lpDialogTemplate
全部都是空的,所以出现错误
查看上方的注释,可以判断出,构造的时候带资源模板,也就是我们没有创建对话框资源,所以添加一个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
按钮消息
新建按钮并设置好ID后,鼠标移到按钮上直接双击
MFC已经帮我们写好了case,我们只需要填充内容即可
cvoid CDlgDlg::OnBnClickedTest1() { // TODO: 在此添加控件通知处理程序代码 AfxMessageBox(L"测试1"); }
填写完编译并运行
常规添加消息的方式
右键添加事件处理程序
在 类列表中选择 这个消息最终在哪个类里处理就选择哪个类,并选择相对应的消息类型,我们依然使用按钮来演示,所以消息类型就选择BN_CLICKED
,函数名一般保持默认即可。
点击确定即可创建
写完处理程序代码,后编译并运行
类向导
在按钮上右键选择类向导,在对象ID中选择按钮ID,在消息中选择对应的消息类型,点击添加处理程序,就会弹出 添加成员函数框,点击确定
创建完成后,可以在成员函数列表中,左键双击进入编辑,也可以点击右侧的编辑代码
编译并运行
在我们添加消息响应的时候,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
指示函数将处理的用户定义消息。
语法
cON_MESSAGE(message, memberFxn)
参数
在头文件中添加自动消息宏
c# define WM_MYMSG1 WM_USER + 1
在头文件中写入函数的声明,并在cpp
文件中创建
接下来在cpp
文件中,消息映射表添加
在对话框中新建一个按钮
写入程序代码
编译并运行
第一个宏,带了两个参数,第一个参数:对话框类名,第二个参数:父类的类名
cBEGIN_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;
将上边展开的代码替换成我们的
cconst 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; \
修改完头文件,接下来在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
添加函数并编辑代码
此时头文件中及cpp文件名中就会生成相应的声明及实现
我们可以添加相应的代码,如:
按下键盘A
键, 弹出MessageBox
按下回车键, 窗口会关闭
cBOOL 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
上面
此时敲击回车键,就会直接关闭窗口
本文作者:Na1r
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!