本节讲解信号槽的一些扩展知识点,使用自定义信号槽一节的长官和士兵的demo

1. 如何连接重载的信号槽

在信号和槽存在重载时,有多种连接信号和槽的方法。

1.1 SIGNAL/SLOT(Qt4)

Qt4的写法中,同时包含函数名和函数参数,因此写法比较简单

MyWidget::MyWidget(QWidget* parent) : QWidget(parent), ui(new Ui::MyWidget) {
    ui->setupUi(this);

    // 1、信号槽重载时:SIGNAL/SLOT
#if 1
    commander = new Commander(this);
    soldier = new Soldier(this);

    connect(commander, SIGNAL(go()), soldier, SLOT(fight()));
    connect(commander, SIGNAL(go(QString)), soldier, SLOT(fight(QString)));

    emit commander->go();
    emit commander->go("freedom");
#endif

}

1.2 自定义函数指针

Qt5的写法中,只指明了函数名,没有函数参数,因此需要自定义函数指针

MyWidget::MyWidget(QWidget* parent) : QWidget(parent), ui(new Ui::MyWidget) {
    ui->setupUi(this);

    // 2、信号槽重载时:函数地址
#if 1
    commander = new Commander(this);
    soldier = new Soldier(this);

    // 没有同名的信号和槽时,可以直接这样写。因为不存在二义性
    // connect(&commander, &Commander::go, &soldier, &Soldier::fight);

    // 有同名的信号和槽时,需要向下面这样定义函数指针。因为存在二义性
    // 编译器自动推断:将无参的信号go和无参的槽,赋值给函数指针(ctrl+鼠标点击可以智能跳转过去)
    void (Commander::*pGo)() = &Commander::go;
    void (Soldier::*pFight)() = &Soldier::fight;
    connect(commander, pGo, soldier, pFight);

    // 编译器自动推断:将有参的信号go和有参的槽,赋值给函数指针(ctrl+鼠标点击可以智能跳转过去)
    void (Commander::*pGoForFreedom)(QString) = &Commander::go;
    void (Soldier::*pFightForFreedom)(QString) = &Soldier::fight;
    connect(commander, pGoForFreedom, soldier, pFightForFreedom);

    emit commander->go();
    emit commander->go("freedom");
#endif

}

1.3 精简写法

使用类模板、函数模板,可以简化写法

MyWidget::MyWidget(QWidget* parent) : QWidget(parent), ui(new Ui::MyWidget) {
    ui->setupUi(this);

    // 3、信号槽重载时:精简写法
#if 1
    commander = new Commander(this);
    soldier = new Soldier(this);

    // 使用类模板:QOverload
    // connect(commander, QOverload<>::of(&Commander::go), soldier, QOverload<>::of(&Soldier::fight));
    // connect(commander, QOverload<QString>::of(&Commander::go), soldier, QOverload<QString>::of(&Soldier::fight));

    // 或者使用函数模板:qOverload
    connect(commander, qOverload<>(&Commander::go), soldier, qOverload<>(&Soldier::fight));
    connect(commander, qOverload<QString>(&Commander::go), soldier, qOverload<QString>(&Soldier::fight));

    emit commander->go();
    emit commander->go("freedom");
#endif

}

2. 一个信号连接多个槽

一个信号可以连接多个槽函数,如下:

connect(sender, &Sender::signal, receiver1, &Receiver1::slot1);
connect(sender, &Sender::signal, receiver2, &Receiver2::slot2);

这样,当sender发出signal信号发出时,它连接的2个槽函数slot1slot2都会被执行,并且:

  • Qt4 信号发射时,与之相连接的槽函数的执行顺序是随机的
  • Qt5+ 信号发射时,这些槽函数的执行顺序与建立连接的顺序相同

接下来,以 “长官和士兵” 的例子为例,实现如下效果:

// 士兵1很勇敢,收到冲锋的信号后,开始战斗
connect(&commander, &Commander::go, &soldier, &Soldier::fight);

// 士兵2很怕死,收到冲锋的信号后,开始逃跑
connect(&commander, &Commander::go, &soldier2, &Soldier::escape);

首先,在Soldier类中添加槽函数的声明和定义,如下:

// soldier.h
class Soldier : public QObject
{

public slots:
    void fight();
    void fight(QString);

    // 添加一个“逃跑”的槽函数
    void escape();
};

// soldier.cpp
void Soldier::escape() {
    qDebug() << "i'm afraid of death, escape...";
}

然后,来到mywidget.h中,声明一个soldier2的士兵,如下:

class MyWidget : public QWidget
{
private:
    Commander* commander;
    Soldier* soldier;
    Soldier* soldier2;
};

最后,来到mywidget.cpp中连接信号槽,并发送信号,如下:

MyWidget::MyWidget(QWidget* parent) : QWidget(parent), ui(new Ui::MyWidget) {
    ui->setupUi(this);

    // 4、一个信号连接多个槽函数
#if 1
    commander = new Commander(this);
    soldier = new Soldier(this);
    soldier2 = new Soldier(this);

    // 士兵1很勇敢,收到冲锋的信号后,开始战斗
    connect(commander, qOverload<>(&Commander::go), soldier, qOverload<>(&Soldier::fight));

    // 士兵2很怕死,收到冲锋的信号后,开始逃跑
    connect(commander, qOverload<>(&Commander::go), soldier2, qOverload<>(&Soldier::escape));

    emit commander->go();
#endif
}

3. 多个信号连接一个槽

可以将多个信号连接到同一个槽函数,如下:

connect(sender, &Sender::signal1, receiver, &Receiver::slot);
connect(sender, &Sender::signal2, receiver, &Receiver::slot);

这样,当signal1singnal22个信号发出时,都会执行槽函数slot

接下来,以 “长官和士兵” 的例子为例:

// 当 commander 发射 go 信号和 move 信号时,都会执行士兵的 fight 槽函数,开始战斗
connect(&commander, &Commander::go, &soldier, &Soldier::fight);
connect(&commander, &Commander::move, &soldier, &Soldier::fight);

首先,在Commander类中新添加一个move的信号,如下:

class Commander : public QObject
{
signals:
    void go();
    void go(QString);

    // 新添加一个 move 信号
    void move();
};

然后,来到mywidget.cpp中连接信号槽,并发送信号,如下:

MyWidget::MyWidget(QWidget* parent) : QWidget(parent), ui(new Ui::MyWidget) {
    ui->setupUi(this);

    // 5、多个信号连接一个槽函数
#if 1
    commander = new Commander(this);
    soldier = new Soldier(this);

    // 当 commander 发射 go 信号和 move 信号时,都会执行士兵的 fight 槽函数,开始战斗
    connect(commander, qOverload<>(&Commander::go), soldier, qOverload<>(&Soldier::fight));
    connect(commander, qOverload<>(&Commander::move), soldier, qOverload<>(&Soldier::fight));

    emit commander->go();
    emit commander->move();
#endif
}

4. 信号连接信号

信号不仅可以连接槽, 还可以和连接信号,如下:

connect(obj1, &Obj1::signal1, obj2, &Obj2::signal2);

这样,当obj1发送signal1信号时,就会触发obj2发送signal2信号。

接下来,以 “长官和士兵” 的例子为例:

// 按钮的点击会发射clicked信号 => commander发射move信号 => soldier执行escapse槽函数
connect(ui->btnAction, &QPushButton::clicked, commander, &Commander::move);
connect(commander, &Commander::move, soldier, &Soldier::escape);

首先,在mywdget.ui中,放置一个按钮,并修改属性并布局,如下:
qt-base

然后,来到mywidget.cpp中,信号连接信号:

MyWidget::MyWidget(QWidget* parent) : QWidget(parent), ui(new Ui::MyWidget) {
    ui->setupUi(this);

    // 6、信号连接信号
#if 1
    commander = new Commander();
    soldier = new Soldier();

    connect(ui->btnSignal2Signal, &QPushButton::clicked, commander, &Commander::move);
    connect(commander, &Commander::move, soldier, &Soldier::escape);
#endif
}

此时,点击按钮,按钮会发射clicked信号, 接着commander发射move信号,move信号的发射,会去执行soldierescape槽函数

5. 断开连接 - disconnect

disconnect用于断开信号的连接。
这种情况并不常用,因为当一个对象delete之后, Qt自动取消所有连接到这个对象上面的槽。

MyWidget::MyWidget(QWidget* parent) : QWidget(parent), ui(new Ui::MyWidget) {
    ui->setupUi(this);

    // 7、断开信号的连接
#if 1
    commander = new Commander();
    soldier = new Soldier();

    connect(commander, qOverload<>(&Commander::go), soldier, qOverload<>(&Soldier::fight));
    connect(commander, qOverload<QString>(&Commander::go), soldier, qOverload<QString>(&Soldier::fight));

    emit commander->go();

    // 断开所有连接到 commander 信号上的槽函数
    commander->disconnect();
    emit commander->go("freedom");
#endif
}

6. 获取发送信号的对象

在槽函数内部,可以直接调用sender()函数,获取发送信号的对象
案例:点击两个按钮,调用同一个槽函数,在槽函数中区分是哪个按钮发送的信号。

首先,界面布局,如下:
qt-base

然后,在mywidget.h中,声明两个按钮对应的槽函数:

class MyWidget : public QWidget
{
public slots:
    void onBtnsClicked();
};

最后,在mywidget.cpp中,实现槽函数并关联信号槽:

MyWidget::MyWidget(QWidget* parent) : QWidget(parent), ui(new Ui::MyWidget) {
    ui->setupUi(this);

    // 8、获取发送信号的对象
    connect(ui->btnStart, &QPushButton::clicked, this, &MyWidget::onBtnsClicked);
    connect(ui->btnStop, &QPushButton::clicked, this, &MyWidget::onBtnsClicked);
}

void MyWidget::onBtnsClicked() {
    // qDebug() << "onBtnsClicked...";

    // 获取发送信号的对象的指针
    QObject* senderObj = sender();

    // 尝试将其转换为 QPushButton 类型
    QPushButton* button = qobject_cast<QPushButton*>(senderObj);

    // 如果转换成功,则说明是一个按钮发送了信号
    if ( button ) {
        if ( button == ui->btnStart ) {
            qDebug() << "点击了启动按钮";
        } else if ( button == ui->btnStop ) {
            qDebug() << "点击了停止按钮";
        }
    }
}

此时,点击 “启动” 和 “停止” 按钮,在槽函数中可以区分开来,根据不同的按钮执行不同逻辑。

7. 点赞、获取源码

看到这里的小伙伴,去B站给明王一个【免费的点赞】吧,你的支持,是我持续更新优质内容的动力,感谢~

源码下载地址
链接: https://pan.baidu.com/s/1U2TSwiJ7_8x6iBHeUwgubQ
提取码: ming