pyqt5信号槽机制应用
Published in:2024-08-21 |
Words: 2.3k | Reading time: 10min | reading:

pyqt5信号槽机制应用

简介

PyQt5中的信号和槽是用于对象之间的事件处理和通信的一种机制。信号在某个特定事件发生时发出,槽是用来响应这些信号的函数。信号(Signal)与槽(Slot)是Qt中的核心机制,也是在PyQt编程中对象之间进行通信的机制; 在Qt中,每一个QObject对象和PyQt中所有继承自QWidget的控件(这些都是QObject的子对象)都支持信号与槽机制。当信号发射时,连接的槽函数将会自动执行。在PyQt 5中信号与槽通过object.signal.connect()方法连接。

  • 类图

img

简易说明demo

通过按钮触发事件,按钮连接槽函数,当按钮点击时触发事件,事件发送信号;槽函数收到信号时输出信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import sys
from PyQt5.QtWidgets import QApplication, QPushButton
from PyQt5.QtCore import QObject, pyqtSlot

class MyButton(QPushButton):
def __init__(self, text):
super().__init__(text)

def mouseReleaseEvent(self, event):
self.clicked.emit() # 发射信号

class MyWindow(QObject):
def __init__(self):
super().__init__()
self.button = MyButton("Click Me")
self.button.clicked.connect(self.on_button_clicked) # 连接信号槽

@pyqtSlot()
def on_button_clicked(self):
print("Button clicked!")

def main():
app = QApplication(sys.argv)
window = MyWindow()
window.button.show()
sys.exit(app.exec_())

if __name__ == '__main__':
main()

信号与槽的特点

  • 一个信号可以连接多个槽。
  • 一个信号可以连接另一个信号。
  • 信号参数可以使任何Python类型。
  • 一个槽可以连接到多个信号。
  • 信号与槽的连接方式可以是同步连接,也可以是异步连接。
  • 信号与槽的连接可能会跨线程。
  • 信号可以断开连接。

机制使用

自定义信号与槽

  • 自定义信号
1
2
3
4
5
6
7
8
9
10
11
12
class MyWidget(QWidget): 
# 无参数的信号
Signal_NoParameters = pyqtSignal()
# 带一个参数(整数)的信号
Signal_OneParameter = pyqtSignal(int)
# 带一个参数(整数或者字符串)的重载版本的信号
Signal_OneParameter_Overload = pyqtSignal([int],[str])
# 带两个参数(整数,字符串)的信号
Signal_TwoParameters = pyqtSignal(int,str)
# 带两个参数([整数,整数]或者[整数,字符串])的重载版本的信号
Signal_TwoParameters_Overload = pyqtSignal([int,int],[int,str])

  • 自定义槽函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyWidget(QWidget): 
def setValue_NoParameters(self):
'''无参数的槽函数'''
pass

def setValue_OneParameter(self,nIndex):
'''带一个参数(整数)的槽函数'''
pass

def setValue_OneParameter_String(self,szIndex):
'''带一个参数(字符串)的槽函数'''
pass

def setValue_TwoParameters(self,x,y):
'''带两个参数(整数,整数)的槽函数'''
pass

def setValue_TwoParameters_String(self,x,szY):
'''带两个参数(整数,字符串)槽函数'''
pass
  • 信号槽与槽函数连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
app = QApplication(sys.argv)  
widget = MyWidget()
# 连接无参数的信号
widget.Signal_NoParameters.connect(self.setValue_NoParameters )

# 连接带一个整数参数的信号
widget.Signal_OneParameter.connect(self.setValue_OneParameter)

# 连接带一个整数参数,经过重载的信号
widget.Signal_OneParameter_Overload[int].
connect(self.setValue_OneParameter)

# 连接带一个整数参数,经过重载的信号
widget.Signal_OneParameter_Overload[str].
connect(self.setValue_OneParameter_String )

# 连接一个信号,它有两个整数参数
widget.Signal_TwoParameters.connect(self.setValue_TwoParameters )

# 连接带两个参数(整数,整数)的重载版本的信号
widget.Signal_TwoParameters_Overload[int,int].
connect(self.setValue_TwoParameters )

# 连接带两个参数(整数,字符串)的重载版本的信号
widget.Signal_TwoParameters_Overload[int,str].
connect(self.setValue_TwoParameters_String )
widget.show()

  • 信号发送
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyWidget(QWidget): 

def mousePressEvent(self, event):
# 发射无参数的信号
self.Signal_NoParameters.emit()
# 发射带一个参数(整数)的信号
self.Signal_OneParameter.emit(1)
# 发射带一个参数(整数)的重载版本的信号
self.Signal_OneParameter_Overload.emit(1)
# 发射带一个参数(字符串)的重载版本的信号
self.Signal_OneParameter_Overload.emit("abc")
# 发射带两个参数(整数,字符串)的信号
self.Signal_TwoParameters.emit(1,"abc")
# 发射带两个参数(整数,整数)的重载版本的信号
self.Signal_TwoParameters_Overload.emit(1,2)
# 发射带两个参数(整数,字符串)的重载版本的信号
self.Signal_TwoParameters_Overload.emit (1,"abc")
  • all code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

from PyQt5.QtCore import QObject , pyqtSignal

class CustSignal(QObject):

#声明无参数的信号
signal1 = pyqtSignal()

#声明带一个int类型参数的信号
signal2 = pyqtSignal(int)

#声明带int和str类型参数的信号
signal3 = pyqtSignal(int,str)

#声明带一个列表类型参数的信号
signal4 = pyqtSignal(list)

#声明带一个字典类型参数的信号
signal5 = pyqtSignal(dict)

#声明一个多重载版本的信号,包括带int和str类型参数的信号和带str类型参数的信号
signal6 = pyqtSignal([int,str], [str])

def __init__(self,parent=None):
super(CustSignal,self).__init__(parent)

#将信号连接到指定槽函数
self.signal1.connect(self.signalCall1)
self.signal2.connect(self.signalCall2)
self.signal3.connect(self.signalCall3)
self.signal4.connect(self.signalCall4)
self.signal5.connect(self.signalCall5)
self.signal6[int,str].connect(self.signalCall6)
self.signal6[str].connect(self.signalCall6OverLoad)

#发射信号
self.signal1.emit()
self.signal2.emit(1)
self.signal3.emit(1,"text")
self.signal4.emit([1,2,3,4])
self.signal5.emit({"name":"wangwu","age":"25"})
self.signal6[int,str].emit(1,"text")
self.signal6[str].emit("text")

def signalCall1(self):
print("signal1 emit")

def signalCall2(self,val):
print("signal2 emit,value:",val)

def signalCall3(self,val,text):
print("signal3 emit,value:",val,text)

def signalCall4(self,val):
print("signal4 emit,value:",val)

def signalCall5(self,val):
print("signal5 emit,value:",val)

def signalCall6(self,val,text):
print("signal6 emit,value:",val,text)

def signalCall6OverLoad(self,val):
print("signal6 overload emit,value:",val)

if __name__ == '__main__':
custSignal = CustSignal()
  • 信号与槽传递自定义参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from PyQt5.QtWidgets import QMainWindow, QPushButton , QWidget , QMessageBox, QApplication, QHBoxLayout
import sys

class WinForm(QMainWindow):
def __init__(self, parent=None):
super(WinForm, self).__init__(parent)
button1 = QPushButton('Button 1')
button2 = QPushButton('Button 2')

button1.clicked.connect(lambda: self.onButtonClick(1))
button2.clicked.connect(lambda: self.onButtonClick(2))

layout = QHBoxLayout()
layout.addWidget(button1)
layout.addWidget(button2)

main_frame = QWidget()
main_frame.setLayout(layout)
self.setCentralWidget(main_frame)

def onButtonClick(self, n):
print('Button {0} 被按下了'.format(n))
QMessageBox.information(self, "信息提示框", 'Button {0} clicked'.format(n))

if __name__ == "__main__":
app = QApplication(sys.argv)
form = WinForm()
form.show()
sys.exit(app.exec_())

装饰器定义信号与槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from PyQt5 import QtCore 
from PyQt5.QtWidgets import QApplication ,QWidget ,QHBoxLayout , QPushButton
import sys

class CustWidget( QWidget ):

def __init__(self, parent=None):
super(CustWidget, self).__init__(parent)

self.okButton = QPushButton("OK", self)
#使用setObjectName设置对象名称
self.okButton.setObjectName("okButton")
layout = QHBoxLayout()
layout.addWidget(self.okButton)
self.setLayout(layout)
QtCore.QMetaObject.connectSlotsByName(self)

@QtCore.pyqtSlot()
def on_okButton_clicked(self):
print( "单击了OK按钮")

if __name__ == "__main__":
app = QApplication(sys.argv)
win = CustWidget()
win.show()
app.exec_()

信号与槽的断开连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from PyQt5.QtCore import QObject , pyqtSignal

class SignalClass(QObject):

# 声明无参数的信号
signal1 = pyqtSignal()

# 声明带一个int类型参数的信号
signal2 = pyqtSignal(int)

def __init__(self,parent=None):
super(SignalClass,self).__init__(parent)

# 将信号signal1连接到sin1Call和sin2Call这两个槽函数
self.signal1.connect(self.sin1Call)
self.signal1.connect(self.sin2Call)

# 将信号signal2连接到信号signal1
self.signal2.connect(self.signal1)

# 发射信号
self.signal1.emit()
self.signal2.emit(1)

# 断开signal1、signal2信号与各槽函数的连接
self.signal1.disconnect(self.sin1Call)
self.signal1.disconnect(self.sin2Call)
self.signal2.disconnect(self.signal1)

# 将信号signal1和signal2连接到同一个槽函数sin1Call
self.signal1.connect(self.sin1Call)
self.signal2.connect(self.sin1Call)

# 再次发射信号
self.signal1.emit()
self.signal2.emit(1)

def sin1Call(self):
print("signal-1 emit")

def sin2Call(self):
print("signal-2 emit")

if __name__ == '__main__':
signal = SignalClass()

多线程使用

  • QThread函数和信号与槽简单的结合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from PyQt5.QtWidgets import QApplication ,QWidget
from PyQt5.QtCore import QThread , pyqtSignal
import sys

class Main(QWidget):
def __init__(self, parent = None):
super(Main,self).__init__(parent)

# 创建一个线程实例并设置名称、变量、信号与槽
self.thread = MyThread()
self.thread.setIdentity("thread1")
self.thread.sinOut.connect(self.outText)
self.thread.setVal(6)

def outText(self,text):
print(text)

class MyThread(QThread):
sinOut = pyqtSignal(str)

def __init__(self,parent=None):
super(MyThread,self).__init__(parent)
self.identity = None

def setIdentity(self,text):
self.identity = text

def setVal(self,val):
self.times = int(val)
# 执行线程的run方法
self.start()

def run(self):
while self.times 0 and self.identity:
# 发射信号
self.sinOut.emit(self.identity+"== "+str(self.times))
self.times -= 1

if __name__ == '__main__':
app = QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())
  • 后台线程内容实时更新到UI线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

from PyQt5.QtCore import QThread , pyqtSignal, QDateTime
from PyQt5.QtWidgets import QApplication, QDialog, QLineEdit
import time
import sys

class BackendThread(QThread):
# 通过类成员对象定义信号
update_date = pyqtSignal(str)

# 处理业务逻辑
def run(self):
while True:
data = QDateTime.currentDateTime()
currTime = data.toString("yyyy-MM-dd hh:mm:ss")
self.update_date.emit( str(currTime) )
time.sleep(1)

class Window(QDialog):
def __init__(self):
QDialog.__init__(self)
self.setWindowTitle('PyQt5界面实时更新例子')
self.resize(400, 100)
self.input = QLineEdit(self)
self.input.resize(400, 100)
self.initUI()

def initUI(self):
# 创建线程
self.backend = BackendThread()
# 连接信号
self.backend.update_date.connect(self.handleDisplay)
# 开始线程
self.backend.start()

# 将当前时间输出到文本框
def handleDisplay(self, data):
self.input.setText(data)

if __name__ == '__main__':
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())

案例

机制回顾

img

跨线程信号与槽应用

  • 定义信号与槽函数类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QLineEdit, QPushButton
from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot, QObject, QRunnable

"""
pyqt5 信号槽
"""


class WorkerSignals(QObject):
finished = pyqtSignal(str)


class Worker(QRunnable):
def __init__(self, text):
super(Worker, self).__init__()
self.text = text
self.signals = WorkerSignals()

@pyqtSlot()
def run(self):
# 模拟长时间运行的任务
# ...
# 完成任务后发出信号
self.signals.finished.emit(self.text)

  • 信号绑定与事件触发
1
2
3
4
5
6
7
8
9
def test():
"""
"""
...
worker = Worker(constants.detect_dsa_version_msg)
worker.signals.finished.connect(self.on_hex_changed)
QThreadPool.globalInstance().start(worker)
worker.run()

  • 事件处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@logger.catch
def on_hex_changed(self, msg=None, _=None):
"""

:param text:
:param _:
:return:
"""
# bool
...
if True:
if msg:
logger.debug(f"detect signal to msg: {msg}]")
self.input_box_hex.setPlaceholderText(msg)
input_str = msg.replace(" ", "")

See

Next:
python 使用tesseract实现实时监控桌面