Qt - Сигналы и слоты: различия между версиями
(Стабильная работа с обучением, зарплата высокая!) |
Bit (обсуждение | вклад) м (Откат правок 188.32.138.203 (обсуждение) к версии 81.23.104.106) |
||
Строка 1: | Строка 1: | ||
[[Файл:Qt-logo.png|right|150px]] | |||
Сигналы и слоты - это то, как в Qt взаимодействуют между собой объекты разных классов. | |||
== Как это работает в теории == | |||
Связь между объектами устанавливается следующим образом: у одного объекта должен быть сигнал, а у второго - слот. Сигнал объявляется однажды и на этом всё, ему не нужна реализация. Слот же, в общем-то, представляет собой функцию, и потому кроме объявления должен иметь реализацию, как и обычная функция. | |||
Потому, соединив сигнал первого объекта и слот второго, мы получаем следующее: каждый раз, когда первый объект посылает свой сигнал, второй объект принимает его в свой слот и выполняет его функцию. | |||
Таким образом, чтобы соединить два объекта, нужно: | |||
# создать у одного сигнал, а у второго слот; | |||
# соединить сигнал первого и слот второго. | |||
Это можно изобразить вот так: | |||
[[Файл:Qt signls.and.slots pic1.png|800px]] | |||
На рисунке: | |||
:а) два изначальных объекта, ничем ни с кем не соединены; | |||
:б) у первого объекта появился сигнал, а у второго - слот. Теперь им есть чем соединяться, но они всё ещё ни с кем не соединены; | |||
:в) сигнал первого объекта соединён со слотом второго. | |||
Каждый объект может иметь больше одного сигнала и больше одного слота. Соединяться могут также более двух объектов: | |||
[[Файл:Qt signls.and.slots pic2.png|500px]] | |||
Как видно, кроме очевидных соединений, при отправке Объектом 4 сигнала выполнятся слот Объекта 3 и слот Объекта 1. | |||
=== Примеры === | |||
Немного поясняющих картинок: | |||
<gallery widths="400px" heights="250"> | |||
Файл:Signals.and.slots.start.jpg|Один сигнал соединён с одинаковыми слотами разных объектов (наследников одного класса) | |||
Файл:Signals.and.slots.abe.jpg|Один сигнал соединён с разными слотами разных объектов | |||
</gallery> | |||
Картинка с мудакенами может внести некоторую путаницу, потому её следует сопроводить разъяснением. | |||
Каждый мудакен (из пяти слева) имеет набор слотов, будем считать, что у всех одинаковый (потому что все они унаследованы от одного класса <code>Мудакен</code>). То есть, у каждого из них есть следующие: | |||
* <code>здарова()</code>; | |||
* <code>сам_привет()</code>; | |||
* <code>ну_привет()</code>; | |||
* <code>хай()</code>; | |||
* <code>пошёл_ты()</code>. | |||
Эйб может подать сигнал <code>привет()</code>. | |||
Можно было бы соединить его сигнал с каким-то одним слотом, одинаковым для всех пяти остальных мудакенов, к которым он обращается, но такая ситуация уже показана на картинке со стометровкой. Потому мы соединили сигнал Эйба с различными слотами ответов мудакенов. | |||
Путаница могла возникнуть такая, что можно было подумать, что <code>пошёл_ты()</code> (ну и остальные) - это уже реакция на приветствие Эйба. Но это не так, это лишь ''имя реакции'' (имя слота), а реализация у неё может быть какой угодно. | |||
Например, очевидный: | |||
<syntaxhighlight lang="cpp"> | |||
QString MudakenAngry::пошёл_ты() | |||
{ | |||
QString answer = "Да пошёл ты. Пива не принёс, ничего не принёс, ещё хочет чего-то. Вообще охренеть."; | |||
return answer; | |||
} | |||
</syntaxhighlight> | |||
или не очень: | |||
<syntaxhighlight lang="cpp"> | |||
QString MudakenAngry::пошёл_ты() | |||
{ | |||
QString answer = "Ну наконец-то! Где тебя носило? Пошли за пивом уже!"; | |||
return answer; | |||
} | |||
</syntaxhighlight> | |||
Конечно, при написании кода лучше делать так, чтобы название и функционал совпадали по смыслу, потому второй вариант приведён лишь в качестве примера. | |||
== Как это сделать в Qt == | |||
=== QObject === | |||
Необходимое условие - при описании класса должен быть использован макрос <code>Q_OBJECT</code>, а сами классы должны так или иначе происходить от класса <code>QObject</code>: | |||
<syntaxhighlight lang="cpp" line highlight="3"> | |||
class MeClass : public QObject | |||
{ | |||
Q_OBJECT | |||
public: | |||
MeClass(); | |||
... | |||
}; | |||
</syntaxhighlight> | |||
=== Сигналы === | |||
Создать сигнал можно так: | |||
<syntaxhighlight lang="cpp" line highlight="9"> | |||
class MeClass : public QObject | |||
{ | |||
Q_OBJECT | |||
public: | |||
MeClass(); | |||
signals: | |||
void someSignal(); | |||
... | |||
}; | |||
</syntaxhighlight> | |||
Теперь сигнал можно отправить в любом месте: | |||
<syntaxhighlight lang="cpp"> | |||
emit someSignal(); | |||
</syntaxhighlight> | |||
=== Слоты === | |||
Создать слот можно так: | |||
<syntaxhighlight lang="cpp" line highlight="12"> | |||
class MeClass : public QObject | |||
{ | |||
Q_OBJECT | |||
public: | |||
MeClass(); | |||
signals: | |||
void someSignal(); | |||
public slots: | |||
void someSlot(); | |||
... | |||
}; | |||
</syntaxhighlight> | |||
=== Сигналы и слоты с параметрами === | |||
Кроме простого соединения, сигналы и слоты позволяют передавать между объектами переменные. Для этого соединяемые сигнал и слот должны иметь параметр одного типа: | |||
<syntaxhighlight lang="cpp" line highlight="11, 27"> | |||
// первый класс, отправитель | |||
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); | |||
... | |||
}; | |||
</syntaxhighlight> | |||
=== Соединение === | |||
Функция соединения сигнала первого объекта и слота второго объекта имеет четыре параметра: | |||
# отправитель сигнала; | |||
# его сигнал; | |||
# получатель сигнала; | |||
# его слот. | |||
Соединение сигнала <code>meClass</code> и слота <code>yaClass</code>: | |||
<syntaxhighlight lang="cpp" line highlight="4"> | |||
MeClass meClass(); | |||
YaClass yaClass(); | |||
connect(meClass, SIGNAL(someSignal(int)), yaClass, SLOT(someSlot(int))); | |||
</syntaxhighlight> | |||
Теперь при каждой отправке сигнала <code>someSignal(int)</code> объекта <code>meClass</code> будет выполняться слот <code>someSlot(int)</code> объекта <code>yaClass</code>. | |||
== Демонстрационный пример == | |||
Приложение состоит из главного окна Сигналы и слоты, Первого окна и Второго окна. | |||
[[Файл:Qt signls.and.slots pic3.png]] | |||
Первое и Второе окно (точнее, объекты их классов) соединяются посредством сигналов и слотов для обмена строкой из своих полей ввода (каждый из двух классов содержит как сигнал, так и слот). Полученная строка будет отображаться в соответствующей надписи окна-получателя. | |||
Главное окно также соединяется сигналом своего закрытия (уничтожения) со слотами закрытия Первого и Второго окон. | |||
Проект приложения можно загрузить [http://yadi.sk/d/Csajnu040IX5Z здесь]. | |||
=== Главное окно === | |||
<code>mainwindow.h:</code> | |||
<syntaxhighlight lang="cpp" line> | |||
... | |||
protected: | |||
// событие закрытия главного окна, будет посылать всем сигнал закрытия | |||
/// это стандартная виртуальная функция класса, поэтому мы её лишь переопределяем | |||
virtual void closeEvent(QCloseEvent *event); | |||
... | |||
</syntaxhighlight> | |||
<code>mainwindow.cpp:</code> | |||
<syntaxhighlight lang="cpp" line> | |||
... | |||
// реализация переопределённой функции | |||
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))); | |||
</syntaxhighlight> | |||
=== Первое окно === | |||
<code>frst.h:</code> | |||
<syntaxhighlight lang="cpp" line> | |||
... | |||
signals: | |||
/// @brief сигнал отправки сообщения второму окну | |||
/// @param msg2send - отправляемая в сигнале строка | |||
void sendMessage(QString msg2send); | |||
public slots: | |||
/// @brief слот, в которой будет приходить сигнал от второго окна | |||
/// @param msg2recieve - получаемая из сигнала второго окна строка | |||
void receiveMessage(QString msg2recieve); | |||
... | |||
</syntaxhighlight> | |||
<code>frst.cpp:</code> | |||
<syntaxhighlight lang="cpp" line> | |||
... | |||
// обработчик нажатия кнопки отправки | |||
void Frst::on_btn_sendToSecond_clicked() | |||
{ | |||
// посылает сигнал, содержащий строку из поля ввода | |||
emit sendMessage(ui->lineEdit_first->text()); | |||
} | |||
// слот получения сигнала | |||
void Frst::receiveMessage(QString msg2recieve) | |||
{ | |||
// принимает строку из сигнала и вставляет её в надпись | |||
ui->label_received_first->setText(msg2recieve); | |||
} | |||
... | |||
</syntaxhighlight> | |||
=== Второе окно === | |||
<code>scnd.h:</code> | |||
<syntaxhighlight lang="cpp" line> | |||
... | |||
signals: | |||
/// @brief сигнал отправки сообщения первому окну | |||
/// @param msg2send - отправляемая в сигнале строка | |||
void sendMessage(QString msg2send); | |||
public slots: | |||
/// @brief слот, в которой будет приходить сигнал от первого окна | |||
/// @param msg2recieve - получаемая из сигнала первого окна строка | |||
void receiveMessage(QString msg2recieve); | |||
... | |||
</syntaxhighlight> | |||
<code>scnd.cpp:</code> | |||
<syntaxhighlight lang="cpp" line> | |||
... | |||
// обработчик нажатия кнопки отправки | |||
void Scnd::on_btn_sendToFirst_clicked() | |||
{ | |||
// посылает сигнал, содержащий строку из поля ввода | |||
emit sendMessage(ui->lineEdit_second->text()); | |||
} | |||
// слот получения сигнала | |||
void Scnd::receiveMessage(QString msg2recieve) | |||
{ | |||
// принимает строку из сигнала и вставляет её в надпись | |||
ui->label_received_second->setText(msg2recieve); | |||
} | |||
... | |||
</syntaxhighlight> | |||
== Дополнительно == | |||
Более подробно про механизм сигналов и слотов можно прочитать [[Qt#Книги | в книгах по Qt]] и в следующих статьях: | |||
* [http://doc.crossplatform.ru/qt/4.3.2/signalsandslots.html CrossPlatform.RU - Сигналы и слоты]; | |||
* [http://www.developer.nokia.com/Community/Wiki/%D0%9C%D0%B5%D1%85%D0%B0%D0%BD%D0%B8%D0%B7%D0%BC_%D1%81%D0%B8%D0%B3%D0%BD%D0%B0%D0%BB%D0%BE%D0%B2_%D0%B8_%D1%81%D0%BB%D0%BE%D1%82%D0%BE%D0%B2_%D0%B2_Qt NOKIA Developer - Механизм сигналов и слотов в Qt]. | |||
Видео: | |||
* [http://www.youtube.com/watch?v=JtyCM4BTbYo C++ Qt 04 - Signals and Slots] (на английском языке). | |||
[[Категория:Погроммирование]] |
Текущая версия от 11:55, 12 марта 2018

Сигналы и слоты - это то, как в Qt взаимодействуют между собой объекты разных классов.
Как это работает в теории
Связь между объектами устанавливается следующим образом: у одного объекта должен быть сигнал, а у второго - слот. Сигнал объявляется однажды и на этом всё, ему не нужна реализация. Слот же, в общем-то, представляет собой функцию, и потому кроме объявления должен иметь реализацию, как и обычная функция.
Потому, соединив сигнал первого объекта и слот второго, мы получаем следующее: каждый раз, когда первый объект посылает свой сигнал, второй объект принимает его в свой слот и выполняет его функцию.
Таким образом, чтобы соединить два объекта, нужно:
- создать у одного сигнал, а у второго слот;
- соединить сигнал первого и слот второго.
Это можно изобразить вот так:
На рисунке:
- а) два изначальных объекта, ничем ни с кем не соединены;
- б) у первого объекта появился сигнал, а у второго - слот. Теперь им есть чем соединяться, но они всё ещё ни с кем не соединены;
- в) сигнал первого объекта соединён со слотом второго.
Каждый объект может иметь больше одного сигнала и больше одного слота. Соединяться могут также более двух объектов:
Как видно, кроме очевидных соединений, при отправке Объектом 4 сигнала выполнятся слот Объекта 3 и слот Объекта 1.
Примеры
Немного поясняющих картинок:
-
Один сигнал соединён с одинаковыми слотами разных объектов (наследников одного класса)
-
Один сигнал соединён с разными слотами разных объектов
Картинка с мудакенами может внести некоторую путаницу, потому её следует сопроводить разъяснением.
Каждый мудакен (из пяти слева) имеет набор слотов, будем считать, что у всех одинаковый (потому что все они унаследованы от одного класса Мудакен
). То есть, у каждого из них есть следующие:
здарова()
;сам_привет()
;ну_привет()
;хай()
;пошёл_ты()
.
Эйб может подать сигнал привет()
.
Можно было бы соединить его сигнал с каким-то одним слотом, одинаковым для всех пяти остальных мудакенов, к которым он обращается, но такая ситуация уже показана на картинке со стометровкой. Потому мы соединили сигнал Эйба с различными слотами ответов мудакенов.
Путаница могла возникнуть такая, что можно было подумать, что пошёл_ты()
(ну и остальные) - это уже реакция на приветствие Эйба. Но это не так, это лишь имя реакции (имя слота), а реализация у неё может быть какой угодно.
Например, очевидный:
QString MudakenAngry::пошёл_ты()
{
QString answer = "Да пошёл ты. Пива не принёс, ничего не принёс, ещё хочет чего-то. Вообще охренеть.";
return answer;
}
или не очень:
QString MudakenAngry::пошёл_ты()
{
QString answer = "Ну наконец-то! Где тебя носило? Пошли за пивом уже!";
return answer;
}
Конечно, при написании кода лучше делать так, чтобы название и функционал совпадали по смыслу, потому второй вариант приведён лишь в качестве примера.
Как это сделать в 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 и в следующих статьях:
Видео:
- C++ Qt 04 - Signals and Slots (на английском языке).