289 lines
9.9 KiB
Python
289 lines
9.9 KiB
Python
"""
|
||
简易计算器GUI程序
|
||
[版本信息]
|
||
版本号 | 日期 | 作者 | 修改摘要
|
||
----------|------------|------------|------------------
|
||
v1.0.0 | 2025-04-19 | 鲁豫 | 初始版本,实现基本加减法功能
|
||
|
||
[作者信息]
|
||
开发团队:Python学习
|
||
程序员:鲁豫<2085520973@qq.com>
|
||
代码审核:Xuekun Lu
|
||
GitHub仓库:https://github.com/learn-python/calculator-gui
|
||
版权声明:本代码遵循MIT开源协议
|
||
|
||
[功能说明]
|
||
实现带图形界面的四则运算计算器,支持以下特性:
|
||
1. 基础运算:加减乘除
|
||
2. 连续运算功能
|
||
3. 错误处理机制
|
||
4. 历史记录显示(开发中)
|
||
|
||
代码结构说明:
|
||
1.全局状态:使用三个变量跟踪计算器状态
|
||
2.样式配置:集中管理颜色、字体等视觉元素
|
||
3.事件处理:5个函数处理不同按钮的点击逻辑
|
||
4.界面构建:分模块创建各个界面组件
|
||
5.布局管理:使用grid布局实现计算器按键矩阵
|
||
|
||
学习重点:
|
||
1.StringVar的使用:实现显示内容的动态更新
|
||
2.lambda表达式:用于传递参数的按钮命令绑定
|
||
3.全局状态管理:如何处理连续运算的中间状态
|
||
4.异常处理:保证程序在错误输入时不会崩溃
|
||
5.布局技巧:grid布局的跨列(rowspan)和跨行(columnspan)使用
|
||
|
||
测试建议:
|
||
1.测试乘法:5 × 3 = 15
|
||
2.测试除法:9 ÷ 3 = 3
|
||
3.测试除零:5 ÷ 0 → 显示"除零错误"
|
||
4.连续运算测试:2 + 3 = 5 → × 4 = 20
|
||
"""
|
||
|
||
# 导入Tkinter图形界面库(Python标准库)
|
||
import tkinter as tk
|
||
|
||
# ==================== 全局状态变量 ====================
|
||
# 这些变量用于跟踪计算器的当前状态
|
||
current_input = "" # 存储用户当前输入的数字(字符串形式,如"12.34")
|
||
first_operand = None # 存储第一个运算数字(当用户点击运算符时保存)
|
||
operator = None # 存储当前选择的运算符(+/-等)
|
||
|
||
# ==================== 界面样式常量配置 ====================
|
||
# 使用字典统一管理样式,方便后期修改和维护
|
||
COLORS = {
|
||
'operator': '#b1b2b2', # 运算符按钮背景色(浅灰色)
|
||
'number': '#eacda1', # 数字按钮背景色(米黄色)
|
||
'equal': '#eacda1' # 等号按钮背景色(与数字键相同)
|
||
}
|
||
|
||
FONT = {
|
||
'main': ('宋体', 20), # 显示屏字体(字体,字号)
|
||
'button': ('宋体', 16) # 按钮字体
|
||
}
|
||
|
||
LAYOUT = {
|
||
'pad': 4, # 按钮之间的水平间距(单位:像素)
|
||
'margin': 2 # 按钮之间的垂直间距
|
||
}
|
||
|
||
|
||
# ==================== 事件处理函数 ====================
|
||
# 以下函数用于响应按钮点击事件
|
||
|
||
def on_number_click(char):
|
||
"""处理数字和小数点按钮点击事件
|
||
参数:char - 按钮对应的字符(0-9 或 .)
|
||
"""
|
||
global current_input # 声明使用全局变量
|
||
|
||
# 处理小数点输入限制
|
||
if char == '.' and '.' in current_input:
|
||
return # 如果已包含小数点,不做任何操作
|
||
|
||
current_input += char # 将新字符追加到当前输入
|
||
result_var.set(current_input) # 更新显示屏内容
|
||
|
||
|
||
def on_operator_click(op):
|
||
"""处理运算符按钮点击事件(+/-/*//)
|
||
参数:op - 运算符符号(字符串)
|
||
"""
|
||
global first_operand, operator, current_input
|
||
|
||
if current_input: # 确保有输入值时才执行
|
||
first_operand = float(current_input) # 将当前输入转为浮点数存储
|
||
operator = op # 记录运算符
|
||
current_input = "" # 清空当前输入,准备接收第二个数字
|
||
result_var.set(op) # 在显示屏显示当前运算符
|
||
|
||
|
||
def calculate():
|
||
"""执行计算并显示结果"""
|
||
global current_input, first_operand, operator
|
||
|
||
# 有效性检查:确保三个要素都存在
|
||
if first_operand is None or operator is None or not current_input:
|
||
return
|
||
|
||
try:
|
||
second_operand = float(current_input) # 将当前输入转为浮点数
|
||
|
||
# 根据运算符执行计算(新增乘除运算)
|
||
if operator == '+':
|
||
result = first_operand + second_operand
|
||
elif operator == '-':
|
||
result = first_operand - second_operand
|
||
elif operator == '*':
|
||
result = first_operand * second_operand
|
||
elif operator == '/':
|
||
if second_operand == 0:
|
||
raise ZeroDivisionError
|
||
result = first_operand / second_operand
|
||
else:
|
||
return # 无效运算符
|
||
|
||
# 格式化显示结果:整数去零,小数保留10位
|
||
if result.is_integer():
|
||
current_input = str(int(result)) # 5.0 → 5
|
||
else:
|
||
# 去除末尾多余的零和小数点
|
||
formatted = "{:.10f}".format(result).rstrip('0').rstrip('.')
|
||
current_input = formatted if formatted else "0"
|
||
|
||
result_var.set(current_input) # 更新显示
|
||
|
||
# 保留计算结果作为下一次运算的第一个操作数
|
||
first_operand = float(current_input)
|
||
operator = None # 重置运算符需手动选择
|
||
|
||
except ZeroDivisionError:
|
||
result_var.set("除零错误")
|
||
# reset_calculator()
|
||
except Exception as e:
|
||
result_var.set("错误"+e.__cause__) # 显示错误提示
|
||
# reset_calculator() # 重置计算器
|
||
|
||
|
||
def reset_calculator():
|
||
"""重置所有状态到初始值"""
|
||
global current_input, first_operand, operator
|
||
current_input = ""
|
||
first_operand = None
|
||
operator = None
|
||
result_var.set('0') # 显示屏归零
|
||
|
||
|
||
def backspace():
|
||
"""删除最后一个输入的字符"""
|
||
global current_input
|
||
if current_input:
|
||
current_input = current_input[:-1] # 移除最后一个字符
|
||
result_var.set(current_input if current_input else '0') # 显示剩余内容或0
|
||
|
||
|
||
# ==================== 主窗口初始化 ====================
|
||
# 创建主窗口并设置基本属性
|
||
root = tk.Tk() # 创建Tkinter主窗口对象
|
||
root.title('简易计算器') # 窗口标题
|
||
root.geometry('295x280+100+100') # 窗口尺寸(宽x高)和初始位置(x+y)
|
||
root.attributes("-alpha", 0.9) # 设置窗口透明度(0.0完全透明-1.0不透明)
|
||
|
||
# ==================== 结果显示区域 ====================
|
||
# 使用StringVar实现动态更新显示内容
|
||
result_var = tk.StringVar(value='0') # 创建绑定到显示区域的变量
|
||
|
||
# 创建显示标签(显示屏)
|
||
display = tk.Label(
|
||
root,
|
||
textvariable=result_var, # 绑定动态变量
|
||
font=FONT['main'], # 使用主字体
|
||
height=2, # 高度(行数)
|
||
width=20, # 宽度(字符数)
|
||
justify=tk.LEFT, # 文本左对齐
|
||
anchor=tk.SE # 文本定位到右下角(东南角)
|
||
)
|
||
# 使用grid布局管理器定位,跨4列显示
|
||
display.grid(row=1, column=1, columnspan=4)
|
||
|
||
# ==================== 操作符按钮行 ====================
|
||
# 定义第一行操作符按钮的配置(清除、退格、除、乘)
|
||
operator_buttons = [
|
||
# 格式:(显示文本, 行号, 列号, 点击命令)
|
||
('c', 2, 1, reset_calculator), # 清除按钮
|
||
('←', 2, 2, backspace), # 退格按钮
|
||
('/', 2, 3, lambda: on_operator_click('/')), # 除法按钮
|
||
('×', 2, 4, lambda: on_operator_click('*')) # 乘法按钮
|
||
]
|
||
|
||
# 批量创建操作符按钮
|
||
for text, row, col, command in operator_buttons:
|
||
tk.Button(
|
||
root,
|
||
text=text,
|
||
width=5, # 按钮宽度(字符数)
|
||
font=FONT['button'], # 使用按钮字体
|
||
relief=tk.FLAT, # 扁平化按钮样式
|
||
bg=COLORS['operator'], # 设置背景色
|
||
command=command # 绑定点击事件处理函数
|
||
).grid(
|
||
row=row,
|
||
column=col,
|
||
padx=LAYOUT['pad'],
|
||
pady=LAYOUT['margin']
|
||
)
|
||
|
||
# ==================== 数字按钮布局 ====================
|
||
# 定义数字按钮的布局配置(使用嵌套元组)
|
||
button_configs = [
|
||
# 每行按钮配置(格式:(按钮1配置, 按钮2配置, ...))
|
||
# 第三行:7、8、9、减号
|
||
(('7', 3, 1), ('8', 3, 2), ('9', 3, 3), ('-', 3, 4, lambda: on_operator_click('-'))),
|
||
# 第四行:4、5、6、加号
|
||
(('4', 4, 1), ('5', 4, 2), ('6', 4, 3), ('+', 4, 4, lambda: on_operator_click('+'))),
|
||
# 第五行:1、2、3
|
||
(('1', 5, 1), ('2', 5, 2), ('3', 5, 3)),
|
||
# 第六行:0、小数点
|
||
(('0', 6, 1), ('.', 6, 3))
|
||
]
|
||
|
||
# 遍历配置创建数字按钮
|
||
for row_group in button_configs:
|
||
for config in row_group:
|
||
# 解析按钮配置
|
||
parts = list(config)
|
||
text, row, col = parts[0:3] # 前三个元素固定
|
||
|
||
# 确定点击命令:数字/小数点调用on_number_click,运算符调用on_operator_click
|
||
command = parts[3] if len(parts) > 3 else (lambda t=text: on_number_click(t))
|
||
|
||
# 创建按钮对象
|
||
btn = tk.Button(
|
||
root,
|
||
text=text,
|
||
width=5 if text != '0' else 12, # 0按钮特殊宽度
|
||
font=FONT['button'],
|
||
relief=tk.FLAT,
|
||
bg=COLORS['number'] if text in '1234567890.' else COLORS['operator'],
|
||
command=command # 绑定点击事件
|
||
)
|
||
|
||
# 布局按钮(0按钮需要跨列)
|
||
if text == '0':
|
||
btn.grid(
|
||
row=row,
|
||
column=col,
|
||
columnspan=2, # 跨2列
|
||
padx=LAYOUT['pad'],
|
||
pady=LAYOUT['margin']
|
||
)
|
||
else:
|
||
btn.grid(
|
||
row=row,
|
||
column=col,
|
||
padx=LAYOUT['pad'],
|
||
pady=LAYOUT['margin']
|
||
)
|
||
|
||
# ==================== 等号按钮 ====================
|
||
# 创建特殊尺寸的等号按钮
|
||
tk.Button(
|
||
root,
|
||
text='=',
|
||
width=5, # 宽度(字符数)
|
||
height=3, # 高度(行数)
|
||
font=FONT['button'],
|
||
relief=tk.FLAT,
|
||
bg=COLORS['equal'], # 使用等号专用颜色
|
||
command=calculate # 绑定计算函数
|
||
).grid(
|
||
row=5,
|
||
column=4,
|
||
rowspan=2, # 跨5-6两行
|
||
padx=LAYOUT['pad'],
|
||
pady=LAYOUT['margin']
|
||
)
|
||
|
||
# ==================== 启动程序 ====================
|
||
root.mainloop() # 进入Tkinter事件循环,等待用户操作
|