QSignalMapper ist eine großartige Klasse zum Organisieren von Signalen und Slots für dynamisch erstellte Objekte. Beispielsweise dynamisch erstellte Schaltflächen oder Objekte in QStackedWidget. Dies galt insbesondere für ältere Softwareversionen, dh basierend auf Qt 4.8 , wo das System von Signalen und Slots auf der Verwendung von basierte Makros. Aber in der aktuellen Realität ist die neue Syntax für Zeiger viel bequemer und unterstützt auch Lambda-Funktionen, die die Verwendung von QSignalMapper vollständig beseitigen können, was in neuen Projekten, die die verwenden, wie ein monströser Atavismus aussehen wird neuesten Versionen des Qt-Frameworks und der C++ -Sprachstandards.
Und wenn Sie auch die Überladung von map() und mapped() berücksichtigen, dann macht das den Code mit QSignalMapper noch schlimmer, wenn Sie Signal- und Slot-Verbindungen mit Zeigern verwenden, da Sie als Signale und Slots casten müssen, aber dazu später mehr.
Betrachten wir daher ein kleines Projekt, das auf einem Beispiel aus der offiziellen Qt-Dokumentation basiert. Das Beispiel wird nämlich das folgende sein. Wir haben QLabel, QPushButton und Vertical Layout . Durch Drücken der Schaltfläche werden weitere dynamische Schaltflächen zum vertikalen Layout hinzugefügt, durch Klicken auf diese wird der Text mit der Schaltflächennummer im QLabel in folgender Form angezeigt: "Schaltfläche 2". Die folgende Abbildung zeigt ein Beispiel dieser Anwendung, die sich im Aussehen nicht unterscheiden wird, während es mehrere Implementierungen des Programmcodes geben wird.
Option 1 - QSignalMapper und Makrosyntax
Lassen Sie uns zunächst die Option analysieren, die in der offiziellen Dokumentation als Beispiel angeboten wird. Wenn Signale und Slots über Makros verbunden werden, gibt es eine Variante, die mit Qt 4.8 kompatibel ist.
Das Erscheinungsbild der Anwendung wurde in einem Grafikdesigner erstellt, also wundern Sie sich nicht über die Verwendung eines UI-Objekts.
Hauptfenster.h
Damit die Anwendung funktioniert, benötigen wir also QSignalMapper und einen Slot, in dem der Klick auf eine dynamische Schaltfläche verarbeitet wird. Die Nuance ist, dass der Text von dieser Schaltfläche in QLabel gesetzt wird, also verwenden wir das Signal QSignalMapper::mapped(const QString &) , das vom Slot MainWindow empfangen wird: :clicked(const QString & ), in diesem Slot wird das Widget in ein QPushButton, Objekt umgewandelt und wir nehmen den Text daraus. Der Text wird beim Erstellen dieser Schaltfläche voreingestellt. Zur Nummerierung wird der Zähler der erstellten Buttons verwendet (Variable int counter).
#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(); // Слот, в котором будут создаваться кнопки void clicked(const QString &str); // Слот, в котором будут обрабатываться клики кнопок private: Ui::MainWindow *ui; int counter; QSignalMapper *mapper; // маппер, который будет обрабатываться сигналы от кнопок }; #endif // MAINWINDOW_H
Hauptfenster.cpp
Und mal sehen, wie das alles im Code aussieht.
#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); // Инициализируем маппер } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_pushButton_clicked() { QPushButton *button = new QPushButton(this); // Создаём кнопку button->setText("Button " + QString::number(counter)); // Устанавливаем в неё текст counter++; // Инкрементируем счётчик ui->verticalLayout->addWidget(button); // Помещаем кнопку в vertical layout // подключаем сигнал клика кнопки к мапперу connect(button, SIGNAL(clicked(bool)), mapper, SLOT(map())); mapper->setMapping(button, button->text()); // по клику кнопки будем передавать текст из этой кнопки // передаём текст с кнопки из маппера в слот, где будет установлен текст connect(mapper, SIGNAL(mapped(QString)), this, SLOT(clicked(QString))); } void MainWindow::clicked(const QString &str) { // конечно, у QLabel метод setText уже является слотом и // можно было бы сразу его подключить к сигналу, // но для обзора всех сложностей будет оптимальнее показать это отдельным слотом ui->label->setText(str); }
Option 2 - QSignalMapper und neue Syntax
In der ersten Version ist der Code mit dem Code auf Qt 4.8 kompatibel und im Allgemeinen gut lesbar, aber was ist, wenn wir das Projekt auf Version 4.8 nicht unterstützen werden? Dann ist das erste, was zu tun ist, diesen Code mit der Zeigersyntax neu zu schreiben und kleine Einschlüsse von Lambda-Funktionen zu machen. Und dann werden Sie sehen, was ich meinte, als ich sagte, dass QSignalMapper wie ein monströser Rückfall aussehen würde.
Hauptfenster.h
Jetzt hat die Header-Datei keinen Slot mehr, um den Klick auf eine dynamische Schaltfläche zu verarbeiten, da hier bereits Lambdas verwendet werden.
#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(); // Слот, в котором будут создаваться кнопки private: Ui::MainWindow *ui; int counter; QSignalMapper *mapper; // маппер, который будет обрабатываться сигналы от кнопок }; #endif // MAINWINDOW_H
Hauptfenster.cpp
Mal sehen, wie der Code jetzt aussieht. Es scheint, dass wir einen eindeutigen WIN erhalten:
- Einen Slot entfernt, dank der Lambda-Funktion;
- Wir haben die Möglichkeit, Fehler bereits in der Kompilierungsphase zu verfolgen und nicht zur Laufzeit, was eine Sünde von Signal- und Slot-Makros ist;
- Den Code auf den Qt5-Standard gebracht.
Der Code sieht jedoch ziemlich gruselig aus, weil er static_cast für QSignalMapper-Signale und -Slots verwendet. Dies liegt daran, dass sowohl der map() -Slot als auch das mapped() -Signal überladen sind und der Compiler benötigt ihre Unterschrift angeben. Und die folgenden Konstruktionen verleihen dem Code unten keine Kürze und Schönheit. Diese Situation kann jedoch korrigiert werden - ziehen Sie die dritte Option in Betracht.
#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); // Инициализируем маппер } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_pushButton_clicked() { QPushButton *button = new QPushButton(this); // Создаём кнопку button->setText("Button " + QString::number(counter)); // Устанавливаем в неё текст counter++; // Инкрементируем счётчик ui->verticalLayout->addWidget(button); // Помещаем кнопку в vertical layout // подключаем сигнал клика кнопки к мапперу connect(button, &QPushButton::clicked, mapper, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map)); mapper->setMapping(button, button->text()); // по клику кнопки будем передавать текст из этой кнопки // передаём текст с кнопки из маппера в слот, где будет установлен текст connect(mapper, static_cast<void(QSignalMapper::*)(const QString &)>(&QSignalMapper::mapped), [=](const QString str){ ui->label->setText(str); }); }
Option 3 - QSignalMapper loswerden
Lassen Sie uns jetzt QSignalMapper loswerden. Denn wenn Sie alle Möglichkeiten von Lambda-Funktionen nutzen, dann wird QSignalMapper überhaupt nicht benötigt, um solche Aufgaben wie in diesem Beispiel zu implementieren.
Hauptfenster.h
Wie Sie sehen, wurde der Code in der Header-Datei etwas gekürzt.
#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(); // Слот, в котором будут создаваться кнопки private: Ui::MainWindow *ui; int counter; }; #endif // MAINWINDOW_H
Hauptfenster.cpp
Und wie Sie sehen können, reduziert die Verwendung der Lambda-Funktion den Code erheblich und eliminiert in dieser Situation auch die Verwendung der Klasse QSignalMapper insgesamt. Es ist durchaus möglich, dass diese Klasse aufgrund der Entwicklung der C++-Sprache selbst ganz ausstirbt.
#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); // Создаём кнопку button->setText("Button " + QString::number(counter)); // Устанавливаем в неё текст counter++; // Инкрементируем счётчик ui->verticalLayout->addWidget(button); // Помещаем кнопку в vertical layout // В лямбде захватываем из внешней области переменных указатель на MainWindow, // что позволит использовать переменные ui, а также саму кнопку button connect(button, &QPushButton::clicked, [this, button](){ ui->label->setText(button->text()); }); }
Zusammenfassung...
Die Verwendung der neuen Syntax von Signalen und Slots ermöglicht es Ihnen, die leistungsstarken Funktionen der Sprache C ++ zu verbinden und bei Anwendung auf Qt die Verwendung einiger Klassen vollständig zu beseitigen, deren Verwendung in veralteten Versionen von nicht zu leugnen war Qt. Und auch die Verwendung von Lambdas kann den Programmcode deutlich reduzieren und vereinfachen.
Добрый день.
Ругается такНа
Не можете подсказать почему?
currentIndexChanged является сигналом с перегрузкой. Поэтому его нужно подключать через static_cast с указанием сигнатуры сигнала.
Вот такая петрушка получается:
Спасибо! Знал же, что сигнал с перегрузкой. Думал : "А как компилятор поймет какую именно вызывать?",- но не знал такой прием как указать сигнатуру через static_cast :)
Этот приём в официальной документации на QComboBox указан ;-)
Лямбда удобная штука. Только вчера научился, мне нравится :)
Можешь сказать, когда лучше использовать слот и когда лучше лямбду.
По-моему, связывание сигнала с лямбдой нарушает принцип сигнал слотов.
А писать слот - это значит, что слот может быть вызван из вне. А это не всегда нужно. Я искал про private slots, думал, что private slot может быть связан только с сигналом своего класса, но я ошибался.
По сути лямбда ограничивает область видимости , но нарушает принцип qt сигнал-слот. Это так?
Был бы рад услышать твое мнение по этой теме :)
Я бы не сказал, что лямбда нарушает принципы сигналов и слотов. Это таже самая функция, просто анонимная.
На мой взгляд, принцип сигналов и слотов - это связать некоторые части кода, чтобы при определённом сигнале выполнился необходимый код. А всё остальное это уже домыслы, поскольку лямда подчиняется всем остальным особенностям работы сигналов и слотов, как последовательность подключений и т.д.
Плюс лямбды в том, что она может захватывать необходимые объекты, которые являются локальными в определённом методе. Если же использовать полноценный слот, то будет происходит разрастание кода, поскольку все эти локальные объекты нужно будет объявлять в заголовочном файле класса, а это по сути не нужно. Например, как в этом примере с динамическими кнопками.
В примере нужно получить сигнал от кнопки, и что-то сделать с той же самой кнопкой. Если использовать слот, то придётся держать какой-то вектор объектов в заголовочном файле, либо как сделано здесь, использовать QSignalMapper, а это опять же разрастание кода и повышение избыточности кода. А так лямбда захватывает кнопку и спокойно выполняет необходимый код. Когда кнопка будет уничтожена, а память освобождена, то лямбда автоматически будет отключена от сигнала этой кнопки и так же будет уничтожена. Так что проблем здесь никаких не возникнет.
Конечно, нужен несколько больший профессиональный уровень, чтобы понимать лямбды и работать с ними. Но профит здесь очевиден. Кода меньше. Работа с кодом становится гибче. Не происходит разрастания заголовочного файла методами, которые используются в одном единственном месте и больше нигде.
Плюс некоторые возможности шаблонизации, которые присущи лямбдам с аргументами auto. Плюсов слишком много, чтобы игнорировать использование лямбд в сигналах и слотах.
Как вам такое
Может и кривовато но чёрт побери работает и класс от ненужной больше ни где фигни не разбухает.
Вы используете стандартную практику замыканий, когда нет никакой необходимости объявлять функции в классе, поскольку они используются в одном единственном месте класса, а объявление всех эти лямбд вело бы, как вы подметили, к разбуханию класса.
Мы у себя на проекте такими же приёмами пользуемся, чтобы не загромождать код подобным мусором.