信號槽是Qt的核心機制,也是PyQt編程中對象進行通訊的機制。在Qt中,QObject對象和PyQt中全部繼承自QWidget的控件都支持信號槽機制。當信號發射時,鏈接的槽函數會自動執行。在PyQt5中,信號與槽函數經過object.signal.connect()方法進行鏈接。
信號槽特色以下:
(1)一個信號能夠鏈接多個槽
(2)一個信號能夠鏈接另外一個信號
(3)信號參數能夠是任意Python類型
(4)一個槽能夠監聽多個信號
(5)信號與槽的鏈接方式能夠是同步鏈接,也能夠是異步鏈接。
(6)信號與槽的鏈接能夠跨線程
(7)信號能夠斷開
在編寫一個類時,要首先定義的類的信號與槽,在類中信號與槽進行鏈接,實現對象之間的數據傳輸。當事件或狀態發生變化時,會發出信號,進而觸發執行事件或信號關聯的槽函數。信號槽機制示意以下:
編程
PyQt的內置信號是自動定義的,使用PyQt5.QtCore.pyqtSignal函數能夠爲QObject對象建立一個信號,使用pyqtSignal函數能夠把信號定義爲類的屬性。app
class pyqtSignal: def __init__(self, *types, name: str = ...) -> None: ...
types參數表示定義信號時參數的類型,name參數表示信號的名稱,默認使用類的屬性名稱。
使用pyqtSignal函數建立一個或多個重載的未綁定的信號做爲類的屬性,信號只能在QObject的子類中定義。信號必須在類建立時定義,不能在類建立後做爲類的屬性動態添加進來。使用pyqtSignal函數定義信號時,信號能夠傳遞多各參數,並指定信號傳遞參數的類型,參數類型是標準的Python數據類型,包括字符串、日期、布爾類型、數字、列表、字典、元組。異步
from PyQt5.QtCore import pyqtSignal, QObject class StandardItem(QObject): # 定義信號,信號有兩個參數,兩個參數的類型分別爲str,str,信號名稱爲dataChanged data_changed = pyqtSignal(str, str, name="dataChanged") # 更新信息,發送信號 def update(self): self.dataChanged.emit("old status", "new status")
使用connect函數能夠將信號綁定到槽函數上,使用disconnect函數能夠解除信號與槽函數的綁定,使用emit函數能夠發射信號。QObject.signal.connect(self, slot, type=None, no_receiver_check=False)
創建信號到槽函數的鏈接,type爲鏈接類型。QObject.signal.disconnect(self, slot=None)
斷開信號到槽的鏈接emit(self, *args)
發送信號,args爲可變參數。ide
import sys from PyQt5.QtCore import pyqtSignal, QObject, QCoreApplication class StandardItem(QObject): # 定義信號,信號有兩個參數,兩個參數的類型分別爲str,str,信號名稱爲dataChanged data_changed = pyqtSignal(str, str, name="dataChanged") # 更新信息,發送信號 def update(self): self.dataChanged.emit("old status", "new status") # 定義槽函數 def onDataChanged(self, old, new): print(old) print(new) if __name__ == "__main__": app = QCoreApplication(sys.argv) item = StandardItem() item.dataChanged.connect(item.onDataChanged) item.update() sys.exit(app.exec_()) # OUTPUT: # old status # new status
內置信號是QObject對象自動定義的信號,內置槽函數是QObject對象自動定義的槽函數,能夠經過QObject.signal.connect函數將QObject對象的內置信號鏈接到QObject對象的槽函數。函數
import sys from PyQt5.QtWidgets import QWidget, QApplication, QPushButton class MainWindow(QWidget): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("MainWindow Demo") self.resize(800, 600) button = QPushButton("close", self) # 鏈接內置信號與自定義槽 button.clicked.connect(self.onClose) # 自定義槽函數 def onClose(self): self.close() if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())
點擊按鈕時觸發按鈕內置的clicked信號,執行綁定的自定義槽函數onClose。性能
import sys from PyQt5.QtCore import pyqtSignal from PyQt5.QtWidgets import QWidget, QApplication, QPushButton class MainWindow(QWidget): closeSignal = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("MainWindow Demo") self.resize(800, 600) button = QPushButton("close", self) # 鏈接內置信號與自定義槽 button.clicked.connect(self.onClose) # 鏈接自定義信號closeSignal與內置槽函數close self.closeSignal.connect(self.close) # 自定義槽函數 def onClose(self): # 發送自定義信號 self.closeSignal.emit() if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())
經過鏈接內置信號clicked到自定義槽函數onClose,在自定義槽函數onClose內發送自定義信號closeSignal,並將自定義信號closeSignal與內置槽函數close鏈接。ui
import sys from PyQt5.QtCore import pyqtSignal from PyQt5.QtWidgets import QWidget, QApplication, QPushButton class MainWindow(QWidget): closeSignal = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("MainWindow Demo") self.resize(800, 600) button = QPushButton("close", self) # 鏈接內置信號與自定義槽 button.clicked.connect(self.onClicked) # 鏈接自定義信號closeSignal與內置槽函數close self.closeSignal.connect(self.onClose) # 自定義槽函數 def onClicked(self): # 發送自定義信號 self.closeSignal.emit() # 自定義槽函數 def onClose(self): self.close() if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())
一般經過類變量定義信號對象,在__init__
函數前定義自定義信號。線程
class TestObject(QObject): # 定義無參的信號 noParametersSignal = pyqtSignal() # 定義一個參數的信號,參數類型爲int oneParameterSignal = pyqtSignal(int) # 定義一個參數的重載版本的信號,參數類型能夠爲int或str oneParameterOverloadSignal = pyqtSignal([int], [str]) # 定義兩個參數的重載版本的信號,參數類型爲int,str或int,int twoParametersOverloadSignal = pyqtSignal([int, str], [int, int]) # 定義一個list參數類型的信號 oneParameterSignalList = pyqtSignal(list) # 定義一個dict參數類型的信號 oneParameterSignalDict = pyqtSignal(dict)
類的槽函數定義與類的普通方法定義相同。code
class TestObject(QObject): # 定義無參的信號 noParametersSignal = pyqtSignal() # 定義一個參數的信號,參數類型爲int oneParameterSignal = pyqtSignal(int) # 定義一個參數的重載版本的信號,參數類型能夠爲int或str oneParameterOverloadSignal = pyqtSignal([int], [str]) # 定義兩個參數的重載版本的信號,參數類型爲int,str或int,int twoParametersOverloadSignal = pyqtSignal([int, str], [int, int]) # 定義無參的槽函數 def onNoParameterSlot(self): pass # 定義一個參數的槽函數,參數爲整型nIndex def onOneParameterSlot(self, nIndex): pass # 定義兩個參數的槽函數,參數爲整型nIndex,字符串型sStatus def onTwoParametersSlot(self, nIndex, sStatus): pass
經過connect方法鏈接信號與槽函數,信號與槽函數能夠屬於同一個QObject對象,也能夠是不一樣的QObject對象。orm
test = TestObject() test.noParametersSignal.connect(test.onNoParameterSlot) test.oneParameterSignal.connect(test.onOneParameterSlot) test.twoParametersOverloadSignal.connect(test.onTwoParametersSlot)
信號的發送經過emit方法進行發送。
def update(self): self.noParametersSignal.emit() self.oneParameterSignal.emit(100) self.oneParameterOverloadSignal("Hello, PyQt5") self.twoParametersOverloadSignal(100, "Hello, PyQt5")
Qt中信號發出的參數個數必須大於等於槽函數的參數個數,PyQt使用自定義參數傳遞解決槽函數參數比信號參數多的問題。使用Lambda表達式或functools的partial函數能夠傳遞自定義參數給槽函數,自定義參數類型能夠是Python任意類型。
import sys from PyQt5.QtWidgets import QWidget, QApplication, QPushButton, QHBoxLayout from functools import partial class MainWindow(QWidget): def __init__(self, parent=None): super().__init__(parent) button1 = QPushButton("Button1", self) button2 = QPushButton("Button2", self) layout = QHBoxLayout() layout.addWidget(button1) layout.addWidget(button2) self.setLayout(layout) self.setWindowTitle("MainWindow Demo") self.resize(800, 600) # lambda button1.clicked.connect(lambda: self.onButtonClicked(1)) button2.clicked.connect(lambda: self.onButtonClicked(2)) # partial button1.clicked.connect(partial(self.onButtonClicked, 1)) button2.clicked.connect(partial(self.onButtonClicked, 2)) # 自定義槽函數 def onButtonClicked(self, n): print("Button {0} is Clicked".format(n)) if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())
PyQt中能夠經過Python裝飾器定義信號與槽函數,使用方法以下:
@PyQt5.QtCore.pyqtSlot(bool) def on_發送者對象名稱_發射信號名稱(self, parameter): pass
發送者對象名稱是使用setObjectName爲QObject對象設置的對象名稱,經過信號名稱鏈接到槽函數的connectSlotsByName函數以下:QtCore.QMetaObject.connectSlotsByName(self, QObject)
connectSlotsByName用於將QObject子孫對象的某些信號根據名稱鏈接到某些QObject對象的相應槽函數。
import sys from PyQt5 import QtCore from PyQt5.QtWidgets import QWidget, QApplication, QPushButton, QHBoxLayout class MainWindow(QWidget): def __init__(self, parent=None): super().__init__(parent) button1 = QPushButton("Button1", self) button1.setObjectName("Button1") button2 = QPushButton("Button2", self) button2.setObjectName("Button2") layout = QHBoxLayout() layout.addWidget(button1) layout.addWidget(button2) self.setLayout(layout) self.setWindowTitle("MainWindow Demo") self.resize(800, 600) QtCore.QMetaObject.connectSlotsByName(self) @QtCore.pyqtSlot() def on_Button1_clicked(self): print("Button1 is clicked") @QtCore.pyqtSlot() def on_Button2_clicked(self): print("Button2 is clicked") if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())
PyQt爲事件處理提供了高級別的信號槽機制和低級別的事件處理機制,信號槽機制是事件處理機制的高級封裝。使用控件時,不用考慮事件處理機制,只須要關心信號槽便可;對於自定義派生控件,必須考慮事件處理機制,根據控件的行爲需求從新實現相應的事件處理函數。
PyQt提供了5種事件處理和過濾方法,分別爲:
(1)從新實現事件處理函數
經常使用的事件處理函數如paintEvent、mouseMoveEvent、mousePressEvent、mouseReleaseEvent等。
(2)從新實現QObject.event事件分發函數
在增長新的事件時,須要從新實現QObject.event方法,並增長新事件的分發路由。
(3)安裝事件過濾器
若是對QObject對象調用installEventFilter方法,則爲QObject對象安裝事件過濾器。QObject對象的全部事件都會先傳遞到事件過濾器eventFilter函數,在事件過濾器eventFilter函數中能夠丟棄或修改某些事件,對感興趣的事件使用自定義的事件處理機制,對其它事件使用默認事件處理機制。事件過濾機制會對QObject的全部事件進行過濾,所以若是要過濾的事件比較多則會影響程序性能。
(4)在QApplication安裝事件過濾器
在QApplication對象安裝事件過濾器將會對全部QObject對象的全部事件進行過濾,而且會首先得到事件,即將事件發送給其它任何一個事件過濾器前,都會首先發送給QApplication的事件過濾器。
(5)從新QApplication的notify方法
PyQt使用QApplication對象的notify方法進行分發事件,要想在任何事件過濾器前捕獲事件惟一的方法就是從新實現QApplication的notify方法。
QDialog對話框在ESC按鍵按下時會自動退出,使用事件處理和過濾對按下ESC按鍵進行處理。
(1)從新實現事件處理函數
import sys from PyQt5.QtWidgets import QDialog, QApplication from PyQt5.QtCore import Qt class Dialog(QDialog): def __init__(self, parent=None): super().__init__(parent) # 從新實現keyPressEvent def keyPressEvent(self, event): if event.key() != Qt.Key_Escape: QDialog.keyPressEvent(self, event) if __name__ == "__main__": app = QApplication(sys.argv) dialog = Dialog() dialog.exec_() sys.exit(app.exec_())
(2)從新實現event函數
import sys from PyQt5.QtWidgets import QDialog, QApplication from PyQt5.QtCore import Qt from PyQt5.QtGui import QKeyEvent class Dialog(QDialog): def __init__(self, parent=None): super().__init__(parent) # 從新實現keyPressEvent def event(self, event): if event.type() == QKeyEvent.KeyPress and event.key() == Qt.Key_Escape: return True else: return QDialog.event(self, event) if __name__ == "__main__": app = QApplication(sys.argv) dialog = Dialog() dialog.exec_() sys.exit(app.exec_())
(3)QObject安裝事件過濾器
import sys from PyQt5.QtWidgets import QDialog, QApplication from PyQt5.QtCore import Qt from PyQt5.QtGui import QKeyEvent class Dialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.installEventFilter(self) def eventFilter(self, watched, event): if event.type() == QKeyEvent.KeyPress and event.key() == Qt.Key_Escape: return True else: return QDialog.eventFilter(self, watched, event) if __name__ == "__main__": app = QApplication(sys.argv) dialog = Dialog() dialog.exec_() sys.exit(app.exec_())
(4)QApplication安裝事件過濾器
import sys from PyQt5.QtWidgets import QDialog, QApplication from PyQt5.QtCore import Qt from PyQt5.QtGui import QKeyEvent class Dialog(QDialog): def __init__(self, parent=None): super().__init__(parent) def eventFilter(self, watched, event): if event.type() == QKeyEvent.KeyPress and event.key() == Qt.Key_Escape: return True else: return QDialog.eventFilter(self, watched, event) if __name__ == "__main__": app = QApplication(sys.argv) dialog = Dialog() app.installEventFilter(dialog) dialog.exec_() sys.exit(app.exec_())