Компания ALT Linux
Опубликован: 07.03.2015 | Доступ: свободный | Студентов: 2136 / 487 | Длительность: 24:14:00
Лекция 13:

Создание графического интерфейса средствами Qt

13.4 Сигнально-слотовые соединения

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

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

Таким образом, для реализации такого взаимодействия программисту нужно вызвать метод другого объекта в ответ на изменение состояния. Объект, который сменил состояние, может для этого просто хранить указатель на другой объект, чей метод нужно вызвать. В ответ на изменение состояния он будет обращаться через указатель и вызывать метод. Однако, в таком случае теряется гибкость. Например, в случае с элементами управления в окне программы, придётся программировать каждый элемент отдельно — наследовать от класса элемента управления, добавлять указатель и переопределять метод для обработки действий пользователя с целью взаимодействия с другими объектами в программе. К тому же такая связь не может быть динамической и задаётся жёстко на этапе компиляции.

Конечно можно передавать указатель на метод, который будет вызываться в ответ (callback), но такой подход тоже имеет некоторые недостатки (меньшая безопасность, всегда работает как прямой вызов метода, нужны знания о деталях реализации). Именно поэтому в Qt используется концепция сигнально-слотовых соединений.

Сигнально-слотовые соединения являются простым, но в то же время важным средством кроссплатформенного инструментария разработки Qt. Один объект при изменении своего состояния может сообщить других с помощью сигнала. Визуальные компоненты тоже вырабатывают сигналы в ответ на действия пользователя (например, нажатия кнопки, установки флага, изменения положения слайдера, редактирования текста в поле ввода и т. п.). Другие объекты могут присоединиться к сигналу слотом — специальным методом, который реализует некоторую функциональность и вызывается каждый раз, когда был выпущен присоединённый к нему сигнал. Как отмечалось ранее, сигнально-слотовые соединения могут использоваться как для взаимодействия объектов в пределах одного потока, так и между потоками (при соответствующих условиях), также возможна организация отложенного выполнения операций.

Для задания соединения используют метод connect() класса QObject. Метод принимает пять параметров:

  • указатель на объект, который посылает сигнал (sender);
  • название сигнала (signal) и его параметры, которые задаются с помощью макроса SIGNAL();
  • указатель на объект, который получает сигнал (receiver);
  • название слота (slot) и его параметры, которые задаются с помощью макроса SLOT(), или название другого сигнала, будет эмитироваться (выпускаться) в ответ;
  • тип сигнально-слотового соединения (имеет значение по умолчанию Qt::AutoConnection).

В следующем примере мы продемонстрируем сигнально-слотовое соединения между кнопкой QPushButton и виджетом-окном QWidget. В ответ на нажатие кнопки (сигнал clicked()), вызывается метод-слот close(), который закрывает окно. Заметим, что слоты имеют спецификатор доступа (private/protected/public) и вызывают их как и обычные методы класса. Этим они не отличаются от других методов.

connect(lPushButton,SIGNAL(clicked()),lWindow, SLOT(close()));

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

  • Qt::AutoConnection — тип соединения по умолчанию. При соединении объектов в пределах потока ведёт себя как Direct Connection, иначе — как Queued Connection;
  • Qt::DirectConnection — слот вызывается немедленно после того, как был выпущен сигнал. По сути это напоминает обычний вызов слота как метода;
  • Qt::QueuedConnection — слот выполняется, как только управления перейдёт к очереди обработки сообщений потока получателя;
  • Qt::BlockingQueuedConnection — то же, что и Queued Connection, но поток, из которого был выпущен сигнал блокируется, пока выполнение слота не будет завершено. Этот тип соединения должен использоваться только когда взаимодействующие объекты находятся в разных потоках.
  • Qt::UniqueConnection — этот тип соединения такой же как и Qt::AutoConnection, но соединение происходит только тогда, когда оно уникально (то есть такое, которое не дублирует уже существующие соединений).

Также с помощью соединений между слотами и сигналами может происходить передача параметров. Например, визуальный элемент QCheckBox выпускает сигнал toggled() каждый раз при установке и снятии флажка. Сигнал toggled() передаёт один параметр — булевое значение: true — если флажок установлен, false — если нет. Мы сможем соединить его со слотом setChecked() кнопки, который также принимает булево значение и устанавливает кнопку в положение "включено" (true) или "выключено" (false). Заметьте: мы использовали метод setCheckable(), который устанавливает для кнопки режим переключения между двумя состояниями.

lPushButton->setCheckable ( true );
connect ( lCheckBox, SIGNAL( toggled ( bool ) ), lPushButton,SLOT( setChecked ( bool ) ) );
  • порядок и тип параметров должен совпадать у объекта, который передаёт сигнал, и у объекта-получателя;
  • сигнал или слот получателя может опускать некоторые или все остальные параметры, при этом порядок и тип параметров, которые остались у получателя, должны совпадать с первыми параметрами сигнала.

Один и тот же сигнал объекта может быть подключён к нескольким различным сигналам и слотам другого объекта и наоборот. При этом последовательность, в которой будут вызываться присоединённые сигналы и слоты будет соответствовать порядку в котором их соединили. Также надо помнить, что одно и то же соединение может быть выполнено несколько раз подряд. В таком случае при вызове сигнала слот сработает столько же раз, сколько раз было повторно выполнено соединение. Для того, чтобы избежать этого, необходимо передавать тип соединения Qt::UniqueConnection каждый раз в случаях, когда создание повторных соединений не требуется.

Возможность создания перекрёстного сигнально-слотового соединения продемонстрирована в следующем примере:

connect ( lCheckBox, SIGNAL( toggled ( bool ) ), lPushButton,SLOT( setChecked ( bool ) ) );
connect ( lPushButton, SIGNAL( toggled ( bool ) ), lCheckBox,SLOT( setChecked ( bool ) ) );

Здесь мы видим взаимодействие между обоими элементами управления. При установке\сбросе флажка кнопка будет устанавливаться во включённое состояние или сбрасываться и наоборот.

Сергей Радыгин
Сергей Радыгин

Символы кириллицы выводит некорректно. Как сделать чтобы выводился читабельный текст на русском языке?

Тип приложения - не Qt,

Qt Creator 4.5.0 основан на Qt 5.10.0. Win7.

 

Юрий Герко
Юрий Герко

Кому удалось собрать пример из раздела 13.2 Компоновка (Layouts)? Если создавать проект по изложенному алгоритму, автоматически не создается  файл mainwindow.cpp. Если создавать этот файл вручную и добавлять в проект, сборка не получается - компилятор сообщает об отсутствии класса MainWindow. Как правильно выполнить пример?