В статье подробно разобран код приложения, написанного с использованием кроссплатформенного фреймворка Qt основанного на языке C++. Приложение Dynamic Layouts является одним из примеров, входящих в пакет Qt Creator. На примере данного приложения рассматриваются файлы dialog.h и dialog.cpp. Показана работа с классами QDialogButtonBox, QGridLayout и QGroupBox, которые используются в коде приложения. Также дано описание и пояснение к использованным функциям, элементам и методам, что позволяет наиболее полно понять работу с контейнерами, кнопками, диалоговыми окнами и другими элементами GUI.
Ключевые слова: элемент, выпадающий список, кнопка, секция, класс, аргумент метода, диалоговое окно, вызов функции, контейнер, функция, конструктор класса
The article details the code of the application written using a cross-platform Qt framework based on C ++. The Dynamic Layouts application is one of the examples included in the Qt Creator package. The example of this application looks at the dialog.h and dialog.cpp files. It shows the work with the QDialogButtonBox, QGridLayout and QGroupBox classes, which are used in the application code. Also, a description and explanation is given to the functions, elements and methods used, which allows you to fully understand the work with containers, buttons, dialog boxes and other GUI elements.
Keywords: element, drop-down list, button, section, class, method argument, dialog box, function call, container, function, class constructor
Приложение Dynamic Layouts выводит на экран диалоговое окно с тремя секциями (рис.1). В секции «Rotable Widgets» находятся: индикатор прогресса, ползунок, установщик и счетчик. Все элементы связаны по значению — при изменении значения одного из них, синхронно изменяются значения остальных. В секции «Options» расположены элемент надписи и выпадающий список. По клику на значениях выпадающего списка можно целиком перемещать находящуюся внизу секцию с кнопками. В третьей секции находятся три кнопки. При нажатии на кнопку «Rotate Widgets» элементы первой секции сдвигаются на одну позицию по часовой стрелке. При нажатии кнопки «Help» появляется информационное окно с описанием приложения. При нажатии на кнопку «Close» приложение закрывается.
Рис. 1. Диалоговое окно приложения Dynamic Layouts
Основная часть программы находится в файлах dialog.h и dialog.cpp, которые являются объявлением и определением класса Dialog. Класс Dialog наследуется от базового класса диалоговых окон QDialog. Под диалоговым окном обычно понимается отдельное от главного окна программы, дополнительное окно, которое выводится на экран в определенном случае. В программе Dynamic Layouts нет главного окна с меню и панелью клавиш быстрого доступа (ToolBar), как это обычно бывает в программах, использующих диалоговые окна, поэтому самостоятельная роль программы Dynamic Layouts сомнительна. Однако, созданный в программе класс Dialog, может использоваться в качестве шаблона для разработки пользовательских диалоговых окон, поскольку создан по всем правилам создания диалоговых окон профессиональных приложений, и использует большинство функций необходимых при работе с диалоговыми окнами. Назначение и структура диалоговых окон многих приложений зачастую весьма однотипны. Поэтому в библиотеке Qt создано большое количество наследников класса QDialog, со стандартными предназначениями, с помощью которых можно создавать полноценные диалоговые окна, ограничиваясь минимальным количеством строк кода.
Описание файла dialog.h
В качестве классов для управления размещением элементов использованы QGridLayout и QGroupBox. QGridLayout — один из наиболее часто используемых компоновщиков. Его преимущество в удобной форме задания позиции элемента. Форма задается строкой mainLayout->addWidget(rotableGroupBox, 0, 0); путем передачи в аргументы метода, добавления элемента в компоновщик, номера позиции по горизонтали и вертикали.
Совместно с QGridLayout при компоновке элементов используется класс QGroupBox. Он необходим для группировки элементов одного типа в секции с рамками и заголовком. Основное предназначение класса группировки QGroupBox — объединение в группу переключателей (QRadioButtons) и флажков (QCheckBoxes), но из-за удобства использования, его можно использовать для группировки остальных элементов.
Для компоновки кнопок в программе Dynamic Layouts используется класс QDialogButtonBox, который предоставляет методы для быстрого создания кнопок. Удобство использования этого класса при создании диалогов в том, что в нем содержится большое количество стандартных кнопок, для которых определены сигналы, соответствующие их назначению.
Для хранения указателей на элементы первой секции используется контейнер «очередь».
Способ хранения указателей на объекты разных классов в одном контейнере демонстрирует такое свойство языка C++ как полиморфизм. Контейнер rotableWidgets определен как очередь из указателей на объекты класса QWidget. Поскольку классы, на основе которых созданы объекты элементов первой секции, имеют в качестве базового класс QWidget, становится возможным обращение к ним посредством контейнера rotableWidgets. Далее в программе отсутствуют отдельные указатели или ссылки на элементы первой секции, а все обращения к ним происходят через обращение к контейнеру rotableWidgets. Выбор, в качестве контейнера, очереди, обусловлен последовательностью доступа к его элементам. В программе Dynamic Layouts для организации последовательности доступа используется цикл for, при этом обращение происходит с помощью оператора [], как при обращении к обычному массиву, и цикла foreach, который последовательно перебирает элементы контейнера.
Описание файла dialog.cpp
В конструкторе класса dialog по очереди вызываются три функции, создающие секции с элементами:
– createRotableGroupBox() создает секцию Rotable Widgets;
– createOptionsGroupBox() создает секцию Options;
– createButtonBox() создает секцию с кнопками.
По последовательности выполняемых действий эти функции очень похожи. Сначала создается объект группировки элементов. Для секций Rotable Widgets и Options это объект класса QGroupBox. В конструктор класса передаются названия заголовков. Для секции с кнопками создается объект класса QDialogButtonBox. Затем создаются сами элементы. Элементы секции Rotable Widgets создаются с помощью оператора new, который возвращает указатель на созданный элемент. С помощью функции enqueue() указатели сразу загружаются в контейнер очереди. Таким образом, сами объекты находятся в оперативной памяти, доступ к ним осуществляется через указатели, хранящиеся в контейнере rotableWidgets, а прямые ссылки на эти элементы в программе отсутствуют. Так создаются объекты: индикатор прогресса, ползунок, установщик и счетчик. Элементы второй секции создаются обычным способом — указатели на, созданные с помощью операции new, элементы присваиваются, созданным в объявлении класса Dialog, переменным указателя на метку (buttonsOrientationLabel) и выпадающий список (buttonsOrientationComboBox). Для выпадающего списка методом addItem() добавляются элементы списка. В аргументы метода addItem() передается текст, который будет отображаться на элементе списка, и параметр userData. В этом параметре хранится значение переменной типа Orientation, которая может принимать значения Horizontal и Vertical. В зависимости от значения этой переменной, в слоте, обрабатывающем сигнал изменения значения выпадающего списка, будет задана ориентация секции кнопок. В секции кнопок создание объектов кнопок совпадает с добавлением их в компоновщик buttonBox. Метод компоновщика addButton(), одновременно с добавлением кнопки в компоновщик, создает её объект и возвращает на неё указатель. В качестве аргументов перегруженного метода addButton() могут быть, либо указатель на кнопку и её роль, либо строка с названием, которое будет отражаться на кнопке и роль, либо обозначение одной из стандартных кнопок для которой роль задана по умолчанию. Стандартные кнопки «Help» и «Close» создаются последним способом, а кнопка «Rotate Widgets» создается путем передачи в аргументы метода, строки с названием и роли. Затем сигналы созданных элементов подключаются к нужным слотам. В секции «Rotable Widgets» элементы связаны по кругу — сигнал valueChanged(int) каждого элемента связан со слотом setValue(int) следующего элемента. Для подключения всех элементов к друг-другу использован цикл for, в каждой итерации которого, происходит вызов функции connect(). Первым и третьим аргументом этой функции необходимо задать указатель на объект выславший сигнал и объект получатель сигнала. Эти указатели извлекаются из очереди при помощи оператора.
В аргументы этого оператора, в случае объекта отправителя, передается значение счетчика цикла, а в случае объекта получателя передается результат операции (i + 1) % n. Использование операции остатка от деления гарантирует, что на последней итерации цикла элемент, стоящий в конце очереди, подключится к первому. В функции createOptionsGroupBox() вызовом функции connect() происходит подключения сигнала, сообщающего об изменении значения выпадающего списка, со слотом buttonsOrientationChanged(int) реализованным в классе Dialog. В аргументах передается значение индекса, выбранного пользователем, элемента выпадающего списка. В функции createButtonBox(), также без всяких излишеств, сигналы кнопок подключаются к, реализованным в классе Dialog, слотам.
На следующем этапе создаются объекты менеджеров компоновки и добавляются элементы. В случае с кнопками, класс QDialogButtonBox сам имеет функции компоновщика, поэтому в функции createButtonBox() этот этап не требуется. В секции «Rotable Widgets» элементы меняют свою позицию после нажатия на кнопку «Rotate Widgets». Процесс перемещения реализован путем перестановки элементов в компоновщике. Сама перестановка реализована в слоте rotateWidgets(). Чтобы не писать лишний код, в функции createRotableGroupBox(), для первой расстановки элементов в компоновщик, вызывается слот rotateWidgets(). В функции createOptionsGroupBox() все происходит стандартным путем. Создается объект компоновщика (). В него добавляются элементы, позиции которых задаются аргументами метода addWidget(). И слой с элементами устанавливается на виджет группировки. На этом все функции по созданию визуальных элементов завершаются. В конструкторе три слоя добавляются на главный слой (mainLayout). Главный слой устанавливается в качестве переднего плана диалогового окна создаваемого классом Dialog. Методом setSizeConstraint() устанавливается минимальный режим для размера окна.
Строка Q_ASSERT(rotableWidgets.count() % 2 == 0); следит за тем, чтобы количество элементов в контейнере было четным. Только при таком условии расстановка элементов будет корректной. Если это условие не выполняется, выводится сообщение об ошибке и программа завершается. Строка rotableWidgets.enqueue(rotableWidgets.dequeue()); перемещает один элемент из начала очереди в конец, при этом все элементы сдвигаются на одну позицию. Метод dequeue() удаляет элемент из начала очереди и передает его в качестве возвращаемого значения. Метод enqueue() добавляет элемент в конец очереди.
Цикл for заполняет компоновщик элементами очереди. В каждой итерации цикла устанавливается сразу по два элемента. Один элемент из начала очереди и один из конца. На следующей итерации устанавливаются элементы, находящиеся на одну позицию ближе к середине очереди.
Слот buttonsOrientationChanged() обрабатывает сигнал, который высылается, когда пользователь изменяет значение выпадающего списка. В аргументах передается индекс выбранного пользователем элемента списка. В слоте buttonsOrientationChanged() создается переменная типа Orientation и инициализируется значением, возвращаемым методом itemData(). Метод itemData(), которому в аргументы передается индекс элемента, извлекает из элемента значение ориентации. С помощью условного оператора if значение ориентации выпадающего списка сравнивается с текущей ориентацией секции кнопок. Если их значения совпадают, управление переходит на оператор return и происходит выход из слота buttonsOrientationChanged(). С помощью метода removeWidget() из главного слоя удаляется виджет с кнопками. Создается переменная oldSizeHint для сохранения значений размеров, которые были необходимы для размещения секции кнопок при старой ориентации. Она равна сумме размеров секции кнопок и размера пространства между элементами. Новая ориентация для набора кнопок устанавливается методом setOrientation(). После установки новой ориентации, создается переменная для хранения значений, которые потребуются для размещения секции кнопок при новой ориентации. Оператор if определяет, в какую позиция на слое mainLayout нужно разместить секцию кнопок с новой ориентацией. После размещения секции кнопок методом resize() задаются новые размеры для главного окна. Если кнопки размещены снизу, то из ширины прежнего размера вычитается значение ширины, хранящееся в oldSizeHint, а к значению высоты прибавляется значение newSizeHint. Если кнопки добавляются сбоку, все происходит наоборот.
Слот help() выводит на экран модальное информационное окно с текстом. Окно выводится вызовом функции information(), в аргументы, которой передается указатель на объект предка и отображаемый текст.
Таким образом, на примере разобранного кода приложения Dynamic Layouts, показана работа с контейнерами, кнопками, диалоговыми окнами и другими элементами GUI, что упрощает работу и понимание некоторых элементов фреймворка Qt.
Литература:
- Шлее М. Qt 5.3 Профессиональное программирование на C++. — СПб.: БХВ-Петербург, 2015.
- Саммерфилд М. Qt. Профессиональное программирование. Разработка кроссплатформенных приложений на С++. — СПб.: Символ-Плюс, 2011.
- Ж. Бланшет, М. Саммерфилд. Qt 4: Программирование GUI на C++. 2-е дополненное издание. — М.: Кудиц-пресс, 2008.
- Алексеев Е. Р., Злобин Г. Г., Костюк Д. А., Чеснокова О. В., Чмыхало А. С. Программирование на языке C++ в среде Qt Creator. — М.: ALT Linux, 2015.