您好,登錄后才能下訂單哦!
這篇文章主要介紹PyQt5中信號與槽機制、自定義信號的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
信號和槽機制是 QT 的核心機制,要精通 QT 編程就必須對信號和槽有所了解。信號和槽是一種高級接口,應用于對象之間的通信,它是 QT 的核心特性,也是 QT 區別于其它工具包的重要地方。
在linux、windows等 GUI 工具包中,GUI組件都會注冊回調函數用于處理組件所觸發的動作,通常是注冊對應的函數的函數指針。在之前關于Button的文章中提到了信號與槽的機制的使用,通過該機制可以很好的將組件的信號(如button的clocked、toggled、pressed等)和處理該信號的槽關聯起來。通過 信號與槽機制,能夠讓我們很簡潔和快速的來完成相關的功能。
信號和槽是用來在對象間傳遞數據的方法:當一個特定事件發生的時候,signal會被emit出來,slot調用是用來響應相應的signal的。Qt中對象已經包含了許多預定義的 signal(基本組件都有各自特有的預定義的信號),根據使用的場景我們可以添加新的signal。Qt的對象中已經包含了許多預定義的槽函數,但我們也根據使用的場景添加新的槽函數。
當對象的狀態發生改變的時候,信號就由該對象發射 (emit) 出去。當一個信號被發射(emit)時候,與其關聯的槽函數被立刻執行。其中該對象只負責發送信號,發射該信號的對象并不知道是那個對象在接收這個信號。這樣保證了對象與對象之間的低耦合。
如果存在信號和多個槽函數相關聯的時候,當信號被發射時,這些槽的執行順序將會是隨機的、不確定的。
用于接受信號,而且槽只是普通的對象成員函數。當和槽連接的信號被發射時,槽會被調用。一個槽并不知道是否有任何信號與自己相連接。
通過調用 QObject 對象的 connect 函數來將某個對象的信號與另外一個對象的槽函數相關聯,這樣當發射者發射信號時,接收者的槽函數將被調用。該函數的定義如下::
connect(slot[, type=PyQt5.QtCore.Qt.AutoConnection[, no_receiver_check=False]])
Parameters:
slot – the slot to connect to, either a Python callable or another bound signal.
type – the type of the connection to make.
no_receiver_check – suppress the check that the underlying C++ receiver instance still exists and deliver the signal anyway.
當信號與槽沒有必要繼續保持關聯時,我們可以使用 disconnect 函數來斷開連接。其定義如下:
disconnect([slot])
Parameters: slot – the optional slot to disconnect from, either a Python callable or another bound signal. If it is omitted then all slots connected to the signal are disconnected.
當信號發出后,槽函數都會被調用,但是調用的順序是隨機的,不確定的。
self.slider.valueChanged.connect(self.pBar.setValue)
self.slider.valueChanged.connect(self.lcdNumber.display)
QSlider數據的變化同時綁定在setValue()和display()兩個槽上。
其中任何一個信號發出,槽函數都會被執行。
self.buttonOn.clicked.connect(self.showMessage)
self.buttonOff.clicked.connect(self.showMessage)
showMessage()同時綁定在兩個button的clicked信號上
如list,dict等python獨有的類型。自定義信號的時候舉例說明。
比如斷開某個特定信號的關聯。
self.buttonOn.clicked.connect(self.showMessage)
第一個信號發出后,第二個信號也同時發送。比如關閉系統的信號發出之后,同時會發出保存數據的信號。
代碼示例:
關于信號和槽的式樣代碼如下:
#-*- coding:utf-8 -*- ''' Signal & Slot ''' __author__ = 'Tony Zhu' import sys from PyQt5.QtCore import Qt from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,QGridLayout,QLabel,QHBoxLayout, QGroupBox, QVBoxLayout, QApplication,QProgressBar,QPushButton,QMessageBox) class SignalSlot(QWidget): def __init__(self): super(SignalSlot,self).__init__() self.initUI() def initUI(self): self.controlsGroup = QGroupBox("運行樣本") self.lcdNumber = QLCDNumber(self) self.slider = QSlider(Qt.Horizontal, self) self.pBar = QProgressBar(self) vbox = QVBoxLayout() vbox.addWidget(self.pBar) vbox.addWidget(self.lcdNumber) vbox.addWidget(self.slider) self.controlsGroup.setLayout(vbox) controlsLayout = QGridLayout() self.label1 = QLabel("保存狀態:") self.saveLabel = QLabel() self.label2 = QLabel("運行狀態:") self.runLabel = QLabel() self.buttonSave = QPushButton("保存") self.buttonRun = QPushButton("運行") self.buttonStop = QPushButton("停止") self.buttonDisconnect = QPushButton("解除關聯") self.buttonConnect = QPushButton("綁定關聯") controlsLayout.addWidget(self.label1,0,0) controlsLayout.addWidget(self.saveLabel,0,1) controlsLayout.addWidget(self.label2,1,0) controlsLayout.addWidget(self.runLabel,1,1) controlsLayout.addWidget(self.buttonSave,2,0) controlsLayout.addWidget(self.buttonRun,2,1) controlsLayout.addWidget(self.buttonStop,2,2) controlsLayout.addWidget(self.buttonDisconnect,3,0) controlsLayout.addWidget(self.buttonConnect,3,1) layout = QHBoxLayout() layout.addWidget(self.controlsGroup) layout.addLayout(controlsLayout) self.setLayout(layout) self.buttonRun.clicked.connect(self.buttonSave.clicked) self.slider.valueChanged.connect(self.pBar.setValue) self.slider.valueChanged.connect(self.lcdNumber.display) self.buttonSave.clicked.connect(self.showMessage) self.buttonRun.clicked.connect(self.showMessage) self.buttonDisconnect.clicked.connect(self.unbindConnection) self.buttonConnect.clicked.connect(self.bindConnection) self.buttonStop.clicked.connect(self.stop) self.setGeometry(300, 500, 500, 180) self.setWindowTitle('信號和槽') def showMessage(self): if self.sender().text() == "保存": self.saveLabel.setText("Saved") elif self.sender().text() == "運行": self.saveLabel.setText("Saved") self.runLabel.setText("Running") def unbindConnection(self): self.slider.valueChanged.disconnect() def bindConnection(self): self.slider.valueChanged.connect(self.pBar.setValue) self.slider.valueChanged.connect(self.lcdNumber.display) def stop(self): self.saveLabel.setText("") self.runLabel.setText("") if __name__ == '__main__': app = QApplication(sys.argv) ex = SignalSlot() ex.show() sys.exit(app.exec_())
程序運行的結果:
控件說明:
控件類型 | 控件名稱 | 作用 |
---|---|---|
QLCDNumber | lcdNumber | 顯示slider滑動之后的數據 |
QProgressBar | pBar | 顯示slider滑動之后的數據(百分比數據) |
QSlider | slider | 滑動塊調整數據 |
QPushButton | buttonSave | 在saveLabel顯示保存的狀態”Saved” |
QPushButton | buttonRun | 在runLabel顯示運行的狀態”Running” |
QPushButton | buttonDisconnect | 解除slider.valueChanged信號的綁定 |
QPushButton | buttonConnect | 連接slider.valueChanged信號的綁定 |
QPushButton | buttonStop | 清除saveLabel和runLabel的信息 |
示例說明:
程序樣本運行的界面邏輯,先設定運行的程序樣本數量,然后先保存后運行的邏輯狀態。通過slider的滑動來改變progressBar和LCD的顯示數據;“保存”按鈕保存運行的樣本;“運行”按鈕運行程序樣本;“解除關聯”解除slider.valueChanged信號的綁定,此時slider的滑動,不會改變progressBar和LCD的顯示
示例說明:
L22~30:
self.controlsGroup = QGroupBox("運行樣本") self.lcdNumber = QLCDNumber(self) self.slider = QSlider(Qt.Horizontal, self) self.pBar = QProgressBar(self) vbox = QVBoxLayout() vbox.addWidget(self.pBar) vbox.addWidget(self.lcdNumber) vbox.addWidget(self.slider) self.controlsGroup.setLayout(vbox)
實例化一個QGroupBox,在其中添加QSlider,QProgressBar,QLCDNumber控件。
L32~41:
controlsLayout = QGridLayout() self.label1 = QLabel("保存狀態:") ..... self.buttonDisconnect = QPushButton("解除關聯") self.buttonConnect = QPushButton("綁定關聯")
實例化,界面中右半部分的控件。
L58~65:
self.buttonRun.clicked.connect(self.buttonSave.clicked) self.slider.valueChanged.connect(self.pBar.setValue) self.slider.valueChanged.connect(self.lcdNumber.display) self.buttonSave.clicked.connect(self.showMessage) self.buttonRun.clicked.connect(self.showMessage) self.buttonDisconnect.clicked.connect(self.unbindConnection) self.buttonConnect.clicked.connect(self.bindConnection) self.buttonStop.clicked.connect(self.stop)
self.slider.valueChanged.connect(self.pBar.setValue)
self.slider.valueChanged.connect(self.lcdNumber.display)
slider控件的valueChanged信號,同時與QProgressBar的setValue(),QLCDNumber的display()槽函數綁定,當valueChanged信號觸發的時候,這兩個槽函數均會被調用。
self.buttonSave.clicked.connect(self.showMessage)
self.buttonRun.clicked.connect(self.showMessage)
buttonSave和buttonRun這兩個對象的clicked信號,同時綁定到showMessage()這個槽函數。無論哪一個信號被觸發,showMessage()這個槽函數均會被調用。
self.buttonDisconnect.clicked.connect(self.unbindConnection)
當buttonDisconnect信號觸發之后,與其關聯的槽函數unbindConnection()中就會執行disconnect()方法,如下:
def unbindConnection(self): self.slider.valueChanged.disconnect()
其中執行disconnect()的時候可以指定解除與某個特定的slot槽的關聯,比如self.slider.valueChanged.disconnect(self.pBar.setValue),此時解除和QProgressBar的setValue()的關聯;或者不指定,在不指定slot的場景下這樣將解除和這個信號所有關聯的槽。
self.buttonRun.clicked.connect(self.buttonSave.clicked)
在示例說明中提到,在運行之前要對樣本進行保存,所以為了保證運行的時候執行了保存的操作,所以將buttonRun.clicked信號和buttonSave.clicked信號關聯起來。
示例中在沒有執行“保存”(buttonSave)的時候,執行“運行”(buttonRun),此時由于兩個對象的clicked信號已經關聯,所以buttonSave的clicked同樣會執行。
PyQt5已經自動定義了很多QT內建的信號。但是在實際的使用中為了靈活使用信號與槽機制,我們可以根據需要自定義signal。可以使用pyqtSignal()方法定義新的信號,新的信號作為類的屬性。
pyqtSignal()方法原型(PyQt官網的定義):
PyQt5.QtCore.pyqtSignal(types[, name[, revision=0[, arguments=[]]]])
Create one or more overloaded unbound signals as a class attribute.
Parameters:
types – the types that define the C++ signature of the signal. Each type may be a Python type object or a string that is the name of a C++ type. Alternatively each may be a sequence of type arguments. In this case each sequence defines the signature of a different signal overload. The first overload will be the default.
name – the name of the signal. If it is omitted then the name of the class attribute is used. This may only be given as a keyword argument.
revision – the revision of the signal that is exported to QML. This may only be given as a keyword argument.
arguments – the sequence of the names of the signal's arguments that is exported to QML. This may only be given as a keyword argument.
Return type: an unbound signal
新的信號應該定義在QObject的子類中。新的信號必須作為定義類的一部分,不允許將信號作為類的屬性在類定義之后通過動態的方式進行添加。通過這種方式新的信號才能自動的添加到QMetaObject類中。這就意味這新定義的信號將會出現在Qt Designer,并且可以通過QMetaObject API實現內省。
通過下面的例子,了解一下關于signal的定義:
from PyQt5.QtCore import QObject, pyqtSignal class NewSignal(QObject): # 定義了一個“closed”信號,該信號沒有參數據 closed= pyqtSignal() # 定義了一個"range_changed"信號,該信號有兩個int類型的參數 range_changed = pyqtSignal(int, int, name='rangeChanged')
自定義信號的發射,通過emit()方法類實現,具體參見該函數的原型:
emit(*args)
Parameters: args – the optional sequence of arguments to pass to any connected slots.
通過下面的例子,了解一下關于emit()的使用:
from PyQt5.QtCore import QObject, pyqtSignal class NewSignal(QObject): # 一個valueChanged的信號,該信號沒有參數. valueChanged = pyqtSignal() def connect_and_emit_valueChanged(self): # 綁定信號和槽函數 self.valueChanged.connect(self.handle_valueChanged) # 發射信號. self.trigger.emit() def handle_valueChanged(self): print("trigger signal received")
示例說明:
自定義信號的一般流程如下:
定義信號
定義槽函數
綁定信號和槽
發射信號
通過代碼示例來了解一下信號的自定義過程:
#-*- coding:utf-8 -*- ''' defined Signal ''' __author__ = 'Tony Zhu' import sys from PyQt5.QtCore import pyqtSignal, QObject, Qt, pyqtSlot from PyQt5.QtWidgets import QWidget, QApplication, QGroupBox, QPushButton, QLabel, QCheckBox, QSpinBox, QHBoxLayout, QComboBox, QGridLayout class SignalEmit(QWidget): helpSignal = pyqtSignal(str) printSignal = pyqtSignal(list) #聲明一個多重載版本的信號,包括了一個帶int和str類型參數的信號,以及帶str參數的信號 previewSignal = pyqtSignal([int,str],[str]) def __init__(self): super().__init__() self.initUI() def initUI(self): self.creatContorls("打印控制:") self.creatResult("操作結果:") layout = QHBoxLayout() layout.addWidget(self.controlsGroup) layout.addWidget(self.resultGroup) self.setLayout(layout) self.helpSignal.connect(self.showHelpMessage) self.printSignal.connect(self.printPaper) self.previewSignal[str].connect(self.previewPaper) self.previewSignal[int,str].connect(self.previewPaperWithArgs) self.printButton.clicked.connect(self.emitPrintSignal) self.previewButton.clicked.connect(self.emitPreviewSignal) self.setGeometry(300, 300, 290, 150) self.setWindowTitle('defined signal') self.show() def creatContorls(self,title): self.controlsGroup = QGroupBox(title) self.printButton = QPushButton("打印") self.previewButton = QPushButton("預覽") numberLabel = QLabel("打印份數:") pageLabel = QLabel("紙張類型:") self.previewStatus = QCheckBox("全屏預覽") self.numberSpinBox = QSpinBox() self.numberSpinBox.setRange(1, 100) self.styleCombo = QComboBox(self) self.styleCombo.addItem("A4") self.styleCombo.addItem("A5") controlsLayout = QGridLayout() controlsLayout.addWidget(numberLabel, 0, 0) controlsLayout.addWidget(self.numberSpinBox, 0, 1) controlsLayout.addWidget(pageLabel, 0, 2) controlsLayout.addWidget(self.styleCombo, 0, 3) controlsLayout.addWidget(self.printButton, 0, 4) controlsLayout.addWidget(self.previewStatus, 3, 0) controlsLayout.addWidget(self.previewButton, 3, 1) self.controlsGroup.setLayout(controlsLayout) def creatResult(self,title): self.resultGroup = QGroupBox(title) self.resultLabel = QLabel("") layout = QHBoxLayout() layout.addWidget(self.resultLabel) self.resultGroup.setLayout(layout) def emitPreviewSignal(self): if self.previewStatus.isChecked() == True: self.previewSignal[int,str].emit(1080," Full Screen") elif self.previewStatus.isChecked() == False: self.previewSignal[str].emit("Preview") def emitPrintSignal(self): pList = [] pList.append(self.numberSpinBox.value ()) pList.append(self.styleCombo.currentText()) self.printSignal.emit(pList) def printPaper(self,list): self.resultLabel.setText("Print: "+"份數:"+ str(list[0]) +" 紙張:"+str(list[1])) def previewPaperWithArgs(self,style,text): self.resultLabel.setText(str(style)+text) def previewPaper(self,text): self.resultLabel.setText(text) def keyPressEvent(self, event): if event.key() == Qt.Key_F1: self.helpSignal.emit("help message") def showHelpMessage(self,message): self.resultLabel.setText(message) #self.statusBar().showMessage(message) if __name__ == '__main__': app = QApplication(sys.argv) dispatch = SignalEmit() sys.exit(app.exec_())
運行該函數之后的效果如下:
示例說明:
通過一個模擬打印的界面來詳細說明一下關于信號的自定義,在打印的時候可以設定打印的分數,紙張類型,觸發“打印”按鈕之后,將執行結果顯示到右側;通過全屏預覽QCheckBox來選擇是否通過全屏模式進行預覽,將執行結果顯示到右側。
通過點擊F1快捷鍵,可以顯示helpMessage信息。
代碼分析:
L12~15:
helpSignal = pyqtSignal(str) printSignal = pyqtSignal(list) #聲明一個多重載版本的信號,包括了一個帶int和str類型參數的信號,以及帶str參數的信號 previewSignal = pyqtSignal([int,str],[str])
通過pyqtSignal()定義了三個信號,helpSignal ,printSignal ,previewSignal 。其中:
helpSignal 為str參數類型的信號;
printSignal 為list參數類型的信號;
previewSignal為一個多重載版本的信號,包括了一個帶int和str類型參數的信號,以及str類行的參數。
L31~36:
self.helpSignal.connect(self.showHelpMessage)
self.printSignal.connect(self.printPaper)
self.previewSignal[str].connect(self.previewPaper)
self.previewSignal[int,str].connect(self.previewPaperWithArgs)
self.printButton.clicked.connect(self.emitPrintSignal)
self.previewButton.clicked.connect(self.emitPreviewSignal)
綁定信號和槽;著重說明一下多重載版本的信號的綁定,previewSignal有兩個版本previewSignal(str),previewSignal(int,str)。由于存在兩個版本,從因此在綁定的時候需要顯式的指定信號和槽的綁定關系。
具體如下:
self.previewSignal[str].connect(self.previewPaper)
self.previewSignal[int,str].connect(self.previewPaperWithArgs)
其中[str]參數的previewSignal信號綁定previewPaper();[int,str]的previewSignal信號綁定previewPaperWithArgs()
L72~76:
def emitPreviewSignal(self): if self.previewStatus.isChecked() == True: self.previewSignal[int,str].emit(1080," Full Screen") elif self.previewStatus.isChecked() == False: self.previewSignal[str].emit("Preview")
多重載版本的信號的發射也需要制定對應發射的版本,類似同信號的版定。
L78~82:
def emitPrintSignal(self): pList = [] pList.append(self.numberSpinBox.value ()) pList.append(self.styleCombo.currentText()) self.printSignal.emit(pList)
如代碼中所示,在信號發射的時候可以傳遞python數據類型的參數,在本例中傳遞list類型的參數pList.
L93~96:
def keyPressEvent(self, event): if event.key() == Qt.Key_F1: self.helpSignal.emit("help message")
通過復寫keyPressEvent()方法,將F1快捷鍵進行功能的拓展。在windows的大部分應用,我們都會使用一些快捷鍵來快速的完成某些特定的功能。比如F1鍵,會快速調出幫助界面。那我們就可以復寫keyPressEvent()方法來模擬發送所需的信號,來完成我們的對應任務.
注意事項:
自定義的信號在init()函數之前定義;
自定義型號可以傳遞,str、int、list、object、float、tuple、dict等很多類型的參數;
注意signal和slot的調用邏輯,避免signal和slot之間出現死循環。如在slot方法中繼續發射該信號;
以上是“PyQt5中信號與槽機制、自定義信號的示例分析”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。