1. 程式人生 > >Pyqt5系列(八)-自定義訊號

Pyqt5系列(八)-自定義訊號

PyQt5已經自動定義了很多QT內建的訊號。但是在實際的使用中為了靈活使用訊號與槽機制,我們可以根據需要自定義signal。可以使用pyqtSignal()方法定義新的訊號,新的訊號作為類的屬性。

自定義signal說明:

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. namethe 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")

示例說明:

自定義訊號的一般流程如下:
1、定義訊號
2、定義槽函式
3、繫結訊號和槽
4、發射訊號

通過程式碼示例來了解一下訊號的自定義過程:

#-*- 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)
    #宣告一個多過載版本的訊號,包括了一個帶intstr型別引數的訊號,以及帶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()方法來模擬傳送所需的訊號,來完成我們的對應任務.

注意事項:

1、自定義的訊號在init()函式之前定義;
2、自定義型號可以傳遞,str、int、list、object、float、tuple、dict等很多型別的引數;
3、注意signal和slot的呼叫邏輯,避免signal和slot之間出現死迴圈。如在slot方法中繼續發射該訊號;