PyQt4信號與槽詳解

GUI 的程序開發人員並不是須要甚至根本不須要知道全部的控件實現的底層細節,咱們只須要知道當按鈕按下時可以適當的相應便可。基於這一緣由,Qt 和 pyqt 提供了兩種通訊機制:低級事件處理機制和高級事件處理機制,前者與其餘 GUI 庫提供的功能相似,或者被稱之爲 「信號與槽」。python

QT 的一個關鍵特性是它使用信號和槽來進行對象之間的通信。當一個組件發出一個信號時,一個可用的插槽應應該作出相應。若是一個信號鏈接到一個插槽,那麼當信號被髮射時,該插槽應該被調用。若是信號沒有鏈接,則不會發生任何事情。微信

信號與槽機制的特色

  • 信號可能鏈接到不少插槽
  • 信號也可能鏈接到另一個信號
  • 信號參數能夠是任何 python 類型
  • 一個插槽可能鏈接到許多信號
  • 鏈接能夠是直接的 (同步) 也但是是排隊的 (異步)
  • 鏈接能夠跨線程
  • 鏈接可能中斷

綁定和未綁定信號

信號(特別是非綁定信號)是做爲 QObject 子類的類的屬性。一個綁定信號具備connect(),disconnect()和emit()實現相關聯的功能的方法。要截取一個信號必須把它鏈接到槽上。app

鏈接信號與槽的語法形式

QtCore.QObject.connect(a, QtCore.SIGNAL('QtSig()'), pyFunction)
QtCore.QObject.connect(a, QtCore.SIGNAL('QtSig()'), pyClass.pyMethod)
QtCore.QObject.connect(a, QtCore.SIGNAL('QtSig()'), b, QtCore.SLOT('QtSlot()'))
QtCore.QObject.connect(a, QtCore.SIGNAL('PySig()'), b, QtCore.SLOT('QtSlot()'))
QtCore.QObject.connect(a, QtCore.SIGNAL('PySig'), pyFunction)
複製代碼

以上語法能夠理解爲:鏈接 a 對象的信號到槽函數或者某個 b 對象的某個函數。異步

發射信號與槽的語法

a.emit(QtCore.SIGNAL('clicked()'))
a.emit(QtCore.SIGNAL('pySig'), "Hello", "World")
複製代碼

發射一個信號,也能夠帶參數。函數

內置的信號與槽

大部分的窗口控件都提早預置了一些槽,因此不少時候能夠直接把預置的信號和預置的槽相鏈接,無需作任何事情就能夠獲得想要的行爲效果。ui

下面咱們看兩個簡單的窗口部件 Dial 和 SpinBox。這兩個窗口部件都有 valueChange() 信號,當這個信號觸發時就會帶有新值。這兩個窗口部件也都有 setValue() 槽,帶有整數型參數。所以能夠將兩個部件的信號和槽鏈接起來,不管用戶改變哪個窗口部件,都會讓另外一個值作出改變。this

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

# Form implementation generated from reading ui file 'D:\pyqtSingAndSloft\SingSloftDialog.ui'
#
# Created by: PyQt4 UI code generator 4.11.4
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.UnicodeUTF8
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName(_fromUtf8("Dialog"))
        Dialog.resize(400, 300)
        Dialog.setSizeGripEnabled(True)
        self.dial = QtGui.QDial(Dialog)
        self.dial.setGeometry(QtCore.QRect(60, 100, 50, 64))
        self.dial.setObjectName(_fromUtf8("dial"))
        self.spinBox = QtGui.QSpinBox(Dialog)
        self.spinBox.setGeometry(QtCore.QRect(190, 120, 54, 25))
        self.spinBox.setObjectName(_fromUtf8("spinBox"))

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        Dialog.setWindowTitle(_translate("Dialog", "Dialog", None))


if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    Dialog = QtGui.QDialog()
    ui = Ui_Dialog()
    ui.setupUi(Dialog)
    Dialog.show()
    sys.exit(app.exec_())


複製代碼

繼承上面的窗口來編寫咱們的信號與槽:spa

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

""" Module implementing Dialog. """
from PyQt4 import QtGui
from PyQt4.QtGui import *
from PyQt4.QtCore import *

from Ui_SingSloftDialog import Ui_Dialog

class Dialog(QDialog, Ui_Dialog):
    """ Class documentation goes here. """
    def __init__(self, parent=None):
        """ Constructor @param parent reference to the parent widget @type QWidget """
        QDialog.__init__(self, parent)

        self.setupUi(self)
        # 鏈接信號與槽
        self.connect(self.dial, SIGNAL('valueChanged(int)'), self.spinBox.setValue)
        self.connect(self.spinBox, SIGNAL('valueChanged(int)'), self.dial.setValue)

if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    ui = Dialog()
    ui.show()
    sys.exit(app.exec_())
複製代碼

運行結果:線程

若是用戶拖動撥號盤爲 20,此時撥號盤就會發射一個 valueChange(20) 的信號,相應的就會對輸入框的 setValue() 槽進行調整,並將 20 做爲參數傳遞進去。相反輸入框的值發生改變撥號盤的槽函數也會觸發。貌似會發生死循環,其實不用擔憂,若是傳遞額值並未發生改變 valueChange() 就不會再次發射信號。code

第二種寫法

在上邊的撥號盤例子中咱們用的是 instance.metnodName() 的語法。可是當槽函數其實是個 Qt 槽而不是 Python 方法時,用 SLOT() 語法可能會更高效。

self.connect(dial,SIGNAL("valueChanged(int)"),spinbox,SLOT("setValue(int)"))
self.connect(spinbox,SIGNAL("valueChanged(int)"),dial,SLOT("setValue(int)"))
複製代碼

使用 QObject.connect() 能夠創建各種鏈接,也可使用 QObject.disconnect() 來取消這些鏈接。實際應用中咱們並不須要本身去取消這些鏈接,這是由於,當刪除一個對象後,PyQt 就會自動斷開改對象的全部鏈接。

發射自定義信號的組件

咱們已經知道如何鏈接信號與槽函數,這些槽就是一些常規的函數或者方法。可是若是咱們想建立一個能夠發射自定義信號的組件該怎麼辦呢?使用 QObject.emit() 就能夠輕鬆的實現這一點。

class ZeroSpinBox(QSpinBox):
    zeros =0
    def __init__(self, parent=None):
        super(ZeroSpinBox, self).__init__(parent)
        # 首先是把 valueChange 信號鏈接到 checkzeor() 函數
        self.connect(self, SIGNAL('valueChanged(int)'),self.checkzeor )
    def checkzeor(self):
        if self.value() ==0:
            self.zeros += 1
            # 發射一個名爲 amout 的信號,而且帶上參數
            self.emit(SIGNAL('amount'), self.zeros)
複製代碼

繼續修改剛纔的例子:

class Dialog(QDialog, Ui_Dialog):
    """ Class documentation goes here. """
    def __init__(self, parent=None):
        """ Constructor @param parent reference to the parent widget @type QWidget """
        QDialog.__init__(self, parent)
        # 初始化剛剛自定義的控件
        zerospinbox = ZeroSpinBox()
        layout = QHBoxLayout()
        layout.addWidget(zerospinbox)
        self.setLayout(layout)
        self.setupUi(self)
        self.connect(self.dial, SIGNAL('valueChanged(int)'), self.spinBox.setValue)
        self.connect(self.spinBox, SIGNAL('valueChanged(int)'), self.dial.setValue)
        # 把控件裏邊定義的 amount 信號綁定到 announce 函數
        self.connect(zerospinbox, SIGNAL('amount'), self.announce)

    def announce(self, zeros):
        print '等於0的次數' + str(zeros)


if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    ui = Dialog()
    ui.show()
    sys.exit(app.exec_())
複製代碼

執行結果:

不一樣信號鏈接到同一個槽上

咱們以前的例子把不一樣的信號鏈接到同一個槽上,也不關心是誰發射了這個信號。有時候咱們須要將兩個或者更多的信號鏈接到同一個槽上,並須要根據鏈接的不一樣信號作出不一樣的反應。

如圖咱們有 5 個按鈕和 1 個標籤:

定義不一樣的槽

先說最簡單的鏈接,這個鏈接用在 button1 中。下面是 button1 的鏈接方式:

self.connect(button1, SIGNAL("clicked()"), self.one)
複製代碼

咱們定義一個 one() 方法去改變標籤的值:

def one(self):
        self.label.setText("You clicked button 'One'")
複製代碼

使用 Python2.5 以後的高階函數

高階函數除了能夠接受函數做爲參數外,還能夠把函數做爲結果值返回。

def partial(func, arg):
        def callme():
            return func(arg)
        return callme
複製代碼

使用這個高階函數做爲槽函數:

self.button2callback = partial(self.anyButton, "Two")
self.connect(button2, SIGNAL("clicked()"),self.button2callback)
複製代碼

在咱們的定義函數裏邊改變標籤的值:

def anyButton(self, who):
        self.label.setText("You clicked button '%s'" % who)
複製代碼

鏈接到同一個槽函數

若是將不一樣的槽鏈接到同一個槽函數咱們使用 self.sender() 來發現信號是來自哪一個 QObject 對象。

綁定信號:

self.connect(button4, SIGNAL("clicked()"), self.clicked)
self.connect(button5, SIGNAL("clicked()"), self.clicked)
複製代碼

定義函數:

def clicked(self):
        button = self.sender()
        if button is None or not isinstance(button, QPushButton):
            return
        self.label.setText("You clicked button '%s'" % button.text())
複製代碼

總結

PyQt 信號與槽的機制須要好好的理解,更重要的是須要大量的實踐才能理解。

關注微信公衆號,獲取更多即時資訊

相關文章
相關標籤/搜索