简介:基于VC++6.0与MFC框架实现的计算器程序完整工程,面向C++初学者、需要完成Windows课程设计的本科生以及想了解对话框编程的开发者。该工程演示了从创建MFC对话框应用、放置数字与运算符按钮、使用静态文本作为显示区域,到定义消息映射、编写点击事件函数、处理CString与double转换、完成加减乘除运算并更新界面的完整流程,有助于直观理解MFC的消息驱动机制与窗口对象封装方式,正是切入Windows应用开发的高效路径。
资源包共31个文件,包含6个.h头文件和5个.cpp源文件,另有.rc界面资源、.bmp图标、.ico光标、.swf动画素材以及一份MFC说明文档和相关工程配置文件,压缩包约106KB,结构紧凑,下载后可在VC++6.0环境中直接打开编译运行。已有383人浏览学习,适合作为MFC入门练手、课程设计参考或毕业设计主题的基础工程。
通过阅读源码和说明文档,可以掌握对话框控件ID与消息处理函数的绑定方法、数值动态显示策略、错误处理思路以及工程组织方式,同时能从案例中借鉴调试经验,提升独立排错能力。基于此程序可继续扩展括号、平方、历史记录等高级计算功能,是熟悉面向对象Windows编程简洁而完整的实操样例。
1. 用 VC6.0 写 MFC 计算器:一份能直接编译的完整工程,比零散教程更省心
MFC 计算器是 VC6.0 时代最具代表性的入门项目:麻雀虽小,却覆盖了对话框框架、控件布局、消息映射和数值处理,一条完整链路走下来,Windows 桌面程序的底子就基本有了。市面上的计算器代码大多是贴出来的零散片段,你能看到消息映射宏,却看不到工程怎么组织、资源文件怎么配、环境怎么搭。这份 vc6.0-mfc.rar 的难得之处在于它是一份完整工程:jisuan.dsw 工作区、jisuan.rc 对话框模板、Resource.h 资源定义、jisuanDlg.cpp 主窗口实现一应俱全,还附带了一个基于 Shockwave Flash 控件的扩展对话框模块,兼顾了基础学习和 ActiveX 控件集成两件事。对正在做课程设计的本科生、刚接触 MFC 的初学者,或者想找一份能跑通的工程做二次开发的从业者,这份资源都有它的用处——先能跑起来,再谈改代码。
2. 解剖工程文件:jisuan.dsw 到 jisuanDlg.cpp,七个文件看懂 MFC 对话框程序
2.1 jisuan.dsw 与 jisuan.dsp:VC6.0 的项目入口和编译配置
在 VC6.0 里双击 jisuan.dsw 就能打开整个工作区。dsw 原名 Developer Studio Workspace,是工作区文件,记录了这一工作区包含哪些项目,内容非常简短;真正承载编译配置的是 dsp 文件,即 Developer Studio Project,里面写明了源文件列表、预处理器宏、库依赖和 Debug/Release 两套构建参数。
所以下次看到“工程文件损坏、无法打开”这类提示,先不要慌。凡是 .dsw/.dsp 报错,九成是因为路径变化或者 dsp 与 dsw 之间的项目名引用不一致。我自己比较常用的处理是:把整个工程目录复制到新位置后,删除 .ncb、.opt、.plg 三个缓存文件,然后重新打开 dsw。.ncb 是 IntelliSense 用来做自动补全提示的数据库,.opt 记录 IDE 的窗口布局和断点,.plg 是上一次编译的文本日志,这三者全部可以再生,删了之后 IDE 从头扫描目录,反而能清除旧路径缓存。
值得注意的还有 jisuan.aps。它是由 .rc 编译后生成的二进制缓存,核心功能是让资源编辑器打开得更快。资源编辑器中每一次保存都会同时更新 .rc 和 .aps;如果你手动修改了 .rc,而 .aps 还是旧的,VC6.0 会弹出“资源脚本已修改,是否重新加载”的提示,选“是”即可让 .aps 重新生成,正常操作不影响工程。
2.2 Resource.h 与 jisuan.rc:控件 ID 是消息映射的锚点
Resource.h 把控件标识符映射到唯一的整数值,例如 IDC_BUTTON_ADD 对应整数 1001;jisuan.rc 是资源脚本,用文本描述了对话框布局、每个控件的位置、大小、标题和 ID。计算器面板上所有按钮都来自这份 .rc 的 DIALOG 模板。按钮能在界面上响应点击,靠的是控件 ID 和消息映射表这一对组合。
如果你要给计算器新增一个按钮,用资源编辑器最稳妥:在 IDD_JISUAN_DIALOG 对话框模板上拖一个按钮,右键修改 ID,新 ID 会被写入 Resource.h,然后用 ClassWizard 生成消息映射函数。ClassWizard 会自动把映射关系写进 .cpp、.h 和 jisuan.clw 三个文件。jisuan.clw 是 ClassWizard 数据库,专门给 IDE 记录“哪个类响应哪些消息”;如果手动改了 .rc 和 Resource.h 却没同步 clw,ClassWizard 面板里看不到新控件,所以别光靠手写几个宏就把后续维护坑了。
注意:.rc、Resource.h、.clw 三方必须保持一致。最常见的故障是手动编辑 Resource.h 时误删了某个 IDC_ 宏,编译报错一大堆,恢复起来很头疼。
2.3 StdAfx.h 与预编译头:MFC 编译提速的核心机制
StdAfx.h 和 StdAfx.cpp 是预编译头的两个载体。MFC 的 afxwin.h、afxext.h、afxcmn.h 几个大头文件加起来有几千行,如果没有预编译机制,每次编译都要重新解析这些头文件,单是 include 展开就会耗费大量时间。预编译头把 StdAfx.h 包含的内容编译一次,结果存到后缀为 .pch 的文件里,后续其他源文件编译时直接复用这批编译产物。
// stdafx.h:标准系统包含文件 #include <afxwin.h> // MFC 核心和标准组件 #include <afxext.h> // MFC 扩展库 #include <afxdisp.h> // MFC 自动化与 COM 支持 #include <afxdtctl.h> // MFC 对 IE 公共控件的支持上面是 VC6.0 AppWizard 自动生成的 StdAfx.h 骨架,只保留了最基础的四个 MFC 头文件。实际工程需要的其他头文件,都放在各个 .cpp 里按需 include,避免被全局预编译,否则任何头文件的改动都会引发全工程重编。
如果你拿到这份工程后改动过 StdAfx.h,或者从 Release 切到 Debug 构建时 IDE 没有重新生成 .pch,最容易遇见的是编译错误 C1010:在查找预编译头文件时遇到意外的文件结尾。解决方式是执行 Rebuild All,强制整个工程重新生成 .pch。还有一点容易忽略:不要把 .pch 和 .ncb 这类构建产物打包发给别人,它们和平台相关,到了别人的机器上反而会触发各种奇怪的编译问题。
2.4 jisuan.cpp 与 jisuanDlg.cpp:从入口到对话框的两层结构
jisuan.cpp 定义了一个从 CWinApp 派生的应用程序类,以及一个全局唯一的 theApp 实例。MFC 程序在 main 执行之前先构造 theApp,进入 WinMain 后调用 InitInstance,主对话框在这里被创建并进入消息循环。
BOOL CJisuanApp::InitInstance() { AfxEnableControlContainer(); // 允许使用 ActiveX 控件 CJisuanDlg dlg; m_pMainWnd = &dlg; INT_PTR nResponse = dlg.DoModal(); // 模态对话框的消息循环 return FALSE; // 对话框关闭后进程退出 }AfxEnableControlContainer 是为 ActiveX 控件准备的初始化调用,工程里的 Flash 控件如果没有这行代码,在对话框上创建时很可能会失败。DoModal 是 CDialog 的核心方法,它会启动消息循环,直到用户点击关闭按钮。这个循环正是 Windows 事件驱动模型的关键:程序暂停在 DoModal 内部,界面事件排队进来,分发到消息映射表,直到对话框销毁才返回。懂得这条链路后,你再看“程序一闪而过”的问题,就能判断出问题大概率发生在 InitInstance 之前还是之后。
2.5 flashdlg 与 shockwaveflash:工程里藏着的一条 ActiveX 支线
工程里的 flashdlg.cpp/h 和 shockwaveflash.cpp/h 是嵌入 Shockwave Flash 控件的扩展模块。shockwaveflash.h 是 Flash ActiveX 控件的 C++ 包装类,flashdlg.cpp 是承载该控件的对话框实现。
嵌入 ActiveX 控件是 MFC 中比较进阶的用法,步骤是:在对话框模板上右键选择 Insert ActiveX Control,选中 Shockwave Flash Object,VC6.0 会自动生成包装类代码(shockwaveflash.cpp/h),然后在对话框类的 OnInitDialog 里设置 Movie 属性:
BOOL CFlashDlg::OnInitDialog() { CDialog::OnInitDialog(); // 给 Flash 控件指定要加载的 SWF 文件 m_flashCtrl.SetMovie(_T("calculator.swf")); // 自动播放 m_flashCtrl.SetPlaying(TRUE); // 画质参数:0 低,1 中,2 高 m_flashCtrl.SetQuality(2); return TRUE; }SetMovie 接受的路径可以是与 exe 同目录的文件名,也可以是绝对路径;SetPlaying 传 FALSE 则只加载不播放;SetQuality 的值域是 0 到 2,实际用下来建议直接设 2,低画质在动画变化时会有明显锯齿。SetMovie 和 SetPlaying 在运行时修改能立即生效,所以 Flash 控件常被用来做动态换片展示。
把上面这些串起来,你就有了一张完整的图景:jisuan.rc 管界面长什么样,Resource.h 给控件编号,jisuanDlg.cpp 管核心交互,jisuan.cpp 是进程入口,StdAfx.h 提高编译效率,Flash 模块承担扩展展示。拿到任何一份 MFC 对话框工程,按这个顺序扫一遍文件清单,心里基本就有数了。
3. 让计算器转起来:消息映射、控件取值与数值回显的三段式
3.1 打开、编译、运行:VC6.0 环境的兼容性设置与链接方式
以这份 jisuan.dsw 为例,拿到后先不要急着双击,把整个目录解压到一个不带空格的路径下,比如 D:\jisuan\,避免旧版 IDE 在某些系统上对带空格路径处理不当,导致编译时找不到中间文件。双击 dsw 打开后,按 F7 执行 Build,正常情况下十几秒就能在 Debug 目录下生成 jisuan.exe。
如果你在 Windows 7/10/11 上运行,首要注意的是 VC6.0 本身的兼容性。VC6.0 是 1998 年的产品,装好后建议对 MSDEV.EXE 右键设置“以兼容模式运行(Windows XP SP2)”,并勾选“以管理员身份运行”。如果编译时提示 cannot open precompiled header file,删掉 Debug 目录下的 .pch 文件再重新 Build 即可。还有一个常见的环境坑:机器上同时装了新版 Visual Studio 时,环境变量可能被改写,导致 VC6.0 偶发找不到 mspdb60.dll,处理方式要么在虚拟机里单独跑 VC6.0,要么保证命令行环境一致,不要混合调用两个版本的编译器。
工程默认使用动态链接 MFC(Use MFC in a Shared DLL),生成的 exe 依赖 MFC42.dll 才能运行。要把成品拿到别的机器上演示,在 Project → Settings → General → Microsoft Foundation Classes 里改成 Use MFC in a Static Library,重新编译之后 exe 自带 MFC 代码,体积大约会增加 1MB 左右,但不再需要目标机器安装运行库。做课程设计交作业时,这个改动基本能避免“老师电脑上跑不起来”的尴尬。
3.2 消息映射:ON_BN_CLICKED 把按钮点击变成函数调用
对话框程序的交互本质是消息的流转:鼠标按下、按键按下、控件焦点变化,每个事件都会变成消息进入队列。MFC 的消息映射用一张表把消息 ID 和响应函数绑在一起,省去了手写 switch-case 分发的过程。
以加法按钮为例,头文件里要声明处理函数,cpp 文件里要写映射宏:
// jisuanDlg.h 中的声明 class CJisuanDlg : public CDialog { ... afx_msg void OnBnClickedButtonAdd(); }; // jisuanDlg.cpp 中的消息映射 BEGIN_MESSAGE_MAP(CJisuanDlg, CDialog) ON_BN_CLICKED(IDC_BUTTON_ADD, &CJisuanDlg::OnBnClickedButtonAdd) END_MESSAGE_MAP() void CJisuanDlg::OnBnClickedButtonAdd() { // 读取显示区当前字符串 CString strCur; GetDlgItemText(IDC_EDIT_DISPLAY, strCur); // 转成 double 存入成员变量 m_dOperand = atof(strCur); // 记录本次运算符 m_nOperator = OP_ADD; // 显示区归零,等待输入第二个操作数 SetDlgItemText(IDC_EDIT_DISPLAY, _T("0")); }ON_BN_CLICKED 宏的第一个参数是控件 ID,第二个参数是响应函数地址。只要这个 ID 与 .rc 中按钮的 ID 一致,点击事件就能进入这个函数。函数体做的事可分为三类:取(GetDlgItemText)、算(赋值和状态记录)、显(SetDlgItemText)。这里的 m_dOperand 和 m_nOperator 是跨消息保留状态的成员变量,它们的存在决定了计算器能否连续运算。
要注意的是这里用的是 atof,来自 C 标准库。它会忽略字符串开头的空白,解析尽可能长的数字片段,遇到第一个不能构成数字的字符就停止。也就是说用户输入“12abc”时,atof 会解析出 12 而不会报错,所以如果你想把输入做严格校验,需要在入口处单独判断字符串是否真的是合法数字,否则奇怪输入会静默产生错误结果。
3.3 数字键与等号键:输入处理和运算触发的两处关键代码
数字键的处理逻辑是所有数字按钮共通的:先取显示区文本,判断当前状态,决定是替换还是追加。
void CJisuanDlg::OnBnClickedButton5() { CString strCur; GetDlgItemText(IDC_EDIT_DISPLAY, strCur); if (strCur == _T("0")) // 尚未输入有效数字 strCur = _T("5"); else strCur += _T("5"); // 已有数字,直接追加 SetDlgItemText(IDC_EDIT_DISPLAY, strCur); }CString 的 += 操作符直接做字符串追加,这是 MFC 中最常用的字符串拼接方式。这段代码隐藏着一个边界问题:如果用户按过运算符,显示区可能还留着上一个操作数的结果,这时再按数字 5,5 会被追加到结果的末尾,而不是作为新输入。常规解法是在运算符处理函数里加一个标志位 m_bNewNumber,按运算符时置 TRUE,在数字键处理里先判断这个标志,为真则直接替换显示内容而不是追加。
等号键的处理则汇总了所有中间状态:
void CJisuanDlg::OnBnClickedButtonEqual() { double dResult = 0.0; double dCurrent = atof(m_strDisplay); switch (m_nOperator) { case OP_ADD: dResult = m_dOperand + dCurrent; break; case OP_SUB: dResult = m_dOperand - dCurrent; break; case OP_MUL: dResult = m_dOperand * dCurrent; break; case OP_DIV: // 除零保护:任何数除以 0 都返回 0 并提示 if (dCurrent == 0.0) { AfxMessageBox(_T("除数不能为 0")); return; } dResult = m_dOperand / dCurrent; break; } CString strOut; strOut.Format(_T("%.8g"), dResult); // 8 位有效数字输出 SetDlgItemText(IDC_EDIT_DISPLAY, strOut); m_nOperator = OP_NONE; // 复位运算符状态 }等号处理的核心是把成员变量 m_dOperand 里的值和当前显示区数值做运算。m_dOperand 是按下运算符那一刻存下来的值,显示区字符串则是第二个操作数。这里最典型的 bug 出现在连续按等号时:如果 m_nOperator 没有复位,再次按等号会把上一次的结果继续运算。比如 2 + 3 = =,第一次等号得到 5,第二次又把 5 当第二个操作数加了进去,结果变成 7,这在真正的计算器逻辑里是错的。处理办法就是代码里的 m_nOperator = OP_NONE,让等号只触发一次有效运算。
除以零的边界处理也在这里写清楚了。浮点数除法在 C 语言里不会崩溃,但会产生 inf 这样的非有限值,显示到界面上就成了一串奇怪的字符。加一个 dCurrent == 0.0 的判断,弹提示框再返回,是计算器程序的基本素养。%.8g 这个格式说明符在输出浮点数时只保留 8 位有效数字,并自动去掉末尾多余的零,比直接用 %f 干净得多。
这三层逻辑——取值、消息分发、结果回显——是 MFC 对话框程序最常用的循环链路。任何按钮功能,哪怕只是改个字体、换个颜色,都逃不出这套“消息 → 处理 → 更新界面”的框架。
4. 避坑手册:VC6.0 + MFC 计算器最容易翻车的五个场景
4.1 现象:编译通过,运行却一闪而过,对话框根本没机会出现
按 F5 调试或者直接双击 Debug 目录下的 exe,程序闪一下就退出,没有任何报错信息。这种情况十有八九不是计算逻辑的问题,而是程序在启动早期阶段就失败了。
原因:Debug 版本的 MFC 程序链接的是 MFC42D.DLL、MSVCRTD.DLL 这组调试版运行库,目标机器缺少这些 DLL 时,加载器在进程初始化阶段就会终止运行,程序连 WinMain 都进不去。另一个常见原因是 VC6.0 生成的程序在 Windows 7 以上的系统里权限或兼容性受限,窗口创建失败后默认直接退出。
解决:先到 Project → Settings → General → Microsoft Foundation Classes 里把 MFC 链接方式改为 Use MFC in a Static Library,然后 Rebuild All。静态链接后 exe 不再依赖 MFC42.dll,大多数闪退问题就此解决。如果仍然闪退,在 InitInstance 的 DoModal 调用前加一句 AfxMessageBox(_T("start")),程序能弹框说明已经走到了入口;弹不出来则说明问题出在更早的运行库加载阶段,按兼容模式运行、管理员权限执行逐个尝试。
经验上,VC6.0 生成的程序放在非系统盘、非中文目录下运行,并给 exe 单独设置“以兼容模式运行(Windows XP SP2)”,稳定性会明显改善。后来我做课程设计辅导时遇到存疑的 exe,第一件事就是远程看对方的目录路径和 MFC 链接方式,大多数都一样的问题。
4.2 现象:菜单和按钮上的中文全部变成乱码
在中文 Windows 上写的计算器,按钮标题是“加、减、乘、除”,换到另一台机器上编译运行,界面变成一片问号或方框。
原因:.rc 资源脚本是 ANSI 编码保存的,编译器读取时按当前系统的活动代码页来解释字符。中文 Windows 的活动代码页是 GBK,如果 .rc 文件被某些编辑器改写成了 UTF-8 或者其他编码,编译器按 GBK 去读自然得到乱码。VC6.0 的资源编辑器本身也可能在保存时重新按系统代码页写入,导致原有编码发生偏移。
解决:用记事本打开 jisuan.rc,检查右下角编码显示,如果不是 ANSI,选择“另存为”并选中 ANSI 编码覆盖保存,然后再用 VC6.0 打开编译。处理过程中注意不要再经过任何默认 UTF-8 的编辑器二次保存。如果 .rc 已经损坏,另一条退路是把界面上的中文按钮标题全部改成英文,课程设计一般不会被扣分,程序反而在各种机器上更稳定。
4.3 现象:链接时报 LNK2005 重复定义,或 LNK2001 未解析的外部符号
编译阶段一个错都没有,链接时突然抛出一批 LNK2005 或 LNK2001,错误列表里指到的文件还不固定,很容易误导人去怀疑工程文件损坏。
原因:LNK2005 的经典原因是头文件被多个 cpp 文件包含,而头文件内部没有加 include guard,导致类或全局变量的定义在多个编译单元里各生成一份。LNK2001 则和字符集宏有关:成员函数名在 Unicode 构建下会带 W 后缀,多字节构建下带 A 后缀,如果工程的 Debug 和 Release 两个配置里预处理器定义不一致,一个定义了 UNICODE 而另一个没定义,链接时符号名对不上,就报未解析。
解决:先给所有自定义头文件补上标准的 #ifndef 保护;然后在 Project → Settings → C/C++ → Preprocessor 里检查 _UNICODE 和 UNICODE 的定义,把 Debug 和 Release 配置拉齐。代码里涉及字符串和字符处理的地方,尽量用 _T() 包裹,让它在多字节和 Unicode 下都能正确编译。这套做完再 Build,LNK 系列错误基本消失。如果还有残留,逐一检查 StdAfx.h 里 include 的头文件是否重复。
4.4 现象:对话框初始化时弹出 ActiveX 控件相关错误或者崩溃
运行到某一刻对话框就崩了,断点定位在 OnInitDialog 里创建 Flash 控件的那一行,提示 Class not registered 或者类似 COM 错误。
原因:这套工程里的 flashdlg 使用了 Shockwave Flash 控件,它是一个 COM 组件,需要在系统注册表里有对应的类注册信息。机器上没装过 Flash Player ActiveX,控件创建自然失败。如果目标机器恰好删过 Flash 组件,或者装了精简版 Flash,也会出现同样的问题。
解决:如果你只想学计算器本身,最快的方法是切除 Flash 模块,分三步才能做干净:第一步在 jisuan.rc 里找到放置 Flash 控件的对话框模板,删除整个对话框或删除该控件条目;第二步从工程中移除 flashdlg.cpp、flashdlg.h、shockwaveflash.cpp、shockwaveflash.h 四个文件;第三步重新 Build。这三步缺一不可,因为 .rc、源文件和 ClassWizard 数据库必须保持同步。如果想保留 Flash 功能,安装 Flash Player ActiveX 版,并确认注册表 HKCR\ShockwaveFlash.ShockwaveFlash 存在。不过考虑到 Flash 早已停止维护,我建议直接切除,这个组件本身已经成为安全漏洞的常客。
4.5 现象:连续运算时结果总是多算一次或者漏算
操作序列是 12 + 3 + 5 =,期望得到 20,实际却出来一个莫名其妙的数,比如 155 或者 12+35 混合在一起的结果。
原因:这是计算器程序中最经典的状态管理 bug。数字键的处理函数默认对显示字符串做追加;按下第一个运算符后,代码如果没有把显示区重置为等待新输入的状态,用户输入 5 时,上一个结果 15 还在显示区里,追加后变成 155。本质是代码没有区分“输入第一个操作数”“按下运算符”“输入第二个操作数”“显示结果”这四个阶段。
解决:在运算符处理函数中,把显示区内容存入 m_dOperand 之后,立即把显示区清零并设置一个标志位 m_bNewNumber。数字键处理函数在拼接字符串之前先检查这个标志:为真则直接替换显示内容,然后置假;为假则正常追加。这个改动做完,12 + 3 之后显示区回到 0,用户输入 5 时直接显示 5,再按等号得到 20。这也更贴近 Windows 自带计算器的交互习惯。调试这类问题时,在数字键处理函数入口打印 strCur 的值比在等号按钮处断点更有效,因为错误发生在输入阶段,而不是计算阶段。
5. 把计算器做得更耐打:状态机重构与键盘输入扩展
这份工程本身能跑通四则运算,但我最建议投入时间去做的一件事,是把“按钮 → 字符串拼接 → 数值计算”这套充满状态标志的代码改造成状态机。理由就在 4.5 那条坑里:计算器本质上一个状态流转系统,用布尔标志去表达“当前处于哪个阶段”,状态组合一多必然漏路径,而状态机从结构上消灭这一类问题。
一个够用的状态机只需要四个状态:ST_EMPTY 表示初始化后还没输入;ST_NUM1 表示正在输入第一个操作数;ST_OP 表示已经按下运算符,等待第二个操作数;ST_NUM2 表示正在输入第二个操作数。事件分为数字键、运算符键、等号键、清除键四类。核心分发函数的写法如下:
enum CalcState { ST_EMPTY, ST_NUM1, ST_OP, ST_NUM2 }; // 数字键事件的统一入口 void CJisuanDlg::OnDigit(int nDigit) { switch (m_state) { case ST_EMPTY: case ST_OP: // 新输入:替换显示区 m_strDisplay.Format(_T("%d"), nDigit); m_state = (m_state == ST_OP) ? ST_NUM2 : ST_NUM1; break; case ST_NUM1: case ST_NUM2: // 连续输入:追加数字 if (m_strDisplay == _T("0")) m_strDisplay.Format(_T("%d"), nDigit); else m_strDisplay.AppendFormat(_T("%d"), nDigit); break; } SetDlgItemText(IDC_EDIT_DISPLAY, m_strDisplay); }运算符事件就简化成“记录运算符,把状态切到 ST_OP”,等号事件则是按当前状态决定是否计算,并把结果状态切回 ST_NUM1。状态机的核心价值在于:每个按钮的处理函数只需要知道当前状态,不需要回溯前几次点击发生了什么。4.5 里那个多算一次的 bug,在这种结构下根本没有存在空间。
键盘输入扩展是另一个性价比极高的功能。在对话框类里重写虚函数 PreTranslateMessage,拦截按键消息并转发到统一入口:
BOOL CJisuanDlg::PreTranslateMessage(MSG* pMsg) { if (pMsg->message == WM_KEYDOWN) { switch (pMsg->wParam) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': OnDigit(pMsg->wParam - '0'); return TRUE; case '+': OnOperator(OP_ADD); return TRUE; case VK_RETURN: OnEqual(); return TRUE; case VK_BACK: OnBackspace(); return TRUE; } } return CDialog::PreTranslateMessage(pMsg); }字符键直接用字符字面量判断,数字键通过 wParam - '0' 得到实际数值,回车映射到等号,退格映射到删除。函数返回 TRUE 表示消息已被消费,对话框不会发出无按键响应的提示音。做完这个扩展,计算器立刻从“只能鼠标点”变成“键盘盲打也流畅”,演示时的体验完全不一样。
我当时处理这份工程最大的收获,是意识到按钮只是输入设备,计算逻辑完全可以和界面解耦。如果一个计算逻辑被界面代码裹挟得没法测试,那问题一定出在架构上而不是算法上。从那以后我每次写带 UI 的小工具,都会强制先画一遍状态图再动手,界面越来越薄,逻辑反而越来越耐改。这份工程的价值在于你可以从一条消息映射开始,亲手走通 MFC 的全链路,希望你在复现的时候少踩几个坑,希望帮到你。
本文还有配套的精品资源,点击获取