三、信号槽
- 概念
信号和槽是两种函数,这是Qt在C++基础上新增的特性,类似于其他技术中的回调的概念。
信号槽通过程序员提前设定的“约定”,可以实现对象之间的通信,有两个先决条件。
- 通信的对象都是在QOBject类中派生出来的。
QOBject类是Qt所有类型的基类。
- 类中要有Q_OBJECT宏。
- 函数原型
信号槽需要在使用进行“约定”。这个约定也称为连接。
【例子】:如果新周考试考100分,那么宵宾请新周吃饭。
// 参数1:发射者,表示因发起的对象【新周】
// 参数2:信号函数,表示因发起的动作【考100】
// 参数3:接收者,表示果发起的对象【宵宾】
// 参数4:槽函数,表示果发起的动作【请吃饭】
connect(const QObject * sender, const char * signal, const QObject * receiver, const char * method)[static]
- 实现
为了学习,把信号槽分为三种实现的方式。
- 自带信号→自带槽
- 自带信号→自定义槽
- 自定义信号
3.1 自带信号→自带槽
这种连接方式是最简单的,因为信号函数和槽函数都是Qt内置的,只需要在文档中查询出函数后,使用connect函数连接即可。
【例子】:点击按钮,关闭窗口。
// 点击按钮,发射此信号
void QAbstractButton::clicked(bool checked = false)[signal]
// 关闭窗口槽函数
bool QWidget::close()[slot]
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
private:
QPushButton *btn; // 成员变量
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
// 设置窗口的宽高
resize(500,500);
btn = new QPushButton("关闭",this);
// 设置按钮位置
btn->move(200,250);
// 连接信号槽【点击按钮,关闭窗口】
// 参数1:按钮对象
// 参数2:点击信号函数clicked
// 参数3:窗口对象
// 参数4:关闭窗口槽函数close
connect(btn,SIGNAL(clicked()),this,SLOT(close()));
}
Dialog::~Dialog()
{
// 堆内存回收
delete btn;
}
3.2 自带信号→自定义槽
Qt不可能内置所有执行的动作代码,特别是一些复杂的动作,需要开发者手动编写槽函数,这种方式是所有连接方式中使用最多的。
槽函数实际上是一种特殊的成员函数,在声明的时候权限的作用主要是修饰其作为普通成员函数的使用效果,不影响信号槽的连接效果。
【例子】:点击按钮,向右边和下边移动窗口10个相似,同时输出当前窗口的坐标。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
#include <QDebug>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
private:
QPushButton *btn; // 成员变量
private slots:
void mySlot(); // 小驼峰命名:第一个单词小写,其他单词首字母大写
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
// 设置窗口的宽高
resize(500,500);
btn = new QPushButton("关闭",this);
// 设置按钮位置
btn->move(200,250);
// 连接信号槽
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));
}
// 自定义槽函数实现
void Dialog::mySlot()
{
// 先获取当前的坐标
int x = this->x();
int y = this->y();
// 移动坐标位置
move(x+10,y+10);
// 输出当前位置
qDebug() << x+10 << y+10;
}
Dialog::~Dialog()
{
// 堆内存回收
delete btn;
}
3.3 自定义信号
为了讲解,强行使用自定义信号,并非问题的最优解,主要学习写法。
信号函数是一种非常特殊的函数,因为只有声明,没有定义。即没有函数体,因此无法调用,只能使用emit关键字发射。
【例子】:点击按钮,关闭窗口。
3.1 的信号连接方式。
本节中强行在中间加一层自定义信号的转发过程。
上图中→表示信号槽连接。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
private:
QPushButton *btn;
private slots:
void mySlot();
// 自定义信号
signals:
void mySignal();
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
// 创建按钮对象
btn = new QPushButton("关闭",this);
btn->move(100,150);
// 信号槽连接
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));
// 第二次槽函数连接
connect(this,SIGNAL(mySignal()),this,SLOT(close()));
}
void Dialog::mySlot()
{
// 发射自定义信号
emit mySignal();
}
Dialog::~Dialog()
{
delete btn;
}
4、信号槽传参
【例子】点击按钮,按钮上显示点击的次数。
QPushButton的按钮文字属性为text : QString,可以使用setter更改按钮文字。
// 设置按钮显示的文字
// 显示的文字,QString类型
void setText(const QString & text)
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private:
Ui::Dialog *ui;
QPushButton *btn;
private slots:
void mySlot();
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
btn = new QPushButton("0",this);
btn->move(100,150);
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));
}
Dialog::~Dialog()
{
delete btn;
delete ui;
}
void Dialog::mySlot()
{
// 静态局部变量
static int count = 0;
count++;
// 类型转换 int → QString
QString text = QString::number(count);
// 更改按钮文字
btn->setText(text);
}
把上面的案例强行改为信号槽传参:
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private:
Ui::Dialog *ui;
QPushButton *btn;
private slots:
void mySlot();
void mySlot2(int); // 与mySignal(int)连接的自定义槽函数
signals:
void mySignal(int); // 带参数的信号函数
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
btn = new QPushButton("0",this);
btn->move(100,150);
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));
connect(this,SIGNAL(mySignal(int)),
this,SLOT(mySlot2(int)));
}
Dialog::~Dialog()
{
delete btn;
delete ui;
}
void Dialog::mySlot()
{
// 静态局部变量
static int count = 0;
count++;
emit mySignal(count);
}
void Dialog::mySlot2(int count)
{
// 类型转换 int → QString
QString text = QString::number(count);
// 更改按钮文字
btn->setText(text);
}
需要注意的是:
- 理论上可以传递多个参数,建议最多写两个参数,多了会很冗余。如果非得传递多个参数,可以定义成一个类,传递对象。
- 信号的参数个数必须大于等于槽函数参数个数。
- 信号的参数类型要与槽的参数相匹配。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
#include <QDebug>
class Demo
{
public:
int a = 10;
int b = 20;
double c = 3.23;
float d = 1.1;
};
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
private:
QPushButton *btn;
private slots:
void mySlot(); // 自定义槽函数
void mySlot2(const Demo &); // 与mySignal(int)连接的自定义槽函数
signals:
void mySignal(const Demo &); // 带参数的信号函数
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
resize(500,500);
btn = new QPushButton("0",this);
btn->move(150,150);
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));
connect(this,SIGNAL(mySignal(const Demo &)),this,SLOT(mySlot2(const Demo &)));
}
void Dialog::mySlot()
{
Demo demo;
// 发射带参数的自定义信号函数
emit mySignal(demo);
}
void Dialog::mySlot2(const Demo &demo)
{
qDebug() << demo.a;
qDebug() << demo.b;
qDebug() << demo.c;
qDebug() << demo.d;
}
Dialog::~Dialog()
{
delete btn;
}
5、对应关系
5.1 一对多
一对多指的是一个信号可以连接多个槽函数。
对于一对多的连接关系,可以合并为一对一,因为槽函数也是一个成员函数,可以整合到一个槽函数中进行连接。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
#include <QDebug>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
private:
QPushButton *btn1; // 一对多
QPushButton *btn2; // 一对一
private slots:
void mySlot1();
void mySlot2();
void mySlot3();
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
resize(500,500);
btn1 = new QPushButton("一对多",this);
btn1->move(200,200);
// 一对多的优势就是可以灵活处理每个对应关系
// 例如可以断开某个信号槽的连接
// 断开连接的函数与连接槽函数一样,只需要在函数名称前面加dis
disconnect(btn1,SIGNAL(clicked()),this,SLOT(mySlot2()));
// 一对多
connect(btn1,SIGNAL(clicked()),this,SLOT(mySlot1()));
connect(btn1,SIGNAL(clicked()),this,SLOT(mySlot2()));
// 一对一
btn2 = new QPushButton("一对一",this);
btn2->move(200,250);
connect(btn2,SIGNAL(clicked()),this,SLOT(mySlot3()));
disconnect(btn1,SIGNAL(clicked()),this,SLOT(mySlot2()));
}
Dialog::~Dialog()
{
}
void Dialog::mySlot1()
{
qDebug() << "A" ;
}
void Dialog::mySlot2()
{
qDebug() << "B" ;
}
void Dialog::mySlot3()
{
mySlot1();
mySlot2();
}
5.2 多对一
多对一指的是多个信号连接同一个槽函数,多对一的问题在于槽函数无法直接判断那个信号触发的槽函数调用,但是可以通过sender函数在槽函数中获得发射者对象,通过对象的比对判断来源。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
#include <QDebug>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
private:
QPushButton *btn1;
QPushButton *btn2;
private slots:
// 多对一的槽函数
void btnClickedSlot();
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
resize(500,500);
btn1 = new QPushButton("多对一A",this);
btn1->move(200,200);
btn2 = new QPushButton("多对一B",this);
btn2->move(200,250);
// 多对一
connect(btn1,SIGNAL(clicked()),
this,SLOT(btnClickedSlot()));
connect(btn2,SIGNAL(clicked()),
this,SLOT(btnClickedSlot()));
}
Dialog::~Dialog()
{
delete btn1;
delete btn2;
}
void Dialog::btnClickedSlot()
{
if(btn1 == sender())
{
qDebug() << "A" ;
}
else if(btn2 == sender())
{
qDebug() << "B" ;
}
else
{
qDebug() << "对象错误" ;
}
}