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

论坛 期权论坛 脚本     
匿名网站用户   2020-12-21 11:19   3757   0

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}

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

  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种方法。

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1136255
帖子:227251
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP