aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst
blob: 0bfd9e2768a7fce7c6a3dff8ca7f2d05b71f7b3d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
.. _signals-and-slots:

Signals and Slots
=================

Due to the nature of Qt, ``QObject``\s require a way to communicate, and that's
the reason for this mechanism to be a **central feature of Qt**.

In simple terms, you can understand **Signal and Slots** in the same way you
interact with the lights in your house. When you move the light switch
(signal) you get a result which may be that your light bulbs are switched
on/off (slot).

While developing interfaces, you can get a real example by the effect of
clicking a button: the 'click' will be the signal, and the slot will be what
happens when that button is clicked, like closing a window, saving a document,
etc.

.. note::
    If you have experience with other frameworks or toolkits, it's likely
    that you read a concept called 'callback'. Leaving the implementation
    details aside, a callback will be related to a notification function,
    passing a pointer to a function in case it's required due to the events
    that happen in your program. This approach might sound similar, but
    there are essential differences that make it an unintuitive approach,
    like ensuring the type correctness of callback arguments, and some others.

All classes that inherit from ``QObject`` or one of its subclasses, like
``QWidget`` can contain signals and slots. **Signals are emitted by objects**
when they change their state in a way that may be interesting to other objects.
This is all the object does to communicate. It does not know or care whether
anything is receiving the signals it emits. This is true information
encapsulation, and ensures that the object can be used as a software component.

**Slots can be used for receiving signals**, but they are also normal member
functions. Just as an object does not know if anything receives its signals,
a slot does not know if it has any signals connected to it. This ensures that
truly independent components can be created with Qt.

You can connect as many signals as you want to a single slot, and a signal can
be connected to as many slots as you need. It is even possible to connect
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:

.. code-block:: python

    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.

Signals can also be connected to free functions:

.. code-block:: python

    import sys
    from PySide6.QtWidgets import QApplication, QPushButton


    def function():
        print("The 'function' has been called!")

    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 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
follows:

.. code-block:: python

    from PySide6.QtCore import Qt, Signal
    from PySide6.QtWidgets import QWidget

    class Button(QWidget):

        clicked = Signal(Qt.MouseButton)

        ...

        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

    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

    # 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

    sumResult = Signal(int, arguments=['sum'])

.. code-block:: javascript

    Connections {
        target: ...
        function onSumResult(sum) {
            // do something with 'sum'
        }


.. _slot-decorator:

The Slot Class
--------------

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

    @Slot(str)
    def slot_function(self, s):
        ...


``Slot()`` also accepts a ``name`` and a ``result`` keyword.
The ``result`` keyword defines the type that will be returned and can be a C or
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:

Overloading Signals and Slots with Different Types
--------------------------------------------------

It is actually possible to use signals and slots of the same name with different
parameter type lists. This is legacy from Qt 5 and not recommended for new code.
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

    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,))

        def __init__(self, parent=None):
            super().__init__(parent)

            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)

    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!")


.. _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:

.. 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

    wizard.registerField("text", line_edit, "text",
                         SIGNAL("textChanged(QString)"))

The signature strings can be found by querying ``QMetaMethod.methodSignature()``
when introspecting ``QMetaObject``:

.. code-block:: python

    mo = widget.metaObject()
    for m in range(mo.methodOffset(), mo.methodCount()):
        print(mo.method(m).methodSignature())

Slots should be decorated using :ref:`@Slot <slot-decorator>`.