信号和槽

一、Qt信号和槽机制&emit的使用

(一)相关概念

1.信号(Signal)就是在特定情况下被发射的事件

例如PushButton 最常见的信号就是鼠标单击时发射的 clicked() 信号,一个 ComboBox 最常见的信号是选择的列表项变化时发射的 CurrentIndexChanged() 信号。GUI 程序设计的主要内容就是对界面上各组件的信号的响应,只需要知道什么情况下发射哪些信号,合理地去响应和处理这些信号就可以了。

2.槽(Slot)就是对信号响应的函数。槽就是一个函数

与一般的C++函数是一样的,可以定义在类的任何部分(public、private 或 protected),可以具有任何参数,也可以被直接调用。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。

3.信号与槽之间的关联:是用 QObject::connect() 函数实现的,其基本格式是:

QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));

//信号发出者,处理的信号, 信号接收者,处理动作方法(槽函数)。

注解:

  1. sender 是发射信号的对象的名称
  2. signal() 是信号名称。信号可以看做是特殊的函数,需要带括号,有参数时还需要指明参数。
  3. receiver 是接收信号的对象名称,slot() 是槽函数的名称,需要带括号,有参数时还需要指明参数。
  4. SIGNAL 和 SLOT 是 Qt 的宏,用于指明信号和槽,并将它们的参数转换为相应的字符串。

(二)注意点

1.一个信号可以连接多个槽, 当一个信号与多个槽函数关联时,槽函数按照建立连接时的顺序依次执行,例如:

  • connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(addFun(int));
  • connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(updateStatus(int));

2.多个信号可以连接同一个槽

  • connect(ui->rBtnBlue,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
  • connect(ui->rBtnRed,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
  • connect(ui->rBtnBlack,SIGNAL(clicked()),this,SLOT(setTextFontColor()))

3. 一个信号可以连接另外一个信号

4.严格的情况下,信号与槽的参数个数和类型需要一致,至少信号的参数不能少于槽的参数。如果不匹配,会出现编译错误或运行错误。

5.使用signals/slots必须要加入宏Q_OBJECT

6. 当一个信号被发射时,与其关联的槽函数通常被立即执行,就像正常调用一个函数一样。只有当信号关联的所有槽函数执行完毕后,才会执行发射信号处后面的代码。

7.一定要有signals关键字,定义信号时这个关键字不可或缺,比如我们定义一个信号void signal(),一定要在前面加上关键字“signals:”,

  • signals:
  • void signal();
  • 就像类中的public、protected、pravate一样,但是一定不能在signals前面加上public、protected、private,
    • publi signals:
    • 这样写是错误的

8.slots可以写可以不写,一般的函数也可以与signals下的信号关联,我们定义槽函数时可以像signals那样加上slots关键字,也可以不加,但是需要注意的是,如果加上了,那就必须加上public、protected、paivate

  • public slots:
  • void func();这样写
    • slots:
    • void func();这样写就是错的
  • 当然不加上slots的一般函数也可以与信号关联

9.signals下的函数必须是void类型,而且只需要给出声明即可,具体实现QT内部自己处理,但是槽函数一定要实现,从我们角度思考是这种信号处理是一致的,但是槽函数的功能确实根据我们需要自己设计,所以有了这种差异。

10.信号与槽的参数不能是宏和函数指针

11.信号一般与emit配合使用,使用emit发射信号给关联的槽

12.connect若触发,它后面的不会再运行

(三)自定义槽

可当作槽函数的:任意的成员函数,普通全局函数,静态函数。

槽函数需要和信号一致(参数列表,返回值)如果信号没有返回值,槽函数一定没有返回值。

【举例】:让按钮2点击一下,就能改变按钮上的文本。

1.首先,在.h文件中声明:

2.在.cpp文件对该函数进行定义:

3.给按钮联结自定义的槽函数 mySlot:

connect(b2, &QPushButton::released, this, &MainWidget::mySlot);

4.运行

(四)自定义信号

1.信号必须有signals关键字来声明

2.信号没有返回值,但可以有参数。

3.信号就是函数的声明,只需声明,无需定义。

(五)emit发射信号

emit是Qt关键字,像其他关Qt扩展一样,它也会被C++预处理器转换成标准的C++代码。

使用:在A中对B使用信号

主要步骤:信号的创建,槽函数的创建,A类信号和B类槽函数的联接和使用

二、Connect几种方式

(一)说明

QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));

//信号发出者,处理的信号, 信号接收者,处理动作方法(槽函数)。
  1. sender 是发射信号的对象的名称
  2. signal() 是信号名称。信号可以看做是特殊的函数,需要带括号,有参数时还需要指明参数。
  3. receiver 是接收信号的对象名称,slot() 是槽函数的名称,需要带括号,有参数时还需要指明参数。
  4. SIGNAL 和 SLOT 是 Qt 的宏,用于指明信号和槽,并将它们的参数转换为相应的字符串。

1.

这应该是QT4.0最为传统的方法

 connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(pushButon1_clicked()));

2.

这应该是QT5.0之后退出来的绑定方法,去掉了之前的宏

 connect(ui->pushButton_2,&QPushButton::clicked,this,&::MainWindow::pushButon2_clicked);

3.

C++11出来之后,新的特性就出来,static_cast 用法

connect(ui->pushButton_3, QOverload<bool>::of(&QPushButton::clicked),this,&::MainWindow::pushButon3_clicked);

4.

lamda表达式

connect(ui->pushButton_4, QOverload<bool>::of(&QPushButton::clicked),[=](bool check){
                   ui->textBrowser->setText("按钮4信号绑定成功");
           });

如果槽函数很简单,可以直接利用 lambda表达式进行连接,以减少代码量

这里需要注意 Lambda表达式是C++ 11 的内容,所以,需要再Pro项目文件中加入 CONFIG += C++ 11

(二)例子

1.彻底退出

connect(quitAction,&QAction::triggered,[=](){this->close(); });

2.跳转其他页面

connect(m_button[0], &ClickLabel::clicked, this, [=]() {
        zoom(m_button[0]);
        m_guideScene->show();
        this->hide();
        });

3.connect嵌套

        //监听每个按钮的点击事件
        connect(menuBtn,&MyPushButton::clicked,[=](){
            ...
            //进入到游戏场景
            this->hide(); //将选关场景隐藏掉
            play = new PlayScene(i+1); //创建游戏场景
            //这里注意,每次点击按钮都会新建一个游戏场景对象指针,因此返回时应当删除
            ...
            play->show(); //显示游戏场景
            connect(play,&PlayScene::chooseSceneBack,[=](){
                this->setGeometry(play->geometry());//设定新窗口的坐标在屏幕上与原先窗口坐标相同
                this->show();
                delete play;
                play = NULL;
            });

        });
//点击返回//从子页面返回
connect(backBtn,&MyPushButton::clicked,[=](){
        QTimer::singleShot(500,[=](){
            emit this->chooseSceneBack();
        });
});

三、connect 第五个参数

(一)函数原型

connect 函数原型如下,第五个(5种)参数根据接收者和发送者是否在同一个线程不同

connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
        const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot,
        Qt::ConnectionType type = Qt::AutoConnection)

(二)Qt::ConnectionType  详解

1.Qt::AutoConnection 

自动。一般不写就默认是这个,使用这个值会根据实际情况去判断,如果sender和receiver在同一个线程,则自动使用Qt::DirectConnection,如果不在一个线程,则自动使用Qt::QueuedConnection。

2.Qt::DirectConnection

直连。槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。

3.Qt::QueuedConnection

队列连接。槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。

4.Qt::BlockingQueuedConnection

阻塞队列连接。槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。

5.Qt::UniqueConnection

唯一连接。这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。

四、disconnect函数

disconect函数的原型如下:

bool QObject::disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)

(一)使用方法

一般有四种用法:

1.解除myObject对象的所有信号连接,调用后myObject对象发出的所有信号都得不到响应。

disconnect(myObject, 0, 0, 0);
//等同于
myObject->disconnect();

2.解除myObject对象的mySignal信号的所有连接,调用后myObject对象发出的mySignal信号得不到响应。

disconnect(myObject, SIGNAL(mySignal()), 0, 0);
//等同于
myObject->disconnect(SIGNAL(mySignal()));

3.解除myObject对象与myReceiver对象的信号连接,调用后myObject对象发出的所有信号得不到myReceiver对象的响应。

disconnect(myObject, 0, myReceiver, 0);
//等同于
myObject->disconnect(myReceiver);

4.解除myObject对象的mySignal信号与myReceiver对象的mySlot槽的连接,调用后myObject对象发出的mySignal信号得不到myReceiver对象的mySlot槽的响应。

disconnect(myObject, SIGNAL(mySignal()), myReceiver, SLOT(mySlot()));
//等同于
myObject->disconnect(SIGNAL(mySignal(), myReceiver, SLOT(mySlot())));

上述4种用法中,0是通配符,它表示任一信号、任一接收对象、任一槽

五、blockSignals

blockSignals的函数原型如下:

bool QObject::blockSignals(bool block)

(一)用法

//object发出的信号被阻塞,系统不会调用任何连接到object的处理。
object->blockSignals(true);
 
//解除信号阻塞
object->blockSignals(false);

六、案例

//widget.h

#ifndef WIDGET_H
#define WIDGET_H
 
#include <QWidget>
 
class Widget : public QWidget
{
    Q_OBJECT
 
public:
    Widget(QWidget *parent = 0);
    ~Widget(){}
};
 
#endif // WIDGET_H

//widget.cpp

#include "widget.h"
 
#include <QPushButton>
#include <QGridLayout>
#include <QDebug>
 
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    /* 创建控件 */
    QPushButton *btnClick      = new QPushButton("click me", this);
    QPushButton *btnBlock      = new QPushButton("block", this);
    QPushButton *btnUnblock    = new QPushButton("unblock", this);
    QPushButton *btnDisconnect = new QPushButton("disconnect", this);
 
    QGridLayout *pLayout = new QGridLayout();
    pLayout->addWidget(btnClick, 0, 0);
    pLayout->addWidget(btnBlock, 0, 1);
    pLayout->addWidget(btnUnblock, 0, 2);
    pLayout->addWidget(btnDisconnect, 0, 3);
    this->setLayout(pLayout);
 
    /* 信号槽 */
    connect(btnClick, &QPushButton::clicked, this, [=]()
    {
        qDebug() << "点击按钮";
    });
 
    connect(btnBlock, &QPushButton::clicked, this, [=]()
    {
        btnClick->blockSignals(true);
        btnBlock->setEnabled(false);
        btnUnblock->setEnabled(true);
    });
 
    connect(btnUnblock, &QPushButton::clicked, this, [=]()
    {
        btnClick->blockSignals(false);
        btnBlock->setEnabled(true);
        btnUnblock->setEnabled(false);
    });
 
    connect(btnDisconnect, &QPushButton::clicked, this, [=]()
    {
        disconnect(btnClick, &QPushButton::clicked, this, 0);
    });
 
}

//main.c

#include "widget.h"
#include <QApplication>
 
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
 
    return a.exec();
}

结果:

1.按下"click me"按钮,窗口接收到"click me"按钮发出的clicked信号,打印"点击按钮"。

2.然后再按下"block",再次按下"click me"按钮,没有打印"点击按钮"。这是因为 "click me"按钮发出的信号被阻塞了,没有得到响应。

3.然后再按下"unblock"按钮,再次按下"click me"按钮,打印"点击按钮"。这是因为解除了"click me"按钮的信号阻塞。

4.最后按下"disconnect"按钮,此时按下"click me"按钮没有反应。这是因为解除了本窗口与"click me"按钮的clicked信号的连接。

posted @ 2023-02-07 19:26  ImreW  阅读(95)  评论(0编辑  收藏  举报