Qt 信号和槽

news/2024/12/5 6:17:14/

一、信号和槽机制概述

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

槽的本质是类的成员函数,其参数可以是任意类型的。和普通C++成员函数几乎没有区别,它可以是虚函数;也可以被重载;可以是公有的、保护的、私有的、也可以被其他C++成员函数调用。唯一区别的是:槽可以与信号连接在一起,每当和槽连接的信号被发射的时候,就会调用这个槽。
早期,对象间的通信采用回调来实现。回调实际上是利用函数指针来实现,当我们希望某件事发生时处理函数能够获得通知,就需要将回调函数的指针传递给处理函数,这样处理函数就会在合适的时候调用回调函数。回调有两个明显的缺点:

  • 它们不是类型安全的,我们无法保证处理函数传递给回调函数的参数都是正确的。
  • 回调函数和处理函数紧密耦合,源于处理函数必须知道哪一个函数被回调。

二、典型应用示例

下面通过一个简单的例子来进一步讲解信号和槽的相关知识。新建Qt Gui应用,项目名称为mySignalSlot,基类选择QWidget,然后类名保持Widget不变。完成后首先在widget.h文件中声明信号和槽:

#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QDebug>namespace Ui {
class Widget;
}class Widget : public QWidget
{Q_OBJECT //必须包含的宏public:explicit Widget(QWidget *parent = nullptr);~Widget();public slots:void testSolts(); //测试槽函数signals:void testSignals(); //测试信号private:Ui::Widget *ui;
};#endif // WIDGET_H

使用信号和槽还必须在类声明 的最开始处添加Q_OBJECT宏,在这个程序中,类的声明是自动生成的,已经添加了这个宏。
signal的代码会由 moc 自动生成,开发人员不能在自己的C++代码中实现它。反之,槽应该由开发人员来实现。修改widget.cpp文件如下:

#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{ui->setupUi(this);//连接信号槽connect(this,SIGNAL(testSignals()),this,SLOT(testSolts()));//发射信号emit testSignals();
}Widget::~Widget()
{delete ui;
}//测试槽函数
void Widget::testSolts()
{qDebug() << "调用了测试槽函数";
}

这个例子实现的效果是:发射信号后,会调用testSolts()槽函数,则“应用程序输出”窗口会打印出“调用了测试槽函数”。

三、使用信号和槽的注意事项

下面列举一下使用信号和槽应该注意的注意事项:

  • 发送者和接收者都需要继承自QObject或其子类;
  • 必须在类声明的最开始添加Q_OBJECT宏;
  • 槽中的参数类型要和信号的参数的类型相对应,且不能比信号的参数多;
  • 使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;
  • 使用 emit 在恰当的位置发送信号;
  • 槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
  • 任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数。

四、信号和槽的关联

信号和槽进行关联使用的是QObject类的connect()函数,Qt4中这个函数的原型如下:

static QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal,const QObject *receiver, const char *member, Qt::ConnectionType = Qt::AutoConnection);

第一个参数sender为发送信号的对象,第二个参数signal为要发送的信号,第三个参数receiver为接收信号的对象,第4个参数slot为接收对象在接收到信号之后所需要调用的槽函数。connect()函数的最后一个参数表明了关联的方式,默认值是Qt::AutoConnection。对于信号和槽,必须使用SIGNAL()和SLOT()宏,它们可以将其参数转化为const char*类型。connect()函数的返回值为bool类型,当关联成功时返回true
对于信号和槽的参数问题,基本原则是信号中的参数类型要和槽中的参数类型相对应,而且信号中的参数可以多于槽中的参数,但是不能反过来;如果信号中有多余的参数,那么它们将被忽略。
而Qt5中connect()函数新加入的一种重载形式如下:

static QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal,const QObject *receiver, PointerToMemberFunction member, Qt::ConnectionType type = Qt::AutoConnection);

与Qt4最大的不同就是,指定信号和槽两个参数时可以不用再使用SIGNAL()和SLOT()宏,并且槽函数不再必须是使用slots关键字声明的函数,而可以是任意能和信号关联的成员函数。要使一个成员函数可以和信号关联,那么这个函数的参数数目不能超过信号的参数数目,但是并不要求该函数拥有的参数类型与信号中对应的参数类型完全一致,只需要可以进行隐式转换即可。使用这种重载形式,前面程序中的关联可以使用以下代码代替:

connect(this,&Widget::testSignals,this,&Widget::testSolts);

使用这种方式与前一种相比,还有一个好处就是可以在编译时进行检查,信号与槽的拼写错误、参函数参数数目多于信号的参数数目等错误在编译时就能够发现。所以建议在编写Qt5代码时使用这种关联形式。
信号和槽还有一种自动关联方式,例如在设计模式直接生成的按钮的单击信号的槽,就是使用的这种方式: on_pushButton_clicked() 由字符串on、部件的objectName和信号名称这三部分组成,中间用下划线隔开。这样形式命名的槽可以直接和信号关联,不用再使用connect()函数,不过使用这种方式还要进行其他设置。

五、断开信号和槽的关联

可以通过disconnect()函数来断开信号和槽的关联,其原型如下:

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

(1)断开与一个发送对象所有信号的所有关联

disconnect(myObject, nullptr, nullptr, nullptr);

等价于

myObject->disconnect();

(2)断开与一个指定信号的所有关联

disconnect(myObject, SIGNAL(mySignal()), nullptr, nullptr);

等价于

myObject->disconnect(SIGNAL(mySignal()));

(3)断开与一个指定接受对象的所有关联

disconnect(myObject, nullptr, myReceiver, nullptr);

等价于

myObject->disconnect(myReceiver);

(4)断开一个指定信号和槽的关联

disconnect(myObject, SIGNAL(mySignal()), myReceiver, SLOT(mySlot()));

其用法类似,只是其信号、槽参数需要使用函数指针 &MyObject::mySignal()、&MyReceiver::mySlot() 等形式。这个函数并不能断开信号与一般函数或者lambda表达式之间的关联,如果有这方面需要,则可以使用connect()返回值进行断开。实际上当对象被delete时,其关联的所有链接都会失效,QT会自动移除和这个对象的所有链接。

六、信号和槽的更多用法

(1)一个信号可以连接多个槽
使用QObject::connect可以把一个信号连接到多个槽,而当信号发射时,将按声明联系时的顺序依次调用槽

MyStr  a;
MyStr  b;
MyStr  c;
//信号连接到两个槽
QObject::connect(&a,SIGNAL(valueChanged(QString)),&b,SLOT(setValue(QString)));
QObject::connect(&a,SIGNAL(valueChanged(QString)),&c,SLOT(setValue(QString)));
a.setValue("this is A");
//依次调用b.setValue()、c.setValue()

(2)多个信号可以连接同一个槽
同样的,可以让多个信号连接到同一个槽上 ,而且其中的每一个信号的发送,都会调用了那个槽。

MyStr  a;
MyStr  b;
MyStr  c;
//两个信号连接到同一个槽
QObject::connect(&a,SIGNAL(valueChanged(QString)),&c,SLOT(setValue(QString)));
QObject::connect(&b,SIGNAL(valueChanged(QString)),&c,SLOT(setValue(QString)));
//下面的操作皆会调用到槽c.setValue()
a.setValue("this is A");
b.setValue("this is B");

(3)一个信号可以和另外一个信号相连接
当发射第一个信号的时候,也会把第二个信号一个发送出去。

MyStr  a;
MyStr  b;
MyStr  c;
//两个信号相连接
QObject::connect(&a,SIGNAL(valueChanged(QString)),&b,SIGNAL(valueChanged(QString)));
//再建立b与c的连接
QObject::connect(&b,SIGNAL(valueChanged(QString)),&c,SLOT(setValue(QString)));
//下面的操作同时发送了信号a.valueChanged与b.valueChanged
a.setValue("this is A");
//从而信号b.valueChanged被槽c.setValue所接收

http://www.ppmy.cn/news/522795.html

相关文章

安卓通话记录转移到iphone_如何将Android SMS,联系人和通话记录导入iPhone

Apple如何导入2113&#xff1f;当我们更换新手机时&#xff0c;总共5261将面临数据传输4102的问题&#xff0c;但是一些小伙伴对此并不熟悉&#xff0c;因此今天1653编辑器总结了几种方法&#xff0c;所有这些方法都非常简单易用&#xff0c;以下是来学习吧. 方法一: 直接用SIM…

android删除某个号码的通话记录

增加权限manifast.xml: <uses-permission android:name"android.permission.READ_CONTACTS" /> <uses-permission android:name"android.permission.WRITE_CONTACTS" />删除某个号码的通话记录&#xff1a; public void DeleteCallLogByNumbe…

一般通话记录能保存多少条_iPhone最近通话保存多久_iPhone保存多少条通话记录_智能家...

iphone所搭载的A12处理器以及最新的iOS12系统&#xff0c;能够给我们带来非常强大的性能&#xff0c;其实你能够给我们带来十分流畅的体验&#xff0c;不仅如此&#xff0c;即便是现如今的华为已经拿走了拍照第一&#xff0c;但对于苹果来说&#xff0c;仅仅用双1200的变焦双摄…

手机通话记录重复显示怎么处理_手机通话记录怎么找回

手机通话记录误删除怎么找回呢&#xff1f;最新版本的百度云管家提供了“手机忘带”功能&#xff0c;用于在忘带手机时查看手机通话记录的功能&#xff0c;借助此功能&#xff0c;我们还可以找到手机中误删除的重要通话记录。下面就是具体的操作方法。 要想找回手机中误删除的通…

安卓删除通话记录恢复?如何来恢复

安卓删除通话记录恢复&#xff1f;如何来恢复&#xff1f;我们有些时候经常的会将我们的手机通话记录给忽略掉&#xff0c;因为我们并不是经常的会使用到我们的手机通话记录&#xff0c;那么当我们删除我们的手机通话记录之后&#xff0c;我们该怎么恢复呢&#xff1f; 其实恢复…

通话记录

&#xfeff;&#xfeff; 通话记录步骤 1 通话记录的查看&#xff1b; *** CallLog.Calls 完成对表的管理 *** CACHED_PHOTO_ID "photo_id";被隐藏隐藏直接引用 --1.0添加读写权限《user_permission <uses-permission android:name"android.permission.R…

Android——查询通话记录和删除记录

------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流&#xff01; ------- 负责存放呼叫记录的内容提供者源码在 ContactsProvider 项目下&#xff1a; 源码路径&#xff1a; com/android/providers/contacts/CallLogProvider.java 使用到的数据库在&#xff1a…

MIPI CSI RX接口简介

MIPI 是 Mobile Industry Processor Interface&#xff08;移动行业处理器接口&#xff09;的缩写。 MIPI CSI-2 (Camera) and MIPI DSI (Display)则是目前业界使用最广的两个 MIPI 接口标准&#xff0c;而这也是和视频传输相关的标准&#xff0c;本文主要对 CSI-2 摄像头标准进…