1、概念
信号和槽是两种函数,这是Qt在C++基础上新增的特性,类似于其他技术中的回调的概念。
信号和槽通过程序员提前设定的“约定”,可以实现对象之间的通信,有两个先决的条件:
- 通信的对象必须都是从QObject类中派生出来的。
- 类中要有Q_OBJECT宏。
-
2、函数原型
信号槽需要在使用前进行“约定”,这个约定被称为连接。
【例子】:如果金龙考试考了100分,新宇请金龙吃饭。
// 参数1:const QObject * sender 发射者,表示因发起对象
// 参数2:const char * signal信号函数,表示因的发起动作,使用SIGNAL()包裹。
// 参数3:const QObject * receiver 接收者,表示果的发起对象
// 参数4:const char * method 槽函数,表示果发起的动作,请吃饭,SLOT()包裹。
QObject:: connect(const QObject * sender, const char * signal, const QObject * receiver, const char * method)[static]
3、实现
为了学习,把信号槽分为三种实现方式。
- 自带信号 -> 自带槽
- 自带信号 -> 自定义槽
- 自定义信号
3.1 自带信号->自带槽
这种连接方式是最简单的,因为信号函数和槽函数都是Qt内置的,只需要在文档中查询出函数后,使用connect函数连接即可。
查找函数 F1F1 查找手册
查找函数:在对应的QPushButton Class基类继承自(QAbstractButton)类,查找自带槽(Public Slots),找到对应槽函数(click),
// 按钮按下后时发射的信号
void QAbstractButton:: clicked(bool checked = false)[signal]
查询函数:在对应的Widget基类查找自带槽(Public Slots),找到对应槽函数(close),
// 关闭 槽函数
bool QWidget:: close()
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QDebug>
#include <QPushButton>class Dialog : public QDialog
{Q_OBJECTpublic:Dialog(QWidget *parent = 0);
~Dialog();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);//but 发起者、SIGNAL()包裹发起动作:点击、this 接收者、SLOT(close()) 接收结果 close关闭窗口connect(btn,SIGNAL(clicked()),this,SLOT(close()));
}
Dialog::~Dialog()
{// C++内存回收
delete btn;
}
3.2 自带信号->自定义槽
Qt不可能内置所有执行的动作代码,特别是一些复杂的操作,需要开发者手动编写槽函数。这种方式也是所有连接方式中使用最多的。
槽函数时一个特殊的成员函数,在声明的时候权限的作用主要是修饰其作为普通成员函数的使用效果,不影响信号槽的连接效果。
【例子】:点击按钮,向右边和下面移动窗口10个像素。同时输出当前窗口的坐标。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
// 添加头文件QDialog对话框基类,Qt自带类型通常使用Q开头
#include <QDialog>
#include <QDebug>
#include <QPushButton>
// 自定义对话框类
// 继承于QDialog类
class Dialog : public QDialog
{
// 是一个宏是必要条件:一个标准,有这个宏才可以用connect链接
Q_OBJECT
public:
Dialog(QWidget *parent = 0); // 构造函数
~Dialog(); // 析构函数
QPushButton *btn; // 成员变量
//声明自定义槽函数
private slots://最小权限法则,能使用私有权限就是用私有(固定写法:表示声明的是一个槽函数,connect连接时才能找到该槽函数)
void mySlot();//小驼峰命名:第一个单词首字母小写,其他大写
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
// 构造函数定义
// parent 参数
Dialog::Dialog(QWidget *parent): QDialog(parent) // 透传构造
{
//设置窗口宽高
resize(500,500);
btn = new QPushButton("移动",this);
//设置按钮位置
btn->move(200,200);
//but 发起者、SIGNAL(clicked()) 发起动作:点击、this 接收者、SLOT(close()) 接收结果 close关闭窗口
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));
}
// 析构函数类外定义
Dialog::~Dialog()
{
// C++内存回收
delete btn;
}
//槽函数定义
void Dialog::mySlot()
{
//先获取当前窗口坐标
int x = this->x();//坐标函数返回值就是坐标值
int y = this->y();
//移动坐标位置,每次获取的都是最新的坐标位置,所以不需要赋值
move(x+10,y+10);
//输出当前坐标位置
qDebug()<<x+10<<y+10;
}
3.3 自定义信号
emit关键字发射。
为了讲解,强行使用自定义信号,并非问题的最优解,主要学习写法。
信号函数是一个非常特殊的函数,因此只有声明,没有定义,没有函数体。因此无法调用,只能使用emit关键字发射。
【例子】点击按钮,关闭窗口。
3.1 节的信号连接方式
本节强行在中间加一层自定义信号的转发过程。
上图中表示信号槽连接。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QPushButton>class Dialog : public QDialog
{
Q_OBJECTpublic:
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)
{
resize(500,500);
btn = new QPushButton("杀鸡用牛刀",this);
btn->move(200,200);
//第一次信号槽链接
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));
//第二次信号槽链接
connect(this,SIGNAL(mySignal()),this,SLOT(close()));
}
Dialog::~Dialog()
{//释放类对象堆空间
delete btn;
}
void Dialog::mySlot()
{
//发射信号
emit mySignal();
}
步骤:自定义信号无法调用,只能发射信号,因此在头函数内声明自定义信号和自定义槽函数,在自定义槽函数内发射该自定义信号,并调用相关槽函数。(声明自定义槽函数和自定义信号,需要connect连接多次)
4、信号槽传参
【例子】点击按钮,按钮上显式点击的次数。
// QPushButton的文字属性为text:QString,可以使用setText更改按钮文字
// 参数:更新的文字
void setText(const QString & text)
正常解法(非信号槽传参):
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
QPushButton *btn;
//自定义槽函数声明
private slots:
void mySlot();
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
resize(500,500);//btn初始化 初始数据为”0“
btn = new QPushButton("0",this);
btn->move(200,200);
//connect 连接
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));
}
Dialog::~Dialog()
{
delete btn;
}
void Dialog::mySlot()
{
//静态局部变量
static int count = 0;
count++;
//类型转换 int-> QString,整形转字符型,number(形参):形参:要转换的数据
QString text = QString::number(count);
btn->setText(text);//更改按钮文字
}
信号槽传参法:
把上面的案例强行改为信号槽传参:
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
QPushButton *btn;
//自定义槽函数声明
private slots:
void mySlot();
void mySlot2(int);//有参自定义槽函数
signals:
void mySignal(int);//有参自定义信号函数
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
resize(500,500);
btn = new QPushButton("0",this);
btn->move(200,200);
//connect 连接
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));
connect(this,SIGNAL(mySignal(int)),this,SLOT(mySlot2(int)));
}
Dialog::~Dialog()
{
delete btn;
}
//自定义信号槽初始化
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);
}
需要注意的是:
- 理论上可以传递任意多个参数,建议最多写两个参数,多了会很冗余。如果非得传多个参数的话,可以定义成一个类,传递对象。
- 信号的参数个数必须大于等于槽的参数个数。
- 信号的参数类型要与槽的参数类型匹配。
5、对应关系
5.1 一对多
一对多指的是一个信号连接多个槽函数。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QDebug>
#include <QPushButton>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
QPushButton *btn0;
QPushButton *btn1;
QPushButton *btn2;
private slots:
void mySlot0();
void mySlot1();
void mySlot2();
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
resize(500,500);
btn0 = new QPushButton("一对多",this);
btn0->move(200,250);
//一对多的优势是可以灵活处理每个对应关系
//例如可以断开某个信号槽连接
//断开连接的函数与连接函数传参一样,只需要在前面加一个dis
//disconnect(btn0,SIGNAL(clicked()),this,SLOT(mySlot0()));
//一对多信号槽连接
connect(btn0,SIGNAL(clicked()),this,SLOT(mySlot0()));
connect(btn0,SIGNAL(clicked()),this,SLOT(mySlot1()));
//一对一信号槽链接,连接简单,但处理不灵活
btn1 = new QPushButton("一对一",this);
btn1->move(100,200);
connect(btn1,SIGNAL(clicked()),this,SLOT(mySlot2()));
}
Dialog::~Dialog()
{
delete btn0;
}
void Dialog::mySlot0()
{
qDebug()<<"A";
}
void Dialog::mySlot1()
{
qDebug()<<"B";
}
void Dialog::mySlot2()
{
mySlot0();
mySlot1();
}
在头文件内声明的函数,可以以下操作在主函数内自定添加定义
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();
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(300,300); // 多对一连接
connect(btn1,SIGNAL(clicked()),this,SLOT(btnClickedSlot()));
connect(btn2,SIGNAL(clicked()),this,SLOT(btnClickedSlot()));
}
Dialog::~Dialog()
{
delete btn1;
delete btn2;
}
void Dialog::btnClickedSlot()
{
// 通过sender函数获取发射者对象
if(sender() == btn1)
{
qDebug() << "A" ;
}
else if(sender() == btn2)
{
qDebug() << "B" ;
}
else
{
qDebug() << "对象错误" ;}
}