Qt6入门教程 8:信号和槽机制(连接方式)

news/2024/5/18 22:18:33 标签: Qt, 信号, , signal, slot, signals, slots

目录

一.一个信号连接的例子

二.第五个参数

Qt%3A%3AAutoConnection-toc" style="margin-left:40px;">1.Qt::AutoConnection

Qt%3A%3ADirectConnection-toc" style="margin-left:40px;">2.Qt::DirectConnection

Qt%3A%3AQueuedConnection-toc" style="margin-left:40px;">3.Qt::QueuedConnection

Qt%3A%3ABlockingQueuedConnection-toc" style="margin-left:40px;">4.Qt::BlockingQueuedConnection

Qt%3A%3AUniqueConnection-toc" style="margin-left:40px;">5.Qt::UniqueConnection

三.信号

四.connect函数原型

五.信号的多种用法

六.的属性


一.一个信号连接的例子

#include <QObject>
#include <QDebug>

class Counter : public QObject
{
    Q_OBJECT

public:
    Counter() { m_value = 0; }

    int value() const
    {
        return m_value;
    }

public slots:
    void setValue(int value)
    {
        if (value != m_value)
        {
            m_value = value;
            emit valueChanged(value);
        }
    }

signals:
    void valueChanged(int newValue);

private:
    int m_value;
};

int main(int argc, char *argv[])
{
    Counter a, b;
    QObject::connect(&a, &Counter::valueChanged,
                     &b, &Counter::setValue);

    a.setValue(12); // a.value() == 12, b.value() == 12
    qDebug() << "a: " << a.value() << " b: " << b.value();
    b.setValue(48); // a.value() == 12, b.value() == 48
    qDebug() << "a: " << a.value() << " b: " << b.value();


    return 0;
}

#include "main.moc"

在调用a.setValue(12)时会发送valueChanged(12)信号,此时函数b.setValue(12)会被调用。然后b也会发送valueChanged(12)信号,但是由于没有连接到这个信号,所以这个信号被忽略了,不做处理。
需要注意的是,在setValue()函数中在赋值和发送信号之前做了 value != m_value的判断。这样做是为了防止特定情况下触发的无限循环调用,比如此时b.valueChanged()连接到了a.setValue()。

二.第五个参数

在上面的例子中,调用connect()函数时并没有指定第五个参数,因第五个参数一般不填,为默认值。

Qt%3A%3AAutoConnection">1.Qt::AutoConnection

默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。

Qt%3A%3ADirectConnection">2.Qt::DirectConnection

函数会在信号发送的时候直接被调用,函数和信号发送者在同一线程。效果看上去就像是直接在信号发送位置调用了函数,同步执行。
emit语句后面的代码将在与信号关联的所有函数执行完毕后才被执行。
无论函数所属对象在哪个线程,函数都在发射信号的线程内执行。但需要注意的是,当信号在不同的线程时,Qt::DirectConnection连接方式是不安全的,就像不能直接调用不同线程中的函数一样。但QObject::connect() 方法本身是线程安全的。

Qt%3A%3AQueuedConnection">3.Qt::QueuedConnection

信号发出后,信号会暂时被放到一个消息队列中,需等到接收对象所属线程的事件循环取得控制权时才取得该信号,然后执行和信号关联的函数,这种方式既可以在同一线程内传递消息也可以跨线程操作。
emit语句后的代码将在发出信号后立即被执行,无需等待函数执行完毕。
函数在接收者所依附线程执行。

Qt%3A%3ABlockingQueuedConnection">4.Qt::BlockingQueuedConnection

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

// Qt部分源码如下
.....//其他代码
else if(type == Qt::BlockingQueuedConnection)
{
    if(currentThread == objectThread) // 如果是同一条线程,就死锁了
        qWarning("QMetaObject::invoke: Dead lock detected");
 
    QSemaphore semaphore; // 信号量
    QCoreApplication::postEvent(Object, new QMetaCallEvent(slot, 0, -1, 0, 0, qrgv, &semaphore));  // 将函数指针、函数参数、信号量的指针发送到事件队列
    semaphore.acquire(); // 默认形参为1,;获取1个数据,如果没有准备1个好数据,则阻塞
}
else
{
.... //其他代码
}

Qt%3A%3AUniqueConnection">5.Qt::UniqueConnection

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

三.信号

声明信号使用signals关键字,发送信号使用emit关键字。
注意点:
1.所有的信号声明都是公有的,所以Qt规定不能在signals前面加public、private、protected。
2.所有的信号都没有返回值,所以返回值都用void。
3.所有的信号都不需要定义。
4.必须直接或间接继承自QOBject类,并且开头私有声明包含Q_OBJECT。
5.在同一个线程中,当一个信号被emit发出时,会立即执行其函数,等函数执行完毕后,才会执行emit后面的代码,如果一个信号链接了多个,那么会等所有的函数执行完毕后才执行后面的代码,函数的执行顺序是按照它们链接时的顺序执行的。不同线程中(即跨线程时),函数的执行顺序是随机的。
6.在链接信号时,可以设置链接方式为:在发出信号后,不需要等待函数执行完,而是直接执行后面的代码,是通过connect的第5个参数。
7.信号机制要求信号的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,信号的参数可以比函数的参数多,即便如此,函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在函数中选择忽略信号传来的数据(也就是函数的参数比信号的少),但是不能说信号根本没有这个数据,你就要在函数中使用(就是函数的参数比信号的多,这是不允许的)。

四.connect函数原型

1.如:connect(pushButton, SIGNAL(clicked()), dialog, SLOT(close()));Qt4和Qt5都可以使用这种连接方式。

static QMetaObject::Connection connect(
    const QObject *sender,    //信号发送对象指针
    const char *signal,       //信号函数字符串,使用SIGNAL()
    const QObject *receiver,  //函数对象指针
    const char *member,       //函数字符串,使用SLOT()
    Qt::ConnectionType = Qt::AutoConnection);

2.如:connect(pushButton, &QPushButton::clicked, dialog, &QDialog::close);这是Qt5新增的连接方式,在编译期间就可以进行类型检查,推荐使用这种连接方式。

static QMetaObject::Connection connect(
    const QObject *sender,      //信号发送对象指针
    const QMetaMethod &signal,  //信号函数地址
    const QObject *receiver,    //函数对象指针
    const QMetaMethod &method,  //函数地址
    Qt::ConnectionType type = Qt::AutoConnection);

3.两者的对比

概述基于字符串的语法基于函子的语法
做类型检查的阶段运行时编译时
是支持行类型的隐式转换
是否支持信号连接到lambda表达式
是否支持的参数比信号的参数多(此时使用默认参数)
是否支持连接C++函数到QML函数

注:qt5之后,这种新型的写法,支持所有的函数类型,无需定义slots关键字也可以。

如何选择重载的信号呢?例如,QLCDNumber有三个版本的display()
●QLCDNumber::display(int)
●QLCDNumber::display(double)
●QLCDNumber::display(QString)
连接到int版本的QSlider::valueChanged()信号, 两种语法的写法如下:

auto slider = new QSlider(this);
auto lcd = new QLCDNumber(this);

// 基于字符串的语法
connect(slider, SIGNAL(valueChanged(int)),
        lcd, SLOT(display(int)));

// 基于函子的语法,第一种方式(推荐)
connect(slider, &QSlider::valueChanged,
        lcd, static_cast<void (QLCDNumber::*)(int)>(&QLCDNumber::display));

// 基于函子的语法,第二种方式
void (QLCDNumber::*mySlot)(int) = &QLCDNumber::display;
connect(slider, &QSlider::valueChanged,
        lcd, mySlot);

// 基于函子的语法,第三种方式
connect(slider, &QSlider::valueChanged,
        lcd, QOverload<int>::of(&QLCDNumber::display));

// 基于函子的语法,第四种方式(C++14)
connect(slider, &QSlider::valueChanged,
        lcd, qOverload<int>(&QLCDNumber::display));

在需要信号发送方信息的情况下,Qt提供了QObject::sender()函数,它返回一个指向发送信号的对象的指针。如下所示:

void MyWidget::on_pushButton_clicked()
{
    QPushButton *button = static_cast<QPushButton*>(QObject::sender());
    qDebug() << button->text(); 
}

五.信号的多种用法

1.一个信号可以和多个相连
2.多个信号可以连接到一个
3.一个信号可以连接到另外的一个信号
4.可以被取消链接.
其写法和connect一样,只需要将connect换成disconnect即可。
disconnect() 通常以三种方式使用,如下面的示例所示:
1.断开myObject对象的信号与其他对象间的连接,使用后myObject发出的信号没有对应的函数进行响应

disconnect(myObject, nullptr, nullptr, nullptr);
//or
myObject->disconnect();

2.断开myObject对象的mySignal()信号与其他对象间的连接,使用后myObject发出的mySignal()信号没有对应的函数进行响应

disconnect(myObject, SIGNAL(mySignal()), nullptr, nullptr);
//or
myObject->disconnect(SIGNAL(mySignal()));

3.断开myObject对象的与myReceiver对象间的连接,使用后myObject发出mySignal()信号myReceiver没有对应的函数进行响应

disconnect(myObject, nullptr, myReceiver, nullptr);
//or
myObject->disconnect(myReceiver);

六.的属性

public slots:在这个区内声明的意味着所有对象都可将信号和之相连接。这对于组件编程非常有用,你能创建彼此互不了解的对象,将他们的信号进行连接以便信息能够正确的传递。 
protected slots:在这个区内声明的意味着当前类及其子类能将信号和之相连接。
private slots:在这个区内声明的意味着只有类自己能将信号和之相连接。
注:信号不能携带模板类参数

原文链接:https://blog.csdn.net/caoshangpa/article/details/135639126


http://www.niftyadmin.cn/n/5332523.html

相关文章

芯片设计重要工具—— IBM LSF 分布式高性能计算调度平台

IBM Spectrum LSF Suites 是面向分布式高性能计算 (HPC) 的工作负载管理平台和作业调度程序。基于 Terraform 的自动化现已可用&#xff0c;该功能可在 IBM Cloud 上为基于 IBM Spectrum LSF 的集群供应和配置资源。 借助我们针对任务关键型 HPC 环境的集成解决方案&#xff0…

docker安装运行CloudBeaver并设置默认语言为中文

1、CloudBeaver CloudBeaver 是一个开源的 Web 数据库管理工具&#xff0c;它提供了一个基于浏览器的用户界面&#xff0c;允许用户管理和操作各种类型的数据库。CloudBeaver 支持多种数据库系统&#xff0c;包括但不限于 PostgreSQL、MySQL、SQLite、Oracle、SQL Server 以及…

更适合3D项目的UI、事件交互!纯国产数字孪生引擎持续升级中!!!

UI和事件交互是3D可视化项目中最常见的模块&#xff0c;主要用于信息添加、展示&#xff0c;用来确保按照用户需求呈现内容并完成交互。 平时工作在进行UI和交互设计时&#xff0c;经常出现以下问题&#xff1a;UI过于复杂导致3D项目内交互效率低下&#xff0c;或者是结合3D项目…

第十一部分 隐含规则(四)

目录 一、老式风格的"后缀规则" 二、隐含规则搜索算法 一、老式风格的"后缀规则" 后缀规则是一个比较老式的定义隐含规则的方法。后缀规则会被模式规则逐步地取代。 因为模式规则更强更清晰。为了和老版本的 Makefile 兼容&#xff0c;GNU make 同样兼容于…

【高危】Google Chrome V8 < 120.0.6099.224 越界内存访问漏洞

漏洞描述 Google Chrome V8 是一个由Google开发的开源JavaScript引擎&#xff0c;用于Google Chrome及Chromium中。 Google Chrome 120.0.6099.224版本之前中&#xff0c;当通过runtime-object.cc的DeleteObjectPropertyFast删除对象属性时&#xff0c;可能未能正确处理内存或…

分布式拒绝服务攻击(DDoS)| 防御 | 监测

概述 介绍什么是分布式拒绝服务攻击&#xff08;DDoS&#xff09;。解释 DDoS 攻击对网络和服务的影响。 分布式拒绝服务攻击&#xff08;DDoS&#xff09;是一种网络攻击&#xff0c;通过大量流量或请求淹没目标服务器/网络资源&#xff0c;导致服务不可用或降级。 其主要特…

Stable Diffusion中不同的采样方法

在 Stable Diffusion 模型中&#xff0c;采样方法是从学习到的概率分布中生成图像的算法。采样方法影响生成图像的质量、样式、速度以及过程的控制程度。以下是一些采样方法的概述和它们对图像生成可能产生的影响&#xff1a; DPM系列 DPM 2M / 3M: 这些是扩展的扩散概率模型&…

《Linux C编程实战》笔记:出错处理

这一节书上把它放到线程这一章&#xff0c;按理说应该在前面就讲了 头文件errno.h定义了变量errno&#xff0c;它存储了错误发生时的错误码&#xff0c;通过错误码可以得到错误的信息 程序开始执行时&#xff0c;变量errno被初始化为0。很多库函数在执行过程中遇到错误时就会…