锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

Qt 之connect 信号和槽函数连接的几种方法的总结(含signalmaper、lamda方式)

时间:2023-01-30 19:30:00 lot连接器

Qt 之connect 总结几种连接信号和槽函数的方法(包括signalmaper、lamda方式)

1. 最常规的用法:

信号可以由插件或定义,如:

      //新按钮     QPushButton * btn = new QPushButton(this);     btn->setText("设置");     //连接信号和槽 其中btnclicked()自定义槽函数     connect(btn, SIGNAL(clicked()), this, SLOT(btnclicked()));   

2. 带参数的信号和槽函数

当信号的参数与槽函数的参数数量不同时,只能是信号的参数槽函数的参数,而且前面相同数量的参数类型信号中多余的参数将被忽略。

//信号: void mySignal(int a, float b);   //槽: void MainWindow::mySlot(int b) { 
         //do something!! }   ///信号槽: connect(this, SIGNAL(mySignal(int, float)), this, SLOT(mySLot(int)));   ///发送信号: emit mySignal(5, 2.2); 

此外,当没有参数传输时,信号槽绑定也要求信号的参数大于或等于槽函数的参数。这种情况通常是一个带有参数的信号绑定一个无参数的槽函数。如下例所示。

//信号 void iSignal(int a, float b);
 
//槽
void MainWindow::iSlot() //int b
{ 
        
    QString qString = "I am lyc_daniel.";
    qDebug()<<qString;
}
 
//信号槽
connect(this, SIGNAL(iSignal(int, float)), this, SLOT(iSlot()));
 
 
//发送信号
emit iSignal(5, 0.3);

3. connect()函数基于函数指针的重载形式:

[static] QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, 
const QObject *receiver,
PointerToMemberFunction method,
Qt::ConnectionType type = Qt::AutoConnection)

这是QT5中加入的一种重载形式,指定信号和槽两个参数不再使用SIGNAL()SLOT()宏,并且槽函数不再必须是使用slots关键字声明的函数,可以是任意能和信号关联的成员函数。要使一个成员函数可以和信号关联,那么这个函数的参数数目不能超过信号的参数数目,但是并不要求该函数拥有的参数类型和信号中对应的参数类型完全一致,只需要可以进行隐式转换即可。如:

connect(dlg, &mydialog::dlgReturn, this, &Widget::showValue);

使用这种方式与前一种相比,还有一个好处就是可以在编译时进行检查,信号或槽的拼写错误、槽函数参数数目多于信号的参数数目等错误在编译时就能够被发现。所以在qt5中建议使用这种关联形式。

Note: 亲测 qt4 没有这种形式的链接

4. connect 应用c++11 lamda

另外依据上一种形式,还支持C++11 中的lambda表达式,可以在关联时直接编写信号发射后要执行的代码,例如程序中的关联可以写为:

connect(dlg, &MyDialog::dlgReturn, [ = ](int value){ 
        
ui->label->setText(tr("获取的值是:%1")arg(value));});

引用一个应用例子理解上述的几种方法

/* * 作者:韩大宝 * 时间:2022年4月1日 * 简述:该Demo仅仅用于测试和演示Qt5与Qt4的连接方式以及最新的槽函数支持lambda表达式 */
 
#include "widget.h"
#include "ui_widget.h"
#include 
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{ 
        
    ui->setupUi(this);
 
    // 传统Qt是连接方式
    // 传统Qt4连接方式为 信号发送者,信号,信号接受者,处理函数
    QObject::connect(ui->pushButton,SIGNAL(clicked(bool)),this,SLOT(qT4_slot()));
 
    //Qt5连接方式
    //其实这么写的方式和Qt4没有啥却别,只是在Qt4 中引用了信号槽,在简单的使用时没有问题,但是在庞大的工程中,信号和曹 仅仅是宏替换,在编译的时候没有安全监测
    //Qt5的新方法,在编译的时候就会有监测,如果我们手误操作失误,就会出现问题
    QObject::connect(ui->pushButton_2,&QPushButton::clicked,this,&Widget::qT5_slot);
 
 
 
    //Qt5 Lambda表达式
    //这里需要注意 Lambda表达式是C++ 11 的内容,所以,需要再Pro项目文件中加入 CONFIG += C++ 11
    QObject::connect(ui->pushButton_3,&QPushButton::clicked,[=](){ 
        qDebug()<<"lambda 表达式";});
 
 
 
}
 
Widget::~Widget()
{ 
        
    delete ui;
}
 
void Widget::qT4_slot()
{ 
        
    qDebug()<< "This is Qt 4 Connect method";
}
 
void Widget::qT5_slot()
{ 
        
    qDebug()<< "This is Qt 5 Connect method";
}

下面通过一个例子介绍lambda表达式的方法:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include 
#include 
 
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

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include 
 
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));
}

这里的槽就是一个Lambda匿名函数,完整形式如下:

[capture](parameters) mutable ->return-type{ 
        statement}
  1. [capture]捕捉列表。捕捉列表总是出现在Lambda函数的开始处。实际上,[]是Lambda引出符。编译器根据该引出符判断接下来的代码是否是Lambda函数。捕捉列表能够捕捉上下文中的变量以供Lambda函数使用;
  2. (parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号“()”一起省略;
  3. mutable:mutable修饰符。默认情况下,Lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空);
  4. ->return-type:返回类型。用追踪返回类型形式声明函数的返回类型。我们可以在不需要返回值的时候也可以连同符号”->”一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导;
  5. {statement}:函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量。

与普通函数最大的区别是,除了可以使用参数以外,Lambda函数还可以通过捕获列表访问一些上下文中的数据。具体地,捕捉列表描述了上下文中哪些数据可以被Lambda使用,以及使用方式(以值传递的方式或引用传递的方式)。语法上,在“[]”包括起来的是捕捉列表,捕捉列表由多个捕捉项组成,并以逗号分隔。捕捉列表有以下几种形式

  1. [var]表示值传递方式捕捉变量var;
  2. [=]表示值传递方式捕捉所有父作用域的变量(包括this);
  3. [&var]表示引用传递捕捉变量var;
  4. [&]表示引用传递方式捕捉所有父作用域的变量(包括this);
  5. [this]表示值传递方式捕捉当前的this指针。

也就是说我这里定义了一个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];
        }));

信号和槽的自动关联

信号和槽还有一种自动关联方式,例如on_pushButton_clicked()由字符串on、部件的objectName和信号名称3部分组成,中间用下划线隔开,之中形式命名的槽可以直接和信号关联,不再需要connect()函数。不过使用这种方式还要进行其他设置,

//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include 
 
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{ 
        
    QPushButton *button = new QPushButton(this); // 创建按钮
    button->setObjectName("myButton");           // 指定按钮的对象名
    ui->setupUi(this);                      // 要在定义了部件以后再调用这个函数
}
 
 
Widget::~Widget()
{ 
        
    delete ui;
}
 
void Widget::on_myButton_clicked()          // 使用自动关联
{ 
        
    close();
}
 
 
 
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
 
#include 
 
namespace Ui { 
        
class Widget;
}
 
class Widget : public QWidget
{ 
        
    Q_OBJECT
 
public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
 
private:
    Ui::Widget *ui;
 
private slots:
    void on_myButton_clicked();
 
};
 
#endif // WIDGET_H

因为setupUi()函数中调用了connectSlotByName()函数,所以要使用自动关联的部件的定义,都要放在setupUi()函数调用之前,而且还必须使用setObjectName()指定它们的objectName,只有这样才能正常使用自动关联。

其实也不一定要这样,如果你是用QT的designerUI 设计界面,designerUI链接到槽函数,槽函数名字你填好,UIC就可以自动的生成链接函数。(VS 使用)

6. 高级应用QSignalMapper

Qt 提供了QObject::sender()函数来返回发送该信号的对象的指针。但是如果有多个信号关联到了同一个槽上,而该槽中需要对每一个信号进行不同的处理,则使用这种方法就麻烦了,这是可以使用QSignalMapper 类。

QSignalMapper被叫做信号映射器,可以实现对多个相同部件的相同信号进行银树沟和,为其添加字符串或者数值参数,然后再发射出去。

    void setMapping(QObject *sender, int id);
    void setMapping(QObject *sender, const QString &text);
    void setMapping(QObject *sender, QWidget *widget);
    void setMapping(QObject *sender, QObject *object);
    void removeMappings(QObject *sender);
 
Q_SIGNALS:
    void mapped(int);
    void mapped(const QString &);
    void mapped(QWidget *);
    void mapped(QObject *);

四种捆绑方式,使用灵活。同一个sender在一个map中可以被捆绑多次;int 型的以及 QStringQWidget等 的Map捆绑互相独立,互不影响。一个 signalMapmapped信号最多可以连接到4个不同类型的槽函数,这四个信号槽相互独立。

举个简单的例子,现在我们有一个私有变量,存放了一个QString的数组,一共5项。我们希望动态地创建一个大小为5的QPushButton数组,实现的功能是点击第i个按键就让label显示第i个QString

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include 
#include 
#include 
 
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

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include 
#include 
 
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,让它们的点击信号能触发一个查询QSignalMapper的槽。而QSignalMapper中的内容为按键和整型变量的键值对。根据点击的按键可以查询到唯一一个映射项,并发射一个信号,其参数为按键对应的值。这个信号就可以触发自己定义的槽函数,实现参数的传递。

信号和槽的特色和优越性:

  • 信号和槽机制是类型安全的,相关联的信号和槽的参数必须匹配;

  • 信号和槽的松耦合的,信号发送者不知道也不需要知道接受者的信息;

  • 信号和槽可以使用任意类型数量的参数。

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

Qt5中的lambda表达式和使用lambda来写connect

QT 使用 lambda来写connect

在connect中使用lambda实现高效的信号/槽关联

如何利用 C++ 的 Lambda 表达式提升 Qt 代码

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章