欢迎关注『OpenCV-PyQT项目实战 @ Youcans』系列,持续更新中
OpenCV-PyQT项目实战(1)安装与环境配置
OpenCV-PyQT项目实战(2)QtDesigner 和 PyUIC 快速入门
OpenCV-PyQT项目实战(3)信号与槽机制
在上节的 GUIDemo03.py 中,我们使用 QtDesigner 设计了一个简单的图像界面 ,包括菜单、工具栏、图像和按钮。但是,这些按钮和菜单项还是无效的,点击后不会调用相应的处理函数,这是因为我们还没有为这些控件关联动作程序(连接槽函数)。
本节介绍 PyQt 的信号与槽。信号与槽机制是 PyQt 的核心机制,用于对象之间的通信,也就是实现函数之间的自动调用。
简单地说,将信号与槽函数连接后,当信号被触发时,槽函数将被自动调用。
分析这个过程,涉及到几个基本概念和关系:
信号的发送者通常是一个控件对象,在控件对象的状态发生变化时发送信号。常见的发送者是图形窗口中的各种控件对象,但也可以是动作对象。
槽的接收者通常也是控件对象。槽函数是一个自定义的槽函数,或控件内置的槽函数。一般地,槽函数也有一个对象作为主体,即对于接受者这个控件对象执行函数定义的操作。例如槽函数执行的功能是关闭,哪么究竟是关闭那个控件呢?关闭对象就是接受者。
为了方便讲解信号与槽的连接,我们用 QtDesigner 在上节设计的图形窗口 uiDemo3.ui 的基础上,增加几个按钮对象和文本行编辑对象:
将这个应用程序图形界面另存为 uiDemo3.ui,其预览效果如下:
QtDesigner 提供了便捷和直观的信号/槽编辑方法。
本例介绍不同的发送者与接收者,槽函数为控件的内置函数的操作方法。不同类型的控件分别内置了若干方法,例如 QPushButton 控件内置的方法包括:点击、选中、状态变化、显示菜单等,而 QLineEdit 控件内置的方法包括:清空、复制、剪切、粘贴、全选、撤销操作等。使用控件内置的方法作为槽函数,可以直接调用,不需要对函数进行定义。
我们所设计的功能是:当点击按钮控件 “pushButton_1” 时,清空文本编辑控件 “lineEdit_1” 的显示内容。注意我们称按钮控件为 “lineEdit_1” 而不是 “文本编辑行-1”,这是因为 lineEdit_1 才是控件名称,在程序中是不变的,而 “文本编辑行-1” 只是显示内容,可以在程序中修改。
QtDesigner 设置信号/槽的连接的操作步骤如下:
本例介绍不同的发送者与接收者,槽函数为自定义函数的操作方法。
在 2.1 中介绍了使用控件内置的方法作为槽函数,可以直接调用,不需要对函数进行定义。程序设计中的核心功能通常是程序员根据需求开发的自定义函数。使用自定义函数作为槽函数,一方面当然是要编写自定义函数,另一方面要将自定义函数添加到槽函数配置连接表中。
我们所设计的功能是:当点击按钮控件 “pushButton_2” 时,清空文本编辑控件 “lineEdit_2” 的显示内容,并显示文本信息 “current signal: click pushButton_2”。在主程序中要编写一个自定义函数实现该功能,将该自定义函数命名为 click_pushButton_2()。
注意我们编写的自定义函数 click_pushButton_2(),虽然功能只是对文本编辑控件 “lineEdit_2” 进行操作,但对于自定义函数也可以完成任意的其它功能,对其它控件按照控件名称进行操作。因此该槽函数的接收者并不是文本编辑控件 “lineEdit_2”,而是主窗口控件 “MainWindow”。
QtDesigner 设置信号/槽的连接的操作步骤如下:
**首先要在 QtDesigner 将自定义函数添加到槽函数配置连接表中——非常重要。**网上的很多文章都没有讲具体实现方法,这个操作的入口也很难找到。
然后设置信号/槽的连接:
最后,别忘了要在主程序中编写自定义的函数。但这已不属于 QtDesigner 设计的内容了,在此不再详述。
类似地,我们设计:当点击按钮控件 “pushButton_3” 时,在文本编辑控件 "lineEdit_1"显示当前系统日期,在文本编辑控件 "lineEdit_2"显示当前系统时间,在文本编辑控件 “lineEdit_3” 显示提示信息。在主程序中编写一个自定义函数 click_pushButton_3(),并与 “pushButton_3” 建立信号/槽连接。
这表明:在自定义的子函数中,可以同时操作多个控件对象,进而可以实现用户定义的各种功能。
本例介绍相同的发送者与接收者,槽函数为控件的内置函数的操作方法。
顾名思义,相同的发送者与接收者,就是说信号的发送者与槽函数的接收者是同一个控件对象。这是什么情况?例如,一个开关按钮有 “On/Off” 两种状态,每按一次则按钮状态发生翻转。类似地,选项框也有选中、未选中两种状态。特殊地,点击按钮后,关闭该按钮控件,也属于相同的发送者与接收者。
我们首先将控件对象 “pushButton_4” 从按钮控件 QPushButton 改变为 选项框控件 “QCheckBox”:
此时,设计界面窗口中的按钮控件,变成了一个选项框。同时,右侧 “对象查看器” 中的控件 “pushButton_4”,也自动变更为 “checkBox_4”。变更的控件 “checkBox_4” 继承了原来控件 “pushButton_4” 的一些属性,如:位置、尺寸、显示内容。
接下来设置信号/槽的连接:
注意:本节的方法用于讲解,在本节例程中没有使用这种方法,但后续项目例程中会用到。
常见的信号发送者是图形窗口中的各种控件对象,但也可以是动作对象。本例介绍对菜单栏和工具栏中控件对象建立信号与槽的连接。
信号的发送者是动作对象时,信号的接收者通常是顶层对象 “MainWindow”,而槽函数可以是对象 “MainWindow” 的内置函数,也可以是自定义函数。
在上一篇文章中我们曾为菜单栏和工具栏中的动作 “actionQuit” 建立信号/槽连接,就是发送者是动作对象、连接到对象 “MainWindow” 的内置函数的案例。其操作过程如下:
以上操作的作用是:发送者 对象 “actionQuit” 触发 “triggered()” 时,接收者 对象"MainWindow" 执行槽函数 “closed()”。
下面我们再为另一个动作 “actionHelp” 建立信号/槽连接,连接的槽函数为自定义函数 trigger_actHelp()。其操作过程如下:
至此,本章介绍了用 QtDesigner 进行几种常见的信号/槽连接的编辑和设置方法。如下图所示,在 QtDesigner 中所添加的信号/槽连接都会在信号/槽编辑器窗口内显示。
上节在 QtDesigner 中完成了图形界面的设计,将该文件另存为 uiDemo4.ui。打开 PyCharm,选中文件 uiDemo3.ui,使用 PyUIC 可以将其转换为 uiDemo3.py。
接下来我们要编写图形界面的主程序,调用图形界面设计文件 uiDemo4.py。
将自定义的槽函数 click_pushButton_1()、click_pushButton_2、trigger_actHelp(self) 添加到主程序中。
def click_pushButton_1(self): # 点击 pushButton_1 触发
img = cv.imread("../images/Lena.tif") # OpenCV 读取图像
qtImg = QtGui.QImage(img.data, img.shape[1], img.shape[0],
QtGui.QImage.Format_RGB888).rgbSwapped() # 转为为 PyQt 图像格式
self.label.setPixmap((QtGui.QPixmap.fromImage(qtImg))) # 加载 PyQt 图像
self.label.setScaledContents(True) # 图片自适应 QLabel 区域大小
return
def trigger_actHelp(self): # 动作 actHelp 触发
QMessageBox.about(self, "About",
"""数字图像处理工具箱 v1.0\nCopyright YouCans, XUPT 2023""")
return
第 2 节介绍了使用 QtDesigner 建立信号与槽的连接。
对于较为复杂的项目,在程序中直接定义信号与槽的连接更加方便、灵活。例如:
# 建立信号与槽的连接
self.pushButton_1.clicked.connect(self.click_pushButton_1) # 点击 pushButton_1 触发
self.pushButton_2.clicked.connect(self.trigger_actHelp) # 点击 pushButton_2 触发
self.pushButton_3.clicked.connect(self.close) # 点击 pushButton_3 关闭窗口
其中,click_pushButton_1 和 trigger_actHelp 是上文的自定义槽函数,而 close 关闭窗口是内部函数。
主程序 OpenCVPyqt03.py:
# OpenCVPyqt03.py
# Demo03 of GUI by PyQt5
# Copyright 2023 Youcans, XUPT
# Crated:2023-01-26
import sys
import cv2 as cv
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox
from uiDemo3 import Ui_MainWindow # 导入 uiDemo5.py 中的 Ui_MainWindow 界面类
class MyMainWindow(QMainWindow, Ui_MainWindow): # 继承 QMainWindow 类和 Ui_MainWindow 界面类
def __init__(self, parent=None):
super(MyMainWindow, self).__init__(parent) # 初始化父类
self.setupUi(self) # 继承 Ui_MainWindow 界面类
# 建立信号与槽的连接
self.pushButton_1.clicked.connect(self.click_pushButton_1) # 点击 pushButton_1 触发
self.pushButton_2.clicked.connect(self.trigger_actHelp) # 点击 pushButton_2 触发
self.pushButton_3.clicked.connect(self.close) # 点击 pushButton_3 关闭窗口
return
def click_pushButton_1(self): # 点击 pushButton_1 触发
img = cv.imread("../images/Lena.tif") # OpenCV 读取图像
qtImg = QtGui.QImage(img.data, img.shape[1], img.shape[0],
QtGui.QImage.Format_RGB888).rgbSwapped() # 转为为 PyQt 图像格式
self.label.setPixmap((QtGui.QPixmap.fromImage(qtImg))) # 加载 PyQt 图像
self.label.setScaledContents(True) # 图片自适应 QLabel 区域大小
return
def trigger_actHelp(self): # 动作 actHelp 触发
QMessageBox.about(self, "About",
"""数字图像处理工具箱 v1.0\nCopyright YouCans, XUPT 2023""")
return
if __name__ == '__main__':
app = QApplication(sys.argv) # 在 QApplication 方法中使用,创建应用程序对象
myWin = MyMainWindow() # 实例化 MyMainWindow 类,创建主窗口
myWin.show() # 在桌面显示控件 myWin
sys.exit(app.exec_()) # 结束进程,退出程序
UI 程序 uiDemo3.py(由 uiDemo3.ui 自动生成,不需要自己编程):
# -*- coding: utf-8 -*-
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(600, 480)
MainWindow.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap("../images/youcansSmallLogo.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
MainWindow.setWindowIcon(icon)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(20, 10, 560, 330))
self.label.setText("")
self.label.setPixmap(QtGui.QPixmap("../images/Fig0301.png"))
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setObjectName("label")
self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_3.setGeometry(QtCore.QRect(390, 350, 81, 40))
self.pushButton_3.setObjectName("pushButton_3")
self.pushButton_1 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_1.setGeometry(QtCore.QRect(130, 350, 81, 40))
self.pushButton_1.setObjectName("pushButton_1")
self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_2.setGeometry(QtCore.QRect(260, 350, 81, 40))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Ignored)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.pushButton_2.sizePolicy().hasHeightForWidth())
self.pushButton_2.setSizePolicy(sizePolicy)
self.pushButton_2.setObjectName("pushButton_2")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 600, 25))
self.menubar.setBaseSize(QtCore.QSize(9, 0))
font = QtGui.QFont()
font.setPointSize(10)
self.menubar.setFont(font)
self.menubar.setObjectName("menubar")
self.menuFile = QtWidgets.QMenu(self.menubar)
self.menuFile.setObjectName("menuFile")
self.menuQuit = QtWidgets.QMenu(self.menubar)
self.menuQuit.setObjectName("menuQuit")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.toolBar = QtWidgets.QToolBar(MainWindow)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.toolBar.sizePolicy().hasHeightForWidth())
self.toolBar.setSizePolicy(sizePolicy)
self.toolBar.setMinimumSize(QtCore.QSize(0, 30))
font = QtGui.QFont()
font.setPointSize(10)
self.toolBar.setFont(font)
self.toolBar.setObjectName("toolBar")
MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar)
self.actionOpen = QtWidgets.QAction(MainWindow)
self.actionOpen.setEnabled(True)
self.actionOpen.setIconVisibleInMenu(False)
self.actionOpen.setObjectName("actionOpen")
self.actionSave = QtWidgets.QAction(MainWindow)
self.actionSave.setIconVisibleInMenu(False)
self.actionSave.setObjectName("actionSave")
self.actionClose = QtWidgets.QAction(MainWindow)
self.actionClose.setIconVisibleInMenu(False)
self.actionClose.setObjectName("actionClose")
self.actionQuit = QtWidgets.QAction(MainWindow)
self.actionQuit.setVisible(True)
self.actionQuit.setIconVisibleInMenu(False)
self.actionQuit.setObjectName("actionQuit")
self.actionSetup = QtWidgets.QAction(MainWindow)
self.actionSetup.setObjectName("actionSetup")
self.actionHelp = QtWidgets.QAction(MainWindow)
self.actionHelp.setObjectName("actionHelp")
self.menuFile.addAction(self.actionOpen)
self.menuFile.addAction(self.actionSave)
self.menuFile.addAction(self.actionClose)
self.menuQuit.addAction(self.actionQuit)
self.menubar.addAction(self.menuFile.menuAction())
self.menubar.addAction(self.menuQuit.menuAction())
self.toolBar.addAction(self.actionOpen)
self.toolBar.addAction(self.actionClose)
self.toolBar.addAction(self.actionSave)
self.toolBar.addAction(self.actionSetup)
self.toolBar.addAction(self.actionHelp)
self.toolBar.addAction(self.actionQuit)
self.retranslateUi(MainWindow)
self.actionQuit.triggered.connect(MainWindow.close)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "OpenCV-PyQt"))
self.pushButton_3.setText(_translate("MainWindow", "3 退出"))
self.pushButton_1.setText(_translate("MainWindow", "1 处理"))
self.pushButton_2.setText(_translate("MainWindow", "2 帮助"))
self.menuFile.setTitle(_translate("MainWindow", "文件"))
self.menuQuit.setTitle(_translate("MainWindow", "退出"))
self.toolBar.setWindowTitle(_translate("MainWindow", "toolBar"))
self.actionOpen.setText(_translate("MainWindow", "打开"))
self.actionSave.setText(_translate("MainWindow", "保存"))
self.actionClose.setText(_translate("MainWindow", "关闭"))
self.actionQuit.setText(_translate("MainWindow", "退出"))
self.actionSetup.setText(_translate("MainWindow", "设置"))
self.actionHelp.setText(_translate("MainWindow", "帮助"))
现在我们就得到了一个虽然简单但是很完整的面向对象的图形界面应用程序 OpenCVPyqt03。
检查一下应用程序 GUIdemo4 的各项功能:
如果以上测试都成功了,那么恭喜你已经掌握了用 QtDesigner 设计 PyQt5 图形界面的基本功能。
【本节完】
版权声明:
原创作品,转载必须标注原文链接:https://blog.csdn.net/youcans/article/details/128775270
Copyright 2023 youcans, XUPT
Crated:2023-01-28