第四篇 -- 信号与槽的使用
学习书籍《Python Qt GUI与数据可视化编程》
一、信号与槽功能概述
信号(Signal):就是在特定情况下被发射(emit)的一种通告,例如一个PushButton按钮最常见的信号就是鼠标单击时发射的clicked()信号,一个ComboBox最常见的信号是选择的项变化时发射的CurrentIndexChanged()信号。GUI程序设计的主要内容就是对界面上各组件发射的特定信号进行响应,只需要知道什么情况下发射了哪些信号,然后合理地去响应和处理这些信号就可以了。
槽(Slot):就是对信号响应的函数。槽实质上是一个函数,它可以被直接调用。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数会被自动执行。Qt的类一般都有一些内建(build-in)的槽函数,例如QWidget有一个槽函数close(),其功能是关闭窗口。如果将一个PushButton按钮的clicked()信号与窗体的close()槽函数关联,那么点击按钮时就会关闭窗口。
二、组件的信号与内建槽函数的关联
1. 先画一个图
属性设置表格如下:
对象名 | 类名称 | 属性设置 | 功能 |
Dialog | QDialog | windowTitle="Demo2-3信号与槽" | 窗体的类名称是Dialog,objectName不要修改 |
textEdit | QPlainTextEdit |
Text="PyQt5 编程指南\nPython 和 Qt." Font.PointSize=20 Font.bold=True |
用于显示文字,可编辑 |
chkBoxUnder | QCheckBox | Text="Underline" | 设置字体的下划线特定 |
chkBoxItalix | QCheckBox | Text="Italic" | 设置字体的斜体特性 |
chkBoxBold | QCheckBox | Text="Bold" | 设置字体的粗体特性 |
radioBlack | QRadioButton | Text="Black" | 设置字体颜色为黑色 |
radioRed | QRadioButton | Text="Red" | 设置字体颜色为红色 |
radioBlue | QRadioButton | Text="Blue" | 设置字体颜色为蓝色 |
btnClear | QPushButton | Text="清空" | 清空文本框内容 |
btnOK | QPushButton | Text="确定" | 返回确定,并关闭窗口 |
btnClose | QPushButton | Text="退出" | 退出程序 |
2. 界面组件布局管理
布局组件 | 功能 |
Vertival Layout | 垂直布局,组件自动在垂直方向上分布 |
Horizontal Layout | 水平布局,组件自动在水平方向上分布 |
Grid Layout | 网格状布局,网格状布局大小改变时,每个网格的大小都改变 |
Form Layout | 窗体布局,与网格布局类似。但是只有最右侧的一列网格会改变大小 |
Horizontal Spacer | 一个用于水平分隔的空格 |
Vertical Spacer | 一个用于垂直分隔的空格 |
使用:先拖一个布局组件到窗体上,例如窗体下方的3个按钮的布局,先放一个Horizontal Layout到窗体上,布局组件会以红色矩形框显示,在向布局组件里拖放3个PushButton和两个Horizontal Spacer,就可以得到3个按钮的水平布局。每个布局还有layoutTopMargin、layoutBottomMargin、layoutLeftMargin、layoutRightMargin这四个属性用于调整布局边框与内部组件之间的上、下、左、右的边距大小。
在设计窗体的上方有一个工具栏,用于使界面进入不同的设计状态,以及进行布局设计,工具栏商各按钮的功能如下:
按钮及快捷键 | 功能 |
Edit Widget(F3) | 界面设计进入编辑状态,也就是正常的设计状态 |
Edit Signals/Slots(F4) | 进入信号与槽的可视化设计状态 |
Edit Buddies | 进入伙伴关系编辑状态,可以设置一个Lable与一个组件成为伙伴关系 |
Edit Tab Order | 进入Tab顺序编辑状态,Tab顺序是指在键盘上按Tab键时,输入焦点在界面各组件之间跳动的顺序 |
3. 组件的信号与内建槽函数的关联
Qt的界面组件都是从QWidget继承而来的,都支持信号与槽的功能。每个类都有一些内建的信号和槽函数。例如QPushButton按钮类常用的信号是clicked(),在按钮被单击时发射此信号。QDialog是对话框类,它有以下3个内建的槽函数。
- accept():功能是关闭对话框,表示肯定的选择,例如“确定”。
- reject():功能是关闭对话框,表示否定的选择,例如“取消”。
- close():功能是关闭对话框
这3个槽函数都可以关闭对话框,但是表示对话框的返回值不同,我们希望将“确定”按钮与对话框的accept()槽函数关联,将“退出”按钮与对话框的close()槽函数关联。
可以在Qt Designer里使用可视化的方式实现信号与槽函数的关联。在Qt Designer里单击上方工具栏里的“Edit Signals/Slots”按钮,窗体进入信号与槽函数编辑状态。
鼠标点选“确定”按钮,在按住鼠标左键拖动到窗体的空白区域后释放左键,这时出现关联设置对话框
此对话框里左边的列表框里显示了btnOK的信号(上图显示的是btnClear,因为btnOK之前自己已经关联过,就不重复关联了),选择clicked(),右边的列表框里显示了Dialog的槽函数,选择accept(),然后单击“OK”按钮。同样的方法可以将btnClose的clicked()信号与Dialog的close()槽函数关联,值得注意的是,如果没有看到close()槽函数,可以将下方的“Show signals and slots inherited from QWidget”打勾。
设置好这两个按钮的信号与槽关联后,在窗体右下方的Signals Slots编辑器里就显示了这两个关联。实际上可以直接在Signals Slots编辑器进行某个组件的内建信号与其他组件的内建槽函数关联。
4. 将ui文件转换为py
Dialog.py
# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'Dialog.ui' # # Created by: PyQt5 UI code generator 5.13.0 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") Dialog.resize(400, 300) self.verticalLayout = QtWidgets.QVBoxLayout(Dialog) self.verticalLayout.setObjectName("verticalLayout") self.groupBox = QtWidgets.QGroupBox(Dialog) self.groupBox.setTitle("") self.groupBox.setObjectName("groupBox") self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.groupBox) self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.chkBoxUnder = QtWidgets.QCheckBox(self.groupBox) self.chkBoxUnder.setObjectName("chkBoxUnder") self.horizontalLayout_3.addWidget(self.chkBoxUnder) self.chkBoxItalic = QtWidgets.QCheckBox(self.groupBox) self.chkBoxItalic.setObjectName("chkBoxItalic") self.horizontalLayout_3.addWidget(self.chkBoxItalic) self.chkBoxBold = QtWidgets.QCheckBox(self.groupBox) self.chkBoxBold.setObjectName("chkBoxBold") self.horizontalLayout_3.addWidget(self.chkBoxBold) self.verticalLayout.addWidget(self.groupBox) self.groupBox_2 = QtWidgets.QGroupBox(Dialog) self.groupBox_2.setTitle("") self.groupBox_2.setObjectName("groupBox_2") self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.groupBox_2) self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.radioBlack = QtWidgets.QRadioButton(self.groupBox_2) self.radioBlack.setObjectName("radioBlack") self.horizontalLayout_4.addWidget(self.radioBlack) self.radioRed = QtWidgets.QRadioButton(self.groupBox_2) self.radioRed.setObjectName("radioRed") self.horizontalLayout_4.addWidget(self.radioRed) self.radioBlue = QtWidgets.QRadioButton(self.groupBox_2) self.radioBlue.setObjectName("radioBlue") self.horizontalLayout_4.addWidget(self.radioBlue) self.verticalLayout.addWidget(self.groupBox_2) self.plainTextEdit = QtWidgets.QPlainTextEdit(Dialog) font = QtGui.QFont() font.setPointSize(20) font.setBold(True) font.setWeight(75) self.plainTextEdit.setFont(font) self.plainTextEdit.setObjectName("plainTextEdit") self.verticalLayout.addWidget(self.plainTextEdit) self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setObjectName("horizontalLayout_2") spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem) self.btnClear = QtWidgets.QPushButton(Dialog) self.btnClear.setObjectName("btnClear") self.horizontalLayout_2.addWidget(self.btnClear) spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem1) self.btnOK = QtWidgets.QPushButton(Dialog) self.btnOK.setObjectName("btnOK") self.horizontalLayout_2.addWidget(self.btnOK) self.btnClose = QtWidgets.QPushButton(Dialog) self.btnClose.setObjectName("btnClose") self.horizontalLayout_2.addWidget(self.btnClose) self.verticalLayout.addLayout(self.horizontalLayout_2) self.retranslateUi(Dialog) self.btnOK.clicked.connect(Dialog.accept) self.btnClose.clicked.connect(Dialog.close) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslateUi(self, Dialog): _translate = QtCore.QCoreApplication.translate Dialog.setWindowTitle(_translate("Dialog", "Demo2-3信号与槽")) self.chkBoxUnder.setText(_translate("Dialog", "Underline")) self.chkBoxItalic.setText(_translate("Dialog", "Italic")) self.chkBoxBold.setText(_translate("Dialog", "Bold")) self.radioBlack.setText(_translate("Dialog", "Black")) self.radioRed.setText(_translate("Dialog", "Red")) self.radioBlue.setText(_translate("Dialog", "Blue")) self.plainTextEdit.setPlainText(_translate("Dialog", "PyQt5 编程指南\n" "Python 和 Qt.")) self.btnClear.setText(_translate("Dialog", "清空")) self.btnOK.setText(_translate("Dialog", "确定")) self.btnClose.setText(_translate("Dialog", "退出"))
5. 窗体业务逻辑类文件myDialog.py
# # 与UI窗体类对应的业务逻辑类 import sys from PyQt5.QtWidgets import QDialog, QApplication from Dialog import Ui_Dialog class QmyDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) # 调用父类构造函数,创建QWidget窗体 self.ui = Ui_Dialog() # 创建UI对象 self.ui.setupUi(self) # 构造UI if __name__ == "__main__": app = QApplication(sys.argv) # 创建app,用QApplication类 form = QmyDialog() form.show() sys.exit(app.exec_())
6. 应用程序主程序文件appMain.py
# # GUI应用程序主程序 import sys from PyQt5.QtWidgets import QApplication from myDialog import QmyDialog app = QApplication(sys.argv) # 创建GUI应用程序 mainform = QmyDialog() # 创建主窗体 mainform.show() # 显示主窗体 sys.exit(app.exec_())
notes: 程序myDialog.py可以当做主程序直接运行,但是建议单独编写一个主程序文件appMain.py,appMain.py的功能是创建应用程序和主窗体,然后显示主窗体,并开始运行应用程序。它将myDialog.py文件的测试运行部分单独拿出来作为一个文件。当一个应用程序有多个窗体,并且窗体之间有数据传递时,appMain.py负责创建应用程序的主窗体并运行起来,这样使整个应用程序的结构更清晰。