一、子線程中更新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鍵"。
====