Qt как написать свой сигнал

Время на прочтение
10 мин

Количество просмотров 242K

Сигналы и слоты используются для коммуникации между объектами. Механизм сигналов и слотов главная особенность Qt и вероятно та часть, которая отличаетcя от особенностей, предоставляемых другими фреймворками.

Введение

В программировании графического интерфейса, когда мы меняем один виджет, мы часто хотим что бы другой виджет получил об этом уведомление. В общем случае, мы хотим что бы объекты любого типа могла общаться с другими. Например, если пользователь нажимает кнопку Закрыть, мы вероятно хотим что бы была вызвана функция окна close().
Другие библиотеки добиваются такого рода общения используя обратный вызов. Обратный вызов это указатель на функцию, таким образом, если мы хотим что бы функция уведомила нас о каких-нибудь событиях, мы передаем указатель на другую функцию (обратновызываемую) этой функции. Функция в таком случае делает обратный вызов когда необходимо. Обратный вызов имеет два основных недостатка. Во-первых, он не является типобезопасным. Мы никогда не можем быть уверены что функция делает обратный вызов с корректными аргументами. Во-вторых, обратный вызов жестко связан с вызывающей его функцией, так как эта функция должна точно знать какой обратный вызов надо делать.

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

В Qt используется другая техника — сигналы и слоты. Сигнал вырабатывается когда происходит определенное событие. Слот это функция, которая вызывается в ответ на определенный сигнал. Виджеты Qt имеют много предопределенных сигналов и слотов, но мы всегда можем сделать дочерний класс и добавить наши сигналы и слоты в нем.

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

Механизм сигналов и слотов типобезопасен. Сигнатура сигнала должна совпадать с сигнатурой слота-получателя. (Фактически слот может иметь более короткую сигнатуру чем сигнал который он получает, так как он может игнорировать дополнительные аргументы). Так как сигнатуры сравнимы, компилятор может помочь нам обнаружить несовпадение типов. Сигналы и слоты слабо связаны. Класс, который вырабатывает сигнал не знает и не заботится о том, какие слоты его получат. Механизм сигналов и слотов Qt гарантирует, что если мы подключим сигнал к слоту, слот будет вызван с параметрами сигнала в нужное время. Сигналы и слоты могут принимать любое число аргументов любого типа. Они полностью типобезопасны.
Все классы, наследуемые от QObject или его дочерних классов (например, QWidget) могут содержать сигналы и слоты. Сигналы вырабатываются объектами когда они изменяют свое состояние так, что это может заинтересовать другие объекты. При этом он на знает и не заботится о том что у его сигнала может не быть получателя.
Слоты могут быть использованы для получения сигналов, но они так же нормальные функции-члены. Так же как объект не знает ничего о получателях своих сигналов, слот ничего не знает о сигналах, которые к нему подключены. Это гарантирует что полностью независимые компоненты могут быть созданы с помощью Qt.
Мы можем подключать к одному слоту столько сигналов, сколько захотим, также один сигнал может быть подключен к стольким слотам, сколько необходимо. Так же возможно подключать сигнал к другому сигналу (это вызовет выработку второго сигнала немедленно после появления первого).
Сигналы и слоты вместе составляют мощный механизм создания компонентов.

Небольшой пример

Описание класса на C++ может выглядеть вот так:

  1. class Counter
  2. {
  3. public:
  4.    Counter() { m_value = 0; }
  5.    int value() const { return m_value; }
  6.    void setValue(int value);
  7. private:
  8.    int m_value;
  9. };

* This source code was highlighted with Source Code Highlighter.

Класс, наследуемый от QObject будет выглядеть следующим образом:

  1. #include <QObject>
  2. class Counter : public QObject
  3. {
  4.    Q_OBJECT
  5. public:
  6.    Counter() { m_value = 0; }
  7.    int value() const { return m_value; }
  8. public slots:
  9.    void setValue(int value);
  10. signals:
  11.    void valueChanged(int newValue);
  12. private:
  13.    int m_value;
  14. };

* This source code was highlighted with Source Code Highlighter.

Класс, наследованный от QObject имеет то же самое внутреннее состояние и обеспечивает публичные методы для доступа к этому состоянию, но дополнительно у него есть поддержка для использования сигналов и слотов. Этот класс может сообщить внешнему миру что его состояние изменилось выработав сигнал valueChanged() и у него есть слот, в который другие объекты могут посылать сигналы.
Все классы, содержащие сигналы и слоты должны указывать макрос Q_OBJECT в начале их описания. Они также должны быть потомками (прямо или косвенно) QObject.
Слоты реализуются программистом. Возможная реализация слота Counter::setValue() выглядит следующим образом:

  1. void Counter::setValue(int value)
  2. {
  3.    if (value != m_value) {
  4.      m_value = value;
  5.      emit valueChanged(value);
  6.    }
  7. }

* This source code was highlighted with Source Code Highlighter.

Ключевое слово emit вырабатывает сигнал valueChanged() объекта с новым значением в качестве аргумента.
В следующем примере мы создаем два объекта типа Counter и соединяем сигнал valueChanged() первого со слотом setValue() второго используя статическую функцию QObject::connect():

  1.    Counter a, b;
  2.    QObject::connect(&a, SIGNAL(valueChanged(int)),
  3.            &b, SLOT(setValue(int)));
  4.    a.setValue(12);   // a.value() == 12, b.value() == 12
  5.    b.setValue(48);   // a.value() == 12, b.value() == 48

* This source code was highlighted with Source Code Highlighter.

Вызов a.setValue(12) вырабатывает сигнал valueChanged(12), который получит объект b в свой слот setValue() slot, т.е. будет вызвана функция b.setValue(12). Тогда b вырабатывает такой же сигнал valueChanged(), но так как он не подключен ни к одному слоту, это сигнал будет проигнорирован.
Отмечу что функция setValue() устанавливает новое значение и вырабатывает сигнал только есть value != m_value. Это предотвращает бесконечный цикл в случае кругового соединения (например, если бы b.valueChanged() был бы подключен к a.setValue()).
Сигнал вырабатывается для каждого соединения. Если соединение продублировать, два сигнала будут выработаны. Соединение всегда можно разорвать использовав функцию QObject::disconnect().
Приведенный выше пример показывает как объекты могут работать вместе без необходимости знать что-либо друг о друге. Что бы задействовать это, объекты должны быть соединены вместе и это может быть достигнуто простым вызовом функции QObject::connect() или с помощью свойства автоматического соединения программы uic.

Компилирование примера

Мета-объектный компилятор (meta-object compiler, moc) просматривает описание классов в файлах исходных кодов и генерирует код на C++, который инициализирует мета-объекты. Мета-объекты содержат имена все сигналов и слотов, так же как и указатели на эти функции.
Запуская программу moc для описания класса, содержащего сигналы и слоты, мы получаем файл исходных кодов, который должен быть скомпилирован и слинкован с другими объектными файлами приложения. При использовании qmake, правила для автоматического вызова moc будут добавлены в Makefile проекта.

Сигналы

Сигналы вырабатываются объектами когда они изменяют свое состояние так, что это может заинтересовать другие объекты. Только класс, который определяет сигнал или его потомки могут вырабатывать сигнал.
Когда сигнал вырабатывается, слот, к которому он подключен обычно выполняется немедленно, так же как и нормальный вызов процедуры. Когда это происходит, механизм сигналов и сигналов и слотов полностью независим от любого цикла событий графического интерфейса. Выполнение кода, следующего за выпуском сигнала произойдет сразу после выхода из всех слотов. Ситуация слегка отличается когда используются отложенные соединения (queued connections); в этом случае код после ключевого слова emit продолжает выполнение немедленно, а слоты будут выполнены позже.
Если несколько слотов подключены к одному сигналу, слоты будут выполнены один за другим в произвольном порядке после выработки сигнала.
Сигналы автоматически генерируются программой moc и не должны быть реализованы в исходном коде. Они могут не возвращать значение (т. е., используем тип void).
Замечание по поводу аргументов: опыт показывает, что сигналы и слоты легче повторно использовать при написании программ, если они не используют специальных типов. Например, если бы сигнал QScrollBar::valueChanged() использовал бы специальный тип вроде гипотетического QScrollBar::Range, он мог бы быть подключенным только к слотам, спроектированным специально для него.

Слоты

Слот вызывается когда вырабатывается сигнал, с которым он связан. Слот это обычная функция в C++ и может вызываться обычным способом; единственная его особенность, что с ним можно соединсять сигналы.
Так как слоты это нормальные функции-члены, они следуют обычным правилам C++ при прямом вызове. Тем не менее, как слоты, они могут быть вызваны любым компонентом, независимо от их уровней доступа, через соединение сигнал-слот. Это значит, что сигнал, выработаный объектом произвольного класса может вызвать защищенный (private) слот объекта несвязанного с ним класса.
Слоты так же можно объявлять виртуальными, что иногда бывает довольно удобно.
По сравнению с обратными вызовами, сигналы и слоты слегка медленнее из-за увеличенной гибкости, которую они обеспечивают, хотя разница для реальных приложений незаметна. В общем, выработка сигнала, который подключен к некоторым слотам, в среднем в 10 раз медленнее, чем вызов получателя напрямую, при вызове не виртуальной функции. Эти накладные расходы требуются для нахождения объекта, для безопасного перебора всех его соединений (т. е. проверка что последующий получатель не был уничтожен во время выпуска сигнала) и передачи любых параметров в общем виде. Хотя вызов десяти невиртуальных процедур может показаться дорогим, это менее затратно, чем, например, операция создания или удаления объекта. Пока мы создаем строку, вектор или список, что неявно требует создание объекта, затраты сигналов и слотов отвечают за очень маленькую долю в затратах среди всех вызовов процедур.
То же самое верно делаете ли вы системный вызов в слот или косвенно вызываете более десяти функций. На i586-500, мы можем вырабатывать около 2,000,000 сигналов в секунду, соединенных с одним слотом или 1,200,000 в секунду, при соединении в двумя слотами. Простота и гибкость механизма сигналов и слотов окупает дополнительные затраты, которые пользователь программы даже не заметит.
Следует заметить, что библиотеки, которые определяют переменные с именами signal или slot, могут вызывать предупреждения или ошибки компилятора при компиляции вместе с программой, написанной на Qt. Что бы решить данную проблему, необходимо убрать определение мешающегося символа препроцессора с помощью директивы #undef.

Метаобъектная информация

Метаобъект содержит дополнительную информацию, такую как имя объекта. Можно так же проверить наследует ли объект определенный класс, например:

  1.    if (widget->inherits("QAbstractButton")) {
  2.      QAbstractButton *button = static_cast<QAbstractButton *>(widget);
  3.      button->toggle();
  4.    }

* This source code was highlighted with Source Code Highlighter.

Метаобъектная информация также испльзуется qobject_cast<T>(), который похож на QObject::inherits(), но менее предрасположен к ошибкам:

  1.    if (QAbstractButton *button = qobject_cast<QAbstractButton *>(widget))
  2.      button->toggle();

* This source code was highlighted with Source Code Highlighter.

Реальный пример

Ниже приведен простой пример виджета с комментариями.

  1. #ifndef LCDNUMBER_H
  2. #define LCDNUMBER_H
  3. #include <QFrame>
  4. class LcdNumber : public QFrame
  5. {
  6.    Q_OBJECT

* This source code was highlighted with Source Code Highlighter.

Класс LcdNumber наследует QObject, который обладает большинством информации о сигналах и слотах через классы QFrame и QWidget. Он похож на встроенный виджет QLCDNumber.
Макрос Q_OBJECT указывает препроцессору объявить несколько функций-членов, которые будут реализованы программой moc; если при компилировании среди прочих будет появляется запись «undefined reference to vtable for LcdNumber», то скорее всего забыли запустить moc или добавить результат его работы в команду линковки.

  1. public:
  2.    LcdNumber(QWidget *parent = 0);

* This source code was highlighted with Source Code Highlighter.

Это не явно относится к moc’у, но если мы наследуем класс Qwidget, мы скорее всего захотим иметь аргумент parent (родитель) в конструкторе и передавать его конструктору родительского класса.
Некоторые деструкторы и функции-члены опущены здесь; moc игнорирует функции-члены.

  1. signals:
  2.    void overflow();

* This source code was highlighted with Source Code Highlighter.

LcdNumber вырабатывает сигнал когда его просят показать невозможное значение.
Если мы не заботимся о переполнении или знаем что оно не может произойти, мы может игнорировать этот сигнал, т.е. никуда его не подключать.
С другой стороны, если мы захотим вызвать две разные функции для реакции на эту ошибку, тогда просто подключаем эту функцию к двум разным слотам. Qt вызовет их оба (в произвольном порядке).

  1. public slots:
  2.    void display(int num);
  3.    void display(double num);
  4.    void display(const QString &str);
  5.    void setHexMode();
  6.    void setDecMode();
  7.    void setOctMode();
  8.    void setBinMode();
  9.    void setSmallDecimalPoint(bool point);
  10. };
  11. #endif

* This source code was highlighted with Source Code Highlighter.

Слоты это функции, используемые для получения информации об изменениях состояний других виджетов. LcdNumber использует их, как показано в коде выше, для установки отображаемого числа. Так как функция display() часть интерфейса класса с остальной программой, этот слот публичный.
Стоит отметить что функция display() перегружена. Qt выберет подходящую версию при соединении сигнала и слота. С обратным вызовом нам бы пришлось искать пять разных имен и контролировать типы самостоятельно.
Некоторые незначительные функции-члены были опущены в данном примере.

Продвинутое использование сигналов и слотов

В некоторых случаях может потребоваться информация об отправителе сигнала. Qt предоставляет функцию Qobject::sender(), которая возвращает указатель на объект, пославший сигнал.
Класс QSignalMapper необходим в ситуациях, когда много сигналов подключены к одному и тому же слоту, и этот слот должен реагировать на каждый сигнал по-разному.
Предположим что у нас есть три кнопки, которые определяют, какой файл мы хотим открыть: «Tax File», «Accounts File», or «Report File».
Что бы открыть нужный файл мы соединяем их сигнал QPushButton::clicked() со слотом readFile(). Теперь используем функцию класса QSignalMapper — setMapping() — для преобразования всех сигналов в объект QSignalMapper.

  1.    signalMapper = new QSignalMapper(this);
  2.    signalMapper->setMapping(taxFileButton, QString("taxfile.txt"));
  3.    signalMapper->setMapping(accountFileButton, QString("accountsfile.txt"));
  4.    signalMapper->setMapping(reportFileButton, QString("reportfile.txt"));
  5.    connect(taxFileButton, SIGNAL(clicked()),
  6.      signalMapper, SLOT (map()));
  7.    connect(accountFileButton, SIGNAL(clicked()),
  8.      signalMapper, SLOT (map()));
  9.    connect(reportFileButton, SIGNAL(clicked()),
  10.      signalMapper, SLOT (map()));

* This source code was highlighted with Source Code Highlighter.

Теперь подключаем сигнал mapped() к слоту readFile() в котором разные файлы будут открыты в зависимости от нажатой кнопки.

  1.   connect(signalMapper, SIGNAL(mapped(const QString &)),
  2.      this, SLOT(readFile(const QString &)));

* This source code was highlighted with Source Code Highlighter.

Использование Qt со сторонними сигналами и слотами

Можно использовать Qt со сторонним механизмом сигналов и слотов. Можно использовать несколько механизмов в одном проекте. Для этого надо добавить следующую строку в файл проекта (.pro):

CONFIG += no_keywords

Эта опция говорит Qt не определять ключевые слова moc’a — signals, slots, и emit, так как эти имена будут использованы строронней библиотекой, например, Boost. Что бы использовать сигналы и слоты Qt с установленным флагом no_keywords, надо просто заменить все использования ключевых слов moc’а Qt в исходных файлах на соотствующие макросы — Q_SIGNALS, Q_SLOTS, и Q_EMIT.

  1. 1. Введение
  2. 2. Сигналы и слоты
  3. 3. Сигналы
  4. 4. Слоты
  5. 5. Подключение сигнала к слоту
  6. 6. Пример использования сигналов и слотов
  7. 7. Структура проекта
  8. 8. mainwindow.h
  9. 9. mainwindow.cpp
  10. 10. Видеоурок

Сигналы и слоты используются для коммуникации между объектами в 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));
}

Видеоурок

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

Сигналы и слоты используются для связи между объектами. Механизм сигналов и слотов — это центральная особенность Qt и, вероятно, часть, которая больше всего отличается от функций, предоставляемых другими фреймворками. Сигналы и слоты стали возможными благодаря метаобъектной системе Qt .

Introduction

В программировании с графическим интерфейсом, когда мы изменяем один виджет, мы часто хотим, чтобы другой виджет был уведомлен. В более общем смысле мы хотим, чтобы объекты любого вида могли общаться друг с другом. Например, если пользователь нажимает кнопку « Закрыть », мы, вероятно, хотим, чтобы была вызвана функция закрытия окна ().

Другие наборы инструментов позволяют достичь такого рода связи с помощью обратных вызовов.Обратный вызов-это указатель на функцию,поэтому,если вы хотите,чтобы функция обработки уведомила вас о каком-то событии,вы передаете указатель на другую функцию (обратный вызов)в функцию обработки.Затем функция обработки вызывает обратный вызов,когда это необходимо.Хотя успешные фреймворки,использующие этот метод,действительно существуют,обратные вызовы могут быть не интуитивно понятными и могут страдать от проблем с обеспечением типовой корректности аргументов обратного вызова.

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

В Qt у нас есть альтернатива технике обратного вызова:Мы используем сигналы и слоты.Сигнал посылается,когда происходит определенное событие.Виджеты Qt имеют много предопределенных сигналов,но мы всегда можем использовать виджеты подкласса,чтобы добавлять к ним наши собственные сигналы.Слот-это функция,которая вызывается в ответ на определенный сигнал.Виджеты Qt имеют много предопределенных слотов,но это обычная практика для виджетов подклассов и добавления собственных слотов так,чтобы вы могли обрабатывать интересующие вас сигналы.

Механизм сигналов и слотов безопасен по типу:Подпись сигнала должна совпадать с подписью приемного слота.(Фактически,слот может иметь более короткую сигнатуру,чем сигнал,который он получает,так как он может игнорировать дополнительные аргументы).Так как сигнатуры совместимы,компилятор может помочь обнаружить несовпадение типов при использовании синтаксиса,основанного на указателях функций.Синтаксис SIGNAL и SLOT на основе строк обнаружит несовпадение типов во время выполнения.Сигналы и слоты слабо связаны:Класс,который излучает сигнал,не знает и не заботится о том,какие слоты принимают сигнал.Механизм сигналов и слотов Qt гарантирует,что если вы подключите сигнал к слоту,то слот будет вызван с параметрами сигнала в нужное время.Сигналы и слоты могут принимать любое количество аргументов любого типа.Они полностью безопасны для типов.

Все классы, которые наследуются от QObject или одного из его подклассов (например, QWidget ), могут содержать сигналы и слоты. Сигналы излучаются объектами, когда они меняют свое состояние таким образом, чтобы это могло быть интересно другим объектам. Это все, что объект делает для общения. Он не знает и не заботится о том, получает ли что-нибудь излучаемые им сигналы. Это истинная инкапсуляция информации, гарантирующая, что объект может использоваться в качестве программного компонента.

Слоты могут использоваться для приема сигналов,но они также являются обычными функциями членов.Подобно тому,как объект не знает,принимает ли что-либо его сигналы,слот не знает,есть ли у него подключенные к нему сигналы.Это гарантирует,что действительно независимые компоненты могут быть созданы с Qt.

Вы можете подключить столько сигналов,сколько вы хотите к одному слоту,и сигнал может быть подключен к столько слотов,сколько вам нужно.Можно даже подключить сигнал непосредственно к другому сигналу.(При этом второй сигнал будет подаваться сразу после первого).

Вместе сигналы и слоты составляют мощный механизм компонентного программирования.

Signals

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

Когда сигнал испускается, слоты, связанные с ним, обычно выполняются немедленно, как и обычный вызов функции. Когда это происходит, механизм сигналов и слотов полностью независим от какого-либо цикла обработки событий GUI. Выполнение кода, следующего за оператором emit , произойдет после возврата всех слотов. Ситуация немного отличается при использовании соединений с очередями ; в таком случае код, следующий за ключевым словом emit , будет продолжен немедленно, а слоты будут выполнены позже.

Если к одному сигналу подключено несколько слотов,то при подаче сигнала слоты будут выполняться один за другим,в порядке их подключения.

Сигналы автоматически генерируются moc и не должны быть реализованы в файле .cpp . Они никогда не могут иметь возвращаемые типы (т.е. использовать void ).

Примечание об аргументах: наш опыт показывает, что сигналы и слоты более пригодны для многократного использования, если они не используют специальные типы. Если бы QScrollBar :: valueChanged () использовал специальный тип, такой как гипотетический QScrollBar :: Range, он мог бы быть подключен только к слотам, разработанным специально для QScrollBar . Соединить разные виджеты ввода вместе было бы невозможно.

Slots

Слот вызывается при поступлении подключенного к нему сигнала.Слоты являются обычными функциями С++и могут вызываться нормально;их единственной особенностью является возможность подключения к ним сигналов.

Так как слоты являются обычными функциями-членами,то при прямом вызове они следуют обычным правилам C++.Однако,будучи слотами,они могут быть вызваны любым компонентом,независимо от уровня его доступа,через соединение сигнал-слот.Это означает,что сигнал,исходящий от экземпляра произвольного класса,может привести к вызову приватного слота в экземпляре несвязанного класса.

Вы также можете определить слоты,которые будут виртуальными,что мы нашли довольно полезным на практике.

По сравнению с обратными вызовами сигналы и слоты работают немного медленнее из-за большей гибкости, которую они обеспечивают, хотя для реальных приложений разница незначительна. В общем, передача сигнала, подключенного к некоторым слотам, примерно в десять раз медленнее, чем прямой вызов приемников с вызовами не виртуальных функций. Это накладные расходы, необходимые для определения местоположения объекта соединения, безопасного перебора всех соединений (т. Е. Проверки того, что последующие приемники не были уничтожены во время передачи), а также для сортировки любых параметров обычным образом. Хотя десять вызовов невиртуальных функций могут показаться большим количеством, это намного меньше накладных расходов, чем любое new или delete операция, например. Как только вы выполняете операцию со строкой, вектором или списком, которая за сценой требует new или delete , накладные расходы на сигналы и слоты несут ответственность только за очень небольшую часть полной стоимости вызова функции. То же самое верно всякий раз, когда вы выполняете системный вызов в слоте; или косвенно вызывать более десяти функций. Простота и гибкость механизма сигналов и слотов стоит накладных расходов, которые ваши пользователи даже не заметят.

Обратите внимание, что другие библиотеки, которые определяют переменные, называемые signals или slots могут вызывать предупреждения и ошибки компилятора при компиляции вместе с приложением на основе Qt. Чтобы решить эту проблему, #undef ошибочный символ препроцессора.

Маленький пример

Минимальное объявление класса C++может быть прочитано:

class Counter
{
public:
    Counter() { m_value = 0; }

    int value() const { return m_value; }
    void setValue(int value);

private:
    int m_value;
};

Небольшой класс на основе QObject может читать:

#include <QObject>

class Counter : public QObject
{
    Q_OBJECT

public:
    Counter() { m_value = 0; }

    int value() const { return m_value; }

public slots:
    void setValue(int value);

signals:
    void valueChanged(int newValue);

private:
    int m_value;
};

Версия на основе QObject имеет такое же внутреннее состояние и предоставляет публичные методы для доступа к состоянию, но, кроме того, она поддерживает программирование компонентов с использованием сигналов и слотов. Этот класс может сообщить внешнему миру, что его состояние изменилось, испуская сигнал valueChanged() , и у него есть слот, в который другие объекты могут отправлять сигналы.

Все классы, содержащие сигналы или слоты, должны упоминать Q_OBJECT в верхней части своего объявления. Они также должны быть производными (прямо или косвенно) от QObject .

Слоты реализованы прикладным программистом. Вот возможная реализация слота Counter::setValue() :

void Counter::setValue(int value)
{
    if (value != m_value) {
        m_value = value;
        emit valueChanged(value);
    }
}

emit линии излучают сигнал valueChanged() из объекта, с новым значением в качестве аргумента.

В следующем фрагменте кода мы создаем два объекта Counter и соединяем сигнал valueChanged()первого объекта со valueChanged() setValue() объекта, используя QObject::connect ():

    Counter a, b;
    QObject::connect(&a, &Counter::valueChanged,
                     &b, &Counter::setValue);

    a.setValue(12);     
    b.setValue(48);     

Вызов a.setValue(12) заставляет a испустить valueChanged(12) , который b получит в своем setValue() , то есть b.setValue(12) . Тогда b излучает тот же valueChanged() сигнала, но так как никто не был подключен к b «ы valueChanged() сигнала, сигнал игнорируется.

Обратите внимание, что setValue() устанавливает значение и излучает сигнал, только если value != m_value . Это предотвращает бесконечный цикл в случае циклических подключений (например, если b.valueChanged() был подключен к a.setValue() ).

По умолчанию для каждого соединения, которое вы устанавливаете, выдается сигнал; два сигнала испускаются для дублирующих соединений. Вы можете разорвать все эти соединения одним вызовом разъединения (). Если вы передаете Qt::UniqueConnection type, соединение будет установлено только в том случае, если это не дубликат. Если уже есть дубликат (точно такой же сигнал в один и тот же слот на тех же объектах), соединение не удастся и connect вернет false .

Этот пример показывает, что объекты могут работать вместе без необходимости знать какую-либо информацию друг о друге. Для этого объекты должны быть соединены только вместе, и это может быть достигнуто с помощью некоторых простых вызовов функции QObject::connect () или с помощью функции автоматического соединения uic .

Реальный пример

Ниже приведен пример заголовка простого класса виджетов без функций-членов.Цель-показать,как можно использовать сигналы и слоты в собственных приложениях.

#ifndef LCDNUMBER_H
#define LCDNUMBER_H

#include <QFrame>

class LcdNumber : public QFrame
{
    Q_OBJECT

LcdNumber наследует QObject , который имеет большую часть информации о сигнальных слотах, через QFrame и QWidget . Он чем-то похож на встроенный виджет QLCDNumber .

Q_OBJECT макро расширяется препроцессором , чтобы объявить несколько функций членов, которые реализуются в moc ; если вы получаете ошибки компилятора в строках «неопределенная ссылка на vtable для LcdNumber », вы, вероятно, забыли запустить moc или включить вывод moc в команду ссылки.

public:
    LcdNumber(QWidget *parent = nullptr);

signals:
    void overflow();

После конструктора класса и public членов мы объявляем signals класса . Класс LcdNumber излучает сигнал overflow() , когда его просят показать невозможное значение.

Если вас не волнует переполнение или вы знаете, что переполнение не может произойти, вы можете игнорировать сигнал overflow() , т.е. не подключать его ни к какому слоту.

Если,с другой стороны,вы хотите вызвать две различные функции ошибок,когда номер переполняется,просто подключите сигнал в два разных слота.Qt будет вызывать оба (в порядке их соединения).

public slots:
    void display(int num);
    void display(double num);
    void display(const QString &str);
    void setHexMode();
    void setDecMode();
    void setOctMode();
    void setBinMode();
    void setSmallDecimalPoint(bool point);
};

#endif

Слот — это функция приема, используемая для получения информации об изменениях состояния в других виджетах. LcdNumber использует его, как показывает приведенный выше код, для установки отображаемого числа. Поскольку display() является частью интерфейса класса с остальной частью программы, слот является общедоступным.

Некоторые из программ-примеров соединяют сигнал valueChanged () QScrollBar со слотом display() , так что номер ЖК-дисплея постоянно показывает значение полосы прокрутки.

Обратите внимание, что display() перегружен; Qt выберет соответствующую версию, когда вы подключите сигнал к слоту. С обратными вызовами вам нужно будет найти пять разных имен и самостоятельно отслеживать типы.

Сигналы и слоты с аргументами по умолчанию

Сигнатуры сигналов и слотов могут содержать аргументы, и аргументы могут иметь значения по умолчанию. Рассмотрим QObject::destroyed ():

void destroyed(QObject* = nullptr);

Когда QObject удаляется, он испускает этот сигнал QObject::destroyed (). Мы хотим поймать этот сигнал везде, где у нас может быть оборванная ссылка на удаленный QObject , чтобы мы могли его очистить. Подходящей сигнатурой слота может быть:

void objectDestroyed(QObject* obj = nullptr);

Для подключения сигнала к слоту мы используем QObject::connect (). Существует несколько способов подключения сигнала и слотов. Первый заключается в использовании указателей на функции:

connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);

Есть несколько преимуществ использования QObject::connect () с указателями на функции. Во-первых, это позволяет компилятору проверить совместимость аргументов сигнала с аргументами слота. Аргументы также могут быть неявно преобразованы компилятором, если это необходимо.

Вы также можете подключиться к functors или C++11 lambdas:

connect(sender, &QObject::destroyed, this, [=](){ this->m_objects.remove(sender); });

В обоих случаях мы предоставляемthisв качестве контекста в вызове connect().Контекстный объект предоставляет информацию о том,в каком потоке должен быть выполнен приемник.Это важно,так как предоставление контекста гарантирует,что приемник будет выполнен в потоке контекста.

лямбда будет отключена,когда отправитель или контекст будут уничтожены.Вы должны позаботиться о том,чтобы все объекты,используемые внутри воронки,оставались живы при подаче сигнала.

Другой способ подключить сигнал к слоту — использовать QObject::connect () и макросы SIGNAL и SLOT .Правило о том, следует ли включать аргументы в макросы SIGNAL() и SLOT() , если аргументы имеют значения по умолчанию, заключается в том, что подпись, передаваемая в макрос SIGNAL() , должнаnotимеют меньше аргументов, чем сигнатура, переданная макросу SLOT() .

Все это сработает:

connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(Qbject*)));
connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed()));
connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed()));

Но это не сработает:

connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed(QObject*)));

… потому что слот будет ожидать объект QObject, который сигнал не отправит. Это соединение сообщит об ошибке выполнения.

Обратите внимание, что аргументы signal и slot не проверяются компилятором при использовании этой перегрузки QObject::connect ().

Расширенное использование сигналов и слотов

Для случаев, когда вам может потребоваться информация об отправителе сигнала, Qt предоставляет функцию QObject::sender (), которая возвращает указатель на объект, отправивший сигнал.

Выражения Lambda-это удобный способ передачи пользовательских аргументов в слот:

connect(action, &QAction::triggered, engine,
        [=]() { engine->processAction(action->text()); });

Использование Qt со сторонними сигналами и слотами

Возможно использовать Qt с сигналом/слотовым механизмом стороннего производителя.Вы даже можете использовать оба механизма в одном проекте.Просто добавьте следующую строку в ваш файл проекта qmake (.pro).

CONFIG += no_keywords

Он говорит Qt не определять ключевые слова moc signal emit , slots что signals имена будут использоваться сторонней библиотекой, например Boost. Затем, чтобы продолжить использование сигналов и слотов Qt с флагом no_keywords , просто замените все случаи использования ключевых слов Qt moc в ваших источниках соответствующими макросами Qt Q_SIGNALS (или Q_SIGNAL ), Q_SLOTS (или Q_SLOT ) и Q_EMIT .

См. также QLCDNumber , QObject::connect (), Пример цифровых часов , Пример Tetrix , Система метаобъектов и Система свойств Qt .


Qt

6.2

  • Создание клиентов OPC UA с поддержкой безопасности

    Одной из основных особенностей OPC UA является поддержка безопасности,что означает,что мы получаем криптографически зашифрованный и подписанный протокол,авторизацию аутентификации пользователя.

  • Неявно разделяемые классы

    Эти классы Qt Core обеспечивают безопасный и эффективный способ совместного использования манипулирования данными неявным образом QBitArray Массив битов QBitmap Монохромный (глубина 1 бит)

  • Различия между шнуровыми и функировочными соединениями

    Начиная с Qt 5.0,предлагаются два различных способа записи соединений сигнальных слотов в синтаксисе на основе строк и на основе векторов.

  • Решения для разработки приложений

    Как разработчик,вы,скорее всего,имеете любимую операционную систему,набор инструментов и язык.

Прогер

632 / 263 / 15

Регистрация: 17.11.2010

Сообщений: 1,371

Записей в блоге: 2

1

как создать только сигнал?

09.04.2012, 18:48. Показов 12461. Ответов 6


Ето опять Я…

на пример хочу отправить уже созданному слоту мой сигнал, как ето надо сделать?

на пример у spinBox же есть слот setValue, как создать сигнал чтобы можно было отправить его у setValue? ? ?

Спасибо заранее…



0



Lord_Voodoo

Супер-модератор

8781 / 2532 / 144

Регистрация: 07.03.2007

Сообщений: 11,873

09.04.2012, 18:54

2

в хидере пишем:

C++ (Qt)
1
2
signals:
  void MySetValueSignal(int);

в сырцах, где надо послать сигнал, пишем:

C++ (Qt)
1
emit MySetValueSignal(value);

ну и не забыть законнектить:

C++ (Qt)
1
connect(this, SIGNAL(MySetValueSignal(int)), spinBox, SLOT(setValue(int)));



1



Programmer.

Прогер

632 / 263 / 15

Регистрация: 17.11.2010

Сообщений: 1,371

Записей в блоге: 2

09.04.2012, 19:21

 [ТС]

3

Lord_Voodoo,
спасибо огромное!
вот делаю так, после етого, спинбокс же дольжен изминится?
.h

C++ (Qt)
1
2
3
4
public:
void sendValue(int value);
signals: 
void valueChange(int);

.cpp

C++ (Qt)
1
2
3
4
5
void qet::sendValue(int value)
{
value = 10;
  emit valueChange(value);
}

а после етого во так:

C++ (Qt)
1
2
3
QObject::connect(this,SIGNAL(valueChange(int)),
                 ui->spinBox,
                 SLOT(setValue(int)));

нечего в spinBox не изменяется… как исправить?

спасибо заранее..



0



Paporotnik

385 / 229 / 12

Регистрация: 06.07.2011

Сообщений: 512

09.04.2012, 19:48

4

откройте книжку и почитайте.
сигналы соединяются только со слотами или другими сигналами. слот = метод класса, просто со спецификаторами:

C++ (Qt)
1
2
3
public slots:
или 
private slots:

в реализации слота (то есть в его теле) пишите код, который изменяет значение счетчика.
затем что-то вроде

C++ (Qt)
1
connect(pSender,SIGNAL(changeSpinbox(int)),pReceiver,SLOT(changeSpinbox(int)));

Теперь когда сигнал будет вызван строкой

C++ (Qt)
1
emit changeSpinbox(1);

Изменится и показание счетчика.



1



Programmer.

Прогер

632 / 263 / 15

Регистрация: 17.11.2010

Сообщений: 1,371

Записей в блоге: 2

10.04.2012, 01:23

 [ТС]

5

Paporotnik,

Цитата
Сообщение от Paporotnik
Посмотреть сообщение

откройте книжку и почитайте.

Спасибо за совет, но мне лучше методом научного ТЫК-а, много узнаеш на ощыбках

П.С и книгу тоже читаю…

Цитата
Сообщение от Paporotnik
Посмотреть сообщение

сигналы соединяются только со слотами или другими сигналами. слот = метод класса, просто со спецификаторами:

Ето я тоже знаю…

Цитата
Сообщение от Paporotnik
Посмотреть сообщение

в реализации слота (то есть в его теле) пишите код, который изменяет значение счетчика.

Типа етого?

C++ (Qt)
1
ui->spinBox->setValue(value);

У меня вот такой код:
.cpp

C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "qet.h"
#include "ui_untitled.h"
qet::qet(QWidget *parent):QMainWindow(parent),
        ui(new Ui::qet)
{
QObject::connect(this,SIGNAL(valueChange(int)),
                 ui->spinBox,
                 SLOT(setValue(int)));
}
void qet::sendValue(int value)
{
value = 10;
 ui->spinBox->setValue(value);
  emit valueChange(value);
}
qet::~qet()
{
delete ui;
}

Вот .h

C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef QET_H
#define QET_H
#include <QtGui/QMainWindow>
#include <ui_untitled.h>
namespace Ui{
class qet;
}
class qet : public QMainWindow, Ui::qet
{
 Q_OBJECT
  public: 
  explicit qet(QWidget *parent=0);
          ~qet();
public slots:  
void sendValue(int value); 
private: 
   Ui::qet *ui;
signals: 
    void valueChange(int);
};
#endif //QET

Как его изменить чтоб в spinBox изменилос число?



1



9 / 9 / 0

Регистрация: 10.11.2011

Сообщений: 241

10.04.2012, 02:56

6

Programmer., судя по наследованию ваш класс — окно. Вы уверены что в нём есть сигнал valueChanged ? Я сомневаюсь что в окне есть какоето значение которое меняется.



0



Programmer.

Прогер

632 / 263 / 15

Регистрация: 17.11.2010

Сообщений: 1,371

Записей в блоге: 2

10.04.2012, 09:04

 [ТС]

7

OrmaJever,

Ето я делаю с Qt Designer и потом ручную…

Цитата
Сообщение от OrmaJever
Посмотреть сообщение

. Вы уверены что в нём есть сигнал valueChanged ? Я сомневаюсь что в окне есть какоето значение которое меняется.

Как ето понять?

Добавлено через 42 минуты
OrmaJever,
в нем обьект: spinbox

Добавлено через 4 часа 6 минут
Сделал спасибо!!!

надо было указать через «this»

C++ (Qt)
1
this->valueChange(10);

после connect- а

Спасибо всем, шас спокоино поиду спать!



1



Когда я начинал изучать библиотеку Qt, весьма полезным показался пример виджета, выводящего бегущую строку. Подобный пример описан в настоящей статье, на нем мы разберем:

  1. механизм сигналов и слотов Qt;
  2. организацию объектов Qt в древовидную структуру, обеспечивающую автоматическое освобождение памяти при разрушении родительского объекта;
  3. обработку событий в Qt.

В результате у нас получится создать свой собственный виджет и связать его со стандартными элементами управления.

castom_widget_in_Qt

Собственный виджет Qt

Класс QObject

В библиотеке Qt есть множество самых различных классов, многие из них являются потомками класса QObject, который позволяет:

  • использовать механизм сигналов и слотов Qt;
  • организовывать объекты в древовидную структуру;
  • дополняет объект информацией о типе (мета-объектной информацией);
  • перемещать объект в отдельный поток [1];
  • обрабатывать события (QEvent);
  • использовать встроенный таймер QObject;

Механизм сигналов и слотов Qt

Механизм сигналов и слотов позволяет объектам обмениваться сообщениями. Например, при изменении текста внутри поля ввода (QLineEdit) генерируется сигнал textChanged, который может обработать любой другой объект, при этом его функция-обработчик называется слотом. Для того, чтобы при возникновении некоторого сигнала управление получил некоторый слот, достаточно соединить их функцией connect.

connect(speedSpinbox, SIGNAL(valueChanged(int)), runline, SLOT(setSpeed(int)));
connect(textLine, SIGNAL(textChanged(QString)), runline, SLOT(setString(QString)));

В приведенном примере при генерацией сигнала valueChanged объекта speedSpinBox активируется слот setSpeed объекта runline.

Один сигнал может быть соединен сразу с несколькими слотами. Механизм сигналов и слотов позволяет общаться объектам, находящихся в различных потоках. Последним аргументом метода connect может быть тип соединения, позволяющий переключать асинхронную и синхронную обработку сигналов. По умолчанию используется соединение типа Qt::AutoConnection, означающее асинхронную обработку в случае если источник и обработчик сигнала находятся в одном потоке и синхронную (сигналы накапливаются в очереди потока), в противном случае. Для использования данного механизма в начале описания класса должен стоять макрос Q_OBJECT.

class RunLine : public QLabel {
  Q_OBJECT
public:
  RunLine(QWidget *parent = 0);
public slots:
  void setString(const QString string);
  void setSpeed(const int speed);
protected:
  virtual void timerEvent(QTimerEvent*);

  int m_shift, m_timerId;
  QString m_string;
};

Из листинга видно, что наша бегущая строка имеет 2 слота, для изменения текста и скорости движения строки, но не генерирует ни одного сигнала.

Автоматическая сборка мусора в Qt

При использовании библиотеки Qt объекты часто формируют древовидную структуру с отношениями «родитель-потомок». Для любого объекта можно назначить родителя вызовом метода setParent, получить список дочерних объектов методом children или, допустим, выполнять поиск среди дочерних объектов. Обычно родительский объект передается в качестве параметра конструктора дочернему объекту.

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

В нашем примере создается главное окно (класс MainWidget), которое включает в себя объекты классов QSpinBox, QLineEdit и RunLine. Очевидно, что вложенный QLineEdit не имеет смысла «оставлять живым» после того, как главное окно будет закрыто, т.е. время жизни поля ввода не больше чем время жизни главного окна, поэтому главное окно может быть родителем для вложенных в него объектов. Не всегда ситуация так очевидна, при установлении отношений «родитель-потомок» в Qt надо руководствоваться именно временем жизни объектов.

MainWidget::MainWidget(QWidget *parent) : QWidget(parent) {
  QGridLayout *layout(new QGridLayout(this));
  QSpinBox *speedSpinbox(new QSpinBox(this));
  QLineEdit *textLine(new QLineEdit(this));
  RunLine *runline(new RunLine(this));
// ...
}

UML-class_diagram_Qt

Диаграмма используемых классов Qt

Методы findChild и findChildren позволяют находить среди дочерних объектов, такие, которые приводятся к требуемому типу и имеют заданное имя (имя может быть задано методом setObjectName). Информация о типе и имени объекта называется мета-объектной.

Мета-объектная информация используется также, например при вызове функции qobject_cast, проверяющий принадлежность объекта к определенному типу.

Компилятор c++ ничего не знает об именах объектов (классы в c++ устроены проще), сигналах, слотах, событиях и т.п. В связи с этим, всякий раз, когда вы отправляете свою Qt-программу на компиляцию, сначала она подается на вход мета-объектного компилятора (MOC), который инициирует мета-объекты и генерирует код, понятный обычному компилятору.

Обработка событий в Qt

После работы с другими библиотеками, такими как WinAPI может быть трудно понять разницу между сигналом и событием, она кроется в источнике воздействия:

  • если мы нажимаем на кнопку, то кнопка обрабатывает соответствующее событие (QKeyEvent/QMouseEvent,QTouchEvent, смотря чем нажали), при этом кнопка генерирует сигнал;
  • если мы провели мышью над окном, то элементы под мышью получали об этом событие QHoverEvent, кроме того, обрабатывалось событие QPaintEvent, т.к. область окна под мышью перерисовывалась.

Из этих примеров понятно, что сигнал должен генерируется объектом при изменении его внутреннего состояния, а событие всегда приходит из внешней среды. Источником события может быть указатель, колесико и кнопки мыши, кнопки клавиатуры, тачскрин, сетевой адаптер или, например, системный таймер.

При поступлении события в приложение, оно распространяется до тех пор, пока не будет обработано (если какой-либо объект обработал событие, он должен вызвать у него метод accept чтобы оно не распространялось дальше).

Например, мы щелкнули мышью над кнопкой спинбокса, при этом событие получает сначала MainWidget, но т.к. обработать он его не может, то передает его тому дочернему элементу, над которым был произведен щелчок (по спинбоксу), спинбокс передает событие кнопке, которая его обрабатывает и вызывает метод accept, генерируя сигнал clicked.

Пример отражает суть механизма обработки событий, однако, любое из промежуточных звеньев тоже могло обработать событие (при этом как выполнив для него accept, прервав обработку, так и передать событие обработанное дальше). Для реализации такого поведения может быть использован виртуальный метод event. Кроме того, объект может генерировать события методами sendEvent и postEvent.

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

Обработка событий таймера

В библиотеке Qt есть 3 вида таймера — встроенный в QObject, QTimer и QBasicTimer. Все эти таймеры по умолчанию запускаются в том потоке, в котором были созданы, т.е. они не могут сработать пока выполняется какой-либо другой код в текущем потоке, а значит, их точность зависит от загруженности и зернистости (granularity, трудоемкости функций) потока. Если вам нужна точность, таймер стоит перенести в отдельный поток.

Наиболее удобным из них является QTimer, именно его рекомендует использовать официальная документация — он с заданной периодичностью генерирует сигналы. Более подробное описание и пример использования QTimer можно найти в соседней статье [2].

QBasicTimer является низкоуровневым, периодическим (генерирует события до тех пор, пока не будет остановлен) таймером. Весь интерфейс таймера составляют 4 метода — isActive возвращает true если таймер работает, timerId возвращает идентификатор таймера (идентификатор нужен в том случае, если объект-обработчик принимает события от нескольких таймеров и должен их различать). Методы stop и start останавливают и запускают таймер соответственно. Метод start в качестве аргументов принимает период в миллисекундах, с которым должны генерироваться события и указатель на объект-обработчик событий, для которого должен быть перегружен метод timerEvent.

Интерфейс таймеров, встроенных в QObject очень поход на QBasicTimer. Они тоже являются периодическими и порождают события, а не сигналы. Запуск таймера осуществляется методом startTimer, который принимает периодичность и возвращает идентификатор таймера. Таймер работает и порождает события до тех пор, пока не будет уничтожен методом killTimer (принимающий идентификатор таймера в качестве аргумента).

Наша бегущая строка должна перемещаться через равные промежутки времени, для выполнения этого требования удобнее всего использовать таймер. Мы применим встроенный таймер для того, чтобы привести пример обработки событий в Qt.

void RunLine::timerEvent(QTimerEvent *) {
  const int length = m_string.length();

  if(++m_shift >= length)
    m_shift = 0;

  setText(m_string.right(m_shift) + m_string.left(length - m_shift));
}

void RunLine::setSpeed(const int speed) {
  if (m_timerId)
    killTimer(m_timerId);
  m_timerId = 0;

  if (speed < 0)
    return;

  if (speed)
    m_timerId = startTimer(1000/speed);
}

void RunLine::setString(const QString string) {
  m_string = string;
  m_shift = 0;
  setText(m_string);
}

Мы не используем объект QTimerEvent в методе timerEvent, т.к. у нас работает лишь один таймер (нам не требуется идентифицировать источник события). Скорость движения строки у нас задается методом setSpeed, однако мы могли избавить от этого метода при использовании внешнего QBasicTimer — достаточно было бы указать объект RunLine в качестве адресата событий.

Создание пользовательского интерфейса Qt

Класс QWidget является базовым для всех виджетов (элементов управления). Этот класс содержит множество полей и методов, например, методы изменения размера или перемещения объекта. Виджеты могут вкладываться друг в друга (визуально, а не с установкой отношения «родитель-потомок», рассмотренного выше), при этом виджет-контейнер может использовать менеджер размещения (QLayout).

В листинге конструктора MainWidget представленного выше уже использовали менеджер QGridLayout, позволяющий размещать вложенные виджеты по сетке, кроме него может использоваться QVBoxLayout или QHBoxLayout (для размещения виджетов в линию по вертикали или горизонтали соответственно). Менеджер размещения управляет размерами и положением вложенных виджетов при изменении размера виджета-контейнера. Кроме того, менеджеры размещения могут вкладываться друг в друга. Размещать виджеты удобно визуально, мышью с использованием QtDesigner [2], но в этой статье мы вызываем addWidget явно. Во многих случаях явное использование addWidget оказывается единственным возможным, например если бы мы разрабатывали игру «Сапер» и размер игрового поля был бы заранее не известен.

layout->addWidget(textLine, 1, 1, 1, 1);
layout->addWidget(speedSpinbox, 1, 2, 1, 1);
layout->addWidget(runline, 2, 1, 1, 2);

Метод addWidget класса QGridLayout принимает в качестве второго и третьего аргумента координаты верхней левой ячейки таблицы, в которую требуется поместить виджет, указанный в первом аргументе. Последние два аргумента указывают количество ячеек по вертикали и горизонтали, которое займет данный виджет.

На приведенных выше листингах вы могли заметить, что класс бегущей строки наследует QLabel и вызывает его метод setText. Класс QLabel предназначен для вывода текста или изображений, является наследником класса QFrame. QLabel позволяет использовать HTML теги для оформления содержимого, однако, в текущей статье используется лишь метод setText() для задания выводимого текста.

Класс QFrame расширяет возможности QWidget в плане отображения рамки вокруг виджета и является базовым для элементов управления, нуждающихся в особой рамке. Наследниками класса QFrame являются, например, виджет панели инструментов (QToolBox), виджет видовой прокрутки (QAbstractScrollArea) или виджет вывода текста/изображений (QLabel).

Бегущая строка Qt. исходный код.

Рекомендуемая литература:

  1. Многопоточный сервер Qt. Пул потоков. Паттерн Decorator. Пример перемещения объекта в поток [Электронный ресурс] – режим доступа: https://pro-prof.com/archives/1390. Дата обращения: 06.05.2016.
  2. Собственные виджеты в Qt Designer. Описание и пример использования QTimer [Электронный ресурс] – режим доступа: https://pro-prof.com/archives/958. Дата обращения: 06.05.2016.
  3. Разработка игры «Сапер». Пример использования QGridLayout [Электронный ресурс] – режим доступа: https://pro-prof.com/archives/887. Дата обращения: 06.05.2016.
  4. Официальная документация по библиотеке Qt [Электронный ресурс] – режим доступа: https://doc.qt.io/. Дата обращения: 06.05.2016.
  5. Библиотека Qt. Уроки [Электронный ресурс] – режим доступа: https://evileg.com/ru/. Дата обращения: 28.11.2019.

Introduction

Signals and slots are used for communication between objects. The signals and slots mechanism is a central feature of Qt. In GUI programming, when we change one widget, we often want another widget to be notified. More generally, we want objects of any kind to be able to communicate with one another. Signals are emitted by objects when they change their state in a way that may be interesting to other objects. Slots can be used for receiving signals, but they are also normal member functions.

Official documentation on this topic can be found here.

A Small Example

Signals and slots are used for communication between objects. The signals and slots mechanism is a central feature of Qt and probably the part that differs most from the features provided by other frameworks.

The minimal example requires a class with one signal, one slot and one connection:

counter.h

#ifndef COUNTER_H
#define COUNTER_H

#include <QWidget>
#include <QDebug>

class Counter : public QWidget
{
    /*
     * All classes that contain signals or slots must mention Q_OBJECT
     * at the top of their declaration.
     * They must also derive (directly or indirectly) from QObject.
     */
    Q_OBJECT

public:
    Counter (QWidget *parent = 0): QWidget(parent)
    {
            m_value = 0;

            /*
             * The most important line: connect the signal to the slot.
             */
            connect(this, &Counter::valueChanged, this, &Counter::printvalue);
    }

    void setValue(int value)
    {
        if (value != m_value) {
            m_value = value;
            /*
             * The emit line emits the signal valueChanged() from
             * the object, with the new value as argument.
             */
            emit valueChanged(m_value);
        }
    }

public slots:
    void printValue(int value)
    {
        qDebug() << "new value: " << value;
    }

signals:
    void valueChanged(int newValue);

private:
    int m_value;

};

#endif

The main sets a new value. We can check how the slot is called, printing the value.

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

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    Counter counter;
    counter.setValue(10);
    counter.show();

    return app.exec();
}

Finally, our project file:

SOURCES   = 
            main.cpp
HEADERS   = 
            counter.h

The new Qt5 connection syntax

The conventional connect syntax that uses SIGNAL and SLOT macros works entirely at runtime, which has two drawbacks: it has some runtime overhead (resulting also in binary size overhead), and there’s no compile-time correctness checking. The new syntax addresses both issues. Before checking the syntax in an example, we’d better know what happens in particular.

Let’s say we are building a house and we want to connect the cables. This is exactly what connect function does. Signals and slots are the ones needing this connection. The point is if you do one connection, you need to be careful about the further overlaping connections. Whenever you connect a signal to a slot, you are trying to tell the compiler that whenever the signal was emitted, simply invoke the slot function. This is what exactly happens.

Here’s a sample main.cpp:

#include <QApplication>
#include <QDebug>
#include <QTimer>

inline void onTick()
{
   qDebug() << "onTick()";
}

struct OnTimerTickListener {
   void onTimerTick()
   {
       qDebug() << "OnTimerTickListener::onTimerTick()";
   }
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    OnTimerTickListener listenerObject;

    QTimer timer;
    // Connecting to a non-member function
    QObject::connect(&timer, &QTimer::timeout, onTick);
    // Connecting to an object member method
    QObject::connect(&timer, &QTimer::timeout, &listenerObject, &OnTimerTickListener::onTimerTick);
    // Connecting to a lambda
    QObject::connect(&timer, &QTimer::timeout, [](){
        qDebug() << "lambda-onTick";
    });    

    return app.exec();
}

Hint: the old syntax (SIGNAL/SLOT macros) requires that the Qt metacompiler (MOC) is run for any class that has either slots or signals. From the coding standpoint that means that such classes need to have the Q_OBJECT macro (which indicates the necessity to run MOC on this class).

The new syntax, on the other hand, still requires MOC for signals to work, but not for slots. If a class only has slots and no signals, it need not have the Q_OBJECT macro and hence may not invoke the MOC, which not only reduces the final binary size but also reduces compilation time (no MOC call and no subsequent compiler call for the generated *_moc.cpp file).

Connecting overloaded signals/slots

While being better in many regards, the new connection syntax in Qt5 has one big weakness: Connecting overloaded signals and slots. In order to let the compiler resolve the overloads we need to use static_casts to member function pointers, or (starting in Qt 5.7) qOverload and friends:

#include <QObject>

class MyObject : public QObject
{
    Q_OBJECT
public:
    explicit MyObject(QObject *parent = nullptr) : QObject(parent) {}

public slots:
    void slot(const QString &string) {}
    void slot(const int integer) {}

signals:
    void signal(const QString &string) {}
    void signal(const int integer) {}
};

int main(int argc, char **argv)
{
    QCoreApplication app(argc, argv);

    // using pointers to make connect calls just a little simpler
    MyObject *a = new MyObject;
    MyObject *b = new MyObject;

    // COMPILE ERROR! the compiler does not know which overloads to pick :(
    QObject::connect(a, &MyObject::signal, b, &MyObject::slot);

    // this works, now the compiler knows which overload to pick, it is very ugly and hard to remember though...
    QObject::connect(
        a,
        static_cast<void(MyObject::*)(int)>(&MyObject::signal),
        b,
        static_cast<void(MyObject::*)(int)>(&MyObject::slot));

    // ...so starting in Qt 5.7 we can use qOverload and friends:
    // this requires C++14 enabled:
    QObject::connect(
        a,
        qOverload<int>(&MyObject::signal),
        b,
        qOverload<int>(&MyObject::slot));

    // this is slightly longer, but works in C++11:
    QObject::connect(
        a,
        QOverload<int>::of(&MyObject::signal),
        b,
        QOverload<int>::of(&MyObject::slot));

    // there are also qConstOverload/qNonConstOverload and QConstOverload/QNonConstOverload, the names should be self-explanatory
}

Multi window signal slot connection

A simple multiwindow example using signals and slots.

There is a MainWindow class that controls the Main Window view. A second window controlled by Website class.

The two classes are connected so that when you click a button on the Website window something happens in the MainWindow (a text label is changed).

I made a simple example that is also on GitHub:

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "website.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

public slots:
    void changeText();

private slots:
    void on_openButton_clicked();

private:
    Ui::MainWindow *ui;

    //You want to keep a pointer to a new Website window
    Website* webWindow;
};

#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);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::changeText()
{
    ui->text->setText("New Text");
    delete webWindow;
}

void MainWindow::on_openButton_clicked()
{
    webWindow = new Website();
    QObject::connect(webWindow, SIGNAL(buttonPressed()), this, SLOT(changeText()));
    webWindow->show();
}

website.h

#ifndef WEBSITE_H
#define WEBSITE_H

#include <QDialog>

namespace Ui {
class Website;
}

class Website : public QDialog
{
    Q_OBJECT

public:
    explicit Website(QWidget *parent = 0);
    ~Website();

signals:
    void buttonPressed();

private slots:
    void on_changeButton_clicked();

private:
    Ui::Website *ui;
};

#endif // WEBSITE_H

website.cpp

#include "website.h"
#include "ui_website.h"

Website::Website(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Website)
{
    ui->setupUi(this);
}

Website::~Website()
{
    delete ui;
}

void Website::on_changeButton_clicked()
{
    emit buttonPressed();
}

Project composition:

SOURCES += main.cpp
        mainwindow.cpp 
    website.cpp

HEADERS  += mainwindow.h 
    website.h

FORMS    += mainwindow.ui 
    website.ui

Consider the Uis to be composed:

  • Main Window: a label called «text» and a button called «openButton»
  • Website Window: a button called «changeButton»

So the keypoints are the connections between signals and slots and the management of windows pointers or references.

QT: работаем с сигналами и слотами

Этот «классический» слегка доработанный пример на сигналы и слоты в QT показывает, как их соединять, как разрывать и возобновлять соединение. Сначала немного теории.

В QT реализована концепция функций обратного вызова (callback functions) — в результате действий пользователя вызываются обычные методы класса типа void. Чтобы сопоставить код с кнопкой, необходимо передать в функцию указатель на кнопку. Элементы графического интерфейса пользователя оказываются тесно связаны с функциональными частями программы. Для обеспечения связей сообщения и методов обработки используются макросы — карты сообщений. Примеры интерфейсов, где так сделано — Windows API, MFC.

В QT препроцессор вставляет дополнительную информацию на место метки Q_OBJECT в описании класса. Внедрять макрос в определение класса имеет смысл в тех случаях, когда созданный класс использует такой механизм обмена сообщениями, как сигналы и слоты, или если ему необходима информация о свойствах.

Механизм сигналов и слотов основан на следующих принципах:

  • каждый класс, унаследованный от QObject, может иметь любое количество сигналов и слотов;
  • сообщения, посылаемые посредством сигналов, могут иметь множество аргументов любого типа;
  • сигнал можно соединять с различным количеством слотов. Отправляемый сигнал поступит ко всем подсоединенным слотам;
  • слот может принимать сообщения от многих сигналов, принадлежащих разным объектам;
  • соединение сигналов и слотов можно производить в любой точке приложения;
  • сигналы и слоты являются механизмами, обеспечивающими связь между объектами. Связь также может выполняться между объектами, которые находятся в различных потоках;
  • при уничтожении объекта происходит автоматическое разъединение всех сигнально-слотовых связей. Это гарантирует, что сигналы не будут отправляться к несуществующим объектам.

Особенности работы механизма сигналов и слотов следующие:

  • сигналы и слоты не являются частью языка C++, поэтому требуется запуск дополнительного препроцессора перед компиляцией программы;
  • отправка сигналов происходит медленнее, чем обычный вызов функции, который производится при использовании механизма функций обратного вызова;
  • существует необходимость в наследовании класса QObject;
  • в процессе компиляции не производится никаких проверок: имеется ли сигнал или слот в соответствующих классах или нет; совместимы ли сигнал и слот друг с другом и могут ли они быть соединены вместе. Об ошибке можно будет узнать лишь тогда, когда приложение будет запущено. Вся эта информация выводится на консоль, поэтому, для того чтобы увидеть ее в Windows, в проектном файле необходимо в секции CONFIG добавить опцию console.

Сигналы (signals) — это методы, которые в состоянии осуществлять пересылку сообщений.
Сигналы определяются в классе, как обычные методы, но без реализации. Они являются прототипами методов, содержащихся в заголовочном файле определения класса. Всю дальнейшую заботу о реализации кода для этих методов берет на себя препроцессор. Методы сигналов не должны возвращать каких-либо значений, поэтому перед именем метода всегда должно стоять void.

Сигнал не обязательно соединять со слотом. Если соединения не произошло, то он просто не будет обрабатываться. Подобное разделение отправляющих и получающих объектов исключает возможность того, что один из подсоединенных слотов каким-то образом сможет помешать объекту, отправившему сигналы.
Библиотека предоставляет большое количество уже готовых сигналов для существующих элементов управления. В основном, для решения поставленных задач хватает этих сигналов, но иногда возникает необходимость реализации новых сигналов в своих классах.

class MySignal {
 Q_OBJECT
 //...
 signals:
  void doIt();
 //...
};

Препроцессор обеспечит примерно такую реализацию сигнала:

void MySignal::doIt() {
 QMetaObject::activate(this, &staticMetaObject, 0, 0);
}

Выслать сигнал можно при помощи ключевого слова emit. Ввиду того, что сигналы играют роль вызывающих методов, конструкция отправки сигнала emit doIt() приведет к обычному вызову метода doIt(). Сигналы могут отправляться из классов, которые их содержат. Например, в листинге выше сигнал doIt() может отсылаться только объектами класса MySignal, и никакими другими. Чтобы иметь возможность отослать сигнал программно из объекта этого класса, следует добавить метод sendSignal(), вызов которого заставит объект класса MySignal отправлять сигнал doIt():

class MySignal {
 Q_OBJECT
 public:
  void sendSignal() {
   emit doIt();
  }
 signals:
  void doIt();
};

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

class MySignal : public QObject {
 Q_OBJECT
 public:
  void sendSignal() {
   emit sendString("Information");
  }
 signals:
  void sendString(const QString&);
};

Обратите внимание, что в прототипе функции-сигнала не указываются имена параметров, а только типы.

Слоты (slots) — это методы, которые присоединяются к сигналам. По сути, они являются обычными методами. Основное их отличие состоит в возможности принимать сигналы. Как и обычные методы, они определяются в классе как public, private или protected. Соответственно, перед каждой группой слотов должно стоять одно из ключевых слов private slots:, protected slots: или public slots:

В слотах нельзя использовать параметры по умолчанию, например slotMethod (int n = 8), или определять слоты как static.
Классы библиотеки содержат целый ряд уже реализованных слотов. Но определение слотов для своих классов — это частая процедура.

class MySlot : public QObject {
 Q_OBJECT
 public:
 MySlot();
 public slots:
  void slot() {
   qDebug() << "I’m a slot";
  }
};

Внутри слота вызовом метода sender() можно узнать, от какого объекта был выслан сигнал. Он возвращает указатель на объект типа QObject. Например, в этом случае на консоль будет выведено имя объекта, выславшего сигнал:

void slot() {
 qDebug() << sender()->objectName();
}

Соединение объектов осуществляется при помощи статического метода connect(), который определен в классе QObject. В общем виде, вызов метода connect() выглядит следующим образом:

QObject::connect(const QObject* sender,
const char* signal,
const QObject* receiver,
const char* slot,
Qt::ConnectionType type = Qt::AutoConnection
);

Ему передаются пять следующих параметров:

  1. sender — указатель на объект, отправляющий сигнал;
  2. signal — это сигнал, с которым осуществляется соединение. Прототип (имя и аргументы) метода сигнала должен быть заключен в специальный макрос SIGNAL(method());
  3. receiver — указатель на объект, который имеет слот для обработки сигнала;
  4. slot — слот, который вызывается при получении сигнала. Прототип слота должен быть заключен в специальном макросе SLOT(method());
  5. type — управляет режимом обработки. Имеется три возможных значения:
    • Qt::DirectConnection — сигнал обрабатывается сразу вызовом соответствующего метода слота
    • Qt::QueuedConnection — сигнал преобразуется в событие и ставится в общую очередь для обработки
    • Qt::AutoConnection — это автоматический режим, который действует следующим образом: если отсылающий сигнал объект находится в одном потоке с принимающим его объектом, то устанавливается режим Qt::DirectConnection, в противном случае — режим Qt::QueuedConnection. Этот режим (Qt::AutoConnection) определен в методе connection() по умолчанию.

Как может быть осуществлено соединение объектов в программе:

void main() {
 QObject::connect(pSender, SIGNAL(signalMethod()),pReceiver, SLOT(slotMethod()));
}

Если вызов происходит из класса, унаследованного от QObject, тогда указание QObject:: можно опустить:

MyClass::MyClass() : QObject() {
 connect(pSender, SIGNAL(signalMethod()),pReceiver, SLOT(slotMethod()));
}

В случае, если слот содержится в классе, из которого производится соединение, то можно воспользоваться сокращенной формой метода connect(), опустив третий параметр (pReceiver), указывающий на объект-получатель. Другими словами, если в качестве объекта-получателя должен стоять указатель this, его можно просто не указывать:

MyClass::MyClass() : QObject() {
 connect(pSender, SIGNAL(signalMethod()), SLOT(slot()));
}

void MyClass::slot() {
 qDebug() << "I’m a slot";
}

Иногда возникают ситуации, когда объект не обрабатывает сигнал, а просто передает его дальше. Для этого необязательно определять слот, который в ответ на получение сигнала (при помощи emit) отсылает свой собственный. Можно просто соединить сигналы друг с другом. Отправляемый сигнал должен содержаться в определении класса:

MyClass::MyClass() : QObject() {
 connect(pSender, SIGNAL(signalMethod()), SIGNAL(mySignal()));
}

Отправку сигналов заблокировать можно на некоторое время, вызвав метод blockSignals() с параметром true. Объект будет «молчать», пока блокировка не будет снята тем же методом blockSignals() с параметром false.
При помощи метода signalsBlocked() можно узнать текущее состояние блокировки сигналов.

Параметры в слот передаются из сигнала, если количество, порядок и типы этих параметров в сигнале и слоте совпадают (или в слоте их может быть меньше).

В качестве законченного примера приведём проект Counter. Он умеет увеличивать счётчик на QLabel по нажатию кнопки «Add», а также разрывать и восстанавливать обработку сигналов по нажатию кнопки «Connect»/»Disconnect». Когда соединение отсутствует, счётчик не увеличивается. После 10 увеличений счётчика приложение в любом случае завершается.

Файл counter.h
#ifndef COUNTER_H
#define COUNTER_H

#include <QObject>
#include <QLabel>
#include <QPushButton>

class Counter : public QObject
{
    Q_OBJECT
private:
 int Value;
 bool Connected;
public:
 Counter(QObject *parent=0);
 QLabel lbl;
 QPushButton cmd,cmd2;
public slots:
 void slotInc();
 void disconnector();
signals:
 void goodbye ();
 void counterChanged(int);
};

#endif // COUNTER_H
Файл counter.cpp
#include "counter.h"

Counter::Counter (QObject *parent) :  QObject(parent), Value(0) {
 QObject::connect(&cmd2, SIGNAL(clicked()),this, SLOT(disconnector()) );
 this->Connected = false;
 this->disconnector();
}

void Counter::slotInc() {
 if (this->Connected==true) {
  emit counterChanged(++this->Value);
  if (this->Value == 10) { emit goodbye(); } //ограничиваемся 10 нажатиями
 }
}

void Counter::disconnector() {
 if (this->Connected == true) {
  QObject::disconnect(&cmd, SIGNAL(clicked()),this, SLOT(slotInc()) );
  QObject::disconnect(this, SIGNAL(counterChanged(int)), &lbl, SLOT(setNum(int)));
                                       //метод setNum(int) есть в QLabel
  this->Connected = false;
  cmd2.setText("CONNECT");
 }
 else {
  QObject::connect(&cmd, SIGNAL(clicked()),this, SLOT(slotInc()) );
  QObject::connect(this, SIGNAL(counterChanged(int)), &lbl, SLOT(setNum(int)), Qt::DirectConnection );
  this->Connected = true;
  cmd2.setText("DISCONNECT");
 }
}
Файл main.cpp
#include <QApplication>
#include "counter.h"

int main(int argc, char *argv[]) {
 QApplication a(argc, argv);
 Counter counter;
 counter.lbl.setText("0");
 counter.lbl.move(100,100);
 counter.cmd.setText("ADD");
 counter.cmd.move(100,200);
 counter.cmd2.setText("DISCONNECT");
 counter.cmd2.move(100,300);

 counter.lbl.show();
 counter.cmd.show();
 counter.cmd2.show();
 QObject::connect(&counter, SIGNAL(goodbye()), &a, SLOT(quit()) );
 return a.exec();
}

Соединять сигналы со слотами, разумеется, не обязательно программно. В режиме дизайна формы нажмите клавишу F4 для доступа к интерфейсу управления сигналами и слотами. Там же можно добавить в список новые, заданные программистом слоты.

соединение сигналов и слотов в режиме дизайна формы

соединение сигналов и слотов в режиме дизайна формы

 Скачать этот пример в архиве .ZIP с проектом QT5 (2 Кб)

Проиллюстрируем также «визуальное» соединение сигналов со слотами на примере обработки текстового поля QLineEdit и кнопки QPushButton, размещённых на форме виджета:

вид формы в режиме дизайна

вид формы в режиме дизайна

Нажмём в режиме дизайна формы клавишу F4 или обведённую на рисунке кнопку «Изменение сигналов/слотов», затем зажмём левую кнопку мыши на поверхности PushButton и протянем красную линию в окно виджета:

вызов настройки соединения

вызов настройки соединения

После отпускания кнопки мыши появилось окно «Настройка соединения», слева выберем сигнал clicked(), а справа нажмём кнопку Изменить, затем в новом окне Сигналы/Слоты кнопку «+» под списком слотов. К виджету добавился слот slot1(), после нажатия OK он появился в окне настройки соединения:

окно настройки соединения

окно настройки соединения

После нажатия OK связь создана и отображена на форме, вернуться к обычном виду можно нажатием клавиши F3.

Если мы хотим просто автоматически создать пустую функцию-слот, достаточно нажать правой кнопкой мыши на PushButton и выбрать пункт меню «Перейти к слоту…», а затем сигнал clicked(), для которого создаётся слот.

В добавленной таким способом в модуль функции можно писать код, например:

void Widget::on_pushButton_clicked()
{
    QMessageBox msg; 
     //Не забудьте добавить #include <QMessageBox> в widget.h!
    msg.setText(ui->lineEdit->text());
    msg.exec();
}

Приложение готово к работе, по нажатию кнопки выполняется этот код:

приложение в работе

приложение в работе

Существенно также то, что в пятой версии QT стало можно применять запись соединения, основанную на указателях.

Запись с макросами:

connect(button, SIGNAL(clicked()), this, SLOT(slotButton()));

Запись на основе указателей:

connect(button, &QPushButton::clicked, this, &MainWindow::slotButton);

Преимущество второго варианта заключается в том, что имеется возможность определить несоответствие сигнатур и неверное наименование слота или сигнала ещё на стадии компиляции проекта, а не в процессе тестирования приложения.

28.04.2015, 17:25 [44118 просмотров]


К этой статье пока нет комментариев, Ваш будет первым

Понравилась статья? Поделить с друзьями:
  • Oflain как пишется
  • Office как пишется на английском
  • Off road как пишется
  • Nostalgia как пишется правильно
  • Noone как пишется