Qt Road 2 -- signal and slot

Posted by TweetyPie on Wed, 09 Feb 2022 11:24:17 +0100

Signal slot is one of the mechanisms that Qt framework is proud of. Skillfully use and understand the signal slot, and be able to design a very beautiful decoupling program, which is conducive to enhancing our technical design ability.

The so-called signal slot is actually the observer mode. When an event occurs, for example, a button detects that it has been clicked, it sends a signal. This kind of transmission has no purpose, similar to broadcasting. If an object is interested in the signal, it will use the connect function, which means that it processes the signal with one of its own functions (called slots). That is, when the signal is sent, the connected slot function will be called back automatically. This is similar to observer mode: when an event of interest occurs, an operation will be automatically triggered.

In order to experience the use of signal slot, let's explain it with a simple code:

#include <QApplication>
#include <QPushButton>

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

    QPushButton button("Quit");
    QObject::connect(&button, &QPushButton::clicked, 
    &QApplication::quit);
    button.show();

    return app.exec();
}

Create the project in Qt Creator, and then modify the main() function to the above code. Click Run and we will see a button with the word "Quit" on it. Click the button to exit the program.

In Qt 5, QObject::connect() has five overloads:

QMetaObject::Connection connect(const QObject *, 
								const char *,
                                const QObject *, 
                                const char *,
                                Qt::ConnectionType);

QMetaObject::Connection connect(const QObject *,
								const QMetaMethod &,
                                const QObject *, 
                                const QMetaMethod &,
                                Qt::ConnectionType);

QMetaObject::Connection connect(const QObject *,
								const char *,
                                const char *,
                                Qt::ConnectionType) const;
                                
QMetaObject::Connection connect(const QObject *, 
								PointerToMemberFunction,
                                const QObject *, 
                                PointerToMemberFunction,
                                Qt::ConnectionType)

QMetaObject::Connection connect(const QObject *, 
								PointerToMemberFunction,
                                Functor);

The return values of these five overloads are QMetaObject::Connection. Now we don't care about this return value. Let's take a look at the most common general form of the connect() function:

connect(sender, signal, receiver, slot);

This is our most commonly used form. connect() generally uses the first four parameters. The first is the object that sends the signal, the second is the signal sent by the sending object, the third is the object that receives the signal, and the fourth is the function that the receiving object needs to call after receiving the signal. In other words, when the sender sends a signal, it will automatically call the slot function of the receiver.

This is the most commonly used form. We can apply this form to analyze the five overloads given above.
First, the sender type is const QObject *, the signal type is const char *, the receiver type is const QObject *, and the slot type is const char *. This function treats signal and slot as strings.
Second, sender and receiver are const QObject *, but both signal and slot are const QMetaMethod &. We can think of each function as a subclass of QMetaMethod. Therefore, this writing method can use QMetaMethod for type comparison.
Third, the sender is const QObject *, and the signal and slot are const char *, but the receiver is missing. This function actually takes this pointer as the receiver.
Fourth, both sender and receiver exist, which are const QObject *, but the types of signal and slot are PointerToMemberFunction. You should know from the name. This is a pointer to a member function.
Fifth, the first two parameters are no different. The last parameter is of type Functor. This type can accept static functions, global functions, and Lambda expressions.

From this, we can see that the connect() function, sender and receiver are no different, they are QObject pointers; The main difference is the form of signal and slot. As for our example, our connect() function is obviously the fifth overload used, and the last parameter is the static function quit() of QApplication. In other words, when our button sends a clicked() signal, it will call the quit () function of QApplication to exit the program.

The signal slot requires the parameters of the signal and slot to be consistent. The so-called consistency means that the parameter types are consistent. If it is inconsistent, it is allowed that the parameters of the slot function can be less than those of the signal. Even so, the order of those parameters of the slot function must be consistent with the first few of the signal. This is because you can choose to ignore the data transmitted by the signal in the slot function (that is, the parameters of the slot function are less than those of the signal), but you can't say that the signal doesn't have this data at all. You have to use it in the slot function (that is, the parameters of the slot function are more than those of the signal, which is not allowed).

If the signal slot does not match or the signal slot function cannot be found at all, for example, we change it to:

//QApplication does not have functions like quit2, so there will be compilation errors during compilation:

QObject::connect(&button, &QPushButton::clicked, 
&QApplication::quit2);  

Qt 5 signal slot syntax, we can connect the signal of an object to a Lambda expression

#include <QApplication>
#include <QPushButton>
#include <QDebug>

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

    QPushButton button("Quit");
    QObject::connect(&button, &QPushButton::clicked, 
    [](bool) {
        qDebug() << "You clicked me!";
    });
    button.show();
    
    return app.exec();
}

Note that the Lambda expression here receives a bool parameter because the clicked() signal of QPushButton actually has a parameter. qDebug() in Lambda expression is similar to cout, which prints the following string to standard output. If you want to compile the above code, you need to add this sentence to the pro file:

QMAKE_CXXFLAGS += -std=c++0x

The signal slot of Qt 4 is similar to Qt 5. In QObject of Qt 4, there are three different connect() overloads:

bool connect(const QObject *, const char *,
             const QObject *, const char *,
             Qt::ConnectionType);

bool connect(const QObject *, const QMetaMethod &,
             const QObject *, const QMetaMethod &,
             Qt::ConnectionType);

bool connect(const QObject *, const char *,
             const char *,
             Qt::ConnectionType) const

In addition to the return value, the biggest difference between the connect() function of Qt 4 and Qt 5 is that the signal and slot of Qt 4 only have the form of const char *. If we modify the above code to Qt 4, it should be as follows:

#include <QApplication>
#include <QPushButton>

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

    QPushButton button("Quit");
    QObject::connect(&button, SIGNAL(clicked()),
                     &app, SLOT(quit()));
    button.show();

    return app.exec();
}

We use two macros, signal and slot, to convert the two function names into strings. Note that even if quit() is the static function of QApplication, an object pointer must be passed in. This is also the limitation of Qt 4 signal slot syntax. In addition, note that both the signal and slot of the connect() function accept strings. Therefore, you cannot pass a global function or Lambda expression into connect(). In case of unsuccessful connection, Qt 4 has no compilation error (because everything is a string, and the compilation period does not check whether the string matches), but gives an error at runtime. This will undoubtedly increase the instability of the program.

Topics: C++ Qt