訊號的定義

PyQt5中的訊號我的理解就像飯店出菜口的那個鈴,➡️

傳菜鈴

需要的時候就是“叮”一下.然後廚師會叫出菜名或者桌號,服務員就會把菜送到正確的顧客桌上.

下面說正體:(此處不講什麼訊號與槽,single與slot,我覺得太複雜)

關於自定義訊號,你需要掌握以下幾個點:

  1. 訊號的宣告
  2. 訊號的觸發
  3. 訊號的接收

下面我們要實現這樣一個例子:有一個QTableView控制元件(3*3大小),無論我們單擊哪個單元格,都會在控制檯輸出該行的第一列的內容.

  • 訊號的宣告(買一個傳菜鈴)

需要自定義某個控制元件的訊號,你需要建立一個新類繼承原來的類,比如:

class MyTableView(QTableView):
    def __init__(self, parent = None):
        super(MyTableView,self).__init__(parent)

這裡我們就建立了一個自己的MyTableView類,繼承於QTableView.

他的用法跟正常的QTableView用法一毛一樣,然後我們就要來對訊號進行宣告

class MyTableView(QTableView):
    doubleClickedItem = pyqtSignal(str)
    
    def __init__(self, parent = None):
        super(MyTableView,self).__init__(parent)

在初始化函式__init__ 之前加入的就是自定義訊號的申明,這個宣告只能在初始化函式外面,pyqtSignal這個類是在PyQt5.QtCore裡面.

引數(str),這個引數只能是資料型別,不能是變數名,表示這個訊號傳送的是一個字串,你也可以傳送任何你需要的型別,比如:

訊號名 = pyqtSignal(型別)

mySignal = pyqtSignal(int)        #傳送int型資料
mySignal = pyqtSignal(float)      #傳送float型資料
mySignal = pyqtSignal(str, int)   #傳送的訊號第一個是str,第二個是int
mySignal = pyqtSignal(int, int, int, ...) #傳送幾個都可以
mySignal = pyqtSignal(list)       #傳送list,如[1,2,3]['a','b','c']等

宣告完了,就需要決定什麼時候來觸發他.

  • 訊號的觸發(廚師按下傳菜鈴,並叫出菜名或者桌號)

需要雙擊來觸發這個訊號,我們要先看看官方有沒有給出什麼方法,通過官方文件找到一個差不多的:

不斷的查他的定義,發現這個也是個訊號,傳送的是QModelIndex型別的index,可以告訴我們是哪個位置的單元格被雙擊了,有點接近但也不是我們需要的[該行第一列]的內容,所以需要藉助這個訊號來處理以下:

class MyTableView(QTableView):
    doubleClickedItem = pyqtSignal(str)

    def __init__(self, parent = None):
        super(MyTableView,self).__init__(parent)
        self.doubleClicked.connect(self.doubleClickedHandle)

    def doubleClickedHandle(self, index):
        text = self.model().item(index.row(),0).text()
        self.doubleClickedItem.emit(text)

__init__中的self.doubleClicked.connect(self.doubleClickedHandle)表示把自己建立的類的doubleClicked訊號連線到doubleClickedHandle函式,注意這個引數中的函式只有名字,沒有括號,連線訊號的格式為:

某個類.某個訊號.connect(接收函式)

然後再來doubleClickedHandle中處理接收的函式.詳細內容將在下一節講解

函式中的text就是獲取了被雙擊的單元格所在行的第一列內容.

然後通過emit方法傳送出去.格式為:

某個訊號.emit(訊號內容)

訊號內容的型別必須和宣告時定義的型別一致

處理完以後我們我們要來處理如何接收這個訊號.

  • 訊號的接收(服務員來擡菜)

在傳送我們自己的訊號時已經進行了一次訊號的接收處理,只不過這個訊號的定義和觸發都是PyQt自己定義好的,我們只是完成接收處理:

從文件中可以看到這個訊號有一個QModelIndex型別的index引數,這裡我們必須要得到這個引數才能知道點選了哪一個單元格,所以處理函式doubleClickedHandle必須有一個在同位置同類型的引數:

    def doubleClicked(self, QModelIndex): #訊號的定義
        pass
    def doubleClickedHandle(self, index): #接收函式的定義
        pass

QModelIndex與index是同一個型別,而且必須在同一個位置:

    mySignal = pyqtSignal(str, float, int) #定義一個訊號傳送三個值
    def mySignalHandle(par1, par2, par3): #接收函式
        pass    #par1收到的是str,par2收到的是float,par3收到的是int

回到doubleClickedHandle

    def doubleClickedHandle(self, index):
        text = self.model().item(index.row(),0).text()
        self.doubleClickedItem.emit(text)

第二行中,index.row()獲取被雙擊的行號(int型)

self.model().item(row, column),通過設定row和column值來定位單元格,最後的.text()獲取文字內容,

詳細不多做解釋官方文件裡面都有,這裡只重點講解訊號.

最後一行:就是把獲取到的第一列的單元格內容通過訊號doubleClickedItem傳送出去.

在外的話我們也要接收doubleClickedItem這個訊號,接收函式就是print:

myTable是上面MyTableView的一個例項,在某個地方我可以這樣來接收訊號:

    myTable.doubleClickedItem.connect(print)    

實現的效果就是雙擊任意單元格可以打印出該行第一列的單元格內容

  • 最後

關於自定義訊號,你需要掌握以下幾個點:

  1. 訊號的宣告:  訊號名 = pyqtSignal(型別)

  2. 訊號的觸發:  訊號名.emit(訊號內容)

  3. 訊號的接收:  訊號所在類例項.訊號名.connect(接收函式)

完整程式碼

from PyQt5.QtWidgets import QApplication
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtCore import pyqtSignal

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle('')
        self.initUI()
        self.show()

    def initUI(self):
        table = MyTableView(self)
        model = QStandardItemModel()
        model.setColumnCount(3)
        [model.appendRow([QStandardItem(str(i + j)) for i in [0,1,2]])for j in [1,2,3]]
        table.setModel(model)
        table.doubleClickedItem.connect(print)
        self.setCentralWidget(table)


class MyTableView(QTableView):
    doubleClickedItem = pyqtSignal(str)

    def __init__(self, parent = None):
        super(MyTableView,self).__init__(parent)
        self.doubleClicked.connect(self.doubleClickedHandle)

    def doubleClickedHandle(self, index):
        text = self.model().item(index.row(),0).text() 
        #單元格沒有值時會出錯,None type沒有text()這個方法,如有需要自己catch
        self.doubleClickedItem.emit(text)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    mw = MainWindow()
    sys.exit(app.exec_())