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
函数能够创建一个从事件发出对象到事件接受对象的连接,返回一个用来解除这种连接的句柄。你可以用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有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();
}