项目概述
在日常生活和程序开发中,计算器作为一个基础但功能强大的工具,其设计与实现能全面展示GUI编程的核心概念。本项目基于C# WinForm技术,完整仿制Windows 10系统中的计算器应用,实现了标准模式、科学模式以及程序员模式三大核心功能模块,支持键盘与鼠标双操作模式,具备完整的编辑功能和界面切换能力。
📊 技术架构与设计模式
项目结构
设计模式应用
状态模式:管理计算器的不同运算状态
策略模式:实现不同计算算法的灵活切换
🎯 功能实现详解
1. 核心计算引擎实现
// --- 核心逻辑 5:等号 (=) --- private void btn_Result_Click(object sender, EventArgs e) { if (CheckErrorState()) return; if (!string.IsNullOrEmpty(_operationClicked)) { double secondOperand; double.TryParse(textBox1.Text, out secondOperand); // 如果有单目/百分比显示的特殊字符串,优先用它拼表达式,否则用数字 string exprPart = string.IsNullOrEmpty(_currentExpressionPart) ? secondOperand.ToString() : _currentExpressionPart; string fullExpr = $"{_resultValue} {_operationClicked} {exprPart} ="; double finalRes = CalculateMath(_resultValue, secondOperand, _operationClicked); if (double.IsNaN(finalRes)) { ShowError("除数不能为零"); return; } textBox1.Text = finalRes.ToString(); textBox2.Text = fullExpr; AddToHistory(fullExpr, finalRes.ToString()); _resultValue = 0; _operationClicked = ""; _isNewEntry = true; _isCalculationPerformed = true; _currentExpressionPart = ""; } // 单目运算按等号也进历史 else if (!string.IsNullOrEmpty(_currentExpressionPart)) { string fullExpr = $"{_currentExpressionPart} ="; string res = textBox1.Text; textBox2.Text = fullExpr; AddToHistory(fullExpr, res); _isNewEntry = true; _isCalculationPerformed = true; _currentExpressionPart = ""; } }2. 界面设计
1.标准模式界面设计
计算器标准模式
2.科学模式界面设计
计算器科学模式
3.程序员模式界面设计
计算器程序员模式
3. 键盘事件处理
// --- 键盘支持 (实现 ProcessCmdKey 以处理所有按键) --- protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { // --- 处理主键盘的组合键 (Shift + 8 为乘号 *) --- // 注意:必须放在 switch 之前判断,因为 switch 很难直接匹配组合键 if (keyData == (Keys.Shift | Keys.D8)) { btn_mult.PerformClick(); return true; } // 数字键 (主键盘 + 小键盘) if ((keyData >= Keys.D0 && keyData <= Keys.D9) || (keyData >= Keys.NumPad0 && keyData <= Keys.NumPad9)) { // 排除 Shift+数字 的情况(防止冲突,比如 Shift+8 是乘号,不应输入数字8) // 如果按下了 Shift 且按的是主键盘数字,则跳过数字输入逻辑(交由上面的组合键或忽略) if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift && keyData <= Keys.D9) { // 这里不做处理,让它往下走,或者直接 break } else { string numStr = keyData.ToString().Replace("NumPad", "").Replace("D", ""); SimulateNumInput(numStr); return true; } } // 运算符与功能键 switch (keyData) { // 加号 (小键盘 + 或 主键盘 Shift + =) case Keys.Add: case (Keys.Shift | Keys.Oemplus): btn_add.PerformClick(); return true; // 减号 (小键盘 - 或 主键盘 -) case Keys.Subtract: case Keys.OemMinus: btn_sub.PerformClick(); return true; // 乘号 (小键盘 *) case Keys.Multiply: btn_mult.PerformClick(); return true; // 除号 (小键盘 / 或 主键盘 /) case Keys.Divide: case Keys.OemQuestion: btn_div.PerformClick(); return true; // 回车键 (=) case Keys.Enter: btn_equal.PerformClick(); return true; // 退格键 (BackSpace) case Keys.Back: btn_back.PerformClick(); return true; // ESC键 (C) case Keys.Escape: btn_clear2.PerformClick(); return true; // 小数点 (小键盘 . 或 主键盘 .) case Keys.Decimal: case Keys.OemPeriod: btn_point.PerformClick(); return true; } return base.ProcessCmdKey(ref msg, keyData); } private void SimulateNumInput(string num) { // 创建一个临时按钮触发点击逻辑,复用现有代码 Button temp = new Button() { Text = num }; btn_Number_Click(temp, EventArgs.Empty); }4. 科学计算功能实现
科学计算函数
// --- 2. 双目运算符 (+ - * /) --- private void btn_Operator_Click(object sender, EventArgs e) { if (CheckErrorState()) return; Button btn = (Button)sender; string op = btn.Text; if (op == "×") op = "×"; if (op == "÷") op = "÷"; if (btn == Btxdeycifang) op = "^"; if (btn == Btmod) op = "Mod"; // 如果刚按过运算符,只替换运算符 if (_isNewEntry && _expressionTokens.Count > 0 && IsOperator(_expressionTokens.Last()) && string.IsNullOrEmpty(_currentExpressionPart)) { _expressionTokens[_expressionTokens.Count - 1] = op; UpdateDisplay(false); return; } // 1. 将当前数值(或单目表达式)推入列表 PushCurrentValueToTokens(); // 2. 推入运算符 _expressionTokens.Add(op); _currentExpressionPart = ""; // 3. 连续计算预览 // 计算当前所有 Token 的结果并显示在 T1,但不清除 Token 列表 double previewRes = MathEvaluator.Evaluate(_expressionTokens, true); // true = 忽略末尾运算符 if (!double.IsNaN(previewRes)) { textBox1.Text = previewRes.ToString(); } UpdateDisplay(false); _isNewEntry = true; _isResultShowing = false; } // --- 3. 单目运算符 (立即包裹) --- private void btn_Unary_Click(object sender, EventArgs e) { if (CheckErrorState()) return; Button btn = (Button)sender; if (!double.TryParse(textBox1.Text, out double num)) return; double res = 0; string func = btn.Text.ToLower(); string innerContent; if (!string.IsNullOrEmpty(_currentExpressionPart)) { innerContent = _currentExpressionPart; } else { innerContent = num.ToString(); } // 根据按钮识别功能 if (btn == Btln || func == "ln") { if (num <= 0) { ShowError("无效输入"); return; } res = Math.Log(num); } else if (btn == Btlog || func == "log") { if (num <= 0) { ShowError("无效输入"); return; } res = Math.Log10(num); } else if (btn == Btjiecheng || func.Contains("!")) { if (num < 0 || Math.Floor(num) != num) { ShowError("无效输入"); return; } res = Factorial((int)num); } else if (btn == Btkaipingfang || func.Contains("√")) { if (num < 0) { ShowError("无效输入"); return; } res = Math.Sqrt(num); } else if (btn == Btsquare || func.Contains("x^2")) { res = Math.Pow(num, 2); } else if (btn == Btdaoshu || func.Contains("1/x")) { if (num == 0) { ShowError("除数不能为零"); return; } res = 1.0 / num; } else if (btn == Bt10decifang || func.Contains("10^")) { res = Math.Pow(10, num); } else if (btn == Btjueduizhi || func.Contains("abs")) { res = Math.Abs(num); } // 三角函数兼容 if (func.Contains("sin")) { res = Math.Sin(ToRad(num)); } if (func.Contains("cos")) { res = Math.Cos(ToRad(num)); } if (func.Contains("tan")) { res = Math.Tan(ToRad(num)); } string symbol = ""; if (btn == Btln || func == "ln") symbol = $"ln({innerContent})"; else if (btn == Btlog || func == "log") symbol = $"log({innerContent})"; else if (btn == Btjiecheng || func.Contains("!")) symbol = $"fact({innerContent})"; else if (btn == Btkaipingfang || func.Contains("√")) symbol = $"√({innerContent})"; else if (btn == Btsquare || func.Contains("x^2")) symbol = $"sqr({innerContent})"; else if (btn == Btdaoshu || func.Contains("1/x")) symbol = $"1/({innerContent})"; else if (btn == Bt10decifang || func.Contains("10^")) symbol = $"10^({innerContent})"; else if (btn == Btjueduizhi || func.Contains("abs")) symbol = $"abs({innerContent})"; else if (func.Contains("sin")) symbol = $"sin({innerContent})"; else if (func.Contains("cos")) symbol = $"cos({innerContent})"; else if (func.Contains("tan")) symbol = $"tan({innerContent})"; // 核心逻辑: 立即更新 T1 结果,记录 T2 表达式部分 textBox1.Text = res.ToString(); _currentExpressionPart = symbol; // 更新上方 T2 显示 (不按等号) UpdateDisplay(false); _isNewEntry = true; }5. 右键菜单与编辑功能
private void InitializeContextMenu() { // 简单的右键复制粘贴 ContextMenuStrip ctx = new ContextMenuStrip(); ctx.Items.Add("复制", null, (s, e) => Clipboard.SetText(textBox1.Text)); ctx.Items.Add("粘贴", null, (s, e) => { if (Clipboard.ContainsText() && double.TryParse(Clipboard.GetText(), out _)) { textBox1.Text = Clipboard.GetText(); } }); textBox1.ContextMenuStrip = ctx; }🔧 核心代码实现
主窗体实现
public Form1() { InitializeComponent(); this.StartPosition = FormStartPosition.CenterScreen; this.KeyPreview = true; // 允许窗体捕获键盘 //设定所有button按钮属性 SetAllButton(); InitializeCalculator(); InitializeContextMenu(); this.FormClosed += (s, e) => Application.Exit(); }计算器数字输入
// --- 核心逻辑 1:数字输入 --- private void btn_Number_Click(object sender, EventArgs e) { if (CheckErrorState()) return; Button btn = (Button)sender; // 如果刚做完单目运算(如sqrt),又直接输数字,需要重置表达式部分 if (!string.IsNullOrEmpty(_currentExpressionPart)) { _currentExpressionPart = ""; // 如果没有运算符,说明是全新开始 if (string.IsNullOrEmpty(_operationClicked)) { textBox2.Text = ""; } else { textBox2.Text = $"{_resultValue} {_operationClicked}"; } } if (textBox1.Text == "0" || _isNewEntry) { textBox1.Text = btn.Text == "." ? "0." : btn.Text; _isNewEntry = false; } else { if (btn.Text == "." && textBox1.Text.Contains(".")) return; textBox1.Text += btn.Text; } // 如果刚运算完(=)就输数字,彻底重置 if (_isCalculationPerformed) { textBox2.Text = ""; _resultValue = 0; _operationClicked = ""; _isCalculationPerformed = false; } }🎨 界面美化技巧
1. Win10风格按钮设计
private void SetAllButton() { List<Control> buttonList = new List<Control>(); foreach (Control con in Controls) { //判断控件类型是否为按钮 if (con is Button) { buttonList.Add(con); } } //遍历所有List,设定属性 for (int i = 0; i < buttonList.Count; i++) { Button btn = buttonList[i] as Button; btn.Fillet(0.3); } }🚀 高级特性实现
1. 计算历史记录
// --- 历史记录系统 --- private void AddToHistory(string expr, string res) { _historyList.Add(new CalcHistory { Expr = expr, Res = res }); RenderHistoryPanel(); } private void RenderHistoryPanel() { historyPanel.Controls.Clear(); // 倒序遍历,最新的在上面 for (int i = _historyList.Count - 1; i >= 0; i--) { var item = _historyList[i]; Panel p = new Panel { Size = new Size(330, 60), Dock = DockStyle.Top, Padding = new Padding(0, 0, 0, 5) }; p.BackColor = Color.Transparent; Label lblExpr = new Label { Text = item.Expr, Location = new Point(10, 5), AutoSize = true, ForeColor = Color.Gray }; Label lblRes = new Label { Text = item.Res, Location = new Point(10, 28), Font = new Font("Microsoft YaHei", 14, FontStyle.Bold), AutoSize = true }; // 点击单条记录填入输入框 EventHandler clickHandler = (s, e) => { textBox1.Text = item.Res; textBox2.Text = item.Expr; _isNewEntry = true; _isCalculationPerformed = true; _operationClicked = ""; _resultValue = 0; }; p.Click += clickHandler; lblExpr.Click += clickHandler; lblRes.Click += clickHandler; // 鼠标悬停效果 p.MouseEnter += (s, e) => p.BackColor = Color.LightGray; p.MouseLeave += (s, e) => p.BackColor = Color.Transparent; p.Controls.Add(lblRes); p.Controls.Add(lblExpr); historyPanel.Controls.Add(p); } } private void button1_Click(object sender, EventArgs e) // 垃圾桶清除历史 { _historyList.Clear(); RenderHistoryPanel(); } // --- 记忆系统 (Memory) 深度重构 --- // 主界面的 MS: 添加新记忆 (不覆盖,添加到栈顶) private void btn_MS_Click(object sender, EventArgs e) { if (CheckErrorState()) return; double val; if (double.TryParse(textBox1.Text, out val)) { _memoryList.Insert(0, val); // 插到最前面 RenderMemoryPanel(); _isNewEntry = true; } } // 主界面的 M+: 给最近的一条记忆加值 private void btn_M_Main_Add_Click(object sender, EventArgs e) { if (CheckErrorState()) return; if (_memoryList.Count == 0) { btn_MS_Click(sender, e); return; } double val; if (double.TryParse(textBox1.Text, out val)) { _memoryList[0] += val; RenderMemoryPanel(); _isNewEntry = true; } } // 主界面的 M-: 给最近的一条记忆减值 private void btn_M_Main_Sub_Click(object sender, EventArgs e) { if (CheckErrorState()) return; if (_memoryList.Count == 0) { btn_MS_Click(sender, e); return; } // Win10如果没记忆按M-也会存新 double val; if (double.TryParse(textBox1.Text, out val)) { _memoryList[0] -= val; RenderMemoryPanel(); _isNewEntry = true; } } // 渲染记忆面板,每条带三个按钮 private void RenderMemoryPanel() { memoryPanel.Controls.Clear(); for (int i = _memoryList.Count - 1; i >= 0; i--) { int index = i; // 闭包陷阱,存局部变量 double memVal = _memoryList[index]; Panel p = new Panel { Size = new Size(330, 75), Dock = DockStyle.Top }; Label lblVal = new Label { Text = memVal.ToString(), Font = new Font("Microsoft YaHei", 16, FontStyle.Bold), Location = new Point(10, 5), AutoSize = true }; // 点击数值填入 lblVal.Click += (s, e) => { textBox1.Text = memVal.ToString(); _isNewEntry = true; }; // 创建三个小按钮 Button btnMC = CreateMemButton("MC", 10, 40); Button btnMAdd = CreateMemButton("M+", 70, 40); Button btnMSub = CreateMemButton("M-", 130, 40); // --- 按钮逻辑 --- btnMC.Click += (s, e) => { _memoryList.RemoveAt(index); RenderMemoryPanel(); }; btnMAdd.Click += (s, e) => { double currentInput; if (double.TryParse(textBox1.Text, out currentInput)) { _memoryList[index] += currentInput; RenderMemoryPanel(); // 刷新显示 _isNewEntry = true; } }; btnMSub.Click += (s, e) => { double currentInput; if (double.TryParse(textBox1.Text, out currentInput)) { _memoryList[index] -= currentInput; RenderMemoryPanel(); _isNewEntry = true; } }; p.Controls.Add(lblVal); p.Controls.Add(btnMC); p.Controls.Add(btnMAdd); p.Controls.Add(btnMSub); p.MouseEnter += (s, e) => p.BackColor = Color.LightGray; p.MouseLeave += (s, e) => p.BackColor = Color.WhiteSmoke; memoryPanel.Controls.Add(p); } }📱 部署与发布
1. 发布设置
目标框架:.NET Framework 4.8
输出类型:Windows应用程序
生成单文件:可选
生成可移植版本:支持
2. 安装程序制作
使用Visual Studio Installer Projects或Inno Setup创建安装程序
🎯 扩展功能建议
程序员模式:添加位运算、逻辑运算
单位换算:长度、重量、温度等
汇率转换:实时获取汇率数据
历史图表:可视化计算历史
自定义主题:支持用户自定义界面颜色
语音输入:通过语音识别进行输入
多语言支持:国际化界面
云端同步:保存计算历史和设置
📋 项目总结
通过本项目的实现,我们不仅完成了一个功能完整的计算器应用,还深入掌握了:
WinForm高级编程:自定义绘制、事件处理、控件布局
计算器核心算法:表达式求值、状态管理、错误处理
用户体验优化:键盘支持、右键菜单、界面美化
工程化实践:代码分层、设计模式、单元测试
本项目可作为学习WinForm编程的优秀示例,涵盖了GUI开发的各个方面,适合初学者系统学习和进阶开发者参考借鉴。