文章目录
- 基本概念
- 添加信号与槽
- 方法一
- 方法二
- 自定义信号与槽
- CheckBox
- 自定义QMessageBox
- 自定义信号
- 信号与槽多对多
- 一个信号连接多个槽
- 多个信号连接一个槽
- 一个信号连接一个信号连接一个槽
- 断开连接
- 方法一
- 方法二
- 判断是否连接成功
基本概念
信号(signal)和槽(slot)是Qt框架引以为豪的机制之一,用于完成界面操作的响应,是完成任意两个Qt对象之间的通信机制。
信号:特定条件下发射的事件。例如pushButton最常见的信号是鼠标单击时发射一个clicked信号。
槽:对信号响应的函数。槽就是一个类成员函数可以是任何属性的(public、protected、private),可以带有参数、也可以被直接调用,槽函数与一般的函数的区别:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。
当某个事件发生之后他就会发射一个信号,如果有对象对这个信号感兴趣,将信号和自己的一个函数(称之为槽(slot))绑定来处理这个函数,这个槽函数就会执行,也就是回调。所以槽的本质是一个类成员函数。
所有使用信号与槽的类中,必须有Q_OBJECT这个宏。
添加信号与槽
方法一
在UI界面编辑器中,我们手动从窗口添加一个信号与槽,假设我们想点击一下窗口上的按钮使窗口关闭程序退出。
我们可以先拉出一个Push Button组件,然后切换到第二个工具按钮
拖拽窗口拉出一根线后松开,会出现一个弹出框,左边是信号右边是槽
按需求选中对应的信号和槽后点击ok就可以了
这种方式的信号与槽是通过ui_mainwindow.h中的一行connect代码进行关联链接的
方法二
还有一种比较快捷的添加方法,选中某个组件后右键点击转到槽,然后选择一个信号,然后就会在按钮的父窗口mainwindow中自动生成一个槽,也就是说这个信号的处理函数,由mainwindow中自定义函数处理。
在.h文件中也会有相应的槽函数声明
这种方式添加的信号槽并未直接用到connect函数关联绑定,而是在ui_mainwindow.h的setupUi中按name规则进行绑定的。
自定义信号与槽
在QMetaObject::connectSlotsByName(MainWindow);执行后,会自动匹配特定命名规则的槽函数,也就是说我们可以手动添加一个能被自动调用的槽函数。但这种方式并不是长久之计,如果我们手动指定信号与槽的绑定连接,需要用到函数QObject::connect。
//方式一:*(常用推荐)
QObject::connect(
const QObject *sender, //发出信号的对象
const char *signal, //发送对象发出的具体信号
const QObject *receiver,//接收信号的对象
const char *method, //接收对象在接收信号之后所需要调用的函数
Qt::ConnectionType type = Qt::AutoConnection //连接类型
)
//方式一:*(常用推荐)
QMetaObject::connection QObject::connect(
const QObject *sender,
PointerToMemberFunction signal,
const QObject *receiver,
PointerToMemberFunction method,
Qt::ConnectionType type = Qt::AutoConnection //连接类型
)
方式一指定信号和槽分别用宏SIGNAL(a)和SLOT(a),这两个宏最终将信号槽转换为字符串。
connect中信号与槽需要指定函数名,参数列表形参类型但不包含其变量名,不需要返回值。
CheckBox
我们在设计界面向窗口内添加一个Check Box按钮和Label文本标记
然后手动的在主窗口头文件与源文件中声明和定义槽函数,并且在构造函数中对信号-槽进行绑定链接
槽函数声明
void slots_stateChange(int); //返回类型为 void ,参数应当与信号保持一致
定义
void MainWindow::slots_stateChange(int a){}
连接
//信号-槽 绑定连接
connect(ui->checkBox/*信号的发送者(对象)*/,
SIGNAL(stateChanged(int))/*信号*/, //SIGNAL 宏 参数 为信号的函数名和参数列表,如果有形参名应当去掉
this/*接收者(对象)*/,
SLOT(slots_stateChange(int))/*接收的槽函数*/);
通过测试我们发现槽函数的形参a在勾选时为2,不勾选为0,那么就可以根据这个参数来改变文本标记
void MainWindow::slots_stateChange(int a){
qDebug()<<"slots_stateChange :"<<a;
if(a==0){ //不勾选
ui->label->setText("不勾选");
}else if(a==2){ //勾选
ui->label->setText("勾选");
}
}
我们发现参数状态是0,2缺少1。原来1代表的是半选状态。
所以我们可以在构造函数中去设置三态
ui->checkBox->setTristate(true); //设置三态
这样就可以在槽函数中去对半选状态进行操作了
自定义QMessageBox
对于QMessageBox中提供的静态函数,只是支持已经定义好的一些按钮,却不支持自定义按钮。
所以我们尝试手动构建QMessageBox对话框,在MainWindiw构造函数中创建一个QMessageBox对象,并添加标题、文本和按钮。
QMessageBox * pMsg = new QMessageBox;
pMsg->setWindowTitle("title");
pMsg->setText("我的文本");
pMsg->addButton("是",QMessageBox::YesRole);
pMsg->addButton("否",QMessageBox::NoRole);
然后我们可以在QMessageBox中找到对应的信号,所以此时信号发送者和信号还有接收者都有了,我们只需要再创建一个槽函数就可以了。
我们在头文件中去声明一下槽函数,参数与信号保持一致,然后在源文件中定义。这样我们就可以将信号与槽进行绑定链接了。
void slots_buttonClicked(QAbstractButton *button);
void MainWindow::slots_buttonClicked(QAbstractButton *button){
}
绑定链接
connect(pMsg,SIGNAL(buttonClicked(QAbstractButton *)),this,SLOT(slots_buttonClicked(QAbstractButton *)));
随后我们要对槽函数进行实现,参数中有一个button,我们可以利用这个参数来封装按钮,这个参数是之前写的按钮的返回值,所以我们在类成员属性中定义两个QPushButton*类型的变量用来接yes和no的返回值,然后由于我们创建QMessagebox时也是在构造函数中new的对象,所以就相当于是临时变量接了一个永久的对象,所以也将这个变量在类成员属性中去声明。
然后就可以根据这个返回值做对应操作了
void MainWindow::slots_buttonClicked(QAbstractButton *button){
if(button == pYes){
qDebug()<<"是";
}else if(button == pNo){
qDebug()<<"否";
}
}
最后就是窗口显示,我们想让这个窗口与checkbox进行联系,所以就在checkbox的槽函数中如果勾选就显示窗口,不勾选就隐藏窗口就可以了。
测试一下
点击是在控制台上也会输出“是”。
自定义信号
在mainwindow.ui设计界面添加一个按钮和一个单行文本输入框,然后直接右键按钮后选择转到槽 直接创建一个发射信号的槽函数。
然后在槽函数中获取编辑框中的文本,然后发射信号并且携带参数QString。
所以我们要自己定义一个信号,定义信号的关键字是signals,不能加访问修饰符,并且只声明不定义
signals: //关键字修饰,不能加访问修饰符,只声明不定义
void signals_sendQString(QString);
因为信号不是最终的执行者,就算定义了也用不了,所以干脆就不用定义
发射信号的槽函数,信号函数前面一般加一个emit,为的是告诉看代码的人这里不是正常的函数,而是一个信号,当然不写也没关系。
//发射信号
void MainWindow::on_pb_send_clicked(){
QString str = ui->lineEdit->text(); //获取编辑框上的文本
//发射信号,并且携带参数 QString
emit signals_sendQString(str);
}
现在发送者有了,信号也有了,但是还没有接收者和接收的槽函数
我们决定发送给另一个窗口,现在只有一个mainwindow主窗口,所以要添加一个新的窗口
之后完成就可以了,就会创建出一个新的头文件、源文件以及设计窗口
我们在这个窗口上添加一个lable,用来显示接收到的文本
现在接收者的类有了,但是对象还没有定义,我们在主函数中去定义一下这个对象。
我们可以先不显示这个窗口,等到接收到信号之后在显示,那么接收的对象有了,就缺槽函数了
所以在这个窗口类中去声明定义槽函数,注意参数与信号保持一致就可以了,然后在定义时别忘了加类名作用域,否则就变成全局函数了。
public slots:
void slots_recvQString(QString);
void Dialog::slots_recvQString(QString s){
}
最后就是连接了,我们要在主函数去连接信号与槽,因为信号和槽属于不同类,所以只能在主函数中去进行连接
QObject::connect(&w,SIGNAL(signals_sendQString(QString)),&dia,SLOT(slots_recvQString(QString)));
这里connect前要加QObject,之前不用加是因为mainwindow属于是它的子类,而main函数属于是外人,就不能直接使用。
然后在槽函数中实现显示接收到的文本信号并显示窗口就可以了
void Dialog::slots_recvQString(QString s){
ui->label->setText(s); //设置显示的文本
this->show();
}
以上我们做的内容就是 通过自定义的信号槽以及手动的去绑定链接,实现了一个窗口到另一个窗口传递信息。
信号与槽多对多
一个信号连接多个槽
在窗口上添加一个水平滑块、一个时间编辑框和一个进度条共三个组件,想要通过水平滑块控制其他两个组件。
现在我们是有了信号发出者和信号,也有信号接收者,只差槽函数
所以我们去声明定义分别与两个组件有关的槽函数
void slots_timeChanged(int);
void slots_progressChanged(int);
定义就先不写了
有了槽函数之后我们就可以对信号和槽进行绑定了,因为我们是一个信号连接多个槽,所以再绑定时只有槽函数位置不同
connect(ui->horizontalSlider,SIGNAL(valueChanged(int)),this,SLOT(slots_timeChanged(int)));
connect(ui->horizontalSlider,SIGNAL(valueChanged(int)),this,SLOT(slots_progressChanged(int)));
这样信号就与这两个槽函数绑定成功了,剩下的就是对槽函数进行实现了
因为我们水平滑块默认范围是0-99,我们想让他是0-100,所以我们手动设定一下水平滑块范围
ui->horizontalSlider->setRange(0,100);
因为我们的时间编辑框想设为一天的时间,所以我们定义一个QTime,然后根据信号a的值给其赋值(一个单位代表多少秒)然后向这个组件传入这个时间就可以了。顺便我们也实值一下编辑框的显示格式
ui->timeEdit->setDisplayFormat("hh-mm:ss"); //设置显示的格式
void MainWindow::slots_timeChanged(int a){
qDebug()<<"a = "<<a;
QTime time(0,0); //定义一个初始时间
time = time.addSecs(24*6*6*a); //初始时间加上多少秒
ui->timeEdit->setTime(time); //时间组件上设置时间
}
进度条的槽函数时间就很简单了,只需要将整型信号当作进度条的值传入即可
void MainWindow::slots_progressChanged(int a){
ui->progressBar->setValue(a); //设置进度条的值
}
这样我们就实现了一个信号绑定多个槽了
多个信号连接一个槽
我们在主窗口添加两个push button按钮分别命名为门口开关和床头开关,想要让这两个开关的信号同时控制dialog窗口的显示与隐藏。
现在我们也是有信号发送者、信号和信号接收者,所以我们只需要去信号接收者类中写一个槽函数就可以进行连接了。
void slots_isShow();
有了槽函数之后我们开始连接,因为是两个窗口对象之间的连接,所以要在主函数中实现,不过在主函数中我们要调用主窗口的ui时我们发现ui被锁住了,原因是在类成员属性中我们将ui设为私有了,所以我们可以弄一个接口函数。
public:
Ui::MainWindow * GetUI(){return ui;}
同时我们要在主函数中加上ui的头文件
#include "ui_mainwindow.h"
现在就可以实现链接了
QObject::connect(w.GetUI()->pb_door,SIGNAL(clicked()),&dia,SLOT(slots_isShow()));
QObject::connect(w.GetUI()->pb_bed,SIGNAL(clicked()),&dia,SLOT(slots_isShow()));
最后就是去实现槽函数的功能,我们想要的就是如果窗口显示那么按一下按钮就关闭,如果窗口关闭,那么按按钮就显示
void Dialog::slots_isShow(){
if(this->isVisible()){ //是否可视
this->hide();
}else{
this->show();
}
}
现在就实现了只要任意一个信号发出(点击任何一个按钮),这个槽就会被调用(Dialog会显示或隐藏)。
一个信号连接一个信号连接一个槽
在主窗口添加一个SpinBox组件用来输入数字,然后在Dialog窗口上添加一个LCD Number组件,用来显示数字。我们想通过改变主窗口上的数字来同时对dialog上的数字进行更改。
正常思路就是向刚才我们写的主窗口的开关调用dialog窗口的显示一样。但是这里有一个限制,就是不能用ui的公共接口,所以我们需要自己声明一个信号,然后用已有信号连接到自己声明的信号,再用这个信号与槽进行连接。
所以先自定义一个信号,参数与spinBox发送的信号保持一致
void signals_transportValue(int); //信号,只声明
然后在mainwindow的构造函数中对两个信号进行连接
//主窗口中的 spinBOx 信号 与 主窗口的信号进行连接
connect(ui->spinBox,SIGNAL(valueChanged(int)),this,SIGNAL(signals_transportValue(int)));
这样我们只需要去主函数中将自定义信号和dialog的槽函数进行连接就可以了
先在dialog中声明一个槽函数
void slots_setValue(int);
然后连接
QObject::connect(&w,SIGNAL(signals_transportValue(int)),&dia,SLOT(slots_setValue(int)));
最后去定义一下槽函数就可以了
void Dialog::slots_setValue(int a){
ui->lcdNumber->display(a);
}
这样我们就实现了信号->自定义信号->槽函数,其实不管多少个信号之间进行传递,最终都要有一个槽函数进行接收
断开连接
在主窗口上增加两个checkbox并命名为断开连接,作用是控制水平滑块和时间组件、进度条之间是否连接
我们可以直接通过转到槽来声明定义这两个状态改变的槽函数
这里断开连接的方式有两种
方法一
当参数为0就是不勾选,那么就重新连接,如果参数为2勾选就断开连接
我们可以在勾选时直接将连接函数改为disconnect,然后不勾选时还是connect
void MainWindow::on_cb_time_stateChanged(int arg1)
{
if(arg1 == 0){ //不勾选 重新连接
connect(ui->horizontalSlider,SIGNAL(valueChanged(int)),this,SLOT(slots_timeChanged(int)));
}else if(arg1 == 2){ //勾选 断开连接
disconnect(ui->horizontalSlider,SIGNAL(valueChanged(int)),this,SLOT(slots_timeChanged(int)));
}
}
方法二
还有一种方法就是通过返回值来执行断开连接
我们在水平滑块和进度条连接函数前面加一个QMetaObject::Connection类型的返回值,这个返回值放到类成员属性中
QMetaObject::Connection con; //连接信息
con = connect(ui->horizontalSlider,SIGNAL(valueChanged(int)),this,SLOT(slots_progressChanged(int)));
然后在槽函数中如果勾选了就在disconnect()中填入返回值就可以了,如果没勾选就还是执行一遍连接函数,注意这里返回值不能省略
void MainWindow::on_cb_progress_stateChanged(int arg1)
{
if(arg1 == 0){ //不勾选 重新连接
con = connect(ui->horizontalSlider,SIGNAL(valueChanged(int)),this,SLOT(slots_progressChanged(int)));
}else if(arg1 == 2){ //勾选 断开连接
disconnect(con); //直接传递,连接信息
}
}
判断是否连接成功
返回值的作用还可以判断是否连接成功,如果返回为真就是成功,否则连接失败
if(con){ //可用于判断是否连接成功
qDebug()<<"连接成功";
}else{
qDebug()<<"连接失败";
}