Опубликован: 07.03.2015 | Уровень: для всех | Доступ: свободно | ВУЗ: Компания ALT Linux
Лекция 13:

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

13.5 Создание сигналов (signals) и слотов (slots)

Для того, чтобы объект мог принимать участие в сигнально-слотовом взаимодействии, нужно удовлетворить несколько условий. Класс, экземпляром которого является объект, должен наследовать от класса QObject (в случае множественного наследования QObject должен быть первым в списке классов).

Также нужно добавить к описанию класса (перед описанием любых других членов класса в секции private) макрос Q_OBJECT, который будет обработан метаобъектным компилятором moc (программа, которая выполняет предварительную обработку текста программы при запуске qmake и генерирует дополнительный код для реализации возможностей, которые предоставляет Qt). Далее в описании класса можно указать собственно сигналы и слоты. Сигналы описываются в разделе signals, а слоты — в разделе slots, где перед названием раздела стоит спецификатор доступа (public, private либо protected).

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

Создание собственных слотов и использования их в программе продемонстрируем на примере нашего предыдущего проекта. Добавим секцию private slots и добавим описания слотов для обработки нажатия кнопки калькулятора.

private slots :
void slotClear ( ); //Обработка нажатия кнопки сброса
void slotButtonPressed ( int pNum); //Обработка цифровых кнопок
void slotPlusEqual ( ); //Обработка кнопки сумирования/вывода результата

Для сохранения результата и слагаемого, добавим частный член класса:

private :
	int mSum; //Результат
	int mNextNumber; //Следующее слагаемое

Для упрощения примера, наш калькулятор будет выполнять только одну операцию — сложение чисел от 0 до 9 с предыдущим результатом и вывод результата при нажатии кнопки вывода результата и суммирования. Реализацию слотов добавим в файл реализации класса CalculatorMainWindow.

void CalculatorMainWindow::slotClear ( )
{
	lcdNumber->display ( 0 );
	mSum = 0;
	mNextNumber = 0;
}
void CalculatorMainWindow::slotButtonPressed ( int pNum)
{
	mNextNumber = pNum;
	lcdNumber->display (pNum);
}
void CalculatorMainWindow::slotPlusEqual ( )
{
	mSum += mNextNumber;
	lcdNumber->display (mSum);
	mNextNumber = 0;
}

Теперь выполним соединения для кнопок суммирования и сброса в конструкторе класса.

connect ( pushButtonC, SIGNAL( clicked ( ) ), this,SLOT( slotClear ( ) ),Qt : :
	UniqueConnection );
connect ( pushButtonPlus, SIGNAL( clicked ( ) ), this,SLOT( slotPlusEqual ( ) ),Qt : :
	UniqueConnection );

Для реализации обработки цифровых кнопок можно использовать следующие подходы:

  • отдельные слоты для каждого случая
    • скорее всего будут содержать почти одинаковый код;
    • трудно поддерживать программный код;
    • трудно расширять возможности — требует ещё больше дополнительных слотов.
  • наследовать кнопку и добавить дополнительный сигнал к классу, который будет передавать значение
  • дополнительный специализированный класс.

Однако возможен и другой подход — использование класса QSignalMapper. QSignalMapper привязывает некоторое значение к каждому сигналу и позволяет избежать чрезмерного дополнительного создания слотов или специализированных классов. По сути, он выполняет роль посредника между объектами, которые посылают сигналы, и слотом, который принимает параметр, изменяющийся в зависимости от объекта, который его выполнил. Добавим описание нового поля класса, содержащий указатель на QSignalMapper:

//Предварительное объявление классов
class QSignalMapper;
...
private :
	QSignalMapper *mMapper;

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

#include <QSignalMapper>
...
mMapper = new QSignalMapper ( this );
connect ( pushButton, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt::UniqueConnection
	);
connect ( pushButton_2, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : :
	UniqueConnection );
connect ( pushButton_3, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : :
	UniqueConnection );
connect ( pushButton_4, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : :
	UniqueConnection );
connect ( pushButton_5, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : :
	UniqueConnection );
connect ( pushButton_6, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : :
	UniqueConnection );
connect ( pushButton_7, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : :
	UniqueConnection );
connect ( pushButton_8, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : :
	UniqueConnection );
connect ( pushButton_9, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : :
	UniqueConnection );
connect ( pushButton_10, SIGNAL( clicked ( ) ),mMapper,SLOT(map ( ) ),Qt : :
	UniqueConnection );
mMapper->setMapp ing ( pushButton, 1 );
mMapper->setMapp ing ( pushButton_2, 2 );
mMapper->setMapp ing ( pushButton_3, 3 );
mMapper->setMapp ing ( pushButton_4, 4 );
mMapper->setMapp ing ( pushButton_5, 5 );
mMapper->setMapp ing ( pushButton_6, 6 );
mMapper->setMapp ing ( pushButton_7, 7 );
mMapper->setMapp ing ( pushButton_8, 8 );
mMapper->setMapp ing ( pushButton_9, 9 );
mMapper->setMapp ing ( pushButton_10, 0 );
slotClear ( );

После этого соединим QSignalMapper сигнально-слотовым соединением с нашим слотом для обработки нажатий цифровых кнопок:

connect (mMapper, SIGNAL(mapped ( int ) ), this,SLOT( slotButtonPressed ( int ) ),Qt : :
	UniqueConnection );

Теперь программа-калькулятор готова к работе.

Заметим, что для создания соединения обычно используют метод connect(), но существует способ установить соединение автоматически. Обычно такие соединения можно использовать в случае, когда необходимо соединить элементы на форме созданной как Ui-файл с программным кодом, реализующий обработку действий пользователя. Автоматическое соединение задают следующим образом:

on_<имя объекта>_<имя сигнала> (параметры)

Соединение происходит с помощью вызова статического метода QMetaObject:: connectSlotsByName.

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

Подытожим наши знания о сигналах, слотах и сигнально-слотовых соединениях:

Слоты:

  • слот реализуют как обычный метод класса;
  • определяют в одной из секций для слотов (private slots, protected slots, public slots);
  • слот может возвращать значение, но это нельзя каким-либо образом использовать в сигнально-слотовом соединении;
  • произвольное количество сигналов может быть присоединено к одному слоту;
  • слот можно вызвать, как обычный метод класса.

Сигналы:

  • определяют в секции для сигналов (signals);
  • сигналы всегда возвращают void;
  • сигнал должен быть без реализации (реализацию для сигнала предоставляет метаобьектный компилятор moc);
  • сигнал может быть присоединён к произвольному количеству слотов;
  • обычно эмитирование (выпускание) сигнала приводит к прямому вызову слота, но вызов может также быть косвенным (зависит от типа соединения);
  • слоты при этом могут вызываться в произвольном порядке;
  • для посылки сигнала достаточно простого вызова (как в случае с методами), но предпочтительно использовать перед вызовом макрос emit (используется для различия вызова метода и эмитирования сигнала, но фактически не выполняет никакой специальной роли).
Сергей Радыгин
Сергей Радыгин

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

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

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

 

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

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