前情回顾
上两期课程,我们为大家详细介绍了PyQt
中基础的组件信息,同时也教大家如何处理相关的一些事件循环和信号槽位机制,还未阅读的小伙伴可以点击文末链接事先预览下。
作为本系列课程的最后一期文章,我们将带大家从头构建属于你的第一款GUI
应用程序,一款简易的桌面计算器。
Calculator App
在本节中,我们将遵循模型-视图-控制器(MVC
)的设计模式开发一个计算器GUI
应用程序。这个模式有三层代码,每一层都有不同的角色:
-
模型负责应用程序的业务逻辑。它包含核心功能和数据。在计算器应用程序中,模型将处理输入值和计算。
-
视图实现了应用程序的
GUI
。它承载终端用户与应用程序交互所需的所有小组件。视图还接收用户的操作和事件。在这里,视图对应屏幕上的计算器窗口。 -
控制器连接模型和视图以使应用程序工作。用户的事件或请求被发送到控制器,控制器使模型工作。当模型以正确的格式交付所请求的结果或数据时,控制器将其转发给视图。在计算器应用程序中,控制器将从
GUI
接收目标数学表达式,要求模型执行计算,并使用结果更新GUI
。
下面,让我们简单描述下,构建GUI
计算器应用程序的详细步骤:
-
用户在视图上执行操作或请求(事件); -
视图通知控制器用户的操作; -
控制器获取用户的请求并查询模型以获得响应; -
模型处理控制器的查询,执行所需的计算,并返回结果; -
控制器接收模型的响应并相应地更新视图。 -
用户最终在视图上看到请求的结果。
Creating the Skeleton
首先,让我们在一个名为pycalc.py
的文件中为应用程序实现一个最小的框架。现在,打开你最喜欢的代码编辑器或IDE
并键入以下代码:
# pycalc.py
"""PyCalc is a simple calculator built with Python and PyQt."""
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget
WINDOW_SIZE = 235
class PyCalcWindow(QMainWindow):
"""PyCalc's main window (GUI or view)."""
def __init__(self):
super().__init__()
self.setWindowTitle("PyCalc")
self.setFixedSize(WINDOW_SIZE, WINDOW_SIZE)
centralWidget = QWidget(self)
self.setCentralWidget(centralWidget)
def main():
"""PyCalc's main function."""
pycalcApp = QApplication([])
pycalcWindow = PyCalcWindow()
pycalcWindow.show()
sys.exit(pycalcApp.exec())
if __name__ == "__main__":
main()
这个脚本实现了运行基本GUI
应用程序所需的所有代码,我们将使用这个框架来构建计算器应用程序。下面是这段代码的工作原理:
第5行导入
sys
。这个模块提供了exit()
函数,可用于终止应用程序。
第7行从
PyQt6.QtWidgets
中导入所需的类。
第9行创建一个
Python
常量,为计算器应用程序保存一个固定的窗口大小(以像素为单位)。
第11行创建
PyCalcWindow
类来提供应用程序的GUI
。注意,这个类继承自QMainWindow
。
第14行定义了类初始化式。
第15行调用超类的
.__init__()
以实现初始化目的。
第16行将窗口标题设置为
PyCalc
。
第17行使用
. setfixedsize()
给窗口一个固定的大小。这确保了用户在应用程序执行期间不能调整窗口的大小。
第18行和第19行创建一个
QWidget
对象,并将其设置为窗口的中心小部件。这个对象将是计算器应用程序中所有必需的GUI
组件的父组件。
第21行定义了计算器的主要函数。这个函数提供应用程序的入口点。
23行创建一个名为
pycalcApp
的QApplication
对象。
第24行创建了应用程序窗口
pycalcWindow
的一个实例。
第25行通过在窗口对象上调用
.show()
来显示GUI
。
第26行使用
.exec()
运行应用程序的事件循环。
最后,第29行调用
main()
来执行计算器应用程序。当运行上述脚本时,屏幕上会出现以下窗口:

Completing the App’s View
在之前的基础上,我们可以通过添加显示数学运算的显示器和表示数字和基本数学运算符的按钮键盘来完成这个GUI
。因此,我们还需要添加表示其他所需符号和操作的按钮,比如清除显示。
首先,导入一些必要的库:
# pycalc.py
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
QApplication,
QGridLayout,
QLineEdit,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)
# ...
你将使用QVBoxLayout
布局管理器来进行计算器的全局布局。要排列按钮,我们创建了一个QGridLayout
对象。QLineEdit
类将作为计算器的显示,而QPushButton
将提供所需的按钮。
作为这些准备工作后,先实例化这些对象:
# pycalc.py
# ...
class PyCalcWindow(QMainWindow):
"""PyCalc's main window (GUI or view)."""
def __init__(self):
super().__init__()
self.setWindowTitle("PyCalc")
self.setFixedSize(WINDOW_SIZE, WINDOW_SIZE)
self.generalLayout = QVBoxLayout()
centralWidget = QWidget(self)
centralWidget.setLayout(self.generalLayout)
self.setCentralWidget(centralWidget)
self._createDisplay()
self._createButtons()
# ...
好了,到这里我们添加完突出显示的代码行。你将使用.generallayout
作为应用程序的总体布局。在这个布局中,我们把显示器放在顶部,键盘按钮放在底部的网格布局中。
对._createdisplay()
和._createbuttons()
的调用此时还不能工作,因为我们还没有实现这些方法。要修复这个问题,首先编写`._createdisplay()“。
回到代码编辑器,继续更新pycalc.py脚本:
# pycalc.py
# ...
WINDOW_SIZE = 235
DISPLAY_HEIGHT = 35
class PyCalcWindow(QMainWindow):
# ...
def _createDisplay(self):
self.display = QLineEdit()
self.display.setFixedHeight(DISPLAY_HEIGHT)
self.display.setAlignment(Qt.AlignmentFlag.AlignRight)
self.display.setReadOnly(True)
self.generalLayout.addWidget(self.display)
# ...
在此代码片段中,首先定义一个新常量来保存以像素为单位的显示高度。然后在PyCalcWindow
中定义._createdisplay()
。
要创建计算器的显示,可以使用QLineEdit
小部件。然后使用DISPLAY_HEIGHT
常量为显示设置35
个像素的固定高度。显示的文本将左对齐。最后,显示将是只读的,以防止用户直接编辑。最后一行代码将显示添加到计算器的总体布局中。
接下来,实现._createbuttons()
方法,为计算器键盘创建所需的按钮。这些按钮将位于网格布局中,因此您需要一种方法在网格中表示它们的坐标。每个坐标对将由一行和一列组成。要表示坐标对,你将使用列表的列表,每个嵌套列表将表示一行。
现在继续,用以下代码更新pycalc.py文件:
# pycalc.py
# ...
WINDOW_SIZE = 235
DISPLAY_HEIGHT = 35
BUTTON_SIZE = 40
# ...
在这段代码中,我们定义了一个名为BUTTON_SIZE
的新常量,用于提供计算器按钮的大小。在这个特定的例子中,所有的按钮都是一个40
像素的正方形。
通过这个初始设置,你可以编写._createbuttons()
方法,并使用嵌套列表来保存键或按钮及其在计算器键盘上的位置。QGridLayout
允许我们设置计算器窗口上的按钮:
# pycalc.py
# ...
class PyCalcWindow(QMainWindow):
# ...
def _createButtons(self):
self.buttonMap = {}
buttonsLayout = QGridLayout()
keyBoard = [
["7", "8", "9", "/", "C"],
["4", "5", "6", "*", "("],
["1", "2", "3", "-", ")"],
["0", "00", ".", "+", "="],
]
for row, keys in enumerate(keyBoard):
for col, key in enumerate(keys):
self.buttonMap[key] = QPushButton(key)
self.buttonMap[key].setFixedSize(BUTTON_SIZE, BUTTON_SIZE)
buttonsLayout.addWidget(self.buttonMap[key], row, col)
self.generalLayout.addLayout(buttonsLayout)
# ...
首先创建空字典self.buttonMap
,用于保存计算器按钮。然后,创建一个嵌套列表来存储键标签。每一行或嵌套列表将表示网格布局中的一行,而每个键标签的索引将表示布局中相应的列。
接下来定义两个for
循环。外部循环遍历行,内部循环遍历列。在内部循环中,创建按钮并将它们添加到两个self.buttonMap
和buttonsLayout
变量中。每个按钮都有一个40x40
像素的固定大小,这可以通过.setfixedsize()
和BUTTON_SIZE
常量来设置。
最后,通过调用.generalayout
对象上的.addlayout()
方法将网格布局嵌入到计算器的通用布局中。
注意:当涉及到组件大小时,你很少会在
PyQt
文档中找到度量单位。测量单位被假定为像素,除非使用QPrinter类
。
现在,计算器的GUI
将优雅地显示显示和按钮。但是,你无法更新显示的信息。你可以通过在PyCalcWindow
中添加一些额外的方法来解决这个问题:

这些方法将提供GUI
的公共接口,并为Python
计算器应用程序完成视图类。下面让我们来完成最后的代码部分:
# pycalc.py
# ...
class PyCalcWindow(QMainWindow):
# ...
def setDisplayText(self, text):
"""Set the display's text."""
self.display.setText(text)
self.display.setFocus()
def displayText(self):
"""Get the display's text."""
return self.display.text()
def clearDisplay(self):
"""Clear the display."""
self.setDisplayText("")
# ...
以下是每种方法的具体功能:
-
.setdisplaytext()
使用.settext()
来设置和更新显示的文本。它还使用.setfocus()
将光标的焦点设置在显示器上。 -
.displaytext()
是一个返回显示当前文本的getter
方法。当用户点击计算器键盘上的等号(=)时,应用程序将使用.displaytext()
的返回值作为要求值的数学表达式。 -
.cleardisplay()
将显示的文本设置为空字符串(“”),以便用户可以引入一个新的数学表达式。每当用户按下计算器面板上的C按钮时,就会触发此方法。
现在,我们的计算器终于有个雏形了!当你运行这个应用程序时,你会看到一个如下所示的窗口:

到这里,大家已经完成了计算器的GUI
,它看起来非常流畅!然而,如果此时你尝试进行一些计算,那么计算器将不会像预期的那样响应。这是因为我们还没有实现模型和控制器组件。在下一节中,我们将编写计算器的模型。
Implementing the Calculator’s Model
在MVC
模式中,模型是负责业务逻辑的代码层。在计算器应用程序中,业务逻辑都是关于基本的数学计算。因此,我们的模型负责计算用户在计算器的GUI
中输入的数学表达式。此外,计算器还需要能够处理异常错误。为此,我们首先定义以下全局常数:
# pycalc.py
# ...
ERROR_MSG = "ERROR"
WINDOW_SIZE = 235
# ...
如果用户引入了无效的数学表达式,则ERROR_MSG
常量是用户将在计算器显示器上看到的消息。有了以上的更改,你就可以编写应用程序的模型了,在这个例子中,它将是一个单独的函数:
# pycalc.py
# ...
class PyCalcWindow(QMainWindow):
# ...
def evaluateExpression(expression):
"""Evaluate an expression (Model)."""
try:
result = str(eval(expression, {}, {}))
except Exception:
result = ERROR_MSG
return result
# ...
在evaluateExpression()
中,使用eval()
对作为字符串的数学表达式求值。如果计算成功,则返回结果。否则,将返回预定义的错误消息。注意,这个函数并不完美。它有几个重要的问题:
-
try…except
块不会捕获特定的异常,因此它使用的是Python
中不鼓励使用的实践。 -
该函数使用 eval()
,这可能会导致一些严重的安全问题。
你可以自由地修改该函数,使其更加可靠和安全。在本教程中,你将按原样使用该函数,将重点放在实现GUI
上。
Creating the Controller Class for Your Calculator
下面让我们继续编写计算器的控制器类。这个类将视图连接到刚刚编写的模型,我们将使用控制器类使计算器执行响应用户事件的操作,其主要包含以下几个功能:
-
访问 GUI
的公共接口。 -
处理数学表达式的创建。 -
连接所有按钮的点击信号与适当的插槽。
下面编写一个新的PyCalc
类,继续沿用前面的代码:
# pytcalc.py
import sys
from functools import partial
# ...
def evaluateExpression(expression):
# ...
class PyCalc:
"""PyCalc's controller class."""
def __init__(self, model, view):
self._evaluate = model
self._view = view
self._connectSignalsAndSlots()
def _calculateResult(self):
result = self._evaluate(expression=self._view.displayText())
self._view.setDisplayText(result)
def _buildExpression(self, subExpression):
if self._view.displayText() == ERROR_MSG:
self._view.clearDisplay()
expression = self._view.displayText() + subExpression
self._view.setDisplayText(expression)
def _connectSignalsAndSlots(self):
for keySymbol, button in self._view.buttonMap.items():
if keySymbol not in {"=", "C"}:
button.clicked.connect(
partial(self._buildExpression, keySymbol)
)
self._view.buttonMap["="].clicked.connect(self._calculateResult)
self._view.display.returnPressed.connect(self._calculateResult)
self._view.buttonMap["C"].clicked.connect(self._view.clearDisplay)
# ...
在pycalc.py
的开头,我们从functools
导入partial()
函数,此函数将信号与需要接受额外参数的方法连接起来。
在PyCalc
函数体里面,定义类初始化式,它接受两个参数,即应用程序的模型及其视图。然后将这些参数存储在适当的实例属性中。最后,调用._connectsignalsandslots()
来建立所有必需的信号和插槽连接。
在._calculateresult()
中,我们使用._evaluate()
对用户刚刚输入计算器显示的数学表达式求值。然后在计算器视图上调用.setdisplaytext()
,用计算结果更新显示文本。顾名思义,._buildexpression()
负责构建目标数学表达式。为此,该方法将初始显示值与用户在计算器键盘上输入的每个新值连接起来。
最后,._connectsignalsandslots()
法将所有按钮的clicked
号与控制器类中的适当slots
方法连接起来。好了,现在控制器类已经准备好了,让我们在主函数调用它:
# pytcalc.py
# ...
def main():
"""PyCalc's main function."""
pycalcApp = QApplication([])
pycalcWindow = PyCalcWindow()
pycalcWindow.show()
PyCalc(model=evaluateExpression, view=pycalcWindow)
sys.exit(pycalcApp.exec())
这段代码创建了PyCalc
的一个新实例。PyCalc
类构造函数的模型参数保存了对evaluateExpression()
函数的引用,而视图参数保存了对pycalcWindow
对象的引用,该对象提供了应用程序的GUI
。现在PyQt
计算器应用程序可以运行了。
Running the Calculator
现在我们已经完成了用Python
和PyQt
编写计算器应用程序,是时候进行现场测试了!如果你从你的命令行运行应用程序,那么你会得到这样的结果:

大家可以尝试输入一段数学表达式,不出意外的话应该能得到期望的结果。最后,让我们为这个应用增添一些额外的功能。
Additional Tools
PyQt6
提供了一组有用的附加工具,可以帮助你构建可靠的、现代的、功能齐全的GUI
应用程序。与PyQt
相关的一些最显著的工具包括Qt Designer
和internationalization kit
。
Qt Designer
允许用户使用拖放的功能进行界面设计和构建图形用户界面。你可以使用这个工具通过使用屏幕上的表单和拖放机制来设计小部件、对话框和主窗口。下面的动画展示了Qt Designer
的一些功能:
Qt Designer
使用XML .ui
文件来存储GUI
设计。PyQt
包含一个名为uic
的模块来帮助处理.ui
文件。你还可以使用名为pyuic6
的命令行工具将.ui
文件内容转换为Python
代码。
注意:要深入了解
Qt Designer
并更好地理解如何使用该工具创建图形用户界面,请查看<Qt Designer and Python: Build Your GUI Applications Faster
>章节。
此外,PyQt6
还提供了一套全面的工具,用于将应用程序国际化为本地语言。pylupdate6
命令行工具创建和更新翻译(.ts)
文件,该文件可以包含接口字符串的翻译。如果你更喜欢GUI
工具,那么可以使用Qt Linguist
创建和更新.ts
文件,其中包含接口字符串的翻译。
Conclusion
在本教程中,我们学习了如何使用PyQt
,它是基于Python
开发GUI
应用程序最流行和最可靠的库之一。在本系列教程中,我们学习了以下知识点:
-
使用Python和PyQt构建图形用户界面 -
将用户的事件与应用程序的逻辑连接起来 -
使用适当的项目布局组织PyQt应用程序 -
使用PyQt创建一个真实的GUI应用程序
现在,大家可以尝试使用Python
和PyQt
知识为自己的桌面GUI
应用程序注入活力,学有余力的同学也可以尝试开发其他项目。下面将为大家列举一些常用的学习资料。
[1] PyQt6’s documentation
[2] PyQt5’s documentation
[3] Qt v6’s documentation
[4] The Rapid GUI Programming with Python and Qt book
[5] The Qt Designer manual
[6] Qt for Python’s documentation
【Python项目实战】网页爬取(上)
【Python项目实战】网页爬取(中)
【Python项目实战】网页爬取(下)
【Python项目实战】主流游戏引擎(上)
【Python项目实战】主流游戏引擎(中)
【Python项目实战】主流游戏引擎(下)
原文始发于微信公众号(Pytrick):【Python项目实战】构建GUI桌面计算器(下)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/56343.html