1. 程式人生 > 其它 >PyQt5-訊號與槽的使用

PyQt5-訊號與槽的使用

訊號與槽的使用

訊號與槽(Signals/Slots)是Qt程式設計的基礎,也是Qt的一大特色。因為有了訊號與槽的程式設計機制,在Qt中處理介面元件的互動操作時變得比較直觀和簡單。

訊號(Signal)就是在特定情況下被髮射(emit)的一種通告,例如一個PushButton按鈕最常見的訊號就是滑鼠單擊時發射的clicked()訊號,一個ComboBox最常見的訊號是選擇的項變化時發射的CurrentIndexChanged()訊號。GUI程式設計的主要內容就是對介面上各元件發射的特定訊號進行響應,只需要知道什麼情況下發射了哪些訊號,然後合理地去響應和處理這些訊號就可以了。

槽(Slot)就是對訊號響應的函式。槽實質上是一個函式,它可以被直接呼叫。槽函式與一般的函式不同的是:槽函式可以與一個訊號關聯,當訊號被髮射時,關聯的槽函式會被自動執行。Qt的類一般都有一些內建(build-in)的槽函式,例如QWidget有一個槽函式close(),其功能是關閉視窗。如果將一個PushButton按鈕的clicked()訊號與窗體的close()槽函式關聯,那麼點選按鈕時就會關閉視窗。

將展示下面的結果

  • 上方的3個複選框可以控制文字框內的字型的下劃線、斜體、粗體特性。

  • 3個RadioButton按鈕可以控制文字框內的文字顏色。

  • “清空”按鈕可以清空文字框內的文字。

  • “確定”和“退出”按鈕都可以關閉視窗,但是表示對話方塊的不同選擇結果。

Qt Creator的使用

啟動Qt Creator,建立一個名為QtApp的C++ GUI應用程式專案

建立一個對話方塊,所以選擇基類QDialog,新窗體的類名稱就使用預設的Dialog,這將自動建立3個檔案,即Dialog.h、Dialog.cpp和Dialog.ui。

設計成這樣的介面

介面元件佈局管理

介面元件的層次關係

為了將介面上的各個元件的分佈設計得更加美觀,經常使用一些容器類元件,如GroupBox、TabWidget、Frame等。例如,將3個CheckBox(複選框)元件放置在一個GroupBox元件裡,這個GroupBox元件就是這3個複選框的容器,移動這個GroupBox就會同時移動其中的3個CheckBox。

佈局管理

Qt為窗體設計提供了豐富的佈局管理功能,在UI Designer裡,元件面板裡有Layouts和Spacers兩個分組,在窗體上方的工具欄裡有佈局管理的按鈕

元件面板裡Layouts和Spacers這兩個分組裡的佈局元件的功能如下表所示。

使用元件面板裡的佈局元件設計佈局時,先拖放一個佈局元件到窗體上,例如在設計上圖窗體下方的3個按鈕的佈局時,先放一個Horizontal Layout到窗體上,佈局元件會以紅色矩形框顯示。再向佈局元件裡拖放3個PushButton和兩個HorizontalSpacer,就可以得到上圖中3個按鈕的水平佈局效果。

每個佈局還有layoutTopMargin、layoutBottomMargin、layoutLeftMargin、layoutRightMargin這4個屬性用於調整佈局邊框與內部元件之間的上、下、左、右的邊距大小。

在設計窗體的上方有一個工具欄,用於使介面進入不同的設計狀態,以及進行佈局設計。

夥伴關係與Tab順序

在UI Designer工具欄上單擊“Edit Buddies”按鈕可以進入夥伴關係編輯狀態,例如設計一個窗體時,進入夥伴關係編輯狀態之後如下圖所示。

夥伴關係(Buddy)是指介面上一個Label和一個具有輸入焦點的元件相關聯,在上圖的夥伴關係編輯狀態,單擊一個Label,按住滑鼠左鍵,然後拖向一個元件,就建立了Label和元件之間的夥伴關係。

夥伴關係是為了在程式執行時,在窗體上用快捷鍵快速將輸入焦點切換到某個元件上。例如,在上圖的介面上,設定“姓名”標籤的text屬性為“姓名(&N)”,其中符號“&”用來指定快捷字元,介面上並不顯示“&”。這裡指定快捷字母為N,那麼程式執行時,如果使用者按下Alt+N,輸入焦點就會快速切換到“姓名”標籤關聯的文字框內。

元件的訊號與內建槽函式的關聯

Qt的介面元件都是從QWidget繼承而來的,都支援訊號與槽的功能。每個類都有一些內建的訊號和槽函式,例如QPushButton按鈕類常用的訊號是clicked(),在按鈕被單擊時發射此訊號。QDialog是對話方塊類,它有以下3個內建的槽函式。

· accept(),功能是關閉對話方塊,表示肯定的選擇,例如“確定”。

· reject(),功能是關閉對話方塊,表示否定的選擇,例如“取消”。

· close(),功能是關閉對話方塊。

這3個槽函式都可以關閉對話方塊,但是表示的對話方塊的返回值不同

在上圖的對話方塊上,我們希望將“確定”按鈕與對話方塊的accept()槽函式關聯,將“退出”按鈕與對話方塊的close()槽函式關聯。

可以在UI Designer裡使用視覺化的方式實現訊號與槽函式的關聯。在UI Designer裡單擊上方工具欄裡的“EditSignals/Slots”按鈕,窗體進入訊號與槽函式編輯狀態,

訊號與槽的關聯器

PyQt5 GUI專案程式框架

窗體介面定義檔案ui_QtApp.py

在完成上一步的窗體視覺化設計後,就可以將窗體檔案Dialog.ui編譯轉換為相應的Python類定義檔案,並編寫PyQt5GUI應用程式,測試程式執行效果。

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'QtApp.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(400, 298)
        self.groupBox = QtWidgets.QGroupBox(Dialog)
        self.groupBox.setGeometry(QtCore.QRect(10, 69, 371, 41))
        self.groupBox.setTitle("")
        self.groupBox.setObjectName("groupBox")
        self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.groupBox)
        self.horizontalLayout_3.setObjectName("horizontalLayout_3")
        self.radioButton = QtWidgets.QRadioButton(self.groupBox)
        self.radioButton.setObjectName("radioButton")
        self.horizontalLayout_3.addWidget(self.radioButton)
        self.radioButton_2 = QtWidgets.QRadioButton(self.groupBox)
        self.radioButton_2.setObjectName("radioButton_2")
        self.horizontalLayout_3.addWidget(self.radioButton_2)
        self.radioButton_3 = QtWidgets.QRadioButton(self.groupBox)
        self.radioButton_3.setObjectName("radioButton_3")
        self.horizontalLayout_3.addWidget(self.radioButton_3)
        self.groupBox_2 = QtWidgets.QGroupBox(Dialog)
        self.groupBox_2.setGeometry(QtCore.QRect(10, 10, 371, 51))
        self.groupBox_2.setTitle("")
        self.groupBox_2.setObjectName("groupBox_2")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.groupBox_2)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.checkBox = QtWidgets.QCheckBox(self.groupBox_2)
        self.checkBox.setObjectName("checkBox")
        self.horizontalLayout_2.addWidget(self.checkBox)
        self.checkBox_2 = QtWidgets.QCheckBox(self.groupBox_2)
        self.checkBox_2.setObjectName("checkBox_2")
        self.horizontalLayout_2.addWidget(self.checkBox_2)
        self.checkBox_3 = QtWidgets.QCheckBox(self.groupBox_2)
        self.checkBox_3.setObjectName("checkBox_3")
        self.horizontalLayout_2.addWidget(self.checkBox_3)
        self.plainTextEdit = QtWidgets.QPlainTextEdit(Dialog)
        self.plainTextEdit.setGeometry(QtCore.QRect(10, 120, 371, 101))
        self.plainTextEdit.setTabStopWidth(80)
        self.plainTextEdit.setBackgroundVisible(False)
        self.plainTextEdit.setCenterOnScroll(True)
        self.plainTextEdit.setObjectName("plainTextEdit")
        self.groupBox_3 = QtWidgets.QGroupBox(Dialog)
        self.groupBox_3.setGeometry(QtCore.QRect(10, 240, 371, 43))
        self.groupBox_3.setTitle("")
        self.groupBox_3.setObjectName("groupBox_3")
        self.pushButton = QtWidgets.QPushButton(self.groupBox_3)
        self.pushButton.setGeometry(QtCore.QRect(10, 10, 75, 23))
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(self.groupBox_3)
        self.pushButton_2.setGeometry(QtCore.QRect(140, 10, 75, 23))
        self.pushButton_2.setObjectName("pushButton_2")
        self.pushButton_3 = QtWidgets.QPushButton(self.groupBox_3)
        self.pushButton_3.setGeometry(QtCore.QRect(280, 10, 75, 23))
        self.pushButton_3.setObjectName("pushButton_3")

        self.retranslateUi(Dialog)
        self.pushButton_2.clicked.connect(Dialog.accept)
        self.pushButton_3.clicked.connect(Dialog.close)
        QtCore.QMetaObject.connectSlotsByName(Dialog)
        Dialog.setTabOrder(self.checkBox, self.checkBox_2)
        Dialog.setTabOrder(self.checkBox_2, self.checkBox_3)
        Dialog.setTabOrder(self.checkBox_3, self.radioButton)
        Dialog.setTabOrder(self.radioButton, self.radioButton_2)
        Dialog.setTabOrder(self.radioButton_2, self.radioButton_3)
        Dialog.setTabOrder(self.radioButton_3, self.plainTextEdit)
        Dialog.setTabOrder(self.plainTextEdit, self.pushButton)
        Dialog.setTabOrder(self.pushButton, self.pushButton_2)
        Dialog.setTabOrder(self.pushButton_2, self.pushButton_3)

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "訊號與槽"))
        self.radioButton.setText(_translate("Dialog", "Black"))
        self.radioButton_2.setText(_translate("Dialog", "Red"))
        self.radioButton_3.setText(_translate("Dialog", "Blue"))
        self.checkBox.setText(_translate("Dialog", "Underline"))
        self.checkBox_2.setText(_translate("Dialog", "Bold"))
        self.checkBox_3.setText(_translate("Dialog", "Italic"))
        self.plainTextEdit.setPlainText(_translate("Dialog", "PyQt5程式設計指南\n"
"Python和Qt."))
        self.pushButton.setText(_translate("Dialog", "清空"))
        self.pushButton_2.setText(_translate("Dialog", "確定"))
        self.pushButton_3.setText(_translate("Dialog", "退出"))

窗體業務邏輯類檔案myDialog.py

按照上節介紹的介面與業務邏輯分離且介面獨立封裝的方式定義一個類QmyDialog,並儲存為檔案myDialog.py。檔案程式碼如下:

##與Ui視窗類對應的業務邏輯類
import sys
from PyQt5.QtWidgets import  QDialog,QApplication
from ui_QtApp import Ui_Dialog

class QmyDialog(QDialog):
    def __init__(self, parent=None) :
        super().__init__(parent)  #呼叫父類建構函式,建立QWidget窗體
        self.__ui=Ui_Dialog() #建立UI物件
        self.__ui.setupUi(self) #構造UI

if __name__ == "__main__": #用於當前窗體測試
    app = QApplication(sys.argv) # 建立app,用QApplication類
    myWidget = QmyDialog() #建立窗體
    myWidget.show()
    sys.exit(app.exec_())

這個檔案有窗體測試程式,執行此檔案時,就會執行檔案後面部分的程式,其功能是建立應用程式和窗體,並執行應用程式。

執行結果如下:

現在執行程式myDialog.py就會出現所設計的窗體,點選窗體上的“確定”和“退出”按鈕可以關閉窗體並退出程式,說明這兩個按鈕的功能實現了。這是因為在QmyDialog類的建構函式中,建立了窗體類的例項物件self.ui,並呼叫了其setupUi()函式,即下面這兩行語句:

self.__ui=Ui_Dialog() #建立UI物件
self.__ui.setupUi(self) #構造UI

而Ui_Dialog的setupUi()函式實現了這兩個按鈕的訊號與窗體相關槽函式的關聯,所以點選按鈕的操作起作用了。

應用程式主程式檔案appMain.py

程式myDialog.py可以當作主程式直接執行,但是建議單獨編寫一個主程式檔案appMain.py,此檔案的程式碼如下:

## GUI應用程式主程式
import sys
from PyQt5.QtWidgets import  QApplication
from myDialog import  QmyDialog

app = QApplication(sys.argv) # 建立app,用QApplication類
myWidget = QmyDialog() #建立窗體
myWidget.show()
sys.exit(app.exec_())

依然能顯示出窗體

appMain.py的功能是建立應用程式和主窗體,然後顯示主窗體,並開始執行應用程式。它將myDialog.py檔案的測試執行部分單獨拿出來作為一個檔案。當一個應用程式有多個窗體,並且窗體之間有資料傳遞時,appMain.py負責建立應用程式的主窗體並執行起來,這樣使整個應用程式的結構更清晰。

為元件的內建訊號編寫槽函式

自動關聯的槽函式

下面為窗體上的“清空”按鈕編寫槽函式,首先要找到應該使用該按鈕的那個訊號。

在myDialog.py檔案中加入以下程式碼,為清空按鈕繫結一個函式

self.ui.btnClear.clicked.connect(self.clear_text)

如下所示:

其次在myDialog類中編寫clear_text()函式

def clear_text(self):
    self.__ui.plainTextEdit.clear()

這樣清空按鈕的功能就實現了。

那麼整體的myDialog,py檔案程式碼如下:

##與Ui視窗類對應的業務邏輯類
import sys
from PyQt5.QtWidgets import  QDialog,QApplication
from ui_QtApp import Ui_Dialog

class QmyDialog(QDialog):
    def __init__(self, parent=None) :
        super().__init__(parent)  #呼叫父類建構函式,建立QWidget窗體
        self.__ui=Ui_Dialog() #建立UI物件
        self.__ui.setupUi(self) #構造UI
        self.__ui.pushButton.clicked.connect(self.clear_text)

    def clear_text(self):
        self.__ui.plainTextEdit.clear()

if __name__ == "__main__": #用於當前窗體測試
    app = QApplication(sys.argv) # 建立app,用QApplication類
    myWidget = QmyDialog() #建立窗體
    myWidget.show()
    sys.exit(app.exec_())

這時候 點選清空按鈕

同樣,在UI Designer裡視覺化設計窗體時,選中“Bold”複選框,新增按鈕引數

##與Ui視窗類對應的業務邏輯類
import sys
from PyQt5.QtWidgets import  QDialog,QApplication
from ui_QtApp import Ui_Dialog

class QmyDialog(QDialog):
    def __init__(self, parent=None) :
        super().__init__(parent)  #呼叫父類建構函式,建立QWidget窗體
        self.__ui=Ui_Dialog() #建立UI物件
        self.__ui.setupUi(self) #構造UI
        self.__ui.pushButton.clicked.connect(self.clear_text)
        self.__ui.checkBox_2.toggled.connect(self.on_chkBoxBold_toggled)

    def clear_text(self):
        self.__ui.plainTextEdit.clear()

    def on_chkBoxBold_toggled(self):
        checked=self.__ui.checkBox_2.isChecked() #讀取勾選狀態
        font=self.__ui.plainTextEdit.font()
        font.setBold(checked)
        self.__ui.plainTextEdit.setFont(font)

if __name__ == "__main__": #用於當前窗體測試
    app = QApplication(sys.argv) # 建立app,用QApplication類
    myWidget = QmyDialog() #建立窗體
    myWidget.show()
    sys.exit(app.exec_())

點選Bold之後 效果如下:

同樣,在窗體視覺化設計時,選中“Underline”複選框

在對話方塊裡不選擇toggled(bool)訊號,而是選擇clicked()訊號。而且要注意,還有一個帶引數的clicked(bool)訊號,它會將點選複選框時的勾選狀態當作一個引數傳遞給槽函式。

程式碼如下:

##與Ui視窗類對應的業務邏輯類
import sys
from PyQt5.QtWidgets import  QDialog,QApplication
from ui_QtApp import Ui_Dialog

class QmyDialog(QDialog):
    def __init__(self, parent=None) :
        super().__init__(parent)  #呼叫父類建構函式,建立QWidget窗體
        self.__ui=Ui_Dialog() #建立UI物件
        self.__ui.setupUi(self) #構造UI
        self.__ui.pushButton.clicked.connect(self.clear_text)
        self.__ui.checkBox_2.toggled.connect(self.on_chkBoxBold_toggled)
        self.__ui.checkBox.clicked.connect(self.on_chkBoxUnder_clicked)

    def clear_text(self):
        self.__ui.plainTextEdit.clear()

    def on_chkBoxBold_toggled(self):
        checked=self.__ui.checkBox_2.isChecked() #讀取勾選狀態
        font=self.__ui.plainTextEdit.font()
        font.setBold(checked)
        self.__ui.plainTextEdit.setFont(font)
    def on_chkBoxUnder_clicked(self):
        checked=self.__ui.checkBox.isChecked()
        font = self.__ui.plainTextEdit.font()
        font.setUnderline(checked)
        self.__ui.plainTextEdit.setFont(font)


if __name__ == "__main__": #用於當前窗體測試
    app = QApplication(sys.argv) # 建立app,用QApplication類
    myWidget = QmyDialog() #建立窗體
    myWidget.show()
    sys.exit(app.exec_())

點選Underline之後 效果如下:

overload型訊號的處理

在QCheckBox類元件的Go to slot對話方塊中,有兩個名稱為clicked的訊號,一個是不帶引數的clicked()訊號,“Underline”複選框使用這個訊號生成槽函式是可以自動關聯的;另一個是帶引數的clicked(bool)訊號,它將複選框的當前勾選狀態作為引數傳遞給槽函式。這種名稱相同但引數個數或型別不同的訊號就是overload型訊號。

對於窗體上的“Italic”複選框,在其Go to slot對話方塊中選擇clicked(bool)訊號生成槽函式原型,用相應的函式名在QmyDialog類中定義一個函式,程式碼如下:

    def on_chkBoxItalic_clicked(self):
        checked=self.__ui.checkBox_3.isChecked()
        font=self.__ui.plainTextEdit.font()
        font.setItalic(checked)
        self.__ui.plainTextEdit.setFont(font)

總程式碼如下:

##與Ui視窗類對應的業務邏輯類
import sys
from PyQt5.QtWidgets import  QDialog,QApplication
from ui_QtApp import Ui_Dialog

class QmyDialog(QDialog):
    def __init__(self, parent=None) :
        super().__init__(parent)  #呼叫父類建構函式,建立QWidget窗體
        self.__ui=Ui_Dialog() #建立UI物件
        self.__ui.setupUi(self) #構造UI
        self.__ui.pushButton.clicked.connect(self.clear_text)
        self.__ui.checkBox_2.toggled.connect(self.on_chkBoxBold_toggled)
        self.__ui.checkBox.clicked.connect(self.on_chkBoxUnder_clicked)
        self.__ui.checkBox_3.clicked.connect(self.on_chkBoxItalic_clicked)

    def clear_text(self):
        self.__ui.plainTextEdit.clear()

    def on_chkBoxBold_toggled(self):
        checked=self.__ui.checkBox_2.isChecked() #讀取勾選狀態
        font=self.__ui.plainTextEdit.font()
        font.setBold(checked)
        self.__ui.plainTextEdit.setFont(font)
    def on_chkBoxUnder_clicked(self):
        checked=self.__ui.checkBox.isChecked()
        font = self.__ui.plainTextEdit.font()
        font.setUnderline(checked)
        self.__ui.plainTextEdit.setFont(font)

    def on_chkBoxItalic_clicked(self):
        checked=self.__ui.checkBox_3.isChecked()
        font=self.__ui.plainTextEdit.font()
        font.setItalic(checked)
        self.__ui.plainTextEdit.setFont(font)

if __name__ == "__main__": #用於當前窗體測試
    app = QApplication(sys.argv) # 建立app,用QApplication類
    myWidget = QmyDialog() #建立窗體
    myWidget.show()
    sys.exit(app.exec_())

點選之後Italic效果如下:

手動關聯訊號與槽函式

很多情況下也需要手工編寫程式碼進行訊號與槽的關聯,例如在上圖的窗體上,希望將設定顏色的3個RadioButton按鈕的clicked()訊號與同一個槽函式關聯。

在QmyDialog類裡定義一個新的函式do_setTextColor(),並且在建構函式裡進行關聯,新增這些功能後的myDialog.py的完整程式碼如下:

##與Ui視窗類對應的業務邏輯類
import sys
from PyQt5.QtWidgets import  QDialog,QApplication
from ui_QtApp import Ui_Dialog
from PyQt5.QtGui import QPalette
from PyQt5.QtCore import Qt,pyqtSlot

class QmyDialog(QDialog):
    def __init__(self, parent=None) :
        super().__init__(parent)  #呼叫父類建構函式,建立QWidget窗體
        self.__ui=Ui_Dialog() #建立UI物件
        self.__ui.setupUi(self) #構造UI
        self.__ui.pushButton.clicked.connect(self.clear_text)
        self.__ui.checkBox_2.toggled.connect(self.on_chkBoxBold_toggled)
        self.__ui.checkBox.clicked.connect(self.on_chkBoxUnder_clicked)
        self.__ui.checkBox_3.clicked.connect(self.on_chkBoxItalic_clicked)
        self.__ui.radioButton.clicked.connect(self.do_setTextColor)
        self.__ui.radioButton_2.clicked.connect(self.do_setTextColor)
        self.__ui.radioButton_3.clicked.connect(self.do_setTextColor)


    def clear_text(self):
        self.__ui.plainTextEdit.clear()

    def on_chkBoxBold_toggled(self):
        checked=self.__ui.checkBox_2.isChecked() #讀取勾選狀態
        font=self.__ui.plainTextEdit.font()
        font.setBold(checked)
        self.__ui.plainTextEdit.setFont(font)
    def on_chkBoxUnder_clicked(self):
        checked=self.__ui.checkBox.isChecked()
        font = self.__ui.plainTextEdit.font()
        font.setUnderline(checked)
        self.__ui.plainTextEdit.setFont(font)

    def on_chkBoxItalic_clicked(self):
        checked=self.__ui.checkBox_3.isChecked()
        font=self.__ui.plainTextEdit.font()
        font.setItalic(checked)
        self.__ui.plainTextEdit.setFont(font)

    def do_setTextColor(self):
        plet=self.__ui.plainTextEdit.palette() #獲取palette
        if(self.__ui.radioButton.isChecked()):
            plet.setColor(QPalette.Text,Qt.black)
        elif(self.__ui.radioButton_2.isChecked()):
            plet.setColor(QPalette.Text,Qt.red)
        elif(self.__ui.radioButton_3.isChecked()):
            plet.setColor(QPalette.Text,Qt.blue)
        self.__ui.plainTextEdit.setPalette(plet)

if __name__ == "__main__": #用於當前窗體測試
    app = QApplication(sys.argv) # 建立app,用QApplication類
    myWidget = QmyDialog() #建立窗體
    myWidget.show()
    sys.exit(app.exec_())

效果如下所示:

程式碼裡用到了QPalette、Qt、pyqtSlot等類或函式,所以需要用import語句從相應的模組匯入。

提示 為了與connectSlotsByName()自動關聯的槽函式區別,本章中自定義槽函式的函式名一律使用“do_”作為字首。當然,這只是個人習慣的命名規則。

現在執行程式myDialog.py,整個窗體的所有功能都實現了。