Слоты и сигналы. Соединение слотов и сигналов

Слоты и сигналы

Пользовательский интерфейс приложения Qt состоит из отдельных элементов управления, называемых виджетами. Это могут быть кнопки, поля ввода, полосы прокрутки, меню, и даже само окно приложения является виджетом.

При взаимодействии пользователя с приложениями виджеты могут посылать сигналы - сообщения о том, что с виджетом происходят какие-то изменения. Примеры сигналов - пользователь нажал на кнопку, или пользователь отредактировал введённое значение.

Сигналы могут быть подключены к слотам, при помощи которых можно управлять виджетами. Каждый сигнал может быть соединен с несколькими слотами, к слоту может быть подключено несколько виджетов.

У каждого слота и сигнала есть набор параметров. Например, при изменении текста в виджете QLineEdit (поле ввода) посылается сигнал, с одним параметром - строка текста (введенное значение), а изменение значения в виджете типа "полоса прокрутки" посылается сигнал, с числовым параметром. Сигналы соединяются со слотами  только если у них полностью совпадают наборы параметров, то есть сигнал, передающий в качестве параметра строку, можно соединить только со слотом, принимающим в качестве параметра строку. Если есть необходимость соединить сигнал со слотом, с отличным набором параметров, нужно создать собственный класс, сделав у него промежуточный слот, который принимает сигнал с одним набором параметров, а после этого сам посылает сигнал с другим набором параметров, и соединять нужный сигнал со слотом нужно через этот "промежуточный" слот.

Создание класса, реализующего концепцию слотов и сигналов

В программе можно создавать собственные классы, реализующие слоты и сигналы. Пример объявления класса, реализующего концепцию слотов и сигналов.

class MyClass : public QObject
{
    Q_OBJECT
private:
    // Объявление закрытых полей класса

public:

    // Конструктор по умолчанию
    MyClass(QObject *parent = 0);

signals:

    // Описание сигналов, посылаемых классом
    void signal1();
    void signal2(int);

public slots:

    // Описание слотов класса
    void slot1();
    void slot2(int);
};

Класс должен быть унаследован от класса QObject или производного от него класса (например, QWidget, QMainWindow и т.д.).

Сразу же после объявления класса до объявления любых полей и методов должно быть написано слово Q_OBJECT. Это директива преропроцессора, которая будет обработана при компиляции программы.

Описание сигналов должно идти после слова "signals:". Это тоже определение препроцессора. Сигналы далее определяются как функции, имеющие определенный набор параметров, и возвращающие значение типа void. Но реализовывать эти функции не нужно, это просто описание того, что класс может послать сигнал с таким названием.

А вот слоты должны быть объявлены и реализованы, как отдельные методы класса, ведь это код, который будет выполняться при подключении его к сигналу. Для описания публичных слотов используются ключевые слова "public slots".

Из метода класса (например, из слота) можно послать какой-либо объявленный ранее сигнал при помощи ключевого слова emit. Например:

emit signal1();

emit signal2(value);

Пример приложения с классом, реализующим слоты и сигналы

Это приложение содержит виджеты QSpinBox, QSlider и QLineEdit. При изменении значения в одном из этих виджетов, изменения происходит и в других виджетах, то есть имеются некоторые общие разделяемые данные, доступные для всех виджетов.

Проблема заключается в том, что QSpinBox и QSlider при изменении значения посылают сигнал, передающий в качестве параметра int, а QLineEdit при изменении передает строку.

Поэтому соединить все эти виджеты напрямую не получится. Создадим дополнительный класс Counter, в котором будет храниться одно число (значение), и все три виджета смогут менять значение этого числа при помощи сигналов. Поэтому Counter будет содержать два слота - один принимает значения типа int (их мы соединим с сигналами виджетов QSpinBox и QSlider), а другой значение типа QString (его соединим с сигналом виджетов QLineEdit).

Но чтобы изменение значения в одном виджете отобразилось во всех остальных виджетах, сделаем так, чтобы класс Counter посылал ещё и сигналы, которые мы соединим на слоты трёх виджетов. Нам также понадобятся два типа сигналов - с числом и со строкой.

Пример реализации

Файл counter.h

#ifndef COUNTER_H
#define COUNTER_H

#include <QObject>

class Counter : public QObject
{
    Q_OBJECT
private:
    int data;

public:
    Counter(QObject *parent = 0);

signals:
    void valueChanged(int);
    void valueChanged(const QString &);

public slots:
    void updateValue(int);
    void updateValue(QString);
};

#endif // COUNTER_H

Этот файл содержит определение класса Counter, объявление поля data, в котором будет храниться число, объявление конструктора с одним параметром - указатель на родительский объект, объявление двух сигналов и двух слотов.

Файл counter.cpp

#include "counter.h"
#include <QDebug>

Counter::Counter(QObject *parent) :
    QObject(parent)
{
    data = 0;
}

void Counter::updateValue(int val)
{
    data = val;
    qDebug() << "Counter::UpdateValue" << val;
    emit valueChanged(val);
    QString S;
    S.setNum(val);
    emit valueChanged(S);
}

void Counter::updateValue(QString S)
{
    int val;
    bool ok;
    val = S.toInt(&ok);
    if (ok)
        updateValue(val);
}

Этот файл содержит реализацию конструктора и двух слотов, обновляющих значение. После обновления значения, слот посылает два сигнала valueChanged с разными параметрами, для того, чтобы можно было обновить значения в связанных виджетах.

Файл main.cpp

#include <QApplication>
#include <QMainWindow>
#include <QPushButton>
#include <QSpinBox>
#include <QSlider>
#include <QLineEdit>
#include "counter.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QMainWindow * w = new QMainWindow();
    QPushButton * Button = new QPushButton("Quit", w);
    QSpinBox * SpinBox = new QSpinBox(w);
    QSlider * Slider = new QSlider(Qt::Horizontal, w);
    QLineEdit * Edit = new QLineEdit(w);
    Counter * MyCounter = new Counter();

    w -> setGeometry(300, 300, 120, 200);
    w -> setWindowTitle("Application");
    Button -> setGeometry(10, 10, 100, 30);
    SpinBox -> setGeometry(10, 50, 100, 30);
    Slider -> setGeometry(10, 90, 100, 30);
    Edit -> setGeometry(10, 130, 100, 30);

    QObject::connect(Button, SIGNAL(clicked()),
                     w, SLOT(close()));
    QObject::connect(SpinBox, SIGNAL(valueChanged(int)),
                     MyCounter, SLOT(updateValue(int)));
    QObject::connect(MyCounter, SIGNAL(valueChanged(int)),
                     Slider, SLOT(setValue(int)));
    QObject::connect(Slider, SIGNAL(valueChanged(int)),
                     MyCounter, SLOT(updateValue(int)));
    QObject::connect(MyCounter, SIGNAL(valueChanged(int)),
                     SpinBox, SLOT(setValue(int)));
    QObject::connect(Edit, SIGNAL(textChanged(QString)),
                     MyCounter, SLOT(updateValue(QString)));
    QObject::connect(MyCounter, SIGNAL(valueChanged(const QString &)),
                     Edit, SLOT(setText(const QString &)));
    w -> show();
    return a.exec();
}

Этот файл содержит объявления трех виджетов - класса QSpinBox, QSlider, QLineEdit, а также экземпляра класса Counter. Сигналы valueChanged(int), которые посылают QSpinBox и QSlider соединяются со слотом updateValue(int) класса Counter, сигнал textChanged(QString) класса Counter соединяется со слотом updateValue(QString) класса Counter.

Наоборот, сигналы valueChanged(int) и valueChanged(const QString &) соединяются со слотами setValue(int) и setText (const QString &) соответствующих классов.