Qt5学习笔记基础篇(2)信号和槽

信号和槽

2.1 概述

在GUI应用程序中,经常会有这样的需要,当一个组件的状态发生变化时其他组件要对此做出响应,比如单击关闭按钮后主程序要退出,文本框内容发生改变时提交按钮才生效,进度条要随着示数同步变化等等。为了实现这种对象之间的通信功能,不同的开发框架有不同的处理方式。在基于回调函数(callback)的框架中,我们把回调函数的指针传递给调用者,这样就可以在事件发生时通过指针找到回调函数并完成自动调用。Qt采用了不同的方式来实现对象之间的通信,事件产生时会自动发射信号(signals),而与之关联的槽函数(slot)则会在信号发射时执行,从而实现了对事件的响应,这一机制就是信号与槽机制

Qt中的组件(Widgets,这个词好难翻译啊)已经预定义了很多信号和槽,但是我们也可以通过继承的方式给Wdiget添加自己感兴趣的信号和槽。从C++的角度看,所谓信号与槽和普通的成员函数并没有区别,我们基本上可以像声明成员函数一样来声明一个信号,而槽函数也可以像普通成员函数那样被调用。微小的区别在于在这个过程中我们需要使用Qt提供的一些关键字和宏来完成信号与槽的创建和关联,具体的语法细节我们在后面的小节中叙述。

一个信号可以被多个槽函数接受,一个槽函数也可以对多种信号做出响应。Qt并没有对这些进行任何的限制,因为信号与槽彼此完全独立,信号不知道自己会不会被接受或者自己会被谁接受,槽函数也不知道自己在什么时候会对哪个信号进行响应,这样的实现最大程度上降低了对象之间的耦合,这就确保了可以通过Qt创建完全独立的组件。信号与槽的关系可以通过下图直观反映:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HCdE2e4Y-1581476882453)(img/2/1.png)]

2.2 使用预定义的信号和槽

Qt的QObject基类中已经预定义了很多信号和槽,对于常见事件我们可以直接拿来使用而不必自己重新定义。下面我们就试一下如何使用已有的信号和槽,我们试着响应按钮的单击事件,并在槽函数中修改主窗口的颜色。如果使用Qt Designer来设计界面,那么添加信号和槽的过程非常简单,只要右键单击感兴趣的组件然后选择转到槽即可,在弹出的对话框中我们可以根据需求选择信号,完成之后会跳转到槽函数体内,这种操作方式默认连接到主窗体的槽:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sjQs5jS2-1581476882455)(img/2/2.png)]

这里我们选择 clicked信号,单击OK按钮之后便会进入槽函数,该函数定义在主窗口类之中,我们创建如下的代码响应单击信号:

void TestWindow::on_changeColor_clicked()
{
    QPalette pal(this->palette());
    pal.setColor(QPalette::Background, Qt::green);
    this->setPalette(pal);
    this->show();
}

这样就实现了单击按钮后让主窗体背景变为绿色的功能,效果如下:

在这里插入图片描述

另外一种方法是进入设计模式,单击编辑菜单中的编辑信号和槽功能(也可以点击编辑模式工具栏下面的第二个小图标,快捷键F4),我们会发现设计界面基本没有变化,但是左面的控件面板变成了不可操作的灰色,这样就是进入了信号和槽编辑器。现在只需要左键点击要发射信号的组件并拖动到接受信号的组件(会看到一条红色连线),就会进入连接配置界面:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2H4gkY7u-1581476882461)(img/2/4.png)]

配置连接对话框中我们选择好需要使用的信号和槽就完成了信号与槽的连接。

2.3 信号与槽的连接

前面通过Qt Designer工具完成了对信号与槽的配置,这些图形工具做的也是背后添加响应代码来实现功能,所以我们也可以手动添加代码来完成信号与槽的定义和关联。这里完全通过编写代码的方式尝试重新实现上面的小项目,为了加深基础知识的理解,我们建立一个空项目:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9JGnWFz6-1581476882463)(img/2/5.png)]

剩下的操作和建立Qt Widgit项目的过程是类似的,只是省去了建立类的过程,建立好的空项目中除了一个工程配置文件以外再没有其他内容。该工程配置文件所遵循的语法可以在Qt参考文档中的qmake参考手册下的建立工程文件部分查询到。我们一定要在其中添加下面两行:

QT       += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

QT变量指定了在项目中使用的模块,如果不添加这个变量就无法找到各种头文件,具体信息按照上面指定的位置去官方文档参考吧,细节过多这里就不列出来了。

首先我们要声明一个继承自QWidget的主窗口类,该类的实例表示主窗口本身。然后分析需求,该程序的窗口构成非常简单,只需要一个QPushButton和一个QHBoxLayout用于布局,因此我们的类至少需要两个成员变量来保存他们(实际使用的是指针变量保存两个控件的地址)。进一步为了代码看起来更加规范,我们把跟界面创建有关的代码单独封装到一个界面类中,因此应该把保存控件的成员变量移动到界面类中,并且给界面类添加一个方法用于界面布局的初始化,而该方法中因为要访问窗口本身所以需要接收一个窗口对象为参数。

//changecolor.h
#ifndef CHANGECOLOR_H
#define CHANGECOLOR_H

#include<QWidget>
#include<QHBoxLayout>
#include<QPushButton>

class ChangeColor;//前置声明
class ui_ChangeColor//界面类
{
public:
    void setupUi(ChangeColor* widget);//初始化界面布局的方法
private:
    QHBoxLayout *h_layout;//保存布局控件
    QPushButton *changeButton;//保存按钮
};

class ChangeColor : public QWidget//窗口类
{
    Q_OBJECT
private:
    ui_ChangeColor *ui;//指向界面类实例
public:
    explicit ChangeColor();//构造函数,里面给ui初始化一个界面类实例,然后通过ui调用界面初始化方法
    ~ChangeColor();//析构函数,留空即可
public slots:
    void on_changecolor_clicked();//响应按钮单击信号的槽函数
};

#endif // CHANGECOLOR_H

指得注意的是Qt中凡是直接或间接继承自QObject的类都是支持信号与槽的,为了引入信号与槽语法上的扩展支持,需要在使用信号或者槽的类声明中写入Q_OBJECT宏,不妨给所有继承自QObject的类都加上这个宏,即便没有用到也不会带来任何坏处。

接下来需要给出上面声明的具体实现:

#include"changecolor.h"
#include<QHBoxLayout>
#include<QPushButton>
#include<QObject>

//界面初始化
void ui_ChangeColor::setupUi(ChangeColor *widget)
{
    widget->setObjectName(QStringLiteral("ChangeColor"));
    widget->resize(400, 300);//窗口大小

    h_layout = new QHBoxLayout(widget);
    h_layout->setObjectName(QStringLiteral("horizontallayout"));
    changeButton = new QPushButton();
    changeButton->setObjectName(QStringLiteral("changeColor"));
    changeButton->setText(QStringLiteral("Change Color"));//按钮显示文本
    h_layout->addWidget(changeButton);

    //连接信号和槽
    QObject::connect(changeButton,&QPushButton::clicked,widget,&ChangeColor::on_changecolor_clicked);
}

//构造函数
ChangeColor::ChangeColor():
    QWidget(),//调用基类构造函数
    ui(new ui_ChangeColor())//初始化Ui
{
    ui->setupUi(this);
}

ChangeColor::~ChangeColor()
{
}

//槽函数实现
void ChangeColor::on_changecolor_clicked()
{
    QPalette pal(this->palette());//使用当前调色板信息创建一个调色板对象
    pal.setColor(QPalette::Background, Qt::green);//更改调色板背景颜色为绿色
    this->setPalette(pal);//设置当前调色板
    this->show();
}

注意用于连接信号和槽的函数是QObject::connect,该函数最多需要5个参数,分别表示信号发射者,信号,信号接收者,槽,连接类型。该函数一共有五种重载形式:

connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)//等价于connect(sender, signal, this, method, type),信号和槽处于同一个对象
connect(const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type) constconnect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type)connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type)

注意以下几点:

  1. 五种形式中sender和receiver的表达比较统一,均定义为对象指针,其中sender参数必不可少,而receiver参数只存在于第一和第三种形式中。
  2. 信号和槽的表达有两种方式,一种是Qt5新增的基于指针的方式,另一种是基于字符串的方式,后者通常需要使用SIGNAL和SLOT宏将函数名转换为字符串。
  3. 连接类型参数具有默认值,调用时可以省略。
  4. 信号和槽的参数必须统一,槽的参数个数可以少于信号的参数个数(忽略末尾的几个参数),但是参数类型和顺序不能错,指针型的信号和槽将在编译器检查是否匹配,字符串型的匹配检查则要在运行时才完成。

2.4 自定义信号和槽

当预定义的信号不能满足需求时,我们也可以自定义,信号在语法形式上就是一个不加实现的public成员函数声明,他可以具有**任意参数列表和void返回值。**有了上面这些基础就可以很容易的创建信号和槽了,下面我们尝试一个信息发布订阅的例子:

//publisher.h
#ifndef PUBLISHER_H
#define PUBLISHER_H

#include <QObject>

class Publisher : public QObject
{
    Q_OBJECT
private:
    QString newsName;
public:
    explicit Publisher(const QString newsName, QObject *parent = nullptr);
    void send(const QString &newsName);
signals:
    void publishNew(const QString &name);//信号
public slots:
};

#endif // PUBLISHER_H

#endif  PUBLISHER_H


//subscriber.h
#ifndef SUBSCRIBER_H
#define SUBSCRIBER_H

#include <QObject>

class Subscriber : public QObject
{
    Q_OBJECT
public:
    explicit Subscriber(QObject *parent = nullptr);

signals:

public slots:
    void subscribe(const QString &newsName);
};

#endif SUBSCRIBER_H


//publisher.cpp
#include "publisher.h"

Publisher::Publisher(const QString newsName, QObject *parent) : QObject(parent)
{
    this->newsName = newsName;
}

void Publisher::send(const QString &newsName)
{
    this->newsName = newsName;
    emit this->publishNew(this->newsName);//发射信号
}


//subscriber.cpp
#include "subscriber.h"
#include <QDebug>
Subscriber::Subscriber(QObject *parent) : QObject(parent)
{

}

void Subscriber::subscribe(const QString &newsName)//槽函数
{
    qDebug()<<newsName<<"is subscribed!"<<endl;
}

//main.cpp
#include <QCoreApplication>
#include "publisher.h"
#include "subscriber.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Publisher publisher("Newspaper A");
    Subscriber subscriber;
    QObject::connect(&publisher, &Publisher::publishNew, &subscriber, &Subscriber::subscribe);
    publisher.send("Newspaper A");
    publisher.send("Newspaper B");
    publisher.send("Newspaper C");
    return a.exec();
}

关键字emit用于发射信号,信号的参数会传递给槽,通常用于将对象的状态传递给其他对象,这样接收方也就获得了信号发送发的状态。

上面例子的运行结果如下:

在这里插入图片描述

发布了22 篇原创文章 · 获赞 36 · 访问量 6080

猜你喜欢

转载自blog.csdn.net/cxm2643199642/article/details/104275197