Посмотрим на примере, как создать сигналы и слоты и связать их друг с другом и для демонстрации и простоты определим новый проект по типу Qt Console Application
В качестве системы построения проектов для простоты выберем qmake:
Все остальные настройки оставим по умолчанию. В итоге у нас будет создан проект, где есть один файл main.cpp
Сигналы и слоты определяются внутри класса, поэтому добавим в проект новый класс. Для этого в структуре проекта нажмем на узел Source и в появившемся контекстном меню выберем пункт Add New...
В окне добавления нового элемента выберем пункт С++ Class
Назовем добавляемый класс Counter:
Все остальные настройки на последующих шагах оставим по умолчанию, и в проект будет доблено два файла: заголовочный файл counter.h и файл с реализацией класса counter.cpp
В заголовочном файле 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 мы сможем увидеть диагностическое сообщение:
Это простейший пример, предназначенный только для демонстрации. Так как в данном случае нет смысла в организации подобной связи, поскольку мы можем вывести строку на консоль непосредственно в конструкторе без необходимости создавать слот. Да и генерация сигнала в конструкторе имеет ограничения. Поэтому рассмотрим чуть более сложный пример. Изменим заголовочный файл 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.