信号槽机制

Qt的信号槽机制主要是为了解耦Qt控件的事件和对应的处理逻辑,它能使得代码逻辑更加清淅。信号在某个操作下被触发,其对应槽函数的代码被回调执行。一个信号可以绑定多个槽,一个槽也可以对应多个信号。

信号槽基本使用例子

在Qt简介和环境搭建章节,我们写了一个最简单的HelloWorld程序,这里我们再次实现相同的功能。

main.cpp

#include <QApplication>
#include <QPushButton>
#include <QWidget>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    //主窗口
    QWidget w;
    //创建一个按钮
    QPushButton button("点击我关闭窗口", &w);
    //使用connect函数,连接按钮的点击事件和窗口的关闭动作
    QObject::connect(&button, SIGNAL(clicked()), &w, SLOT(close()));
    //显示主窗口
    w.show();

    return app.exec();
}

注意其中我们使用了QObject::connect这个函数,它响应按钮点击事件(click),对应事件的处理函数则是关闭主窗口(close)。这里我们可以查阅文档,具体看一下这个connect函数如何使用。

使用connect函数

connect函数能够创建一个从事件发出对象到事件接受对象的连接,返回一个用来解除这种连接的句柄。你可以用SIGNAL()SLOT()这两个宏对信号和接受函数进行预处理(此时函数定义的参数类型是const char *),也可以直接传递函数指针。

Qt中,将发出事件的函数叫信号函数,接收事件的函数叫槽函数,我们可以将多个信号绑定在一个槽上,也可以绑定一个信号到多个槽上,这就是Qt中所谓的信号槽机制。

带有参数的信号和槽函数

下面是一个带参数的信号槽例子。

QLabel *label = new QLabel;
QScrollBar *scrollBar = new QScrollBar;
QObject::connect(scrollBar, SIGNAL(valueChanged(int)),
                   label,  SLOT(setNum(int)));

很显然,上面例子代码实现的功能,就是让一个标签显示滚动条的位置。注意,和HelloWorld程序不同的是,上面例子代码中,信号和槽函数都包含了一个int类型的参数。使用SIGNAL()SLOT()时,我们要声明参数的类型,但是不能带有任何形参名。

除此之外,为了保证槽函数能够正确接受信号,显然信号函数和槽函数的参数类型也必须是一致的。

connect函数的其它写法

根据文档,connect有5个重载函数,实际上使用SIGNAL()SLOT()是Qt4时代比较古老的一种信号槽机制实现方式了,Qt5中,我们也可以直接向connect函数传函数指针,下面是一个例子。

QObject::connect(&button, &QPushButton::clicked, &w, &QWidget::close);

当然,使用Lambda表达式也是可以的,但和之前的写法一样要保证信号函数和槽函数的参数一致。

默认简写槽函数

使用Qt Designer时我们给一个按钮绑定回调,经常使用的方式是在按钮上右键,点击Go to slot...,这个操作会生成一个默认槽函数。

private slots:
    void on_pushButton_clicked();
void MainWindow::on_pushButton_clicked() {}

这种情况下,实际上我们不需要编写connect函数来连接按钮对象的click()。Qt中,这类简单的回调可以将函数名简写为on_<对象名>_<信号名>(<参数>),这样就省去了编写connect函数的麻烦。如果是简单的按钮点击监听等功能,建议通过这种方式实现。

自定义信号和槽函数

如果我们想在一个自定义的控件类上手动发射信号,可以使用signals关键字定义信号并使用emit关键字发射,然后通过connect函数绑定接收。定义信号很简单,只要在头文件中像函数一样声明即可,下面是一个例子。

signals:
    void mySignal(const QString s);

发射信号写法如下。

emit mySignal(s);

使用emit发射信号后,被connect的槽函数就会被回调,收到和信号函数一致的实参。自定义槽函数就更简单了,和signals差不多,只要在头文件中使用slots声明一个函数,然后实现它即可。

自定义信号槽的一个常见用途就是在窗口间传递数据。下面例子中,子窗口向主窗口发送信号,传递一个字符串,添加到主窗口的listWidget中。

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QString>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_clicked();
    void onAddData(QString s);

private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "mydialog.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    MyDialog *myDialog = new MyDialog(this);
    myDialog->setAttribute(Qt::WA_DeleteOnClose);
    connect(myDialog, &MyDialog::addData, this, &MainWindow::onAddData);
    myDialog->show();
}

void MainWindow::onAddData(QString s)
{
    ui->listWidget->addItem(s);
}

mydialog.h

#ifndef MYDIALOG_H
#define MYDIALOG_H

#include <QString>
#include <QDialog>

namespace Ui {
class MyDialog;
}

class MyDialog : public QDialog
{
    Q_OBJECT

public:
    explicit MyDialog(QWidget *parent = nullptr);
    ~MyDialog();

private:
    Ui::MyDialog *ui;

signals:
    void addData(QString s);
private slots:
    void on_pushButton_clicked();
};

#endif // MYDIALOG_H

mydialog.cpp

#include "mydialog.h"
#include "ui_mydialog.h"

MyDialog::MyDialog(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::MyDialog)
{
    ui->setupUi(this);
}

MyDialog::~MyDialog()
{
    delete ui;
}

void MyDialog::on_pushButton_clicked()
{
    QString s = ui->lineEdit->text();
    emit addData(s);
    ui->lineEdit->clear();
}
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap