写在前面
信号与槽机制是Qt中最重要的特性之一,也是其与其他GUI框架的主要区别之一。信号与槽机制允许不同对象之间进行通信和交互,从而实现程序的模块化和可重用性。
在Qt中,信号是一种事件,它可以被任何对象接收并执行相应的操作。信号通常由一个字符串参数组成,用于描述信号所关联的操作。例如,一个按钮被点击时会发出一个clicked()信号,一个文本框内容改变时会发出textChanged()信号等。
槽是一种与信号相关联的方法,它是一个无参数的虚函数。当某个对象接收到与其相关的信号时,它会自动调用与之关联的槽函数。槽函数可以执行任何操作,包括修改对象的状态、更新视图等。例如,当一个按钮被点击时,程序会自动调用按钮的clicked()槽函数。
通过使用信号与槽机制,Qt应用程序可以实现以下功能:
①实现对象之间的通信和交互。
②实现模块化和可重用性。
③支持多线程编程。
④支持自定义控件和插件。
connect
Qt中的connect函数用于将信号和槽连接起来,使得当信号被发射时,槽函数会被自动调用。
connect函数的语法如下:
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
其中,参数含义如下:
- sender:发送信号的对象。
- signal:信号的名称。
- receiver:接收信号的对象。
- method:槽函数的名称。
- type:连接类型,默认为Qt::AutoConnection。
connect函数返回一个QMetaObject::Connection对象,该对象表示连接的关系。可以使用该对象进行断开连接等操作。
connect简单示例
这里创建一个QWidget应用程序,在Widget中添加一个按钮,实现点击该按钮关闭整个窗口的功能。
核心代码如下:
#include "widget.h"
#include "mypushbutton.h"
#include <QDebug>
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
MyPushButton* myBtn = new MyPushButton(this);
myBtn->setText("我的按钮");
myBtn->move(0, 100);
//信号和槽基本使用
connect(myBtn, &MyPushButton::clicked, this, &QWidget::close);
}
Widget::~Widget()
{
qDebug() << "Widget 析构函数";
}
自定义信号和槽
在Qt中,若窗口或控件当前的信号和槽不满足自己的需求,或者想使自己定义的类拥有其特有的信号和槽,Qt是支持使用自定义的信号和槽的。
自定义信号和槽,需注意:
①自定义信号和槽的类需继承自 QObject。自定义信号和槽必须继承自 QObject,因为它们是 Qt 元对象系统的一部分。
②使用 Q_OBJECT 宏。为了使自定义信号和槽能够在运行时调用其 metaObject() 方法,必须在类的头文件中使用 Q_OBJECT 宏进行标记。
自定义信号
自定义信号,需要注意以下几点:
①自定义信号需要在signals:限定符下声明
②自定义信号只需声明,无需实现
③自定义信号可以有参数,可以重载。这里下面会单独介绍
自定义槽
自定义槽函数,需要注意以下几点:
①需在public slots:下声明
②返回值为void
③需要声明,也需要实现
④可以有参数,可以重载。这里下面会单独介绍
这里实现一个简单的无参的自定义信号和槽的应用。新建一个DIYSignal和DIYSlot类,为支持自定义信号和槽,这两个类都需继承自QObject类。
添加后的工程文件内容如下:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++17
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
diysignal.cpp \
diyslot.cpp \
main.cpp \
mypushbutton.cpp \
widget.cpp
HEADERS += \
diysignal.h \
diyslot.h \
mypushbutton.h \
widget.h
在DIYSignal类的signal: 限定符下声明我们的自定义信号:
#ifndef DIYSIGNAL_H
#define DIYSIGNAL_H
#include <QObject>
class DIYSignal : public QObject
{
Q_OBJECT
public:
explicit DIYSignal(QObject *parent = nullptr);
signals:
//自定义信号
void mySignal();
};
#endif // DIYSIGNAL_H
因为信号只需声明无需实现,因此这里省略DIYSignal.cpp的代码。
在DIYSlot类的public slots: 限定符下声明自定义槽函数:
#ifndef DIYSLOT_H
#define DIYSLOT_H
#include <QObject>
class DIYSlot : public QObject
{
Q_OBJECT
public:
explicit DIYSlot(QObject *parent = nullptr);
signals:
public slots:
//自定义槽函数
void mySlot();
};
#endif // DIYSLOT_H
在DIYSlot.cpp中实现自定义槽函数:
#include "diyslot.h"
#include <QDebug>
DIYSlot::DIYSlot(QObject *parent)
: QObject{parent}
{
}
void DIYSlot::mySlot()
{
qDebug() << "响应自定义信号,自定义槽函数";
}
void DIYSlot::mySlot(QString qsTips)
{
qDebug() << "响应带参的自定义信号,带参自定义槽函数, 参数:" << qsTips;
}
然后在QWidget窗口的构造中自动连接触发自定义信号:
#include "widget.h"
#include "mypushbutton.h"
#include <QDebug>
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
MyPushButton* myBtn = new MyPushButton(this);
myBtn->setText("我的按钮");
myBtn->move(0, 100);
//信号和槽基本使用
connect(myBtn, &MyPushButton::clicked, this, &QWidget::close);
//自定义信号和槽
m_pDIYSignal = new DIYSignal(this);
m_pDIYSlot = new DIYSlot(this);
//连接
connect(m_pDIYSignal, &DIYSignal::mySignal, m_pDIYSlot, &DIYSlot::mySlot);
//触发
emit m_pDIYSignal->mySignal();
}
Widget::~Widget()
{
qDebug() << "Widget 析构函数";
}
运行后可以看到自定义信号被触发后,相应的槽函数会被调用:
重载时的自定义信号和槽
当自定义信号和槽有重载版本时,在connect连接时需要明确指出连接的信号和槽的版本。
例,这里添加重载的信号和槽版本:
DIYSignal.h如下:
#ifndef DIYSIGNAL_H
#define DIYSIGNAL_H
#include <QObject>
class DIYSignal : public QObject
{
Q_OBJECT
public:
explicit DIYSignal(QObject *parent = nullptr);
signals:
//自定义信号
void mySignal();
//重载版本
void mySignal(QString qsTips);
};
#endif // DIYSIGNAL_H
DIYSlot.h如下:
#ifndef DIYSLOT_H
#define DIYSLOT_H
#include <QObject>
class DIYSlot : public QObject
{
Q_OBJECT
public:
explicit DIYSlot(QObject *parent = nullptr);
signals:
public slots:
//自定义槽函数
void mySlot();
//重载版本
void mySlot(QString qsTips);
};
#endif // DIYSLOT_H
DIYSlot.cpp如下:
#include "diyslot.h"
#include <QDebug>
DIYSlot::DIYSlot(QObject *parent)
: QObject{parent}
{
}
void DIYSlot::mySlot()
{
qDebug() << "响应自定义信号,自定义槽函数";
}
void DIYSlot::mySlot(QString qsTips)
{
qDebug() << "响应带参的自定义信号,带参自定义槽函数, 参数:" << qsTips;
}
如果还想之前那样连接,编译器就无法得到这里连接的是哪个信号和槽:
//自定义信号和槽
m_pDIYSignal = new DIYSignal(this);
m_pDIYSlot = new DIYSlot(this);
//连接
connect(m_pDIYSignal, &DIYSignal::mySignal, m_pDIYSlot, &DIYSlot::mySlot);
//触发
emit m_pDIYSignal->mySignal();
编译会报错提示:
因此这里需明确指定信号和对应的槽函数版本:
//自定义信号和槽
m_pDIYSignal = new DIYSignal(this);
m_pDIYSlot = new DIYSlot(this);
//连接
void (DIYSignal::*mySignalFun)() = &DIYSignal::mySignal;
void (DIYSlot::*mySlotFun)() = &DIYSlot::mySlot;
connect(m_pDIYSignal, mySignalFun, m_pDIYSlot, mySlotFun);
//触发
emit m_pDIYSignal->mySignal();
//自定义信号和槽有重载版本时,连接需要明确指出重载的版本
void (DIYSignal::*mySignalFunWithArg)(QString) = &DIYSignal::mySignal;
void (DIYSlot::*mySlotFunWithArg)(QString) = &DIYSlot::mySlot;
connect(m_pDIYSignal, mySignalFunWithArg, m_pDIYSlot, mySlotFunWithArg);
emit m_pDIYSignal->mySignal("tips");
使用函数指针明确重载版本,需要注意别忘了添加对应类的作用域。
运行后可以看到两个信号被触发,对应的槽函数会被调用:
自定义信号和槽的扩展
除了上面提到的重载时的情况,自定义信号和槽也还有以下扩展:
①信号可以连接信号
②一个信号可以连接到多个槽函数
③多个信号可以连接同一个槽函数
④信号和槽函数的参数,必须一一对应
⑤信号和槽函数的参数个数可以不同,但信号的参数个数必须多于槽函数的参数个数,且相等部分的参数类型必须一一对应(同第④点)
⑥可以使用Qt4版本以前的信号和槽的连接方式
下面将逐一举例介绍。
信号可以连接信号
Qt的信号和槽机制并没有强制要求信号只能和槽连接,也可以通过一个信号触发另一个信号。
上面示例中,都是程序运行后自动触发自定义信号,这里调整成点击按钮,来触发自定义信号。
//自定义信号和槽
m_pDIYSignal = new DIYSignal(this);
m_pDIYSlot = new DIYSlot(this);
//连接
void (DIYSignal::*mySignalFun)() = &DIYSignal::mySignal;
void (DIYSlot::*mySlotFun)() = &DIYSlot::mySlot;
connect(m_pDIYSignal, mySignalFun, m_pDIYSlot, mySlotFun);
//触发
//emit m_pDIYSignal->mySignal();
//自定义信号和槽有重载版本时,连接需要明确指出重载的版本
void (DIYSignal::*mySignalFunWithArg)(QString) = &DIYSignal::mySignal;
void (DIYSlot::*mySlotFunWithArg)(QString) = &DIYSlot::mySlot;
connect(m_pDIYSignal, mySignalFunWithArg, m_pDIYSlot, mySlotFunWithArg);
//emit m_pDIYSignal->mySignal("tips");
//信号连接信号
QPushButton* myDiySigBtn = new QPushButton(this);
myDiySigBtn->setText("触发无参自定义信号");
connect(myDiySigBtn, &QPushButton::clicked, m_pDIYSignal, mySignalFun);
一个信号可以连接到多个槽函数
一个信号可以连接到多个槽函数,但必须遵循上面的第④、⑤点,即自定义信号的参数必须大于等于槽函数的参数,且相等部分的参数类型需一一对应。
这里使用带参的自定义信号,分别连接无参和带参的槽函数。
ps:因为无参的自定义信号,不能连接带参的槽函数。不满足第⑤点信号的参数大于等于槽函数的参数个数条件。
//自定义信号和槽
m_pDIYSignal = new DIYSignal(this);
m_pDIYSlot = new DIYSlot(this);
//连接
void (DIYSignal::*mySignalFun)() = &DIYSignal::mySignal;
void (DIYSlot::*mySlotFun)() = &DIYSlot::mySlot;
//自定义信号和槽有重载版本时,连接需要明确指出重载的版本
void (DIYSignal::*mySignalFunWithArg)(QString) = &DIYSignal::mySignal;
void (DIYSlot::*mySlotFunWithArg)(QString) = &DIYSlot::mySlot;
//一个信号连接多个槽函数
//带参信号连接无参槽函数
connect(m_pDIYSignal, mySignalFunWithArg, m_pDIYSlot, mySlotFun);
//带参信号连接带参槽函数
connect(m_pDIYSignal, mySignalFunWithArg, m_pDIYSlot, mySlotFunWithArg);
//自动触发
emit m_pDIYSignal->mySignal("arg");
多个信号连接同一个槽函数
不同对象的不同信号可以连接到同一槽函数,同样必须遵循上面的第④、⑤点,即自定义信号的参数必须大于等于槽函数的参数,且相等部分的参数类型需一一对应。
这里使用QPushButton的点击信号和无参(有参的也可以,因为满足④、⑤点条件)的自定义信号,连接无参的自定义槽函数。
//自定义信号和槽
m_pDIYSignal = new DIYSignal(this);
m_pDIYSlot = new DIYSlot(this);
//连接
void (DIYSignal::*mySignalFun)() = &DIYSignal::mySignal;
void (DIYSlot::*mySlotFun)() = &DIYSlot::mySlot;
//无参自定义信号 连接 无参槽函数
connect(m_pDIYSignal, mySignalFun, m_pDIYSlot, mySlotFun);
//触发
emit m_pDIYSignal->mySignal();
//QPushButton的点击信号 连接 无参的槽函数
QPushButton* myDiySigBtn = new QPushButton(this);
myDiySigBtn->setText("触发无参自定义槽函数");
connect(myDiySigBtn, &QPushButton::clicked, m_pDIYSlot, mySlotFun);
结果如下:
信号和槽函数的参数,必须一一对应
因为在信号触发时,调用已连接的槽函数,所以信号的参数会传递到槽函数的形参中。因此要求信号和槽函数的参数类型必须一一对象,否则就无法调用槽函数。
上面的示例中均有体现,这里就不再举例赘述。
信号和槽函数的参数个数可以不同,但信号的参数个数必须多于槽函数的参数个数,且相等部分的参数类型必须一一对应
信号的参数会逐一传递到槽函数参数中,但允许信号的参数多于槽函数的参数,相当于可以多传递参数给槽函数,但槽函数只接收自己对应的相等部分的参数即可。
不过这里需注意,信号和槽函数相等部分的参数,类型必须一一对应,以满足第④点的要求。
可以使用Qt4版本以前的信号和槽的连接方式
在存在重载版本的信号和槽函数时,在连接时需要通过声明函数指针来指定对应的重载版本,当重载版本过多时,工作量就会相应的增加。
因此可以考虑使用Qt4版本以前的信号和槽的连接方式,不仅不需要每个重载版本都声明一个对应的函数指针类型,而且还可以兼容Qt4版本开发的代码,因此推荐使用该方式连接信号和槽。
该版本的连接方式,在connect函数的第二、四个参数使用SIGNAL、SLOT宏代替信号和槽函数的地址。
//使用Qt4版本的信号和槽连接方式
//连接无参信号和槽
connect(m_pDIYSignal, SIGNAL(mySignal()), m_pDIYSlot, SLOT(mySlot()));
//连接有参信号和槽
connect(m_pDIYSignal, SIGNAL(mySignal(QString)), m_pDIYSlot, SLOT(mySlot(QString)));
//触发
emit m_pDIYSignal->mySignal();
emit m_pDIYSignal->mySignal("tips");
运行结果如下:
总结
此次,Qt中的信号和槽机制已全部介绍完毕。
要想使自己定义的类支持信号和槽机制,需要满足以下两点:
①自定义信号和槽的类需继承自 QObject。自定义信号和槽必须继承自 QObject,因为它们是 Qt 元对象系统的一部分。
②使用 Q_OBJECT 宏。为了使自定义信号和槽能够在运行时调用其 metaObject() 方法,必须在类的头文件中使用 Q_OBJECT 宏进行标记。
声明自定义信号和槽时,需要注意以下几点:
自定义信号,需要注意以下几点:
①自定义信号需要在signals:限定符下声明
②自定义信号只需声明,无需实现
③自定义信号可以有参数,可以重载
自定义槽函数,需要注意以下几点:
①需在public slots:下声明
②返回值为void
③需要声明,也需要实现
④可以有参数,可以重载
使用自定义信号和槽时,需要注意以下几点:
①信号可以连接信号
②一个信号可以连接到多个槽函数
③多个信号可以连接同一个槽函数
④信号和槽函数的参数,必须一一对应
⑤信号和槽函数的参数个数可以不同,但信号的参数个数必须多于槽函数的参数个数,且相等部分的参数类型必须一一对应(同第④点)
⑥可以使用Qt4版本以前的信号和槽的连接方式
这里推荐使用Qt4版本之前的信号和槽的连接方式,connect时不仅清晰明了哪个版本的信号连接到哪个槽函数,而且还兼容之前使用Qt4开发的代码。