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:
- We removed one slot through a lambda function;
- We got to track errors already at compile time, rather than runtime than the sin signal macros and slots;
- 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.
Добрый день.
Ругается такНа
Не можете подсказать почему?
currentIndexChanged является сигналом с перегрузкой. Поэтому его нужно подключать через static_cast с указанием сигнатуры сигнала.
Вот такая петрушка получается:
Спасибо! Знал же, что сигнал с перегрузкой. Думал : "А как компилятор поймет какую именно вызывать?",- но не знал такой прием как указать сигнатуру через static_cast :)
Этот приём в официальной документации на QComboBox указан ;-)
Лямбда удобная штука. Только вчера научился, мне нравится :)
Можешь сказать, когда лучше использовать слот и когда лучше лямбду.
По-моему, связывание сигнала с лямбдой нарушает принцип сигнал слотов.
А писать слот - это значит, что слот может быть вызван из вне. А это не всегда нужно. Я искал про private slots, думал, что private slot может быть связан только с сигналом своего класса, но я ошибался.
По сути лямбда ограничивает область видимости , но нарушает принцип qt сигнал-слот. Это так?
Был бы рад услышать твое мнение по этой теме :)
Я бы не сказал, что лямбда нарушает принципы сигналов и слотов. Это таже самая функция, просто анонимная.
На мой взгляд, принцип сигналов и слотов - это связать некоторые части кода, чтобы при определённом сигнале выполнился необходимый код. А всё остальное это уже домыслы, поскольку лямда подчиняется всем остальным особенностям работы сигналов и слотов, как последовательность подключений и т.д.
Плюс лямбды в том, что она может захватывать необходимые объекты, которые являются локальными в определённом методе. Если же использовать полноценный слот, то будет происходит разрастание кода, поскольку все эти локальные объекты нужно будет объявлять в заголовочном файле класса, а это по сути не нужно. Например, как в этом примере с динамическими кнопками.
В примере нужно получить сигнал от кнопки, и что-то сделать с той же самой кнопкой. Если использовать слот, то придётся держать какой-то вектор объектов в заголовочном файле, либо как сделано здесь, использовать QSignalMapper, а это опять же разрастание кода и повышение избыточности кода. А так лямбда захватывает кнопку и спокойно выполняет необходимый код. Когда кнопка будет уничтожена, а память освобождена, то лямбда автоматически будет отключена от сигнала этой кнопки и так же будет уничтожена. Так что проблем здесь никаких не возникнет.
Конечно, нужен несколько больший профессиональный уровень, чтобы понимать лямбды и работать с ними. Но профит здесь очевиден. Кода меньше. Работа с кодом становится гибче. Не происходит разрастания заголовочного файла методами, которые используются в одном единственном месте и больше нигде.
Плюс некоторые возможности шаблонизации, которые присущи лямбдам с аргументами auto. Плюсов слишком много, чтобы игнорировать использование лямбд в сигналах и слотах.
Как вам такое
Может и кривовато но чёрт побери работает и класс от ненужной больше ни где фигни не разбухает.
Вы используете стандартную практику замыканий, когда нет никакой необходимости объявлять функции в классе, поскольку они используются в одном единственном месте класса, а объявление всех эти лямбд вело бы, как вы подметили, к разбуханию класса.
Мы у себя на проекте такими же приёмами пользуемся, чтобы не загромождать код подобным мусором.