Hello Qt -- QT event mechanism analysis

Posted by the-Jerry on Sat, 05 Mar 2022 17:28:19 +0100

1, Event mechanism

Events are sent by the system or QT platform itself at different times. When the user presses the mouse, taps the keyboard, or the window needs to be redrawn, a corresponding event will be issued. Some events are issued in response to user operations, such as keyboard events; Other events are automatically issued by the system, such as timer events.

The occurrence of events makes the program code not execute in the original linear order. The linear programming style is not suitable for dealing with complex user interaction. For example, in the process of user interaction, the user clicks "open file" to start the operation of opening the file, and the user clicks "save file" to start the operation of saving the file. What kind of operations are carried out in the process of user interaction is determined by the user, which cannot be predicted in advance during program design. At the same time, the user's operations will send corresponding events. Therefore, in the program design of user interaction, the execution sequence of the program is no longer linear, but driven by events one by one. If there are no events, the program will be blocked, No code is executed.

In QT, when using QT components, you usually don't focus on events. More concerned about the signals associated with events. For example, for the mouse click of QPushButton, you don't need to care about the mouse click event, but about the click () signal. However, the events and signal slots in QT can not replace each other. The signal is sent by the specific object. Once the signal is sent, it will be immediately handed over to the slot connected by the connect() function for processing; For events, QT uses an event queue to maintain all issued events. When a new event occurs, it will be added to the tail of the event queue. After the previous event is completed, the subsequent events will be taken out for processing, and QT events can also be processed directly without entering the event queue. Events can be filtered by using the "event filter". Some events are handled additionally, while others are not concerned.

In general, if components are used, the concern is the signal slot; If you customize components, you are concerned about events. Because the default operation of components can be changed through events. For example, if you want to customize an EventLabel that can respond to mouse events, you need to rewrite the mouse events of QLabel and make the desired operation. You may also have to send a button like clicked() signal at the right time (if you want the EventLabel to be used by other components) or other signals.

The QT program needs to create a QCoreApplication object in the main() function and then call the exec () function. The exec() function starts the QT event loop. After the exec () function is executed, the program will enter the event loop to listen for the events of the application. When an event occurs, QT creates an event object. All event classes in QT inherit from QEvent. After the event object is created, QT passes the created event object to the event() function of QObject. The event () function does not directly handle events, but is assigned to a specific event handler according to the type of event object.

In the parent QWidget of all components, many callback functions for event processing are defined, such as keyPressEvent(), keyReleaseEvent(), mouseDoubleClickEvent(), mouseMoveEvent(), mousePressEvent(), mouserereleaseevent(), etc. If you want to handle events in a custom component, you need to re implement the event handling function in the subclass.

The QWidget component has a mouseTracking property, which is used to set whether to track the mouse. mouseMoveEvent() is issued only when the mouse is tracked. If mouseTracking is false (default), the QWidget component can only be tracked after at least one mouse click, that is, the mouseMoveEvent() event can be issued after the mouse click. If mouseMoveEvent() is set to true, mouseMoveEvent() can be emitted directly.

2, Event handling

1. Event handler function of custom class

Event handling includes event acceptance and ignore.

Example code:

CustomButton.h documents:

#ifndef CUSTOMBUTTON_H
#define CUSTOMBUTTON_H

#include <QPushButton>
#include <QDebug>

class CustomButton : public QPushButton
{
public:
    CustomButton(QWidget* parent = 0);
protected:
    void mousePressEvent(QMouseEvent *event);
private slots:
    void onButton();
};

#endif // CUSTOMBUTTON_H

CustomButton.cpp file:

#include "CustomButton.h"
#include <QMouseEvent>

CustomButton::CustomButton(QWidget* parent):QPushButton(parent)
{
    connect(this, &CustomButton::clicked, this, &CustomButton::onButton);
}

void CustomButton::onButton()
{
  qDebug() << "child clicked";
}

void CustomButton::mousePressEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
    {
        qDebug() << "Left press";
    }
    else
    {
        QPushButton::mousePressEvent(event);
    }
}

Mainc.cpp file:

#include "CustomButton.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    CustomButton button;

    button.setText("button");

    button.show();

    return a.exec();
}

The custom class CustomButton inherits from QPushButton and overrides the mousePressEvent() function, that is, the mouse press event handler. In the mousePressEvent() function, if the Left mouse button is pressed, the "Left press" string will be printed. Otherwise, the function with the same name of the parent class will be called. The slot function onButton() prints "child clicked".

Compile and run the program. After clicking the mouse button, the "Left press" string is printed, but the "child clicked" string is not printed.

The reason is that the event handler function mousePressEvent() of the custom class CustomButton overrides the corresponding event handler function of the parent class QPushButton. The mousePressEvent() event handler function of the parent class QPushButton will send a clicked() signal, while the implementation of the custom class CustomButton will not send a clicked() signal. Therefore, the custom class CustomButton object does not send a clicked () signal when the mouse is pressed, and the connected slot function onButton() will not be executed.

When overriding the event handler of the parent class in a custom class, the corresponding event handler of the parent class will be overwritten, and the operations contained in the event handler of the parent class (such as sending some signals) will also be overwritten. Therefore, when rewriting the event callback function, you must pay attention to whether you need to call the function with the same name of the parent class to ensure that the original implementation can still proceed.

By calling the function with the same name of the parent class, the event delivery of QT can be regarded as a chain: if the child class does not handle this event, it will continue to pass to its parent class. The event object of QT has two functions: accept() and ignore(). Accept() notifies Qt platform that the event handling function wants to handle this event; Ignore() notifies the QT platform that the event handler does not handle this event. In the event handler function, you can use isAccepted() to query whether an event has been accepted.

If an event handler calls the accept() function of an event object, the event will not be propagated to its parent component; If the event object calls the ignore() function of the event, the QT platform will find another receiver from its parent component.

Generally, the accept() and ignore () functions are rarely used. If you want to ignore the event, just call the corresponding event handler function of the parent class. Since it is impossible to confirm whether the corresponding event handling function in the parent class has additional operations, if the ignore() function is directly used in the subclass to ignore events, QT will find other recipients, and the operations of the parent class will be ignored (because the function with the same name of the parent class is not called), which may be potentially dangerous. In order to avoid subclasses calling accept() and ignore () functions, QT makes a special design to call the parent class implementation as much as possible: the event object is accepted by default, while the default implementation of QWidget, which is the parent class of all components, calls ignore (). Therefore, if the subclass re implements the event handling function and does not call the default implementation of QWidget, it is equivalent to accepting the event; If you want to ignore events, just call the default event handler function of QWidget.

The source code implementation of mousePressEvent() function of QWidget in QT5 is as follows:

void QWidget::mousePressEvent(QMouseEvent *event)
{
    event->ignore();
    if ((windowType() == Qt::Popup)) 
    {
        event->accept();
        QWidget* w;
        while ((w = QApplication::activePopupWidget()) && w != this)
        {
            w->close();
            if (QApplication::activePopupWidget() == w)
                w->hide(); // hide at least
        }
        if (!rect().contains(event->pos()))
        {
            close();
        }
    }
}

If the subclass does not override the mousePressEvent() function, QT will ignore this event by default and continue to find the next event receiver. If the parent function of pressevent() is called, the corresponding event of pressevent() may not be handled directly in the parent function of pressevent().

2. Propagation of events

CustomButton.h documents:

#ifndef CUSTOMBUTTON_H
#define CUSTOMBUTTON_H

#include <QPushButton>

class CustomButton : public QPushButton
{
    Q_OBJECT
public:
    CustomButton(QWidget* parent = 0);
protected:
    void mousePressEvent(QMouseEvent *event);
};

#endif // CUSTOMBUTTON_H

CustomButton.cpp file:

#include "CustomButton.h"
#include <QMouseEvent>
#include <QDebug>

CustomButton::CustomButton(QWidget* parent):QPushButton(parent)
{

}

void CustomButton::mousePressEvent(QMouseEvent *event)
{
   event->ignore();

   qDebug() << "CustomButton";
}

CustomButtonEx.h documents:

#ifndef CUSTOMBUTTONEX_H
#define CUSTOMBUTTONEX_H

#include "CustomButton.h"

class CustomButtonEx : public CustomButton
{
    Q_OBJECT
public:
    CustomButtonEx(QWidget* parent = 0);
protected:
    void mousePressEvent(QMouseEvent *event);
};

#endif // CUSTOMBUTTONEX_H

CustomButtonEx.cpp file:

#include "CustomButtonEx.h"
#include "CustomWidget.h"
#include <QMouseEvent>
#include <QDebug>

CustomButtonEx::CustomButtonEx(QWidget* parent):CustomButton(parent)
{

}

void CustomButtonEx::mousePressEvent(QMouseEvent *event)
{
    event->ignore();
    qDebug() << "CustomButtonEx";
}

CustomWidget.h documents:

#ifndef CUSTOMWIDGET_H
#define CUSTOMWIDGET_H

#include <QWidget>

class CustomWidget : public QWidget
{
    Q_OBJECT
public:
    explicit CustomWidget(QWidget *parent = 0);
protected:
    void mousePressEvent(QMouseEvent *event);
};

#endif // CUSTOMWIDGET_H

CustomWidget.cpp file:

#include "CustomWidget.h"
#include <QMouseEvent>
#include <QDebug>


CustomWidget::CustomWidget(QWidget *parent) : QWidget(parent)
{

}

void CustomWidget::mousePressEvent(QMouseEvent *event)
{
    qDebug() << "CustomWidget";
}

Main.cpp file:

#include "CustomButton.h"
#include "CustomButtonEx.h"
#include "CustomWidget.h"
#include <QApplication>
#include <QHBoxLayout


int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    CustomWidget* customWidget = new CustomWidget();

    CustomButton* custombutton = new CustomButton(customWidget);
    custombutton->setText("CustomBuuton");
    CustomButtonEx* custombuttonex = new CustomButtonEx(customWidget);
    custombuttonex->setText("CustomButtonEx");

    QHBoxLayout* layout = new QHBoxLayout(customWidget);
    layout->addWidget(custombutton);
    layout->addWidget(custombuttonex)
    customWidget->setLayout(layout);

    customWidget->show();

    return a.exec();
}

Place two button objects in the custom component CustomWidget: CustomButton and CustomButtonEx. Each class overrides the mousePressEvent() function.

If you call event->accept () in CustomButtonEx's mousePressEvent (), the mouse click event will not propagate to the parent component CustomWidget, only print "CustomButtonEx". If event->ignore () is invoked in mousePressEvent () of CustomButtonEx, the mouse click event will continue to propagate to the parent component CustomWidget, and print "CustomButtonEx" and "CustomWidget". Since event - > accept() is not called in the mousePressEvent() function of the customwidget, the mouse press event is propagated to the customwidget component.

The event of CustomButtonEx is propagated to the parent component CustomWidget instead of the parent CustomButton. The propagation of events is at the component level, rather than relying on the class inheritance mechanism.

In the window closing event handler, closeEvent must use the accept() and ignore() functions. For the QCloseEvent event when the window is closed, calling accept() means that QT will stop the propagation of the event and the window is closed; Calling ignore () means that the event continues to propagate, that is, preventing the window from closing.

void CustomWidget::closeEvent(QCloseEvent *event)
{
    bool exit = QMessageBox::question(this,
                     tr("Quit"),
                     tr("Are you sure to quit this application?"),
                     QMessageBox::Yes | QMessageBox::No,
                     QMessageBox::No) == QMessageBox::Yes;
    if (exit)
    {
        event->accept();
    }
    else
    {
        event->ignore();
    }
}

3. Event distribution

After the event object is created, QT passes the event object to the event() function of QObject. The event () function does not directly handle events, but distributes these event objects to different event handler s according to their different types.

The event() function is mainly used for event distribution. If you want to do something before event distribution, you can override the event () function.

Listen for a key press event in the QWidget component. The implementation is as follows:

bool CustomWidget::event(QEvent *event)
{
    if (event->type() == QEvent::KeyPress)
    {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
        if (keyEvent->key() == Qt::Key_K)
        {
            qDebug() << "You press K.";
            return true;
        }
    }
    return QWidget::event(event);
}

CustomWidget inherits from QWidget and rewrites the event() function. The event() function has a QEvent object as a parameter, which is the event object to be forwarded. The return value of the function is of type bool. If the incoming event has been recognized and processed, you need to return true, otherwise return false. If the return value is true and the event object is set to accept(), QT will think that the event has been processed and will not send the event to other components, but will continue to process the next event in the event queue. Note that in the event() function, calling the accept() and ignore() function of the event object has no effect and does not affect the propagation of the event.

By using the QEvent::type() function, you can check the actual type of the event, and its return value is an enumeration of type QEvent::Type. After processing the event you are interested in, you can directly return true, indicating that this event has been processed; For other events that we don't care about, we need to call the event() function of the parent class to continue forwarding, otherwise the custom component can only handle the events we are interested in, and other events will be discarded.

The source code of QObject::event() function in QT5 is as follows:

bool QObject::event(QEvent *e)
{
    switch (e->type()) 
    {
    case QEvent::Timer:
        timerEvent((QTimerEvent*)e);
        break;
    case QEvent::ChildAdded:
    case QEvent::ChildPolished:
    case QEvent::ChildRemoved:
        childEvent((QChildEvent*)e);
        break;
        // ...
    default:
        if (e->type() >= QEvent::User) 
        {
            customEvent(e);
            break;
        }
        return false;
    }
    return true;
}

QT uses QEvent::type() to judge the event type and calls a specific event handler to distribute the event. For example, if the return value of event - > type() is QEvent::Timer, call the timerEvent() function.

4. Event filter

After QT creates the QEvent event object, it will call the event() function of QObject to handle the event distribution. Although the interception operation can be implemented in the event () function, the event () function has two disadvantages. A. The event () function is protected and needs to inherit the existing class. If there are many components, you need to rewrite many event () functions.

B. Although the event() function can intercept events, the component actually receives events.

In order to solve the defect of event() function intercepting events, QT provides another way to intercept events: event filter.

QObject has an eventFilter() function, which is used to establish an event filter.

virtual bool QObject::eventFilter(QObject * watched, QEvent * event);

The event filter checks for received events. If the event is a type of interest, process it; If not, continue forwarding. The eventFilter function returns a bool type. If you want to filter out the parameter event, for example, if you don't want it to continue forwarding, it returns true. Otherwise, it returns false. The call time of the event filter is before the target object (watched object) receives the event object. If an event is stopped in the event filter, the watched object and all subsequent event filters will not know the event.

bool CustomWidget::eventFilter(QObject *watched, QEvent *event)
{
    if (watched == this && event->type() == QEvent::KeyPress)
    {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
        if (keyEvent->key() == Qt::Key_K)
        {
            qDebug() << "eventFilter: You press K.";
            return true;
        }
        else
        {
            return false;
        }
    }
    return false;
}

The custom class CustomWidget overrides the eventFilter() function. In order to filter events on a specific component, you first need to judge whether the component object is a component of interest, and then judge the type of events to be filtered. If it is an event to be filtered, it will directly return true, that is, this event will be filtered out, and other events will continue to be processed, so it will return false. For other components, since there is no guarantee that there is a filter, the safest way is to call the EventFilter () function of the parent class to ensure that the event filter set on the parent object can be called.

The eventFilter() function is equivalent to creating a filter. To make the filter effective, you need to install the filter. To install a filter, you need to call the QObject::installEventFilter() function.

void QObject::installEventFilter (QObject * filterObj);

filterObj is the filter object, that is, the class object to which the event filter belongs.

CustomWidget::CustomWidget(QWidget *parent) : QWidget(parent)
{
    this->installEventFilter(this);
}

After the event filter is created and installed, when the CustomWidget presses the mouse, the mouse event will be filtered, so the "eventFilter: You press K." string will be printed, and the mousePressEvent() function will not be called and the "CustomWidget" string will not be printed.

The eventFilter() function is a member function of QObject, so any QObject can be used as an event filter (if the eventFilter() function is not overridden, the event filter has no effect, because nothing will be filtered by default). Existing filters can be removed through the QObject::removeEventFilter() function.

You can install multiple event handlers on an object by calling the installEventFilter() function multiple times. If there are multiple event filters for an object, the last one installed will be executed first, that is, in the order of last in first out.

The power of event filters is that you can add an event filter to the entire application. installEventFilter() function is a function of QObject. QApplication or QCoreApplication objects are subclasses of QObject. Therefore, you can add event filters to QApplication or QCoreApplication.

If you use the installEventFilter() function to install an event filter for an object, the event filter is only valid for the object. Only the events of this object need to be passed to the eventFilter() function of the event filter for filtering, and other objects are not affected. If an event filter is installed on the QApplication object, the filter is valid for every object in the program, and the events of any object are passed to the EventFilter () function first. This global event filter will be invoked before any event filter for all other feature objects. Although powerful, this behavior can seriously reduce the efficiency of event distribution throughout the application. Therefore, you should not do this unless you have to use it.

Note that if a receiving component is delete d in the event filter, be sure to set the return value of the function to true. Otherwise, QT will still distribute events to the receiving component, resulting in program crash.

The event filter and the component to which the filter is installed must be in the same thread, otherwise the filter will not work. If two components go to different threads after installing the filter, the filter will not be effective until they return to the same thread. In QT, after an object is created, you can use the moveToThread() function to move an object to another thread.

5. Event handling summary

QT event processing level:

A. Rewriting paintEvent(), mousePressEvent() and other event handling functions is the most common and simplest form, and the function is also the simplest.

B. Override the event() function. Event () function is the event entry of all objects. It is implemented in QObject and QWidget. By default, it passes events to specific event handling functions.

C. Install an event filter on a specific object. The event filter only filters the events received by the object.

D. Install the event filter on QCoreApplication::instance(). The event filter filters all events for all objects. The global event filter can see the mouse events emitted from the disabled component. Global filters can only be used on the main thread.

E. Rewrite the QCoreApplication::notify() function. Like the global event filter, it provides full control and is not limited by threads. But only one can be used globally (because QCoreApplication is singleton).

CustomWidget.h documents:

#ifndef CUSTOMWIDGET_H
#define CUSTOMWIDGET_H

#include <QWidget>

class CustomWidget : public QWidget
{
    Q_OBJECT
public:
    explicit CustomWidget(QWidget *parent = 0);
protected:
    void mousePressEvent(QMouseEvent *event);
    void closeEvent(QCloseEvent *event);
    bool event(QEvent *event);
    bool eventFilter(QObject *watched, QEvent *event);
};

#endif // CUSTOMWIDGET_H

CustomWidget.cpp file:

#include "CustomWidget.h"
#include <QMouseEvent>
#include <QDebug>


CustomWidget::CustomWidget(QWidget *parent) : QWidget(parent)
{
    this->installEventFilter(this);
}

void CustomWidget::mousePressEvent(QMouseEvent *event)
{
    qDebug() << "CustomWidget::mousePressEvent";
}

bool CustomWidget::event(QEvent *event)
{
    if (event->type() == QEvent::MouseButtonPress)
    {
        qDebug() << "CustomWidget::event";
    }

    return QWidget::event(event);
}

bool CustomWidget::eventFilter(QObject *watched, QEvent *event)
{
    if (event->type() == QEvent::MouseButtonPress)
    {
        qDebug() << "CustomWidget::eventFilter";
    }

    return false;
}

Main.cpp file:

#include "CustomWidget.h"
#include <QApplication>
#include <QDebug>

class EventFilter : public QObject
{
public:
    EventFilter(QObject *watched, QObject *parent = 0) :
        QObject(parent),
        m_watched(watched)
    {

    }

    bool eventFilter(QObject *watched, QEvent *event)
    {
        if (watched == m_watched)
        {
            if (event->type() == QEvent::MouseButtonPress)
            {
                qDebug() << "QApplication::eventFilter";
            }
        }
        return false;
    }
private:
    QObject *m_watched;
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    CustomWidget customWidget;

    a.installEventFilter(new EventFilter(&customWidget, &customWidget));

    customWidget.show();

    return a.exec();
}

Press the mouse button to print out the string:

QApplication::eventFilter

CustomWidget::eventFilter

CustomWidget::event

CustomWidget::mousePressEvent

The global event filter is called first, the event filter on the component object is called second, the event() function is called third, and the specific event handler function is called fourth.

3, Custom event

The distribution of events can be synchronous or asynchronous, and the callback of signal slot is always synchronous. And events can use filters.

1. Type of custom event

Custom events need to be inherited from Q QT events. QEvent provides a parameter of QEvent::Type as the type value of custom event.

QEvent::Type is an enumeration defined by QEvent. It should be noted that the user-defined event type cannot duplicate the existing type value, otherwise unexpected errors will occur, because the system will dispatch and call the newly added user-defined event as a system event. In QT, the system keeps the value of 0 – 999, and the type of user-defined event should be greater than 999. QT defines two boundary values: QEvent::User and QEvent::MaxUser. The type of custom event should be between the two values. The value of QEvent::User is 1000 and the value of QEvent::MaxUser is 65535. Through these two enumeration values, you can ensure that the custom event type will not overwrite the system defined event type. However, there is no guarantee that custom events will not be overwritten with each other. To avoid overlapping between custom events, QT provides a function: registerEventType(), which is used to register custom events.

static int QEvent::registerEventType ( int hint = -1 );

The registerEventType function is static and can be called directly using the QEvent class. The return value of the function is the value of the new type registered with the system. If hint is legal, that is, hint will not be overwritten (system and other user-defined events), this value will be returned directly; Otherwise, the system will automatically assign a legal value and return it. Use the registerEventType function to specify the type value. The registerEventType function is thread safe and there is no need to add synchronization.

2. Event sending method

You can add the required data in the custom event, and then send the event.

QT provides two ways to send events:

A. Non blocking transmission

static bool QCoreApplication::sendEvent(QObject *receiver,QEvent *event);

Send the event event directly to the receiver receiver, using the QCoreApplication::notify() function. The return value of the function is the return value of the event handler function. The event object is not destroyed when the event is sent. Event objects are usually created on the stack, for example:

QMouseEvent event(QEvent::MouseButtonPress, pos, 0, 0, 0);

QApplication::sendEvent(receiver, &event);

B. Blocking transmission

static void QCoreApplication::postEvent(QObject *receiver,QEvent *event);

Add the event event and its receiver to the event queue, and the function returns immediately.

Because the post event queue will hold the event object and delete it when it is posted, the event object must be created on the heap. After the object is sent, there will be a problem when trying to access the event object again (because the event object will be deleted after post).

When control returns to the main thread loop, all events saved in the event queue are sent out through the notify() function.

Events are processed according to the order of post. If you want to change the processing order of events, consider assigning a priority to them. The default priority is Qt::NormalEventPriority.

The postEvent function is thread safe.

static void QCoreApplication::sendPostedEvents(QObject *receiver,int event_type);

The sendPostedEvents function is used to set the receiver in the event queue as receiver, and the event is similar to event_ All events of type are immediately sent to the receiver for processing. It should be noted that the events from the window system are not handled by the sendPostedEvents function, but processEvent().

3. Custom event handler

The handling of user-defined events can be defined as a user-defined event handling function or handled directly in the event() function.

void CustomWidget::customEvent(QEvent *event)
{
    CustomEvent *customEvent = static_cast<CustomEvent *>(event);
    // ...
}

bool CustomWidget::event(QEvent *event) 
{
    if (event->type() == CustomEventType) 
    {
        CustomEvent *myEvent = static_cast<CustomEvent *>(event);
        // processing...
        return true;
    }

    return QWidget::event(event);
}

Topics: Qt