In depth application of event processing in PyQt5 advanced programming practice

Posted by chrisdarby on Wed, 05 Jan 2022 20:03:15 +0100

In depth application of event handling

  1. Re implement event()

  2. Using event filters

  3. Global event filter

  4. Keep the window responsive when processing time-consuming tasks

stay Event handling of quick grasp PyQt5 In this chapter, the author introduces some common event functions and demonstrates them through cases. However, it has not really reflected the power of PyQt5 event function. In this chapter, the author will take you to understand how to write a more personalized event function and other knowledge points related to events.

1. Re implement event()

Although PyQt5 has provided me with many built-in event functions, such as mousePressEvent(), closeEvent(), keyPressEvent(), and so on. However, there are many event types, and some event types do not provide corresponding built-in functions. For example: I want a window to respond to a mouse entry event, but I don't have mouseEnterEvent().

So at this time, we should overload event() to achieve the desired event response:

  • The e parameter contains more than 100 event types, which can meet most of our needs. You can use this parameter link View a summary list of all events.
  • Returns True if the corresponding event is captured.
  • Finally, remember to give other unhandled events to the event() of the parent class.

Now, let's use the following example to demonstrate how to make the corresponding mouse in the window enter the event:

import sys
from PyQt5.QtWidgets import QApplication, QWidget

class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()

    def event(self, e):
        if e.type() == e.Enter:
            print('The mouse enters the window')
            return True
        return super(Demo, self).event(e)
        

if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

If the event type e.type() is equal to e.Enter, we will capture the event, print the message in the console, and finally return a True. If it is other event types, it will be handled by the event() of the parent class.

The operation screenshot is as follows:

2. Use event filter

In fact, just from the name, we can guess the function of event filter - filtering out events we don't want. This powerful filter allows us to write more personalized event processing. The implementation method is very simple. Let's overload the eventFilter function first:

  • The watched parameter refers to the object for event filtering.
  • The event parameter refers to the event to be filtered.
  • Returns True if you explicitly want to filter the specified events of the specified object, otherwise False.

After the function is overloaded, we can call installEventFilter on the corresponding object. If you don't want this filter, call removeEventFilter to uninstall it.

The event filter will capture the corresponding event before the built-in event function. If the event is filtered in eventFilter, the built-in event function will no longer respond. For example, if the keyboard event of an input box is filtered in eventFilter, the keyPressEvent() event function of the input box will not respond.

Let's use a case to demonstrate the following:

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

class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.line = QLineEdit()                     # 1
        self.btn = QPushButton('Install event filter')
        self.btn.clicked.connect(self.install_remove_slot)
        v_layout = QVBoxLayout()
        v_layout.addWidget(self.line)
        v_layout.addWidget(self.btn)
        self.setLayout(v_layout)

    def install_remove_slot(self):
        if self.btn.text() == 'Install event filter':
            self.line.installEventFilter(self)
            self.btn.setText('Uninstall event filter')
        else:
            self.line.removeEventFilter(self)
            self.btn.setText('Install event filter')

    def eventFilter(self, watched, event):          # 2
        if watched == self.line and event.type() == event.KeyPress:
            print('Keyboard events are filtered')
            return True
        return super(Demo, self).eventFilter(watched, event)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

\1. Instantiate a QLineEdit input box. The control will be used as a filter demonstration object, and the button is used to install or uninstall the filter for the input box control.

\2. In the eventFilter function, we make a judgment. If the object is a previously instantiated input box and is a keyboard event, we return True to filter this event. Note that the unhandled events are returned to the eventFilter function of the base class, so the following line of code needs to be added:

return super(Demo, self).eventFilter(watched, event)

The operation screenshot is as follows:

No event filter is installed. Keyboard events can be received and text can be entered

After installing the event filter, keyboard events are filtered and no text can be entered

Uninstall the event filter, keyboard events can be received again, and text can be entered

3. Global event filter

We can separate the event filter to make it globally available:

import sys
from PyQt5.QtCore import QObject
from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QVBoxLayout
 

class KeyPressEater(QObject):                   # 1
    def __init__(self):
        super(KeyPressEater, self).__init__()
        
    def eventFilter(self, watched, event):
        if event.type() == event.KeyPress:
            print('Keyboard events are filtered')
            return True
        return super(KeyPressEater, self).eventFilter(watched, event)


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.line1 = QLineEdit()
        self.line2 = QLineEdit()
        self.line3 = QLineEdit()
        self.line1.installEventFilter(keypress_eater)
        self.line2.installEventFilter(keypress_eater)
        self.line3.installEventFilter(keypress_eater)
        v_layout = QVBoxLayout()
        v_layout.addWidget(self.line1)
        v_layout.addWidget(self.line2)
        v_layout.addWidget(self.line3)
        self.setLayout(v_layout)

if __name__ == '__main__':
    keypress_eater = KeyPressEater()            # 2
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())
  1. Write the eventFilter event filter function in a separate class to filter all keyboard events.

  2. Instantiate the keyboard filter object at the program entrance, and then install all three input boxes.

The operation screenshot is as follows:

However, it is too troublesome to call the installEventFilter function one by one. After all, there are too many controls. In fact, we can directly install filters on QApplication, so that each object in the application will not receive filtered events. This operation is very useful in program debugging. Now let's modify the code at the program entry:

if __name__ == '__main__':
    keypress_eater = KeyPressEater()
    app = QApplication(sys.argv)
    app.installEventFilter(keypress_eater)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

Instantiate a filter object at the entrance and install it on QApplication.

If we want individual controls not to be affected by the filter, we can directly change the content of eventFilter:

import sys
from PyQt5.QtCore import QObject
from PyQt5.QtGui import QWindow
from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QVBoxLayout

class KeyPressEater(QObject):
    def __init__(self):
        super(KeyPressEater, self).__init__()
    def eventFilter(self, watched, event):      # 1
        if type(watched) != QWindow and watched.objectName() != 'line3':
            if event.type() == event.KeyPress:
                print('Keyboard events are filtered')
                return True
        return super(KeyPressEater, self).eventFilter(watched, event)

class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.line1 = QLineEdit()
        self.line2 = QLineEdit()
        self.line3 = QLineEdit()
        self.line3.setObjectName('line3')       # 2
        v_layout = QVBoxLayout()
        v_layout.addWidget(self.line1)
        v_layout.addWidget(self.line2)
        v_layout.addWidget(self.line3)
        self.setLayout(v_layout)


if __name__ == '__main__':
    keypress_eater = KeyPressEater()
    app = QApplication(sys.argv)
    app.installEventFilter(keypress_eater)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())
  1. Determine the object type and name of the control. Because the window will receive events first, the type of QWindow should be excluded first. If watched is a QLineEdit object, you need to judge its object name, which cannot be 'line3'. This is self The keyboard events of line3 will not be filtered, only self Line1 and self Line2.

  2. To self The Line3 control sets an object name.

The operation screenshot is as follows:

4. Keep the window responsive when processing time-consuming tasks

Usually, we first think of using multithreading to handle time-consuming tasks, but in fact, a simpler way is to call QApplication processEvents(). This function will refresh the interface when the program executes time-consuming tasks, handle the events to be responded on the interface, and solve the problem that the interface is stuck and unresponsive.

We only need to call QApplication. in time consuming code. Processevents(), please see the example:

import sys
from PyQt5.QtCore import Qt, QEventLoop
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.count = 0
        self.btn = QPushButton('count', self)
        self.btn.clicked.connect(self.count_slot)
        self.label = QLabel('0', self)
        self.label.setAlignment(Qt.AlignCenter)
        v_layout = QVBoxLayout()
        v_layout.addWidget(self.label)
        v_layout.addWidget(self.btn)
        self.setLayout(v_layout)

    def count_slot(self):
        while True:
            self.count += 1
            self.label.setText(str(self.count))
            QApplication.processEvents()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

The count to which the button is connected_ The slot function performs an infinite count operation and passes the value through self The label control is displayed on the interface. If QApplication is not added to the code ProcessEvents (), the interface will jam or even crash when the user clicks the button.

The operation screenshot is as follows:

We can also pass a flag into the processEvents function to control the type of event to be processed by the function when updating the interface. Flag can take the following values:

Valuedescribe
QEventLoop::AllEventsHandle all events
QEventLoop::ExcludeUserInputEventsDo not process user input events, such as mouse events or keyboard events
QEventLoop::ExcludeSocketNotifiersDo not handle socket listening events
QEventLoop::WaitForMoreEventsIf there are no pending events, wait for the event to be generated

Let's modify the above example as follows:

def count_slot(self):
    while True:
        self.count += 1
        self.label.setText(str(self.count))
        QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

After running the program and pressing the button, we will find that the interface no longer handles the user's mouse events, the button can't be pressed again, and the window can't be closed. If your program contains a time-consuming operation and you don't want users to interrupt it, you can use this method.

I reprint to: https://pyqt5.blog.csdn.net/article/details/107589062

Topics: Python Qt PyQt5