四.信号和槽机制
- 概念:
- 信号:各种事件,当某个事件发生后,如某个按钮被点击了一下,它就会发出一个被点击的信号(signal)
- 槽: 响应信号的动作,某个对象接收到这个信号之后,就会做一些相关的处理动作(称为槽slot)
- 链接:Qt对象不会无故收到某个信号,要想让一个对象收到另一个对象发出的信号,这时候需要建立连接(connect),即建立信号和槽的关系
1.系统信号和槽
- 示例:建立点击按钮和窗口关闭的联系
QPushButton *quitBtn = new QPushButton("按钮1",this); connect(quitBtn,&QPushButton::clicked,this,&MyWidget::close);
- 效果:
connect函数是建立信号发送者、信号、信号接收者、槽四者关系的函数:connect(sender, signal, receiver, slot);
- 参数解释:
- sender:信号发送者
- signal:信号
- receiver:信号接收者
- slot:接收对象在接收到信号之后所需要调用的函数(槽函数)
注意:connect的四个参数都是指针,信号和槽是函数指针
- 系统自带的信号和槽的查找:
- 在帮助文档中比如我们上面的按钮的点击信号,在帮助文档中输入QPushButton
- 首先我们可以在Contents中寻找关键字 signals,但是我们发现并没有找到,这时候我们应该想到也许这个信号的被父类继承下来的
- 因此我们去他的父类QAbstractButton中就可以找到该关键字,点击signals索引到系统自带的信号有如下几个
这里的clicked就是我们要找到,槽函数的寻找方式和信号一样,只不过他的关键字是slot
2.自定义信号和槽
Qt框架默认提供的标准信号和槽不足以完成我们日常应用开发的需求,但是Qt信号和槽机制提供了允许我们自己设计自己的信号和槽
- 自定义信号:
- 声明在类的signals域下
- 没有返回值,void类型的函数
- 只有函数声明,没有定义
- 可以有参数,可以重载
- 通过emit关键字来触发信号,形式:emit object->sig(参数);
- 自定义槽函数:
- qt4 必须声明在 private/public/protected slots域下面,qt5之后可以声明public下,同时还可以是静态的成员函数,全局函数,lambda表达式
- 没有返回值,void类型的函数
- 不仅有声明,还得要有实现
- 可以有参数,可以重载
- 使用自定义信号和槽示例:
定义场景:下课了,老师跟同学说肚子饿了(信号),学生请老师吃饭(槽)
- 老师类中声明信号:
signals: void hungry();
- 学生类中声明槽:
public slots: void treat();
- 学生类实现槽函数:
void Student::treat() { qDebug() << "Student treat teacher"; }
- 信号和槽链接:使用connect
teacher = new Teacher(this); student = new Student(this); connect(teacher,&Teacher::hungury,student,&Student::treat);
- 信号触发:在窗口中声明一个公共方法下课,这个方法的调用会触发老师饿了这个信号,而响应槽函数学生请客
void MyWidget::ClassIsOver() { //发送信号 emit teacher->hungry(); }
- 带参数的信号和槽:
void hungry(QString name); //自定义信号 void treat(QString name ); //自定义槽
对于有两个重名的自定义信号和自定义的槽,直接连接会报错,所以需要利用函数指针来指向函数地址, 然后再做连接
//函数重载 函数指定具有二义性需要进行指定 //1.函数指针赋值 void(Teacher::*tqs)(QString)=&Teacher::hungry;//自动赋值对应类型的函数地址 void(Student::*sqs)(QString)=&Student::treat; connect(pTeacher,tqs,pStudent,sqs); //2.使用static_cast转换 connect(pTeacher, static_cast<void(Teacher::*)()>(&Teacher::hungry), pStudent, static_cast<void(Student::*)()>(&Student::treat)); //3.qt4之前使用宏转换 connect(pTeacher,SIGNAL(hungry()),pStudent,SLOT(treat())); connect(pTeacher,SIGNAL(hungry(QString)),pStudent,SLOT(treat(QString)));
- 效果:
注:对于使用SIGNAL()和SLOT()宏指定函数,虽然使用简单,但是宏只是做字符串替换,编译时不会检查,所以运行时可能报错
3.信号和槽的拓展
- 一个信号可以和多个槽相连
如果是这种情况,这些槽会一个接一个的被调用,但是槽函数调用顺序是不确定的
- 多个信号可以连接到一个槽
只要任意一个信号发出,这个槽就会被调用
- 一个信号可以连接到另外的一个信号
当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。这里还是使用connect函数,只是信号的接收者和槽函数换成另一个信号的发送者和信号函数
- 信号和槽可以断开连接
使用disconnect函数,当初建立连接时connect参数怎么填的,disconnect里边4个参数也就怎么填。当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。
- 信号和槽函数参数类型和个数必须同时满足两个条件
信号函数的参数个数必须大于等于槽函数的参数个数
信号函数的参数类型和槽函数的参数类型必须一一对应
4.槽函数使用Lambda表达式
- 以QPushButton点击事件为例:
connect(btn,&QPushButton::clicked,[=](){ qDebug()<<"Clicked"; });
- 效果:
- 说明:
- 使用Lambda表达式作为槽的时候不需要填入信号的接收者
- 当点击按钮的时候,clicked信号被触发,lambda表达式也会直接运行
- 当然lambda表达式还可以指定函数参数,这样也就能够接收到信号函数传递过来的参数了
- 由于lambda表达式比我们自己自定义槽函数要方便而且灵活得多,所以在实现槽函数的时候优先考虑使用Lambda表达式
- 一般使用习惯也是lambda表达式外部函数的局部变量全部通过值传递捕获进来,也就是:[ = ] ( ) { }的形式
表达式也会直接运行
3. 当然lambda表达式还可以指定函数参数,这样也就能够接收到信号函数传递过来的参数了
4. 由于lambda表达式比我们自己自定义槽函数要方便而且灵活得多,所以在实现槽函数的时候优先考虑使用Lambda表达式
5. 一般使用习惯也是lambda表达式外部函数的局部变量全部通过值传递捕获进来,也就是:[ = ] ( ) { }的形式