Qt提供了信号与槽机制用于完成界面操作的响应,是完成任意两个Qt对象之间通信的机制。其中,信号会在某个特定情况或动作下被触发,槽是等同与接受并处理信号的函数。
每个Qt对象都包含若干个预定义的信号和槽,当某一个特定事件发生时,一个信号被发送,与信号相关联的槽则会响应信号并完成相应的处理。当一个类被继承时,该类的信号与槽也同时被继承,也可以根据自定义信号与槽。
信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),但是不能说信号根本没有这个数据,你就要在槽函数中使用(就是槽函数的参数比信号的多,这是不允许的)。
QT使用connect()函数连接信号和槽。
1、QT5的connect()函数最常用的一般形式:
connect(sender, signal, receiver, slot);
/*
参数:
sender:发出信号的对象
signal:发送对象发出的信号
receiver:接收信号的对象
slot:接收对象在接收到信号之后所需要调用的函数
例如:QObject::connect(&button, &QPushButton::clicked,&app, &QApplication::quit);
*/
2、QT4的 connect()函数最常用的一般形式如下:
connect(sender, SIGNAL(signal()), receiver, SLOT(slot));
/*
参数:
sender:发出信号的对象的地址
signal:发送对象发出的信号
receiver:接收信号的对象
slot:接收对象在接收到信号之后所需要调用的函数
例如:QObject::connect(&button, SIGNAL(clicked()),this, SLOT(quit()));
*/
虽然相对于QT5的形式,QT4的形式书写起来更方便,但有两个很大的缺点:
注意信号与槽通过connect函数连接后,也可以使用disconnect()函数解除连接,参数与connect()一致。
使用connect()可以让我们连接系统提供的信号和槽。但是,Qt 的信号槽机制并不仅仅是使用系统提供的那部分,还会允许我们自己设计自己的信号和槽。
#include
////////// newspaper.h //////////
//继承于QObject类
class Newspaper : public QObject
{
/* 提供信号与槽机制 */
Q_OBJECT
public:
Newspaper(const QString & name) :
m_name(name)
{
}
/* 槽函数,用于发送信号 */
void send()
{
/*发送newPaper信号*/
emit newPaper(m_name);
}
signals:
/* 声明newPaper信号而无需定义 */
void newPaper(const QString &name);
private:
QString m_name;
};
////////// reader.h //////////
#include
#include
class Reader : public QObject
{
Q_OBJECT
public:
Reader() {}
void receiveNewspaper(const QString & name)
{
qDebug() << "Receives Newspaper: " << name;
}
};
////////// main.cpp //////////
#include
#include "newspaper.h"
#include "reader.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
Newspaper newspaper("Newspaper A");
Reader reader;
/* 关联信号与槽 */
QObject::connect(&newspaper, &Newspaper::newPaper,
&reader, &Reader::receiveNewspaper);
newspaper.send();
return app.exec();
}
1、只有继承了QObject类的类,才具有信号槽的能力。所以,为了使用信号槽,必须继承QObject。凡是QObject类(不管是直接子类还是间接子类),都应该在第一行代码写上Q_OBJECT。不管是不是使用信号槽,都应该添加这个宏。这个宏的展开将为我们的类提供信号槽机制、国际化机制以及 Qt 提供的不基于 C++ RTTI 的反射能力。
2、在类的声明中,除了public,private外,还有signals 块,且其所列出的,就是该类的信号。信号就是一个个的函数名,返回值是 void(因为无法获得信号的返回值,所以也就无需返回任何值),参数是该类需要让外界知道的数据。信号作为函数名,不需要在 cpp 函数中添加任何实现。
3、emit 是 Qt 对 C++ 的扩展,是一个关键字(其实也是一个宏)。emit 的含义是发出,也就是发出newPaper()信号。感兴趣的接收者会关注这个信号,我们也可以通过传参的形式将实际的报纸名字m_name当做参数传给这个信号。当接收者连接这个信号时,就可以通过槽函数获得实际值。这样就完成了数据从发出者到接收者的一个转移。
4、Qt 5 中,任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数。如果槽函数是成员函数,会受到类的prublic、private、protected影响,如果信号是 private 的,这个信号就不能在类的外面连接,也就没有任何意义。
5、一个信号可以连接多个槽函数,这时候槽函数会一个一个调用,但调用顺序是不确定的。
6、槽函数可以被取消链接,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。
7、在连接信号和槽的时候,槽函数可以使用Lambda表达式的方式进行处理。
8、信号可以重载,在使用重载的信号时,需要借助函数指针。例如:
/* func为无参数的mysignal信号 */
void (SubWidget::*func)() = &SubWidget::mysignal;
/* func1为有参数的mysignal信号 */
void (SubWidget::*func1)(int,QString) = &SubWidget::mysignal;
connect(&w,func,this,&MyWidget::MySlot2);
connect(&w,func1,this,&MyWidget::MySlot3);
C++11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。如果要使用Lambda表达式,必须在QT的.pro文件中加入一句 CONFIG += C++11,以引入C++11模块。首先看一下Lambda表达式的基本构成:
[函数对象参数] (操作符重载函数参数) mutable或exception -> 返回值{函数体}
① 函数对象参数;
[],标识一个Lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。函数对象参数有以下形式:
② 操作符重载函数参数;
标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。
③ 可修改标示符;
按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。
④ 错误抛出标示符;
exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int)
⑤ 函数返回值;
->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
⑥ 是函数体;
{},标识函数的实现。
示例如下:
/* 定义一个局部静态变量按钮b */
QPushButton* b = new QPushButton(this);
/* 定义一个局部变量a */
int a = 10;
/* 使用Lambda表达式关联信号,此时connect只有三个参数 */
connect(b2,&QPushButton::clicked,
[=](bool ch)mutable
{
b1->setText("4444");
a = 1111;
qDebug()<