Описание классов Qt

Работа с QSignalMapper в Qt: объединение сигналов от идентифицируемых отправителей

обновлено 983 просмотров

Назначение класса

Класс 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));
}

Как это работает

  1. При нажатии на кнопку вызывается слот buttonClick().
  2. Внутри слота создаются новый экземпляр QTimer и новый экземпляр QSignalMapper, оба привязанные к текущему виджету через this.
  3. Таймеру задаётся интервал 5 секунд и режим однократного срабатывания (setSingleShot(true)).
  4. Сигнал timeout() таймера подключается к слоту map() сигнального маппера.
  5. Через setMapping(timer, id++) каждому таймеру назначается уникальный целочисленный идентификатор, который увеличивается при каждом новом создании.
  6. Сигнал mapped(int) маппера подключается к пользовательскому слоту crossCapture(int).
  7. Таймер запускается, а в textEdit выводится сообщение о старте.
  8. По истечении 5 секунд таймер испускает timeout(), маппер подставляет нужный идентификатор и вызывает crossCapture(int i), где выводится сообщение об остановке конкретного таймера.

Важные замечания

  1. QSignalMapper удобен для групповой обработки однотипных сигналов — например, набора кнопок, каждая из которых должна вызывать один слот, но с разными аргументами.
  2. Устаревание класса. Начиная с Qt 5.15 класс QSignalMapper помечен как устаревший (deprecated). В современном коде рекомендуется использовать лямбда-выражения в connect():cppconnect(timer, &QTimer::timeout, this, [this, currentId]() { crossCapture(currentId); });Это даёт тот же результат без дополнительного объекта-посредника.
  3. Владение объектами. Таймер и маппер создаются с родителем this, поэтому автоматически удаляются вместе с виджетом. Однако при многократном нажатии кнопки создаётся всё больше объектов — при необходимости их стоит удалять вручную или переиспользовать.
  4. Синтаксис SIGNAL/SLOT. В примере использован старый синтаксис подключения сигналов. В новом коде предпочтителен указатель-на-член-функцию:cppconnect(timer, &QTimer::timeout, signalMapper, &QSignalMapper::map); connect(signalMapper, QOverload<int>::of(&QSignalMapper::mapped), this, &Widget::crossCapture);
  5. mapped() имеет перегрузки — для int, QString, QWidget* и QObject*. При подключении через старый синтаксис компилятор сам выбирает нужную; при новом синтаксисе требуется явное указание через QOverload<>::of().

Итог

QSignalMapper — удобный инструмент для объединения сигналов от множества объектов в один слот с передачей идентификатора отправителя. Несмотря на то, что класс устарел в новых версиях Qt, он остаётся полезным в legacy-коде и хорошо демонстрирует приёмы работы с сигналами и слотами. В новых проектах вместо него стоит использовать лямбда-выражения.