信号和槽
一、Qt信号和槽机制&emit的使用
(一)相关概念
1.信号(Signal)就是在特定情况下被发射的事件
例如PushButton 最常见的信号就是鼠标单击时发射的 clicked() 信号,一个 ComboBox 最常见的信号是选择的列表项变化时发射的 CurrentIndexChanged() 信号。GUI 程序设计的主要内容就是对界面上各组件的信号的响应,只需要知道什么情况下发射哪些信号,合理地去响应和处理这些信号就可以了。
2.槽(Slot)就是对信号响应的函数。槽就是一个函数
与一般的C++函数是一样的,可以定义在类的任何部分(public、private 或 protected),可以具有任何参数,也可以被直接调用。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。
3.信号与槽之间的关联:是用 QObject::connect() 函数实现的,其基本格式是:
QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot())); //信号发出者,处理的信号, 信号接收者,处理动作方法(槽函数)。
注解:
- sender 是发射信号的对象的名称
- signal() 是信号名称。信号可以看做是特殊的函数,需要带括号,有参数时还需要指明参数。
- receiver 是接收信号的对象名称,slot() 是槽函数的名称,需要带括号,有参数时还需要指明参数。
- SIGNAL 和 SLOT 是 Qt 的宏,用于指明信号和槽,并将它们的参数转换为相应的字符串。
(二)注意点
1.一个信号可以连接多个槽, 当一个信号与多个槽函数关联时,槽函数按照建立连接时的顺序依次执行,例如:
- connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(addFun(int));
- connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(updateStatus(int));
2.多个信号可以连接同一个槽,
- connect(ui->rBtnBlue,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
- connect(ui->rBtnRed,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
- connect(ui->rBtnBlack,SIGNAL(clicked()),this,SLOT(setTextFontColor()))
3. 一个信号可以连接另外一个信号
4.严格的情况下,信号与槽的参数个数和类型需要一致,至少信号的参数不能少于槽的参数。如果不匹配,会出现编译错误或运行错误。
5.使用signals/slots必须要加入宏Q_OBJECT
6. 当一个信号被发射时,与其关联的槽函数通常被立即执行,就像正常调用一个函数一样。只有当信号关联的所有槽函数执行完毕后,才会执行发射信号处后面的代码。
7.一定要有signals关键字,定义信号时这个关键字不可或缺,比如我们定义一个信号void signal(),一定要在前面加上关键字“signals:”,
- signals:
- void signal();
- 就像类中的public、protected、pravate一样,但是一定不能在signals前面加上public、protected、private,
- publi signals:
- 这样写是错误的
8.slots可以写可以不写,一般的函数也可以与signals下的信号关联,我们定义槽函数时可以像signals那样加上slots关键字,也可以不加,但是需要注意的是,如果加上了,那就必须加上public、protected、paivate
- public slots:
- void func();这样写
- slots:
- void func();这样写就是错的
- 当然不加上slots的一般函数也可以与信号关联
9.signals下的函数必须是void类型,而且只需要给出声明即可,具体实现QT内部自己处理,但是槽函数一定要实现,从我们角度思考是这种信号处理是一致的,但是槽函数的功能确实根据我们需要自己设计,所以有了这种差异。
10.信号与槽的参数不能是宏和函数指针
11.信号一般与emit配合使用,使用emit发射信号给关联的槽
12.connect若触发,它后面的不会再运行
(三)自定义槽
可当作槽函数的:任意的成员函数,普通全局函数,静态函数。
槽函数需要和信号一致(参数列表,返回值)如果信号没有返回值,槽函数一定没有返回值。
【举例】:让按钮2点击一下,就能改变按钮上的文本。
1.首先,在.h文件中声明:
2.在.cpp文件对该函数进行定义:
3.给按钮联结自定义的槽函数 mySlot:
connect(b2, &QPushButton::released, this, &MainWidget::mySlot);
4.运行
(四)自定义信号
1.信号必须有signals关键字来声明
2.信号没有返回值,但可以有参数。
3.信号就是函数的声明,只需声明,无需定义。
(五)emit发射信号
emit是Qt关键字,像其他关Qt扩展一样,它也会被C++预处理器转换成标准的C++代码。
使用:在A中对B使用信号
主要步骤:信号的创建,槽函数的创建,A类信号和B类槽函数的联接和使用
二、Connect几种方式
(一)说明
QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot())); //信号发出者,处理的信号, 信号接收者,处理动作方法(槽函数)。
- sender 是发射信号的对象的名称
- signal() 是信号名称。信号可以看做是特殊的函数,需要带括号,有参数时还需要指明参数。
- receiver 是接收信号的对象名称,slot() 是槽函数的名称,需要带括号,有参数时还需要指明参数。
- SIGNAL 和 SLOT 是 Qt 的宏,用于指明信号和槽,并将它们的参数转换为相应的字符串。
1.
这应该是QT4.0最为传统的方法
connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(pushButon1_clicked()));
2.
这应该是QT5.0之后退出来的绑定方法,去掉了之前的宏
connect(ui->pushButton_2,&QPushButton::clicked,this,&::MainWindow::pushButon2_clicked);
3.
C++11出来之后,新的特性就出来,static_cast 用法
connect(ui->pushButton_3, QOverload<bool>::of(&QPushButton::clicked),this,&::MainWindow::pushButon3_clicked);
4.
connect(ui->pushButton_4, QOverload<bool>::of(&QPushButton::clicked),[=](bool check){ ui->textBrowser->setText("按钮4信号绑定成功"); });
如果槽函数很简单,可以直接利用 lambda表达式进行连接,以减少代码量
这里需要注意 Lambda表达式是C++ 11 的内容,所以,需要再Pro项目文件中加入 CONFIG += C++ 11
(二)例子
1.彻底退出
connect(quitAction,&QAction::triggered,[=](){this->close(); });
2.跳转其他页面
connect(m_button[0], &ClickLabel::clicked, this, [=]() { zoom(m_button[0]); m_guideScene->show(); this->hide(); });
3.connect嵌套
//监听每个按钮的点击事件 connect(menuBtn,&MyPushButton::clicked,[=](){ ... //进入到游戏场景 this->hide(); //将选关场景隐藏掉 play = new PlayScene(i+1); //创建游戏场景 //这里注意,每次点击按钮都会新建一个游戏场景对象指针,因此返回时应当删除 ... play->show(); //显示游戏场景 connect(play,&PlayScene::chooseSceneBack,[=](){ this->setGeometry(play->geometry());//设定新窗口的坐标在屏幕上与原先窗口坐标相同 this->show(); delete play; play = NULL; }); });
//点击返回//从子页面返回 connect(backBtn,&MyPushButton::clicked,[=](){ QTimer::singleShot(500,[=](){ emit this->chooseSceneBack(); }); });
三、connect 第五个参数
(一)函数原型
connect 函数原型如下,第五个(5种)参数根据接收者和发送者是否在同一个线程不同
connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot, Qt::ConnectionType type = Qt::AutoConnection)
(二)Qt::ConnectionType 详解
1.Qt::AutoConnection
自动。一般不写就默认是这个,使用这个值会根据实际情况去判断,如果sender和receiver在同一个线程,则自动使用Qt::DirectConnection,如果不在一个线程,则自动使用Qt::QueuedConnection。
2.Qt::DirectConnection
直连。槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。
3.Qt::QueuedConnection
队列连接。槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。
4.Qt::BlockingQueuedConnection
阻塞队列连接。槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
5.Qt::UniqueConnection
唯一连接。这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。
四、disconnect函数
disconect函数的原型如下:
bool QObject::disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
(一)使用方法
一般有四种用法:
1.解除myObject对象的所有信号连接,调用后myObject对象发出的所有信号都得不到响应。
disconnect(myObject, 0, 0, 0); //等同于 myObject->disconnect();
2.解除myObject对象的mySignal信号的所有连接,调用后myObject对象发出的mySignal信号得不到响应。
disconnect(myObject, SIGNAL(mySignal()), 0, 0); //等同于 myObject->disconnect(SIGNAL(mySignal()));
3.解除myObject对象与myReceiver对象的信号连接,调用后myObject对象发出的所有信号得不到myReceiver对象的响应。
disconnect(myObject, 0, myReceiver, 0); //等同于 myObject->disconnect(myReceiver);
4.解除myObject对象的mySignal信号与myReceiver对象的mySlot槽的连接,调用后myObject对象发出的mySignal信号得不到myReceiver对象的mySlot槽的响应。
disconnect(myObject, SIGNAL(mySignal()), myReceiver, SLOT(mySlot())); //等同于 myObject->disconnect(SIGNAL(mySignal(), myReceiver, SLOT(mySlot())));
上述4种用法中,0是通配符,它表示任一信号、任一接收对象、任一槽。
五、blockSignals
blockSignals的函数原型如下:
bool QObject::blockSignals(bool block)
(一)用法
//object发出的信号被阻塞,系统不会调用任何连接到object的处理。 object->blockSignals(true); //解除信号阻塞 object->blockSignals(false);
六、案例
//widget.h
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = 0); ~Widget(){} }; #endif // WIDGET_H
//widget.cpp
#include "widget.h" #include <QPushButton> #include <QGridLayout> #include <QDebug> Widget::Widget(QWidget *parent) : QWidget(parent) { /* 创建控件 */ QPushButton *btnClick = new QPushButton("click me", this); QPushButton *btnBlock = new QPushButton("block", this); QPushButton *btnUnblock = new QPushButton("unblock", this); QPushButton *btnDisconnect = new QPushButton("disconnect", this); QGridLayout *pLayout = new QGridLayout(); pLayout->addWidget(btnClick, 0, 0); pLayout->addWidget(btnBlock, 0, 1); pLayout->addWidget(btnUnblock, 0, 2); pLayout->addWidget(btnDisconnect, 0, 3); this->setLayout(pLayout); /* 信号槽 */ connect(btnClick, &QPushButton::clicked, this, [=]() { qDebug() << "点击按钮"; }); connect(btnBlock, &QPushButton::clicked, this, [=]() { btnClick->blockSignals(true); btnBlock->setEnabled(false); btnUnblock->setEnabled(true); }); connect(btnUnblock, &QPushButton::clicked, this, [=]() { btnClick->blockSignals(false); btnBlock->setEnabled(true); btnUnblock->setEnabled(false); }); connect(btnDisconnect, &QPushButton::clicked, this, [=]() { disconnect(btnClick, &QPushButton::clicked, this, 0); }); }
//main.c
#include "widget.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); Widget w; w.show(); return a.exec(); }
结果:
1.按下"click me"按钮,窗口接收到"click me"按钮发出的clicked信号,打印"点击按钮"。
2.然后再按下"block",再次按下"click me"按钮,没有打印"点击按钮"。这是因为 "click me"按钮发出的信号被阻塞了,没有得到响应。
3.然后再按下"unblock"按钮,再次按下"click me"按钮,打印"点击按钮"。这是因为解除了"click me"按钮的信号阻塞。
4.最后按下"disconnect"按钮,此时按下"click me"按钮没有反应。这是因为解除了本窗口与"click me"按钮的clicked信号的连接。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了