学习目标:学会利用python的GUI做界面布局
- 手写计算器代码熟悉控件的使用方法
- 优化计算器代码,解决获取按钮文本的方法
- 了解lambda函数的传参优点和局限
- 打包生成自己的计算器软件,并独立运行
文章图片
目标计算器设计分为三个部分
- 背景部分
根:Tk() - 展示部分:上方展示算式,下方展示计算结果
文章图片
- 按钮部分
文章图片
\1. 加减乘除,放在
=
,按钮上\2. 回退,放在
<-
按钮上\3. 清除,放在
MC
按钮上3、 代码实现
\1. 外观布局:
首先引入包:
from tkinter import *
根据目标图片设计布局按钮:定义计算器类,初始化界面控件。
class Calculator:
def __init__(self, master):
self.master = master
self.master.title("Calculator")
self.master.resizable(0, 0)# 设置窗口不可拉伸
self.master.geometry('320x420')# 设置主窗口的初始尺寸self.result = StringVar()# 用于显示结果的可变文本
self.equation = StringVar()# 显示计算方程
self.result.set(' ')
self.equation.set('0')
# 显示框
self.show_result_eq = Label(self.master, bg='white', fg='black',
font=('Arail', '16'), bd='0',
textvariable=self.equation, anchor='se')
self.show_result = Label(self.master, bg='white', fg='black',
font=('Arail', '20'), bd='0',
textvariable=self.result, anchor='se')
# 按钮
self.button_back = Button(self.master, text='<-', bg='DarkGray', command=self.back)# 返回
self.button_lbracket = Button(self.master, text='(', bg='DarkGray', command=lambda: self.getNum('('))# 左括号
self.button_rbracket = Button(self.master, text=')', bg='DarkGray', command=lambda: self.getNum(')'))# 左括号
self.button_division = Button(self.master, text='÷', bg='DarkGray', command=lambda: self.getNum('÷'))# 除号
# 7 8 9 4 5 6 1 2 3
self.button_7 = Button(self.master, text='7', bg='DarkGray', command=lambda: self.getNum('7'))# 7号
self.button_8 = Button(self.master, text='8', bg='DarkGray', command=lambda: self.getNum('8'))# 8号
self.button_9 = Button(self.master, text='9', bg='DarkGray', command=lambda: self.getNum('9'))# 9号
self.button_multiplication = Button(self.master, text='*', bg='DarkGray',
command=lambda: self.getNum('*'))# 乘号
# 按钮的command参数,是回调函数。lambda函数是为了可以传参数给回调函数
self.button_4 = Button(self.master, text='4', bg='DarkGray', command=lambda: self.getNum('4'))# 4号
self.button_5 = Button(self.master, text='5', bg='DarkGray', command=lambda: self.getNum('5'))# 5号
self.button_6 = Button(self.master, text='6', bg='DarkGray', command=lambda: self.getNum('6'))# 6号
self.button_minus = Button(self.master, text='-', bg='DarkGray', command=lambda: self.getNum('-'))# -号self.button_1 = Button(self.master, text='1', bg='DarkGray', command=lambda: self.getNum('1'))# 1号
self.button_2 = Button(self.master, text='2', bg='DarkGray', command=lambda: self.getNum('2'))# 2号
self.button_3 = Button(self.master, text='3', bg='DarkGray', command=lambda: self.getNum('3'))# 3号
self.button_plus = Button(self.master, text='+', bg='DarkGray', command=lambda: self.getNum('+'))# +号
# 控制按钮 0 .
self.button_MC = Button(self.master, text='MC', bg='DarkGray', command=self.clear)# MC
self.button_0 = Button(self.master, text='0', bg='DarkGray', command=lambda: self.getNum('0'))# 0
self.button_dot = Button(self.master, text='.', bg='DarkGray', command=lambda: self.getNum('.'))# .
self.button_eq = Button(self.master, text='=', bg='DarkGray', command=self.run)# =# Layout布局
self.show_result_eq.place(x='10', y='10', width='300', height='50')
self.show_result.place(x='10', y='60', width='300', height='50')self.button_back.place(x='10', y='150', width='60', height='40')
self.button_lbracket.place(x='90', y='150', width='60', height='40')
self.button_rbracket.place(x='170', y='150', width='60', height='40')
self.button_division.place(x='250', y='150', width='60', height='40')
self.button_7.place(x='10', y='205', width='60', height='40')
self.button_8.place(x='90', y='205', width='60', height='40')
self.button_9.place(x='170', y='205', width='60', height='40')
self.button_multiplication.place(x='250', y='205', width='60', height='40')self.button_4.place(x='10', y='260', width='60', height='40')
self.button_5.place(x='90', y='260', width='60', height='40')
self.button_6.place(x='170', y='260', width='60', height='40')
self.button_minus.place(x='250', y='260', width='60', height='40')self.button_1.place(x='10', y='315', width='60', height='40')
self.button_2.place(x='90', y='315', width='60', height='40')
self.button_3.place(x='170', y='315', width='60', height='40')
self.button_plus.place(x='250', y='315', width='60', height='40')self.button_MC.place(x='10', y='370', width='60', height='40')
self.button_0.place(x='90', y='370', width='60', height='40')
self.button_dot.place(x='170', y='370', width='60', height='40')
self.button_eq.place(x='250', y='370', width='60', height='40')
重点说明
按钮的command参数,是回调函数。lambda函数是为了可以传参数给回调函数。
lambda匿名函数的使用:
command=lambda: self.getNum('3'))
注意这里传的参数是字符串:
'3'
。每一个按钮点击想要获取的文本值不同,所以对应的参数各不相同。从而,也导致初始化界面代码看起来太冗长了。
- 功能布局
设置回退back
,符号获取getNum
,清除clear
,计算run
方法。
def back(self):
temp_equ = self.equation.get()
self.equation.set(temp_equ[:-1])# 一个一个删def getNum(self, arg):
temp_equ = self.equation.get()# 输入算式
temp_result = self.result.get()# 判断基本语法错误
if temp_result != ' ':# 计算器输入前还没有结果,那么结果区域应该设置为空。
self.result.set(' ')
if temp_equ == '0' and (arg not in ['.', '+', '-', '*', '÷']):# 如果首次输入为0,则紧跟则不能是数字,只是小数点或运算符
temp_equ = ''
if len(temp_equ) > 2 and temp_equ[-1] == '0':# 运算符后面也不能出现0+数字的情形03,09,x
if (temp_equ[-2] in ['+', '-', '*', '÷']) and (
arg in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '(']):
temp_equ = temp_equ[:-1]
temp_equ = temp_equ + arg
self.equation.set(temp_equ)def clear(self):
self.equation.set('0')
self.result.set(' ')def run(self):
temp_equ = self.equation.get()
temp_equ = temp_equ.replace('÷', '/')
if temp_equ[0] in ['+', '-', '*', '÷']:
temp_equ = '0' + temp_equ
print(temp_equ)
try:
answer = '%.4f' % eval(temp_equ)# 保留两位小数
self.result.set(str(answer))
except (ZeroDivisionError, SyntaxError):# 其他除0错误,或语法错误返回Error
self.result.set(str('Error'))
3、 测试实验与总结
1. 测试
if __name__ == "__main__":
root = Tk()
my_cal = Calculator(root)
root.mainloop()
文章图片
2. 总结
\1. 按钮的command参数设置的是回调函数,点击按钮后的操作由函数完成。
2.将回调函数设置成匿名函数, lambda函数是可以传参数给回调函数。这个参数是在调用函数的时候才传入,不会在定义的时候保存。
\3. 关于控件的类型,控件的属性,控件的放置方式
控件类型,可以通过多dir函数查看:比如
dir(tkinter)
,找到控件控件:19
'Button','Canvas'画布,'Text'文本,'Checkbutton'复选按钮,'Radiobutton'单选按钮,'Frame'框架,'Message'消息,
'Entry'条目实体,'Label'标签,'LabelFrame'标签框架, 'Listbox'列表框,'Menu'菜单, 'Menubutton'菜单按钮,
'Scale'缩放,'Scrollbar'滚动条,'Toplevel'顶级,'Spinbox'旋转框,'PanedWindow'窗格窗口,'tkMessageBox'消息框
控件属性查看:
dir(Button)
把用得到的控件都查一遍,就清楚怎么用它们了。放置方式–集合布局:当定义一个控件,均需要让它布局到窗口上,用到三个函数:
pack
,grid
,place
pack
的参数side,可设置上下左右布局
;
grid
的参数row,column,可设置行列网格布局
;place
的参数x,y,可设置坐标布局
。同一块画布不能混合使用。
- 关于如何避免同志们乱写算式,使软件奔溃的问题。
本计算器计算功能是通过获取将输入表达式,利用eval()函数来执行python代码字符串的。
文章图片
那么就要杜绝不合理的输入表达式,不能阻止人家乱点,可以在程序中设置try...except...else...finally
语句捕捉异常,设置合理的的响应。
文章图片
1、 初始化布局页面代码冗余;
2、 lambda匿名函数包裹回调函数时传参的问题
代码实现:这里主要优化初始化函数,其他函数采用继承。
优化计算器类Calc继承Calculator的back,clear,run,getNum。增加initPage来定义页面控件布局。
# 计算器,优化程序
class Calc(Calculator):
def __init__(self, master):
self.master = master
self.master.title("Calculator")
self.master.resizable(0, 0)# 设置窗口不可拉伸
self.master.geometry('320x420')# 设置主窗口的初始尺寸self.result = StringVar()# 用于显示结果的可变文本
self.equation = StringVar()# 显示计算方程
self.result.set(' ')
self.equation.set('0')self.labels = ['<-', '(', ')', '÷',
'7', '8', '9', '*',
'4', '5', '6', '-',
'1', '2', '3', '+',
'MC', '0', '.', '=',
]
# 显示框
self.show_result_eq = Label(self.master, bg='white', fg='black',
font=('Arail', '16'), bd='0',
textvariable=self.equation, anchor='se')
self.show_result = Label(self.master, bg='white', fg='black',
font=('Arail', '20'), bd='0',
textvariable=self.result, anchor='se')
# 按钮
# self.button_dict = {}
# Layout布局
self.show_result_eq.place(x='10', y='10', width='300', height='50')
self.show_result.place(x='10', y='60', width='300', height='50')self.initPage()def initPage(self):
X = ['10', '90', '170', '250']
Y = ['150', '205', '260', '315', '370']
lengths = len(self.labels)# 20
y_ = -1
# 设置按钮并布局
for label in self.labels:
print(label)
index = self.labels.index(label)
x_ = index % 4
if x_ == 0:
y_ += 1if label == '<-':
button = Button(self.master, text=label, bg='DarkGray', command=self.back)
button.place(x=X[x_], y=Y[y_], width='60', height='40')
elif label == '=':
button = Button(self.master, text=label, bg='DarkGray', command=self.run)
button.place(x=X[x_], y=Y[y_], width='60', height='40')
elif label == 'MC':
button = Button(self.master, text=label, bg='DarkGray', command=self.clear)
button.place(x=X[x_], y=Y[y_], width='60', height='40')
else:
# 因为lambda函数有传参功能,但只有调用的时候才传参,所以数字按钮永远会调用最后一个label值。解决方案是自己洗一个button来保存label值
button = NumButton(self.master, text=label, bg='DarkGray', fun=self.getNum)
button.btn.place(x=X[x_], y=Y[y_], width='60', height='40')
# self.button_dict[label] = button
重点:以上代码,倒数第二行,我采用自定义的NumButton,而不是原有的Butoon。
因为即使匿名函数lambda函数有传参功能,但只有调用的时候才传参,所以for循环到最后,label变量的值永远为列表最后一个
=
等于符号,一度让人无解,只能自定义一个按钮类型NumButton
,来保存中间值。使按钮们都有自己的文本值,并且command回调的时候能够准确传参。class NumButton():
def __init__(self, frame, text, fun, **kwargs):
# side = kwargs.get('side') if 'side' in kwargs else ()# 此处没用上
self.btn = Button(
frame,
text=text,
activeforeground="blue",
activebackground="pink",
bg='DarkGray',
command=lambda: fun(text)
)
注意
形式参数
frame, text, fun
frame是根部件。
text按钮的标签文本,
fun函数对象,就是待回调的函数。
测试总结
if __name__ == "__main__":
root = Tk()
# my_cal = Calculator(root)
my_cal = Calc(root)
root.mainloop()
文章图片
自定义的NumButton设置了按钮激活时背景和字体的颜色变化,所以有点颜色。Button自己也可以设置的。
【python编程高阶|用Python写一个计算器】测试没有问题,就要开始打包。
1.确定安装pyinstaller包,没有可以在环境下安装:
conda install pyinstaller
或pip install pyinstaller
\2. 打包:
pyinstaller -F calc.py -w
\3. 打包生成可执行exe文件不了解请参考博文。
文章图片
推荐阅读
- Python全能工程师2022版完结含文档源码
- 目标检测|目标检测损失函数
- python|利用Python制作证件照
- Python|Python 证件照换背景色(蓝底->白底,蓝底->红底)!
- Digital|图像梯度(Image gradient)
- 计算机视觉|图像形态学操作
- python|他来了!性能吊打 Node.js 和 Deno 的新一代 javaScript 运行时!
- 目标检测|YOLOv5-6.1添加注意力机制(SE、CBAM、ECA、CA)
- 爬虫案例合集|36氪详情页AES