Сигнали та слоти використовуються для комунікації між об'єктами Qt. Механізм сигналів і слотів є центральною функцією Qt, і ймовірно це те, що відрізняє Qt за своїм функціоналом від інших фреймворків. Сигнали та слоти стали можливими завдяки мета-об'єктній системі Qt.
Вступ
При програмуванні GUI, коли змінюється один із віджетів, ми часто хочемо, щоб інші віджети були про це повідомлені. У загальному випадку ми хочемо, щоб об'єкти могли взаємодіяти один з одним. Наприклад, якщо користувач натиснув кнопку Закрити , ми, можливо, захочемо, щоб об'єкт window викликав функцію close().
Інші інструменти розробки забезпечують подібний функціонал використанням callback. callback є вказівником на функцію, і якщо Ви хочете виконати функцію, яка повідомить Вас про якусь подію, то Ви передаєте вказівник на іншу функцію, тобто callback. Функція, що працює, викликає callback тоді, коли це доречно. У той час як існують фреймворки, які успішно використовують callback функції, проте callback** є неінтуїтивним способом, який може викликати проблеми із забезпеченням коректності аргументів, що повертаються.
Сигнали та слоти
У Qt застосовується альтернативна техніка, тобто використовуються слоти та сигнали. Сигнал виконується тоді, коли відбувається певна подія. Віджети Qt мають безліч визначених сигналів, але ми можемо завжди успадковуватись відвіджета і визначити власні сигнали для них. Слотом є функція, що викликається у відповідь певний сигнал. Віджети Qt також мають безліч зумовлених слотів, але успадкування від віджетів і додавання власних слотів є звичайною практикою, тому Ви можете обробляти ті сигнали, які Вам цікаві.
]
Сигнали та слоти Qt Сигнали та слоти є типо-безпечним механізмом. Сигнатура сигналу повинна співпадати з сигнатурою приймаючого слота (хоча фактично, слот може мати сигнатуру коротше, ніж сигнал, але слот приймає сигнал, оскільки ігнорує зайві аргументи). Оскільки сигнатури сумісні, компілятор може допомогти визначити невідповідності при використанні синтаксису заснованого на покажчиках. Тоді як при синтаксисі, заснованому на макросах SIGNAL і SLOT, можливо визначити невідповідність типів тільки в runtime процесі. Сигнали і слоти слабко пов'язані: клас, який викликає сигнал, знає тільки слот, який приймає сигнал. Механізм сигналів і слотів Qt забезпечується, якщо Ви підключили сигнал до слота, який буде викликатися з параметрами сигналу в потрібний момент. Сигнали та слоти можуть мати кілька аргументів та типів. І вони є цілком типо-безпечними.
Усі класи, які успадковані від QObject або його підкласів (таких як QWidget) можуть містити сигнали та слоти. Сигнали викликаються об'єктами, які змінюють свій стан, що може бути цікавим для інших об'єктів. Це все, що робить об'єкт для комунікації. І об'єкт не переймається тим, хто приймає сигнали, які він випускає. Це є чесною інкапсуляцією інформації і забезпечує те, що об'єкт може використовуватися як програмний компонент.
Слоти можуть бути використані для прийому сигналів, але є також звичайними функціями. Просто як об'єкт не знає, що прийняло його сигнал, так і слот не знає, який сигнал підключений до нього. Це забезпечує реальну незалежність компонентів, які можуть бути створені з Qt.
Ви можете підключити як безліч сигналів одного слоту, так і сигнал може бути підключений до безлічі слотів. І навіть можна підключити сигнал безпосередньо до іншого сигналу. (Це викликає другий сигнал, коли був викликаний перший)
Водночас, сигнали та слоти створюють потужний механізм компонентного програмування.
Сигнали
Сигнали випускаються об'єктом, коли його внутрішній стан змінилося у певному напрямку, яке може бути цікавим для інших об'єктів. Сигнали є публічно доступними функціями і можуть бути викликані будь-де, але рекомендується їх викликати тільки в класі, де вони були визначені, а також у його підкласах.
Коли сигнал викликаний, слот, підключений до нього, зазвичай виконується негайно, просто як нормальна функція. Це можливо тому, що механізм сигналів та слотів є незалежним від будь-яких циклів у GUI. Виконання коду слід викликати директивою emit , яка викликає всі слоти. У тих ситуаціях, коли використовуються черги підключень, код запускатиме сигнал, а слоти будуть виконані трохи пізніше.
Якщо кілька слотів підключено до одного сигналу, то слоти будуть викликані один за одним, у тому порядку, як вони підключені, коли буде викликаний сигнал.
Сигнали автоматично генеруються в moc і не повинні бути визначені в файлі .cpp, а також вони ніколи не повертають результат.
Примітка: За нашим досвідом сигнали та слоти є більш використовуваними, якщо вони не використовуються спеціальними типами. Якщо QScrollBar::valueChanged () є спеціальним типом, таким як гіпотетичний QScrollBar::Range, він може підключатися тільки до слоту розроблений спеціально для QScrollBar . Підключення різних віджетів разом може бути неможливим.
Слоти
Слот викликається тоді, коли сигнал, підключений до нього, був викликаний. Слоти є нормальною З + + функцією і може бути викликана; вони особливі тим, що до них підключаються сигнали.
Також слоти можуть виконуватися як звичайні функції, вони підкоряються звичайним правилам С++, коли викликаються безпосередньо. Однак, як слоти, вони можуть бути викликані іншими компонентами, незважаючи на рівень доступу, через сигнал-слотове підключення. Це означає, що сигнал випускається з одного з класів і може бути переданий до приватного слоту, який буде викликаний з цього незв'язаного класу.
Ви також можете визначити слоти як віртуальні, які ми знаходимо досить корисними в практиці.
У порівнянні з callback сигнали та слоти трохи повільніші через ту гнучкість, що вони надають, хоча відмінності в реальному додатку незначні. В основному виклик сигналу, який підключається до кількох слотів, приблизно в десять разів повільніше, ніж виклик не віртуальної функції. Це накладні витрати, через які потрібно знаходити об'єкт з'єднання шляхом перебору всіх слотів та сигналів та порівняння сигнатур для безпечного виклику функції-слота. Тоді як десять невіртуальних функцій викликаються меншими накладними витратами, ніж кілька операцій new і delete. Як тільки ви виконуєте рядок, вектор або список операція за сценою, що вимагає операцій new і delete, накладні витрати сигналів та слотів дуже малі порівняно з повною вартістю виклику функції. Це вірно, коли ви робите системний виклик в слот або опосередковано викликаєте більш ніж десять функцій. Простота та гнучкість механізму сигналів та слотів є неважливими накладними витратами, які ваші користувачі не помітять.
Зауважте, що інші бібліотеки визначають змінні, звані сигнали та слоти і можуть викликати помилки та попередження, коли компілюється програма, заснована на Qt. Вирішення цих проблем застосування директиви #undef для препроцесора.
Підключення сигналу до слота
До п'ятої версії Qt підключення сигналу до слота записувалося за допомогою макросів, тоді як у п'ятій версії став застосовуватися запис, заснований на покажчиках.
Запис із макросами:
connect(button, SIGNAL(clicked()), this, SLOT(slotButton()));
Запис на основі покажчиків:
connect(button, &QPushButton::clicked, this, &MainWindow::slotButton);
Перевага другого варіанту полягає в тому, що є можливість визначити невідповідність сигнатур та неправильне найменування слота або сигналу ще на стадії компіляції проекту, а не в процесі тестування програми.
Приклад використання сигналів та слотів
Наприклад використання сигналів і слотів було створено проект, що у головному вікні міститься три кнопки, до кожної з яких підключений слот, а ці слоти передають сигнал в один єдиний слот з номером натиснутої кнопки.
Структура проекта
Структура проекту За традицією ведення уроків, що склалася, додаю структуру проекту, яка абсолютно тривіальна і дефолтна до неподобства, що навіть не буду описувати входять до неї класи і файли.
mainwindow.h
Отже, дійство таке: три кнопки - три слоти, один сигнал на всі три кнопки, що подається в слотах кнопок і передає номер кнопки в один спільний слот, який видає повідомлення з номером кнопки.
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QPushButton> #include <QMessageBox> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); signals: void signalFromButton(int buttonID); // Сигнал для передачи номер нажатой кнопки private: Ui::MainWindow *ui; private slots: void slotButton1(); // Слоты-обработчики нажатий кнопок void slotButton2(); void slotButton3(); // Слоты вызывающий сообщение с номеро нажатой кнопки void slotMessage(int buttonID); }; #endif // MAINWINDOW_H
mainwindow.cpp
А в цьому файлі налаштовано логіку, описану в попередніх абзацах. Просто огляньте програмний код і переходьте до перегляду відео, там детально показано весь процес, продемонстровано програму, а також показано, що буде, якщо зробити написання коду з різними помилками.
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); /* Объявляем и инициализируем кнопки * */ QPushButton *but_1 = new QPushButton(this); QPushButton *but_2 = new QPushButton(this); QPushButton *but_3 = new QPushButton(this); /* Устанавливаем номера кнопок * */ but_1->setText("1"); but_2->setText("2"); but_3->setText("3"); /* Добавляем кнопки на слой с вертикальной ориентацией * */ ui->verticalLayout->addWidget(but_1); ui->verticalLayout->addWidget(but_2); ui->verticalLayout->addWidget(but_3); /* Подключаем к кнопкам индивидуальные слоты * */ connect(but_1, SIGNAL(clicked()), this, SLOT(slotButton1())); connect(but_2, SIGNAL(clicked()), this, SLOT(slotButton2())); connect(but_3, SIGNAL(clicked()), this, SLOT(slotButton3())); /* Подключаем сигнал с передачей номера кнопки к слоту вывода сообщения * */ connect(this, &MainWindow::signalFromButton, this, &MainWindow::slotMessage); } MainWindow::~MainWindow() { delete ui; } /* Слоты для обработки нажатия кнопок * */ void MainWindow::slotButton1() { emit signalFromButton(1); } void MainWindow::slotButton2() { emit signalFromButton(2); } void MainWindow::slotButton3() { emit signalFromButton(3); } /* Слоты вывода сообщения * */ void MainWindow::slotMessage(int buttonID) { QMessageBox::information(this, "Уведомление о нажатой кнопке", "Нажата кнопка под номером " + QString::number(buttonID)); }
Простите , а если я например хочу сделать по кнопке клик не левой кнопкой ,а правой или вообще засекать перемещение мыши на кнопке, то как будет выглядить строка
Я так понял что сиглалы виджетов заранее предопределены в библиотеке и в данном случае &QPushButton::clicked это событие из стандартной библиотеки Qt. В справке сказано, что всего есть четыре сигнала clicked, pressed, released,toggled. Может я не ту справку читаю, от того и странные вопросы.
Правильно всё читаете. Так и есть.
Если хотите отслеживать правую кнопку мыши, то вам следует наследоваться от QPushButton и переопределять методы
И переопределять поведение кнопки в случае правой, или левой кнопки мыши.
Извините, последний вопрос.
Написал маленький "проект", хочу стобы сцена ловила события мыши. Особенно когда мышь "отпустили". По сути это нужно для манипулирования с картинками на сцене.
Согласно справке за события на сцене отвечает QGraphicsSceneMouseEvent, однако попытка подружить слот и сигнал со сценой провалились, вот такой код привязки
не работает, компилятор ругается на объект scene, если поставить this то все конечно компилируется, но это же и не должно работать как надо
Вот весь код проекта.
mainwindow.h
mainwindow.cpp
Как можно привязать слот к сцене или либому другому виджету ? Может быть у вас есть видеоурок на этой теме? Особенно интересует обработка событий при столкновении двух пиксельных рисунков на сцене.
какая жуть.... если вам нужно, чтобы сцена что-то делала при кликах мыши, то методы нужно переопределять в наследованном классе сцены, а не запихивать методы из графической сцены в класс окна приложения.
Да я уже нашел ваш проект, с рисованием мышью и понял что это действительно жуть.
Думаю надо отдохнуть, уже 12 учусь.
Возник такой вопрос.
Разбираюсь с одной библиотекой. В ней применен паттерн Pimpl. И в коде есть вызовы метода connect(), но с помощью макросов:
Q_Q(QAmqpClient); - это, как понял, получение q-указателя на основной класс.
В нем сигнал таймера, который находится в приватном классе коннектится к слоту, который находится в основном классе.
Как этот коннект записать без макросов?
В основном классе _q_heartbeat() объявлен как Q_PRIVATE_SLOT(d_func(), void _q_heartbeat()). Т.е., на сколько понял, вызывается приватный метод _q_heartbeat приватного класса. Причем он находится в секции public, а не public slots.
Попытки написать что-то типа
приводят к ошибке компиляции.
ошибка: '_q_heartbeat' is not a member of 'QAmqpClient'
или
ошибка: expected primary-expression before '/' token
Без макросов никак. Приватные методы через указатели не коннектятся извне, что правильно, а вот макросы болт кладут на private и protected модификаторы.