QSignalMapper — динамикалық түрде жасалған нысандар үшін сигналдарды және ұяшықтарды ұйымдастыруға арналған тамаша сынып. Мысалы, QStackedWidget ішіндегі динамикалық түрде жасалған түймелер немесе нысандар. Бұл әсіресе бағдарламалық жасақтаманың ескі нұсқаларында, яғни Qt 4.8 негізіндегі сигналдар мен ұяшықтар жүйесі пайдалануға негізделген болатын. макростар. Бірақ қазіргі шындықта көрсеткіштердегі жаңа синтаксис әлдеқайда ыңғайлы, сонымен қатар QSignalMapper пайдаланудан толығымен арылуға болатын lambda функцияларын қолдайды, ол жаңа жобаларда құбыжық атавизмге ұқсайды. Qt Framework және C++ тіл стандарттарының соңғы нұсқалары.
Ал егер сіз map() және mapped() шамадан тыс жүктелуін ескерсеңіз, көрсеткішті пайдаланып сигнал және ұяшық қосылымдарын пайдалансаңыз, бұл QSignalMapper бар кодты одан да қорқынышты етеді, өйткені сигналдар мен слоттар ретінде шығару керек, бірақ бұл туралы кейінірек.
Сондықтан, ресми Qt құжаттамасындағы мысалға негізделетін шағын жобаны қарастырайық. Атап айтқанда, мысал келесідей болады. Бізде QLabel, QPushButton және Тік орналасу бар. Түймені басу арқылы Тік орналасуға басқа динамикалық түймелер қосылады, оны басу арқылы түйме нөмірі бар мәтін QLabel-де келесі пішінде көрсетіледі: «2-түйме». Келесі суретте бұл қолданбаның мысалы көрсетілген, оның сыртқы түрі ерекшеленбейді, ал бағдарлама кодының бірнеше іске асырылуы болады.
1-нұсқа - QSignalMapper және макросинтаксис
Алдымен ресми құжаттамада мысал ретінде ұсынылған опцияны талдап көрейік. Сигналдар мен ұяшықтар макростар арқылы қосылған кезде Qt 4.8-мен үйлесімді нұсқа бар.
Қолданбаның сыртқы түрі графикалық дизайнерде жасалған, сондықтан ui нысанын пайдалану арқылы таң қалмаңыз.
mainwindow.h
Сонымен, қолданба жұмыс істеуі үшін бізге QSignalMapper және динамикалық түймені басу өңделетін слот қажет. Нюанс мынада, бұл түймедегі мәтін QLabel ішінде орнатылады, сондықтан біз MainWindow ұяшығымен қабылданатын QSignalMapper::mapped(const QString &) сигналын қолданамыз: :clicked(const QString & ), бұл ұяшықта виджет QPushButton, нысанына түрлендіріледі және біз одан мәтінді аламыз. Бұл түйме жасалған кезде мәтін алдын ала орнатылады. Нөмірлеу үшін құрылған түймелердің есептегіші пайдаланылады (айнымалы int есептегіші).
#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
mainwindow.cpp
Мұның бәрі кодта қалай көрінетінін көрейік.
#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); }
2-нұсқа - QSignalMapper және жаңа синтаксис
Бірінші нұсқада код Qt 4.8 кодымен үйлесімді және әдетте оқуға болады, бірақ 4.8 нұсқасындағы жобаны қолдамайтын болсақ ше? Содан кейін бірінші істеу керек - бұл кодты көрсеткіш синтаксисін пайдаланып қайта жазу және лямбда функцияларының шағын қосындыларын жасау. Содан кейін мен QSignalMapper сұмдық кері қайтару сияқты көрінетінін айтқанымда не айтқым келгенін түсінесіз.
mainwindow.h
Енді тақырып файлында динамикалық түймені басуға арналған ұяшық жоқ, өйткені мұнда ламбдалар бұрыннан пайдаланылады.
#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
mainwindow.cpp
Енді кодтың қалай көрінетінін көрейік. Біз бір мәнді WIN алатын сияқтымыз:
- Lambda функциясының арқасында бір ұяшық жойылды;
- Біз қателерді орындау уақытында емес, компиляция сатысында қадағалау мүмкіндігіне ие болдық, бұл сигнал мен ұяшық макростарының күнәсі болып табылады;
- Кодты Qt5 стандартына әкелді.
Дегенмен, код өте қорқынышты көрінеді, себебі ол QSignalMapper сигналдары мен ұяшықтары үшін static_cast пайдаланады. Бұл map() ұяшығы да, mapped() сигналы да шамадан тыс жүктелгендіктен және компилятор қажет. олардың қолтаңбасын көрсету. Ал келесі конструкциялар төмендегі кодқа қысқалық пен сұлулықты қоспайды. Бірақ бұл жағдайды түзетуге болады - үшінші нұсқаны қарастырыңыз.
#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); }); }
3-нұсқа - QSignalMapper-тен құтылу
Енді QSignalMapper-тен арылайық. Өйткені, егер сіз lambda функцияларының барлық мүмкіндіктерін пайдалансаңыз, онда осы мысалдағыдай тапсырмаларды орындау үшін QSignalMapper мүлде қажет емес.
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 slots: void on_pushButton_clicked(); // Слот, в котором будут создаваться кнопки private: Ui::MainWindow *ui; int counter; }; #endif // MAINWINDOW_H
mainwindow.cpp
Көріп отырғаныңыздай, lambda функциясын пайдалану кодты айтарлықтай азайтады, сонымен қатар бұл жағдайда QSignalMapper сыныбын пайдалануды толығымен жояды. Бұл класс С++ тілінің өзін дамытуға байланысты мүлдем жойылып кетуі әбден мүмкін.
#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()); }); }
Қорытынды...
Сигналдар мен слоттар үшін жаңа синтаксисті пайдалану C++ тілінің қуатты мүмкіндіктерін қосуға мүмкіндік береді және Qt-ге қолданған кезде ескірген нұсқаларында қолданылуы даусыз болатын кейбір сыныптарды пайдаланудан толығымен құтылуға мүмкіндік береді. Qt. Сондай-ақ, ламбдаларды пайдалану бағдарлама кодын айтарлықтай азайтады және жеңілдетеді.
Добрый день.
Ругается такНа
Не можете подсказать почему?
currentIndexChanged является сигналом с перегрузкой. Поэтому его нужно подключать через static_cast с указанием сигнатуры сигнала.
Вот такая петрушка получается:
Спасибо! Знал же, что сигнал с перегрузкой. Думал : "А как компилятор поймет какую именно вызывать?",- но не знал такой прием как указать сигнатуру через static_cast :)
Этот приём в официальной документации на QComboBox указан ;-)
Лямбда удобная штука. Только вчера научился, мне нравится :)
Можешь сказать, когда лучше использовать слот и когда лучше лямбду.
По-моему, связывание сигнала с лямбдой нарушает принцип сигнал слотов.
А писать слот - это значит, что слот может быть вызван из вне. А это не всегда нужно. Я искал про private slots, думал, что private slot может быть связан только с сигналом своего класса, но я ошибался.
По сути лямбда ограничивает область видимости , но нарушает принцип qt сигнал-слот. Это так?
Был бы рад услышать твое мнение по этой теме :)
Я бы не сказал, что лямбда нарушает принципы сигналов и слотов. Это таже самая функция, просто анонимная.
На мой взгляд, принцип сигналов и слотов - это связать некоторые части кода, чтобы при определённом сигнале выполнился необходимый код. А всё остальное это уже домыслы, поскольку лямда подчиняется всем остальным особенностям работы сигналов и слотов, как последовательность подключений и т.д.
Плюс лямбды в том, что она может захватывать необходимые объекты, которые являются локальными в определённом методе. Если же использовать полноценный слот, то будет происходит разрастание кода, поскольку все эти локальные объекты нужно будет объявлять в заголовочном файле класса, а это по сути не нужно. Например, как в этом примере с динамическими кнопками.
В примере нужно получить сигнал от кнопки, и что-то сделать с той же самой кнопкой. Если использовать слот, то придётся держать какой-то вектор объектов в заголовочном файле, либо как сделано здесь, использовать QSignalMapper, а это опять же разрастание кода и повышение избыточности кода. А так лямбда захватывает кнопку и спокойно выполняет необходимый код. Когда кнопка будет уничтожена, а память освобождена, то лямбда автоматически будет отключена от сигнала этой кнопки и так же будет уничтожена. Так что проблем здесь никаких не возникнет.
Конечно, нужен несколько больший профессиональный уровень, чтобы понимать лямбды и работать с ними. Но профит здесь очевиден. Кода меньше. Работа с кодом становится гибче. Не происходит разрастания заголовочного файла методами, которые используются в одном единственном месте и больше нигде.
Плюс некоторые возможности шаблонизации, которые присущи лямбдам с аргументами auto. Плюсов слишком много, чтобы игнорировать использование лямбд в сигналах и слотах.
Как вам такое
Может и кривовато но чёрт побери работает и класс от ненужной больше ни где фигни не разбухает.
Вы используете стандартную практику замыканий, когда нет никакой необходимости объявлять функции в классе, поскольку они используются в одном единственном месте класса, а объявление всех эти лямбд вело бы, как вы подметили, к разбуханию класса.
Мы у себя на проекте такими же приёмами пользуемся, чтобы не загромождать код подобным мусором.