Qt中connect函数不能传递参数的两种解决方法

匿名 (未验证) 提交于 2019-12-03 00:27:02

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}

它可以分解为一下几个部分:

  1. [capture]:方括号,其内容是捕捉列表,总是出现在Lambda函数的开始处,是Lambda的引出符。它能够捕捉上下文中的变量供函数体使用。这里可以填入具体的变量名,也可以使用“=”,代表以值传递方式捕捉所有父作用域的变量,还可以使用“&”代表以引用传递方式捕捉所有父作用域的变量。
  2. (parameters):参数列表,表示传递给函数体的参数。如果不需要参数传递,可以省略这一部分。
  3. mutable:修饰符,默认情况下,Lambda函数是一个const函数,使用mutable可以取消其常量性。比如使用引用传递的时候就可以修改参数值等。使用它时参数列表不可省略,即使无参数传递。
  4. ->return-type:返回类型。不需要返回或返回类型明确时可以省略这一部分。
  5. {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种方法。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!