当前位置: 首页 > news >正文

Qt 实战(4)信号与槽 | 4.1、信号与槽机制

文章目录

  • 一、信号与槽机制
    • 1、基本概念
    • 2、信号与槽函数连接
      • 2.1、connect+宏实现信号与槽连接
      • 2.2、Qt5新connect函数
      • 2.3、使用函数指针
      • 2.4、使用lambda表达式
      • 2.5、使用Qt Creator添加信号的槽函数
    • 3、断开信号与槽函数之间的连接
      • 3.1、断开所有信号与特定槽的连接
      • 3.2、断开特定信号与所有槽的连接
      • 3.3、断开特定信号与特定槽的连接
    • 4、信号与槽函数类型要匹配
    • 5、总结

前言:

Qt信号与槽机制是一种用于处理对象间通信的强大机制,它是Qt框架的核心特性之一。信号与槽机制使得Qt对象可以在不了解彼此的情况下进行通信,这种松耦合的设计思想极大地提高了代码的可重用性和灵活性。

一、信号与槽机制

1、基本概念

在Qt中,一个对象可以发出一个信号,而另一个对象的槽函数可以接收这个信号并作出响应。信号是一个类的成员函数,当某个特定的事件发生时,它会被自动调用。槽函数也是类的成员函数,它可以被信号连接,当信号发出时,槽函数会被自动执行。下面是与信号槽机制相关的基本概念,如下:

1)信号

在Qt中,信号是一个特定的成员函数,用于在某个特定事件发生时被发射(emit)。信号本身并不实现任何功能,它只是表明某个事件已经发生。信号可以被定义为一个类的成员函数,但其实现是由Qt的元对象系统自动完成的。信号的声明通常在类的头文件中进行,使用signals关键字来标记。

2)槽

槽是普通的成员函数,可以被信号触发。当信号被发射时,与之连接的槽函数将被调用。槽函数可以实现具体的功能,比如更新界面、处理数据等。槽函数的声明和普通成员函数一样,但在Qt的元对象系统中需要进行一些特殊的标记,以便与信号进行连接。

2、信号与槽函数连接

信号与槽的连接是通过QObject::connect()函数实现的,需要注意的是,为了使类支持信号与槽机制,需要在类的声明中包含Q_OBJECT宏。下面介绍几种不同的连接方式,如下:

2.1、connect+宏实现信号与槽连接

在Qt4及之前的版本基于connect+宏实现信号与槽绑定,其中发送信号和槽函数需要用 SIGNAL()SLOT() 来进行声明,connect函数声明如下:

QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)

下面是一段示例代码,如下:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);// 登录按键绑定槽函数connect(this->ui->btn_ok, SIGNAL(clicked(bool)), this, SLOT(login()));}void MainWindow::login()
{QString username = ui->lineEdit_username->text();QString password = ui->lineEdit_password->text();if (username == "jack" && password == "12345") {qDebug() << "login success";} else {qDebug() << "login fail";}
}

基于connect+宏实现信号与槽连接,需要注意下面这些问题

  • 声明槽函数要使用private slotspublic slots关键字
  • 信号和槽参数不能包含任何变量名,只能包含类型

2.2、Qt5新connect函数

Qt5推出了新的connect函数,不需要使用SIGNAL()SLOT()宏,可以在编译时做类型检查,connect函数声明如下:

QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection)

使用这种方法槽函数的声明不需要放到slots中,只要像普通的函数一样声明就可以了,类型需要与信号保持一致,下面给登录按键绑定槽函数,如下:

class MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();// 声明槽函数,与普通的成员函数一样void login();private:Ui::MainWindow *ui;
};

绑定槽函数

MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);// 登录按键绑定槽函数connect(ui->btn_ok, &QPushButton::clicked, this, &MainWindow::login);
}void MainWindow::login()
{qDebug() << "login";
}

2.3、使用函数指针

在Qt 5版本的connect 函数里,信号与槽函数的参数其实都是函数指针,当信号或槽函数有重载时,使用函数指针可以明确告诉编译器使用哪一个重载函数避免歧义

public:MainWindow(QWidget *parent = nullptr);~MainWindow();// 声明两个同名的login函数void login();void login(int state);

通过函数指针绑定槽函数

MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);// 定义函数指针(注意:声明指向成员函数的指针时,要增加类作用域)void (MainWindow::*pfnLoginSlot)() = &MainWindow::login;// 登录按键绑定无参槽函数connect(ui->btn_ok, &QPushButton::clicked, this, pfnLoginSlot);
}void MainWindow::login()
{qDebug() << "login";
}void MainWindow::login(int state)
{qDebug() << "login state";
}

2.4、使用lambda表达式

在connect函数中,槽函数参数可以改用Lambda表达式的方式来进行传参。使用 Lambda表达式的好处是代码的书写更加方便快捷,同时不需要在类中对槽函数做任何的声明了

MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);// 使用Lamdbda表达式作为槽函数connect(ui->btn_ok, &QPushButton::clicked, this, [=](){qDebug() << "login";});
}

2.5、使用Qt Creator添加信号的槽函数

通过Qt Creator 界面来完成发送信号和槽函数的连接,比如右键点击一个按钮,然后选择“转到槽”:

在这里插入图片描述

Qt Creator会自动生成如下代码,首先是槽函数的声明:

// 槽函数声明
private slots:void on_btn_cancel_clicked(bool checked);

槽函数实现,如下:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::on_btn_cancel_clicked(bool checked)
{}

使用这种方法不需要使用connect函数将信号与槽函数做连接。 这里槽函数的命名有一定的规则,一般是 on_objectname_signal 这样来命名的。这种方法优点是减少了手动敲代码的工作量,缺点是究竟有哪些信号与槽函数做了连接不易被发现,没有connect 函数看起来直观。

3、断开信号与槽函数之间的连接

在Qt中,断开(disconnect)信号与槽函数之间的连接是一个重要的操作,特别是在对象被销毁、连接不再需要或者需要改变信号与槽的关联关系时。Qt提供了QObject::disconnect函数来断开信号与槽之间的连接。以下是几种常见的断开信号与槽的方法:

3.1、断开所有信号与特定槽的连接

如果你想要断开一个对象上所有信号与某个特定槽的连接,可以使用以下方式:

// Qt4
QObject::disconnect(sender, nullptr, receiver, SLOT(specificSlot()));// Qt5
QObject::disconnect(sender, nullptr, receiver, &ReceiverClass::specificSlot);

3.2、断开特定信号与所有槽的连接

如果你想要断开某个特定信号与所有槽的连接,可以这样做:

// Qt4
QObject::disconnect(sender, SIGNAL(specificSignal()), nullptr, nullptr);// Qt5
QObject::disconnect(sender, &SenderClass::specificSignal, nullptr, nullptr);

3.3、断开特定信号与特定槽的连接

如果你想要断开某个特定信号与某个特定槽的连接,可以这样做:

// Qt4
QObject::disconnect(sender, SIGNAL(specificSignal()), receiver, SLOT(specificSlot()));// Qt5
QObject::disconnect(sender, &SenderClass::specificSignal, receiver, &ReceiverClass::specificSlot);

注意:当QObject被销毁时,Qt会自动断开所有与之相关的信号和槽的连接。但是,如果你想要更早地断开连接,或者想要确保某些连接在对象销毁之前被断开,那么你需要显式调用QObject::disconnect。

4、信号与槽函数类型要匹配

信号与槽函数参数类型要匹配,例如:信号函数带的参数是QString,那么绑定的槽函数要么不带参数(表示不接收数据),要么是跟信号函数一样的参数,下面的代码编译时会报错,因为信号与槽函数参数类型不匹配,如下:

MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);// 登录按键绑定槽函数connect(ui->btn_ok, &QPushButton::clicked, this, &MainWindow::login);m_pStudent = new Student(this);m_pTeacher = new Teacher(this);// 信号函数不带参数,槽函数带参数(QString),信号与槽函数参数类型不匹配编译时报错void (Teacher::*pfnTeacherSignal)() = &Teacher::hungry;void (Student::*pfnStudentSlot)(QString) = &Student::treat;QMetaObject::Connection conn = connect(m_pTeacher, pfnTeacherSignal, m_pStudent, pfnStudentSlot);
}

5、总结

Qt的信号与槽机制提供了一种强大且灵活的方式来处理对象间的通信,它是Qt框架中不可或缺的一部分。通过信号和槽,开发者可以轻松地构建出响应式、事件驱动的用户界面和应用程序逻辑。

相关文章:

  • 切换到root用户的方法和区别
  • Linux 编写脚本自动清理旧的日志文件,释放磁盘空间
  • 图论之岛屿系列
  • QGraphicsItem 自定义是否被选中
  • 人体接近传感器,ATM微波传感器,人体存在传感器 微波探测器YTMW8631
  • Python办公自动化—pandas读取Excel进行插入列、修改列的类型,apply函数与字典结合匹配等操作+完整代码
  • leetcode 二分查找·系统掌握 寻找比目标字母大的最小字母
  • 海思SS928/SD3403开发笔记1——使用串口调试开发板
  • PHP 命名空间
  • 基于Spring Boot+VUE职称评审管理系统
  • teamview的商业用途
  • Adobe XD是否收费?试试这几款超值的免费软件吧!
  • 本地运行大语言模型(LLMs)
  • Character Animator 2024 mac/win版:赋予角色生命,动画更传神
  • 北大oj Coins
  • 《用数据讲故事》作者Cole N. Knaflic:消除一切无效的图表
  • 【知识碎片】第三方登录弹窗效果
  • C# 免费离线人脸识别 2.0 Demo
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • Fundebug计费标准解释:事件数是如何定义的?
  • JavaScript设计模式与开发实践系列之策略模式
  • Java超时控制的实现
  • Java多线程(4):使用线程池执行定时任务
  • Python 使用 Tornado 框架实现 WebHook 自动部署 Git 项目
  • SpiderData 2019年2月23日 DApp数据排行榜
  • spring学习第二天
  • 欢迎参加第二届中国游戏开发者大会
  • 前端代码风格自动化系列(二)之Commitlint
  • 如何学习JavaEE,项目又该如何做?
  • 入门到放弃node系列之Hello Word篇
  • 体验javascript之美-第五课 匿名函数自执行和闭包是一回事儿吗?
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • # SpringBoot 如何让指定的Bean先加载
  • # Swust 12th acm 邀请赛# [ K ] 三角形判定 [题解]
  • #在线报价接单​再坚持一下 明天是真的周六.出现货 实单来谈
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (C++)栈的链式存储结构(出栈、入栈、判空、遍历、销毁)(数据结构与算法)
  • (NO.00004)iOS实现打砖块游戏(十二):伸缩自如,我是如意金箍棒(上)!
  • (STM32笔记)九、RCC时钟树与时钟 第二部分
  • (ZT)薛涌:谈贫说富
  • (一)pytest自动化测试框架之生成测试报告(mac系统)
  • (转)mysql使用Navicat 导出和导入数据库
  • (转)负载均衡,回话保持,cookie
  • *1 计算机基础和操作系统基础及几大协议
  • .NET Compact Framework 多线程环境下的UI异步刷新
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .NET MVC 验证码
  • .Net Remoting(分离服务程序实现) - Part.3
  • .NET中两种OCR方式对比
  • @Bean有哪些属性
  • @CacheInvalidate(name = “xxx“, key = “#results.![a+b]“,multi = true)是什么意思
  • @GetMapping和@RequestMapping的区别
  • @Mapper作用
  • [51单片机] 简单介绍 (一)