三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

VS2019 MFC计算器开发实战:从环境搭建到状态机设计

VS2019 MFC计算器开发实战:从环境搭建到状态机设计

1. 项目概述与核心价值

最近在整理一些老项目的代码,翻到了一个用C++ MFC在VS2019里写的计算器。这玩意儿虽然看起来简单,就是个标准四则运算器,但麻雀虽小五脏俱全。对于刚接触Windows桌面开发,特别是想从控制台程序转向有图形界面的朋友来说,用MFC做一个计算器,是一个绝佳的练手项目。它几乎涵盖了MFC对话框程序的所有基础要素:界面布局、控件事件响应、数据验证、业务逻辑与界面分离,甚至还能浅浅地涉及到一些状态管理。网上很多教程要么太老,用的还是VC6.0或者VS2008,环境配置就劝退一片;要么就是只给个最终代码,中间的坑一个不提。今天我就把这个项目的完整实现过程,包括那些容易踩坑的细节,从头到尾捋一遍,附带可以直接在VS2019里跑起来的代码。

这个计算器实现的是基础功能:加、减、乘、除、清空、退格,以及连续运算。我们会采用经典的MFC对话框应用程序架构。通过这个项目,你不仅能学会如何在VS2019中创建和配置MFC项目,更能理解消息映射机制如何将按钮点击转化为具体的运算指令,以及如何设计一个健壮、不易出错的运算逻辑。代码我会放在最后,但更重要的是中间的设计思路和调试过程,这些才是你以后做其他项目时真正用得上的东西。

2. 开发环境搭建与项目创建

2.1 VS2019中MFC支持的安装

很多新手第一步就卡住了:打开VS2019新建项目,发现找不到MFC的选项。这是因为在默认安装模式下,VS2019不会包含用于桌面开发的C++ MFC组件。你需要通过Visual Studio安装程序手动添加。

操作步骤如下:关闭所有VS实例,在Windows开始菜单找到“Visual Studio Installer”并打开。找到你已安装的VS2019版本,点击“修改”。在弹出的工作负载选项卡中,你需要确保“使用C++的桌面开发”这一项是被勾选的。但这还不够,关键在“单个组件”选项卡里。在搜索框输入“MFC”,你会看到两个关键组件:“用于最新v142生成工具的C++ MFC(x86 & x64)”和可选的“MFC用于ATL支持”。勾选前者,这是核心。如果你计划进行一些更底层的COM操作,可以一并勾选后者。然后点击右下角的“修改”按钮,等待安装程序下载并安装这些组件。这个过程可能需要一些时间,取决于你的网速。

注意:务必确认安装的是对应你VS2019版本(v142工具集)的MFC组件。如果你之前安装过其他版本的VS(如2017),系统里可能存在多个版本的MFC库,在项目属性设置时需要留意,避免链接错误。

2.2 创建MFC对话框项目

组件安装成功后,启动VS2019,点击“创建新项目”。在项目模板搜索框输入“MFC”,选择“MFC应用程序”(注意图标,别选成了ATL项目)。点击“下一步”。

在“配置项目”页面,给你的项目起个名字,比如“SimpleCalculator”。选择项目存放的位置。注意“解决方案”和“解决方案名称”默认会和项目名一致,放在一个文件夹里,这对于单个小项目来说很清晰,点击“创建”。

接下来是MFC应用程序向导,这里有几处关键配置:

  1. 应用程序类型:选择“基于对话框”。我们不需要文档/视图架构那种复杂的结构,一个简单的对话框足矣。“资源语言”根据你需求选择,这里选“中文(简体,中国)”。
  2. 用户界面功能:保持默认即可。“使用经典菜单”可以勾选,但我们计算器通常不需要顶部菜单栏,所以不勾选。“关于框”可以保留,它是一个标准的对话框,用于显示版本信息。
  3. 高级功能:通常不需要改动。确保“公共控件清单”是启用的,这关系到界面控件的外观(比如是否启用Visual Style)。
  4. 生成的类:最后一步,向导会展示即将生成的类。最主要的是CSimpleCalculatorDlg类,它继承自CDialogEx,是我们程序的主窗口类。你可以在这里修改类名或头文件名,但一般保持默认就好。

点击“完成”,VS2019会自动为你生成一个MFC对话框应用程序的骨架。编译并运行(F5),你应该能看到一个空白的对话框窗口,标题是你的项目名。这说明你的MFC开发环境已经搭建成功。

3. 计算器界面设计与控件布局

3.1 界面元素规划与控件选择

计算器的界面我们模仿经典Windows计算器的布局。主要分为两大区域:显示区和按键区。

  • 显示区:需要一个控件来显示输入的数字、运算符和最终结果。这里我们选用Edit Control(编辑框控件)。为什么不直接用Static Text?因为编辑框自带光标、文本选择、以及通过程序控制文本内容(SetWindowText/GetWindowText)非常方便,虽然我们禁用了用户直接键盘输入,但编程操作很顺手。
  • 按键区:包括数字按钮(0-9)、运算符按钮(+, -, *, /, =)、功能按钮(清除C, 退格←, 小数点.)。这些全部使用Button Control(按钮控件)。

我们需要考虑一个细节:计算器的显示框应该只能显示,不允许用户用键盘胡乱输入字符干扰计算逻辑。这可以通过设置编辑框的属性为“只读”来实现。

3.2 使用对话框编辑器进行布局

在“解决方案资源管理器”中,展开“资源文件”,双击打开SimpleCalculator.rc文件,再展开其中的Dialog文件夹,双击IDD_SIMPLECALCULATOR_DIALOG。这会打开对话框资源编辑器。

首先,调整对话框大小,拖拽右下角,使其变成一个纵向的矩形,适合计算器的形状。删除向导自动生成的“TODO:在此放置对话框控件。”静态文本和“确定”、“取消”按钮。

然后从“工具箱”面板(如果没看到,可以从“视图”菜单打开)拖拽控件:

  1. 拖拽一个Edit Control到对话框顶部,拉长至几乎与对话框同宽。这是我们的显示屏。关键设置:选中这个编辑框,在“属性”面板(通常在右侧)找到“Styles”下的Read-Only,将其设置为True。再找到“外观”下的Align text,选择Right(右对齐),这样数字就像真的计算器一样靠右显示。为了更美观,还可以修改Font属性,选择一个等宽字体(如Consolas)并调大字号。
  2. 拖拽Button Control来创建按键。建议先制作一个数字“1”的按钮,调整好大小和字体。然后,使用复制粘贴(Ctrl+C, Ctrl+V)来快速创建其他数字和运算符按钮,这样可以保证所有按钮大小一致。粘贴后,只需修改新按钮的Caption属性(显示的文字)和ID属性(控件的唯一标识符)。

控件ID命名规范建议:为了代码清晰,不要使用默认的IDC_BUTTON1这样的ID。建议按功能命名,例如:

  • 数字按钮:IDC_BUTTON_0,IDC_BUTTON_1, ...IDC_BUTTON_9
  • 运算符按钮:IDC_BUTTON_ADD,IDC_BUTTON_SUB,IDC_BUTTON_MUL,IDC_BUTTON_DIV
  • 等号:IDC_BUTTON_EQU
  • 清除:IDC_BUTTON_CLR
  • 退格:IDC_BUTTON_BCK
  • 小数点:IDC_BUTTON_DOT

按照计算器常见的布局(3x4数字键,右侧一列运算符)排列好所有按钮。你可以使用对话框编辑器提供的“使宽度相同”、“使高度相同”、“左对齐”、“水平居中”等布局工具来快速对齐控件,让界面看起来整齐专业。

4. 核心计算逻辑与状态设计

在动手写代码关联按钮之前,我们必须先想清楚计算器的大脑——也就是运算逻辑和状态管理。这是本项目最核心的部分,设计的好坏直接决定了代码是清晰优雅还是一团乱麻。

4.1 运算模型与状态变量

一个支持连续运算(如输入3 + 4 * 5 =)的计算器,需要维护几个关键状态。我们在主对话框类CSimpleCalculatorDlg中声明一些成员变量来保存这些状态。

打开SimpleCalculatorDlg.h文件,在CSimpleCalculatorDlg类定义的protected区域添加以下变量:

// SimpleCalculatorDlg.h class CSimpleCalculatorDlg : public CDialogEx { // ... 其他代码 ... protected: CString m_strDisplay; // 显示在编辑框中的字符串 double m_dOperand1; // 第一个操作数 double m_dOperand2; // 第二个操作数 int m_nOperator; // 当前选择的运算符 (用整数常量代表) BOOL m_bWaitForNewNumber; // 标志:是否等待输入新数字(按了运算符或等号后置为TRUE) BOOL m_bHasDecimal; // 标志:当前输入的数字是否已包含小数点 };

变量作用解析

  • m_strDisplay:与编辑框控件关联,始终反映当前应该显示的内容。使用CString操作字符串非常方便。
  • m_dOperand1m_dOperand2:双精度浮点数,用于存储参与运算的两个数。m_dOperand1也经常作为累计结果的暂存地。
  • m_nOperator:用一个整数来标识当前选择的运算。我们可以定义一些常量,例如OP_NONE=0OP_ADD=1OP_SUB=2OP_MUL=3OP_DIV=4
  • m_bWaitForNewNumber:这是一个非常重要的状态标志。当用户点击了运算符(+,-,*,/)或等号(=)之后,下一次按数字键应该开始输入一个新的数字,而不是追加到当前显示的数字后面。这个标志为TRUE时,就表示处于这种“等待新数”的状态。
  • m_bHasDecimal:用于防止用户在一个数字里输入多个小数点。当用户点击了小数点按钮后,此标志置为TRUE,直到开始输入新数字时再重置。

4.2 运算流程与状态机

计算器的行为可以看作一个简单的状态机。我们梳理一下典型操作序列(如计算3 + 4 =):

  1. 初始状态:显示0m_bWaitForNewNumber = TRUE(因为一开始就可以输入新数),m_nOperator = OP_NONE
  2. 用户按3:因为m_bWaitForNewNumber为真,所以清空显示(或从0开始),显示3,然后将m_bWaitForNewNumber设为FALSE(表示正在输入这个数的后续位)。
  3. 用户按+:此时需要将当前显示的数字3保存到m_dOperand1中,同时记录运算符m_nOperator = OP_ADD,并设置m_bWaitForNewNumber = TRUE(等待输入第二个数)。
  4. 用户按4:因为m_bWaitForNewNumber为真,清空显示,开始显示4,并置m_bWaitForNewNumber = FALSE
  5. 用户按=:将当前显示的数字4保存到m_dOperand2中。根据m_nOperatorOP_ADD)对m_dOperand1m_dOperand2执行加法运算,将结果7存回m_dOperand1(以便支持连续运算),同时显示结果7。最后,设置m_bWaitForNewNumber = TRUE(因为结果显示后,再按数字应开始新输入)。

对于连续运算如3 + 4 + 5 =,关键在于第5步:按完第一个+后,输入4,再按第二个+时,此时m_bWaitForNewNumber为假(正在输入4),所以需要立即计算3+4得到7,显示7,并将7存入m_dOperand1,同时记录新的运算符(第二个+),再等待输入5。这个逻辑确保了运算符键除了“记录操作”外,有时也触发“立即计算”。

5. 控件事件绑定与消息处理

界面和逻辑设计好后,就要让按钮“活”起来。MFC通过“消息映射”机制将控件事件(如按钮点击)关联到类的成员函数。

5.1 为按钮添加事件处理程序

在对话框编辑器中,右键点击数字按钮“1”(IDC_BUTTON_1),选择“添加事件处理程序”。在弹出的对话框中,“消息类型”选择BN_CLICKED(按钮点击),类列表选择CSimpleCalculatorDlg。点击“添加编辑”,VS会自动在SimpleCalculatorDlg.cpp中创建消息映射条目和对应的函数框架,并跳转到该函数体。

按照此方法,为所有数字按钮(0-9)、小数点按钮、运算符按钮(+, -, *, /)、等号按钮、清除按钮、退格按钮都添加BN_CLICKED事件处理程序。函数名可以保持默认(如OnBnClickedButton1),但为了可读性,我建议在添加时修改一下,比如数字按钮的函数名可以叫OnDigitClicked,然后在函数内部根据按钮ID来区分具体是哪个数字。不过,为了教学清晰,我们先为每个按钮创建独立的处理函数。

5.2 初始化对话框与变量

CSimpleCalculatorDlg类的OnInitDialog函数中,我们需要初始化所有成员变量和显示控件。找到BOOL CSimpleCalculatorDlg::OnInitDialog()函数,在调用父类的OnInitDialog()之后,添加初始化代码:

BOOL CSimpleCalculatorDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // ... 其他自动生成的代码(如设置图标等)... // 初始化计算器状态 m_strDisplay = _T("0"); m_dOperand1 = 0.0; m_dOperand2 = 0.0; m_nOperator = OP_NONE; // 需要在头文件定义这些常量,例如 enum {OP_NONE, OP_ADD, OP_SUB, OP_MUL, OP_DIV}; m_bWaitForNewNumber = TRUE; m_bHasDecimal = FALSE; // 将初始化后的显示字符串更新到编辑框控件 UpdateDisplay(); return TRUE; }

UpdateDisplay()是我们需要自己写的一个辅助函数,它的作用就是将成员变量m_strDisplay的内容设置到ID为IDC_EDIT_DISPLAY(这是你之前给显示编辑框设置的ID)的控件上。

void CSimpleCalculatorDlg::UpdateDisplay() { SetDlgItemText(IDC_EDIT_DISPLAY, m_strDisplay); }

5.3 实现数字按钮的处理函数

所有数字按钮(0-9)的处理逻辑是相似的:将对应的数字字符追加到显示字符串的末尾。但需要根据m_bWaitForNewNumber标志决定是追加还是重新开始。

IDC_BUTTON_1的处理函数OnBnClickedButton1为例:

void CSimpleCalculatorDlg::OnBnClickedButton1() { AppendDigitToDisplay(_T('1')); }

我们将核心逻辑抽离成一个公共函数AppendDigitToDisplay(TCHAR chDigit),这样所有数字按钮的处理函数只需调用它并传入不同的字符即可。

void CSimpleCalculatorDlg::AppendDigitToDisplay(TCHAR chDigit) { // 如果处于等待新数字状态,或者当前显示的就是"0" if (m_bWaitForNewNumber || m_strDisplay == _T("0")) { m_strDisplay.Empty(); // 清空显示 m_bWaitForNewNumber = FALSE; // 结束等待状态 m_bHasDecimal = FALSE; // 新数字开始,重置小数点标志 } // 追加数字字符 m_strDisplay += chDigit; // 更新显示 UpdateDisplay(); }

这样,数字按钮2-9以及0的处理函数都类似,只是传入的字符不同。注意,对于0按钮,逻辑稍微特殊一点,因为要处理“显示0”和“追加0”的情况,但上面的AppendDigitToDisplay函数已经通过判断m_strDisplay == _T("0")处理了初始状态,所以可以直接复用。

5.4 实现运算符按钮的处理函数

运算符按钮(+, -, *, /)的逻辑是核心中的核心。它需要处理两种场景:1. 首次设置运算符;2. 连续运算中更换运算符。

我们以加法按钮IDC_BUTTON_ADD的处理函数为例:

void CSimpleCalculatorDlg::OnBnClickedButtonAdd() { PerformPendingOperation(); // 如果已有待处理的操作,先执行它 m_nOperator = OP_ADD; // 设置当前运算符为加法 StoreDisplayToOperand1(); // 将当前显示的值存储为第一个操作数 m_bWaitForNewNumber = TRUE; // 等待用户输入第二个数 }

这里引入了两个新的辅助函数:

  • PerformPendingOperation():检查m_nOperator是否是一个有效的运算符(非OP_NONE),并且m_bWaitForNewNumber是否为FALSE(表示用户已经输入了第二个数的一部分)。如果条件满足,则执行m_dOperand1和当前显示值(作为m_dOperand2)的运算,并将结果显示出来,结果同时存回m_dOperand1。这个函数实现了“连续运算”中,当你输入3 + 4 +时,按下第二个+会先计算出7的功能。
  • StoreDisplayToOperand1():将当前显示字符串m_strDisplay转换为双精度浮点数,并存储到m_dOperand1中。可以使用_ttof函数进行转换。

减法、乘法、除法的处理函数与此完全类似,只需改变m_nOperator为对应的常量即可。

5.5 实现等号、清除、退格和小数点功能

  • 等号按钮:等号的处理类似于运算符按钮,但它执行运算后,会将运算符重置为OP_NONE,表示一次完整的计算结束。

    void CSimpleCalculatorDlg::OnBnClickedButtonEqu() { if (m_nOperator != OP_NONE && !m_bWaitForNewNumber) { // 将当前显示值作为第二个操作数 m_dOperand2 = _ttof(m_strDisplay); // 根据运算符进行计算 CalculateResult(); // 计算完成后,重置运算符,并设置等待新数字状态 m_nOperator = OP_NONE; m_bWaitForNewNumber = TRUE; } }

    CalculateResult()函数根据m_nOperator执行具体的四则运算,并将结果格式化后存入m_strDisplay,同时更新m_dOperand1为结果值。

  • 清除按钮:最简单,直接调用一个ResetCalculator()函数,将所有状态变量和显示重置为初始值。

    void CSimpleCalculatorDlg::OnBnClickedButtonClr() { ResetCalculator(); } void CSimpleCalculatorDlg::ResetCalculator() { m_strDisplay = _T("0"); m_dOperand1 = 0.0; m_dOperand2 = 0.0; m_nOperator = OP_NONE; m_bWaitForNewNumber = TRUE; m_bHasDecimal = FALSE; UpdateDisplay(); }
  • 退格按钮:从显示字符串末尾删除一个字符。如果删除后字符串为空,或者只剩下一个负号,则显示0并设置m_bWaitForNewNumber = TRUE。还需要注意,如果删除了小数点,要将m_bHasDecimal标志重置为FALSE

    void CSimpleCalculatorDlg::OnBnClickedButtonBck() { int nLen = m_strDisplay.GetLength(); if (nLen > 1) { // 检查删除的是否是小数点 if (m_strDisplay.GetAt(nLen - 1) == _T('.')) { m_bHasDecimal = FALSE; } m_strDisplay = m_strDisplay.Left(nLen - 1); // 取左边nLen-1个字符 } else { m_strDisplay = _T("0"); m_bWaitForNewNumber = TRUE; } UpdateDisplay(); }
  • 小数点按钮:首先检查m_bHasDecimal标志,如果当前数字还没有小数点,则追加一个小数点.,并将m_bHasDecimal设为TRUE。同样需要处理m_bWaitForNewNumber状态。

    void CSimpleCalculatorDlg::OnBnClickedButtonDot() { if (m_bWaitForNewNumber) { m_strDisplay = _T("0"); m_bWaitForNewNumber = FALSE; } if (!m_bHasDecimal) { m_strDisplay += _T('.'); m_bHasDecimal = TRUE; UpdateDisplay(); } }

6. 关键代码实现与难点解析

6.1 执行待定运算函数详解

PerformPendingOperation()是保证连续运算正确的关键。它的实现需要仔细处理状态。

void CSimpleCalculatorDlg::PerformPendingOperation() { // 只有在已有运算符且不处于“等待新数”状态时,才需要执行前一个运算 // “不处于等待新数”意味着用户已经在输入第二个操作数了 if (m_nOperator != OP_NONE && !m_bWaitForNewNumber) { // 将当前显示的内容作为第二个操作数 m_dOperand2 = _ttof(m_strDisplay); // 执行计算 CalculateResult(); // 计算后,结果显示在m_strDisplay中,同时m_dOperand1也更新为结果 // 此时,m_dOperand1已经保存了最新的结果,作为下一次运算的第一个操作数 // 注意:这里不重置m_nOperator,也不设置m_bWaitForNewNumber // 因为调用此函数后,外部函数(如运算符处理函数)可能会设置新的运算符 } }

6.2 核心计算函数与除零处理

CalculateResult()函数负责具体的算术运算和错误处理。

void CSimpleCalculatorDlg::CalculateResult() { double dResult = 0.0; BOOL bError = FALSE; switch (m_nOperator) { case OP_ADD: dResult = m_dOperand1 + m_dOperand2; break; case OP_SUB: dResult = m_dOperand1 - m_dOperand2; break; case OP_MUL: dResult = m_dOperand1 * m_dOperand2; break; case OP_DIV: if (fabs(m_dOperand2) < 1e-12) // 判断除数是否为零(考虑浮点误差) { bError = TRUE; m_strDisplay = _T("错误:除零"); } else { dResult = m_dOperand1 / m_dOperand2; } break; default: // 不应该执行到这里 dResult = m_dOperand2; // 如果没有运算符,直接显示第二个操作数 break; } if (!bError) { // 将结果转换为字符串。这里需要处理格式化,例如避免显示过多小数位。 FormatResult(dResult); // 将结果存储回m_dOperand1,以便后续连续运算 m_dOperand1 = dResult; } else { // 发生错误(如除零),更新显示即可,不更新m_dOperand1 UpdateDisplay(); } }

FormatResult(double dValue)是一个辅助函数,用于将双精度浮点数格式化为一个美观的字符串。一个简单的实现是使用CStringFormat方法,并控制小数位数,例如:

void CSimpleCalculatorDlg::FormatResult(double dValue) { // 如果结果是整数,则显示为整数格式,否则显示为带两位小数的格式 if (fabs(dValue - (int)dValue) < 1e-12) { m_strDisplay.Format(_T("%.0f"), dValue); } else { // 限制小数位数,避免因浮点精度产生一长串数字 m_strDisplay.Format(_T("%.10g"), dValue); // %.10g 格式会根据数值自动选择定点或科学计数法,并保留最多10位有效数字 } UpdateDisplay(); }

7. 项目调试、常见问题与优化建议

7.1 编译与运行时常见问题

  1. 无法打开源文件“stdafx.h”:这是MFC项目使用的预编译头文件。确保你没有误删stdafx.hstdafx.cpp文件,并且在每个.cpp文件的开头都#include "stdafx.h"。在VS2019中创建MFC项目时,默认配置是使用预编译头的。
  2. 控件ID未定义错误:如果你在代码中使用了IDC_BUTTON_ADD这样的ID,但编译时提示“未声明的标识符”,请检查“资源视图”中控件的ID是否与你代码中写的一致。最可靠的方法是,在对话框编辑器中双击按钮,让VS自动生成消息处理函数,它会使用正确的ID。
  3. 链接错误 LNK2001/LNK2019:通常是成员函数声明了但未定义,或者定义了但实现签名(参数、常量性)与声明不匹配。仔细检查.h文件中的函数声明和.cpp文件中的函数定义是否完全一致。特别是消息映射函数,它们不需要在.h中声明,但必须在.cpp的消息映射宏BEGIN_MESSAGE_MAPEND_MESSAGE_MAP之间正确关联。
  4. 程序运行后点击按钮无反应
    • 首先检查:是否为按钮正确添加了BN_CLICKED消息处理程序?在“类视图”中展开CSimpleCalculatorDlg,查看是否有对应的函数(如OnBnClickedButtonAdd)。
    • 检查消息映射:打开SimpleCalculatorDlg.cpp,找到BEGIN_MESSAGE_MAP(CSimpleCalculatorDlg, CDialogEx)END_MESSAGE_MAP()之间的部分,确认有类似ON_BN_CLICKED(IDC_BUTTON_ADD, &CSimpleCalculatorDlg::OnBnClickedButtonAdd)的条目。如果没有,需要手动添加,或者通过属性窗口的“事件”选项卡重新添加。

7.2 逻辑错误排查技巧

  1. 连续运算结果不对:这是最常见的逻辑bug。使用调试器,在PerformPendingOperation、运算符处理函数和等号处理函数中设置断点。逐步跟踪m_dOperand1m_dOperand2m_nOperatorm_bWaitForNewNumber这几个关键变量的值变化,画出状态转移图,与你的设计逻辑对比。
  2. 小数点可以输入多个:检查m_bHasDecimal标志的逻辑。确保在AppendDigitToDisplay函数中开始输入新数字时(即m_bWaitForNewNumber为真时)将其重置为FALSE。确保在点击小数点按钮时正确设置为TRUE,并且在退格删除小数点时重置为FALSE
  3. 显示格式问题:比如计算结果出现“3.0000000001”或“2.9999999999”。这是浮点数精度问题的典型表现。在FormatResult函数中,不要直接使用%f格式化,而是像上面示例一样,先判断是否为“整数”(在极小的误差范围内),或者使用%g格式符来控制有效数字位数。

7.3 功能扩展与优化建议

一个基础计算器完成后,你可以尝试以下扩展,这能极大提升你对MFC和编程逻辑的理解:

  1. 支持键盘输入:重写对话框的PreTranslateMessage函数,拦截键盘消息(WM_KEYDOWN),将数字键、回车键(等号)、退格键、运算符键映射到相应的按钮点击事件。
  2. 添加历史记录功能:在对话框上增加一个列表框(List Box)控件,每次完成一次计算(按等号或连续运算中按运算符),将运算式(如“3 + 4 = 7”)添加到列表框中。
  3. 实现括号和优先级:这需要将中缀表达式(如3 + 4 * 5)转换为后缀表达式(逆波兰式)再进行求值,或者使用两个栈(操作数栈和运算符栈)来模拟求值过程。这是一个经典的算法练习。
  4. 改进UI:使用位图按钮(CBitmapButton)来制作更漂亮的皮肤。或者,尝试使用MFC的CMFCButton类(如果项目配置了BCGControlBar或类似库),它支持更多的视觉样式。
  5. 代码重构:将计算核心逻辑(状态管理、运算执行)抽离到一个独立的CCalculatorEngine类中。对话框类只负责界面交互和调用引擎类的方法。这样实现了模型与视图的分离,代码更清晰,也便于单元测试。

通过这个完整的MFC计算器项目,你不仅学会了拖控件和写事件处理函数,更重要的是理解了如何为一个交互式应用设计清晰的状态机,如何组织代码使其易于维护和扩展。这些经验,在你未来使用Qt、WPF乃至各种前端框架开发GUI应用时,都是相通的。

← 返回列表