前面已经介绍了按钮的创建与显示,(MFCApplication中最后加上的链接部分)
通过定义一个CButton m_btn 变量,然后使用Create和ShowWindow函数创建按钮和显示,如下面两行代码:
m_btn.Create(_T("我是按钮"), WS_CHILD | BS_DEFPUSHBUTTON, CRect(300, 0, 400, 100), this, 123);
m_btn.ShowWindow(SW_SHOWNORMAL);
也可以直接使用btn_number.Create(temp_num, WS_CHILD | BS_DEFPUSHBUTTON | WS_VISIBLE, CRect, this, 123);来实现上述的两行代码所代表的功能。
接下来尝试做一个 计算器应用程序(理论上是参考Microsoft自带的计算器,科学计数法or标准模式),具备基本的加减乘除功能
具体而言,具备0-9这10个数字按钮,和=+-*/这五个符号按钮,
按照上一次的MFCApplication程序,这次同样建立了一个单文档项目。同样可以直接运行,但是也只有一个界面框架,还需要我们自己设计,添加内容。
整个项目分以下几个步骤:
第一步是添加按钮,一共十五个按钮,分成三行,每一行五个,但是需要输出显示,所以设计四行,第一行的空间用于文本输出,显示字符,后面三行摆放十五个按钮,用于点击输入数据。
第二步则是响应事件,当我们点击按钮时,捕捉消息,然后在内部存储数据
第三步是计算,当按下等号之后,进行事件响应,然后对得到的表达式进行计算,
第四步就是输出显示,显示整个表达式以及计算结果。
第一步,添加按钮。在View中添加按钮,在OnCreate添加代码,可以一句一句的添加,但是发现除了按钮的坐标和指定按钮控件的文本(标题)不一样其他都是一致的,是可以利用循环来解决的。(其实也可以把数字按钮和符号按钮用一个循环实现的,即用一个字符数组存储数字字符和运算符字符,然后循环创建显示)
1 wchar_t temp_num[5]; 2 for (int i = 0; i < 10; i++) { 3 wsprintfW(temp_num, L"%d", i); 4 btn_number[i].Create(temp_num, WS_CHILD | BS_DEFPUSHBUTTON | WS_VISIBLE, CRect(i % 5 * 50, (i / 5 + 1) * 50, i % 5 * 50 + 50, (i / 5 + 2) * 50), this, 100 + i); 5 } 6 wchar_t temp_op[5][5] = { L"+" ,L"-" ,L"*" ,L"/" ,L"=" }; 7 for (int i = 0; i < 5; i++) { 8 btn_operator[i].Create(temp_op[i], WS_CHILD | BS_DEFPUSHBUTTON | WS_VISIBLE, CRect(i * 50, 150, (i + 1) * 50, 200), this, 110 + i); 9 }
第二步,响应点击按钮这个消息事件,显然会点击鼠标左键按下,故而想响应这个左键按下的事件,然后再去判断是哪一个按钮,之前好像是利用OnLButtonDown响应WM_LBUTTONDOWN(见类向导,消息)消息来处理的,但是发生了问题,没有解决,然后删了代码,忘记了
后来百度找到了PreTranslateMessage函数,(类向导,虚函数中),(百度百科:在MFC中,PreTranslateMessage是虚函数,是用来截获消息的。我们可以通过重载它来处理键盘和鼠标消息。)在这个函数里面做判断 if(pMsg->message == WM_LBUTTONDOWN),是否是按下左键的消息,然后再去检查是那一个按钮被按下,可以利用MSG中存储的句柄hwnd和按钮的句柄进行对比,相同则是按下了该按钮。然后就可以把对应的字符信息添加到表达式字符串中,构成我们的表达式字符串。
第三步,当检测到输入等号之后,利用表达式字符串进行计算,显然这里需要用到中缀表达式后缀表达式的相关知识,可以先把其转化为后缀表达式,然后再计算,数据结构,栈,实现 --> 之前有写过一片关于后缀表达式的笔记,借鉴抄一下,(中缀表达式转后缀表达式并计算——栈)
此处是定义了一个Tool类,(就是添加了一个Tool.h和Tool.cpp文件,写的有点烂,勉强用一下)用于实现表达式的计算。
JSQTool.h
1 #pragma once 2 3 #include <stack> 4 #include <string> 5 6 using namespace std; 7 8 class Tool { 9 public: 10 string str; // 中缀表达式 11 12 13 public: 14 string Trans(string str); 15 double Compvalue(string str); 16 };
#include "stdafx.h" #include "JSQTool.h" #include <stack> #include <string> using namespace std; // 中缀转后缀表达式 string Tool::Trans(string str) { stack<char> st; string result = ""; char e; int i = 0; while (str[i] != '=') { switch (str[i]) { case '+': case '-': while (!st.empty()) { e = st.top(); result += e; st.pop(); } st.push(str[i]); i++; break; case '*': case '/': while (!st.empty()) { e = st.top(); if (e == '*' || e == '/') { result += e; st.pop(); } else break; } st.push(str[i]); i++; break; default: // 数字字符处理 while (str[i] >= '0' && str[i] <= '9') { result += str[i]; i++; } result += '#'; // 标识一个数字串结束 } } while (!st.empty()) { e = st.top(); st.pop(); result += e; } // result = '\0'; // 反正不能加 return result; } // 后缀表达式计算 double Tool::Compvalue(string str) { stack<double> st; double result = 0, a = 0, b = 0, temp = 0; int i = 0; while (str[i] != '\0') { switch (str[i]) { case '+':a = st.top(); st.pop(); b = st.top(); st.pop(); // + ->出栈两个元素 st.push(a + b); break; case '-':a = st.top(); st.pop(); b = st.top(); st.pop(); st.push(b - a); break; case '*':a = st.top(); st.pop(); b = st.top(); st.pop(); st.push(a * b); break; case '/':a = st.top(); st.pop(); b = st.top(); st.pop(); if (a != 0) st.push(b / a); else exit(0);// 错误警告 break; default: // 数字字符处理 temp = 0; while (str[i] >= '0' && str[i] <= '9') { temp = 10 * temp + str[i] - '0'; i++; } st.push(temp); // 进栈 break; } i++; } return st.top(); }
第四步,计算表达式,得到了整个表达式的运算结果,此时需要显示运算结果,目前只知道利用MessageBox显示数据
还可以用下面两行代码实现:
CClientDC dc(this);
dc.TextOutW(0, 0, LPCTSTR(result + value));
但是,当接着进行第二次计算时(按下等号之后表达式被清空,重新开始一轮,输出的字符会覆盖在前面计算的字符上,显示区域没清空。。。而且TextOut不支持换行,需要手动控制一下(虽然这里不需要换行)
结果显示:

主要代码:View.cpp中的OnCreate函数和PreTranslateMessage函数
1 // CJSQ0715View 消息处理程序 2 3 4 int CJSQ0715View::OnCreate(LPCREATESTRUCT lpCreateStruct) 5 { 6 if (CView::OnCreate(lpCreateStruct) == -1) 7 return -1; 8 9 // TODO: 在此添加您专用的创建代码 10 // 创建数字按钮 11 wchar_t temp_num[5]; 12 for (int i = 0; i < 10; i++) { 13 wsprintfW(temp_num, L"%d", i); 14 btn_number[i].Create(temp_num, WS_CHILD | BS_DEFPUSHBUTTON | WS_VISIBLE, CRect(i % 5 * 50, (i / 5 + 1) * 50, i % 5 * 50 + 50, (i / 5 + 2) * 50), this, 100 + i); 15 } 16 wchar_t temp_op[5][5] = { L"+" ,L"-" ,L"*" ,L"/" ,L"=" }; 17 for (int i = 0; i < 5; i++) { 18 btn_operator[i].Create(temp_op[i], WS_CHILD | BS_DEFPUSHBUTTON | WS_VISIBLE, CRect(i * 50, 150, (i + 1) * 50, 200), this, 110 + i); 19 } 20 21 return 0; 22 } 23 24 25 BOOL CJSQ0715View::PreTranslateMessage(MSG* pMsg) 26 { 27 // TODO: 在此添加专用代码和/或调用基类 28 char char_jsq[20] = { "0123456789+-*/=" }; 29 30 if (pMsg->message == WM_LBUTTONDOWN) // 鼠标左键按下,按钮按下,但不一定按着按钮 31 { // 判断按着哪一个按钮 32 for (int i = 0; i < 15; i++) // 检查十五个按钮,判断哪一个被按下 33 { 34 if (pMsg->hwnd == GetDlgItem(100 + i)->m_hWnd) 35 { 36 // MessageBox(_T("按下按键")); 37 result += char_jsq[i]; // 表达式 38 if (char_jsq[i] == '=') // 输入结束,可以计算 39 { 40 MessageBox(LPCTSTR(result)); 41 Tool tool; 42 string index = CW2A(result.GetString()); 43 CString value; 44 value.Format(_T("%0.2f"), tool.Compvalue(tool.Trans(index))); 45 MessageBox(LPCTSTR(result + value)); 46 result = ""; // 新一轮 47 } 48 pMsg->hwnd = NULL; 49 } 50 } 51 } 52 return CView::PreTranslateMessage(pMsg); 53 }
1 // 0-9,+-*/=按键 2 private: 3 CButton btn_number[10]; 4 CButton btn_operator[5]; 5 6 public: 7 // wchar_t result[100]; 8 CString result;
用CClientDC dc(this);
dc.TextOutW(0, 0, LPCTSTR(result + value));
进行输出的结果(即直接在View上输出字符):

无妨,本来就存在缺陷,
至此,这个计算器应用程序勉强可以用一下,
下一篇继续改进。MFC实现计算器02之对话框实现
完整文件有时间整理一下。
2022-08-11回来改进:关于上面说的,没有清空TextOut输出的字符问题,TextOut清除 解决TextOut输出重叠
在TextOut函数前加上两行代码就行,完成!
InvalidateRect(&rc); // rc -> 字符显示的矩形区域
UpdateWindow();
----------------------------------- 本程序中就是下面两行代码;
InvalidateRect(&CRect(0,0,200,50));
UpdateWindow();
11:26:18又回来了,这里明明还有其他方法,当初怎么没想到。。。。就孙鑫教的那个,直接把它抹白,然后重新输出就好了。。。当初竟然忘记了,还苦苦寻找方案。。。。
2022-08-02(0718。。。。)
浙公网安备 33010602011771号