diff options
Diffstat (limited to 'sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst')
-rw-r--r-- | sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst | 214 |
1 files changed, 122 insertions, 92 deletions
diff --git a/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst b/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst index 470b4ab70..0bfd9e276 100644 --- a/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst +++ b/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst @@ -43,101 +43,104 @@ a signal directly to another signal. (This will emit the second signal immediately whenever the first is emitted.) Qt's widgets have many predefined signals and slots. For example, -`QAbstractButton` (base class of buttons in Qt) has a `clicked()` -signal and `QLineEdit` (single line input field) has a slot named -'clear()`. So, a text input field with a button to clear the text -could be implemented by placing a `QToolButton` to the right of the -`QLineEdit` and connecting its `clicked()` signal to the slot -'clear()`. This is done using the `connect()` method of the signal: +``QAbstractButton`` (base class of buttons in Qt) has a ``clicked()`` +signal and ``QLineEdit`` (single line input field) has a slot named +``clear()``. So, a text input field with a button to clear the text +could be implemented by placing a ``QToolButton`` to the right of the +``QLineEdit`` and connecting its ``clicked()`` signal to the slot +``clear()``. This is done using the ``connect()`` method of the signal: - .. code-block:: python +.. code-block:: python - button = QToolButton() - line_edit = QLineEdit() - button.clicked.connect(line_edit.clear) + button = QToolButton() + line_edit = QLineEdit() + button.clicked.connect(line_edit.clear) -`connect()` returns a `QMetaObject.Connection` object, which can be -used with the `disconnect()` method to sever the connection. +``connect()`` returns a ``QMetaObject.Connection`` object, which can be +used with the ``disconnect()`` method to sever the connection. Signals can also be connected to free functions: - .. code-block:: python +.. code-block:: python - import sys - from PySide6.QtWidgets import QApplication, QPushButton + import sys + from PySide6.QtWidgets import QApplication, QPushButton - def function(): - print("The 'function' has been called!") + def function(): + print("The 'function' has been called!") - app = QApplication() - button = QPushButton("Call function") - button.clicked.connect(func) - button.show() - sys.exit(app.exec()) + app = QApplication() + button = QPushButton("Call function") + button.clicked.connect(function) + button.show() + sys.exit(app.exec()) Connections can be spelled out in code or, for widget forms, designed in the `Signal-Slot Editor <https://doc.qt.io/qt-6/designer-connection-mode.html>`_ -of Qt Designer. +of *Qt Widgets Designer*. The Signal Class ---------------- When writing classes in Python, signals are declared as class level variables of the class ``QtCore.Signal()``. A QWidget-based button -that emits a `clicked()` signal could look as +that emits a ``clicked()`` signal could look as follows: - .. code-block:: python +.. code-block:: python - from PySide6.QtCore import Qt, Signal - from PySide6.QtWidgets import QWidget + from PySide6.QtCore import Qt, Signal + from PySide6.QtWidgets import QWidget - class Button(QWidget): + class Button(QWidget): - clicked = Signal(Qt.MouseButton) + clicked = Signal(Qt.MouseButton) - ... + ... - def mousePressEvent(self, event): - self.clicked.emit(event.button()) + def mousePressEvent(self, event): + self.clicked.emit(event.button()) The constructor of ``Signal`` takes a tuple or a list of Python types and C types: - .. code-block:: python +.. code-block:: python - signal1 = Signal(int) # Python types - signal2 = Signal(QUrl) # Qt Types - signal3 = Signal(int, str, int) # more than one type - signal4 = Signal((float,), (QDate,)) # optional types + signal1 = Signal(int) # Python types + signal2 = Signal(QUrl) # Qt Types + signal3 = Signal(int, str, int) # more than one type + signal4 = Signal((float,), (QDate,)) # optional types In addition to that, it can receive also a named argument ``name`` that defines the signal name. If nothing is passed, the new signal will have the same name as the variable that it is being assigned to. - .. code-block:: python +.. code-block:: python - # TODO - signal5 = Signal(int, name='rangeChanged') - # ... - rangeChanged.emit(...) + # TODO + signal5 = Signal(int, name='rangeChanged') + # ... + rangeChanged.emit(...) Another useful option of ``Signal`` is the arguments name, useful for QML applications to refer to the emitted values by name: - .. code-block:: python +.. code-block:: python - sumResult = Signal(int, arguments=['sum']) + sumResult = Signal(int, arguments=['sum']) - .. code-block:: javascript +.. code-block:: javascript - Connections { - target: ... - function onSumResult(sum) { - // do something with 'sum' - } + Connections { + target: ... + function onSumResult(sum) { + // do something with 'sum' + } + + +.. _slot-decorator: The Slot Class -------------- @@ -146,11 +149,11 @@ Slots in QObject-derived classes should be indicated by the decorator ``@QtCore.Slot()``. Again, to define a signature just pass the types similar to the ``QtCore.Signal()`` class. - .. code-block:: python +.. code-block:: python - @Slot(str) - def slot_function(self, s): - ... + @Slot(str) + def slot_function(self, s): + ... ``Slot()`` also accepts a ``name`` and a ``result`` keyword. @@ -159,6 +162,19 @@ Python type. The ``name`` keyword behaves the same way as in ``Signal()``. If nothing is passed as name then the new slot will have the same name as the function that is being decorated. +We recommend marking all methods used by signal connections with a +``@QtCore.Slot()`` decorator. Not doing causes run-time overhead due to the +method being added to the ``QMetaObject`` when creating the connection. This is +particularly important for ``QObject`` classes registered with QML, where +missing decorators can introduce bugs. + +Missing decorators can be diagnosed by setting activating warnings of the +logging category ``qt.pyside.libpyside``; for example by setting the +environment variable: + +.. code-block:: bash + + export QT_LOGGING_RULES="qt.pyside.libpyside.warning=true" .. _overloading-signals-and-slots: @@ -172,62 +188,76 @@ In Qt 6, signals have distinct names for different types. The following example uses two handlers for a Signal and a Slot to showcase the different functionality. - .. code-block:: python +.. code-block:: python - import sys - from PySide6.QtWidgets import QApplication, QPushButton - from PySide6.QtCore import QObject, Signal, Slot + import sys + from PySide6.QtWidgets import QApplication, QPushButton + from PySide6.QtCore import QObject, Signal, Slot - class Communicate(QObject): - # create two new signals on the fly: one will handle - # int type, the other will handle strings - speak = Signal((int,), (str,)) + class Communicate(QObject): + # create two new signals on the fly: one will handle + # int type, the other will handle strings + speak = Signal((int,), (str,)) - def __init__(self, parent=None): - super().__init__(self, parent) + def __init__(self, parent=None): + super().__init__(parent) - self.speak[int].connect(self.say_something) - self.speak[str].connect(self.say_something) + self.speak[int].connect(self.say_something) + self.speak[str].connect(self.say_something) - # define a new slot that receives a C 'int' or a 'str' - # and has 'say_something' as its name - @Slot(int) - @Slot(str) - def say_something(self, arg): - if isinstance(arg, int): - print("This is a number:", arg) - elif isinstance(arg, str): - print("This is a string:", arg) + # define a new slot that receives a C 'int' or a 'str' + # and has 'say_something' as its name + @Slot(int) + @Slot(str) + def say_something(self, arg): + if isinstance(arg, int): + print("This is a number:", arg) + elif isinstance(arg, str): + print("This is a string:", arg) + + if __name__ == "__main__": + app = QApplication(sys.argv) + someone = Communicate() - if __name__ == "__main__": - app = QApplication(sys.argv) - someone = Communicate() + # emit 'speak' signal with different arguments. + # we have to specify the str as int is the default + someone.speak.emit(10) + someone.speak[str].emit("Hello everybody!") - # emit 'speak' signal with different arguments. - # we have to specify the str as int is the default - someone.speak.emit(10) - someone.speak[str].emit("Hello everybody!") +.. _signals-and-slots-strings: Specifying Signals and Slots by Method Signature Strings -------------------------------------------------------- Signals and slots can also be specified as C++ method signature -strings passed through the `SIGNAL()` and/or `SLOT()` functions: +strings passed through the ``SIGNAL()`` and/or ``SLOT()`` functions: + +.. code-block:: python + + from PySide6.QtCore import SIGNAL, SLOT + + button.connect(SIGNAL("clicked(Qt::MouseButton)"), + action_handler, SLOT("action1(Qt::MouseButton)")) + +This is not normally recommended; it is only needed +for a few cases where signals are only accessible via ``QMetaObject`` +(``QAxObject``, ``QAxWidget``, ``QDBusInterface`` or ``QWizardPage::registerField()``): - .. code-block:: python +.. code-block:: python - from PySide6.QtCore import SIGNAL, SLOT + wizard.registerField("text", line_edit, "text", + SIGNAL("textChanged(QString)")) - button.connect(SIGNAL("clicked(Qt::MouseButton)"), - action_handler, SLOT("action1(Qt::MouseButton)")) +The signature strings can be found by querying ``QMetaMethod.methodSignature()`` +when introspecting ``QMetaObject``: -This is not recommended for connecting signals, it is mostly -used to specify signals for methods like `QWizardPage::registerField()`: +.. code-block:: python - .. code-block:: python + mo = widget.metaObject() + for m in range(mo.methodOffset(), mo.methodCount()): + print(mo.method(m).methodSignature()) - wizard.registerField("text", line_edit, "text", - SIGNAL("textChanged(QString)")) +Slots should be decorated using :ref:`@Slot <slot-decorator>`. |