Qt的信号与槽

2023-11-18

引入

在GUI编程中,组件组件如何实现通信是核心的技术内容。
Qt使用了信号与槽的机制,为此Qt引入了一些关键字slots、signals、emit,这些都是Qt特有的关键字,然后这些关键字会被Qt的moc转换位标准的C++语句。

Qt 的部件类中有一些已经定义好了的信号和槽,通常的作法是子类化部件类,然后添加自已的信号和槽。

因为信号和槽与函数相似,所以通常把信号称为信号函数,槽称为槽函数。

元对象是指用于描述另一个对象结构的对象。使用编程语言具体实现时,其实就是一个类的对象,只不过这个对象专门用于描述另一个对象而已,比如 class B{…}; class A{…B mb;…};假设 mb 是用来描述类 A 创建的对象的,则 mb 就是元对象

什么是信号和槽?(signal and slot)

C++虽然是面象对象的语言,但程序的具体实现代码仍然是由函数来实现的,因此所谓的对象之间的通信,从程序设计语言语法角度来看就是函数调用的问题,只不过是某个对象的成员函数调用另一个对象的成员函数而已。信号和槽其实是观察者模式的一种实现。

当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。

我们打开Qt ui设计师,在里面放一个组件,右键点击它就可以转到槽了:

在这里插入图片描述

转到槽qt会提供很多信号函数给你用,一个信号函数代表了一个事件,当事件发生的时候就会调用相应的信号函数;
例如clicked():当这个按钮被点击的时候触发的信号函数
在这里插入图片描述

选好信号函数之后,Qt会很贴心的帮你命好槽函数的名字,并帮你绑定对应的信号函数
在这里插入图片描述

你也可以显示的把信号函数和槽函数的绑定写出来,或者你想自己绑定其他的信号或者槽函数:
通过连接函数实现
连接函数 :connect(参数1,参数2,参数3,参数4)

  • 参数1 信号的发送者
  • 参数2 发送的信号(函数地址)
  • 参数3 信号的接受者
  • 参数4 处理的槽函数 (函数的地址)
//实现点击按钮 关闭窗口的案例
connect(btn , &QPushButton::click,this,&QWidget::close );

我们直接看代码,我们不仅仅实现一个自动添加的槽函数,我们还自己写一个槽函数和信号函数供大家深入理解它的含义

Mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
    Q_OBJECT// 只有 QObject 及其派生类才能使用信号和槽机制,且在类之中还需要使用 Q_OBJECT 宏。
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
private:
    Ui::MainWindow *ui;
signals:    //信号
    void print(QString str); //自己添加的信号函数
private slots:    //槽函数
    void on_pushButton_clicked();//qt帮你生成的槽函数
    void printString(QString str);//自己定义的槽函数
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    connect(this,&MainWindow::print,this,&MainWindow::printString);
}
void MainWindow::onClicked()
{
    if(ui->pushButton->text() == "Hello,World")
    {
        ui->pushButton->setText("一个普通的按钮");
    }
    else
    {
        ui->pushButton->setText("Hello,World");
        emit this->print("Hello,World");
        //向print这个信号函数发一个信号,
        //由于print信号函数和printString直接关联,
        //print信号函数再向printString这个槽函数发信号
    }
}
void MainWindow::printString(QString str)
{
    qDebug()<<"要打印的字符串是:"<<str;
}
MainWindow::~MainWindow()
{
    delete ui;
}

信号与槽的原理

创建一个信号,其中创建信号需要一些规则。当需要调用外部函数时,发送一个信号,此时与该信号相关联的槽便会被调用,槽其实就是一个函数,当然要使函数成为槽是有一定规则的。

只有 QObject 及其派生类才能使用信号和槽机制,且在类之中还需要使用 Q_OBJECT 宏。

槽与信号的关联需要由程序员来完成

在 Qt 中,信号和槽都需要位于一个类之中。

几个关键字的原型:

  • signals 关键字:最终被#define 置换为一个访问控制符,其简化后为 #define signals public

  • slots 关键字:最终被#define 置换为一个空宏,即简化后为 #define slots

  • emit 关键字:同样被#define 置换为一个空宏,即简化后为 #define emit

由以上各关键字原型可见,使用 emit 发射信号,其实就是一个简单的函数调用。

signals信号创建规则:

  • 信号使用 signals 关键字声明,在其后面有一个冒号“:”,在其前面不能有 public、private、protected 访问控制符,信号默认是 public 的(因为事件的本质其实是Windows的一个类发出来的一个消息,例如点击事件是Windows的鼠标类来处理的,这个消息最后再发送给Qt的应用程序类的信号函数,所以要公开访问权限)

  • 信号只需像函数那样声明即可,其中可以有参数,参数的主要作用是用于和槽的通信,这就像普通函数的参数传递规则一样。信号虽然像函数,但是对他的调用方式不一样,信号需要使用 emit 关键字发射。

  • 信号只需声明,不能对其进行定义,信号是由 moc 自动生成的。

  • 信号的返回值只能是 void 类型的。

槽创建规则:

  • 声明槽需要使用 slots 关键字,在其后面有一个冒号“:”,且槽需使用 public、private、protected 访问控制符之一。(如果没有外部访问的必要建议设置位private,默认转到槽的生成就是私有的)

  • 槽就是一个普通的函数,可以像使用普通函数一样进行使用,槽与普通函数的主要区别是,槽可以与信号关联

发射信号规则:

  • 发射信号需要使用 emit 关键字,注意,在 emit 后面不需要冒号。

  • emit 发射的信号使用的语法与调用普通函数相同,比如有一个信号为 void f(int),则发送的语法为:emit f(3);

  • 当信号被发射时,与其相关联的槽函数会被调用(注意:信号和槽需要使用QObject::connect 函数进行关联之后,发射信号后才会调用相关联的槽函数)

  • 因为信号位于类之中,因此发射信号的位置需要位于该类的成员函数中或该类能见到信号的标识符的位置。

信号和槽的关系

  • 槽的参数的类型需要与信号参数的类型相对应,

  • 槽的参数不能多于信号的参数,因为若槽的参数更多,则多余的参数不能接收到信号传递过来的值,若在槽中使用了这些多余的无值的参数,就会产生错误。

  • 若信号的参数多余槽的参数,则多余的参数将被忽略。

  • 一个信号可以与多个槽关联,多个信号也可以与同一个槽关联,信号也可以关联到另一个信号上

  • 若一个信号关联到多个槽时,则发射信号时,槽函数按照关联的顺序依次执行。

  • 若信号连接到另一个信号,则当第一个信号发射时,会立即发射第二个信号

信号和槽的绑定机制

除了像我上面写的那样将connect绑定函数写到类的构造函数中之外,还有其他的使用方式。
我们先简单介绍一下这个connect函数,从声明上来看就能知道它可以怎么用。

connect函数的具体声明如下:

static QMetaObject::Connection connect(
  const QObject *sender,   const char *signal, 
  const QObject *receiver, const char *method, 
  Qt::ConnectionType type = Qt::AutoConnection) const
  • sender:表示需要发射信号的对象。

  • signal:表示需要发射的信号,该参数必须使用 SIGNAL()宏。

  • receiver:表示接收信号的对象。

  • method:表示与信号相关联的槽函数,这个参数也可以是信号,从而实现信号与信号的关联。该参数若是槽,需使用 SLOT()宏,若是信号需使用 SIGNAL() 宏。

  • 返回值的类型为 QMetaObject::Connection,如果成功将信号连接到槽,则返回连接的句柄,否则,连接句柄无效,可通过将句柄转换为 bool 来检查该句柄是否有效。该返回值可用于 QObject::disconnect()函数的参数,以断开该信号和槽的关联。

  • type:用于指明信号和槽的关联方式,它决定了信号是立即传送到一个槽还是在稍后时间排队等待传送。关联方式使用枚举 Qt::ConnectionType 进行描述,下表为其取值及意义:

常量 描述
Qt::AutoConnection (自动关联,可以缺省,默认值)。若接收者驻留在发射信号的线程中(即信号和槽在同一线程中),则使用 Qt :: DirectConnection(直接关联),否则,使用 Qt :: QueuedConnection(队列关联)。当信号发射时自动确定使用哪种关联类型。
Qt::DirectConnection 直接关联。当信号发射后,立即调用槽。在槽执行完之后,才会执行发射信号之后的代码(即 emit 关键字之后的代码)。该槽在信号线程中执行。
Qt::QueuedConnection 队列关联。当控制权返回到接收者线程的事件循环后,槽才会被调用,也就是说 emit 关键字后面的代码将立即执行,槽将在稍后执行,该槽在接收者的线程中执行。
Qt::BlockingQueuedConnection 阻塞队列关联。和 Qt :: QueuedConnection 一样,只是信号线程会一直阻塞,直到槽返回。如果接收者驻留在信号线程中,则不能使用此连接,否则应用程序将会死锁。
Qt::UniqueConnection 唯一关联。这是一个标志,可使用按位或与上述任何连接类型组合。当设置 Qt :: UniqueConnection 时,则只有在不重复的情况下才会进行连接,如果已经存在重复连接(即,相同的信号指向同一对象上的完全相同的槽),则连接将失败,此时将返回无效的 QMetaObject::Connection

从声明中能看出来,它是QObject的static函数,所以可以直接在外部类声明使用

方式1(直接声明使用)


class A:public QObject {
  Q_OBJECT 
singals: 
  void s(int i);
};
class B:public QObject{
  Q_OBJECT 
public slots: 
  void x(int i){}
};
A ma; B mb; 
QObject::connect (&ma, SIGNAL(s(int)), &mb, SLOT(x(int));
  • 这种方式是将绑定函数写成全局函数引用的方式,connect是QObject的一个static函数,所以可以这么用;
  • 使用的时候,信号的指定须使用宏SIGNAL(),槽须使用宏SLOT(),这两个能把括号中的内容转换为与形参相对应的const char*形式。
  • 在指定函数时,只能指定函数参数的类型,不能有参数名,也不能指定函数的返回类型。

方式2(Object调用使用)

 A ma; 
 B mb; 
 mb.connect(&ma, SIGNAL(s(int)),SLOT(x(int));

这种方式就是传统的调用方式

方式3(参数上可以使用模板)

 A ma; 
 B mb; 
 QObject::connect(&ma, &A::s, &mb, &B::x );

该函数对信号和槽函数的指定方式不是使用的宏。该形式的函数其实是一个模板函数,通过以指针的形式来指定信号和槽函数来达到不再使用SIGNAL()和SLOT()宏的目的
使用这种方式也有很多其他的好处:

  • 这种方式的槽函数可以不使用slots关键字声明,任意的成员函数都可以是槽函数

  • 这种方式的信号和槽函数的参数类型不需要完全一致,可以进行隐式转换,甚至可以用typedef和命名空间( SINGAL()和SLOT()宏实际上是把该宏的参数转换为字符串,当信号和槽相关联的时候,使用的是字符串进行匹配,所以信号和槽的参数类型必须在字符串意义上相同,所以使用宏的方式无法兼容类型的参数)

  • 当信号或槽函数有重载时,使用这种方式可能会产生二义性的问题,我们可以使用函数指针的方法指定信号或槽函数

class A:public QObject {
  Q_OBJECT 
singals:
  void s(int i);
};
class B:public QObject{
  Q_OBJECT
public slots:
  void x(){} 
  void x(int i){}//槽函数重载
};
A ma; B mb; 
//QObject::connect(&ma, &A::s, &mb, &B::x ); //会发生二义性错误。
//可使用如下方式解决(对于信号类似)
QObject::connect(&ma, &A::s, &mb,static_cast<void (B::*)(int)> (&B::x));

方式4(在构造函数中使用)

这种使用方式是写在了界面窗口类的构造函数中,具体代码可以看最上面我的实现,可以简化很多写的东西(例如实例对象可以用this替换等等),如果绑定的函数过多,建议专门再写一个init初始化的函数将所有的绑定过程放到初始化函数中

断开连接的机制

可以建立信号和槽的绑定关系,就肯定可以断开其关联,我们使用disconnect函数断开其连接,使用方式和connect类似,这里不再赘述了

它的函数声明如下,断开成功返回true:

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

参考

Qt学习之Qt基础入门(上)

Qt教程(3) : 信号与槽

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Qt的信号与槽 的相关文章

随机推荐

  • Ubuntu18 04安装QQ

    Ubuntu18 04安装QQ Created time December 20 2022 5 12 PM Last edited time December 20 2022 6 15 PM Tags QQ Ubuntu18 04 截图工具
  • Flutter 第三方SDK集成(友盟统计,极光推送,百度地图)

    友盟统计 Flutter SDK集成 首先创建一个项目 接着去友盟工作台添加应用 添加成功后会得到AppKey 集成友盟 Flutter SDK 下载Demo Demo地址 https developer umeng com api sdk
  • Java实现多线程下载

    思路 1 基本思路是将文件分段切割 分段传输 分段保存 2 分段切割用到HttpUrlConnection对象的setRequestProperty Range bytes start end 方法 3 分段传输用到HttpUrlConne
  • 《面试准备》C++链表操作3(合并两个有序链表)

    include
  • Unity Shader入门精要第七章 基础纹理 单张纹理

    Unity系列文章目录 文章目录 Unity系列文章目录 前言 一 7 1单张纹理 参考 前言 纹理最初的目的就是使用一张图片来控制模型的外观 使用纹理映射 texture mapping 技 术 我们可以把一张图 黏 在模型表面 逐纹素
  • PID的积分抗饱和

    目录 1 什么是积分饱和 2 实际的例子 3 负面影响 4 如何防止积分饱和 5 PID算法 抗饱和 1 什么是积分饱和 积分饱和 Integral windup或integrator windup 是指PID控制器或是其他有积分器的控制器
  • linux中KVM桥接网卡br0

    在redhat Eenterprise 6或者CentOS 6以上版本在使用KVM虚拟化的时 会需要配置到桥接网卡 下面就非常简单的讲一下桥接的方法 真的很简单 查看eth0的现有状态 root localhost cd etc sysco
  • Spring Boot开发时Java对象和Json对象互转

    作者简介 练习时长两年半的Java up主 个人主页 程序员老茶 ps 点赞 是免费的 却可以让写博客的作者开兴好久好久 系列专栏 Java全栈 计算机系列 火速更新中 格言 种一棵树最好的时间是十年前 其次是现在 动动小手 点个关注不迷路
  • 安卓手机玩游戏卡顿怎么解决_安卓手机卡顿,都是在桌面上卸载软件,这样是没用的!...

    现在的时代手机对我们来说已经是必不可少的了可以说是情人一样走哪都带着 智能手机尤其是这几年 随着国产手机的迅猛发展 其实我们可以看到无论男女老少 基本上都有自己的手机 现在学生都用手机了 我记得我上学那会没家长没给买手机我初3才买手机的 现
  • Composer自动加载(一)

    我的个人博客 逐步前行STEP Composer是PHP的基于项目的依赖管理工具 它本身集成一个autoloader 支持PSR 4 PSR 0 classmap files 四种自动加载方式 首先介绍一下PSR 4与PSR 0 介绍这两种
  • Qt中csv文件的导入与导出

    CSV 1 简介 全称 Comma Separated Values 是 逗号分隔值 的英文缩写 通常是纯文本文件 一般用wordWPS或是记事本打开 2 规则 1 开头不留空 以行为单位 2 可含或不含列名 含列名则居文件第一行 3 一行
  • Lombok插件注解详解

    Lombok主要常用的注解 Data注解 在JavaBean或类JavaBean中使用 这个注解包含范围最广 它包含getter setter NoArgsConstructor注解 即当使用当前注解时 会自动生成包含的所有方法 Gette
  • 如何使用纯js导出Excel+export2Excel.js的图片导出(包教包会)

    需求背景 有一天突发奇想 如果把导出excel放到前端去做会不会更加的简单一些呢 这样对数据库的访问也会比较少 然后就开始前端导出excel的探索 后来发现 如果业务量大 需求变更频繁 兼容问题 好像前端处理并不合适 但是还是记录下来 js
  • 【caffe-windows】 caffe-master 之 cifar10 超详细

    本教程尽量详细 大多步骤都有图 如果运行出错 请先对照自己的文件是否和图上的一样 包括标点啊 空格啊 斜杠 反斜杠啊之类的小细节 本例程是在 win10 64位 caffe master vs2013下进行的 并且已经配置GPU版本 若用C
  • fivem服务器文件,云梦YumVGTA5 FiveM 服务器插件管理器

    转载自云梦 yumy net YumV 是云梦开发的一个 GTA5 FiveM 服务器插件管理器 它的主要功能有 自动安装各种插件 载具 地图 人物 脚本以及工具等 自动更新插件到新版本 一键卸载指定的插件 支持自动更新自身插件到新版本 从
  • python 计算置信区间,Python求解正态分布置信区间

    Python求解正态分布置信区间 正态分布和置信区间 正态分布 Normal Distribution 又叫高斯分布 是一种非常重要的概率分布 其概率密度函数的数学表达如下 f x frac 1 sqrt 2 pi sigma e frac
  • 2. 前端基本知识笔记:HTML&CSS&JavaScript&Vue&ElementUI

    HTML CSS JavaScript三剑客 Web前端 1 HTML基本知识 1 1 HTML HyperText Markup Language 1 2 W3C标准 1 3 HTML 结构标签 1 4 1 基础标签 1 4 2 图片 音
  • 中文 Appium API 文档

    该文档是Testerhome官方翻译的 源地址 https github com appium appium tree master docs cn 官方网站上的 http appium io slate cn master ruby ab
  • 调用“抱抱脸团队打造的Transformers pipeline API” && 通过预训练模型,快速训练和微调自己的模型

    本文章根据官方文件总结而成 根据第三方库Transformers and pytorch快速搭建自己的神经网络架构 可以直接下载预训练模型 涉及的数据集包括音频 文字 图像等 实用性非常强 官方链接直达 GitHub huggingface
  • Qt的信号与槽

    引入 在GUI编程中 组件组件如何实现通信是核心的技术内容 Qt使用了信号与槽的机制 为此Qt引入了一些关键字slots signals emit 这些都是Qt特有的关键字 然后这些关键字会被Qt的moc转换位标准的C 语句 Qt 的部件类