基于PyCharm和PyQt制作一个简易计算器

刚入门PyQt,制作了一个简易的计算器,放在这里方便大家进行学习。

因为PyCharm中安装的python版本是3.11,所以本程序利用的是PyQt 6,而非主流的PyQt 5,大家如果想要使用PyQt 5,将python相应地降低版本即可。至于PyQt的配置步骤,大家自行查阅网上的资料进行配置即可。

这个是计算器的界面,RE代表删除(return),M代表记忆最近一次的运算结果(memory),MR代表将记忆的运算结果展现出来(memory review)。

以下就是本程序的代码(包含了PyQt 6的环境配置)。

run.py

import sys
from PyQt6.QtWidgets import QApplication, QWidget, QMainWindow
from surface import Ui_MainWindow
from logic import Callogic

class MyMainWindow(QMainWindow, Ui_MainWindow):  # 继承
    def __init__(self, *args, **kwargs):
        super(MyMainWindow, self).__init__(*args, **kwargs)
        self.setupUi(self)
        self.cl: Callogic = Callogic(self.lcdNumber)
        # cl是Callogic 类的实例,负责计算器应用程序的逻辑操作

        self.do__signal__slot()

        self.cl.init_cal()

    def do__signal__slot(self):
        # 给数字0到9建立clicked信号和槽函数
        # lambda用于绑定参数,不需要单独定义多个带参数的回调函数,这使得代码更加简洁和易读。
        self.number0.clicked.connect(lambda: self.cl.number_slot(0))
        self.number1.clicked.connect(lambda: self.cl.number_slot(1))
        self.number2.clicked.connect(lambda: self.cl.number_slot(2))
        self.number3.clicked.connect(lambda: self.cl.number_slot(3))
        self.number4.clicked.connect(lambda: self.cl.number_slot(4))
        self.number5.clicked.connect(lambda: self.cl.number_slot(5))
        self.number6.clicked.connect(lambda: self.cl.number_slot(6))
        self.number7.clicked.connect(lambda: self.cl.number_slot(7))
        self.number8.clicked.connect(lambda: self.cl.number_slot(8))
        self.number9.clicked.connect(lambda: self.cl.number_slot(9))

        # 给+-*/建立clicked信号和槽函数
        self.click_addition.clicked.connect(lambda: self.cl.operation_slot("+"))
        self.click_subtraction.clicked.connect(lambda: self.cl.operation_slot("-"))
        self.click_multiplication.clicked.connect(lambda: self.cl.operation_slot("*"))
        self.click_divison.clicked.connect(lambda: self.cl.operation_slot("/"))

        # 给%建立clicked信号和槽函数
        self.percent_click.clicked.connect(self.cl.perc_slot)

        # 给RE、M、MR建立clicked信号和槽函数
        self.RE.clicked.connect(self.cl.re_slot)
        self.M.clicked.connect(self.cl.m_slot)
        self.MR.clicked.connect(self.cl.mr_slot)

        #给=建立clicked信号和槽函数
        self.click_equal.clicked.connect(self.cl.eq_slot)

        # 建立click_point按钮的clicked信号和槽函数连接
        self.click_point.clicked.connect(self.cl.point_slot)

if __name__=='__main__':
    # 创建一个QApplication对象,管理应用程序的控制流和主要设置。
    app = QApplication(sys.argv)

    window = MyMainWindow()
    window.setWindowTitle("计算器")
    window.show()
    app.exec()

logic.py

from PyQt6 import QtWidgets

class CalState:   # 定义计算器状态
    READY = 0  # 计算器准备好接收输入
    INPUT = 1  # 计算器正在接收输入

class Callogic:  # 定义计算器初始类
    def __init__(self, lcd_widget: QtWidgets.QLCDNumber):
        self.lcd_widget = lcd_widget

        self.state = None  # 当前计算器的状态
        self.stack = None  # 存储操作中的操作数

        self.last_operation = None  # 保存最近一次的操作数
        self.current_operator = None  # 保存当前操作数

        self.memory = None  # 存储器,用于M和MR功能

    def init_cal(self):  # 初始化计算机状态
        self.state = CalState.READY
        self.stack = [0]
        self.last_operation = None
        self.current_operator = None
        self.memory = None
        self._display()

    def _display(self):  # 计算器数字显示屏显示
        if self.stack[-1] == 0 and len(self.stack) == 1:
            self.lcd_widget.setText('')  # 如果堆栈中只有一个0,则显示为空
        else:
            self.lcd_widget.setText(str(self.stack[-1]))

    def number_slot(self, num_val):  # 数字0到9的槽函数
        if self.state == CalState.READY:  # 准备接收输入,将状态改成正在接收输入
            self.stack[-1] = num_val
            self.state = CalState.INPUT
        else:  # 否则将当前值追加到堆栈顶部
            current_value = str(self.stack[-1])
            if '.' in current_value:
                # 如果当前值中已有小数点,则继续按浮点数处理
                self.stack[-1] = float(current_value + str(num_val))
            else:
                # 否则,按整数处理
                self.stack[-1] = int(current_value + str(num_val))

        self._display()

    def operation_slot(self, oper):  # 运算符号+-*/的槽函数
        if self.state == CalState.INPUT and len(self.stack) > 1:
            self.eq_slot()  # 处理之前未完成的操作
        self.current_operator = oper
        self.state = CalState.INPUT
        self.stack.append(0)  # 向stack列表添加一个0,此时stack中有两个元素[0,0]

    def perc_slot(self):  # 实现%的槽函数
        self.stack[-1] = float(self.stack[-1]) * 0.01
        self._display()
        self.state = CalState.INPUT

    def eq_slot(self):  # 实现=的槽函数
        if self.current_operator and len(self.stack) > 1:
            try:
                result = self._do_operator(self.stack[0], self.stack[1])
            except ZeroDivisionError:  # 可以改进一下
                self.lcd_widget.setText("错误: 除数不能为零")
                self.stack = [0]  # 清空stack
                self.state = CalState.READY
                self.current_operator = None
                return
            except Exception:  # 清空stack
                self.lcd_widget.setText("错误")
                self.stack = [0]
                self.state = CalState.READY
                self.current_operator = None
                return

            self.stack = [result]
            self.current_operator = None
            self.state = CalState.READY
            self._display()  # self.stack = [3]

    def _do_operator(self, operand1, operand2):  # 运算逻辑
        if self.current_operator == "+":
            result = operand1 + operand2
        elif self.current_operator == "-":
            result = operand1 - operand2
        elif self.current_operator == "*":
            result = operand1 * operand2
        elif self.current_operator == "/":
            if operand2 == 0:
                raise ZeroDivisionError("除数不能为零")
            result = operand1 / operand2
            result = round(result, 15)  # 限制除法结果的小数点后15位

        if isinstance(result, float) and result.is_integer():  # 检查结果是否为整数
            result = int(result)

        return result

    def re_slot(self):  # 回退功能的实现
        if self.state == CalState.INPUT:
            current_value = str(self.stack[-1])
            if len(current_value) > 1:
                new_value = current_value[:-1]
                if new_value == "-":
                    new_value = "0"  # 防止单独一个负号
                elif new_value[-1] == ".":
                    new_value = new_value[:-1]
                if new_value:
                    if "." in new_value:
                        self.stack[-1] = float(new_value)
                    else:
                        self.stack[-1] = int(new_value)
                else:
                    self.stack[-1] = 0
            else:
                self.stack[-1] = 0
            self._display()

    def m_slot(self):  # 记忆功能的实现,可以优化一下
        self.memory = self.stack[-1]  # 将当前值存储在memory中

    def mr_slot(self): # 加载记录了的数字
        if self.memory is not None:
            self.stack[-1] = self.memory  # 读取memory中的值
            self._display()

    def point_slot(self):  # 小数点的槽函数
        current_value = str(self.stack[-1])
        if '.' not in current_value:
            self.stack[-1] = current_value + '.'
            self._display()


surface.py

# Form implementation generated from reading ui file 'surface.ui'
#
# Created by: PyQt6 UI code generator 6.4.2
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt6 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(366, 237)
        MainWindow.setMinimumSize(QtCore.QSize(366, 237))
        MainWindow.setMaximumSize(QtCore.QSize(366, 237))
        self.layoutWidget = QtWidgets.QWidget(parent=MainWindow)
        self.layoutWidget.setGeometry(QtCore.QRect(21, 11, 322, 205))
        self.layoutWidget.setObjectName("layoutWidget")
        self.gridLayout_2 = QtWidgets.QGridLayout(self.layoutWidget)
        self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.lcdNumber = QtWidgets.QLineEdit(parent=self.layoutWidget)
        self.lcdNumber.setMinimumSize(QtCore.QSize(0, 50))
        font = QtGui.QFont()
        font.setPointSize(15)
        font.setBold(True)
        self.lcdNumber.setFont(font)
        self.lcdNumber.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft)
        self.lcdNumber.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
        self.lcdNumber.setObjectName("lcdNumber")
        self.gridLayout_2.addWidget(self.lcdNumber, 0, 0, 1, 1)
        self.gridLayout = QtWidgets.QGridLayout()
        self.gridLayout.setObjectName("gridLayout")
        self.RE = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.RE.setObjectName("RE")
        self.gridLayout.addWidget(self.RE, 0, 0, 1, 1)
        self.M = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.M.setObjectName("M")
        self.gridLayout.addWidget(self.M, 0, 1, 1, 1)
        self.MR = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.MR.setObjectName("MR")
        self.gridLayout.addWidget(self.MR, 0, 2, 1, 1)
        self.click_divison = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.click_divison.setObjectName("click_divison")
        self.gridLayout.addWidget(self.click_divison, 0, 3, 1, 1)
        self.number7 = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.number7.setObjectName("number7")
        self.gridLayout.addWidget(self.number7, 1, 0, 1, 1)
        self.number8 = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.number8.setObjectName("number8")
        self.gridLayout.addWidget(self.number8, 1, 1, 1, 1)
        self.number9 = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.number9.setObjectName("number9")
        self.gridLayout.addWidget(self.number9, 1, 2, 1, 1)
        self.click_multiplication = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.click_multiplication.setObjectName("click_multiplication")
        self.gridLayout.addWidget(self.click_multiplication, 1, 3, 1, 1)
        self.number4 = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.number4.setObjectName("number4")
        self.gridLayout.addWidget(self.number4, 2, 0, 1, 1)
        self.number5 = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.number5.setObjectName("number5")
        self.gridLayout.addWidget(self.number5, 2, 1, 1, 1)
        self.number6 = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.number6.setObjectName("number6")
        self.gridLayout.addWidget(self.number6, 2, 2, 1, 1)
        self.click_subtraction = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.click_subtraction.setObjectName("click_subtraction")
        self.gridLayout.addWidget(self.click_subtraction, 2, 3, 1, 1)
        self.number1 = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.number1.setObjectName("number1")
        self.gridLayout.addWidget(self.number1, 3, 0, 1, 1)
        self.number2 = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.number2.setObjectName("number2")
        self.gridLayout.addWidget(self.number2, 3, 1, 1, 1)
        self.number3 = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.number3.setObjectName("number3")
        self.gridLayout.addWidget(self.number3, 3, 2, 1, 1)
        self.click_addition = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.click_addition.setObjectName("click_addition")
        self.gridLayout.addWidget(self.click_addition, 3, 3, 1, 1)
        self.percent_click = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.percent_click.setObjectName("percent_click")
        self.gridLayout.addWidget(self.percent_click, 4, 0, 1, 1)
        self.number0 = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.number0.setObjectName("number0")
        self.gridLayout.addWidget(self.number0, 4, 1, 1, 1)
        self.click_point = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.click_point.setObjectName("click_point")
        self.gridLayout.addWidget(self.click_point, 4, 2, 1, 1)
        self.click_equal = QtWidgets.QPushButton(parent=self.layoutWidget)
        self.click_equal.setMinimumSize(QtCore.QSize(0, 25))
        self.click_equal.setObjectName("click_equal")
        self.gridLayout.addWidget(self.click_equal, 4, 3, 1, 1)
        self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 1)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "计算器"))
        self.RE.setText(_translate("MainWindow", "RE"))
        self.M.setText(_translate("MainWindow", "M"))
        self.MR.setText(_translate("MainWindow", "MR"))
        self.click_divison.setText(_translate("MainWindow", "÷"))
        self.number7.setText(_translate("MainWindow", "7"))
        self.number8.setText(_translate("MainWindow", "8"))
        self.number9.setText(_translate("MainWindow", "9"))
        self.click_multiplication.setText(_translate("MainWindow", "×"))
        self.number4.setText(_translate("MainWindow", "4"))
        self.number5.setText(_translate("MainWindow", "5"))
        self.number6.setText(_translate("MainWindow", "6"))
        self.click_subtraction.setText(_translate("MainWindow", "-"))
        self.number1.setText(_translate("MainWindow", "1"))
        self.number2.setText(_translate("MainWindow", "2"))
        self.number3.setText(_translate("MainWindow", "3"))
        self.click_addition.setText(_translate("MainWindow", "+"))
        self.percent_click.setText(_translate("MainWindow", "%"))
        self.number0.setText(_translate("MainWindow", "0"))
        self.click_point.setText(_translate("MainWindow", "."))
        self.click_equal.setText(_translate("MainWindow", "="))
  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值