Python 全栈 60 天精通之路

Day 41:PyQt 制作 GUI 实战:通过制作小而美的计算器学会使用 PyQt

发布日期:2022年3月12日 18:02 阅读: 303 访问: 304

前段时间,我们使用 HTML + CSS + JavaScript 前端技术,后端使用 Flask 设计过一款精美的 Web 版计算器,它能实现计算优先级的自动提升。今天与大家一起实现一款桌面版计算器,使用 pyqt 设计器,设计整个 App 界面。第一步:熟悉设计器使用 Qt Designer,安装 Anaconda 后,在如下路径找到 Qt

前段时间,我们使用 HTML + CSS + JavaScript 前端技术,后端使用 Flask 设计过一款精美的 Web 版计算器,它能实现计算优先级的自动提升。

今天与大家一起实现一款桌面版计算器,使用 pyqt 设计器,设计整个 App 界面。

第一步:熟悉设计器

使用 Qt Designer,安装 Anaconda 后,在如下路径找到 Qt Designer:

它使用方便,能实现控件拖拽,布局也更加灵活,远胜于 Tkinter 控件库。如下是它的整个软件界面,左侧是常用的控件对象。

根据弹出的对话框,创建窗体,命名为 XiaoDing。

设计完成后的界面显示如下:

下面解释 XiaoDing 界面的设计过程。

QT Designer 提供的常用控件基本都能满足开发需求,通过拖动左侧的控件,很便捷的就能搭建出如下的 UI 界面,比传统的手写控件代码要方便很多。

选择左上菜单,点击新建窗体,然后选择“Main Window”:

点击创建按钮,出现下面的 GUI 界面:

看到右上窗口的对象查看器,此时根对象为 MainWindow,子对象有 centralwidget,类型为 QWidget,menubar 和 statusbar 对象。

中间是属性设置窗口,上面提到 4 个对象都显示在这里,通过界面,我们可以很方便地修改对象的属性。

修改 Window 的 title 为 XiaoDing:

第二步:添加控件

左侧控件筛选框里输入 QLCDN,自动就筛选出这个控件,然后拖动到界面中。

手动调整控件大小:

下面再选择按钮控件 QPushButton,依次推动计算器界面的所有按钮控件,布局完成后的界面如下所示:

注意布局界面时,可以使用 Grid Layout 控件,能方便我们对齐各个按钮:

右上角的对象生成树结构为:

保存 ui 文件。

第三步:转换为 py 文件

转换 ui 布局文件为 py 文件。

使用如下命令,将设计好的 ui 文件转为 py 文件:

pyuic5 -o ./calculator/MainWindow.py ./calculator/mainwindow.ui

至此,完成 GUI 的设计部分。

第四步:编写槽函数

下面开始编写槽函数,实现计算器的业务逻辑。

首先看一下我们上步生成的 py 文件里的一个重要类 Ui_MainWindow,里面就是实现窗体布局的后台代码。

下面是选取前十几行代码,看到包括布局相关对象,控件相关对象等。

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(503, 433)
        self.centralWidget = QtWidgets.QWidget(MainWindow)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.centralWidget.sizePolicy().hasHeightForWidth())
        self.centralWidget.setSizePolicy(sizePolicy)
        self.centralWidget.setObjectName("centralWidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralWidget)
        self.verticalLayout.setContentsMargins(11, 11, 11, 11)
        self.verticalLayout.setSpacing(6)
        self.verticalLayout.setObjectName("verticalLayout")
        self.lcdNumber = QtWidgets.QLCDNumber(self.centralWidget)
        self.lcdNumber.setDigitCount(10)
        self.lcdNumber.setObjectName("lcdNumber")
        self.verticalLayout.addWidget(self.lcdNumber)
        self.gridLayout = QtWidgets.QGridLayout()

下面开始编写计算器的业务逻辑代码,首先导入需要的库:

from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

import operator

from MainWindow import Ui_MainWindow

两个全局变量,表示当前计算器的输入状态值。

# 计算器的状态值
READY = 0
INPUT = 1

定义 MainWindow 类,继承自两个类:QMainWindow 和我们界面设计完成后刚才看到的类 Ui_MainWindow:

class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self.setupUi(self)

为数字按键安装槽函数:

 # 为数字按键安装槽函数
        for n in range(0, 10):
            getattr(self, 'pushButton_n%s' % n).pressed.connect(lambda v=n: self.input_number(v))

为按钮安装槽函数:

  # 为按钮安装槽函数
        self.pushButton_add.pressed.connect(lambda: self.operation(operator.add))
        self.pushButton_sub.pressed.connect(lambda: self.operation(operator.sub))
        self.pushButton_mul.pressed.connect(lambda: self.operation(operator.mul))
        self.pushButton_div.pressed.connect(lambda: self.operation(operator.truediv)) 
        self.pushButton_pc.pressed.connect(self.operation_pc)
        self.pushButton_eq.pressed.connect(self.equals)   
        self.actionReset.triggered.connect(self.reset)
        self.pushButton_ac.pressed.connect(self.reset)
        self.actionExit.triggered.connect(self.close)
        self.pushButton_m.pressed.connect(self.memory_store)
        self.pushButton_mr.pressed.connect(self.memory_recall)

初始化操作还包括,初始化计算器的所有状态变量等。

self.memory = 0
self.reset()
self.show()

重置到计算器初始状态的行为如下:

    def reset(self):
        self.state = READY
        self.stack = [0]
        self.last_operation = None
        self.current_op = None
        self.display()

display 在计算器的数字显示窗口,显示当前 stack 列表的最后一个数字:

    def display(self):
        self.lcdNumber.display(self.stack[-1])

stack 列表内初始化只有一个数字,为 0。

memory_store 方法内存中记忆着数字显示屏上的值:

def memory_store(self):
        self.memory = self.lcdNumber.value()

memory_recall 方法会将记忆值推到 stack 列表中,同时显示在数字显示屏中。

def memory_recall(self):
        self.state = INPUT
        self.stack[-1] = self.memory
        self.display()

input_number 方法实现数字显示屏界面数字的持续输入,注意第一次输入时,此时 state 值为 READY,直接将用户输入的 v 值推到 stack 列表,同时修改 state 为 INPUT 状态,当用户继续输入第 2 个数字时,此时 state 变为 INPUT,self.stack[-1] * 10 + v 实现将第 2 位数字推入到 stack 列表中,依次类推。

def input_number(self, v):
        if self.state == READY:
            self.state = INPUT
            self.stack[-1] = v
        else:
            self.stack[-1] = self.stack[-1] * 10 + v

        self.display()

operation 方法实现加减乘除操作,推入到 stack 列表中。

def operation(self, op):
        if self.current_op:
            self.equals()

        self.stack.append(0)
        self.state = INPUT
        self.current_op = op

operation_pc 实现当前 stack 列表的最后一个元除以 100 的功能。

def operation_pc(self):
        self.state = INPUT
        self.stack[-1] *= 0.01
        self.display()

equals 方法计算当前结果:

    def equals(self):
        if self.state == READY and self.last_operation:
            s, self.current_op = self.last_operation
            self.stack.append(s)

        if self.current_op:
            self.last_operation = self.stack[-1], self.current_op

            try:
                self.stack = [self.current_op(*self.stack)]
            except Exception:
                self.lcdNumber.display('Err')
                self.stack = [0]
            else:
                self.current_op = None
                self.state = READY
                self.display()

计算器的 Main 函数:

if __name__ == '__main__':
    app = QApplication([])
    app.setApplicationName("XiaoDing")

    window = MainWindow()
    app.exec_()

 

最后计算器的启动界面,如下图所示,大家动手实践一遍吧。

小结

今天通过 QT Designer 设计一个小而美的计算器,然后编写槽函数,实现计算器的业务逻辑,希望通过这个计算器实战项目,大家能入门 PyQt5 的 GUI 开发。

附项目的完整代码下载链接如下:

https://pan.baidu.com/s/1M5K3GDIL3z73vzohi4aYKA

提取码:4gde