支持信号和插槽

Qt的一个关键特性是它使用信号和槽来在对象之间进行通信。它们的使用鼓励开发可重用组件。

当有潜在的兴趣发生时,会发出一个信号。一个插槽是一个Python可调用的。如果一个信号连接到一个插槽,那么当该信号被发射时该插槽被调用。如果信号没有连接,则不会发生任何事情。发出信号的代码(或组件)不知道或在意信号是否正在使用。

信号/插槽机制具有以下特点。

  • 信号可能连接到很多插槽。
  • 信号也可能连接到另一个信号。
  • 信号参数可以是任何Python类型。
  • 一个插槽可能连接到许多信号。
  • 连接可能是直接的(即。同步)或排队(即。异步)。
  • 连接可以跨线程进行。
  • 信号可能会断开。

Unbound and Bound Signals

信号(特别是未绑定信号)是一个类属性。当一个信号被引用为该类的一个实例的属性时,PyQt5会自动将该实例绑定到该信号以创建一个绑定信号这与Python本身用于从类函数创建绑定方法的机制相同。

绑定信号具有实现相关功能的connect()disconnect()emit()方法。它还有一个信号属性,它是Qt的SIGNAL()宏返回的信号的签名。

信号可能过载。即,具有特定名称的信号可能支持多个签名。信号可以用签名索引以选择所需的信号。签名是一系列类型。类型要么是Python类型对象,要么是C++类型名(一个字符串)。The name of a C++ type is automatically normalised so that, for example, QVariant can be used instead of the non-normalised const QVariant &.

如果信号过载,那么它将有一个默认值,如果没有给出索引,它将被使用。

当一个信号被触发时,所有参数都将尽可能地转换为C++类型。如果参数没有对应的C++类型,则将其包装在一个特殊的类型中,以使它可以在Qt的元类型系统中传递,同时确保正确维护其引用计数。

Defining New Signals with pyqtSignal()

PyQt5自动为所有Qt的内置信号定义信号。 New signals can be defined as class attributes using the pyqtSignal() factory.

PyQt5.QtCore.pyqtSignal(types[, name[, revision=0[, arguments=[]]]])

创建一个或多个重载的未绑定信号作为类属性。

Parameters:
  • types - 定义信号的C++签名的类型。每种类型都可以是Python类型的对象,也可以是C++类型的名称。或者,每个可以是一个类型参数的序列。在这种情况下,每个序列定义不同信号过载的签名。第一次重载将是默认值。
  • name - 信号的名称。如果省略,则使用类属性的名称。这只能作为关键字参数给出。
  • verision - 导出到QML的信号版本。这只能作为关键字参数给出。
  • arguments - 导出到QML的信号参数的名称序列。这只能作为关键字参数给出。
Return type:

一个未绑定的信号

以下示例显示了一些新信号的定义:

from PyQt5.QtCore import QObject, pyqtSignal

class Foo(QObject):

    # This defines a signal called 'closed' that takes no arguments.
    closed = pyqtSignal()

    # This defines a signal called 'rangeChanged' that takes two
    # integer arguments.
    range_changed = pyqtSignal(int, int, name='rangeChanged')

    # This defines a signal called 'valueChanged' that has two overloads,
    # one that takes an integer argument and one that takes a QString
    # argument.  Note that because we use a string to specify the type of
    # the QString argument then this code will run under Python v2 and v3.
    valueChanged = pyqtSignal([int], ['QString'])

新信号只能在QObject的子类中定义。它们必须是类定义的一部分,并且在定义类之后不能动态添加为类属性。

以这种方式定义的新信号将自动添加到类的QMetaObject中。这意味着它们将出现在Qt Designer中,并且可以使用QMetaObject API被introspected。

当参数的Python类型没有对应的C类型时,应该小心使用重载信号。PyQt5使用相同的内部C类来表示这样的对象,因此可能会使用不同的Python签名来重载信号,这些签名是用相同的C签名实现的,并带有意想不到的结果。The following is an example of this:

class Foo(QObject):

    # This will cause problems because each has the same C++ signature.
    valueChanged = pyqtSignal([dict], [list])

Connecting, Disconnecting and Emitting Signals

使用绑定信号的connect()方法将信号连接到插槽。

connect(slot[, type=PyQt5.QtCore.Qt.AutoConnection[, no_receiver_check=False]])

Connect a signal to a slot. An exception will be raised if the connection failed.

Parameters:
  • slot - 要连接的插槽,可以是可调用的Python或其他绑定信号。
  • type – the type of the connection to make.
  • no_receiver_check – suppress the check that the underlying C++ receiver instance still exists and deliver the signal anyway.

使用绑定信号的disconnect()方法将信号与插槽断开连接。

disconnect([slot])

从信号中断开一个或多个插槽。如果插槽没有连接到信号,或者信号根本没有连接,则会发生异常。

Parameters:slot - 可断开的可选插槽,可以是可调用的Python或其他绑定信号。如果省略,则连接到信号的所有插槽都将断开连接。

使用绑定信号的emit()方法发射信号。

emit(*args)

Emit a signal.

Parameters:args - 传递给任何连接的插槽的可选参数序列。

以下代码演示了无参数的信号的定义,连接和发射:

from PyQt5.QtCore import QObject, pyqtSignal

class Foo(QObject):

    # Define a new signal called 'trigger' that has no arguments.
    trigger = pyqtSignal()

    def connect_and_emit_trigger(self):
        # Connect the trigger signal to a slot.
        self.trigger.connect(self.handle_trigger)

        # Emit the signal.
        self.trigger.emit()

    def handle_trigger(self):
        # Show that the slot has been called.

        print "trigger signal received"

以下代码演示了重载信号的连接:

from PyQt5.QtWidgets import QComboBox

class Bar(QComboBox):

    def connect_activated(self):
        # The PyQt5 documentation will define what the default overload is.
        # In this case it is the overload with the single integer argument.
        self.activated.connect(self.handle_int)

        # For non-default overloads we have to specify which we want to
        # connect.  In this case the one with the single string argument.
        # (Note that we could also explicitly specify the default if we
        # wanted to.)
        self.activated[str].connect(self.handle_string)

    def handle_int(self, index):
        print "activated signal passed integer", index

    def handle_string(self, text):
        print "activated signal passed QString", text

使用关键字参数连接信号

当创建一个对象时,或者使用pyqtConfigure()方法,也可以通过传递一个插槽作为与信号名称相对应的关键字参数来连接信号。例如,以下三个片段是等同的:

act = QAction("Action", self)
act.triggered.connect(self.on_triggered)

act = QAction("Action", self, triggered=self.on_triggered)

act = QAction("Action", self)
act.pyqtConfigure(triggered=self.on_triggered)

The pyqtSlot() Decorator

虽然PyQt5允许在连接信号时使用任何可调用的Python作为插槽,但有时需要将Python方法显式标记为Qt插槽并为其提供C签名。PyQt5提供了pyqtSlot()函数装饰器来执行此操作。

PyQt5.QtCore.pyqtSlot(types[, name[, result[, revision=0]]])

Decorate a Python method to create a Qt slot.

Parameters:
  • types - 定义插槽的C++的类型。每种类型都可以是Python类型的对象,也可以是C++类型的名称。
  • name – the name of the slot that will be seen by C++. If omitted the name of the Python method being decorated will be used. This may only be given as a keyword argument.
  • revision – the revision of the slot that is exported to QML. This may only be given as a keyword argument.
  • result – the type of the result and may be a Python type object or a string that specifies a C++ type. This may only be given as a keyword argument.

将信号连接到装饰的Python方法也具有减少使用的内存量并且速度稍快的优点。

For example:

from PyQt5.QtCore import QObject, pyqtSlot

class Foo(QObject):

    @pyqtSlot()
    def foo(self):
        """ C++: void foo() """

    @pyqtSlot(int, str)
    def foo(self, arg1, arg2):
        """ C++: void foo(int, QString) """

    @pyqtSlot(int, name='bar')
    def foo(self, arg1):
        """ C++: void bar(int) """

    @pyqtSlot(int, result=int)
    def foo(self, arg1):
        """ C++: int foo(int) """

    @pyqtSlot(int, QObject)
    def foo(self, arg1):
        """ C++: int foo(int, QObject *) """

也可以链接装饰器以便用不同的签名多次定义Python方法。For example:

from PyQt5.QtCore import QObject, pyqtSlot

class Foo(QObject):

    @pyqtSlot(int)
    @pyqtSlot('QString')
    def valueChanged(self, value):
        """ Two slots will be defined in the QMetaObject. """

PyQt_PyObject信号参数类型

通过将PyQt_PyObject指定为签名中参数的类型,可以将任何Python对象作为信号参数传递。For example:

finished = pyqtSignal('PyQt_PyObject')

这通常用于传递实际Python类型未知的对象。例如,它也可以用于传递整数,以便不需要从Python对象到C++整数的正常转换。

被传递的对象的引用计数会自动保持。在调用finished.emit()之后,即使连接排队,信号的发射器也不需要保持对该对象的引用。

Connecting Slots By Name

PyQt5支持pyuic5生成的Python代码最常用的connectSlotsByName()函数,以自动将信号连接到符合简单命名约定的插槽。但是,如果一个类的Qt信号超载(即,具有相同的名称但具有不同的参数)PyQt5需要附加信息才能自动连接正确的信号。

例如, QSpinBox类具有以下信号:

void valueChanged(int i);
void valueChanged(const QString &text);

当旋转框的值改变时,这两个信号将被发射。如果您已经实现了一个名为on_spinbox_valueChanged的插槽(假设您已将QSpinBox实例命名为spinbox),则它将连接到两个信号的变化。因此,当用户更改该值时,您的插槽将被调用两次 - 一次使用整数参数,一次使用字符串参数。

可以使用pyqtSlot()修饰器指定哪个信号应该连接到插槽。

例如,如果您只对信号的整数变体感兴趣,那么您的插槽定义将如下所示:

@pyqtSlot(int)
def on_spinbox_valueChanged(self, i):
    # i will be an integer.
    pass

如果您想要处理这两种信号变体,但使用不同的Python方法,则您的插槽定义可能如下所示:

@pyqtSlot(int, name='on_spinbox_valueChanged')
def spinbox_int_value(self, i):
    # i will be an integer.
    pass

@pyqtSlot(str, name='on_spinbox_valueChanged')
def spinbox_qstring_value(self, s):
    # s will be a Python string object (or a QString if they are enabled).
    pass