Qt--信号和槽

写在前面

信号与槽机制是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 
#include 

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 

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 

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 

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 
#include 

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 

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 

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 

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);

Qt--信号和槽_第1张图片

一个信号可以连接到多个槽函数

一个信号可以连接到多个槽函数,但必须遵循上面的第④、⑤点,即自定义信号的参数必须大于等于槽函数的参数,且相等部分的参数类型需一一对应。

这里使用带参的自定义信号,分别连接无参和带参的槽函数。
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);

结果如下:
Qt--信号和槽_第2张图片

信号和槽函数的参数,必须一一对应

因为在信号触发时,调用已连接的槽函数,所以信号的参数会传递到槽函数的形参中。因此要求信号和槽函数的参数类型必须一一对象,否则就无法调用槽函数。

上面的示例中均有体现,这里就不再举例赘述。

信号和槽函数的参数个数可以不同,但信号的参数个数必须多于槽函数的参数个数,且相等部分的参数类型必须一一对应

信号的参数会逐一传递到槽函数参数中,但允许信号的参数多于槽函数的参数,相当于可以多传递参数给槽函数,但槽函数只接收自己对应的相等部分的参数即可。

不过这里需注意,信号和槽函数相等部分的参数,类型必须一一对应,以满足第④点的要求。

可以使用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");

运行结果如下:
Qt4版本之前的信号和槽的连接

总结

此次,Qt中的信号和槽机制已全部介绍完毕。

要想使自己定义的类支持信号和槽机制,需要满足以下两点:
①自定义信号和槽的类需继承自 QObject。自定义信号和槽必须继承自 QObject,因为它们是 Qt 元对象系统的一部分。
②使用 Q_OBJECT 宏。为了使自定义信号和槽能够在运行时调用其 metaObject() 方法,必须在类的头文件中使用 Q_OBJECT 宏进行标记。

声明自定义信号和槽时,需要注意以下几点:
自定义信号,需要注意以下几点:
①自定义信号需要在signals:限定符下声明
②自定义信号只需声明,无需实现
③自定义信号可以有参数,可以重载

自定义槽函数,需要注意以下几点:
①需在public slots:下声明
②返回值为void
③需要声明,也需要实现
④可以有参数,可以重载

使用自定义信号和槽时,需要注意以下几点:
①信号可以连接信号
②一个信号可以连接到多个槽函数
③多个信号可以连接同一个槽函数
④信号和槽函数的参数,必须一一对应
⑤信号和槽函数的参数个数可以不同,但信号的参数个数必须多于槽函数的参数个数,且相等部分的参数类型必须一一对应(同第④点)
⑥可以使用Qt4版本以前的信号和槽的连接方式

这里推荐使用Qt4版本之前的信号和槽的连接方式,connect时不仅清晰明了哪个版本的信号连接到哪个槽函数,而且还兼容之前使用Qt4开发的代码。

你可能感兴趣的:(QT,C++,qt)