Game Programming using Qt 5 Beginner's Guide
上QQ阅读APP看书,第一时间看更新

Connecting signals and slots

Signals and slots can be connected and disconnected dynamically using the  QObject::connect() and QObject::disconnect() functions. A regular, signal-slot connection is defined by the following four attributes:

  • An object that changes its state (sender)
  • A signal in the sender object
  • An object that contains the function to be called (receiver)
  • A slot in the receiver

If you want to make the connection, you need to call the QObject::connect function and pass these four parameters to it. For example, the following code can be used to clear the input box whenever the button is clicked on:

connect(button,   &QPushButton::clicked,
        lineEdit, &QLineEdit::clear);

Signals and slots in this code are specified using a standard C++ feature called pointers to member functions. Such a pointer contains the name of the class and the name of the method (in our case, signal or slot) in that class. Qt Creator's code autocompletion will help you write connect statements. In particular, if you press Ctrl + Space after 
connect(button, &, it will insert the name of the class, and if you do that after connect(button, &QPushButton::, it will suggest one of the available signals (in another context, it would suggest all the existing methods of the class).

Note that you can't set the arguments of signals or slots when making a connection. Arguments of the source signal are always determined by the function that emits the signal. Arguments of the receiving slot (or signal) are always the same as the arguments of the source signal, with two exceptions:

  • If the receiving slot or signal has fewer arguments than the source signal, the remaining arguments are ignored. For example, if you want to use the  valueChanged(int) signal but don't care about the passed value, you can connect this signal to a slot without arguments.
  • If the types of the corresponding arguments are not the same, but an implicit conversion between them exists, that conversion is performed. This means that you can, for example, connect a signal carrying a double value with a slot taking an int parameter.

If the signal and the slot do not have compatible signatures, you will get a compile-time error.

An existing connection is automatically destroyed after the sender or the receiver objects are deleted. Manual disconnection is rarely needed. The connect() function returns a connection handle that can be passed to disconnect(). Alternatively, you can call  disconnect() with the same arguments the connect() was called with to undo the connection.

You don't always need to declare a slot to perform a connection. It's possible to connect a signal to a standalone function:

connect(button, &QPushButton::clicked, someFunction); 

The function can also be a lambda expression, in which case it is possible to write the code directly in the connect statement:

connect(pushButton, &QPushButton::clicked, []()
{
    qDebug() << "clicked!";
});

It can be useful if you want to invoke a slot with a fixed argument value that can't be carried by a signal because it has less arguments. A solution is to invoke the slot from a lambda function (or a standalone function):

connect(pushButton, &QPushButton::clicked, [label]()
{
    label->setText("button was clicked");
});

A function can even be replaced with a function object (functor). To do this, we create a class, for which we overload the call operator that is compatible with the signal that we wish to connect to, as shown in the following code snippet:

class Functor {
public:
Functor(const QString &name) : m_name(name) {}
void operator()(bool toggled) const {
qDebug() << m_name << ": button state changed to" << toggled;
}
private:
QString m_name;
};

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton *button = new QPushButton();
button->setCheckable(true);
QObject::connect(button, &QPushButton::toggled,
Functor("my functor"));
button->show();
return a.exec();
}

This is often a nice way to execute a slot with an additional parameter that is not carried by the signal, as this is much cleaner than using a lambda expression. However, keep in mind that automatic disconnection will not happen when the object referenced in the lambda expression or the functor is deleted. This can lead to a use-after-free bug.

While it is actually possible to connect a signal to a method of a   QObject-based class that is not a slot, doing this is not recommended. Declaring the method as a slot shows your intent better. Additionally, methods that are not slots are not available to Qt at runtime, which is required in some cases.