Новости программы Industrial Software Engineering
Укажите свой email для того чтобы получать полезные материалы по индустриальной разработке программного обеспечения
Новости программы Industrial Software Engineering
Укажите свой email для того чтобы получать полезные материалы по индустриальной разработке программного обеспечения

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

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

Сложности, которые могут возникнуть:

  • Контроль времени жизни объектов. Если связывать один объект с другим по ссылке или указателю, то нам нужно гарантировать, что в тот момент, когда мы обращаемся к объекту по этому указателю, то есть разименовываем его, объект все еще существует и способен обработать наш запрос, то есть мы способны вызвать функцию этого объекта.
  • Контроль интерфейса функции. Часть объектов может располагаться в библиотеке, и при смене версии библиотеки, программа линкуется автоматически. Мы можем получать ошибки доступа к объектом только из-за того, что поменялись интерфейсы функций, а главное приложение об этом не знает.
  • Необходимость перекомпиляции при изменениях не только приложения, но и всех библиотек, которые с ним связаны при изменении интерфейса.

В Qt используется концепция сигналов и слотов, которая позволяет объектам обмениваться сообщениями не заботясь о времени жизни и не думая об интерфейсах функций.

Таким образом в Qt:

  • Объекты могут излучать сообщения, не заботясь о получателях. То есть, они не передают сообщения, не вызывают функции конкретных объектов, они генерируют сообщения, которые субъекты могут использовать
  • Объекты-отправители и объекты-получатели сообщений не связаны напрямую друг с другом. Они связаны с помощью Meta объектной системы Qt.
  • Связывание объектов и доставка сообщений осуществляется Qt Meta Object System, контроль времени жизни и интерфейса.
Посмотрим на связывание объектов в языке Си++. Есть экземпляр класса А, есть экземпляр класса В. Для того чтобы передать какое-то сообщение с параметрами из класса А в класс В, нам нужно иметь указатель или ссылку на экземпляр класса В.

Проблема заключается в том, что, если время жизни объектов не совпадает, то вызов функции или передачи сообщения в тот момент, когда один объект существует , а второй уже нет, вызов обернется провалом. Этот "провал" - это ошибка доступа к памяти или Segmentation fault.

Если мы посмотрим на систему сигналов и слотов в Qt, то мы увидим, что ситуация немного другая. Можно считать, что все объекты живут в некой среде , в которой распространяются сообщения, в ней могут существовать объекты разных классов. У этих классов есть способность излучать сигналы в информационную среду, в которой они живут, и способность связываться (принимать) сигналы от объектов. Это называется Meta Object System.

Используется такая терминология: сообщения, которые излучает объект - называют сигналами, а специальные функции, которые воспринимают и обрабатывают сигналы - называются слотами. Объект, излучающий сигнал, похож на радиостанцию, а объекты, принимающие сигналы, похожи на радиоприемник. Так же как и в обычном радио, излучение сигналов не зависит от наличия тех или иных радиоприемников, настроенных на нужный сигнал или станцию. Соответственно, прием сигналов, в нашем случае сообщений, тоже не зависит от их наличия объектов, которые находятся в этой среде передач. Можно сказать, что у нас есть объекты m_a, которая может излучать сигнал, m_b, который может обрабатывать сообщения такого типа, и есть Meta Object System - инструментальная система библиотеки Qt, которая может этот конкретный сигнал связать с этим конкретным слотом, так, чтобы экземпляру а и экземпляру в для своей успешной работы не приходилось проверять в памяти существование объекта.

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

Для того, чтобы использовать Мета объектную систему, нужно знать:

  • Во-первых, используется расширение языка С++ и Meta Object compiler, то есть компилятор мета информации объекта.
  • Во-вторых, Meta Object compiler - является реализацией расширения С++, он ищет специальные определения Q_OBJECT в объявлениях классов и внутри этих объявлений интерпретирует специальные ключевые слова emit, signals, slots… Также Meta Object compiler генерирует дополнительный С++ код
  • В-третьих, он необходим для поддержки сигналов и слотов, доступа к свойствам объектов, для поддержки динамических свойств (properties). С помощью properties реализована система наделения объектов свойствами, которая позволяет использовать динамическую систему типов.

Для того, чтобы ваши приложения работали хорошо, требуется каждый раз проверять чек лист (mos: check list):

  • Каждое объявление класса размещено в отдельном заголовочном файле (.h - файл)
  • Каждое определение класса размещено в отдельном .cpp - файле. Дело в том, что Meta Object обрабатывает файлы по одному, он не может собрать Мета информацию по всей программе.
  • Каждый заголовочный файл имеет #ifdef защиту. Это стандартное требование для заголовочных файлов на языке Си, Си++.
  • Каждый .cpp - файл перечислен в секции SOURCES проекта
  • Каждый .h - файл перечислен в секции HEADERS проекта
  • Макро Q_Object присутствует в каждом классе-наследнике Qt класса или в каждом классе, в котором вы используете сигналы и слоты.

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

Таким же точно интерфейсом обладает функция disconnect, которая отсоединяет слот конкретного объекта от сигнала конкретного объекта.

Для излучения сигнала используется функция emit .

Практический пример

Давайте создадим наше первое приложение на Qt вручную, чтобы понять как устроена система сборки QMAKE. Для этого нам потребуется выбрать others projects. Назовем проект hello. Чтобы теперь воспользоваться библиотекой Qt, подключить список модулей к проекту - стандартную базовую библиотеку, которая содержит контейнеры, систему сигналов и слотов, и модуль, содержащий графические интерфейсы.

Теперь добавим Си++ файл, назовем его main. Далее создаем простейшее приложение на Qt. Предположим, что вся наша работа будет заключатся в том, чтобы вывести на экран кнопку, на которой будет написано Hello Word. Подключаем класс кнопки, создаем функцию main, создаем экземпляр класса QApplication, передаем параметры, которые получили в main и стандартный шаблон приложения. Для добавления визуального эффекта, заводим QPushButton, и сделаем эту кнопку видимой. Запускаем -- на экране появилась кнопка.

Давайте сделаем наше приложение более разумным, используя информацию, которую мы получили о сигналах и слотах. Каждый Qt-класс уже содержит реализованные сигналы и слоты. Можно поставить курсор на соответствующий класс, нажать F1 и мы получим описание. Есть публичные слоты, но мы не видим сигналов. Дело в том, что сигналы могут быть реализованы в базовом классе. Давайте попытаемся использовать сигнал clicked. Для его использования, нужно понять с каким сигналом мы его соединим.

Давайте сделаем так, чтобы по нажатию кнопки закрывалось приложение. Для этого смотрим какие слоты есть у приложения. Находим слот quit. Метод connect реализован для каждого объекта, подставляем сигнатуру, и запускаем. Появляется наша кнопка, которая при нажатии закрывается. Точно таким же образом можно использовать любой класс.

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

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

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