catalogue
2.1. Re implement the event function
2.2. Re implement QObject.event()
2.4. Install event filter in QApplication
2.5. Re implement the notify() method of QApplication
Before Python Qt GUI Design: QTimer timer class, QThread multithreading class and event processing class (Basics - 8) In, we have briefly mentioned that PyQt provides two mechanisms for event processing: high-level signal and slot mechanism and low-level event handler. This blog will systematically explain the event handler class and system of Qt.
The event processing mechanism itself is very complex and is the knowledge point at the bottom of PyQt. When the signal and slot mechanism cannot be used, the event processing mechanism will be considered.
Signals and slots can be said to be high-level encapsulation of event processing mechanism. If events are used to create window controls, signals and slots are used to use this window control. For example, when we use a button, we only care about the clicked signal. As for how the button receives and processes the mouse click event, and then transmits the signal, we don't care. But if you want to reload a button, you should be concerned about this problem. For example, you can change its behavior: trigger the clicked signal when the mouse button is pressed, not when it is released.
1. Common event types
There are many types of Qt events. Common Qt events are as follows:
- Keyboard events: key press and release.
- Mouse events: mouse pointer movement, mouse button press and release.
- Drag and drop event: drag and drop with the mouse.
- Wheel event: mouse wheel scrolling.
- Draw screen event: redraws some parts of the screen.
- Timing event: the timer expires.
- Focus event: keyboard focus movement.
- Entry and exit events: move the mouse pointer into or out of the Widget.
- Move event: the location of the Widget changes.
- Size change event: the size of the Widget changes.
- Show and hide events: Widget shows and hides events.
- Window event: whether the window is the current window.
There are also some common Qt events, such as Socket events, clipboard events, font change events, layout change events, etc.
See the description document for specific event types and descriptions:

2. Event handling method
PyQt provides the following five event processing and filtering methods (from weak to strong), of which only the first two methods are used most frequently.
2.1. Re implement the event function
For example, mousePressEvent(), keyPressEvent(), paintEvent(). This is the most common event handling method.
Learn how to re implement event functions through examples. The results are as follows:



This example contains multiple event types, so it is more complex.
The first is the establishment of the class. Create two variables: text and message, and use the paintEvent function to output them to the window.
The update function is used to update the window. Because the paintEvent function will be triggered once during the window update process (paintEvent is the internal function of the window base class QWidget), in this example, the update function is equivalent to the paintEvent function.
Then re implement the window closing event and context menu event. The context menu event mainly affects the result of the message variable, and paintEvent is responsible for outputting this variable at the bottom of the window.
Drawing event is the core event of the code. Its main function is to track the information of text and message variables at all times, draw the content of text to the middle of the window, and draw the content of message to the bottom of the window (it will be cleared after 5 seconds).
And finally some click operations of mouse and keyboard.
The implementation code is as follows:
import sys from PyQt5.QtCore import (QEvent, QTimer, Qt) from PyQt5.QtWidgets import (QApplication, QMenu, QWidget) from PyQt5.QtGui import QPainter class Widget(QWidget): def __init__(self, parent=None): super(Widget, self).__init__(parent) self.justDoubleClicked = False self.key = "" self.text = "" self.message = "" self.resize(400, 300) self.move(100, 100) self.setWindowTitle("Events") QTimer.singleShot(0, self.giveHelp) # To avoid the influence of window size redrawing event, you can change the parameter 0 to 3000 (3 seconds), and then run it to understand the meaning of this line of code. def giveHelp(self): self.text = "Please click here to trigger the tracking mouse function" self.update() # Redraw the event, that is, trigger the paintEvent function. '''Re implement the shutdown event''' def closeEvent(self, event): print("Closed") '''Re implement context menu events''' def contextMenuEvent(self, event): menu = QMenu(self) oneAction = menu.addAction("&One") twoAction = menu.addAction("&Two") oneAction.triggered.connect(self.one) twoAction.triggered.connect(self.two) if not self.message: menu.addSeparator() threeAction = menu.addAction("Thre&e") threeAction.triggered.connect(self.three) menu.exec_(event.globalPos()) '''Context menu slot function''' def one(self): self.message = "Menu option One" self.update() def two(self): self.message = "Menu option Two" self.update() def three(self): self.message = "Menu option Three" self.update() '''Re implement drawing events''' def paintEvent(self, event): text = self.text i = text.find("\n\n") if i >= 0: text = text[0:i] if self.key: # If a keyboard button is triggered, record the button information in the text information. text += "\n\n You pressed: {0}".format(self.key) painter = QPainter(self) painter.setRenderHint(QPainter.TextAntialiasing) painter.drawText(self.rect(), Qt.AlignCenter, text) # Draw the content of the message text if self.message: # If the message text exists, draw the message in the center at the bottom, empty the message text and redraw it after 5 seconds. painter.drawText(self.rect(), Qt.AlignBottom | Qt.AlignHCenter, self.message) QTimer.singleShot(5000, self.clearMessage) QTimer.singleShot(5000, self.update) '''Empty slot function for message text''' def clearMessage(self): self.message = "" '''Re implement the resize window event''' def resizeEvent(self, event): self.text = "Resize window to: QSize({0}, {1})".format( event.size().width(), event.size().height()) self.update() '''Re implement the mouse release event''' def mouseReleaseEvent(self, event): # If the mouse release is double-click release, the mouse movement is not tracked # If the mouse release is click release, the status of the tracking function needs to be changed. If the tracking function is enabled, it will be tracked. If the tracking function is not enabled, it will not be tracked if self.justDoubleClicked: self.justDoubleClicked = False else: self.setMouseTracking(not self.hasMouseTracking()) # click the mouse if self.hasMouseTracking(): self.text = "Turn on mouse tracking.\n" + \ "Please move the mouse!\n" + \ "Click the mouse to turn off this function" else: self.text = "Turn off mouse tracking.\n" + \ "Click the mouse to turn on this function" self.update() '''Re implement the mouse movement event''' def mouseMoveEvent(self, event): if not self.justDoubleClicked: globalPos = self.mapToGlobal(event.pos()) # Convert window coordinates to screen coordinates self.text = """Mouse position: The window coordinates are: QPoint({0}, {1}) Screen coordinates are: QPoint({2}, {3}) """.format(event.pos().x(), event.pos().y(), globalPos.x(), globalPos.y()) self.update() '''Re implement the mouse double click event''' def mouseDoubleClickEvent(self, event): self.justDoubleClicked = True self.text = "You double clicked the mouse" self.update() '''Re implement the keyboard press event''' def keyPressEvent(self, event): self.key = "" if event.key() == Qt.Key_Home: self.key = "Home" elif event.key() == Qt.Key_End: self.key = "End" elif event.key() == Qt.Key_PageUp: if event.modifiers() & Qt.ControlModifier: self.key = "Ctrl+PageUp" else: self.key = "PageUp" elif event.key() == Qt.Key_PageDown: if event.modifiers() & Qt.ControlModifier: self.key = "Ctrl+PageDown" else: self.key = "PageDown" elif Qt.Key_A <= event.key() <= Qt.Key_Z: if event.modifiers() & Qt.ShiftModifier: self.key = "Shift+" self.key += event.text() if self.key: self.key = self.key self.update() else: QWidget.keyPressEvent(self, event) if __name__ == "__main__": app = QApplication(sys.argv) form = Widget() form.show() app.exec_()
2.2. Re implement QObject.event()
It is generally used when PyQt does not provide a handler for this event, that is, when a new event is added.
For the window, all events will be passed to the event function, which will assign events to different functions for processing according to the type of event. For example, for drawing events, event will be handed over to the paintEvent function for processing; For the mouse movement event, the event will be handed over to the mouseMoveEvent function for processing; For the keyboard press event, the event will be handed over to the keyPressEvent function.
A special case is the trigger behavior of the Tab key. The Tab key processing mechanism of the event function is to switch the focus from the position of the current window control to the position of the next window control in the Tab key order and return True instead of handing it over to the keyPressEvent function.
Therefore, we need to rewrite the processing logic of pressing the Tab key in the event function to make it no different from the ordinary key on the keyboard.
In the example of 2.1. Re implementing the event function, the following code is added to realize the redefinition:
'''Re implement other events for PyQt If the handler of this event is not provided, Tab The key is not passed to because it involves focus switching keyPressEvent,Therefore, it needs to be redefined here.''' def event(self, event): if (event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab): self.key = "stay event()Capture in Tab key" self.update() return True
The effect is as follows:

2.3. Install event filter
If installEventFilter is called for QObject, it is equivalent to installing an event filter for the QObject. For all QObject events, they will be passed to the event filter function eventFilter first. In this function, we can discard or modify these events. For example, we can use a custom event processing mechanism for the events we are interested in, Use the default event handling mechanism for other events.
Because this method will filter all QObject events calling installEventFilter, if there are many events to be filtered, the performance of the program will be reduced.
Learn how to use the event filter through examples. The results are as follows:

The key to using event filters is two steps.
Set installEventFilter for the controls to be filtered. All events of these controls will be received and processed by the eventFilter function.
In the example, this filter only processes the events of label1, and only processes its MouseButtonPress event and MouseButtonRelease event.
If you press the mouse button, the picture loaded by label1 will be scaled (half the length and half the width).
The implementation code is as follows:
# -*- coding: utf-8 -*- from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * import sys class EventFilter(QDialog): def __init__(self, parent=None): super(EventFilter, self).__init__(parent) self.setWindowTitle("Event filter") self.label1 = QLabel("Please click") self.label2 = QLabel("Please click") self.label3 = QLabel("Please click") self.LabelState = QLabel("test") self.image1 = QImage("images/cartoon1.ico") self.image2 = QImage("images/cartoon1.ico") self.image3 = QImage("images/cartoon1.ico") self.width = 600 self.height = 300 self.resize(self.width, self.height) self.label1.installEventFilter(self) self.label2.installEventFilter(self) self.label3.installEventFilter(self) mainLayout = QGridLayout(self) mainLayout.addWidget(self.label1, 500, 0) mainLayout.addWidget(self.label2, 500, 1) mainLayout.addWidget(self.label3, 500, 2) mainLayout.addWidget(self.LabelState, 600, 1) self.setLayout(mainLayout) def eventFilter(self, watched, event): if watched == self.label1: # Only the click event of label1 is filtered and its behavior is rewritten, and other events will be ignored if event.type() == QEvent.MouseButtonPress: # Here, the mouse press event is filtered and its behavior is rewritten mouseEvent = QMouseEvent(event) if mouseEvent.buttons() == Qt.LeftButton: self.LabelState.setText("Press the left mouse button") elif mouseEvent.buttons() == Qt.MidButton: self.LabelState.setText("Press the middle mouse button") elif mouseEvent.buttons() == Qt.RightButton: self.LabelState.setText("Press the right mouse button") '''Convert picture size''' transform = QTransform() transform.scale(0.5, 0.5) tmp = self.image1.transformed(transform) self.label1.setPixmap(QPixmap.fromImage(tmp)) if event.type() == QEvent.MouseButtonRelease: # Here, the mouse release event is filtered and its behavior is rewritten self.LabelState.setText("Release the mouse button") self.label1.setPixmap(QPixmap.fromImage(self.image1)) return QDialog.eventFilter(self, watched, event) # In other cases, the default event handling method will be returned. if __name__ == '__main__': app = QApplication(sys.argv) dialog = EventFilter() dialog.show() sys.exit(app.exec_())
2.4. Install event filter in QApplication
This method is more powerful than 2.3 and installing the event filter. The event filter of QApplication will capture all events of all qobjects and get the event first. That is, before sending the event to any other event filter (that is, before the third method), it will be sent to the event filter of QApplication first.
Modify the installEventFilter code of the three label controls based on the example of installing event filter in 2.3. This event processing method indeed filters all events, rather than filtering only the events of the three label controls as the third method. The effect is as follows:

The implementation code is as follows:
# -*- coding: utf-8 -*- from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * import sys class EventFilter(QDialog): def __init__(self, parent=None): super(EventFilter, self).__init__(parent) self.setWindowTitle("Event filter") self.label1 = QLabel("Please click") self.label2 = QLabel("Please click") self.label3 = QLabel("Please click") self.LabelState = QLabel("test") self.image1 = QImage("images/cartoon1.ico") self.image2 = QImage("images/cartoon1.ico") self.image3 = QImage("images/cartoon1.ico") self.width = 600 self.height = 300 self.resize(self.width, self.height) # self.label1.installEventFilter(self) # self.label2.installEventFilter(self) # self.label3.installEventFilter(self) mainLayout = QGridLayout(self) mainLayout.addWidget(self.label1, 500, 0) mainLayout.addWidget(self.label2, 500, 1) mainLayout.addWidget(self.label3, 500, 2) mainLayout.addWidget(self.LabelState, 600, 1) self.setLayout(mainLayout) def eventFilter(self, watched, event): print(type(watched)) if watched == self.label1: # Only the click event of label1 is filtered and its behavior is rewritten, and other events will be ignored if event.type() == QEvent.MouseButtonPress: # Here, the mouse press event is filtered and its behavior is rewritten mouseEvent = QMouseEvent(event) if mouseEvent.buttons() == Qt.LeftButton: self.LabelState.setText("Press the left mouse button") elif mouseEvent.buttons() == Qt.MidButton: self.LabelState.setText("Press the middle mouse button") elif mouseEvent.buttons() == Qt.RightButton: self.LabelState.setText("Press the right mouse button") '''Convert picture size''' transform = QTransform() transform.scale(0.5, 0.5) tmp = self.image1.transformed(transform) self.label1.setPixmap(QPixmap.fromImage(tmp)) if event.type() == QEvent.MouseButtonRelease: # Here, the mouse release event is filtered and its behavior is rewritten self.LabelState.setText("Release the mouse button") self.label1.setPixmap(QPixmap.fromImage(self.image1)) return QDialog.eventFilter(self, watched, event) # In other cases, the default event handling method will be returned. if __name__ == '__main__': app = QApplication(sys.argv) dialog = EventFilter() app.installEventFilter(dialog) dialog.show() sys.exit(app.exec_())
2.5. Re implement the notify() method of QApplication
PyQt uses notify() to distribute events. The only way to capture events before any event handler is to re implement notify() of QApplication. In practice, this method is only used during debugging. In practice, it is basically not used much, so it will not be repeated here.