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

Сигналы и слоты - это то, как в Qt взаимодействуют между собой объекты разных классов.
Как это работает в теории
Связь между объектами в устанавливается следующим образом: у одного объекта должен быть сигнал, а у второго - слот. Сигнал объявляется однажды и на этом всё, ему не нужна реализация. Слот же, в общем-то, представляет собой функцию, и потому кроме объявления должен иметь реализацию, как и обычная функция.
Потому, соединив сигнал первого объекта и слот второго, мы получаем следующее: каждый раз, когда первый объект посылает свой сигнал, второй объект принимает его в свой слот и выполняет его функцию.
Таким образом, чтобы соединить два объекта, нужно:
- создать у одного сигнал, а у второго слот;
- соединить сигнал первого и слот второго.
Это можно изобразить вот так:
На рисунке:
- а) два изначальных объекта, ничем ни с кем не соединены;
- б) у первого объекта появился сигнал, а у второго - слот. Теперь им есть чем соединяться, но они всё ещё ни с кем не соединены;
- в) сигнал первого объекта соединён со слотом второго.
Каждый объект может иметь больше одного сигнала и больше одного слота. Соединяться могут также более двух объектов:
Как видно, кроме очевидных соединений, при отправке Объектом 4 сигнала выполнятся слот Объекта 3 и слот Объекта 1.
Как это сделать в Qt
QObject
Необходимое условие - при описании класса должен быть использован макрос Q_OBJECT
, а сами классы должны так или иначе происходить от класса QObject
:
class MeClass : public QObject
{
Q_OBJECT
public:
MeClass();
...
};
Сигналы
Создать сигнал можно так:
class MeClass : public QObject
{
Q_OBJECT
public:
MeClass();
signals:
void someSignal();
...
};
Теперь сигнал можно отправить в любом месте:
emit someSignal();
Слоты
Создать слот можно так:
class MeClass : public QObject
{
Q_OBJECT
public:
MeClass();
signals:
void someSignal();
public slots:
void someSlot();
...
};
Сигналы и слоты с параметрами
Кроме простого соединения, сигналы и слоты позволяют передавать между объектами переменные. Для этого соединяемые сигнал и слот должны иметь параметр одного типа:
// первый класс, отправитель
class MeClass : public QObject
{
Q_OBJECT
public:
MeClass();
signals:
// сигнал будет передавать переменную типа int
void someSignal(int value2send);
...
};
// второй класс, получатель
class YaClass : public QObject
{
Q_OBJECT
public:
YaClass();
public slots:
// слот будет принимать переменную типа int
void someSlot(int value2get);
...
};
Соединение
Функция соединения сигнала первого объекта и слота второго объекта имеет четыре параметра:
- отправитель сигнала;
- его сигнал;
- получатель сигнала;
- его слот.
Соединение сигнала meClass
и слота yaClass
:
MeClass meClass();
YaClass yaClass();
connect(meClass, SIGNAL(someSignal(int)), yaClass, SLOT(someSlot(int)));
Теперь при каждой отправке сигнала someSignal(int)
объекта meClass
будет выполняться слот someSlot(int)
объекта yaClass
.
Демонстрационный пример
Приложение состоит из главного окна Сигналы и слоты, Первого окна и Второго окна.
Первое и Второе окно (точнее, объекты их классов) соединяются посредством сигналов и слотов для обмена строкой из своих полей ввода (каждый из двух классов содержит как сигнал, так и слот). Полученная строка будет отображаться в соответствующей надписи окна-получателя.
Главное окно также соединяется сигналом своего закрытия (уничтожения) со слотами закрытия Первого и Второго окон.
Проект приложения можно загрузить здесь.
Главное окно
mainwindow.h:
...
protected:
// событие закрытия главного окна, будет посылать всем сигнал закрытия
/// это стандартная виртуальная функция класса, поэтому мы её лишь переопределяем
virtual void closeEvent(QCloseEvent *event);
...
mainwindow.cpp:
...
// реализация переопределённой функции
void MainWindow::closeEvent(QCloseEvent *event)
{
emit destroyed(); // отправить сигнал о закрытии (уничтожении) окна
}
...
// соединение сигнала от главного окна со слотом первого окна
connect(this, SIGNAL(destroyed()), frst, SLOT(close()));
// соединение сигнала от главного окна со слотом второго окна
connect(this, SIGNAL(destroyed()), scnd, SLOT(close()));
// слоты close() являются стандартными, потому объявлять в классах окон их не нужно
...
// соединение сигнала от первого окна со слотом второго окна
connect(frst, SIGNAL(sendMessage(QString)),
scnd, SLOT(receiveMessage(QString)));
// соединение сигнала от второго окна со слотом первого окна
connect(scnd, SIGNAL(sendMessage(QString)),
frst, SLOT(receiveMessage(QString)));
Первое окно
frst.h:
...
signals:
/// @brief сигнал отправки сообщения второму окну
/// @param msg2send - отправляемая в сигнале строка
void sendMessage(QString msg2send);
public slots:
/// @brief слот, в которой будет приходить сигнал от второго окна
/// @param msg2recieve - получаемая из сигнала второго окна строка
void receiveMessage(QString msg2recieve);
...
frst.cpp:
...
// обработчик нажатия кнопки отправки
void Frst::on_btn_sendToSecond_clicked()
{
// посылает сигнал, содержащий строку из поля ввода
emit sendMessage(ui->lineEdit_first->text());
}
// слот получения сигнала
void Frst::receiveMessage(QString msg2recieve)
{
// принимает строку из сигнала и вставляет её в надпись
ui->label_received_first->setText(msg2recieve);
}
...
Второе окно
scnd.h:
...
signals:
/// @brief сигнал отправки сообщения первому окну
/// @param msg2send - отправляемая в сигнале строка
void sendMessage(QString msg2send);
public slots:
/// @brief слот, в которой будет приходить сигнал от первого окна
/// @param msg2recieve - получаемая из сигнала первого окна строка
void receiveMessage(QString msg2recieve);
...
scnd.cpp:
...
// обработчик нажатия кнопки отправки
void Scnd::on_btn_sendToFirst_clicked()
{
// посылает сигнал, содержащий строку из поля ввода
emit sendMessage(ui->lineEdit_second->text());
}
// слот получения сигнала
void Scnd::receiveMessage(QString msg2recieve)
{
// принимает строку из сигнала и вставляет её в надпись
ui->label_received_second->setText(msg2recieve);
}
...
Дополнительно
Более подробно про механизм сигналов и слотов можно прочитать в книгах по Qt и в следующих статьях: