‌Python中使用Tkinter构建计算器(第1节)


计算器是我们日常生活中都会用到的一个应用程序。本节教程中,我们利用Python自带的图形用户界面库Tkinter,构建一个Python计算器程序。

Tkinter作为Python的标准GUI(图形用户界面)库,是Python中设计图形界面最常用的一个标准库,简单易用,不需要额外安装。此外,Tkinter是跨平台的,因此相同的代码可以在macOS,Windows和Linux上运行。

计算器程序效果如下图所示:

以下是计算器程序完整的示例代码:

动手练一练:

import tkinter as tk

# 定义计算器Calculator类
class Calculator:
    # 初始化界面控件
    def __init__(self, master):
        self.master = master
        self.master.title("简易计算器")
        # 设置窗口不可通过鼠标拉伸
        self.master.resizable(0, 0)
        # 设置主窗口的初始大小
        self.master.geometry('385x400')
        # 用于显示计算结果的可变文本
        self.result = tk.StringVar()
        # 用于显示计算方程
        self.equation = tk.StringVar()
        # 设置默认值
        self.result.set(' ')
        self.equation.set('0')
        # 创建一个包含两个Label的Frame,当作计算器的显示框
        frame = tk.Frame(self.master, borderwidth=2, relief="groove")
        frame.grid(row=0, column=0, columnspan=5, padx=10, pady=10)
        self.lb = tk.Label(frame, textvariable=self.equation, bg="white", font=("黑体", 28), anchor=tk.SE, width=19, height=1)
        self.lb.grid(row=0, column=0, columnspan=5)
        self.lb1 = tk.Label(frame, textvariable=self.result, bg="white", font=("黑体", 28), anchor=tk.SE, width=19, height=1)
        self.lb1.grid(row=1, column=0, columnspan=5)
        # 创建计算器按钮
        self.bu_clear = tk.Button(self.master, text="C", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.clear())
        self.bu_clear.grid(padx=5,pady=5, row=2,column=0)
        self.bu_back = tk.Button(self.master, text="←", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.back())
        self.bu_back.grid(padx=5, pady=5,row=2,column=1)
        self.bu_chu = tk.Button(self.master, text="/", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("/"))
        self.bu_chu.grid(padx=5, pady=5, row=2, column=2)
        self.bu_cal = tk.Button(self.master, text="*", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("*"))
        self.bu_cal.grid(padx=5, pady=5, row=2, column=3)
        self.bu_7 = tk.Button(self.master, text="7", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("7"))
        self.bu_7.grid(padx=5,pady=5,row=3,column=0)
        self.bu_8 = tk.Button(self.master, text="8", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("8"))
        self.bu_8.grid(padx=5,pady=5,row=3,column=1)
        self.bu_9 = tk.Button(self.master, text="9", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("9"))
        self.bu_9.grid(padx=5,pady=5,row=3,column=2)
        self.bu_sub = tk.Button(self.master, text="-", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("-"))
        self.bu_sub.grid(padx=5, pady=5, row=3, column=3)
        self.bu_4 = tk.Button(self.master, text="4", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("4"))
        self.bu_4.grid(padx=5,pady=5,row=4,column=0)
        self.bu_5 = tk.Button(self.master, text="5", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("5"))
        self.bu_5.grid(padx=5,pady=5,row=4,column=1)
        self.bu_6 = tk.Button(self.master, text="6", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("6"))
        self.bu_6.grid(padx=5,pady=5,row=4,column=2)
        self.bu_add = tk.Button(self.master, text="+", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("+"))
        self.bu_add.grid(padx=5, pady=5, row=4, column=3)
        self.bu_1 = tk.Button(self.master, text="1", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("1"))
        self.bu_1.grid(padx=5,pady=5,row=5,column=0)
        self.bu_2 = tk.Button(self.master, text="2", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("2"))
        self.bu_2.grid(padx=5,pady=5,row=5,column=1)
        self.bu_3 = tk.Button(self.master, text="3", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("3"))
        self.bu_3.grid(padx=5,pady=5,row=5,column=2)
        self.bu_equal = tk.Button(self.master, text="=", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.equal())
        self.bu_equal.grid(padx=5,pady=5, row=5,column=3)
        self.bu_0 = tk.Button(self.master, text="0", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("0"))
        self.bu_0.grid(padx=5,pady=5,row=6,column=0)
        self.bu_sum = tk.Button(self.master, text=".", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, justify=tk.LEFT,command=lambda: self.calculation("."))
        self.bu_sum.grid(padx=5, pady=5, row=6,column=1)
        self.bu_l_bracket = tk.Button(self.master, text="(", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, justify=tk.LEFT,command=lambda: self.calculation("("))
        self.bu_l_bracket.grid(padx=5, pady=5, row=6,column=2)
        self.bu_r_bracket = tk.Button(self.master, text=")", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, justify=tk.LEFT,command=lambda: self.calculation(")"))
        self.bu_r_bracket.grid(padx=5, pady=5, row=6,column=3)

    # 返回函数
    def back(self):
        temp_equ = self.equation.get()
        # 每一次只删除一个
        self.equation.set(temp_equ[:-1])

    # 清零函数
    def clear(self):
        self.equation.set('0')
        self.result.set(' ')

    # 计算器符号获取
    def calculation(self, arg):
        temp_equ = self.equation.get()
        temp_result = self.result.get()

        # 计算器输入前还没有结果,则设置计算结果为空
        if temp_result != ' ':
            self.result.set(' ')
        # 如果输入第一个数字为0,则紧跟后面不能是数字,只能是小数点或运算符
        if temp_equ == '0' and (arg not in ['.', '+', '-', '*', '÷']):
            temp_equ = ''
        # 运算符后面也不能出现0和数字的组合,比如02和08
        if len(temp_equ) > 2 and temp_equ[-1] == '0':
            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 equal(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 = '%.2f' % eval(temp_equ)
            self.result.set(str(answer))
        # 捕获异常,包括除零错误或语法错误,返回“输入错误”
        except (ZeroDivisionError, SyntaxError):
            self.result.set(str('输入错误'))

# 当前模块直接被执行
if __name__ == "__main__":
    app = tk.Tk()
    my_cal = Calculator(app)
    # 开启主循环,让窗口处于显示状态
    app.mainloop()

计算器程序代码解析

1、界面布局

首先,通过“import tkinter as tk”导入tkinter模块,再使用class关键字定义了一个计算器Calculator类。在Calculator类中,通过构造方法“_init_()”初始化界面控件。代码如下:

import tkinter as tk

# 定义计算器Calculator类
class Calculator:
    # 初始化界面控件
    def __init__(self, master):
        self.master = master
        self.master.title("简易计算器")
        # 设置窗口不可通过鼠标拉伸
        self.master.resizable(0, 0)
        # 设置主窗口的初始大小
        self.master.geometry('385x400')
        # 用于显示计算结果的可变文本
        self.result = tk.StringVar()
        # 用于显示计算方程
        self.equation = tk.StringVar()
        # 设置默认值
        self.result.set(' ')
        self.equation.set('0')
        # 创建一个包含两个Label的Frame,当作计算器的显示框
        frame = tk.Frame(self.master, borderwidth=2, relief="groove")
        frame.grid(row=0, column=0, columnspan=5, padx=10, pady=10)
        self.lb = tk.Label(frame, textvariable=self.equation, bg="white", font=("黑体", 28), anchor=tk.SE, width=19, height=1)
        self.lb.grid(row=0, column=0, columnspan=5)
        self.lb1 = tk.Label(frame, textvariable=self.result, bg="white", font=("黑体", 28), anchor=tk.SE, width=19, height=1)
        self.lb1.grid(row=1, column=0, columnspan=5)
        # 创建计算器按钮
        self.bu_clear = tk.Button(self.master, text="C", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.clear())
        self.bu_clear.grid(padx=5,pady=5, row=2,column=0)
        self.bu_back = tk.Button(self.master, text="←", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.back())
        self.bu_back.grid(padx=5, pady=5,row=2,column=1)
        self.bu_chu = tk.Button(self.master, text="/", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("/"))
        self.bu_chu.grid(padx=5, pady=5, row=2, column=2)
        self.bu_cal = tk.Button(self.master, text="*", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("*"))
        self.bu_cal.grid(padx=5, pady=5, row=2, column=3)
        self.bu_7 = tk.Button(self.master, text="7", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("7"))
        self.bu_7.grid(padx=5,pady=5,row=3,column=0)
        self.bu_8 = tk.Button(self.master, text="8", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("8"))
        self.bu_8.grid(padx=5,pady=5,row=3,column=1)
        self.bu_9 = tk.Button(self.master, text="9", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("9"))
        self.bu_9.grid(padx=5,pady=5,row=3,column=2)
        self.bu_sub = tk.Button(self.master, text="-", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("-"))
        self.bu_sub.grid(padx=5, pady=5, row=3, column=3)
        self.bu_4 = tk.Button(self.master, text="4", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("4"))
        self.bu_4.grid(padx=5,pady=5,row=4,column=0)
        self.bu_5 = tk.Button(self.master, text="5", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("5"))
        self.bu_5.grid(padx=5,pady=5,row=4,column=1)
        self.bu_6 = tk.Button(self.master, text="6", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("6"))
        self.bu_6.grid(padx=5,pady=5,row=4,column=2)
        self.bu_add = tk.Button(self.master, text="+", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("+"))
        self.bu_add.grid(padx=5, pady=5, row=4, column=3)
        self.bu_1 = tk.Button(self.master, text="1", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("1"))
        self.bu_1.grid(padx=5,pady=5,row=5,column=0)
        self.bu_2 = tk.Button(self.master, text="2", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("2"))
        self.bu_2.grid(padx=5,pady=5,row=5,column=1)
        self.bu_3 = tk.Button(self.master, text="3", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("3"))
        self.bu_3.grid(padx=5,pady=5,row=5,column=2)
        self.bu_equal = tk.Button(self.master, text="=", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.equal())
        self.bu_equal.grid(padx=5,pady=5, row=5,column=3)
        self.bu_0 = tk.Button(self.master, text="0", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, command=lambda: self.calculation("0"))
        self.bu_0.grid(padx=5,pady=5,row=6,column=0)
        self.bu_sum = tk.Button(self.master, text=".", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, justify=tk.LEFT,command=lambda: self.calculation("."))
        self.bu_sum.grid(padx=5, pady=5, row=6,column=1)
        self.bu_l_bracket = tk.Button(self.master, text="(", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, justify=tk.LEFT,command=lambda: self.calculation("("))
        self.bu_l_bracket.grid(padx=5, pady=5, row=6,column=2)
        self.bu_r_bracket = tk.Button(self.master, text=")", bg="#b4b2b2", font=("黑体", 20), width=5, height=1, justify=tk.LEFT,command=lambda: self.calculation(")"))
        self.bu_r_bracket.grid(padx=5, pady=5, row=6,column=3)

2、点击按钮时触发回调函数

通过Button控件中的command参数来执行按钮关联的回调函数。当按钮被点击时,会执行lambda函数。lambda匿名函数可以传参数给回调函数,例如“command=lambda: self.calculation("8")”,这里传的参数是字符串:“8”。计算器上的每一个按钮包含的文本值不同,所以对应的参数各不相同。

3、Calculator类中定义的函数

back()函数对应计算器上面的“回退”按钮,通过back()函数获取计算器符号,clear()函数对应计算器上面的“清除”按钮,最后通过equal()函数执行计算。

在equal()函数中,利用eval()函数来执行Python代码字符串表达式。这个过程必须限制不合理的输入表达式,那么如何判断用户输入的表达式是否合理?我们可以在程序执行前设置“try...except”语句来捕获并处理用户的异常输入,如果“except”有捕获到异常则输出字符串“输入错误”。代码如下:

    # 返回函数
    def back(self):
        temp_equ = self.equation.get()
        # 每一次只删除一个
        self.equation.set(temp_equ[:-1])

    # 清零函数
    def clear(self):
        self.equation.set('0')
        self.result.set(' ')

    # 计算器符号获取
    def calculation(self, arg):
        temp_equ = self.equation.get()
        temp_result = self.result.get()

        # 计算器输入前还没有结果,则设置计算结果为空
        if temp_result != ' ':
            self.result.set(' ')
        # 如果输入第一个数字为0,则紧跟后面不能是数字,只能是小数点或运算符
        if temp_equ == '0' and (arg not in ['.', '+', '-', '*', '÷']):
            temp_equ = ''
        # 运算符后面也不能出现0和数字的组合,比如02和08
        if len(temp_equ) > 2 and temp_equ[-1] == '0':
            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 equal(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 = '%.2f' % eval(temp_equ)
            self.result.set(str(answer))
        # 捕获异常,包括除零错误或语法错误,返回“输入错误”
        except (ZeroDivisionError, SyntaxError):
            self.result.set(str('输入错误'))