天天看點

PyQt 5信号與槽的幾種進階玩法

信号(Signal)和槽(Slot)是Qt中的核心機制,也是在PyQt程式設計中對象之間進行通信的機制。本文介紹了幾種PyQt 5信号與槽的幾級玩法。

在Qt中,每一個QObject對象和PyQt中所有繼承自QWidget的控件(這些都是QObject的子對象)都支援信号與槽機制。當信号發射時,連接配接的槽函數将會自動執行。在PyQt 5中信号與槽通過object.signal.connect()方法連接配接。

PyQt的視窗控件類中有很多内置信号,開發者也可以添加自定義信号。信号與槽具有如下特點。

一個信号可以連接配接多個槽。

一個信号可以連接配接另一個信号。

信号參數可以是任何Python類型。

一個槽可以監聽多個信号。

信号與槽的連接配接方式可以是同步連接配接,也可以是異步連接配接。

信号與槽的連接配接可能會跨線程。

信号可能會斷開。

在GUI程式設計中,當改變一個控件的狀态時(如單擊了按鈕),通常需要通知另一個控件,也就是實作了對象之間的通信。在早期的GUI程式設計中使用的是回調機制,在Qt中則使用一種新機制——信号與槽。在編寫一個類時,要先定義該類的信号與槽,在類中信号與槽進行連接配接,實作對象之間的資料傳輸。信号與槽機制示意圖如圖1所示。

PyQt 5信号與槽的幾種進階玩法

圖1

當事件或者狀态發生改變時,就會發出信号。同時,信号會觸發所有與這個事件(信号)相關的函數(槽)。信号與槽可以是多對多的關系。一個信号可以連接配接多個槽,一個槽也可以監聽多個信号。

所謂進階自定義信号與槽,指的是我們可以以自己喜歡的方式定義信号與槽函數,并傳遞參數。自定義信号的一般流程如下:

(1)定義信号。

(2)定義槽函數。

(3)連接配接信号與槽函數。

(4)發射信号。

通過類成員變量定義信号對象。

定義一個槽函數,它有多個不同的輸入參數。

通過connect方法連接配接信号與槽函數或者可調用對象。

通過emit方法發射信号。

本例檔案名為PyQt5/Chapter07/qt07_signalSlot02.py,其完整代碼如下:

運作結果如下:

在PyQt程式設計過程中,經常會遇到給槽函數傳遞自定義參數的情況,比如有一個信号與槽函數的連接配接是

我們知道對于clicked信号來說,它是沒有參數的;對于show_page函數來說,希望它可以接收參數。希望show_page函數像如下這樣:

于是就産生一個問題——信号發出的參數個數為0,槽函數接收的參數個數為1,由于0<1,這樣運作起來一定會報錯(原因是信号發出的參數個數一定要大于槽函數接收的參數個數)。解決這個問題就是本節的重點:自定義參數的傳遞。

本書提供了兩種解決方法,其中一種解決方法是使用lambda表達式。本例檔案名為PyQt5/Chapter07/qt07_ winSignalSlot04.py ,其完整代碼如下:

運作腳本,顯示效果如圖2和圖3所示。

PyQt 5信号與槽的幾種進階玩法

圖2

PyQt 5信号與槽的幾種進階玩法

圖3

代碼分析:

單擊“Button 1”按鈕,将彈出一個資訊提示框,提示資訊為“Button 1 clicked”。Python控制台的輸出資訊為:

這裡重點解釋onButtonClick()函數是怎樣處理從兩個按鈕傳來的信号的。使用lambda表達式傳遞按鈕數字給槽函數,當然也可以傳遞其他任何東西,甚至是按鈕控件本身(假設槽函數打算把傳遞信号的按鈕修改為不可用的話)。

另一種解決方法是使用functools中的partial函數。本例檔案名為PyQt5/Chapter07/qt07_winSignalSlot05.py,其核心代碼如下:

采用哪種方法好一點呢?這屬于風格問題,筆者比較喜歡使用lambda表達式,因為其條理清晰,而且靈活。

所謂裝飾器信号與槽,就是通過裝飾器的方法來定義信号和槽函數。具體的使用方法如下:

這種方法有效的前提是下面的函數已經執行:

在上面代碼中,“發送者對象名稱”就是使用setObjectName函數設定的名稱,是以自定義槽函數的命名規則也可以看成:on + 使用setObjectName設定的名稱 + 信号名稱。接下來看具體的使用方法。

本例檔案名為PyQt5/Chapter07/qt07_connSlotsByName.py,其完整代碼如下:

運作腳本,顯示效果如圖4所示。單擊“OK”按鈕,控制台列印出預期的調試資訊。

PyQt 5信号與槽的幾種進階玩法

圖4

有的讀者可能注意到,我們一直沒有解釋下面這行代碼的含義:

事實上,它是在PyQt 5中根據信号名稱自動連接配接到槽函數的核心代碼。通過前面章節中的例子可以知道,使用pyuic5指令生成的代碼中會帶有這麼一行代碼,接下來對其進行解釋。

這行代碼用來将QObject中的子孫對象的某些信号按照其objectName連接配接到相應的槽函數。這句話讀起來有些拗口,這裡舉個例子進行簡單說明。以上面例子中的代碼為例:

假設代碼QtCore.QMetaObject.connectSlotsByName(self)已經執行,則下面的代碼:

會被自動識别為下面的代碼(注意,函數中去掉了on,因為on會受到connectSlotsByName的影響,加上on運作時會出現問題):

這部分代碼放在PyQt5/Chapter07/qt07_connSlotsByName_2.py檔案中:

運作上述代碼,發現結果和圖4一樣。

有時候基于某些原因,想要臨時或永久斷開某個信号與槽的連接配接。這就是本節案例想要達到的目的。

本例檔案名為PyQt5/Chapter07/qt07_signalSlot03.py,其完整代碼如下:

最簡單的多線程使用方法是利用QThread函數,如下代碼(見PyQt5/Chapter07/ qt07_signalSlot04.py)展示了QThread函數和信号與槽簡單的結合方法。其完整代碼如下:

有時在開發程式時經常會執行一些耗時的操作,這樣就會導緻界面卡頓,這也是多線程的應用範圍之一——為了解決這個問題,我們可以建立多線程,使用主線程更新界面,使用子線程實時處理資料,最後将結果顯示到界面上。

本例中,定義了一個背景線程類BackendThread來模拟背景耗時操作,在這個線程類中定義了信号update_date。使用BackendThread線程類在背景處理資料,每秒發射一次自定義信号update_date。

在初始化視窗界面時,定義背景線程類BackendThread,并把線程類的信号update_date連接配接到槽函數handleDisplay()。這樣背景線程每發射一次信号,就可以把最新的時間值實時顯示在前台視窗的QLineEdit文本對話框中。

本例檔案名為PyQt5/Chapter07/qt07_signalSlotThreaad.py,其完整代碼如下:

運作腳本,顯示效果如圖5所示。

PyQt 5信号與槽的幾種進階玩法

圖5

                  

PyQt 5信号與槽的幾種進階玩法

  想及時獲得更多精彩文章,可在微信中搜尋“博文視點”或者掃描下方二維碼并關注。

                    

PyQt 5信号與槽的幾種進階玩法