Qt中的connect函数可以让我们动态地管理信号和槽。
比如现在界面上有一个标签,id为label。我现在想要动态地创建一个按键,id为push,然后利用connect函数,实现点击push以后,label上显示“Hello world!”,代码如下:
<mainwindow.h>
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private: Ui::MainWindow *ui; private slots: void showLabel(); }; #endif // MAINWINDOW_H <mainwindow.cpp>
#include "mainwindow.h" #include "ui_mainwindow.h" #include <QPushButton> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); //新建一个按钮,id为push QPushButton * push = new QPushButton(this); //设置按钮的(x,y)坐标、长、宽 push->setGeometry(150, 170, 89, 24); //设置按键上显示的文字 push->setText("button"); //将信号和槽连接 connect(push, SIGNAL(clicked()), this, SLOT(showLabel())); } MainWindow::~MainWindow() { delete ui; } void MainWindow::showLabel() { ui->label->setText("Hello world!"); } 这就实现了动态创建一个控件并连接信号和槽。
题外:这里的信号(SIGNAL)可以通过如下方式找到,即右键ui编辑界面中的一个控件,然后点击转到槽:

但是有时候我们可能需要动态创建多个按键,数量我们也并不清楚。我们希望将其存放在一个数组中,每个按键的功能相似,但是也有略微区别,这个区别和它在数组中的下标有关。举个简单的例子,现在我们有一个私有变量,存放了一个QString的数组,一共5项。我们希望动态地创建一个大小为5的QPushButton数组,实现的功能是点击第i个按键就让label显示第i个QString。代码如下:
<mainwindow.h>
<mainwindow.cpp>
#include "mainwindow.h" #include "ui_mainwindow.h" #include <QPushButton> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); //新建一个按钮数组,id为push[i] QPushButton * push[5]; for (int i = 0; i < 5; i++) { push[i] = new QPushButton(this); push[i]->setGeometry(300, 60 + 30 * i, 89, 24); push[i]->setText(QString("button%1").arg(i)); connect(push[i], SIGNAL(clicked()), this, SLOT(showLabel(i))); } } MainWindow::~MainWindow() { delete ui; } void MainWindow::showLabel(int i) { ui->label->setText(QString("button%1 is clicked").arg(i)); } 这是一个错误的方式,报错信息如下:
QObject::connect: No such slot MainWindow::showLabel(i) in ……
QObject::connect: (receiver name: 'MainWindow')
这是connect的机制导致的。网上有很多解释这个的教程,简单来说就是SLOT()中的槽函数如果需要参数,那么这个参数必须来自于SIGNAL()中信号函数的参数。也就是这样:
//这是一个错误的语句! connect(sender, SIGNAL(signal()), context, SLOT(slot(int i))); //这才是正确的! connect(sender, SIGNAL(signal(int i, char c, ...)), context, SLOT(slot(int i)));
这就意味着如果信号函数不能传递我们需要的参数,我们就无法分别给每个按钮分配不同的任务。
这篇文章就是为了解决这种问题的。这里提供两种思路。(其实是2.5种)
QSignalMapper
我们可以将QSignalMapper理解为一个消息转发器,其中存储的是一系列的键值对,这里先来看代码。
<mainwindow.h>
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QString> #include <QSignalMapper> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private: Ui::MainWindow *ui; QString list[5] = {"item1", "item2", "item3", "item4", "item5"}; QSignalMapper * myMapper; private slots: void showLabel(int i); }; #endif // MAINWINDOW_H <mainwindow.cpp>
#include "mainwindow.h" #include "ui_mainwindow.h" #include <QPushButton> #include <QSignalMapper> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); myMapper = new QSignalMapper(); //新建一个按钮数组,id为push[i] QPushButton * push[5]; for (int i = 0; i < 5; i++) { push[i] = new QPushButton(this); push[i]->setGeometry(300, 60 + 30 * i, 89, 24); push[i]->setText(QString("button%1").arg(i)); connect(push[i], SIGNAL(clicked()), myMapper, SLOT(map())); myMapper->setMapping(push[i], i); } connect(myMapper, SIGNAL(mapped(int)), this, SLOT(showLabel(int))); } MainWindow::~MainWindow() { delete ui; } void MainWindow::showLabel(int i) { ui->label->setText(QString("button%1 is clicked").arg(i)); } 运行以后就可以发现,点击每个按键都会有不同的效果,如图所示:


这里对几个重要的语句进行分开解释。
connect(push[i], SIGNAL(clicked()), myMapper, SLOT(map()));
这句话中的信号是按键的点击事件,槽则可以理解为查询QSignalMapper键值对。也就是每次点击都会触发对QSignalMapper的查询。
myMapper->setMapping(push[i], i);
QSignalMapper的内容就是由这句话来设置。它为其添加了一个映射项,键是按键的id,值是一个int类型的值。这里可以根据需要修改数据类型。这句话执行完以后就建立了一个键值对,将每个按钮喝它们各自的下标关联了起来。
connect(myMapper, SIGNAL(mapped(int)), this, SLOT(showLabel(int)));
槽函数map()查询QSignalMapper成功后会返回一个信号mapped(...),这里的参数是一个int,这个整型变量就是之前映射项中的值。这样就能构造出一个带参数的信号,就可以通过connect传递了。
整个过程大概就是:每建立一个按键,就执行一个connect,让它们的点击信号能触发一个查询QSignalMapper的槽。而QSignalMapper中的内容为按键和整型变量的键值对。根据点击的按键可以查询到唯一一个映射项,并发射一个信号,其参数为按键对应的值。这个信号就可以触发自己定义的槽函数,实现参数的传递。
也就是说,这里所做的全部工作就是让connect的信号函数拥有我们需要的参数,那么如果它本身自带参数不就完美了?这里我给大家推荐一个控件,名为Table Widget。这是一个表格,点击每一个格子都可以触发一个信号 cellEntered(int, int) ,参数分别是格子所在的行号和列号。发现了吗?这个信号函数自带参数,并且可以通过这个参数确定点击的位置。详细操作这里不再赘述,作为第0.5个方法。
Lambda
lambda表达式是c++11的新增特性,用于创建匿名函数。
先看代码:
<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 = 0); ~MainWindow(); private: Ui::MainWindow *ui; void showLabel(int i); }; #endif // MAINWINDOW_H <mainwindow.cpp>
#include "mainwindow.h" #include "ui_mainwindow.h" #include <QPushButton> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); //新建一个按钮数组,id为push[i] QPushButton * push[5]; for (int i = 0; i < 5; i++) { push[i] = new QPushButton(this); push[i]->setGeometry(300, 60 + 30 * i, 89, 24); push[i]->setText(QString("button%1").arg(i)); connect(push[i], &QPushButton::clicked, this, [ = ] { showLabel(i); }); } } MainWindow::~MainWindow() { delete ui; } void MainWindow::showLabel(int i) { ui->label->setText(QString("button%1 is clicked").arg(i)); } 这和之前的操作差别在于这句话:
connect(push[i], &QPushButton::clicked, this, [ = ] { showLabel(i); });这里的槽就是一个Lambda匿名函数,完整形式如下:
[capture](parameters) mutable ->return-type{statement}它可以分解为一下几个部分:
- [capture]:方括号,其内容是捕捉列表,总是出现在Lambda函数的开始处,是Lambda的引出符。它能够捕捉上下文中的变量供函数体使用。这里可以填入具体的变量名,也可以使用“=”,代表以值传递方式捕捉所有父作用域的变量,还可以使用“&”代表以引用传递方式捕捉所有父作用域的变量。
- (parameters):参数列表,表示传递给函数体的参数。如果不需要参数传递,可以省略这一部分。
- mutable:修饰符,默认情况下,Lambda函数是一个const函数,使用mutable可以取消其常量性。比如使用引用传递的时候就可以修改参数值等。使用它时参数列表不可省略,即使无参数传递。
- ->return-type:返回类型。不需要返回或返回类型明确时可以省略这一部分。
- {statement}:函数体,可以使用所有捕获与传递的变量。
也就是说我这里定义了一个Lambda匿名函数,捕获了所有父作用域的变量,在函数体内调用了showLabel(int i)函数。这里也可以将showLabel函数嵌入到Lambda函数内,如下所示:
connect(push[i], &QPushButton::clicked, this, [ = ] { ui->label->setText(QString("button%1 is clicked").arg(i)); });由于可以直接使用父作用域的变量,这里就不用担心signal没有参数传递了。
需要注意的是,这里的connect参数里的槽函数不能使用如下的SIGNAL()……SLOT() 形式:
connect(push[i], SIGNAL(clicked()), this, SLOT([i] { showLabel[i]; }));因为这是现场定义的函数,并不属于mainwindow的槽函数。这样写会有如下报错:
QObject::connect: No such slot MainWindow::[i] (){ showLabel[i]; } in ……
QObject::connect: (receiver name: 'MainWindow')
以上就是connect函数给槽函数传递参数的2.5种方法。