Qt tutorial: signals and slots

Posted by kye on Sun, 23 Jan 2022 18:14:02 +0100

Signal and slot are used for communication between objects, which is the core of Qt. To this end, Qt introduces some keywords, such as slots, signals and emit. These keywords are not c + + keywords, but are unique to Qt. These keywords will be converted into standard C + + statements by Qt's moc.

Qt's component classes have some defined signals and slots. The common practice is to subclass the component classes, and then add their own signals and slots.

Because signals and slots are similar to functions, signals are usually called signal functions and slots are called slot functions.

A meta object is an object that describes the structure of another object. When implemented in a programming language, it is actually an object of a class, but this object is specifically used to describe another object, such as class B {...}; class A{…B mb;…}; Assuming MB is used to describe the object created by class A, MB is a meta object.

1, Principle of signal and slot

Although C + + is an object-oriented language, the specific implementation code of the program is still implemented by functions. Therefore, the so-called communication between objects is the problem of function call from the perspective of programming language syntax. It is just that the member function of one object calls the member function of another object. Signals and slots are actually an implementation of observer mode.

When an event occurs, for example, the button detects that it has been clicked, it will send a signal. This kind of transmission has no purpose, similar to broadcasting. If an object is interested in this signal, it will use the connect function, which means that it will bind the signal to be processed with its own function (called slot) to process this signal. That is, when the signal is sent, the connected slot function will be called back automatically.

void g() {
  //content of execution
}
void f() {
  //What to let g() do
}

Function f needs to be executed by g. there are several processing methods:

(1) the simplest way is to call function g directly, but this method has an obvious disadvantage. You must know the name "g" of function g and the parameter type of function G. However, if f only needs the processing result of G, and the processing result of G does not necessarily need to be completed by function g, but it can also be completed by x, y or other functions, then this method of directly calling the function is not competent, because the system does not know which function the user will use to complete the processing result, That is, the system does not know whether the called function name is g, x or other names.

(2) another way is to call back the function, that is, use a pointer to the function in function f to call the required function, so that a function with any name can be called (as long as the function type is the same as the pointer). At this time, any function that completes the function of function g can be called as the result of function F, This will not be limited by the function name. such as

void (*p)(int i,int j); //Suppose this is the internal code of the system
void g(int,int) {       //Suppose g , is code implemented by a programmer
} 
void h(int,int) {       //The principle is the same as g
} 
void f() {              //Suppose f is also an internal source code function of the system
  p=g;
  p(1,2);//Call g
  p=h;
  p(3,4);//Call h
}

(3) signal and slot mechanism used by Qt:

Create a signal that requires some rules. When you need to call an external function, send a signal, and the slot associated with the signal will be called. The slot is actually a function. Of course, there are certain rules to make the function a slot. The correlation between the slot and the signal needs to be completed by the programmer. In Qt, both signals and slots need to be in one class.

void x(int,int) {//signal
}
void g(int,int) {//groove
}
void h(int,int) {//groove
}
relation: (x, g);
relation: (x, h);
void f() {       //groove
  send out: x(1,2);   //g,h will be called
}

Prototype of several keywords:

  • Signals keyword: eventually replaced by #define as an access control character, which is simplified to #define signals public

  • Slots keyword: eventually replaced by #define as an empty macro, that is, simplified as #define slots

  • Emit keyword: also replaced by #define as an empty macro, that is, simplified as #define emit

As can be seen from the above keyword prototypes, using emit to transmit signals is actually a simple function call.

2, Create signals and slots

Only QObject and its derived classes can use the signal and slot mechanism, and Q needs to be used in the class_ Object macro.

Signal creation rules:

  • Signals are declared using the signals keyword, followed by a colon ":", and cannot be preceded by public, private or protected access control characters. Signals are public by default.

  • The signal only needs to be declared like a function, which can have parameters. The main function of the parameters is to communicate with the slot, just like the parameter transfer rules of ordinary functions. Although the signal is like a function, its calling method is different. The signal needs to be emitted using the emit keyword.

  • Signals only need to be declared and cannot be defined. Signals are automatically generated by moc.

  • The return value of a signal can only be of type void.

Slot creation rules:

  • Slots keyword needs to be used to declare slots, followed by a colon ":", and slots need to use one of public, private and protected access control characters.

  • A slot is an ordinary function that can be used like an ordinary function. The main difference between a slot and an ordinary function is that a slot can be associated with a signal.

Transmission signal rules:

  • The emit keyword is required for transmitting signals. Note that a colon is not required after emit.

  • The syntax of the signal transmitted by emit is the same as that of calling ordinary functions. For example, if a signal is void f(int), the syntax of transmission is: emit f(3);  

  • When the signal is transmitted, the slot function associated with it will be called (Note: the signal and slot need to be used)

  • After the QObject::connect function is associated, the associated slot function will be called only after the signal is transmitted).

  • Because the signal is in a class, the location of the transmitted signal needs to be in the member function of the class or in the class

  • Class can see the location of the identifier of the signal.

Relationship between signal and slot:

  • The type of slot parameters needs to correspond to the type of signal parameters,

  • The parameters of the slot cannot be redundant with the parameters of the signal, because if there are more parameters of the slot, the redundant parameters cannot receive the value transmitted by the signal. If these redundant valueless parameters are used in the slot, an error will be generated.

  • If the parameters of the signal are more than the parameters of the slot, the redundant parameters will be ignored.

  • One signal can be associated with multiple slots, multiple signals can also be associated with the same slot, and signals can also be associated with another signal.

  • If a signal is associated with multiple slots, the slot functions are executed in the order of association when transmitting the signal.

  • If the signal is connected to another signal, the second signal will be transmitted immediately when the first signal is transmitted.

//Contents of header file m.h
class A : public QObject{ //Signals and slots must inherit from the QObject class
  Q_OBJECT                    //The macro must be added
  
  //public signals:void s1(int);  // There cannot be an access control character before error signals.
  
  signals:void s();//Use the signals keyword to declare signals. The syntax of signals is the same as that of declaring functions.
  signals:void s(int,int);//Correct, the signal can have parameters or can be overloaded.
  
  //void s2() {} / / error. Signals only need to be declared and cannot be defined.
  
  void s3(); //Note: This is still a signal of declaration
public://After the signal declaration, reuse the access control character, indicating that the following declaration is a member function.
  void g(){
    emit s3(); //Transmit signal
  }
};
class B:public QObject{
  Q_OBJECT
public slots:               //Declare slots using slots keyword
  void x(){
    cout<<"X"<<endl;
  }
  //slots: void x() {} / / error. You need to specify an access control character when declaring slots.
  public:
  void g(){ 
    // emit s3();       // Error, not visible for identifier S3 in class B
  }
};
​
//Contents of source file
int main(int argc, char *argv[]){
  A ma; 
  B mb;
  QObject::connect(&ma,&A::s3,&mb,&B::x); //Associated signals and slots
  ma.g(); //Call g send signal
  return 0;
}

3, Connecting signal and slot

Signals and slots are associated using the member function connect in the QObject class, which has multiple overloaded versions.

Mode 1:

static QMetaObject::Connection connect(
  const QObject *sender,   const char *signal, 
  const QObject *receiver, const char *method, 
  Qt::ConnectionType type = Qt::AutoConnection)
class A:public QObject {
  Q_OBJECT 
singals: 
  void s(int i);
};
class B:public QObject{
  Q_OBJECT 
public slots: 
  void x(int i){}
};
A ma; B mb; 
QObject::connect (&ma, SIGNAL(s(int)), &mb, SLOT(x(int));

The macro SIGNAL() and the macro SLOT() must be used for signal specification. These two macros can convert the contents in parentheses into const char * corresponding to formal parameters. When specifying a function, you can only specify the type of the function parameter, no parameter name, and no return type of the function. For example, slot (x (int i)), is wrong because the parameter name I is specified, and the correct form is SLOT(x(int)).

The meaning of each parameter is as follows

  • sender: indicates the object that needs to transmit a signal.

  • Signal: indicates the signal to be transmitted. This parameter must use the SIGNAL() macro.

  • receiver: indicates the object receiving the signal.

  • method: represents the slot function associated with the SIGNAL. This parameter can also be a SIGNAL, so as to realize the correlation between the SIGNAL and the SIGNAL. If this parameter is a slot, it needs to use the SLOT() macro, and if it is a SIGNAL, it needs to use the SIGNAL macro.

  • The type of the return value is QMetaObject::Connection. If the signal is successfully connected to the slot, the connection handle is returned. Otherwise, the connection handle is invalid. You can check whether the handle is valid by converting the handle to bool. This return value can be used as a parameter of the QObject::disconnect() function to disconnect the signal from the slot.

  • type: used to indicate the correlation between the signal and the slot. It determines whether the signal is transmitted to a slot immediately or queued for transmission at a later time. The association method is described by enumerating Qt::ConnectionType. The following table shows its values and meanings:

constantdescribe

Qt::AutoConnection

(Auto Association, default). If the receiver resides in the thread transmitting the signal (i.e. the signal and slot are in the same thread), QT:: directconnection is used; otherwise, QT:: queuedconnection is used. When the signal is transmitted, determine which correlation type to use.

Qt::DirectConnection

Directly related. When the signal is transmitted, the slot is called immediately. After the slot is executed, the code after transmitting the signal (that is, the code after the emit keyword) will be executed. The slot executes in the signal thread.

Qt::QueuedConnection

Queue Association. The slot will be called only after the control is returned to the event loop of the receiver thread, that is, the code behind the emit keyword will be executed immediately, the slot will be executed later, and the slot will be executed in the receiver thread.

Qt::BlockingQueuedConnection

Blocking queue associations. Just like QT:: queuedconnection, the signal thread will block until the slot returns. If the receiver resides in the signal thread, this connection cannot be used, otherwise the application will deadlock.

Qt::UniqueConnection

Unique association. This is a flag that can be used bitwise or in combination with any of the above connection types. When QT:: uniqueconnection is set, the connection can only be made without repetition. If there is already a duplicate connection (that is, the same signal points to the same slot on the same object), the connection will fail and an invalid QMetaObject::Connection will be returned

Mode 2:

QMetaObject::Connection connect(
  const QObject *sender, 
  const char *signal, const char *method, 
  Qt::ConnectionType type = Qt::AutoConnection) const
  A ma; 
  B mb; 
  mb.connect(&ma, SIGNAL(s(int)), SLOT(x(int));

This function is a simplified version of form 1, equivalent to connect(sender, signal, this, method, type)

Mode 3:

static QMetaObject::Connection connect(
  const QObject *sender, PointerToMemberFunction signal, 
  const QObject *receiver, PointerToMemberFunction method, 
  Qt::ConnectionType type = Qt::AutoConnection)
  A ma; 
  B mb; 
  //Normal connection
  QObject::connect(&ma, &A::s, &mb, &B::x );
  //Lambda expression
  QObject::connect(&ma, &A::s, [=](int result) {result+=1;});

This function specifies signals and slot functions in a way that is not a macro used. The function in this form is actually a template function, and its complete prototype is similar to the following:

template<typename PointerToMemberFunction> 
         static QMetaObject::Connection connect(......)

Mode 4:

static QMetaObject::Connection connect(const QObject *sender, 
       PointerToMemberFunction signal, Functor functor)
  A ma; 
  QObject::connect(&ma, &A::s, &B::x );

The third parameter of the function supports imitation function, global function, static function and Lambda expression, but it cannot be a non static member function of the class.

The function in this form is actually a template function, and its complete prototype is similar to the following:

template<typename PointerToMemberFunction , typename Functor> 
         static QMetaObject::Connection connect(......)

Mode 5:

static QMetaObject :: Connection QObject :: connect(
  const QObject * sender, const QMetaMethod&signal,
  const QObject * receiver, const QMetaMethod& method,
  Qt :: ConnectionType type = Qt :: AutoConnection)

This function works the same way as form 1, except that it uses QMetaMethod to specify signals and slots.

Difference between form 3 and form 1

  • The SINGAL and SLOT macros of form 1 actually convert the parameters of the macro into strings. When the signal and SLOT are associated, the string is used for matching. Therefore, the names of the parameter types of the signal and SLOT must be the same in the sense of string, so the signal and SLOT cannot use the parameters of compatible type, and therefore the types of typedef or namespace cannot be used, Although their actual types are the same, form 1 cannot be used due to different string names.

  • The parameter types of form 3 signal and slot function do not need to be exactly the same, and can be implicitly converted. Form 3 also supports typedef and namespaces.

  • Form 3 specifies the signal and SLOT function in the form of a pointer, without using SIGNAL() and SLOT macros.

  • Slot functions in form 3 can be declared without slots keyword, and any member function can be a slot function. Slot functions of form 1 must be decorated with slots.

  • The slot function of form 1 is not limited by private, that is, even if the slot is private, the slot function can still be called by signal, while form 3 will make an error when using connect.

  • When the signal or slot function has an overloaded form, using form 3 may cause ambiguity errors. At this time, you can specify the signal or slot function in the form of function pointer, or use form 1, such as

class A:public QObject {
  Q_OBJECT 
singals:
  void s(int i);
};
class B:public QObject{
  Q_OBJECT
public slots:
  void x(){} 
  void x(int i){}
};
A ma; B mb; 
QObject::connect(&ma, &A::s, &mb, &B::x ); //Ambiguous error.
//It can be solved as follows (similar for signals)
QObject::connect(&ma, &A::s, &mb, 
                 static_cast<void (B::*)(int)> (&B::x));

4, Disconnect the signal from the slot

Signals and slots are disconnected using the disconnect function, a member function in the QObject class, which has multiple overloaded versions.

Mode 1:

static bool QObject::disconnect( 
  const QObject *sender, const char *signal, 
  const QObject *receiver, const char *method)

  • This function requires the use of signal and sol macros when specifying signal and method.

  • If the connection is successfully disconnected, return true; Otherwise, false is returned.

  • When any object involved is destroyed, the signal slot connection is removed.

  • 0 can be used as a wildcard for "any signal", "any receiver" or "any slot in receiver", respectively.

  • sender will never be 0.

  • If signal is 0, the slot function method in the receiver object is disconnected from all signals. Otherwise, only the specified signals are disconnected. This method can disconnect the slot from all signals.

  • If the receiver is 0, the method must also be 0.

  • If method is 0, disconnect any connection to the receiver.

  • In addition, there are several common uses

  • disconnect(&ma, 0, 0, 0); Disconnect all slots associated with all signals in the object ma.

  • disconnect(&ma , SIGNAL(s()), 0, 0); Disconnect all slots associated with the signal s in the object ma.

  • disconnect(&ma, 0, &mb, 0); Disassociate all signals in Ma from all slots in MB

Mode 2:

static bool QObject::disconnect (
  const QMetaObject::Connection &connection)

This function disconnects the association between the signal returned by the connect function and the slot. If the operation fails, it returns false.

Mode 3:

static bool QObject::disconnect(
  const QObject *sender, PointerToMemberFunction signal, 
  const QObject*receiver, PointerToMemberFunction method)

This method is the same as form 1, except that the function is specified by using the function pointer.

This function cannot disconnect the signal from the association between general functions or Lambda expressions. In this case, form 2 needs to be used to disconnect this association.

Mode 4:

static bool QObject::disconnect(
  const QObject *sender, const QMetaMethod &signal, 
  const QObject*receiver, const QMetaMethod &method)

This function is the same as form 1, except that the function is specified by using the QMetaMethod class.

Mode 5:

bool QObject::disconnect(
  const char *signal = Q_NULLPTR, 
  const QObject *receiver = Q_NULLPTR, 
  const char *method = Q_NULLPTR) const

The function is non static and is an overloaded form of form 1.

Mode 6:

bool QObject::disconnect(
  const QObject *receiver, 
  const char *method = Q_NULLPTR) const

The function is non static and is an overloaded form of form 1.

Note: if the signal and slot are associated in the form of function pointer, it is best to use the corresponding disconnect version in the form of function pointer when disconnecting the signal and slot, so as to avoid the situation that the association cannot be disconnected.

 

 

Topics: Qt