Qt/C++ - Lesson 055. QSignalMapper VS lambda functions

Qt, C++, lambda, QSignalMapper, лямбда функция

QSignalMapper is a wonderful class to organize the work of the signals and slots dynamically created objects. For example, the dynamically created buttons or objects in QStackedWidget. This was particularly true in older versions of the software, that is, and relied on Qt 4.8, which signals and slots system was based on the use of macros. But in the current realities of the new syntax in the indexes is much more convenient, and also supports a lambda function that can afford and does get rid of the application QSignalMapper, which will look like a monstrous throwback to new projects that use the latest version of the framework Qt and C++ language standards.

And when you consider more and congestion map() and mapped(), then it does with QSignalMapper code even more terrible, if used to connect signals and slots with the use of pointers, since it is necessary to cast the signals and slots, but more on that later.

Therefore, let us consider a small project that will be based on the example of the official documentation of Qt. Namely, an example will next. We have QLabel, QPushButton and Vertical Layout . By pressing a button in the Vertical Layout will be added to other dynamic buttons, by clicking on which QLabel will display the text from the button number in the following form: "Button 2". In the following figure shows an example of the application whose appearance will not differ, while the software implementations of the code will be a few.

Variant 1 - QSignalMapper and macro syntax

To begin Let us examine the option, which is offered as an example in official documentation. When the signals and slots are connected via macros, that is an option that is compatible with Qt 4.8.

The appearance of the application created in the Graphics Designer, so do not be surprised to use ui object.

mainwindow.h

So that the application to work, we need QSignalMapper and slot in which pressing the dynamic key is processed. Nuance that will be installed in QLabel text of this same button, so use the signal QSignalMapper::mapped(const QString &) , which will be made a slot MainWindow::clicked(const QString &) , the slot widget will be transformed into an object QPushButton, and we zaberёm of his text. The text will be pre-installed with the creation of this button. For numbering counter created buttons (variable int counter) will be used.

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSignalMapper>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void on_pushButton_clicked();           // The slot in which the buttons will be created
    void clicked(const QString &str);       // The slot in which the buttons will be processed clicks

private:
    Ui::MainWindow *ui;
    int counter;
    QSignalMapper *mapper;                  // The mapper, which will be processed the signals from buttons
};

#endif // MAINWINDOW_H

mainwindow.cpp

And look at how all this looks in code.

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    counter(0)
{
    ui->setupUi(this);

    mapper = new QSignalMapper(this);   // Initialize mapper
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    QPushButton *button = new QPushButton(this);            // create a button
    button->setText("Button " + QString::number(counter));  // Set in it the text
    counter++;                                              // increment counter
    ui->verticalLayout->addWidget(button);                  // Put the button in the vertical layout

    // connect click signal of the button to the signal mapper
    connect(button, SIGNAL(clicked(bool)), mapper, SLOT(map()));
    mapper->setMapping(button, button->text()); // by clicking a button will translate the text of the button,
    // send text button from the mapper in the slot where the text will be set
    connect(mapper, SIGNAL(mapped(QString)), this, SLOT(clicked(QString)));
}

void MainWindow::clicked(const QString &str)
{
    // of course, QLabel setText method is already a slot and
    // Could just connect it to the signal,
    // But for an overview of all the difficulties will optimally show this individual slot
    ui->label->setText(str);
}

Variant 2 - QSignalMapper and new syntax

In a first embodiment, the code is compatible with the code on the Qt 4.8 and generally quite readable, but what if we are not going to support the project to version 4.8? Then the first thing to do is to rewrite the code using the syntax on signs and making small patches of lambda functions. And then you'll see what I mean by saying that QSignalMapper will look like a monstrous throwback.

mainwindow.h

Now in the header file has no slot for processing dynamic button pressing, as is already the lambda will be used here.

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSignalMapper>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void on_pushButton_clicked();           // The slot in which the buttons will be created

private:
    Ui::MainWindow *ui;
    int counter;
    QSignalMapper *mapper;                  // The mapper, which will be processed the signals from buttons
};

#endif // MAINWINDOW_H

mainwindow.cpp

Let's see how the code looks now. It would seem simple to get WIN:

  1. We removed one slot through a lambda function;
  2. We got to track errors already at compile time, rather than runtime than the sin signal macros and slots;
  3. Led code to Qt5 standard.

But this code looks quite scary, because it is used for static_cast QSignalMapper signals and slots. This is due to the fact that as a slot map() , and mapped() signal is overloaded, and the compiler need to specify their signature. And below these design brevity and beauty the code does not give. But this situation can be corrected - consider the third option.

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    counter(0)
{
    ui->setupUi(this);

    mapper = new QSignalMapper(this);   // Initialize mapper
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
   QPushButton *button = new QPushButton(this);             // create a button
    button->setText("Button " + QString::number(counter));  // Set in it the text
    counter++;                                              // increment counter
    ui->verticalLayout->addWidget(button);                  // Put the button in the vertical layout

    // connect the button click to connect to the signal mapper
    connect(button, &QPushButton::clicked,
            mapper, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map));
    mapper->setMapping(button, button->text()); // by clicking a button will translate the text of the button,
    // send text button from the mapper in the slot where the text will be set
    connect(mapper, static_cast<void(QSignalMapper::*)(const QString &)>(&QSignalMapper::mapped),
            [=](const QString str){
        ui->label->setText(str);
    });
}

Variant 3 - delete QSignalMapper

Now get rid of QSignalMapper. After all, if you use all the features of the lambda functions to the realization of these objectives, as in this example, QSignalMapper not needed.

mainwindow.h

The code in the header file declined slightly, as you can see.

#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 slots:
    void on_pushButton_clicked();           // The slot in which the keys will be created

private:
    Ui::MainWindow *ui;
    int counter;
};

#endif // MAINWINDOW_H

mainwindow.cpp

And as you can see, the use of lambda functions considerably reduces the code and saves in the given situation the use QSignalMapper class at all. It is possible that this class due to the development of the C ++ language wither away altogether.

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    counter(0)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    QPushButton *button = new QPushButton(this);            // create a button
    button->setText("Button " + QString::number(counter));  // Set in it the text
    counter++;                                              // increment counter
    ui->verticalLayout->addWidget(button);                  // Put the button in the vertical layout

    // In lambda we grab the external pointer of variables from MainWindow,
    // Allow use variables ui, as well as the very key button
    connect(button, &QPushButton::clicked, [this, button](){
        ui->label->setText(button->text());
    });
}

Conclusion ...

Application of the new syntax signals and slots, allowing you to connect the powerful features of C ++ and Qt application to completely get rid of the use of certain classes, the use of which was doubtless in older versions of Qt. As well as the use of lambdas can significantly reduce and simplify the code.

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.
Support the author Donate
AK

Добрый день.
На

connect(ui->comboBoxProfile, &QComboBox::currentIndexChanged, [=](){ this->close();});
Ругается так
C:\Projects\Qt\BMR-1401LM-U\dialogfeatures.cpp:31: ошибка: no matching function for call to 'DialogFeatures::connect(QComboBox*&, <unresolved overloaded function type>, DialogFeatures::DialogFeatures(int, int, int, int, int, int, QWidget*)::<lambda()>)'
connect(ui->comboBoxProfile, &QComboBox::currentIndexChanged, [=](){ this->close();});
^

Не можете подсказать почему?

currentIndexChanged является сигналом с перегрузкой. Поэтому его нужно подключать через static_cast с указанием сигнатуры сигнала.

Вот такая петрушка получается:

connect(comboBox, static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::currentIndexChanged), 
[=](const QString &text){ }); 
AK

Спасибо! Знал же, что сигнал с перегрузкой. Думал : "А как компилятор поймет какую именно вызывать?",- но не знал такой прием как указать сигнатуру через static_cast :)

Этот приём в официальной документации на QComboBox указан ;-)

AK

Лямбда удобная штука. Только вчера научился, мне нравится :)

Можешь сказать, когда лучше использовать слот и когда лучше лямбду.
По-моему, связывание сигнала с лямбдой нарушает принцип сигнал слотов.

А писать слот - это значит, что слот может быть вызван из вне. А это не всегда нужно. Я искал про private slots, думал, что private slot может быть связан только с сигналом своего класса, но я ошибался.

По сути лямбда ограничивает область видимости , но нарушает принцип qt сигнал-слот. Это так?

Был бы рад услышать твое мнение по этой теме :)

Я бы не сказал, что лямбда нарушает принципы сигналов и слотов. Это таже самая функция, просто анонимная.
На мой взгляд, принцип сигналов и слотов - это связать некоторые части кода, чтобы при определённом сигнале выполнился необходимый код. А всё остальное это уже домыслы, поскольку лямда подчиняется всем остальным особенностям работы сигналов и слотов, как последовательность подключений и т.д.

Плюс лямбды в том, что она может захватывать необходимые объекты, которые являются локальными в определённом методе. Если же использовать полноценный слот, то будет происходит разрастание кода, поскольку все эти локальные объекты нужно будет объявлять в заголовочном файле класса, а это по сути не нужно. Например, как в этом примере с динамическими кнопками.

В примере нужно получить сигнал от кнопки, и что-то сделать с той же самой кнопкой. Если использовать слот, то придётся держать какой-то вектор объектов в заголовочном файле, либо как сделано здесь, использовать QSignalMapper, а это опять же разрастание кода и повышение избыточности кода. А так лямбда захватывает кнопку и спокойно выполняет необходимый код. Когда кнопка будет уничтожена, а память освобождена, то лямбда автоматически будет отключена от сигнала этой кнопки и так же будет уничтожена. Так что проблем здесь никаких не возникнет.

Конечно, нужен несколько больший профессиональный уровень, чтобы понимать лямбды и работать с ними. Но профит здесь очевиден. Кода меньше. Работа с кодом становится гибче. Не происходит разрастания заголовочного файла методами, которые используются в одном единственном месте и больше нигде.

Плюс некоторые возможности шаблонизации, которые присущи лямбдам с аргументами auto. Плюсов слишком много, чтобы игнорировать использование лямбд в сигналах и слотах.

D
  • #
  • Sept. 21, 2017, 4:29 p.m.

Как вам такое


 enum {
        PROFILE_TOOLPATH_FORM,
        POCKET_TOOLPATH_FORM,
        DRILLING_TOOLPATH_FORM
    };
    QToolBar* toolpathToolBar = addToolBar(tr("Toolpath"));
    toolpathToolBar->setIconSize(QSize(24, 24));
    toolpathToolBar->setObjectName(QStringLiteral("toolpathToolBar"));

    static QVector<QAction*> ToolpathActionList;
    auto createDockWidget = [=](QWidget* dwContents, int type) {
        QDockWidget* dwCreatePath = nullptr;
        foreach (QDockWidget* dw, findChildren<QDockWidget*>()) {
            if (dw->objectName() == "dwCreatePath") {
                dwCreatePath = dw;
                break;
            }
        }
        foreach (QAction* action, ToolpathActionList) {
            action->setChecked(false);
        }
        ToolpathActionList[type]->setChecked(true);
        if (dwCreatePath == nullptr) {
            dwCreatePath = new QDockWidget(this);
            dwCreatePath->setObjectName(QStringLiteral("dwCreatePath"));
            dwCreatePath->setFloating(false);
            dwCreatePath->setWindowTitle(tr("Create Toolpath"));
            dwCreatePath->connect(dwCreatePath, &QDockWidget::visibilityChanged,
                [=](bool fl) {
                    if (!fl) {
                        dwCreatePath->deleteLater();
                        foreach (QAction* action, ToolpathActionList) {
                            action->setChecked(false);
                        };
                    }
                });
            addDockWidget(Qt::RightDockWidgetArea, dwCreatePath);
        }
        else {
            dwCreatePath->widget()->deleteLater();
        }
        dwContents->setObjectName(QStringLiteral("dwContents"));
        dwCreatePath->setWidget(dwContents);
        dwCreatePath->show();
    };

    action = toolpathToolBar->addAction(QIcon::fromTheme("object-to-path"), tr("Profile"), [=]() { createDockWidget(new ProfileToolpathForm(), PROFILE_TOOLPATH_FORM); });
    ToolpathActionList.append(action);
    //    action->setShortcut(QKeySequence::FullScreen);
    action = toolpathToolBar->addAction(QIcon::fromTheme("stroke-to-path"), tr("Pocket"), [=]() { createDockWidget(new PocketToolpathForm(), POCKET_TOOLPATH_FORM); });
    ToolpathActionList.append(action);
    //    action->setShortcuts(tr("Ctrl+0"));
    action = toolpathToolBar->addAction(QIcon::fromTheme("roll"), tr("Drilling"), [=]() { createDockWidget(new DrillingToolpathForm(), DRILLING_TOOLPATH_FORM); });
    ToolpathActionList.append(action);
    action = toolpathToolBar->addAction(QIcon::fromTheme("view-form"), tr("Tool Base"), [=]() { ToolDatabase tdb(this); tdb.exec(); });
    //    action->setShortcut(QKeySequence::ZoomIn);
    foreach (QAction* action, ToolpathActionList) {
        action->setCheckable(true);
    }
Или

QList<QString> GerberParser::Format(QString data)
{
    QList<QString> gerberLines;

    enum SATE {
        PARAM,
        MACRO,
        DATA,
    };

    SATE state = DATA;
    QString lastLine;

    auto gerberLinesAppend = [&gerberLines, &lastLine](SATE& state, const QString& val) -> void {
        switch (state) {
        case MACRO:
            lastLine.push_back(val);
            if (lastLine.endsWith('%')) {
                gerberLines << lastLine;
                state = DATA;
            }
            break;
        case PARAM:
            lastLine.push_back(val);
            if (lastLine.endsWith('%')) {
                foreach (QString tmpline, lastLine.remove('%').split('*')) {
                    if (!tmpline.isEmpty()) {
                        gerberLines << ('%' + tmpline + "*%");
                    }
                }
                state = DATA;
            }
            break;
        case DATA:
            break;
        }
    };

    auto lastLineClose = [&gerberLines](SATE state, QString& val) -> void {
        switch (state) {
        case MACRO:
            if (!val.endsWith('%'))
                val.push_back('%');
            if (!val.endsWith("*%"))
                val.insert(val.length() - 2, '*');
            gerberLines << val;
            break;
        case PARAM:
            foreach (QString tmpline, val.remove('%').split('*')) {
                if (!tmpline.isEmpty()) {
                    gerberLines << ('%' + tmpline + "*%");
                }
            }
            break;
        case DATA:
            break;
        }
        val.clear();
    };

    auto dataClose = [&gerberLines](const QString& val) -> void {
        if (val.count('*') > 1) {
            foreach (QString tmpline, val.split('*')) {
                if (!tmpline.isEmpty()) {
                    gerberLines << (tmpline + '*');
                }
            }
        }
        else {
            gerberLines << val;
        }
    };

    foreach (QString line, data.replace('\r', '\n').replace("\n\n", "\n").replace('\t', ' ').split('\n')) {
        line = line.trimmed();
        if (line.isEmpty()) {
            continue;
        }
        if (line.startsWith('%') && line.endsWith('%') && line.size() > 1) {
            lastLineClose(state, lastLine);
            if (line.startsWith("%AM")) {
                lastLineClose(MACRO, line);
            }
            else {
                lastLineClose(PARAM, line);
            }
            state = DATA;
            continue;
        }
        else if (line.startsWith("%AM")) {
            lastLineClose(state, lastLine);
            state = MACRO;
            lastLine = line;
            continue;
        }
        else if (line.startsWith('%')) {
            lastLineClose(state, lastLine);
            state = PARAM;
            lastLine = line;
            continue;
        }
        else if (line.endsWith('*') && line.length() > 1) {
            switch (state) {
            case MACRO:
            case PARAM:
                gerberLinesAppend(state, line);
                continue;
            case DATA:
                dataClose(line);
                continue;
            }
        }
        else {
            switch (state) {
            case MACRO:
            case PARAM:
                gerberLinesAppend(state, line);
                continue;
            case DATA:
                qDebug() << "Хрен его знает:" << line;
                continue;
            }
        }
    }
    return gerberLines;
}
D

Может и кривовато но чёрт побери работает и класс от ненужной больше ни где фигни не разбухает.

Вы используете стандартную практику замыканий, когда нет никакой необходимости объявлять функции в классе, поскольку они используются в одном единственном месте класса, а объявление всех эти лямбд вело бы, как вы подметили, к разбуханию класса.
Мы у себя на проекте такими же приёмами пользуемся, чтобы не загромождать код подобным мусором.


Есть кое-какие замечания и мысли
  • Раз уж вы используете минимум стандарт C++11 , то перепишите foreach , на range-based for цикл. foreach - это Qt-шный макрос, который является deprecated , поскольку в стандарте C++11 наконец-то ввели нормальные for циклы для контейнеров и foreach теперь не нужен.
  • Не то, чтобы замечание, но как-то непривычно наблюдать enum в теле метода, разве где-то в другом месте вы не используете подобный enum ? Возможно стоит проанализировать и немного перепроектировать класс?
  • В первом примере возможно, можно обойтись без статического пула Action`ов, если они больше нигде не используются. Вообще по возможности лучше работать без подобных статических пулов объектов настолько, насколько это возможно. Но естественно всё зависит от конкретного User Case и условий разработки.
  • Также название методов и переменных с большой буквы... Полагаю причина в том, что изначально разработка проекта велась под Visual Studio и сложился подобный код стайл в команде, но Qt использует несколько иные правила кодстайла , который немного удобнее, поскольку не приходится гадать порой, что это такое? - переменная, объявление класса, метод или конструктор в коде. Особенно, если косяк в подсветке синтаксиса кода имеется в самой IDE. Плюс при использовании Qt получаем разнобой в кодстайле от Qt и вашего проекта. Поразмышляйте в команде на досуге об этом.

Comments

Only authorized users can post comments.
Please, Log in or Sign up
Donate

Hello, Dear Users of EVILEG!!!

If the site helped you, then support the development of the site financially, please.

You can do it by following ways:

Thank you, Evgenii Legotckoi

T
Dec. 11, 2019, 4:56 p.m.
Toma

C++ - Тест 003. Условия и циклы

  • Result:50points,
  • Rating points-4
AT
Dec. 10, 2019, 8:06 a.m.
Anastasija Troschenkova

C++ - Test 001. The first program and data types

  • Result:60points,
  • Rating points-1
AT
Dec. 10, 2019, 8:02 a.m.
Anastasija Troschenkova

Qt - Test 001. Signals and slots

  • Result:73points,
  • Rating points1
Last comments
Dec. 9, 2019, 3:41 a.m.
Evgenij Legotskoj

Эта ошибка invalid use of incomplete type ‘class Ui::AnotherWindow’ обычно говорит о том, что не найдено определение класса или структуры. Типичная проблема - не подключён заголовочны…
NB
Dec. 9, 2019, 3:36 a.m.
Nikolaj Batmanov

Ну, не настолько со мной всё полхо...))) Вроде бы. Я ж кнопки отрисовываю.
Dec. 9, 2019, 3:14 a.m.
Evgenij Legotskoj

Добрый день. У вас ui файлов по ходу нет. UI файлы используются для вёрстки в графическом дизайнере.
NB
Dec. 9, 2019, 3:05 a.m.
Nikolaj Batmanov

Здравствуйте! Полностью скопировал ваш пример к себе, чтобы разобраться. А он не хочет запускаться, дает ошибку: invalid use of incomplete type ‘class Ui::AnotherWindow’ ui(new Ui…
Dec. 8, 2019, 7:23 a.m.
Evgenij Legotskoj

У меня здесь есть одна старая статья с примером векторного редактора. Там есть ответы на ваши вопросы. Поизучайте Qt/C++ - Урок 072. Пример векторного редактора на Qt QGraphicsItem, QG…
Now discuss on the forum
Dec. 12, 2019, 11:49 a.m.
qml_puthon_user

Да, с console.log() я разобрался, счётчик ничего не показывает
Dec. 12, 2019, 9:27 a.m.
Evgenij Legotskoj

Добрый день. Вообще Qt Designer это рисовалка формочек. Вы хотите от него больше, чем в нём есть. Максимум, что вы можете сделать, это накидать всю вёрстку в дизайнере, а потом привязыватьс…
MU
Dec. 11, 2019, 8:27 a.m.
Maciej Urmański

Thank you! Now works, and this is solution. num_embed = Embed.objects.filter(added_by=recipe.added_by).count()
Dec. 11, 2019, 8:12 a.m.
Mihailll

Так работает. Взял этот пример https://api-2d3d-cad.com/face_recognition_with_opencv/ void MainWindow::on_pushButton_4_clicked() //фото определение лица{ // Load Face cascade (.xml…
TD
Dec. 10, 2019, 4:14 a.m.
Timur Dosov

Спасибо, работает. А ещё вопрос: как загрузить страницу с динамической подгрузкой контента по скроллингу? Например - [https://ntvplus.ru/tv/]. Пока делаю через костыль - QApplication::s…
EVILEG
About
Services
© EVILEG 2015-2019
Recommend hosting TIMEWEB