Глава 2 Средства библиотеки Qt
Средства поддержки библиотеки классов Qt добавляют к C++:
· возможность описания свойств объектов для работы в Qt Creator;
· механизм непосредственного взаимодействия объектов, называемый «сигналы и слоты»;
· события и фильтры событий;
· контекстный перевод строк для интернационализации;
· защищенные указатели QPointer, автоматически устанавливаемые в 0 при уничтожении объекта, на который они ссылаются;
· динамическое приведение (dynamic cast), которое работает через границы библиотек;
· таймеры, которые делают возможным интеграцию многих задач в графический интерфейс пользователя, управляемый событиями.
Рассмотрим некоторые из указанных средств более подробно.
2.1 Виджеты и их свойства
Как уже упоминалось ранее, все управляющие интерфейсные элементы, такие как кнопки, метки, текстовые редакторы и т.п., в Qt названы виджетами. Виджеты – объекты интерфейсных классов, наследуемых от базового интерфейсного класса QWidget. Этот класс, в свою очередь, наследуется от базового класса большинства классов Qt – класса QObject, обеспечивающего работоспособность главных механизмов Qt.
Объектам класса QWidget соответствует графическое представление – прямоугольный фрагмент экрана – окно. Остальные виджеты, как объекты классов, наследуемых от QWidget, также представляют собой соответствующим образом оформленные прямоугольники.
QWidget – контейнерный класс, объекты которого – контейнеры или «родители» по терминологии Qt – отвечают, как за отображение управляемых виджетов – «детей», так и за освобождение выделенной последним памяти.
Примечание. В теории объектно-ориентированного программирования термины «родитель – ребенок» обычно используют для описания отношения базового и производных классов. Однако использование в Qt этих терминов для обозначения объектов-контейнеров и управляемых виджетов особой путаницы не вносит, если обращать внимание на то, между какими компонентами фиксируется отношение: если речь идет об отношении классов, то имеется в виду наследование, если об отношении объектов – то отношение «контейнер – управляемый элемент».
При создании большинства виджетов используется конструктор базового класса QWidget с двумя параметрами:
QWidget(QWidget* parent=0, Qt::WindowFlags=0) {…}
Первый параметр – родитель. С помощью этого параметра строятся иерархии объектов-виджетов. Если в качестве первого параметра указан 0, то родителя у виджета нет. При отсутствии менеджеров компоновки такой виджет отображается в отдельном окне интерфейса и сам отвечает за выделение и освобождение памяти.
Второй параметр – флаги – битовая комбинация, отвечающая за тип окна: обычное, диалоговое, контекстное меню, панель инструментов, выпадающая подсказка и т.п. В простых приложениях этот параметр обычно берется по умолчанию – обычное окно.
Если при создании метки указать объект класса QWidget в качестве родителя:
QWidget window(0); // окно – родительский виджет
QLabel *label = new QLabel("Label", window); // виджет-ребенок
то метка будет создана в рабочей области окна, будет становиться видимой или невидимой вместе с виджетом win и будет уничтожена вместе с ним.
Контейнерные свойства класса QWidget наследуют все классы-потомки. Соответственно любой виджет может служить контейнером для других виджетов.
Использование контейнерных свойств виджетов существенно упрощает работу с ними, позволяя при создании объединять виджеты формы в динамическую древовидную структуру требуемой конфигурации.
Корневой виджет формы объявляют без родителя. Он соответствует окну приложения. Для корректного выделения/освобождения памяти виджетов-детей при создании/уничтожении корневого виджета, подчиненные виджеты-компоненты размещают в динамической памяти. Выделение памяти под них обычно осуществляют в конструкторе класса контейнера, а освобождение – прописано в деструкторе класса QWidget.
В качестве корневых виджетов для интерфейсных элементов обычно используются объекты классов QWidget, QDialog и QMainWidget. Объекты класса QWidget применяют для создания простых форм, объекты класса QDialog – для конструирования диалоговых окон, а объекты класса QMainWidget – для построения сравнительно сложных окон приложений, включающих строку меню и панели инструментов.
Каждый виджет может настраиваться в среде Qt Creator или вручную посредством изменения его свойств. С помощью свойств можно указать размеры виджетов, их расположение, особенности внешнего вида и др. Так же, как в Delphi, свойства виджетов в Qt Creator доступны через окно Инспектора объектов, но их можно изменять и во время работы программы.
В качестве примера рассмотрим следующие свойства:
· bool visible – видимость виджета и, соответственно, всех его подчиненных виджетов; проверка свойства реализуется функцией bool isVisible(); а изменение – процедурой void setVisible(bool visible);
· bool enabled – способность принимать и обрабатывать сообщения от клавиатуры и мыши: true – способно, false – нет; проверка свойства реализуется функцией bool isEnabled(); а изменение – процедурой void setEnabled(bool enabled);
· Qt::WindowModality windowModality – тип окна: Qt::nonModal (обычное), Qt::WindowModal (модальное); проверка свойства реализуется функцией Qt::WindowModality windowModality (); а изменение – процедурой void setWindowModality ( Qt::WindowModality windowModality);
· QRect geometry – размеры и положение виджета относительно родительского окна; размеры задаются прямоугольником типа QRect с фиксированным верхним левым углом (свойства X,Y), а также шириной и высотой (свойства width,height); при изменении размера формы размеры виджетов могут регулироваться компоновщиком в интервале от заданных минимального minimumSize() до максимального maximumSize(); получение значения осуществляют с помощью функции QRect& geometry(), изменение значений процедурами void set Geometry(int x,int y,int w, int h) или void set Geometry(QRect&);
· QFont font – шрифт, которым выполняются надписи в окне;
· QString objectName – имя объекта (переменной) в программе, устанавливается процедурой void setObjectName(), читается функцией objectName() и используется для задания имени переменной в Qt Creator и при отладке программ.
Всего для объектов класса QWidget определено более 50 свойств и методов (таблица 2.1).
Таблица 2.1 – Классификация свойств и методов класса QWidget
Группа | Свойства и основные методы |
Общие методы | show()– показать, hide()– скрыть, raise() – сделать первым в контейнере, lower()– сделать последним в контейнере, close()– закрыть. |
Управление окнами | windowModified – признак изменения окна, windowTitle – заголовок окна, windowIcon – пиктограмма окна, windowIconText, isActiveWindow – признак активности окна, activateWindow()– активизация окна, minimized – признак свернутого состояния, showMinimized()– свертывание окна, maximized – признак развернутого состояния, showMaximized()– развертывание окна, fullScreen, showFullScreen(), showNormal(). |
Управление содержимым | update()- обновить, repaint()- перерисовать, scroll() – изменить размер рабочей области. |
Управление положением и размерами виджета (геометрия) | pos – положение левой верхней точки, x(), y(), rect – положение левой верхней точки и размеры виджета, size, width(), height(), move()– перемещение виджета, resize()– изменение размеров виджета, sizePolicy,sizeHint(), minimumSizeHint(), updateGeometry(), layout(), frameGeometry, geometry, childrenRect, childrenRegion, adjustSize(), mapFromGlobal(),mapToGlobal(), mapFromParent(), mapToParent(),maximumSize, minimumSize, sizeIncrement, baseSize, setFixedSize(). |
Тип | visible, isVisibleTo(), enabled, isEnabledTo(), modal, isWindow(),mouseTracking, updatesEnabled, visibleRegion(). |
Внешний вид | style(), setStyle(), styleSheet, cursor, font, palette, backgroundRole(), setBackgroundRole(), fontInfo(), fontMetrics(). |
Взаимодействие с клавиатурой | focus, focusPolicy, setFocus(), clearFocus(), setTabOrder(), setFocusProxy(), focusNextChild(), focusPreviousChild(). |
Захват мыши и клавиатуры | grabMouse(), releaseMouse(), grabKeyboard(), releaseKeyboard(), mouseGrabber(), keyboardGrabber(). |
Обработчики событий | event(), mousePressEvent(), mouseReleaseEvent(), mouseDoubleClickEvent(), mouseMoveEvent(), keyPressEvent(), keyReleaseEvent(), focusInEvent(), focusOutEvent(), wheelEvent(), enterEvent(), leaveEvent(), paintEvent(),moveEvent(), resizeEvent(), closeEvent(), dragEnterEvent(), dragMoveEvent(), dragLeaveEvent(), dropEvent(), childEvent(), showEvent(), hideEvent(), customEvent(), changeEvent(). |
Управление контейнером | parentWidget(), window(), setParent(), winId(), find(), metric(). |
Помощь | setToolTip(), setWhatsThis(). |
Пример 2.1. Управление размером окна посредством изменения геометрических свойств корневого виджета.
#include <QApplication>
#include <QWidget>
int main(int argc, char *argv[])
{
QApplication app(argc, argv); // создаем объект-приложение
QWidget window; // создаем корневой виджет
QRect rect=window.geometry(); // читаем размер окна по умолчанию
window.setGeometry(20,20,100,100); // устанавливаем размер окна
window.resize(300,100); // меняем ширину и высоту окна
window.setWindowTitle("Main Window");// устанавливаем заголовок
window.setObjectName("window"); // сохраняем имя объекта
window.show(); // визуализируем окно
return app.exec(); // запускаем цикл обработки сообщений
}
2.2 Управление расположением виджетов в окне
При создании окна приложения на базе любого из перечисленных выше классов-окон возникает проблема управления расположением окон виджетов в окне приложения. Qt предусматривает два способа решения этой проблемы:
· задание координат каждого виджета вручную, например посредством метода setGeometry();
· использование специальных невидимых пользователю менеджеров компоновки.
В первом варианте при изменении размеров окна приложения пересчет геометрических параметров виджетов должен выполняться в программе.
Пример 2.2. Размещение виджетов-редакторов в окне вручную.
#include <QApplication>
#include <QWidget>
#include <QLineEdit>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget window;
window.setWindowTitle("Main Window");
window.setObjectName("window");
QLineEdit *edit1=new QLineEdit("Edit1",&window);
QLineEdit *edit2=new QLineEdit("Edit2",&window);
edit1->setGeometry(20,20,60,60);
edit2->setGeometry(120,20,60,60);
window.show();
return app.exec();
}
В результате на экране появляется окно, содержащее оба однострочных редактора (рисунок 2.1, а).
Рисунок 2.1 – Вид окна с двумя однострочными редакторами в нормальном (а) и в свернутом состоянии (б)
Однако на рисунке 2.1, б видно, что попытка уменьшения размера окна приводит к нарушению внешнего вида, в результате которого виджеты вообще могут исчезнуть из поля зрения. Следовательно, при ручной компоновке пришлось бы программировать, как должен изменяться внешний вид окна при изменении его размеров.
В отличие от ручного варианта при компоновке с использованием менеджеров компоновки осуществляется автоматическая перестройка внешнего вида окна в зависимости от его размеров.
В Qt предусмотрены следующие элементы компоновки:
· QVBoxLayout – вертикальный компоновщик – управляет расположением виджетов в окне по вертикали;
· QHBoxLayout – горизонтальный компоновщик – управляет расположением виджетов в окне по горизонтали;
· QGridLayout – табличный компоновщик – управляет расположением виджетов в направляющей двумерной сетке – матрице или таблице.
Пример 2.3. Автоматическая компоновка виджетов в окне
#include <QApplication>
#include <QWidget>
#include <QLineEdit>
#include <QHBoxLayout>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget window;
window.setWindowTitle("Main Window");
window.setObjectName("window");
QLineEdit *edit1=new QLineEdit("Edit1",&window);
QLineEdit *edit2=new QLineEdit("Edit2",&window);
QHBoxLayout *layout = new QHBoxLayout; // выравнивание по
// горизонтали
layout->setContentsMargins(5,5,5,5); // внешние поля окна
layout->setSpacing(5); // просвет между виджетами
window.setLayout(layout); // связывание layout с виджетом окна
// задание порядка следования элементов
layout->addWidget(edit1);
layout->addWidget(edit2);
window.show();
return app.exec();
}
В результате работы приложения получаем интерфейс, который при изменении размеров окна сохраняет пропорции (рисунок 2.2, а-б).
Рисунок 2.2 – Внешний вид интерфейса при автоматической компоновке виджетов:
а – исходное окно; б – окно после уменьшения размеров; в – окно при смене типа компоновщика
Замена элемента горизонтальной компоновки на вертикальную приводит к тому, что окошки редакторов размещаются вертикально, один над другим (рисунок 2.2, в).
QVBoxLayout *layout = new QVBoxLayout;
Табличная компоновка предполагает задание координат размещения компонентов с точностью до клетки. При этом допускается размещать виджет в нескольких клетках. Для добавления виджетов в менеджер компоновки используют специальный метод, позволяющий указать область таблицы, которую должен занимать элемент:
QGridLayout::addWidget(QWidget *widget, // размещаемый виджет
int fromRow, int fromColumn, // координаты верхней левой ячейки
int rowSpan, int columnSpan, // количество ячеек по горизонтали и
// вертикали соответственно
Qt::Alignment alignment=0); // спороб выравнивания
Пример 2.4. Применение табличного компоновщика.
#include <QApplication>
#include <QWidget>
#include <QLineEdit>
#include <QGridLayout>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget window;
window.setWindowTitle("Main Window");
window.setObjectName("window");
QLineEdit *edit1=new QLineEdit("Edit1",&window);
QLineEdit *edit2=new QLineEdit("Edit2",&window);
QLineEdit *edit3=new QLineEdit("Edit3",&window);
QGridLayout *layout = new QGridLayout; // выравнивание по сетке
layout->setContentsMargins(5,5,5,5); // устанавливаем внешние поля
layout->setSpacing(5); // устанавливаем интервал между виджетами
window.setLayout(layout); // связываем layout с виджетом окна
layout->addWidget(edit1,0,0,1,2);
layout->addWidget(edit2,1,0,1,1);
layout->addWidget(edit3,1,1,1,1);
window.show();
return app.exec();
}
Результат работы программы показан на рисунке 2.3.
Рисунок 2.3 – Результат применения табличной компоновки
Для реализации «поджатия» виджетов одного к другому используют «пружины». Виджеты, поджатые пружиной, при увеличении размеров окна остаются рядом.
Для добавления пружины используют метод addStretch класса QBoxLayout:
void QBoxLayout::addStretch(int stretch=0);
Пример 2.5. Применение пружины.
…
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(edit1);
layout->addWidget(edit2);
layout->addStretch();
…
На рисунке 2.4 показан эффект использования пружины: размер окон редакторов и расстояние между строчными редакторами минимально и независит от размеров окна.
Рисунок 2.4 – Использование «пружины» для поджатия виджетов
Следует иметь в виду, что управление размерами виджетов, осуществляемое менеджерами компоновки, регулируется параметрами растяжения и политиками, отдельно задаваемыми по горизонтали и вертикали [1].
Разделители. Вместо менеджеров компоновки, которые обычно используют политики пропорционального изменения размеров виджетов при изменении размеров форм, можно использовать разделители QSplitter, которые позволяют регулировать размеры виджетов по желанию пользователя.
Разделители бывают вертикальными и горизонтальными. Их применяют в качестве объекта основы окна или его фрагмента.
Пример 2.6. Применение разделителя.
#include <QApplication>
#include <QSplitter>
#include <QLineEdit>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QSplitter splitter(Qt::Horizontal);
splitter.setWindowTitle("Main Window");
QLineEdit *edit1=new QLineEdit("Edit1",&splitter);
QLineEdit *edit2=new QLineEdit("Edit2",&splitter);
splitter.show();
return app.exec();
}
Результат работы программы представлен на рисунке 2.5. Линия между двумя редакторами – ползунок, потянув за который можно изменить соотношение областей, отведенных под каждый редактор.
Рисунок 2.5 – Применение разделителя
Компоновщики всех рассмотренных типов могут вкладываться один в другой в соответствии с реализуемой схемой компоновки окна. Однако при добавлении компоновщика в контейнер другого компоновщика используется не метод addWidget(), а метод addLayout(), например:
layout2->addLayout(layout1);
Вложение различных видов компоновщиков, пружин и ползунков позволяет реализовать практически любые варианты компоновки окна приложения.
2.3 Механизм слотов и сигналов
При использовании средств библиотеки Qt передача сообщений внутри приложения реализуется механизмом слотов и сигналов. Это – наиболее важный механизм Qt, отличающий его от других библиотек интерфейсных элементов C++, например «родной» библиотеки Visual C++ – MFC.
Как уже упоминалось ранее, механизм реализуется метакомпилятором MOC, который генерирует соответствующий код на «чистом» С++.
По правилам Qt любой виджет может посылать сигналы другим виджетам, сообщая им об изменениях, произошедших с ним в процессе функционирования. Чаще всего причиной формирования сигнала бывают действия пользователя. Например, объекты класса QPushButton посылают приложению сигнал clicked(), когда пользователь щелкает мышкой по реализуемой объектом класса кнопке. Причинами генерации сигналов могут быть и достижения каких-либо значений, срабатывания таймеров, действия операционной системы или других приложений.
Посредством специального оператора Qt connect каждый сигнал может быть подключен к одному или нескольким слотам других виджетов. Тогда каждый раз при получении сигнала в виджетах-адресатах будет активизироваться соответствующий обработчик сигналов – слот или последовательно несколько слотов.
Слотом может объявляться любой (перегруженный, виртуальный, общий, защищенный, закрытый) метод, что позволяют подключать его к сигналу. Метод-слот также сохраняет возможность традиционного вызова, не связанного с сигналом.
Точно так же, как один сигнал может быть подключен к нескольким слотам, к одному слоту может быть подключено несколько сигналов. В этом случае приложение одинаково реагирует на указанные при подключении сигналы.
Соединяемые сигналы и слоты должны иметь идентичные сигнатуры, т.е. количество и типы входных аргументов. Исключением является случай, когда сигнал имеет большее число аргументов, чем слот. В этом случае "лишние" аргументы просто не передаются в слот. Если типы входных аргументов не совместимы или сигнал или слот не определены, Qt выдаст предупреждение во время выполнения программы. Точно так же Qt отреагирует, если в сигнатуры сигналов или слотов в макросе connect()включены имена аргументов.
2.3.1 Создание новых слотов и установка связи сигналов со слотами
Программист, применяющий средства Qt , имеет возможность не только использовать предопределенные сигналы и слоты, но и создавать новые. При этом следует следить, чтобы классы, определяющие новые сигналы и слоты, обязательно включали макрос Q _ OBJECT , обрабатываемый MOC .