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 信号与槽的机制须要好好的理解,更重要的是须要大量的实践才能理解。

关注微信公众号,获取更多即时资讯

相关文章
相关标签/搜索