Hello PyQt5PyQt5 signal slot mechanism

Posted by ppatwari on Thu, 24 Feb 2022 17:28:31 +0100

1, Introduction to signal slot mechanism

1. Introduction to signal slot

Signal slot is the core mechanism of Qt and the communication mechanism of objects in PyQt programming. In Qt, QObject object and all controls inherited from QWidget in PyQt support signal slot mechanism. When the signal is transmitted, the connected slot function will be executed automatically. In PyQt5, the signal and slot function pass through object signal. Connect () method.

The characteristics of signal slot are as follows:

A. One signal can connect multiple slots

B. One signal can be connected to another

C. Signal parameters can be any Python type

D. One slot can monitor multiple signals

E. The connection mode between signal and slot can be synchronous connection or asynchronous connection.

F. The connection between signal and slot can cross threads

G. The signal can be disconnected

When writing a class, first define the signal and slot of the class, and connect the signal and slot in the class to realize the data transmission between objects. When the event or state changes, a signal will be sent, which will trigger the execution of the slot function associated with the event or signal. The mechanism of signal slot is as follows:

2. Define signal

The built-in signal of PyQt is automatically defined, using PyQt5 QtCore. pyqtSignal function can create a signal for QObject object, and use pyqtSignal function to define the signal as the attribute of the class.

class pyqtSignal:
    def __init__(self, *types, name: str = ...) -> None: ...

The types parameter indicates the type of the parameter when defining the signal. The name parameter indicates the name of the signal. The attribute name of the class is used by default.

Use pyqtSignal function to create one or more overloaded unbound signals as class properties. Signals can only be defined in subclasses of QObject. Signals must be defined at the time of class creation and cannot be added dynamically as class attributes after class creation. When the pyqtSignal function is used to define a signal, the signal can pass multiple parameters and specify the type of signal passing parameters. The parameter types are standard Python data types, including string, date, boolean type, number, list, dictionary and tuple.

from PyQt5.QtCore import pyqtSignal, QObject


class StandardItem(QObject):
    # Define the signal. The signal has two parameters. The types of the two parameters are STR and STR respectively, and the signal name is dataChanged
    data_changed = pyqtSignal(str, str, name="dataChanged")

    # Update information and send signal
    def update(self):
        self.dataChanged.emit("old status", "new status")

3. Operation signal

Use the connect function to bind the signal to the slot function, use the disconnect function to unbind the signal from the slot function, and use the emit function to transmit the signal.

QObject.signal.connect(self, slot, type=None, no_receiver_check=False)

Establish the connection between the signal and the slot function. Type is the connection type.

QObject.signal.disconnect(self, slot=None)

Disconnect the signal from the slot

emit(self, *args)

Send signal, args is a variable parameter.

import sys
from PyQt5.QtCore import pyqtSignal, QObject, QCoreApplication


class StandardItem(QObject):
    # Define the signal. The signal has two parameters. The types of the two parameters are STR and STR respectively, and the signal name is dataChanged
    data_changed = pyqtSignal(str, str, name="dataChanged")

    # Update information and send signal
    def update(self):
        self.dataChanged.emit("old status", "new status")

    # Define slot function
    def onDataChanged(self, old, new):
        print(old)
        print(new)


if __name__ == "__main__":
    app = QCoreApplication(sys.argv)

    item = StandardItem()
    item.dataChanged.connect(item.onDataChanged)
    item.update()

    sys.exit(app.exec_())


# OUTPUT:
# old status
# new status

2, Signal and slot applications

1. Built in signal and slot function

The built-in signal is the signal automatically defined by the QObject object, and the built-in slot function is the slot function automatically defined by the QObject object signal. The connect function connects the built-in signal of the QObject object to the slot function of the QObject object.

2. Built in signal and custom slot function

import sys
from PyQt5.QtWidgets import QWidget, QApplication, QPushButton


class MainWindow(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("MainWindow Demo")
        self.resize(800, 600)

        button = QPushButton("close", self)
        # Connect built-in signal and custom slot
        button.clicked.connect(self.onClose)

    # Custom slot function
    def onClose(self):
        self.close()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

When the button is clicked, the built-in clicked signal of the button is triggered to execute the bound custom slot function onClose.

3. Custom signal and built-in slot function

import sys
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QWidget, QApplication, QPushButton


class MainWindow(QWidget):
    closeSignal = pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("MainWindow Demo")
        self.resize(800, 600)

        button = QPushButton("close", self)
        # Connect built-in signal and custom slot
        button.clicked.connect(self.onClose)
        # Connect the custom signal closeSignal with the built-in slot function close
        self.closeSignal.connect(self.close)

    # Custom slot function
    def onClose(self):
        # Send custom signal
        self.closeSignal.emit()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

Connect the built-in signal clicked to the user-defined slot function onClose, send the user-defined signal closeSignal in the user-defined slot function onClose, and connect the user-defined signal closeSignal with the built-in slot function close.

4. Custom signal and custom slot function

import sys
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QWidget, QApplication, QPushButton


class MainWindow(QWidget):
    closeSignal = pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("MainWindow Demo")
        self.resize(800, 600)

        button = QPushButton("close", self)
        # Connect built-in signal and custom slot
        button.clicked.connect(self.onClicked)
        # Connect the custom signal closeSignal with the built-in slot function close
        self.closeSignal.connect(self.onClose)

    # Custom slot function
    def onClicked(self):
        # Send custom signal
        self.closeSignal.emit()

    # Custom slot function
    def onClose(self):
        self.close()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

3, Advanced application of signal slot

1. Custom signal slot

Signal objects are usually defined through class variables__ init__ Define a custom signal before the function.

class TestObject(QObject):
    # Define parameterless signals
    noParametersSignal = pyqtSignal()
    # A signal that defines a parameter of type int
    oneParameterSignal = pyqtSignal(int)
    # A signal that defines an overloaded version of a parameter. The parameter type can be int or str
    oneParameterOverloadSignal = pyqtSignal([int], [str])
    # Defines the signal of overloaded version of two parameters. The parameter type is int,str or int,int
    twoParametersOverloadSignal = pyqtSignal([int, str], [int, int])
    # Define a list parameter type signal
    oneParameterSignalList = pyqtSignal(list)
    # Define a signal of dict parameter type
    oneParameterSignalDict = pyqtSignal(dict)

The slot function definition of the class is the same as the ordinary method definition of the class.

class TestObject(QObject):
    # Define parameterless signals
    noParametersSignal = pyqtSignal()
    # A signal that defines a parameter of type int
    oneParameterSignal = pyqtSignal(int)
    # A signal that defines an overloaded version of a parameter. The parameter type can be int or str
    oneParameterOverloadSignal = pyqtSignal([int], [str])
    # Defines the signal of overloaded version of two parameters. The parameter type is int,str or int,int
    twoParametersOverloadSignal = pyqtSignal([int, str], [int, int])

    # Define a slot function without parameters
    def onNoParameterSlot(self):
        pass

    # Define a slot function with a parameter. The parameter is integer nIndex
    def onOneParameterSlot(self, nIndex):
        pass

    # Define a slot function with two parameters. The parameters are integer nIndex and string sStatus
    def onTwoParametersSlot(self, nIndex, sStatus):
        pass

Connect the signal and slot function through the connect method. The signal and slot function can belong to the same QObject object or different QObject objects.

test = TestObject()
test.noParametersSignal.connect(test.onNoParameterSlot)
test.oneParameterSignal.connect(test.onOneParameterSlot)
test.twoParametersOverloadSignal.connect(test.onTwoParametersSlot)

The signal is sent through the emit method.

def update(self):
    self.noParametersSignal.emit()
    self.oneParameterSignal.emit(100)
    self.oneParameterOverloadSignal("Hello, PyQt5")
    self.twoParametersOverloadSignal(100, "Hello, PyQt5")

2. Signal slot pass custom parameters

The number of parameters sent by the signal in Qt must be greater than or equal to the number of parameters of the slot function. PyQt uses user-defined parameter transmission to solve the problem that there are more slot function parameters than signal parameters. You can pass custom parameters to the slot function by using Lambda expression or partial function of functools. The type of custom parameters can be any type of Python.

import sys
from PyQt5.QtWidgets import QWidget, QApplication, QPushButton, QHBoxLayout
from functools import partial


class MainWindow(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        button1 = QPushButton("Button1", self)
        button2 = QPushButton("Button2", self)

        layout = QHBoxLayout()
        layout.addWidget(button1)
        layout.addWidget(button2)
        self.setLayout(layout)

        self.setWindowTitle("MainWindow Demo")
        self.resize(800, 600)

        # lambda
        button1.clicked.connect(lambda: self.onButtonClicked(1))
        button2.clicked.connect(lambda: self.onButtonClicked(2))
        # partial
        button1.clicked.connect(partial(self.onButtonClicked, 1))
        button2.clicked.connect(partial(self.onButtonClicked, 2))

    # Custom slot function
    def onButtonClicked(self, n):
       print("Button {0} is Clicked".format(n))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

3. Signal slot and decorator

In PyQt, you can define signal and slot functions through Python decorator. The usage method is as follows:

@PyQt5.QtCore.pyqtSlot(bool)
def on_Sender object name_Name of transmitted signal(self, parameter):
    pass

The sender object name is the object name set for the QObject object using setObjectName. The connectSlotsByName function connected to the slot function through the signal name is as follows:

QtCore.QMetaObject.connectSlotsByName(self, QObject)

connectSlotsByName is used to connect some signals of QObject descendant objects to the corresponding slot function of some QObject objects according to their names.

import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import QWidget, QApplication, QPushButton, QHBoxLayout


class MainWindow(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        button1 = QPushButton("Button1", self)
        button1.setObjectName("Button1")
        button2 = QPushButton("Button2", self)
        button2.setObjectName("Button2")

        layout = QHBoxLayout()
        layout.addWidget(button1)
        layout.addWidget(button2)
        self.setLayout(layout)

        self.setWindowTitle("MainWindow Demo")
        self.resize(800, 600)

        QtCore.QMetaObject.connectSlotsByName(self)


    @QtCore.pyqtSlot()
    def on_Button1_clicked(self):
        print("Button1 is clicked")

    @QtCore.pyqtSlot()
    def on_Button2_clicked(self):
        print("Button2 is clicked")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

4, Event handling mechanism

1. Difference between event mechanism and signal slot mechanism

PyQt provides high-level signal slot mechanism and low-level event processing mechanism for event processing. Signal slot mechanism is a high-level encapsulation of event processing mechanism. When using the control, you do not need to consider the event processing mechanism, but only need to care about the signal slot; For custom derived controls, we must consider the event handling mechanism and re implement the corresponding event handling function according to the behavior requirements of the control.

2. Method of event handling

PyQt provides five kinds of event processing and filtering methods:

(1) Re implement the event handler

Common event handling functions, such as paintEvent, mouseMoveEvent, mousePressEvent, mouserereleaseevent, etc.

(2) Re implement QObject Event event distribution function

When adding new events, you need to re implement QObject Event method, and add the distribution route of new events.

(3) Install event filter

If the installEventFilter method is called on a QObject object, an event filter is installed for the QObject object. All events of the QObject object will be passed to the eventFilter function of the event filter first. Some events can be discarded or modified in the eventFilter function of the event filter. The user-defined event processing mechanism is used for the events of interest, and the default event processing mechanism is used for other events. The event filtering mechanism will filter all events of QObject. Therefore, if there are many events to be filtered, the program performance will be affected.

(4) Installing event filters in QApplication

Installing the event filter on the QApplication object will filter all events of all QObject objects and get the event first. That is, before the event is sent to any other event filter, it will be sent to the event filter of QApplication first.

(5) Restart the notify method of QApplication

PyQt uses the notify method of QApplication object to distribute events. The only way to capture events before any event filter is to re implement the notify method of QApplication.

3. Event handling instance

The QDialog dialog box will automatically exit when the ESC key is pressed, and use event processing and filtering to process the pressing of ESC key.

(1) Re implement the event handler

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtCore import Qt


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

    # Re implement keyPressEvent
    def keyPressEvent(self, event):
        if event.key() != Qt.Key_Escape:
            QDialog.keyPressEvent(self, event)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    dialog = Dialog()
    dialog.exec_()

    sys.exit(app.exec_())

(2) Re implement the event function

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QKeyEvent


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

    # Re implement keyPressEvent
    def event(self, event):
        if event.type() == QKeyEvent.KeyPress and event.key() == Qt.Key_Escape:
            return True
        else:
            return QDialog.event(self, event)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    dialog = Dialog()
    dialog.exec_()

    sys.exit(app.exec_())

(3) QObject install event filter

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QKeyEvent


class Dialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.installEventFilter(self)

    def eventFilter(self, watched, event):
        if event.type() == QKeyEvent.KeyPress and event.key() == Qt.Key_Escape:
            return True
        else:
            return QDialog.eventFilter(self, watched, event)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    dialog = Dialog()
    dialog.exec_()

    sys.exit(app.exec_())

(4) QApplication install event filter

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QKeyEvent


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

    def eventFilter(self, watched, event):
        if event.type() == QKeyEvent.KeyPress and event.key() == Qt.Key_Escape:
            return True
        else:
            return QDialog.eventFilter(self, watched, event)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    dialog = Dialog()
    app.installEventFilter(dialog)
    dialog.exec_()

    sys.exit(app.exec_())

Topics: Qt PyQt5