前言:
信号和槽用于两个对象之间的通信,信号和槽机制是Qt的核心特征。在使用信号和槽时,一个信号可以关联到多个槽上,多个信号也可以关联到同一个槽上,一个信号还可以关联到另一个信号上。如果是一个信号关联多个槽,这些槽会一个接一个地执行,执行顺序与关联顺序相同。
之前遇到一个面试题:
- 如果同一个信号和同一个槽函数进行了多次连接(connect),是否会造成崩溃?
- 答案是不会,如果同一个信号和同一个槽函数进行多次连接,那么信号在发出后,会多次执行槽函数。
信号和槽详解:
- 声明一个信号需要使用signals关键字,在signals前面不能添加public、private、protected等限定符。信号默认是public的,可以在任何地方进行发射(emit),但是建议只在定义该信号的类及其子类中进行发射改信号。
- 信号只用声明,不需要实现。而且,信号没有返回值,只能是void类型的。
- 只有QObject类及其子类派生的类才可以使用信号和槽机制。使用信号和槽还必须在类声明的最开始处添加Q_OBJECT宏。
- 槽就是普通的C++函数,可以像一般函数一样使用。声明槽要使用slots关键字,一个槽可以是private、public或者protected类型的,也可以被声明为虚函数。
- 槽函数中的参数的类型和信号参数的类型要相对应,且不能比信号的参数多。
connect详解:
- 第一个参数为发射信号的对象。
- 第二个参数为要发射的信号。
- 第三个参数为接收信号的对象。
- 第四个参数为要执行的槽。
- 第五个参数type为关联的方式。
Qt::AutoConnection: 自动关联,默认值。如果receiver于发射信号在同一线程,则该类型表示Qt::DirectConnection;否则,该类型表示Qt::QueuedConnection。在信号被发射时决定表示那种类型。
Qt::DirectConnection: 直接关联。发射完信号后立即调用槽函数,只有槽函数执行完成返回后,发射信号后面的代码才会执行。
Qt::QueuedConnection: 队列关联。当控制返回receiver所在线程的事件循环后再执行槽函数,无论槽函数是否执行,发射信号后面的代码都会立即执行。
Qt::BlockingQueuedConnection: 阻塞队列关联。类似Qt::QueuedConnection,不过,信号线程会一直阻塞,知道槽函数返回。当receiver存在于信号线程时不能使用该类型,不然程序会死锁。
Qt::UniqueConnection: 唯一关联。这是一个标志,可以结合其他几种连接类型,使用按位或操作。这时两个对象间的相同的信号和槽只能有唯一的关联。使用这个类型主要是为了防止重复关联。
- Qt5之前的形式: connect(dlg, SIGNAL(funcSignal(int)), this, SLOT(funcSlot(int)));
- Qt5加入的一种形式: connect(dlg, &MyDialog::funcSignal, this, &Widget::funcSlot);
使用Qt5的这种形式的一个好处就是可以再编译时进行检查,信号或槽的拼写错误、槽函数参数数目多于信号的参数数目等错误都会在编译时发现。
另外,这种形式还支持C++11中的Lambda表达式,可以在关联时直接编写信号发射后要执行的代码,例如:
connect(dlg, &MyDialog::funcSignal, [=](int value){ui->label->setText(QString::number(value))});
信号和槽的自动关联:
槽函数:on_"部件的objectName"_"信号名称"。
- 以这种方式命名的槽函数可以直接和信号关联,不用再使用connect()函数。
- connectSlotsByName()函数是用来支持信号和槽自动关联的,因为setupUi()函数中自动调用了connectSlotsByName()函数,所以要使用自动关联的部件的定义都要放在setupUi()函数调用之前,且必须使用setObjectName()指定它们的objectName,只有这样才能正常使用自动关联。
断开关联 disconnect:
- 断开与一个对象多有信号的所有关联:disconnect(myObject, 0, 0, 0); 等价于myObject->disconnect();
- 断开与一个指定信号的所有关联:disconnect(myObject, SIGNAL(mySignal()), 0, 0);等价于myObject->disconnect(SIGNAL(mySignal()));
- 断开与一个指定的receiver的所有关联:disconnect(myObject, 0, myReceiver, 0); 等价于myObject->disconnect(myReceiver);
- 断开一个指定信号和槽的关联:disconnect(myObject, SIGNAL(mySignal()), myReceiver, SLOT(mySlot())); 等价于myObject->disconnect(SIGNAL(mySignal()), myReceiver, SLOT(mySlot()));还等价于disconnect(myConnection);//myConnection是进行关联时connect()的返回值。
disconnect也可以使用基于函数指针的重载形式,及Qt5后添加的不需要SIGNAL()和SLOT()宏的形式。
关于信号槽的高级应用:
Qt提供了 QObject::sender() 函数来返回发送该信号的对象的指针。
有时存在多个信号关联到同一个槽上的情况,此时如果想在槽函数中对某个信号进行特殊处理,就可以通过QObject::sender()来获取信号的对象指针,来进行判断是否是我们需要特殊处理的对象。
如果在多个信号关联到同一个槽上,而在该槽中需要对每一个信号进行不同的处理,可以使用QSignalMapper类。QSignalMapper被叫做信号映射器,可以实现对多个相同部件的相同信号进行映射,为其添加字符串或者数值参数,然后再发射出去。
QSignalMapper使用举例:
QSignalMapper *signalMapper = new QSignalMapper(this); for (i = 0; i < 5; i++) { QPushButton *button = new QPushButton(this); connect(button, SIGNAL(clicked()), signalMapper, SLOT(map())); signalMapper->setMapping(button, i); } connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(handle(int))); 然后槽中可以这样写: switch(i) //i表示是哪个button发生了clicked信号 { //自定义操作 }