嵌入式学习-QT-Day03
三、信号槽
1、概念
2、函数原型
3、实现
3.1 自带信号→自带槽
3.2 自带信号→自定义槽
3.3 自定义信号
4、信号槽传参
5、对应关系
5.1 一对多
5.2 多对一
三、信号槽
1、概念
信号和槽是两种函数,这是Qt在C++的基础上新增的特性,类似于其他技术中的回调的概念。
信号槽通过程序员提前设定的“约定”,可以实现对象之间的通信,有两个先决条件:
- 通信的对象必须是从QObject类中派生出来的。
- 类中要有Q_OBJECT宏。
2、函数原型
信号槽需要在使用前进行“约定”,这个“约定”也被称为连接,使用下面的函数实现:
【例子】:如果静宜考试考了100分,那么方宇请客吃日料。
// 参数1:发射者。【静宜】
// 参数2:信号函数,通常是一个动词函数表示(考试考100),使用SIGNAL()包裹
// 参数3:接收者。【方宇】
// 参数4:槽函数,通常是一个动词函数表示(请吃日料),使用SLOT()包裹
QMetaObject::Connection QObject::connect(const QObject * sender, const char * signal, const QObject * receiver, const char * method)[static]
第五个参数(了解)
QMetaObject::Connection QObject:: connect(const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection)
第五个参数type就是用来指定连接的类型。它可以是以下几种枚举值之一:Qt::AutoConnection:Qt将自动选择连接类型。如果信号和槽在同一线程中,那么使用Qt::DirectConnection,否则使用Qt::QueuedConnection。Qt::DirectConnection:直接连接。当信号发出时,槽函数会立即在发射信号的线程上被调用。这意味着如果信号和槽位于不同线程,那么可能会出现线程安全问题。Qt::QueuedConnection:队列连接。当信号发出时,Qt会将事件放入接收对象的事件队列中,然后等待目标对象所在的线程处理该事件。因此,槽函数的执行将延迟到接收对象所在的线程中执行,这通常用于跨线程连接。Qt::BlockingQueuedConnection:阻塞队列连接。与Qt::QueuedConnection类似,但是发送者将被阻塞,直到接收者处理完槽函数为止。Qt::UniqueConnection:确保每个连接只建立一次,避免重复连接。
3、实现
为了学习,把信号槽分为三种实现的方式。
- 自带信号→自带槽
- 自带信号→自定义槽
- 自定义信号
3.1 自带信号→自带槽
这种连接方式是最简单的,因为信号函数和槽函数都是Qt内置的,只需要在文档中查询到对应的函数后,使用connect函数连接即可。
【例子】点击按钮,关闭窗口。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#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); // 连接信号槽【点击按钮,关闭窗口】
// 参数1:按钮对象
// 参数2:点击信号函数clicked
// 参数3:窗口对象
// 参数4:槽函数
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_OBJECTpublic:
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 = new QPushButton("移动",this);
// 设置按钮的位置
btn->move(200,250); // 连接槽函数
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));
}// 槽函数类外定义
void Dialog::mySlot()
{
// 获取当前的坐标
int x1 = this->x();
int y1 = this->y(); // 移动坐标位置
move(x1+10,y1+10); // 输出当前坐标
qDebug() << x1+10 << y1+10;
}Dialog::~Dialog()
{
delete btn;
}
3.3 自定义信号
为了讲解,强行使用自定义信号,并非问题的最优解,主要学习写法。
信号函数是一种非常特殊的函数,因为其只有声明,没有定义,即没有函数体,因此无法调用,只能使用emit关键字发射。
【例子】点击按钮,关闭窗口。
3.1 节的信号连接方式。
本节强行在中间加一层自定义信号的转发过程。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QPushButton>
#include <QDebug>class Dialog : public QDialog
{
Q_OBJECTpublic:
Dialog(QWidget *parent = 0);
~Dialog();
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,250); // 连接槽函数
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 ,QString可以使用setter函数更改按钮内容。
void setText(const QString & text)
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QPushButton>
#include <QDebug>class Dialog : public QDialog
{
Q_OBJECTpublic:
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 = new QPushButton("0",this);
// 设置按钮的位置
btn->move(200,250); // 连接槽函数
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));
}// 槽函数类外定义
void Dialog::mySlot()
{
// 静态局部变量
static int count = 0;
count++; // 类型转换int -> QString
QString text = QString::number(count); // 更改按钮文字
btn->setText(text);}Dialog::~Dialog()
{
delete btn;
}
把上面的案例强行改为信号槽传参。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QPushButton>
#include <QDebug>class Dialog : public QDialog
{
Q_OBJECTpublic:
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,250); // 连接槽函数
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot())); connect(this,SIGNAL(mySignal(int)),
this,SLOT(mySlot2(int)));
}// 槽函数类外定义
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::~Dialog()
{
delete btn;
}
需要注意的是:
- 理论上可以传递任意多个参数。建议最多写两个参数,多了会很冗余。如果非得传递多个参数,可以定义成一个类,传递对象。
- 信号的参数个数必须大于等于槽的参数个数。
- 信号的参数类型要与槽的参数类型匹配。
传递类对象的代码如下:
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_OBJECTpublic:
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_OBJECTpublic:
Dialog(QWidget *parent = 0);
~Dialog();private:
QPushButton *btn;
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); btn = new QPushButton("一对多",this);
btn->move(200,200); btn2 = new QPushButton("一对一",this);
btn2->move(200,250); // 一对多
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot1()));
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot2())); // 一对多这种连接方式的优势是可以灵活处理每个对应关系
// 例如可以断开某个信号槽的连接
// 断开连接的函数与连接函数一样,只需要在函数名称前面加一个dis
disconnect(btn,SIGNAL(clicked()),this,SLOT(mySlot1())); // 一对一
connect(btn2,SIGNAL(clicked()),this,SLOT(mySlot3()));
}Dialog::~Dialog()
{
delete btn;
}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_OBJECTpublic:
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()
{
// 通过sender函数可以获得发射者对象
if(sender() == btn1)
{
qDebug() << "A";
}
else if(sender() == btn2)
{
qDebug() << "B";
}
else
{
qDebug() << "对象错误";
}
}