一、子线程中更新UI数据
当我们要持续的更新主线程UI中控件的数据时,可能会导致主窗口阻塞(未响应),这是就需要用子线程将数据传递给主线程,并调用槽函数来更新控件显示数据。
import sys import time # 导入QT,其中包含一些常量,例如颜色等 from PyQt5.QtCore import Qt, QThread, pyqtSignal, QDateTime # 导入常用组件 from PyQt5.QtWidgets import QApplication, QMainWindow from PyQt5.QtWidgets import QLineEdit # 使用调色板等 from PyQt5.QtGui import QIcon # 创建一个子线程 class UpdateThread(QThread): # 创建一个信号,触发时传递当前时间给槽函数 update_data = pyqtSignal(str) def run(self): # 无限循环,每秒钟传递一次时间给UI while True: data = QDateTime.currentDateTime() currentTime = data.toString("yyyy-MM-dd hh:mm:ss") self.update_data.emit(str(currentTime)) time.sleep(1) class DemoWin(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): self.resize(400, 100) self.lineEdit = QLineEdit(self) self.lineEdit.resize(400, 100) # 创建子线程 self.subThread = UpdateThread() # 将子线程中的信号与timeUpdate槽函数绑定 self.subThread.update_data.connect(self.timeUpdate) # 启动子线程(开始更新时间) self.subThread.start() # 添加窗口标题 self.setWindowTitle("SubThreadDemo") # 被子线程的信号触发,更新一次时间 def timeUpdate(self, data): self.lineEdit.setText(data) if __name__ == '__main__': app = QApplication(sys.argv) app.setWindowIcon(QIcon("images/icon.ico")) # 创建一个主窗口 mainWin = DemoWin() # 显示 mainWin.show() # 主循环 sys.exit(app.exec_())
在上述代码中,我们启动了一个子线程来循环发送信号,触发信号绑定的槽函数(位于主线程),每次触发都将需要显示的时间数据传递到主线程,并更新到lineEdit控件中。
实现效果:
二、信号和槽函数的自动绑定
我们使用槽函数装饰器,以及控件对象名可以实现信号和槽函数的自动绑定。
import sys # 导入QT,其中包含一些常量,例如颜色等 from PyQt5.QtCore import Qt from PyQt5 import QtCore # 导入常用组件 from PyQt5.QtWidgets import QApplication, QWidget from PyQt5.QtWidgets import QVBoxLayout from PyQt5.QtWidgets import QPushButton, QLabel # 使用调色板等 from PyQt5.QtGui import QIcon class DemoWin(QWidget): def __init__(self): super(DemoWin, self).__init__() self.initUI() def initUI(self): self.resize(400, 200) self.okButton = QPushButton("OK", self) # 给okButton指定一个ObjectName self.okButton.setObjectName("okButton") self.cancelButton = QPushButton("Cancel", self) # 给cancelButton指定一个ObjectName self.cancelButton.setObjectName("cancelButton") self.displayLabel = QLabel() layout = QVBoxLayout() layout.addWidget(self.okButton) layout.addWidget(self.cancelButton) layout.addWidget(self.displayLabel) self.setLayout(layout) # 设置自动信号槽绑定(通过控件对象名称来查找对应的槽函数) QtCore.QMetaObject.connectSlotsByName(self) # 添加窗口标题 self.setWindowTitle("AutoDemo") @QtCore.pyqtSlot() # 将这个函数指定为槽函数 def on_okButton_clicked(self): print("okButton被点击") self.displayLabel.setText("okButton被点击") @QtCore.pyqtSlot() # 将这个函数指定为槽函数 def on_cancelButton_clicked(self): print("cancelButton被点击") self.displayLabel.setText("cancelButton被点击") if __name__ == '__main__': app = QApplication(sys.argv) app.setWindowIcon(QIcon("images/icon.ico")) # 创建一个主窗口 mainWin = DemoWin() # 显示 mainWin.show() # 主循环 sys.exit(app.exec_())
注意,我们这里使用的是根据ObjectName来查找槽函数,并自动绑定。这种方式的槽函数名必须满足以下规律:
on_ObjectName_SignalName
ObjectName即我们给控件设置的objectname,SignalName就是事件名,例如clicked,triggled等。
实现效果:
三、通过lambda表达式给槽函数传递参数
当我们在使用系统默认提供的事件时(例如按钮的clicked事件),他默认是不带参数的,也就是说用connect直接绑定,只能绑定不接受参数的槽函数。
但是我们有时候需要绑定带参数的槽函数,这时就可以使用lambda表达式将槽函数包装一下。
import sys # 导入QT,其中包含一些常量,例如颜色等 from PyQt5.QtCore import Qt # 导入常用组件 from PyQt5.QtWidgets import QApplication, QWidget from PyQt5.QtWidgets import QVBoxLayout from PyQt5.QtWidgets import QPushButton, QLabel # 使用调色板等 from PyQt5.QtGui import QIcon class DemoWin(QWidget): def __init__(self): super(DemoWin, self).__init__() self.initUI() def initUI(self): self.resize(400, 100) self.okButton = QPushButton("OK", self) # 使用lambda表达式进行参数传递,即将槽函数封装为一个匿名的无参数函数给信号绑定 self.okButton.clicked.connect(lambda: self.on_okButton_clicked(20, 50)) self.displayLabel = QLabel() self.displayLabel.setAlignment(Qt.AlignCenter) layout = QVBoxLayout() layout.addWidget(self.okButton) layout.addWidget(self.displayLabel) self.setLayout(layout) # 添加窗口标题 self.setWindowTitle("LambdaDemo") def on_okButton_clicked(self, x, y): self.displayLabel.setText("x+y = " + str(x + y)) if __name__ == '__main__': app = QApplication(sys.argv) app.setWindowIcon(QIcon("images/icon.ico")) # 创建一个主窗口 mainWin = DemoWin() # 显示 mainWin.show() # 主循环 sys.exit(app.exec_())
实现效果:
四、通过partial偏函数为槽函数传参数
import sys # 导入QT,其中包含一些常量,例如颜色等 from PyQt5.QtCore import Qt # 导入常用组件 from PyQt5.QtWidgets import QApplication, QWidget from PyQt5.QtWidgets import QVBoxLayout from PyQt5.QtWidgets import QPushButton, QLabel # 使用调色板等 from PyQt5.QtGui import QIcon from functools import partial class DemoWin(QWidget): def __init__(self): super(DemoWin, self).__init__() self.initUI() def initUI(self): self.resize(400, 100) self.okButton = QPushButton("OK", self) # 使用functools提供的partial偏函数来传递参数 self.okButton.clicked.connect(partial(self.on_okButton_clicked, 20, 50)) self.displayLabel = QLabel() self.displayLabel.setAlignment(Qt.AlignCenter) layout = QVBoxLayout() layout.addWidget(self.okButton) layout.addWidget(self.displayLabel) self.setLayout(layout) # 添加窗口标题 self.setWindowTitle("LambdaDemo") def on_okButton_clicked(self, x, y): self.displayLabel.setText("x+y = " + str(x + y)) if __name__ == '__main__': app = QApplication(sys.argv) app.setWindowIcon(QIcon("images/icon.ico")) # 创建一个主窗口 mainWin = DemoWin() # 显示 mainWin.show() # 主循环 sys.exit(app.exec_())
用法基本和Lambda差不多,也就起到将槽函数封装的效果。封装后,信号所直接绑定的函数是没有参数的。
五、覆盖(override)槽函数
由于QT为我们提供了很多默认的槽函数(默认操作),我们可以通过重写槽函数将其默认操作进行覆盖。
Demo:
class DemoWin(QWidget): def __init__(self): super(DemoWin, self).__init__() self.initUI() def initUI(self): # 添加窗口标题 self.setWindowTitle("OverrideDemo") def keyPressEvent(self, a0: QtGui.QKeyEvent) -> None: if a0.key() == Qt.Key_Escape: self.close() elif a0.key() == Qt.Key_Alt: self.setWindowTitle("按下了Alt键")
我们通过覆盖keyPressEvent槽函数修改了按ESC和ALT键的行为。当我们按ESC的时候,窗口关闭,当按ALT键的时候窗口标题修改为"按下了Alt键"。
====