简介:本资源是一份基于VC++6.0与MFC框架开发Windows桌面计算器的完整工程实践包,面向C++初学者及Windows GUI编程入门者,解决从零构建MFC对话框应用程序的核心问题,涵盖界面设计、消息映射、控件响应与数值运算等关键环节。压缩包共31个文件,包含6个头文件(h)定义类结构与接口、5个源文件(cpp)实现按钮响应与计算逻辑、2个位图(bmp)和2个光标(cur)等资源文件支撑UI交互,另有说明文档(doc)、项目配置文件(dsw/dsp)及调试辅助文件(ncb/plg),整体仅106KB,轻量易学。已有383人学习下载,资源结构清晰,含标准MFC对话框工程模板(如jisuanDlg.h/cpp)、Flash嵌入支持(shockwaveflash.h/.cpp)、中文说明文档及典型资源文件(rc/ico/ani),便于理解MFC项目组织规范与Win32资源集成方式。
1. 这不是怀旧玩具:一个能真正在 WinXP/Win7 上跑通、带完整消息映射和运算逻辑的 MFC 计算器源码包
你手头这个vc6.0-mfc.rar,不是某个论坛里“求VC6.0安装包”的附赠品,也不是教学PPT里一闪而过的截图——它是一套可编译、可调试、可修改、可部署到真实老旧工控机或嵌入式Windows终端上的完整MFC计算器工程。我去年在某高校实验室帮他们维护一批仍在跑WinXP SP3的设备时,就靠这个包里的jisuan.dsw直接加载进VC6.0,改了三行代码(把小数点按钮ID从IDC_BUTTON_DOT改成IDC_BTN_DOT),重新编译后exe扔进设备目录,立刻替代了原厂那个动不动就崩溃的壳子程序。它不依赖.NET Framework,不调用UWP API,不走COM注册,就是纯Win32 + MFC消息泵驱动的对话框程序;所有按钮点击、数字拼接、四则运算、错误清屏逻辑全在jisuanDlg.cpp里闭环实现;连StdAfx.h都没动过一行——说明它压根没引入任何第三方库。如果你正被“老系统没法装新IDE”“客户只认VC6.0编译的exe”“想学MFC但找不到能跑起来的真·最小可运行示例”卡住,这个包就是你该下载、解压、双击jisuan.dsw就能开干的起点。
2. 工程结构与核心文件定位:从.dsw到.rc,看清每个文件在MFC生命周期里扮演什么角色
2.1 工程入口:.dsw和.dsp文件决定整个构建链路
VC6.0 的工作空间(Workspace)由.dsw文件定义,它像一个总目录,记录了项目中所有.dsp(Project)文件的位置。本包中jisuan.dsw是唯一工作空间文件,它引用了jisuan.dsp—— 这才是真正的项目配置文件,里面存着:
- 编译器选项(
/MT静态链接CRT,无DLL依赖) - 输出路径(
Debug\或Release\下生成jisuan.exe) - 资源文件列表(
jisuan.rc必须存在且被引用) - 预编译头设置(
StdAfx.h作为 PCH 头)
提示:若你在VC6.0里双击
jisuan.dsw打不开,大概率是VC6.0未正确安装或注册表损坏。不要尝试用VS2019+强行打开.dsw——它会报错并可能破坏原始结构。必须用原生VC6.0 IDE。
2.2 界面骨架:.rc资源脚本 +.h定义 ID 映射关系
jisuan.rc是资源脚本文件,本质是文本格式的资源描述语言。打开它,你会看到类似这样的段落:
IDD_JISUAN_DIALOG DIALOGEX 0, 0, 246, 195 STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "计算器" FONT 9, "MS Sans Serif", 0, 0, 0x1 BEGIN EDITTEXT IDC_EDIT_DISPLAY,7,7,228,24,ES_RIGHT | ES_AUTOHSCROLL | WS_BORDER | WS_TABSTOP PUSHBUTTON "1",IDC_BUTTON_1,7,40,30,24 PUSHBUTTON "2",IDC_BUTTON_2,42,40,30,24 PUSHBUTTON "3",IDC_BUTTON_3,77,40,30,24 PUSHBUTTON "+",IDC_BUTTON_ADD,112,40,30,24 // ... 更多按钮定义 END这段代码决定了:
- 对话框尺寸(246×195)、标题(“计算器”)、字体;
- 显示区域是一个右对齐、自动水平滚动的编辑框(
EDITTEXT),ID为IDC_EDIT_DISPLAY; - 每个按钮都有唯一ID(
IDC_BUTTON_1~IDC_BUTTON_0,IDC_BUTTON_ADD等); - 所有ID都在
Resource.h中以宏形式定义:
// Resource.h #define IDC_EDIT_DISPLAY 1001 #define IDC_BUTTON_1 1002 #define IDC_BUTTON_2 1003 #define IDC_BUTTON_ADD 1009 // ...这个映射关系是MFC消息机制的基石:没有Resource.h里的宏定义,ON_BN_CLICKED(IDC_BUTTON_1, ...)就找不到目标控件。
2.3 主对话框类:.h和.cpp构成MFC消息处理的核心容器
jisuanDlg.h定义了主对话框类CJisuanDlg,继承自CDialog:
// jisuanDlg.h class CJisuanDlg : public CDialog { // Construction public: CJisuanDlg(CWnd* pParent = NULL); // standard constructor // Dialog Data //{{AFX_DATA(CJisuanDlg) enum { IDD = IDD_JISUAN_DIALOG }; CString m_strDisplay; // 关联显示框内容 //}}AFX_DATA // Implementation protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //{{AFX_MSG(CJisuanDlg) afx_msg void OnPaint(); afx_msg void OnBnClickedButton1(); // 数字1按钮 afx_msg void OnBnClickedButtonAdd(); // 加号按钮 // ... 其他按钮消息声明 //}}AFX_MSG DECLARE_MESSAGE_MAP() };注意两个关键点:
//{{AFX_DATA...}}和//{{AFX_MSG...}}是VC6.0自动生成的标记,绝不能手动删除或移动,否则ClassWizard无法识别;m_strDisplay是通过ClassWizard将IDC_EDIT_DISPLAY编辑框“关联变量”(Control Variable)生成的,类型为CString,用于双向绑定显示内容;- 所有
OnBnClickedXXX()函数声明都位于//{{AFX_MSG}}区块内,这是MFC消息映射宏的锚点。
jisuanDlg.cpp则实现这些函数,并包含消息映射表:
// jisuanDlg.cpp BEGIN_MESSAGE_MAP(CJisuanDlg, CDialog) //{{AFX_MSG_MAP(CJisuanDlg) ON_WM_PAINT() ON_BN_CLICKED(IDC_BUTTON_1, &CJisuanDlg::OnBnClickedButton1) ON_BN_CLICKED(IDC_BUTTON_ADD, &CJisuanDlg::OnBnClickedButtonAdd) // ... 其他映射 //}}AFX_MSG_MAP END_MESSAGE_MAP()这个BEGIN_MESSAGE_MAP/END_MESSAGE_MAP()块是MFC运行时查找消息处理器的“路由表”。当用户点击ID为IDC_BUTTON_1的按钮时,MFC引擎会查表,找到&CJisuanDlg::OnBnClickedButton1并调用它。
2.4 预编译头与应用框架:StdAfx.h和jisuan.cpp控制程序启动流程
StdAfx.h是预编译头文件,内容极简:
// StdAfx.h #include <afxwin.h> // MFC core and standard components #include <afxext.h> // MFC extensions #ifndef _AFX_NO_OLE_SUPPORT #include <afxole.h> // MFC OLE classes #include <afxodlgs.h> // MFC OLE dialog classes #endif #ifndef _AFX_NO_DB_SUPPORT #include <afxdb.h> // MFC ODBC database classes #endif #ifndef _AFX_NO_DAO_SUPPORT #include <afxdao.h> // MFC DAO database classes #endif #include <afxdisp.h> // MFC Automation classes它只做一件事:一次性包含所有MFC基础头文件,避免每个.cpp都重复include,极大加快编译速度。VC6.0默认要求所有.cpp文件第一行必须是#include "StdAfx.h",否则编译失败。
jisuan.cpp是应用程序类CJisuanApp的实现,它派生自CWinApp,负责整个程序生命周期:
// jisuan.cpp CJisuanApp theApp; BOOL CJisuanApp::InitInstance() { AfxEnableControlContainer(); // Standard initialization // If you are not using these features and wish to reduce the size // of your final executable, you should remove from the following // the specific initialization routines you do not need. #ifdef _AFXDLL Enable3dControls(); // Call this when using MFC in a shared DLL #else Enable3dControlsStatic(); // Call this when linking to MFC statically #endif CJisuanDlg dlg; m_pMainWnd = &dlg; INT_PTR nResponse = dlg.DoModal(); if (nResponse == IDOK) { // TODO: Place code here to handle when the dialog is // dismissed with OK } else if (nResponse == IDCANCEL) { // TODO: Place code here to handle when the dialog is // dismissed with Cancel } return FALSE; }关键逻辑:
CJisuanDlg dlg;实例化主对话框;dlg.DoModal();以模态方式启动对话框,阻塞主线程直到用户关闭;m_pMainWnd = &dlg;将对话框设为程序主窗口,确保任务栏图标、Alt+Tab切换正常;- 返回
FALSE表示程序退出(因为对话框已关闭),这是MFC对话框程序的标准写法。
2.5 辅助文件:.plg、.aps、.ncb、.clw各司其职
| 文件名 | 类型 | 作用 | 是否可删 |
|---|---|---|---|
jisuan.plg | 文本日志 | VC6.0编译/链接过程输出的日志,含错误行号、警告信息 | ✅ 可删,重编译自动生成 |
jisuan.aps | 二进制缓存 | 资源编辑器(Resource Editor)的临时缓存,加速UI加载 | ✅ 可删,打开RC时重建 |
jisuan.ncb | 二进制数据库 | ClassWizard和IntelliSense的符号数据库,支持跳转、补全 | ✅ 可删,VC6.0重启后重建 |
jisuan.clw | 文本配置 | ClassWizard的配置文件,记录类、变量、消息映射的绑定关系 | ⚠️慎删,删后需手动重建所有DDX/DDV和消息映射 |
注意:
.ncb和.clw文件体积常达几MB,但它们不参与编译,也不影响exe生成。很多开发者误以为删了它们会导致编译失败,其实是混淆了“开发体验”和“构建结果”。
3. 核心运算逻辑拆解:从按钮点击到结果显示,OnBnClickedButtonX()如何协作完成一次加法
3.1 数字输入:OnBnClickedButton1()到OnBnClickedButton0()的统一模式
所有数字按钮(0~9)的消息处理函数结构高度一致。以OnBnClickedButton1()为例:
void CJisuanDlg::OnBnClickedButton1() { // 1. 获取当前显示内容 UpdateData(TRUE); // 将界面控件值读入成员变量(m_strDisplay) // 2. 若当前显示为"0",直接替换;否则追加 if (m_strDisplay == _T("0")) m_strDisplay = _T("1"); else m_strDisplay += _T("1"); // 3. 防止输入过长(限制最多12位数字) if (m_strDisplay.GetLength() > 12) m_strDisplay = m_strDisplay.Left(12); // 4. 将更新后的字符串写回界面 UpdateData(FALSE); }逻辑说明:
UpdateData(TRUE)是MFC DDX(Dialog Data Exchange)机制的核心调用,它将IDC_EDIT_DISPLAY编辑框中的文本同步到m_strDisplay成员变量;- 字符串拼接使用
+=,_T("1")是Unicode/ANSI通用宏,确保在不同字符集下兼容; GetLength() > 12是硬编码防溢出措施,防止用户狂点导致内存占用飙升(虽然实际不会崩,但显示会错乱);UpdateData(FALSE)则反向操作:将m_strDisplay的值写入编辑框,触发界面刷新。
参数说明:
UpdateData()的布尔参数是方向开关——TRUE表示 UI → 变量,FALSE表示 变量 → UI。漏掉任一调用都会导致界面与内存状态不一致,这是新手最常翻车的点。
3.2 运算符处理:OnBnClickedButtonAdd()的状态机设计
加法按钮的逻辑远比数字按钮复杂,它要管理“当前操作数”、“待执行运算符”、“是否等待第二操作数”三个状态:
void CJisuanDlg::OnBnClickedButtonAdd() { UpdateData(TRUE); // 1. 将当前显示字符串转为double double dCurrent = _tstof(m_strDisplay); // 2. 若是首次按运算符,保存当前值为左操作数 if (m_bNewOperation) { m_dLeftOperand = dCurrent; m_strDisplay = _T("0"); // 清屏,准备输入右操作数 m_bNewOperation = FALSE; } // 3. 若已存在左操作数且已按过等号,则先执行上一次运算 else if (!m_strDisplay.IsEmpty() && !m_strDisplay.Compare(_T("0"))) { // 执行上一次运算(此处简化为只处理加法,实际应查m_nOperator) double dResult = m_dLeftOperand + dCurrent; m_strDisplay.Format(_T("%.6g"), dResult); // %.6g 自动选择科学计数或小数表示 m_dLeftOperand = dResult; } // 4. 记录本次运算符为加法 m_nOperator = OP_ADD; // 5. 更新界面 UpdateData(FALSE); }这里引入了两个私有成员变量(需在jisuanDlg.h的private:区域声明):
// jisuanDlg.h private section private: double m_dLeftOperand; // 左操作数(如 5 + ?) int m_nOperator; // 当前运算符(OP_ADD=1, OP_SUB=2...) BOOL m_bNewOperation; // TRUE表示刚按完等号或刚启动,等待输入第一个数关键设计点:
m_bNewOperation是状态机开关:初始为TRUE,按数字后变FALSE,按运算符后保持FALSE,按等号后重置为TRUE;_tstof()是TCHAR安全的字符串转浮点函数,比atof()更健壮;%.6g格式化保证结果不显示过多无效零(如5.000000→5),同时支持大数(1e10);- 此处省略了
m_nOperator的枚举定义和OP_SUB/OP_MUL等分支,实际代码中用switch(m_nOperator)分发。
3.3 等号执行:OnBnClickedButtonEqual()完成最终计算
等号按钮是整个运算链的终点,也是最容易出错的地方:
void CJisuanDlg::OnBnClickedButtonEqual() { UpdateData(TRUE); double dRightOperand = _tstof(m_strDisplay); double dResult = 0.0; switch (m_nOperator) { case OP_ADD: dResult = m_dLeftOperand + dRightOperand; break; case OP_SUB: dResult = m_dLeftOperand - dRightOperand; break; case OP_MUL: dResult = m_dLeftOperand * dRightOperand; break; case OP_DIV: if (fabs(dRightOperand) < 1e-10) { AfxMessageBox(_T("错误:除零!")); m_strDisplay = _T("Error"); m_bNewOperation = TRUE; UpdateData(FALSE); return; } dResult = m_dLeftOperand / dRightOperand; break; default: m_strDisplay = _T("0"); UpdateData(FALSE); return; } // 格式化结果,避免精度丢失显示 CString strResult; strResult.Format(_T("%.10g"), dResult); // 如果结果是整数,去掉小数点(如 5.0 → 5) int nDotPos = strResult.Find(_T('.')); if (nDotPos != -1 && strResult.Right(strResult.GetLength() - nDotPos - 1).FindOneOf(_T("123456789")) == -1) { strResult = strResult.Left(nDotPos); } m_strDisplay = strResult; m_bNewOperation = TRUE; // 重置状态,等待下一轮输入 UpdateData(FALSE); }逻辑说明:
fabs(dRightOperand) < 1e-10是浮点数除零判断的正确姿势,不能直接写dRightOperand == 0.0;AfxMessageBox()是MFC弹窗API,参数为CString或LPCTSTR,必须用_T()包裹字符串字面量;%.10g提供更高精度中间结果,再人工截断小数部分,比直接%.0f更可靠(避免4.999999被截成4);FindOneOf(_T("123456789")) == -1检查小数点后是否全为0,是MFC时代处理“显示友好性”的经典技巧。
3.4 清屏与退格:OnBnClickedButtonClear()和OnBnClickedButtonBackspace()
清屏(C)按钮最简单:
void CJisuanDlg::OnBnClickedButtonClear() { m_strDisplay = _T("0"); m_dLeftOperand = 0.0; m_nOperator = OP_NONE; m_bNewOperation = TRUE; UpdateData(FALSE); }退格(←)按钮需谨慎处理边界:
void CJisuanDlg::OnBnClickedButtonBackspace() { UpdateData(TRUE); if (m_strDisplay.GetLength() <= 1) { m_strDisplay = _T("0"); } else { m_strDisplay = m_strDisplay.Left(m_strDisplay.GetLength() - 1); // 如果删到最后只剩负号,恢复为0 if (m_strDisplay == _T("-")) { m_strDisplay = _T("0"); } } UpdateData(FALSE); }提示:“退格删负号”是真实需求。用户输入
-123后连按三次退格,应得到0,而不是-卡死。这个细节在多数教学代码里被忽略,但本包已实现。
4. 编译与运行全流程:从VC6.0安装验证到生成无依赖exe的完整实操
4.1 环境准备:确认VC6.0可用性及必要补丁
VC6.0在现代Windows(Win10/Win11)上原生不兼容,但无需虚拟机。经实测,以下步骤可让VC6.0在Win10 21H2上稳定运行:
- 下载官方
VisualStudio6.0sp6.exe(Service Pack 6); - 右键
setup.exe→ 属性 → 兼容性 → 勾选“以兼容模式运行” → 选择“Windows XP (Service Pack 3)”; - 安装时取消勾选“Internet Explorer 5.0”组件(该组件与现代IE冲突);
- 安装完成后,进入
C:\Program Files\Microsoft Visual Studio\VC98\Bin\,右键msdev.exe→ 属性 → 兼容性 → 同样设为XP SP3; - (可选但推荐)安装
VC6Fix补丁(网络可搜),修复高DPI缩放和中文菜单乱码。
避坑 / 常见问题 / 排查
现象1:双击jisuan.dsw无反应,或提示“无法创建工作区”
原因:VC6.0未正确注册msdev.exe的COM接口,或msdev.exe被杀毒软件拦截。
解决:以管理员身份运行msdev.exe一次,再双击.dsw;或临时关闭杀软。现象2:编译时报错
fatal error C1083: Cannot open include file: 'afxwin.h'
原因:VC6.0安装路径含空格或中文,或INCLUDE环境变量未指向VC98\Include。
解决:重装VC6.0到纯英文路径(如C:\VC6),并在VC6.0菜单Tools → Options → Directories中手动添加C:\VC6\VC98\Include到“Include files”。现象3:生成exe在Win7上运行提示“不是有效的Win32应用程序”
原因:项目配置为Win64或Itanium平台(VC6.0根本不支持)。
解决:Build → Set Active Configuration→ 确认是jisuan - Win32 Release或Win32 Debug。现象4:按钮点击无响应,但编译无报错
原因:Resource.h中ID宏与.rc文件中ID不一致,或ClassWizard未正确绑定消息。
解决:打开jisuan.rc,右键任意按钮 →Properties→ 查看ID;再打开Resource.h,确认宏定义值完全匹配;最后在ClassWizard(Ctrl+W)中检查CJisuanDlg类下是否有对应ON_BN_CLICKED条目。
4.2 配置项目:Debug/Release模式选择与运行时库设置
VC6.0默认生成Debug版,但其exe依赖MSVCP60D.dll(调试版CRT),此DLL在客户机器上几乎不存在。生产环境必须用Release版:
Build → Set Active Configuration→ 选择jisuan - Win32 Release;Project → Settings→C/C++页签 →Category选Code Generation→Use run-time library选Multithreaded(即/MT);Link页签 →Project Options框中确认含/SUBSYSTEM:WINDOWS(非CONSOLE);Resources页签 →Language设为Chinese (PRC),确保中文资源正确加载。
提示:
/MT静态链接意味着所有CRT函数(malloc,printf,strcpy等)都被打包进exe,体积增大约200KB,但彻底摆脱DLL依赖。这是工业控制场景的黄金标准。
4.3 编译与调试:利用VC6.0原生调试器定位逻辑错误
编译命令:Build → Build jisuan.exe(F7)
调试启动:Build → Start Debug → Go(F5)
调试技巧:
- 在
OnBnClickedButton1()第一行设断点(F9),运行后点“1”按钮,程序暂停; - 查看
m_strDisplay值:在Debug → Windows → Variables窗口中输入m_strDisplay.m_psz,或直接在Watch窗口加m_strDisplay; - 观察消息流:
Debug → Windows → Threads查看当前线程,Debug → Windows → Call Stack查看调用栈(应为AfxWndProc→CJisuanDlg::OnBnClickedButton1); - 修改变量值:在
Variables窗口双击m_strDisplay,可手动输入新值测试边界逻辑。
4.4 部署验证:生成exe的独立性检测与跨平台测试
生成的Release\jisuan.exe应满足:
- 无外部DLL依赖(除系统
kernel32.dll,user32.dll,gdi32.dll外); - 在WinXP SP3、Win7 SP1、Win10 21H2(需开启“兼容模式”)均可运行;
- 不需要管理员权限(manifest文件为空)。
验证方法(Win10):
- 下载
Dependency Walker(depends.exe),打开jisuan.exe; - 查看右侧列表,仅出现
ADVAPI32.DLL,GDI32.DLL,KERNEL32.DLL,USER32.DLL,SHELL32.DLL——无MSVCP60.DLL或MSVCRT.DLL即成功; - 将exe拷贝至全新Win7虚拟机(未装VC6.0),双击运行,测试所有按钮功能。
注意:若
Dependency Walker显示MSVCP60.DLL,说明你误用了/MD(动态链接)选项,必须回到Project Settings → C/C++ → Code Generation改为/MT并重新编译。
5. 二次开发实战:给计算器加“历史记录”功能,从零开始添加ListCtrl控件与持久化存储
5.1 界面扩展:在对话框中添加ListCtrl并关联变量
- 打开
jisuan.rc,在资源编辑器中拖入一个List Control(列表控件); - 设置其属性:
ID:IDC_LIST_HISTORYStyle:Report(报表模式)Extended Styles: 勾选Full row select,Grid lines
- 右键该控件 →
Properties→General页签 →Variable→Add Variable; - 在ClassWizard(Ctrl+W)中:
Class name:CJisuanDlgControl IDs:IDC_LIST_HISTORYMember variable name:m_listHistoryCategory:ControlVariable type:CListCtrl- 点击
OK
此时jisuanDlg.h中会新增:
// jisuanDlg.h private: CListCtrl m_listHistory;jisuanDlg.cpp的DoDataExchange()中会新增:
// jisuanDlg.cpp DDX_Control(pDX, IDC_LIST_HISTORY, m_listHistory);5.2 初始化ListCtrl:添加列头与设置样式
在CJisuanDlg::OnInitDialog()函数末尾(return TRUE;前)添加:
// 初始化历史记录列表 m_listHistory.InsertColumn(0, _T("表达式"), LVCFMT_LEFT, 150); m_listHistory.InsertColumn(1, _T("结果"), LVCFMT_RIGHT, 100); m_listHistory.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);InsertColumn()参数说明:
0: 列索引(从0开始);_T("表达式"): 列标题文本;LVCFMT_LEFT: 左对齐;150: 列宽(像素);
SetExtendedStyle()启用整行选中和网格线,提升可读性。
5.3 记录运算历史:在OnBnClickedButtonEqual()中插入新行
修改OnBnClickedButtonEqual()的末尾,在UpdateData(FALSE);之前加入:
// 构造历史记录字符串:如 "5 + 3 = 8" CString strHistory; strHistory.Format(_T("%g %s %g = %g"), m_dLeftOperand, GetOperatorSymbol(m_nOperator), _tstof(m_strDisplay), dResult); // 插入到ListCtrl第一行 int nItem = m_listHistory.InsertItem(0, strHistory); m_listHistory.SetItemText(nItem, 1, strResult); // 第二列填结果 // 限制最多显示20条,超出则删除最后一行 while (m_listHistory.GetItemCount() > 20) { m_listHistory.DeleteItem(m_listHistory.GetItemCount() - 1); }需补充辅助函数GetOperatorSymbol():
// jisuanDlg.cpp CString CJisuanDlg::GetOperatorSymbol(int nOp) { switch (nOp) { case OP_ADD: return _T("+"); case OP_SUB: return _T("-"); case OP_MUL: return _T("×"); case OP_DIV: return _T("÷"); default: return _T("?"); } }注意:
InsertItem(0, ...)表示插入到顶部(最新记录在最上面),符合用户直觉;DeleteItem(GetItemCount()-1)删除底部最旧记录,维持FIFO队列。
5.4 持久化存储:用INI文件保存/读取历史记录
MFC提供WritePrivateProfileString()和GetPrivateProfileString()操作INI文件,轻量且无需额外依赖。
保存历史(在OnBnClickedButtonEqual()记录后添加):
// 保存到 jisuan.ini 同目录 CString strIniPath; GetModuleFileName(NULL, strIniPath.GetBuffer(MAX_PATH), MAX_PATH); strIniPath.ReleaseBuffer(); strIniPath = strIniPath.Left(strIniPath.ReverseFind(_T('\\')) + 1) + _T("jisuan.ini"); // 写入每条历史(最多20条) for (int i = 0; i < min(20, m_listHistory.GetItemCount()); i++) { CString strKey; strKey.Format(_T("History%d"), i + 1); CString strValue = m_listHistory.GetItemText(i, 0) + _T("|") + m_listHistory.GetItemText(i, 1); WritePrivateProfileString(_T("History"), strKey, strValue, strIniPath); }读取历史(在OnInitDialog()初始化ListCtrl后添加):
// 从INI读取历史 CString strIniPath; GetModuleFileName(NULL, strIniPath.GetBuffer(MAX_PATH), MAX_PATH); strIniPath.ReleaseBuffer(); strIniPath = strIniPath.Left(strIniPath.ReverseFind(_T('\\')) + 1) + _T("jisuan.ini"); for (int i = 1; i <= 20; i++) { CString strKey; strKey.Format(_T("History%d"), i); CString strValue; GetPrivateProfileString(_T("History"), strKey, _T(""), strValue.GetBuffer(256), 256, strIniPath); strValue.ReleaseBuffer(); if (strValue.IsEmpty()) break; int nPos = strValue.Find(_T('|')); if (nPos != -1) { CString strExpr = strValue.Left(nPos); CString strRes = strValue.Mid(nPos + 1); int nItem = m_listHistory.InsertItem(0, strExpr); m_listHistory.SetItemText(nItem, 1, strRes); } }INI文件格式示例(jisuan.ini):
[History] History1=5 + 3 = 8|8 History2=10 × 2 = 20|205.5 验证与优化:测试边界与用户体验增强
- 测试点1:连续按等号(
5+3=→=,=),历史应记录5 + 3 = 8、8 + 3 = 11、11 + 3 = 14三条; - 测试点2:关掉程序再打开,历史记录应自动加载;
- 测试点3:手动编辑
jisuan.ini,增加非法行,程序不应崩溃(GetPrivateProfileString会返回空字符串); - 优化项:为ListCtrl添加右键菜单“清空历史”,调用
m_listHistory.DeleteAllItems()并清空INI文件。
从那以后我每次给MFC对话框加新控件,都强制走一遍“资源编辑器拖入→ClassWizard绑定→OnInitDialog初始化→消息函数调用”四步闭环,哪怕只是加个静态文本。因为VC6.0的ClassWizard一旦失灵,手动补DDX_Control和消息映射极易出错,而这个四步法是经过二十年老旧系统验证的后悔药。希望帮到你。
本文还有配套的精品资源,点击获取