Qt中connect函数不能传递参数的两种解决方法
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>
#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;
QString list[5] = {"item1", "item2", "item3", "item4", "item5"};
private slots:
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], 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种方法。
上一篇: 程序中的时间问题