Определение сигналов и слотов в классах C++

Последнее обновление: 06.01.2024

Посмотрим на примере, как создать сигналы и слоты и связать их друг с другом и для демонстрации и простоты определим новый проект по типу Qt Console Application

Рассмотрение сигналов и слотов на примере проекта Qt Console Application

В качестве системы построения проектов для простоты выберем qmake:

сигналы и слоты на примере проекта Qt Console Application

Все остальные настройки оставим по умолчанию. В итоге у нас будет создан проект, где есть один файл main.cpp

Сигналы и слоты определяются внутри класса, поэтому добавим в проект новый класс. Для этого в структуре проекта нажмем на узел Source и в появившемся контекстном меню выберем пункт Add New...

Добавление нового класса в Qt Creator

В окне добавления нового элемента выберем пункт С++ Class

Добавление нового класса C++ в Qt Creator

Назовем добавляемый класс Counter:

Добавление и настройка класса C++ в Qt Creator

Все остальные настройки на последующих шагах оставим по умолчанию, и в проект будет доблено два файла: заголовочный файл counter.h и файл с реализацией класса counter.cpp

Новый класс C++ в Qt Creator

В заголовочном файле counter.h определим следующий код:

#ifndef COUNTER_H
#define COUNTER_H

#include <QObject>

class Counter: public QObject
{
    Q_OBJECT
public:
    explicit Counter(QObject *parent = nullptr);
signals:
    void created();     // сигнал
public slots:
    void onCreated();   // слот
};

#endif // COUNTER_H

Класс Counter унаследован от класса QObject, и поэтому внутри класса применяется макрос Q_OBJECT. В конструктор в качестве параметра передается родительский объект, который по умолчанию равен nullptr.

Сигналы определяются в классе в секции signals. В данном случае определен один сигнал - created

Слоты определяются в классе в секции slots. В данном случае определен один слот - onCreated. Нередко слоты, соответствующие определенным сигналам, называются по имени сигнала + приставка On, например, "clicked" - "onClicked".

А в файле counter.cpp определим следующую реализацию класса:

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

Counter::Counter(QObject *parent) : QObject(parent)
{
    // устанавливаем соединение между сигналом created и слотом onCreated
    QObject::connect(this, SIGNAL(created()),this, SLOT(onCreated()));
    emit created(); // генерируем сигнал created
}
// реализация слота onCreated
void Counter::onCreated()
{
    std::cout << "Counter Object created!" << std::endl;
}

Здесь в реализации слота onCreated просто выводим соответствующее сообщение на консоль.

В конструкторе класса Counter устанавливаем связь между сигналом created и слотом onCreated

QObject::connect(this, SIGNAL(created()),this, SLOT(onCreated()));

А с помощью макроса emit генерируем сигнал created

emit created(); // генерируем сигнал created

Стоит отметить, что в данном случае для организации связи между сигналом и слотом мы могли бы использовать другую версию функции connect:

QObject::connect(this, &Counter::created,this, &Counter::onCreated);

Таким образом, при создании объекта в конструкторе будет генерироваться сигнал created. Слот onCreated получет этот сигнал и обработает его.

Далее в файле main.cpp используем класс Counter:

#include <QCoreApplication>
#include "counter.h"    // подключаем класс Counter

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Counter counter{};   // создаем объект Counter
    return a.exec();
}

Здесь просто создается один объект Counter. Однако поскольку в конструкторе устанавливается связь между сигналом creacted и слотом onCreated, и затем генерируется сигнал created, то слот onCreated получит этот сигнал и выполнит определенные для него действия. В итоге при запуске приложения в консоли Qt Creator мы сможем увидеть диагностическое сообщение:

Связь сигналов и слотов в Qt и QML

Это простейший пример, предназначенный только для демонстрации. Так как в данном случае нет смысла в организации подобной связи, поскольку мы можем вывести строку на консоль непосредственно в конструкторе без необходимости создавать слот. Да и генерация сигнала в конструкторе имеет ограничения. Поэтому рассмотрим чуть более сложный пример. Изменим заголовочный файл counter.h следующим образом:

#ifndef COUNTER_H
#define COUNTER_H

#include <QObject>

class Counter: public QObject
{
    Q_OBJECT
public:
    Counter(){}
    void increase();

signals:
    void increased(int newValue);
private:
    int value{};
};

#endif // COUNTER_H

Теперь класс Counter определяет приватное поле value, которое по умолчанию равно 0. Метод increase будет увеличивать значение value на 1. Также определен сигнал increased, который генерируется при изменении значения value в методе increase.

В файле counter.cpp определим реализацию для метода increase:

#include "counter.h"

void Counter::increase()
{
    value++;
    emit increased(value);
}

Здесь метод increase будет увеличивает значение value на 1 и генерирует сигнал increased, в который передается новое значение.

Используем измененный класс Counter в файле main.cpp:

#include <QCoreApplication>

#include <iostream>
#include "counter.h"    // подключаем класс Counter

void onCounterChanged(int);

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Counter counter{};
    QObject::connect(&counter, &Counter::increased, onCounterChanged);
    // изменяем значение
    counter.increase();       // New value: 1
    counter.increase();       // New value: 2
    counter.increase();       // New value: 3

    return a.exec();
}

void onCounterChanged(int newValue){
    std::cout << "New value: " << newValue << std::endl;
}

Здесь устанавливается связь между сигналом increased объекта counter и функцией onCounterChanged.

QObject::connect(&counter, &Counter::increased, onCounterChanged);

В данном случае в качестве слота применяется функция onCounterChanged, которая определена в текущем файле и которая не является членом класса. Поэтому применяется версия функции connect(), где объект-получатель можно не указывать, а в качестве третьего параметра передается функция-слот.

В функции onCounterChanged для простоты выводим новое значение на консоль.

void onCounterChanged(int newValue){
    std::cout << "New value: " << newValue << std::endl;
}

Сигнатура этой функции совпадает с сигнатурой сигнала increased. Параметр этой функции - newValue представляет число, которое передается в сигнал increased при его генерации.

Далее с помощью метода increase увеличиваем значение объекта counter:

counter.increase();       // New value: 1
counter.increase();       // New value: 2
counter.increase();       // New value: 3

Консольный вывод:

New value: 1
New value: 2
New value: 3

И здесь мы видим преимущество системы сигналов-слотов. При определении класса Counter мы можем не знать, как окружение, где используется класс, будет реагировать на изменение значения в Counter. В данном случае мы выводим на консоль. Но мы могли бы выводить эту информацию в диалоговом окне или на какой-то другой виджет или элемент QML. Мы могли бы обрабатывать сигнал в десктопном или мобильном приложении, где обработка может отличаться. И механизм сигналов позволяет определить сигнал в классе, который не зависит от конкретного окружения, а обработку изменять в зависимости от конкретной ситуации и окружения, без изменения кода класса.

Причем для обработки могут также применяться методы других классов. Например:

#include <QCoreApplication>
#include <QObject>

#include <iostream>
#include "counter.h"    // подключаем класс Counter

// класс для обработки сигнала
class CounterHandler: public QObject
{
public:
    void handle(int newValue)
    {
        std::cout << "Counter value: " << newValue << std::endl;
    }
};


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Counter counter{};
    CounterHandler handler{};
    QObject::connect(&counter, &Counter::increased, &handler, &CounterHandler::handle);
    counter.increase();       // Counter value: 1
    counter.increase();       // Counter value: 2
    counter.increase();       // Counter value: 3

    return a.exec();
}

В данном случае сигнал обрабатывается методом handle объекта CounterHandler.

Подключение нескольких слотов

К одному сигналу можно подключить несколько слотов. Так, в файле counter.h определим следующий код:

#ifndef COUNTER_H
#define COUNTER_H

#include <QObject>

class Counter: public QObject
{
    Q_OBJECT
public:
    Counter(){}
    void increase();
    void decrease();

signals:
    void increased(int newValue);
    void decreased(int newValue);
private:
    int value{};
};

#endif // COUNTER_H

Здесь добавлен метод decrease, который будет уменьшать значение value, и сигнал decreased, который будет генерироваться при изменении значения.

В файле counter.cpp определим реализацию метода:

#include "counter.h"

void Counter::increase()
{
    value++;
    emit increased(value);
}
void Counter::decrease()
{
    value--;
    emit decreased(value);
}

Метод decrease уменьшает значение value на 1 и генерует сигнал decreased.

В файле main.cpp обработаем оба сигнала с помощью одной функции-слота:

#include <QCoreApplication>

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

void onCounterChanged (int);

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Counter counter{};
    QObject::connect(&counter, &Counter::increased, onCounterChanged);
    QObject::connect(&counter, &Counter::decreased, onCounterChanged);

    counter.increase();
    counter.increase();
    counter.decrease();

    return a.exec();
}

void onCounterChanged(int newValue)
{
    std::cout << "New counter value: " << newValue << std::endl;
}

Здесь оба сигнала обрабатываются одной функцией - onCounterChanged. Консольный вывод программы:

New counter value: 1
New counter value: 2
New counter value: 1

Подключение сигнала к другому сигналу

Иногда вам может потребоваться переслать сигнал вместо прямого подключения к слоту. В этом случае можно соединить один сигнал с другим сигналом следующим образом:

connect(sender, SIGNAL(signalA()), forwarder, SIGNAL(signalB()));
// или так
connect(sender,&ClassName::signalA,forwarder,&ClassName::signalB);

Например, для класса Counter в заголовочном файле counter.h определим еще один сигнал:

#ifndef COUNTER_H
#define COUNTER_H

#include <QObject>

class Counter: public QObject
{
    Q_OBJECT
public:
    Counter(){}
    void increase();
    void decrease();

signals:
    void valueChanged(int newValue);
    void increased(int newValue);
    void decreased(int newValue);
private:
    int value{};
};

#endif // COUNTER_H

Здесь добавлен сигнал valueChanged, который нигде не вызывается.

В файле main.cpp обработаем этот сигнал:

#include <QCoreApplication>

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

void onCounterChanged (int);

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Counter counter{};
    // перенаправляем сигнал increased на valueChanged
    QObject::connect(&counter, &Counter::increased, &counter, &Counter::valueChanged);
    // перенаправляем сигнал decreased на valueChanged
    QObject::connect(&counter, &Counter::decreased, &counter, &Counter::valueChanged);
    QObject::connect(&counter, &Counter::valueChanged, onCounterChanged);
    
    counter.increase();
    counter.increase();
    counter.decrease();

    return a.exec();
}

void onCounterChanged(int newValue)
{
    std::cout << "New counter value: " << newValue << std::endl;
}

Здесь оба сигнала increased и decreased перенаправляются на сигнал valueChanged. В итоге при возникновении сигналов increased и decreased, будет генерироваться сигнал valueChanged. Стоит отметить, что здесь сигналы перенаправляются на сигнал того же объекта, но это может быть сигнал другого объекта в том числе другого класса. Фунцкия onCounterChanged же теперь обрабатывает сигнал valueChanged.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850