当前位置: 首页 > news >正文

MFC实战:从零构建一个带历史记录的计算器

1. 项目准备与环境搭建

在开始构建带历史记录功能的MFC计算器之前,我们需要先准备好开发环境。我推荐使用Visual Studio 2017或更高版本,因为它们在MFC开发方面提供了很好的支持。如果你还没有安装VS,可以去微软官网下载社区版,这是完全免费的。

创建新项目时,选择"MFC应用程序"模板,在应用程序类型中选择"基于对话框"。这里有个小技巧:我建议勾选"使用Unicode库"选项,因为现代Windows系统都使用Unicode编码,这样可以避免很多字符编码问题。创建好项目后,你会看到一个空白的对话框,这就是我们计算器的主界面。

在资源视图中,右键点击对话框选择"属性",建议将对话框的标题改为"智能计算器",这样看起来更专业。同时调整对话框的大小,我通常设置为300x400像素,这个尺寸既不会太小影响操作,也不会太大显得空旷。

2. 基础计算器界面设计

让我们先完成基础计算器的界面布局。从工具箱中拖拽以下控件到对话框上:

  • 4个Edit Control:分别用于显示第一个操作数、运算符、第二个操作数和结果
  • 16个Button:数字0-9、小数点、加减乘除运算符、等号和清零按钮

这里有个实用技巧:按住Ctrl键可以多选控件,然后使用工具栏的对齐工具让按钮排列整齐。我习惯将数字按钮按照手机计算器的布局排列,这样用户会更熟悉。记得给每个按钮设置有意义的ID,比如IDC_BUTTON_0、IDC_BUTTON_1等,这样后面写代码时不容易混淆。

对于Edit Control控件,建议将它们的Read Only属性设为True,这样用户不能直接在里面输入,只能通过按钮操作。同时将最后一个Edit Control(显示结果的)的字体调大一些,我通常设为14号加粗字体,这样结果更醒目。

3. 实现基础计算功能

现在我们来编写计算器的核心逻辑。首先在对话框类的头文件中声明必要的成员变量:

private: CString m_strOperand1; // 第一个操作数 CString m_strOperand2; // 第二个操作数 CString m_strOperator; // 运算符 CString m_strResult; // 计算结果 BOOL m_bOperand2Active; // 标记是否开始输入第二个操作数

数字按钮的点击事件处理函数可以这样实现:

void CCalculatorDlg::OnBnClickedButton0() { if (!m_bOperand2Active) { m_strOperand1 += _T("0"); SetDlgItemText(IDC_EDIT_OPERAND1, m_strOperand1); } else { m_strOperand2 += _T("0"); SetDlgItemText(IDC_EDIT_OPERAND2, m_strOperand2); } }

运算符按钮的处理函数:

void CCalculatorDlg::OnBnClickedButtonAdd() { m_strOperator = _T("+"); SetDlgItemText(IDC_EDIT_OPERATOR, m_strOperator); m_bOperand2Active = TRUE; }

等号按钮的处理函数是核心计算逻辑:

void CCalculatorDlg::OnBnClickedButtonEqual() { double dOperand1 = _ttof(m_strOperand1); double dOperand2 = _ttof(m_strOperand2); double dResult = 0; if (m_strOperator == _T("+")) dResult = dOperand1 + dOperand2; else if (m_strOperator == _T("-")) dResult = dOperand1 - dOperand2; else if (m_strOperator == _T("*")) dResult = dOperand1 * dOperand2; else if (m_strOperator == _T("/")) { if (dOperand2 != 0) dResult = dOperand1 / dOperand2; else MessageBox(_T("除数不能为零!"), _T("错误"), MB_ICONERROR); } m_strResult.Format(_T("%g"), dResult); SetDlgItemText(IDC_EDIT_RESULT, m_strResult); }

4. 添加历史记录功能

现在进入本文的重点 - 为计算器添加历史记录功能。我们将使用ListBox控件来显示计算历史。

首先在对话框中添加一个ListBox控件,设置其ID为IDC_LIST_HISTORY。建议将其放在计算器界面的右侧或下方,大小要能显示多条记录。

在对话框类的头文件中添加一个成员变量来关联这个ListBox:

private: CListBox m_listHistory;

在OnInitDialog函数中初始化这个控件:

BOOL CCalculatorDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 其他初始化代码... m_listHistory.SubclassDlgItem(IDC_LIST_HISTORY, this); m_listHistory.SetFont(GetFont()); return TRUE; }

修改等号按钮的处理函数,在计算完成后将表达式和结果添加到历史记录中:

void CCalculatorDlg::OnBnClickedButtonEqual() { // 之前的计算代码... // 添加历史记录 CString strHistory; strHistory.Format(_T("%s %s %s = %s"), m_strOperand1, m_strOperator, m_strOperand2, m_strResult); m_listHistory.InsertString(0, strHistory); // 新记录插入到顶部 // 限制历史记录数量 if (m_listHistory.GetCount() > 20) m_listHistory.DeleteString(20); }

5. 增强历史记录功能

基础的历史记录功能已经实现,但我们可以做得更好。让我们添加一些实用功能:

5.1 历史记录选择功能

当用户点击历史记录中的某条记录时,可以将其重新加载到计算器中:

void CCalculatorDlg::OnLbnSelchangeListHistory() { int nIndex = m_listHistory.GetCurSel(); if (nIndex != LB_ERR) { CString strHistory; m_listHistory.GetText(nIndex, strHistory); // 解析历史记录字符串 // 这里需要根据实际格式编写解析代码 // 将解析出的操作数和运算符设置到对应控件 } }

5.2 添加清空历史记录按钮

添加一个按钮,ID设为IDC_BUTTON_CLEAR_HISTORY,处理函数如下:

void CCalculatorDlg::OnBnClickedButtonClearHistory() { m_listHistory.ResetContent(); }

5.3 保存历史记录到文件

为了让历史记录在程序关闭后仍然保留,我们可以将其保存到文件中:

void CCalculatorDlg::SaveHistory() { CStdioFile file; if (file.Open(_T("calculator_history.txt"), CFile::modeCreate | CFile::modeWrite)) { for (int i = 0; i < m_listHistory.GetCount(); i++) { CString strItem; m_listHistory.GetText(i, strItem); file.WriteString(strItem + _T("\n")); } file.Close(); } } void CCalculatorDlg::LoadHistory() { CStdioFile file; if (file.Open(_T("calculator_history.txt"), CFile::modeRead)) { CString strLine; m_listHistory.ResetContent(); while (file.ReadString(strLine)) { m_listHistory.AddString(strLine); } file.Close(); } }

在对话框的OnDestroy和OnInitDialog中分别调用这两个函数。

6. 界面美化与用户体验优化

一个专业的计算器不仅功能要完善,界面也要美观易用。这里分享几个我常用的优化技巧:

6.1 按钮美化

使用自绘按钮可以让界面更美观。创建一个继承自CButton的新类,重写DrawItem函数:

void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC); CRect rect = lpDrawItemStruct->rcItem; // 绘制按钮背景 if (lpDrawItemStruct->itemState & ODS_SELECTED) pDC->FillSolidRect(rect, RGB(200, 200, 200)); // 按下状态 else pDC->FillSolidRect(rect, RGB(240, 240, 240)); // 正常状态 // 绘制边框 pDC->Draw3dRect(rect, RGB(150, 150, 150), RGB(250, 250, 250)); // 绘制文本 CString strText; GetWindowText(strText); pDC->SetBkMode(TRANSPARENT); pDC->DrawText(strText, rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); }

6.2 添加快捷键支持

重写PreTranslateMessage函数,添加键盘支持:

BOOL CCalculatorDlg::PreTranslateMessage(MSG* pMsg) { if (pMsg->message == WM_KEYDOWN) { switch (pMsg->wParam) { case '0': OnBnClickedButton0(); break; case '1': OnBnClickedButton1(); break; // 其他数字键... case VK_RETURN: OnBnClickedButtonEqual(); break; case VK_ESCAPE: OnBnClickedButtonClear(); break; } } return CDialogEx::PreTranslateMessage(pMsg); }

6.3 添加动画效果

为按钮点击添加简单的动画效果可以提升用户体验。在按钮的BN_CLICKED处理函数中添加:

void CCalculatorDlg::OnBnClickedButton1() { // 获取按钮控件 CWnd* pButton = GetDlgItem(IDC_BUTTON_1); // 创建缩小动画 CRect rect; pButton->GetWindowRect(&rect); ScreenToClient(&rect); rect.DeflateRect(2, 2); pButton->MoveWindow(rect); // 恢复原状 rect.InflateRect(2, 2); pButton->MoveWindow(rect); // 原来的按钮处理代码... }

7. 错误处理与边界情况

一个健壮的计算器需要处理好各种边界情况和错误输入。以下是几个常见的处理点:

7.1 除零错误

在除法运算时检查除数是否为零:

if (m_strOperator == _T("/") && dOperand2 == 0) { MessageBox(_T("除数不能为零!"), _T("错误"), MB_ICONERROR); return; }

7.2 连续运算符处理

防止用户输入多个运算符:

void CCalculatorDlg::OnBnClickedButtonAdd() { if (!m_strOperand1.IsEmpty() && m_strOperand2.IsEmpty()) { m_strOperator = _T("+"); SetDlgItemText(IDC_EDIT_OPERATOR, m_strOperator); m_bOperand2Active = TRUE; } }

7.3 无效输入处理

检查输入是否为有效数字:

BOOL IsValidNumber(const CString& str) { if (str.IsEmpty()) return FALSE; for (int i = 0; i < str.GetLength(); i++) { TCHAR ch = str[i]; if (!(isdigit(ch) || ch == '.' || (i == 0 && ch == '-'))) return FALSE; } return TRUE; }

7.4 内存溢出处理

对于极大或极小的计算结果进行处理:

void CCalculatorDlg::OnBnClickedButtonEqual() { // 计算代码... if (!_finite(dResult)) { MessageBox(_T("计算结果超出范围!"), _T("错误"), MB_ICONERROR); return; } // 显示结果... }

8. 高级功能扩展

如果你想进一步提升这个计算器,可以考虑添加以下功能:

8.1 科学计算功能

添加平方根、幂运算、三角函数等科学计算功能:

void CCalculatorDlg::OnBnClickedButtonSqrt() { if (!m_strOperand1.IsEmpty() && m_strOperand2.IsEmpty()) { double dOperand = _ttof(m_strOperand1); if (dOperand >= 0) { double dResult = sqrt(dOperand); m_strResult.Format(_T("%g"), dResult); SetDlgItemText(IDC_EDIT_RESULT, m_strResult); // 添加到历史记录 CString strHistory; strHistory.Format(_T("√%s = %s"), m_strOperand1, m_strResult); m_listHistory.InsertString(0, strHistory); } else { MessageBox(_T("不能对负数开平方!"), _T("错误"), MB_ICONERROR); } } }

8.2 皮肤切换功能

让用户可以选择不同的界面皮肤:

void CCalculatorDlg::OnBnClickedButtonSkin() { static int nSkinIndex = 0; nSkinIndex = (nSkinIndex + 1) % 3; switch (nSkinIndex) { case 0: SetBackgroundColor(RGB(240, 240, 240)); break; case 1: SetBackgroundColor(RGB(200, 230, 255)); break; case 2: SetBackgroundColor(RGB(255, 240, 200)); break; } RedrawWindow(); }

8.3 计算器模式切换

添加标准计算器和科学计算器模式切换:

void CCalculatorDlg::OnBnClickedButtonMode() { m_bScientificMode = !m_bScientificMode; // 显示/隐藏科学计算按钮 GetDlgItem(IDC_BUTTON_SQRT)->ShowWindow(m_bScientificMode ? SW_SHOW : SW_HIDE); GetDlgItem(IDC_BUTTON_SIN)->ShowWindow(m_bScientificMode ? SW_SHOW : SW_HIDE); // 其他科学计算按钮... // 调整对话框大小 CRect rect; GetWindowRect(&rect); if (m_bScientificMode) rect.right += 100; // 扩大宽度 else rect.right -= 100; // 恢复原宽度 MoveWindow(&rect); }

8.4 语音播报功能

使用Windows语音API实现计算结果的语音播报:

#include <sapi.h> void CCalculatorDlg::SpeakResult() { ISpVoice* pVoice = NULL; if (SUCCEEDED(CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void**)&pVoice))) { CString strSpeak; strSpeak.Format(_T("计算结果为 %s"), m_strResult); pVoice->Speak(strSpeak, 0, NULL); pVoice->Release(); } }

记得在StdAfx.h中添加:

#import "libid:866411B6-7F53-4CDE-8B71-A9FB5F8A8C0B" \ rename_namespace("SpeechLib") \ named_guids using namespace SpeechLib;
http://www.gsyq.cn/news/1394078.html

相关文章:

  • 28nm CMOS Via二极管:高密度RRAM阵列的工艺兼容性选择器方案
  • 2026小红书视频提取方法大全|小红书视频提取免费工具实测推荐 - 科技热点发布
  • 二维码扫描模组怎么选?从技术参数与应用场景综合选型分析
  • Python SQLAlchemy实战:构建PostgreSQL数据操作层
  • 2026年湖南钢模板定制租赁全攻略:从BIM设计到共享平台,如何避坑降本30%+ - 企业名录优选推荐
  • 智能游戏助手Seraphine:英雄联盟排位赛的自动BP与数据分析神器
  • Taotoken模型广场功能使用指南,快速筛选适合你任务的模型
  • Mermaid实时编辑器终极指南:为什么选择实时编辑器胜过其他图表工具
  • 上海出手黄金计价避坑手册 远离克扣克重不良套路 - 奢侈品回收测评
  • 【Lovable平台安全合规白皮书级解析】:等保2.0三级认证必备的6类日志审计配置+3项加密强制项
  • AI公司烧不起Token了!国产Agent杀出,逼近Opus 4.6还免费,天工AI发布SkyClaw-v1.0:面向真实工作流的百万上下文 Agent 模型
  • 5步解锁UI-TARS桌面版:零代码GUI自动化革命
  • WPS 文字 表格美化(三线表)操作步骤解析
  • 旺哥黄金回收(连锁品牌)|2026年5月华宁黄金回收行情,连锁保障高价回收 - 润富黄金珠宝行
  • 深度解析:SillyTavern AI对话前端零数据丢失迁移架构与技术实现
  • 基于参数化量子电路的可训练QRAM设计与量子机器学习应用
  • 基于Split-Attention CNN与RoPE+GCN的鲁棒语音活动检测新架构
  • 镇江黄金回收六大品牌测评(2026年5月)|全市覆盖+实时金价+靠谱商家分级推荐 - 润富黄金珠宝行
  • Exokit支持的10大硬件平台:从Magic Leap到Oculus全攻略
  • Illustrator智能填充脚本:让设计效率飙升80%的自动化解决方案
  • CS2_External:解密游戏逆向工程与外部注入技术的实战秘籍
  • Image-Downloader终极指南:5分钟掌握高效图片批量下载神器
  • stohMCharts:面向CPSS的随机混合状态图建模与概率验证
  • Cats Blender插件:5分钟完成VRChat模型优化的终极指南 [特殊字符]
  • 2026年武夷山酒店推荐哪家好?TOP5酒店排名评测指南 - 江湖评测
  • 为什么Rust中调用泛型函数要用::分隔函数名和泛型参数?
  • 波波改灯改灯21年老店,2026年最新北京改灯市场分析,波波改灯是专业靠谱口碑好的首推五星级门店 - 北京新语
  • Hindsight记忆告警:及时发现和解决系统问题
  • iTop配置管理实战指南:从CMDB设计到资产全生命周期追踪
  • 2026年福建钢模板定制租赁服务商选型指南:从工期零延误到资产价值最大化 - 企业名录优选推荐