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

Собственные классы в Qt. Создание элементов графического интерфейса

Аннотация: Рассказывается о базовом объекте QObject, управлении памятью, иерархии объектов, событиях и их обработке, фильтрах и распространении событий, а также создании собственного элемента интерфейса.

14.1 Класс QObject

QObject является базовым классом для почти всех классов Qt. Исключением являются только классы, которые должны быть достаточно "лёгкими" (экземпляры которых должны занимать как можно меньше памяти) и классы, объекты которых должны копироваться (QObject не поддерживает копирования), а также контейнерные классы. Все виджеты Qt наследуют QObject (класс QWidget является потомком QObject). QObject реализует все базовые особенности, которыми обладают классы Qt:

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

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

  • имя класса (метод const chat *QObject::className());
  • наследование (метод bool QObject::inherits(const char *className));
  • информация о свойствах;
  • информация о сигналах и слотах;
  • общая информация о классе (QОbject::classInfo).

Метаданные собираются во время компиляции (предварительной обработки проекта с помощью qmake) метаобъектным компилятором moc, который анализирует содержание заголовочных файлов программы. Эти метаданные позволяют получить информацию о любом потомке QObject. Эта информация может быть полезна как для отладки программы, так и для создания различных механизмов взаимодействия между объектами в программе.

При разработке с использованием Qt часто возникает необходимость наследовать класс QObject непосредственно или его потомка. Объекты, которые наследуют QObject:

  • имеют имя (QObject :: objectName ()), которое используется в Qt для реализации различных возможностей (стили, QML и т.д.);
  • могут занимать место в иерархии других объектов QObject;
  • могут иметь сигнально-слотовые соединения с другими объектами QObject.

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

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

Создадим новый проект и добавим к нему новый класс, который будет наследовать от QLineEdit (создание новых классов с использованием мастера описано в разделе 13.1). Назовём новый класс IconizedLineEdit. После создания нового класса откроем файл описания (IconizedLineEdit.h).

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

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

Созданный класс выполняет эти условия (наследует от QLineEdit, который наследует от QWidget, а тот в свою очередь от QObject и содержит макрос Q_OBJECT в частной секции класса).

#ifndef QRCLEARABLELINEEDIT_H
#define QRCLEARABLELINEEDIT_H
#include <QLineEdit>
class QLabel;
class IconizedLineEdit : public QLineEdit
{
	Q_OBJECT
public :
	//Режимы отображения пиктограммы, которые определяют её поведение
enum IconVisibilItyMode {
	//Всегда отображать пиктограмму
	IconAlwaysVisible =0,
	//Отображать пиктограмму после наведения курсора на поле ввода
	IconVisibleOnHover,
	//Отображать пиктограмму при присутствии текста
	IconVisibleOnTextPresence,
	//Отображать пиктограмму при отсутствии текста
	IconVisibleOnEmptyText,
	//Всегда прятать пиктограмму
	IconA lwaysH idden
	};
	explicit IconizedLineEdit ( QWidget * parent = 0 );
	bool isIconVisible ( ) const;
	void setIconPixmap ( const QPixmap &pPixmap );
	void setIconTooltip ( const QString &pToolTip );
private :
	void updateIconPositionAndsize ( );
private :
	QLabel *mIconLabel; //Указатель на метку, которая отображает пиктограмму
};
#end if // QRCLEARABLELINEEDIT_H

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

#include " IconizedLineEdit .h "
#include <QSty le>
#include <QLabel>
//Конструктор класса
IconizedLineEdit::IconizedLineEdit ( QWidget * parent ) : QLineEdit ( parent )
{
	mIconLabel = new QLabel ( this ); //Создаём метку для того, чтобы показать пиктограмму
}
//Возвращает true, если пиктограмма видима
bool IconizedLineEdit::isIconVisible ( ) const
{
	return mIconLabel-> isVisible ( );
}
//Устанавливает пиктограмму
void IconizedLineEdit::setIconPixmap ( const QPixmap &pPixmap )
{
	//Устанавливаем пиктограмму для метки
	mIconLabel->setP ixmap ( pPixmap );
	//Обновляем позицию и размеры
	updateIconPositionAndsize ( );
}
//Устанавливаем подсказку для пиктограммы
void IconizedLineEdit::setIconTooltip ( const QString &pToolTip )
{
	//Подсказка будет видимой после наведения курсора на метку с пиктограммой
	mIconLabel->setToolTip ( pToolTip );
}
void IconizedLineEdit::updateIconPositionAndsize ( )
{
	//Обновить размер пиктограммы
	mIconLabel->setScaledContents ( true );
	mIconLabel->setFixedsize ( height ( ), height ( ) );
	//Обновить размещение пиктограммы
	QSizelsize = mIconLabel->size ( );
	mIconLabel->move ( rect ( ).right ( ) - lsize.width ( ), ( rect ( ).bottom ( ) + 1 -
	lsize .height ( ) ) / 2 );
	//Изменить отступы текста внутри поля ввода в зависимости от видимости
	if ( mIconLabel-> isVisible ( ) )
	{
		//Добавить отступ справа чтобы текст не накладывался на пиктограмму
		QMarginslMargins = textMargins ( );
		lMargins.setright ( mIconLabel->size ( ).width ( ) + 1 );
		setTextMargins ( lMargins );
	}
	else
	{
		//Убрать отступы
		setTextMargins ( QMargins ( 0, 0, 0, 0 ) );
	}
}

Чтобы показать значок, мы используем метку, изображения для которой можно передать с помощью метода setPixmap(). Этот метод принимает экземпляр QPixmap класса для работы с растровыми изображениями.

Метод updateIconPositionAndSize() обновляет размер и размещение для метки. Для того, чтобы растянуть\сжать изображения мы передаём true методу метки setScaledContents(). Это позволяет игнорировать размеры изображения и изменить размер для значков. Далее мы устанавливаем фиксированный размер для метки, таким образом, чтобы её пропорции подходили для размещения в поле. Затем размещаем метку в правом конце текстового поля.

Для того, чтобы установить тот или иной режим отображения значка запрограммируем метод setIconVisibility(). Добавим объявление метода в файл описания, а также добавим описание перечисления IconVisibilityMode, содержащее режимы отображения пиктограммы.

public :
	//Режимы отображения пиктограммы, которые определяют её поведение
	enum IconVisibilItyMode
	{
		//Всегда отображать пиктограмму
		IconAlwaysVisible =0,
		//Отображать пиктограмму после наведения на поле ввода
		IconVisibleOnHover,
		//Отображать пиктограмму при присутствии текста
		IconVisibleOnTextPresence,
		//Отображать пиктограмму при отсутствии текста
		IconVisibleOnEmptyText,
		//Всегда прятать пиктограмму
		IconA lwaysH idden
	};
void setIconVisibilIt y ( IconVisibilItyMode pIconVisibilItyMode );
... .
private slots :
	void slotTextChanged ( const QString &pText );
private :
	void updateIconPositionAndsize ( );
	void setIconVisible ( bool pisVisible );
private :
	IconVisibilItyMode mIconVisibilItyMode; //Режим отображения

Добавим к файлу iconizedlineedit.cpp реализацию для этих методов.

//Устанавливает режим отображения для пиктограммы
void IconizedLineEdit::setIconVisibilIty ( IconVisibilItyMode
	pIconVisibilItyMode )
{
	//Сохранение режима
	mIconVisibilItyMode = pIconVisibilItyMode;
	//Выполняем изменения соответсвенно к установленому значению
	switch ( pIconVisibilItyMode )
	{
	case IconAlwaysVisible :
		setIconVisible ( true );
		break;
	case IconVisibleOnEmptyText :
	case IconVisibleOnTextPresence :
		slotTextChanged ( Text( ) );
		break;
	default :
		setIconVisible ( false );
		break;
	}
}
//Реализует реакцию на изменение текста в поле для режимов IconVisibleOnEmptyText
//и IconVisibleOnNotEmptyText
void IconizedLineEdit::slotTextChanged ( const QString &pText )
{
	if ( IconVisibleOnEmptyText == mIconVisibilItyMode )
	{
		setIconVisible ( pText.isEmpty ( ) );
	}
	else if ( IconVisibleOnTextPresence == mIconVisibilItyMode )
	{
		setIconVisible ( ! pText.isEmpty ( ) );
	}
}
//Сделать пиктограмму видимой или спрятать
void IconizedLineEdit::setIconVisible ( bool pisVisible )
{
	//Показать/скрыть метку с пиктограммой
	mIconLabel->setVisible ( pisVisible );
}

Для того, чтобы слот slotTextChanged(QString) работал, добавим в конструктор сигнально-слотовое соединение. Также добавим начальную инициализацию для поля mIconVisibilityMode.

//Конструктор класса
IconizedLineEdit::IconizedLineEdit ( QWidget * parent ) : QLineEdit ( parent ),
	mIconVisibilItyMode ( IconAlwaysVisible ) //Инициализация
{
	mIconLabel = new QLabel ( this ); //Создаём метку для того, чтобы показать пиктограмму
	//Обработка изменения текста в поле
	connect ( this, SIGNAL( textChanged ( QString ) ), this,SLOT( slotTextChanged (
		QString ) ), Qt::UniqueConnection );
}

Чтобы использовать наш виджет в программе, добавим файл описания класса и создадим несколько экземпляров, которые разместим на форме. Ответственным за создание интерфейса окна будет отдельный метод createUi().

В файле описания класса главного окна напишем:

#include <QWidget>
class IconizedLineEdit;
class MainWindow : public QWidget
{
	Q_OBJECT
public :
	explicit MainWindow ( QWidget * parent = 0 );
private :
	void createUi ( );
private :
	IconizedLineEdit * IconizedLineEdit;
	IconizedLineEdit * IconizedLineEdit_ 2;
	IconizedLineEdit * IconizedLineEdit_ 3;
	IconizedLineEdit * IconizedLineEdit_ 4;
	IconizedLineEdit * IconizedLineEdit_ 5;
};

В файле реализации главного окна разместим код:

#include " mainwindow .h "
#include " IconizedLineEdit .h "
#include <QVBoxLayout>
MainWindow::MainWindow ( QWidget * parent ) :
QWidget ( parent )
{
	createUi ( );
}
void MainWindow::createUi ( )
{
QVBoxLayout * lMa inLayout = new QVBoxLayout;
set Layout ( lMa inLayout );
IconizedLineEdit = new IconizedLineEdit;
IconizedLineEdit ->setPlaceholderText( " Click to open File " );
IconizedLineEdit ->setIconPixmap (QPixmap ( " Folder.png " ) );
IconizedLineEdit ->setIconVisibilIt y ( IconizedLineEdit::IconAlwaysVisible );
lMa inLayout->addWidget ( IconizedLineEdit );
IconizedLineEdit_2 = new IconizedLineEdit;
iconizedLineEdit_2 ->setPlaceholderText( " Enter IP address " );
iconizedLineEdit_2 ->setIconPixmap (QPixmap ( " Checkmark.png " ) );
iconizedLineEdit_2 ->setIconVisibilIt y ( IconizedLineEdit::IconAlwaysVisible );
lMa inLayout->addWidget ( IconizedLineEdit_2 );
IconizedLineEdit_3 = new IconizedLineEdit;
iconizedLineEdit_3 ->setPlaceholderText( " " );
iconizedLineEdit_3 ->setIconPixmap (QPixmap ( " Questions.png " ) );
iconizedLineEdit_3 ->setIconVisibilIt y ( IconizedLineEdit : :
	IconVisibleOnTextPresence );
lMa inLayout->addWidget ( IconizedLineEdit_3 );
IconizedLineEdit_4 = new IconizedLineEdit;
iconizedLineEdit_4 ->setPlaceholderText( " Cannot be empty..." );
iconizedLineEdit_4 ->setIconPixmap (QPixmap ( " Warning.png " ) );
iconizedLineEdit_4 ->setIconVisibilIt y ( IconizedLineEdit : :
	IconVisibleOnEmptyText );
lMa inLayout->addWidget ( IconizedLineEdit_4 );
IconizedLineEdit_5 = new IconizedLineEdit;
iconizedLineEdit_5 ->setPlaceholderText( " Clearable " );
iconizedLineEdit_5 ->setIconPixmap (QPixmap ( " X.png " ) );
iconizedLineEdit_5 ->setIconVisibilIt y ( IconizedLineEdit : :
	IconVisibleOnTextPresence );
lMa inLayout->addWidget ( IconizedLineEdit_5 );
}

Пути к файлам изображений для значков мы передаём в конструктор QPixmap. Изображения должны находиться в текущей папке (папке, в которой расположен файл проекта).

После запуска программы мы увидим главное окно и пять полей с пиктограммами. Нашему виджету ещё не хватает нескольких важных возможностей. Значки не двигаются при изменении размера текстовых полей. Также значок не реагирует на нажатие мышкой. Совершенствование этого примера, а также дальнейшее изучение свойств объектов и виджетов мы продолжим в следующих параграфах, посвящённых обработке событий.

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

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

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

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

 

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

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