Назначение класса
Класс QSignalMapper объединяет сигналы от идентифицируемых отправителей. Он собирает набор сигналов без параметров и повторно генерирует их с параметрами — целочисленными, строковыми или виджет-параметрами, соответствующими объекту, который отправил исходный сигнал.
Класс поддерживает сопоставление определённых строк или целых чисел с конкретными объектами с помощью метода setMapping(). Затем сигналы объектов могут быть подключены к слоту map(), который излучает сигнал mapped() со строкой или целым числом, связанным с исходным объектом-отправителем. Сопоставления можно удалить позже с помощью removeMappings().
Это особенно удобно, когда нужно подключить множество однотипных сигналов (например, clicked() от нескольких кнопок) к одному слоту, но при этом различать, от какого именно объекта пришёл сигнал.
Пример приложения
Для демонстрации создадим небольшое приложение, которое запускает таймер и присваивает ему личный идентификатор. Через connect() связываем QTimer и QSignalMapper. При срабатывании таймера сигнал переправляется на QSignalMapper, который связывает его с пользовательским идентификатором и вызывает слот crossCapture(int), подключённый к QSignalMapper.
Определение класса
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTimer>
#include <QSignalMapper>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
int id;
private slots:
void buttonClick();
void crossCapture(int);
};
#endif // WIDGET_H
Реализация класса
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget), id(0)
{
ui->setupUi(this);
connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(buttonClick()));
}
Widget::~Widget()
{
delete ui;
}
void Widget::buttonClick()
{
QTimer* timer = new QTimer(this);
timer->setInterval(5000);
timer->setSingleShot(true);
QSignalMapper *signalMapper = new QSignalMapper(this);
connect(timer, SIGNAL(timeout()), signalMapper, SLOT(map()));
signalMapper->setMapping(timer, id++);
connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(crossCapture(int)));
ui->textEdit->append(QString("Start timer: %1").arg(id));
timer->start();
}
void Widget::crossCapture(int i)
{
ui->textEdit->append(QString("Signal mapper: %1 Stop timer %1").arg(i));
}
Как это работает
- При нажатии на кнопку вызывается слот
buttonClick(). - Внутри слота создаются новый экземпляр
QTimerи новый экземплярQSignalMapper, оба привязанные к текущему виджету черезthis. - Таймеру задаётся интервал 5 секунд и режим однократного срабатывания (
setSingleShot(true)). - Сигнал
timeout()таймера подключается к слотуmap()сигнального маппера. - Через
setMapping(timer, id++)каждому таймеру назначается уникальный целочисленный идентификатор, который увеличивается при каждом новом создании. - Сигнал
mapped(int)маппера подключается к пользовательскому слотуcrossCapture(int). - Таймер запускается, а в
textEditвыводится сообщение о старте. - По истечении 5 секунд таймер испускает
timeout(), маппер подставляет нужный идентификатор и вызываетcrossCapture(int i), где выводится сообщение об остановке конкретного таймера.
Важные замечания
QSignalMapperудобен для групповой обработки однотипных сигналов — например, набора кнопок, каждая из которых должна вызывать один слот, но с разными аргументами.- Устаревание класса. Начиная с Qt 5.15 класс
QSignalMapperпомечен как устаревший (deprecated). В современном коде рекомендуется использовать лямбда-выражения вconnect():cppconnect(timer, &QTimer::timeout, this, [this, currentId]() { crossCapture(currentId); });Это даёт тот же результат без дополнительного объекта-посредника. - Владение объектами. Таймер и маппер создаются с родителем
this, поэтому автоматически удаляются вместе с виджетом. Однако при многократном нажатии кнопки создаётся всё больше объектов — при необходимости их стоит удалять вручную или переиспользовать. - Синтаксис SIGNAL/SLOT. В примере использован старый синтаксис подключения сигналов. В новом коде предпочтителен указатель-на-член-функцию:cppconnect(timer, &QTimer::timeout, signalMapper, &QSignalMapper::map); connect(signalMapper, QOverload<int>::of(&QSignalMapper::mapped), this, &Widget::crossCapture);
mapped()имеет перегрузки — дляint,QString,QWidget*иQObject*. При подключении через старый синтаксис компилятор сам выбирает нужную; при новом синтаксисе требуется явное указание черезQOverload<>::of().
Итог
QSignalMapper — удобный инструмент для объединения сигналов от множества объектов в один слот с передачей идентификатора отправителя. Несмотря на то, что класс устарел в новых версиях Qt, он остаётся полезным в legacy-коде и хорошо демонстрирует приёмы работы с сигналами и слотами. В новых проектах вместо него стоит использовать лямбда-выражения.