前言
本系列上一篇文章(Qt for Python学习笔记—应用程序初探 )中对在 Window 平台上不借助 Qt Designer 工具用纯代码方式开发一个简单的 PySide6 入门 GUI 应用程序进行了介绍(含代码解析)。
本文接着详细介绍如何在 Window 平台上不借助 Qt Designer 工具用纯代码方式来开发一个稍微复杂的 PySide6 GUI 应用程序(含代码解析),让读者有更进一步的体会,供各位 Qt for Python 的初学者们参考。
注:本系列将会以 PySide6 为例进行介绍,原则上同样适用于 PyQt6(只需将代码中导入语句的 PySide6 替换为 PyQt6 即可)。
1. 简介
在上一篇文章中介绍的 PySide6 示例程序,窗口非常简单(单窗体,且只包含一个组件 Widget),就直接在 Python 文件中按面向过程的思路进行编写代码的。但如果需要开发一个复杂些的 GUI 应用程序(如包含众多组件的窗口,或者包含多个窗口,功能也多),则按照面向过程编程就会比较麻烦,编写出的代码也不便于优化和维护。因此,通常会采用面向对象编程来开发 PySide6 GUI应用程序(以类的形式来组织程序代码结构)。
本示例程序将涉及到如下内容:
- 如何定义一个自定义主窗口类
- 如何在类中定义构造函数、设置窗口标题
- 如何创建行编辑器(QLineEdit)对象、按钮(QPushButton)对象,并设置相关属性
- 如何创建网格布局(QGridLayout),并将各组件添加到该网络布局
- 如何创建一个部件(QWidget)对象,将网格布局应用于该部件,并将该部件设置为主窗体的中心部件
- 如何对组件建立起信号与槽之间的连接
- 如何自定义一个槽函数
- 如何创建一个应用程序对象
- 如何创建和显示一个自定义主窗口
- 如何运行和退出应用程序
2. 示例目标及原型
我们确定该示例程序所设想达到的目标,并给出其原型。
一、示例目标:
本示例目标是创建一个 Python GUI 应用程序,其主窗口标题为“PySide6 示例程序”,其右上角有最小化、最大化和关闭按钮,窗口可拉伸。其主窗体中分为上下两个区域:
(一)上面是放置一个行编辑器(QLineEdit):
(1) 缺省显示文本内容为 “用 PySide6 开发的第二个 GUI 应用程序!”
(2) 文本居中对齐,字体设置为蓝色,粗体,13px;
(二)下面放置一排4个按钮(QPushButton),依次为:
(1) 【改色】按钮:点击按钮后,文本字体显示为红色
(2) 【恢复】按钮:点击按钮后,文本字体显示为蓝色
(3) 【清除】按钮:点击按钮后,文本内容清空
(4) 【关闭】按钮:点击按钮后,关闭窗口退出程序
二、示例原型:
在进行 GUI 应用程序编码之前,一般建议先勾画出 GUI 框架(窗体及各部件的布局等)。本示例原型如下:
3. 示例代码及运行
一、编辑代码
利用代码编辑器编辑示例代码,并保存为文件(如:C:\MyPySide6\MyPySide6App2.py)。
from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QGridLayout, QLineEdit, QPushButton)
from PySide6.QtCore import (Qt, Slot)
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.setWindowTitle("PySide6 示例程序")
self.lineEdit = QLineEdit("用 PySide6 开发的第二个 GUI 应用程序!", alignment=Qt.AlignCenter)
self.lineEdit.setStyleSheet("color: blue; font: bold; font-size: 13px;")
self.btnChange = QPushButton("改色")
self.btnReset = QPushButton("恢复")
self.btnClear = QPushButton("清除")
self.btnQuit = QPushButton("关闭")
self.gridLayout = QGridLayout()
self.gridLayout.addWidget(self.lineEdit, 0, 0, 1, 4)
self.gridLayout.addWidget(self.btnChange, 1, 0, 1, 1)
self.gridLayout.addWidget(self.btnReset, 1, 1, 1, 1)
self.gridLayout.addWidget(self.btnClear, 1, 2, 1, 1)
self.gridLayout.addWidget(self.btnQuit, 1, 3, 1, 1)
self.centralwidget = QWidget()
self.centralwidget.setLayout(self.gridLayout)
self.setCentralWidget(self.centralwidget)
self.btnChange.clicked.connect(self.btnChange_Clicked)
self.btnReset.clicked.connect(self.btnReset_Clicked)
self.btnClear.clicked.connect(self.lineEdit.clear)
self.btnQuit.clicked.connect(self.close)
@Slot()
def btnChange_Clicked(self):
self.lineEdit.setStyleSheet("color: red; font: bold; font-size: 13px;")
@Slot()
def btnReset_Clicked(self):
self.lineEdit.setText("用 PySide6 开发的第二个 GUI 应用程序!")
self.lineEdit.setStyleSheet("color: blue; font: bold; font-size: 13px;")
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
win = MyWindow()
win.show()
sys.exit(app.exec_())
从控制台终端通过命令方式(或直接在 Visual Studio Code 上点击主窗体上运行按钮)来运行该示例程序(通常会先进入代码所在目录下):
C:\MyPySide6> python MyPySide6App2.py
顺利的话应该会出现以下程序窗口(可以试着分别点击四个按钮看看效果):
4. 代码解析
本示例代码共分三部分:
4.1 导入模块(或类)部分
首先,导入相关模块(或类):
from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QGridLayout, QLineEdit, QPushButton)
from PySide6.QtCore import (Qt, Slot)
PySide6 模块中的子模块提供对相关 Qt API 的访问。
——第1行代码:由于本示例后续代码中会用到 QApplication 类、QMainWindow 类、QWidget类、QGridLayout 类、QLineEdit 类、QPushButton 类,所以需要从 PySide6.QtWidgets 模块导入上述类;
——第2行代码:由于用到 Qt 类和 Slot 类,所以需要从 PySide6.QtCore 模块中导入前面2个类。
注:导入语句中关键词 import 后可以跟多个类(用逗号隔开)时,可以使用括号,也可以不使用括号。
4.2 自定义MyWindow类部分
其次,自定义 MyWindow 类(即主窗体,继承自 QMainWindow 类):
(一)定义类的声明
class MyMainWindow(QMainWindow):
...
——第1行代码:是类定义的声明语句。Python 中类的声明使用关键词 class,之后是一个空格,然后是类的名字(MyMainWindow),紧跟着的括号及内容表示该类继承自 QMainWindow 类,最后以一个半角冒号结尾。
——后面代码:类体。
(二)定义类的构造函数
定义类的构造函数(构造时会进行初始化工作)。
(1)首先继承父类构造函数的全部属性,并设置主窗口标题
def __init__(self):
super(MyMainWindow, self).__init__()
self.setWindowTitle("PySide6 示例程序")
......
——第1-4行代码:定义自定义 MyMainWindow 类的构造函数
——第2行代码:通过 super() 方法继承父类(QMainWindow )构造函数中的全部属性
注:语法为:super( 自己的类, self ).__init__()
——第3行代码:通过 setWindowTitle() 方法设置主窗体的标题为"PySide6 示例程序"
(2)创建窗体界面组件对象,并设置相应属性
self.lineEdit = QLineEdit("用 PySide6 开发的第二个 GUI 应用程序!", alignment=Qt.AlignCenter)
self.lineEdit.setStyleSheet("color: blue; font: bold; font-size: 13px;")
self.btnChange = QPushButton("改色")
self.btnReset = QPushButton("恢复")
self.btnClear = QPushButton("清除")
self.btnQuit = QPushButton("关闭")
——第1行代码:在构造函数中创建1个行编辑器对象(对象名为 lineEdit),并设置其显示文本内容("用 PySide6 开发的第二个 GUI 应用程序!")、对齐方式(居中)。
——第2行代码:行编辑器对象调用其 setStyleSheet() 方法,通过Qt样式表(Qt Style Sheets)来设置自己的外观(文本颜色为蓝色,粗体、字号为13px)。
——第3行代码:在构造函数中创建1个按钮对象(对象名为 btnChange),并设置其文本内容("改色")
——第4行代码:在构造函数中创建1个按钮对象(对象名为 btnReset),并设置其文本内容("恢复")
——第5行代码:在构造函数中创建1个按钮对象(对象名为 btnClear),并设置其文本内容("清除")
——第6行代码:在构造函数中创建1个按钮对象(对象名为 btnQuit),并设置其文本内容("关闭")
注:可以通过Qt样式表(Qt Style Sheets)来设置组件的多种外观,包括颜色、背景色、字体、边框宽度、边框颜色、边框样式等。会在后续的文章对 “Qt 样式表” 专门加以详细介绍的。
(3)创建一个网格布局,并将窗体界面组件对象逐一添加到网格布局
self.gridLayout = QGridLayout()
self.gridLayout.addWidget(self.lineEdit, 0, 0, 1, 4)
self.gridLayout.addWidget(self.btnChange, 1, 0, 1, 1)
self.gridLayout.addWidget(self.btnReset, 1, 1, 1, 1)
self.gridLayout.addWidget(self.btnClear, 1, 2, 1, 1)
self.gridLayout.addWidget(self.btnQuit, 1, 3, 1, 1)
——第1行代码:在构造函数中使用 QGridLayout 类创建一个网格布局(对象名为 gridLayout)
——第2行代码:调用 addWidget() 方法将行编辑器对象(lineEdit)添加到该网格布局(gridLayout)中
——第3行代码:调用 addWidget() 方法将按钮对象(btnChange)添加到该网格布局(gridLayout)中
——第4行代码:调用 addWidget() 方法将按钮对象(btnReset)添加到该网格布局(gridLayout)中
——第5行代码:调用 addWidget() 方法将按钮对象(btnClear)添加到该网格布局(gridLayout)中
——第6行代码:调用 addWidget() 方法将按钮对象(btnQuit)添加到该网格布局(gridLayout)中
注:可能读者会对上述代码不太理解,不要着急,这里先有个初步印象,知道这些代码能起什么作用就行了,在后续的文章我会对 “布局管理” 专门加以详细介绍的。
(4)创建一个部件对象,将布局应用于该部件,并将该部件设置为主窗体的中心部件
self.centralwidget = QWidget()
self.centralwidget.setLayout(self.gridLayout)
self.setCentralWidget(self.centralwidget)
——第1行代码:在类定义的构造函数中通过实例化 QWidget 类创建一个部件对象(对象名为 centralwidget);
——第2行代码:将之前创建的网格布局(gridLayout)应用于该部件对象(centralwidget);
——第3行代码:调用 setCentralWidget() 方法将该部件对象(centralwidget)设置为主窗体的窗口中心部件(添加到主窗口中)。
注:可能读者会对上述代码不太理解,不要着急,这里先有个初步印象,知道这些代码能起什么作用就行了,有关“中心部件(Central Widget)” 我会在后续的文章“主窗口”中专门加以详细介绍。
(5)为按钮建立信号与槽的连接
self.btnChange.clicked.connect(self.btnChange_Clicked)
self.btnReset.clicked.connect(self.btnReset_Clicked)
self.btnClear.clicked.connect(self.lineEdit.clear)
self.btnQuit.clicked.connect(self.close)
在构造函数中分别为3个按钮建立起clicked信号与各自对应槽函数的连接。
——第1行代码:建立起【更改】按钮的 clicked 信号与自定义槽函数 btnChange_Clicked() 之间的连接。该槽函数需要在该类中自行定义。
——第2行代码:建立起【恢复】按钮的 clicked 信号与自定义槽函数 btnReset_Clicked() 之间的连接。
——第3行代码:建立起【清除】按钮的 clicked 信号与行编辑器对象内置的槽函数 clear() 之间的连接。
——第4行代码:建立起【关闭】按钮的 clicked 信号与主窗体对象内置的槽函数 close() 之间的连接。
注:可能读者会对“信号与槽”感觉有点懵,不要着急,“信号与槽” 是 Qt 的一个重要的机制,这里先有个初步印象,知道有这么个东西就行了,在后续的文章我会对 “信号与槽“ 专门加以介绍的。
(三)、定义类的槽函数,为按钮建立信号与槽的连接
(1)定义槽函数 btnChange_Clicked()
@Slot()
def btnChange_Clicked(self):
self.lineEdit.setStyleSheet("color: red; font: bold; font-size: 13px;")
——第1行代码:@Slot() 是一个装饰器,表示将下面的函数标识为槽函数。
——第2-3行代码:自定义【更改】按钮点击信号连接的槽函数 btnChange_Clicked()。
——第3行代码:行编辑器调用其 setStyleSheet() 方法,通过Qt样式表(Qt Style Sheets)来更改自己的外观(文本颜色为红色,粗体、字号为13px)。
(2)定义槽函数 btnReset_Clicked()
@Slot()
def btnReset_Clicked(self):
self.lineEdit.setText("用 PySide6 开发的第二个 GUI 应用程序!")
self.lineEdit.setStyleSheet("color: blue; font: bold; font-size: 13px;")
——第1行代码:@Slot() 是一个装饰器,表示将下面的函数标识为槽函数。
——第2-4行代码:自定义【更改】按钮点击信号连接的槽函数 btnChange_Clicked()。
——第3行代码:行编辑器调用其 setText() 方法,设置行编辑器的文本内容("用 PySide6 开发的第二个 GUI 应用程序!")。
——第4行代码:行编辑器调用其 setStyleSheet() 方法,通过Qt样式表(Qt Style Sheets)来更改自己的外观(文本颜色为红色,粗体、字号为13px)。
注:可能读者会对“槽函数”还是不太理解,不要着急,“信号与槽” 是 Qt 的一个重要的机制,这里先有个初步印象,知道有这么个东西就行了,在后续的文章我会对 “信号与槽“ 专门加以介绍的。
4.3 测试代码部分
最后,在测试代码部分,对自定义窗口类进行测试(创建应用程序、创建和显示自定义主窗口、运行应用程序直至退出)。
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
win = MyMainWindow()
win.show()
sys.exit(app.exec_())
——第1行代码:表示只有该 Python 文件被直接运行时,该语句之后的代码才会被执行;而如果是该 Python 文件被 import 到 Python 文件中运行时,则该语句之后的代码不会被执行。
注1:一个 Python 文件通常有两种运行方式,第一种是作为脚本被直接运行;第二种是被 import 到其他 Python 文件中(作为模块)被调用运行。
注2:测试代码并非必须,只是一种良好的编码习惯。
——第2行代码:导入 Python 内置的 sys 模块,接下的 sys.argv 和 sys.ext() 会用到该模块。
注:该导入语句也可以与其他导入语句一起放置在文件头部。
——第4行代码:使用 QApplication 类创建一个应用程序对象(app),括号内的 sys.argv 表示构造时含的传递参数。
注:QApplication 是管理 Qt GUI 应用程序的控制流程和主要设置的类。任何 Qt GUI 应用程序都必须包含一个 QApplication 类的实例对象。对于非GUI的Qt应用程序,可以使用 QCoreApplication 类。
——第5行代码:使用自定义的 MyMainWindow 类创建应用程序的主窗口(构造时会完成窗口初始化)。
——第6行代码:调用 MyMainWindow 类的 show() 方法来显示该主窗口。
——第7行代码:该行代码实际上包含两个调用方法,作用是运行应用程序,直至退出。
(1)、执行 QApplication 类的 exec_() 方法,使 Qt GUI 进入程序的主事件循环,直到程序中调用 exit()、或 quit() 时,或应用程序的主窗口被关闭时才会结束,并在退出时返回一个状态码。
(2)、sys.exit() 方法的作用是(当接收到退出状态码)退出当前应用程序。
注1:主事件循环从窗口系统接收事件,并将其分派给应用程序部件进行处理。如果没有调用方法,那么在程序运行的时候还没有进入程序的主事件循环就直接结束了,所以运行的时候窗口会闪退。
注2:直接使用 app.exec_() ,程序也可以正常运行,但是关闭窗口后进程却不会退出。
结束语
本系列介绍如何在 Python 中使用 Qt for Python 进行 GUI 应用程序开发。
本文是《Qt for Python 学习笔记》系列第四篇,如何在 Window 平台上不借助 Qt Designer 工具用纯代码方式来开发一个稍微复杂的 PySide6 应用程序(含较为详细的代码解析),让读者有更进一步的体会。
后续会借助 Qt Designer 工具进行可视化界面设计,所以接下来就对 Qt Designer 进行一个入门介绍,敬请期待!
希望本文能对您有所帮助!若文中存在疏忽不足或错误,还请不吝赐教!