ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 09.11.2023
Просмотров: 56
Скачиваний: 1
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
Министерство образования и науки Российской Федерации
Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования
ПЕТРОЗАВОДСКИЙ ГОСУДАРСТВЕННЫЙ
УНИВЕРСИТЕТ
А. В. Бородин, А. В. Бородина
СРЕДСТВА РАЗРАБОТКИ
ГРАФИЧЕСКИХ ИНТЕРФЕЙСОВ
ПОЛЬЗОВАТЕЛЯ
Учебное пособие
Петрозаводск
Издательство ПетрГУ
2012
ББК 32.973
УДК 004
Б833
Печатается по решению редакционно-издательского совета
Петрозаводского государственного университета
Издается в рамках реализации комплекса мероприятий
Программы стратегического развития ПетрГУ на 2012–2016 г.г.
Р е ц е н з е н т ы:
канд. техн. наук Р. В. Сошкин;
канд. физ.-мат. наук, доцент К. А. Кулаков
Бородин А. В.
Б833
Средства разработки графических интерфейсов пользовате- ля : учебное пособие / А. В. Бородин, А. В. Бородина. — Пет- розаводск : Изд-во ПетрГУ, 2012. — 77 c.
ISBN 978-5-8021-1537-4
Учебное пособие предназначено для студентов математическо- го факультета первого курса направлений подготовки «Прикладная математика и информатика», «Информационные системы и техно- логии»
ББК 32.973
УДК 004
ISBN 978-5-8021-1537-4
© Петрозаводский государственный университет, 2012
© Бородин А. В., Бородина А. В., 2012
3
Содержание
Введение
4
§1.Графические интерфейсы пользователя
5
§2.Семейство библиотек Qt
14 2.1 Первая Qt-программа . . . . . . . . . . . . . . . . . . . .
14 2.2 Обзор средств Qt . . . . . . . . . . . . . . . . . . . . . .
16 2.2.1 Виджеты и конструирование интерфейсов . . .
16 2.2.2 Класс QObject и метаобъектный компилятор . .
18 2.2.3 Прочие возможности . . . . . . . . . . . . . . . .
23
§3.Средства визуального проектирования
25
§4.Компоновка и размещение виджетов
35 4.1 Использование компоновщиков . . . . . . . . . . . . . .
35 4.2 Контейнерные виджеты . . . . . . . . . . . . . . . . . .
39
§5.Обзор библиотеки виджетов Qt
41 5.1 Кнопки . . . . . . . . . . . . . . . . . . . . . . . . . . . .
42 5.2 Надписи . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44 5.3 Инструменты ввода текста . . . . . . . . . . . . . . . . .
45 5.4 Инструменты ввода чисел . . . . . . . . . . . . . . . . .
47
§6.Создание собственных виджетов
48 6.1 Создание виджета композицией имеющихся . . . . . . .
48 6.2 Отрисовка на поверхности виджета . . . . . . . . . . . .
50 6.3 Пример создания виджета с нуля . . . . . . . . . . . . .
54
§7.Работа с 2D-графикой
59 7.1 Graphics View Framework . . . . . . . . . . . . . . . . . .
59 7.2 Управление элементами сцены . . . . . . . . . . . . . .
62 7.3 Создание собственных элементов сцены . . . . . . . . .
67
§8.Интернационализация приложений
73
4
Введение
Введение
Развитие информационных технологий и рост числа пользовате- лей компьютеров предъявляют новые требования к пользовательским интерфейсам. Перед разработчиками программного обеспечения сто- ит непростая задача обеспечить низкий порог вхождения для широко- го круга не обладающих достаточной квалификацией и не прошедших специальную подготовку в использовании ЭВМ лиц за счет упроще- ния средств диалога с машиной, использования интуитивно понятных графических метафор, унификации интерфейсов.
Таким образом, овладение средствами разработки современных графических интерфейсов пользователя является необходимым эле- ментом подготовки разработчика прикладного программного обеспе- чения.
Пособие представляет собой обзор основных концепций, лежащих в основе графических интерфейсов, и краткое введение в программи- рование с использованием фреймворка Qt, предоставляющего набор библиотек и утилит для создания прикладных программ, в том числе,
приложений с графическим интерфейсом.
Выбор Qt среди широкого круга альтернатив обусловлен двумя основными причинами. Во-первых, Qt является продуктом с откры- тым кодом, таким образом его использование не может быть ограниче- но изменением политики компании-разработчика. Во-вторых, Qt из- начально разрабатывался как кроссплатформенный инструмент: на- писанные с использованием этого фреймворка приложения переноси- мы на уровне исходного кода между всеми популярными настольны- ми операционными системами, более того, Qt можно использовать и для разработки для мобильных устройств, таких как смартфоны и планшетные компьютеры. Немаловажным является факт наличия в свободном доступе весьма подробной документации разработчика (см.
[6]).
Материалы пособия используются в Петрозаводском государствен- ном университете на математическом факультете в модуле «Средства разработки графических интерфейсов» дисциплины «Информатика»,
читаемой студентам I курса направлений подготовки «Прикладная математика и информатика» и «Информационные системы и техно- логии».
Графические интерфейсы пользователя
5
§1.
Графические интерфейсы пользовате- ля
Графическими интерфейсами пользователя (англ. Graphical User
Interface, GUI) считают такие, в которых элементы интерфейса пред- ставлены в виде визуальных объектов, т. е. графических изображений.
Элементы интерфейса, используя метафоры, несут информацию об их назначении и способе использования, обеспечивая обучение поль- зователя в процессе работы. Интерфейсы, обладающие этим свой- ством, принято называть интуитивно понятными.
Ко всем видимым элементам интерфейса обеспечивается произ- вольный достум посредством широкого спектра устройств ввода —
клавиатуры, указателя «мышь», сенсорного экрана и др.
Первые разработки в области графических интерфейсов относят- ся к семидесятым годам XX века и, как правило, связываются с ком- пьютером Alto, разработанным в исследовательских лабораториях ком- пании XEROX в Пало Альто, Калифорния. Тогда же впервые появ- ляется аббревиатура WIMP (Windows, Icons, Menus, Pointing Device),
концептуально характеризующая графический интерфейс пользова- теля.
Коммерческий успех к компьютерам, операционные системы ко- торых предоставляли графический интерфейс, пришел в восьмидеся- тые годы XX века и связан с появлением таких разработок, как Star компании Xerox и Lisa компании Apple.
Тогда же были предпринты первые попытки упорядочить разра- ботку графических интерфейсов пользователя, в частности, следует отметить руководство IBM Common User Access Specification.
Одной из первых оконных систем, обеспечивающей стандартные инструменты и протоколы для построения графических интерфейсов,
стала X Window System, разрабатываемая с 1984 года и используемая по настоящее время в UNIX-подобных ОС. X Window System обес- печивает базовые функции графической среды: отрисовку и переме- щение окон на экране, взаимодействие с устройствами ввода, такими как, например, мышь и клавиатура.
X Window System не определяет деталей интерфейса пользовате- ля — этим занимаются менеджеры окон. С другой стороны, в этой системе предусмотрена сетевая прозрачность: графические приложе-
6
Графические интерфейсы пользователя ния могут выполняться на другой машине в сети, а их интерфейс при этом будет передаваться по сети и отображаться на локальной ма- шине пользователя. В контексте X Window System термины «клиент»
и «сервер» имеют непривычное для многих пользователей значение:
«сервер» означает локальный дисплей пользователя (дисплейный сер- вер), а «клиент» — программу, которая этот дисплей использует (она может выполняться на удалённом компьютере).
Архитектура X Window System проиллюстрирована на рис. 1.
Рис. 1: Архитектура X Window System
X Window System предоставляет программный интерфейс для со- здания приложений с графическим интерфейсом посредством исполь- зования библиотеки XLib. Однако, программирование на уровне XLib слишком многословно и перегружает программиста необходимостью реализовывать вручную даже примитивные детали интерфейса.
Рассмотрим достаточно простую XLib-программу, изображающую на экране простое окно с надписью.
Пример 1.1
Графические интерфейсы пользователя
7
#include
#include
#include
#include
#include
#include
#define X 0
#define Y 0
#define WIDTH 400
#define HEIGHT 300
#define WIDTH_MIN 50
#define HEIGHT_MIN 50
#define BORDER_WIDTH 1
#define TITLE "Example"
#define ICON_TITLE "Example"
#define PRG_CLASS "Example"
/* Сообщает оконному менеджеру параметры окна */
static void SetWindowManagerHints(Display * display,
char *PClass, char *argv[], int argc,
Window window, int x, int y,
int win_wdt, int win_hgt,
int win_wdt_min, int win_hgt_min,
char *ptrTtl, char *ptrITtl, Pixmap pixmap)
{
XSizeHints size_hints;
XWMHints wm_hints;
XClassHint class_hint;
XTextProperty winnm, icnm;
if (!XStringListToTextProperty (&ptrTtl, 1, &winnm)
|| !XStringListToTextProperty (&ptrITtl, 1, &icnm)) {
puts ("No memory!\n");
exit (1);
}
8
Графические интерфейсы пользователя size_hints.flags = PPosition | PSize | PMinSize;
size_hints.min_width = win_wdt_min;
size_hints.min_height = win_hgt_min;
wm_hints.flags = StateHint | IconPixmapHint | InputHint;
wm_hints.initial_state = NormalState;
wm_hints.input = True;
wm_hints.icon_pixmap = pixmap;
class_hint.res_name = argv[0];
class_hint.res_class = PClass;
XSetWMProperties (display, window, &winnm,
&icnm, argv, argc, &size_hints,
&wm_hints, &class_hint);
}
/* Обработчик ошибки */
int MyXlibIOErrorHandler(Display *d)
{
XCloseDisplay(d);
exit(0);
}
/* Основная программа */
int main(int argc, char *argv[])
{
Display *display;
int ScreenNumber;
GC gc;
XEvent report;
Window window;
if ((display = XOpenDisplay(NULL)) == NULL) {
puts ("Can not connect to the X server!\n");
exit (1);
}
ScreenNumber = DefaultScreen(display);
Графические интерфейсы пользователя
9
window = XCreateSimpleWindow(display,
RootWindow(display, ScreenNumber),
X, Y, WIDTH, HEIGHT, BORDER_WIDTH,
BlackPixel(display, ScreenNumber),
WhitePixel(display, ScreenNumber));
SetWindowManagerHints (display, PRG_CLASS, argv, argc,
window, X, Y, WIDTH, HEIGHT, WIDTH_MIN,
HEIGHT_MIN, TITLE, ICON_TITLE, 0);
XSelectInput (display, window, ExposureMask
| StructureNotifyMask);
XMapWindow (display, window);
XSetIOErrorHandler (&MyXlibIOErrorHandler);
while (1) {
XNextEvent (display, &report);
switch (report.type) {
case Expose:
if (report.xexpose.count != 0) break;
gc = XCreateGC(display, window, 0, NULL);
XSetForeground(display, gc,
BlackPixel(display, 0));
XDrawString (display, window, gc, 20, 50,
"Hello, world!", strlen ("Hello, world!"));
XFreeGC (display, gc);
XFlush (display);
break;
default:
fprintf (stderr, "%d\n", report.type);
}
}
return 0;
}
10
Графические интерфейсы пользователя
Используя XLib, программист полностью контролирует свойства оконного приложения, именно этим объясняется большое количество параметров в вызовах функций. Также программист самостоятельно выполняет проверку наступления тех или иных событий, организуя бесконечный цикл опроса обработки. На полотне окна программист также самостоятельно вынужден отрисовывать необходимые элемен- ты.
Из приведенного примера можно извлечь следующие выводы.
• Поскольку любая программа с графическим интерфейсом явля- ется событийно-ориентированной, то есть большую часть време- ни проводит в состоянии «сна», выполняя те или иные функции лишь в ответ на активность пользователя, то она, так или иначе,
будет включать цикл обработки событий, подобный приведен- ному в примере. Следовательно, необходимости в включении этого цикла вручную в каждой программе нет — его можно реализовать в виде отдельной функции библиотеки, предостав- ляющей программные средства создания графического интер- фейса. При этом код обработки событий оформляется в виде от- дельных функций, регистрируемых до входа в цикл обработки событий. Такие функции называют функциями обратного вы- зова (callback), так как в программе отсутствуют явные вызовы этих функций, они вызываются из функции, реализующей аб- стракцию основного цикла при наступлении соответствующего события.
• Большая часть параметров оконного приложения либо контро- лируется оконным менеджером, либо имеет стандартные зна- чения (например, цветовая гамма может определяться темой оформления). Поэтому программный интерфейс можно суще- ственно упростить.
Исходя из представленных соображений, над XLib был разработан набор высокоуровневых библиотек Xt (так называемый «тулкит»),
скрывающий сложность XLib за оберточными вызовами. Рассмотрим пример Xt-программы, предоставляющий ту же функциональность,
что и пример 1.1.
Пример 1.2
Графические интерфейсы пользователя
11
#include
#include
#include
#include
void DrawHelloString (Widget prWidget, XtPointer pData,
XEvent *prEvent, Boolean *pbContinue)
{
Display *prDisplay = XtDisplay (prWidget);
Window nWindow = XtWindow (prWidget);
GC prGC;
if (prEvent->type == Expose) {
prGC = XCreateGC (prDisplay, nWindow, 0, NULL);
XDrawString (prDisplay, nWindow, prGC, 10, 50,
"Hello, world!", strlen ("Hello, world!"));
XFreeGC (prDisplay, prGC);
}
}
void main (int argc, char **argv)
{
Arg args[2];
Widget toplevel, prCoreWidget;
toplevel = XtInitialize(argv[0], "GUI Example",
NULL, 0, &argc, argv);
prCoreWidget = XtCreateWidget ("Core", widgetClass,
toplevel, NULL, 0);
XtSetArg(args[0], XtNwidth, 400);
XtSetArg(args[1], XtNheight, 300);
XtSetValues(prCoreWidget, args, 2);
XtManageChild (prCoreWidget);
XtAddEventHandler(prCoreWidget, ExposureMask,
False, DrawHelloString, NULL);
12
Графические интерфейсы пользователя
XtRealizeWidget(toplevel);
XtMainLoop();
}
Типовой цикл обработки событий заменен единственным вызовом
XtMainLoop(). При этом обработчики событий, реализованные в виде функций обратного вызова, связываются с сигналами событий (реги- стрируются) заранее посредством вызова XtAddEventHandler().
Также в Xt введено понятие виджета (widget = visual gadget) — ти- пового элемента графического интерфейса. Однако, отрисовка стро- ки по-прежнему осуществляется с помощью низкоуровневого XLib- вызова XDrawString().
Современные инструменты разработки графических интерфейсов также реализуют абстракцию основного цикла обработки событий и предоставляют разработчику богатую библиотеку виджетов. Следую- щий пример демонстрирует использование средств библиотеки GTK+.
Пример 1.3
#include
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *label;
/* Инициализируем подсистему GTK+ */
gtk_init(&argc, &argv);
/* Создаем окно */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* Привязываем обработчик нажатия кнопки закрытия окна */
g_signal_connect(G_OBJECT (window), "destroy",
G_CALLBACK (gtk_main_quit), NULL);
/* Создаем кнопку с надписью */
label = gtk_label_new("Hello World");
Графические интерфейсы пользователя
13
/* Помещаем кнопку в окно (контейнер) */
gtk_container_add(GTK_CONTAINER (window), label);
/* Отрисовываем окно и все принадлежащие ему объекты */
gtk_widget_show_all(window);
/* Основной цикл обработки событий */
gtk_main();
return 0;
}
14
Семейство библиотек Qt
§2.
Семейство библиотек Qt
2.1
Первая Qt-программа
По сложившейся традиции руководств по программированию, об- зор возможностей семейства библиотек Qt начнем с программы, вы- водящей на экран приветствие «Hello, world!».
Пример 2.1
#include
#include
int main(int argc, char *argv[])
{
// Создаем экземпляр приложения
QApplication a(argc, argv);
// Создаем надпись
QLabel label("Hello World");
// Отображаем надпись на экране label.show();
// Основной цикл обработки событий return a.exec();
}
Текст программы содержит включения файлов заголовков с опре- делениями необходимых классов (QApplication и QLabel).В Qt на каж- дый класс приходится в точности один заголовочный файл.
Выполнение программы начинается с создания объекта класса
QApplication, представляющего абстракцию графического приложе- ния. Ни одна графическая программа не обходится без QApplication,
так как именно в этом классе реализован цикл обработки событий,
определяющий основной алгоритм работы графического приложения.
Мы передаем управление в цикл обработки событий вызовом мето- да exec() класса QApplication. Именно в exec() программа проводит
Семейство библиотек Qt
15
большую часть времени работы, ожидая пользовательской реакции или другого события.
В конструктор QApplication мы передаем аргументы командной строки. Qt-приложение способно распознавать некоторые параметры,
заданные в командной строке и адаптировать поведение в соответ- ствии с ними.
После создания объекта QApplication создается объект QLabel,
представляющий простую текстовую надпись. Текст для отображения передается через конструктор класса.
Метод show() заставляет Qt-приложение отрисовать надпись на экране, при этом, так как надпись не имеет родительского объекта,
то сама формирует окно.
Цикл обработки событий прекращается при выполнении метода quit() класса QApplication, например, если пользователь закрывает окно. В этом случае приложение завершается.
Так как Qt поддерживается на многих различных платформах, от- личающихся организацией процесса сборки, разработчиками Qt пред- ложен специальный проектно-ориентированный инструмент сборки —
qmake.
Программа qmake генерирует файлы сборки Makefile для каж- дой конкретной платформы на основе специального файла проекта
(*.pro), который описывает приложение в независимой от операцион- ной системы и среды программирования форме.
Сохраним нашу первую программу под именем main.cpp в ката- логе myFirstQtProject и сгенерируем в нем шаблон файла проекта с помощью той же программы qmake:
Пример 2.2
user@linux:/myFirstQtProject> qmake -project
В результате будет построен шаблон файла проекта:
Пример 2.3
#myFirstQtProject/myFirstQtProject.pro
16
Семейство библиотек Qt
#####################################
# Automatically generated by qmake
#####################################
TEMPLATE = app
CONFIG -= moc
DEPENDPATH += .
INCLUDEPATH += .
# Input
SOURCES += main.cpp
Файл проекта состоит из директив, в данном примере директива
TEMPLATE указывает, что проект представляет собой приложение
(app), а SOURCES содержит перечень файлов исходного кода проекта
(в нашем примере только main.cpp).
Повторный вызов qmake (теперь без параметров) создаст зависи- мый от платформы файл сборки (Makefile), который можно исполь- зовать для получения исполняемого файла:
Пример 2.4
user@linux:/myFirstQtProject> qmake user@linux:/myFirstQtProject> make user@linux:/myFirstQtProject> ./myFirstQtProject
2.2
Обзор средств Qt
2.2.1
Виджеты и конструирование интерфейсов
Основу библиотеки графического интерфейса составляют видже- ты — классы, представляющие визуальные объекты. Виджеты обра- зуют иерархию классов. Базовым классом для всех виджетов в Qt яв- ляется QWidget. При этом имеется более шестидесяти производных от QWidget классов, реализующих типовые элементы управления. До- полнительно программист имеет возможность создания собственных виджетов, расширяя функциональность элементов стандартного на- бора.
Семейство библиотек Qt
17
Визуальная структура интерфейсов формируется посредством кон- тейнерных классов, позволяющих организовывать виджеты в иерар- хии. Таким образом, виджеты могут содержать в себе другие видже- ты.
Харахтерными особенностями виджетов являются следующие:
• виджет занимает прямоугольную область экрана;
• виджет может получать сообщения о событиях, например, от устройств ввода;
• виджет может отправлять сигналы об изменении состояния.
Все виджеты имеют набор общих свойств, унаследованных от ба- зового класса QWidget:
• enabled — возможна ли реакция на ввод пользователя;
• visible — видим ли виджет (или скрыт).
Действие этих свойств распространяется на вложенные виджеты.
В примере 2.1 интерфейс представляен единственным виджетом
QLabel, поэтому проблем с его расположением нет. Однако интер- фейс приложения может быть сконструирован из десятков виджетов,
и проблема их взаимного расположения окажется весьма серьезной.
Qt предоставляет так называемые средства компоновки, облегча- ющие процесс взаимной расстановки виджетов в пространстве окна.
Следующий пример организовывает две надписи вертикально:
Пример 2.5
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Создаем окно
QWidget window;
18
Семейство библиотек Qt
// Создаем основной компоновщик окна -
// вертикальный компоновщик
QVBoxLayout* mainLayout = new QVBoxLayout(&window);
// Создаем две надписи
QLabel* label1 = new QLabel("One");
QLabel* label2 = new QLabel("Two");
// Организуем надписи с помощью компоновщика mainLayout->addWidget(label1);
mainLayout->addWidget(label2);
// Отображаем окно window.show();
return a.exec();
}
Подробнее вопросы компоновки и виды компоновщиков рассмот- рены в четвертом параграфе. К рассмотренному примеру следует за- метить, что только для окна был вызван метод show(), надписи, вло- женные в окно, автоматически становятся для него «дочерними» объ- ектами и визуализируются автоматически при отображении родителя.
2.2.2
Класс QObject и метаобъектный компилятор
Базовым классом большинства классов семейства библиотек Qt,
в частности, всех виджетов, является QObject. Исключениями явля- ются только классы, для которых важна легковесность (например,
графические примитивы и контейнеры данных). Именно QObject реа- лизует большую часть возможностей, характерных для Qt: обработку событий, сигналы и слоты, свойства объектов, управление памятью.
Каждый унаследованный от QObject имеет так называемый ме- таобъект, содержащий информацию об имени класса, связях и т. д.
Таким образом Qt вводит механизмы интроспекции в язык C++.
Метаданные собираются на этапе трансляции специальной про- граммой — метаобъектным компилятором moc. Программа moc явля-
Семейство библиотек Qt
19
ется препроцессором, обрабатывающим файлы заголовков проекта и конструирующим вспомогательные файлы исходного кода (moc_*.cpp),
также включаемые в проект. При использовании системы qmake ге- нерация этих файлов осуществляется прозрачно для программиста.
Программа moc извлекает из файлов заголовков специальные мак- росы (например, в следующем примере, Q_OBJECT, Q_CLASSINFO),
а также ключевые слова Qt, не являющиеся элементами языка C++:
signals, slots, emit и др. Обработанные файлы содержат только кон- струкции C++ и могут быть скомпилированы обычным компилято- ром C++.
Макрос Q_OBJECT должен присутствовать в любом классе, на- следуемом от QObject.
Пример 2.6
class MyClass : public QObject
{
Q_OBJECT
Q_CLASSINFO("author", "John Doe")
public:
MyClass(const Foo &foo, QObject *parent=0);
Foo foo() const;
public slots:
void setFoo( const Foo &foo );
signals:
void fooChanged( Foo );
private:
Foo m_foo;
};
Класс QObject позволяет создавать свойства с методами чтения и записи. Свойство не просто представляет некоторое поле класса,
предназначенное для хранения значения. В результате установки зна- чения свойства визуальный объект, возможно должен быть модифи- цирован (например, при изменении цвета). Поэтому свойству соответ- ствует пара методов чтения и установки значения, выполняющие и соответствующий дополнительный код — так называемые «геттер» и
«сеттер». В соответствии с соглашениями имя геттер-метода должно
20
Семейство библиотек Qt совпадать с именем свойства. Имя сеттер-метода начинается со слова set, за которым следует имя свойства с заглавной буквы.
Пример 2.7
class QLabel : public QFrame
{
Q_OBJECT
// Создаем свойство text с геттером text
// и сеттером setText
Q_PROPERTY(QString text READ text WRITE setText)
public:
QString text() const;
public slots:
void setText(const QString &);
};
В примере 2.5 объект класса QWidget, представляющий окно, со- здан на стеке, а под компоновщик и надписи память выделена динами- чески. Это не случайно. Вообще говоря, C++ не предусматривает ав- томатического управления памятью, однако в Qt некоторые подобные возможности введены. Объекты, наследующие от класса QObject (в частности, виджеты) могут организовываться в древовидные иерар- хии, имея родителя (parent object) и дочерних объектов (child objects).
Если такой объект уничтожается (вызывается деструктор), то Qt ав- томатически уничтожает все дочерние объекты. Те, в свою очередь,
уничтожают свои дочерние объекты и так далее. Это освобождает программиста от необходимости отслеживать и освобождать занятую дочерними объектами память вручную.
Однако для того, чтобы автоматическое управление памятью мог- ло быть реализовано, память под дочерние объекты должна быть вы- делена на куче динамической памяти.
Любое нетривиальное графическое приложение должно иметь воз- можность отреагировать на пользовательские действия, а значит долж- на существовать система коммуникации между объектами. В отличие от многих инструментов создания графических интерфейсов, в Qt не используются функции обратного вызова для обеспечения реакции
Семейство библиотек Qt
21
на события, а применяется более совершенная технология сигналов и слотов.
И сигнал, и слот являются методами класса, за тем исключением,
что тело сигнала программист не реализует, его создает метаобъект- ный компилятор. Тип возвращаемого значения сигнала — void. Слот может иметь отличный от void тип возвращаемого значения, но ис- пользовать возвращаемое значение можно только, если слот вызыва- ется как обычная функция, а не по сигналу. Сигналы в определении класса объявляются в секции signals, слоты — в секции slots. Отпра- вить сигнал из кода можно с помощью ключевого слова emit.
Сигнатуры связываемых сигнала и слота должны либо совпадать,
либо в сигнале может присутствовать часть дополнительных парамет- ров, которые будут проигнорировать в слоте. В общем случае верно правило: «Qt может игнорировать часть данных, но не может полу- чить данные из ничего».
В следующем примере сигнал, отправляемый кнопкой в момент клика, связывается с стандартным слотом quit() объекта QApplication.
Пример 2.8
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Создаем и отрисовываем кнопку с надписью "Quit"
QPushButton button("Quit");
button.show();
// Связываем сигнал clicked() и слот quit()
QObject::connect(&button, SIGNAL(clicked()),
&a, SLOT(quit()));
return a.exec();
}
22
Семейство библиотек Qt
Статическая функция connect() класса QObject выполняет связы- вание сигнала и слота. Сигнал — функция, уведомляющая о наступле- нии события, слот — функция, получающая этот сигнал и корректно обрабатывающая его. Функция connect() получает четыре аргумента:
объект-отправитель, сигнал, объект-получатель, слот. При связыва- нии необходимо использовать макросы SIGNAL() и SLOT().
В примере 2.8 никакой дополнительной информации из сигнала в слот не передается. Рассмотрим более реальный пример. Пусть ин- терфейс содержит три виджета: QLabel, QSpinBox и QSlider. Послед- ние два обеспечивают просто различные способы ввода целых чисел.
Свяжем эти виджеты друг с другом таким образом, чтобы все они отображали одно и то же число.
Пример 2.9
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Создаем элементы интерфейса
QWidget window;
QVBoxLayout* mainLayout = new QVBoxLayout(&window);
QLabel* label = new QLabel("0");
QSpinBox* spinBox = new QSpinBox;
QSlider* slider = new QSlider(Qt::Horizontal);
// Выполняем компоновку mainLayout->addWidget(label);
mainLayout->addWidget(spinBox);
mainLayout->addWidget(slider);
// Выполняем связывание сигналов и слотов
Семейство библиотек Qt
23
QObject::connect(spinBox, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)));
QObject::connect(spinBox, SIGNAL(valueChanged(int)),
slider, SLOT(setValue(int)));
QObject::connect(slider, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)));
QObject::connect(slider, SIGNAL(valueChanged(int)),
spinBox, SLOT(setValue(int)));
window.show();
return a.exec();
}
Сигналы valueChanged() отправляются виджетами ввода целых чисел при изменении состояния (хранимого значения), слот setValue()
позволяет установить значение виджета ввода целых чисел вручную,
слот setNum() позволяет заменить текст надписи на заданное число- вое значение.
2.2.3
Прочие возможности
Контейнеры данных — классы, обеспечивающие возможности кон- тейнеров C++ STL (абстракции структур данных), но обладающих большей функциональностью.
В качестве примера можно привести QString — контейнер для хранения строк и манипуляций над ними. В отличие от своего STL- аналога string, QString хранит и обрабатывает строки в Unicode, при- меняет механизм неявного совместного использования и т.д.
Другие примеры контейнеров Qt — QVector, QList, QLinkedList,
QStringList, QMap, QHash, QStack, QQueue и другие.
Различные операционные системы могут иметь различные интер- фейсы доступа к носителям данных. Например, в ОС Windows ком- поненты пути к файлу разделяются символом обратной косой черты,
а в ОС Linux — обычной косой черты; в ОС Windows приложения обычно размещаются в каталоге Program Files, а в ОС Linux могут храниться, например, в /usr/bin.
24
Семейство библиотек Qt
Кроссплатформенная система программирования, такая как Qt,
должна предоставлять программисту некий унифицированный интер- фейс доступа к файлам и каталогам, не зависящий от деталей кон- кретной целевой системы.
Qt имеет набор классов для представления объектов на накопите- лях (QDir, QFile), потокового ввода-вывода (QTextStream, QDataStream),
а также богатый набор функций, значительно упрощающих задачу программиста.
Следует также отметить, что возможности Qt гораздо шире биб- лиотеки функций для создания графического интерфейса пользова- теля. Qt включает несколько модулей, реализующих классы для ор- ганизации обмена данными по сети, обработки структурированных
XML-данных, доступа к базам данных и даже работы с подсистемами мобильных устройств, такими как GPS-приемник или акселерометр.
Средства визуального проектирования
25
§3.
Средства визуального проектирования
Qt предоставляет разработчику не только набор библиотек, но и комплекс утилит, позволяющих, в частности, значительно сократить объем написанного вручную кода за счет использования визуальных средств.
В настоящее время программисту доступны для загрузки как биб- лиотеки и утилиты в отдельности, так и комплект разработчика — Qt
SDK, включающий и то, и другое. Комплект разработчика поставля- ется с интегрированной средой Qt Creator, объединяющей возможно- сти практически всех утилит, входящих в комплект, а также предо- ставляющий редактор кода, средства интеграции с отладчиком и си- стемами управления версиями и т.п. К числу основных утилит Qt сле- дует отнести Qt Designer — визуальный редактор форм, Qt Assistant
— средство организации справочной информации, Qt Linguist — ин- струментарий создания многоязычного интерфейса приложения.
Одним из основных принципов Qt выражен слоганом «пиши мень- ше — создавай больше» (code less — create more). Для наглядности поясним этот принцип на очень показательном примере создания про- стого приложения, заимствованном из [5], попутно рассмотрев основ- ные возможности дизайнера форм.
Разрабатываемое приложение представляет собой простейшую те- лефонную книгу.
Главное окно приложения отображает перечень записей (пар имя–
номер), а также предоставляет интерфейс для управления записями:
кнопку «Add» для добавления новой записи, кнопку «Edit» для ре- дактирования записи, кнопку «Del» для удаления выделенной записи и кнопку «Clear» для удаления всех записей.
По нажатию на кнопку «Add» или «Edit» открывается вспомога- тельное диалоговое окно, позволяющее ввести или отредактировать имя и номер. Диалоговое окно имеет две кнопки — «Ok» для приня- тия изменений и «Cancel» для отмены.
Схематично интерфейс приложения изображен на рис. 2.
Откроем Qt Creator. Внешний вид стартовой страницы показан на рис 3. Помимо доступа к среде разработки, начальная страница открывает программисту богатую коллекцию примеров и докумен- тацию. Для начала работы выберем вариант «Создать проект...». С
помощью открывшегося мастера проекта последовательно устанавли-
26
Средства визуального проектирования
Рис. 2: Набросок интерфейса пользователя ваем следующем параметры:
1. в окне «Новый проект» выбираем вариант «Проект Qt Widget
/ GUI приложение Qt»;
2. в окне «Введение и размещение проекта» задаем имя («PhoneBook»)
и каталог проекта;
3. в окне «Настройка цели» выбираем вариант «Desktop» (прило- жение для настолького компьютера);
4. в окне «Информация о классе» установим QWidget в качестве базового класса, убедимся, что отмечен флажок «Создать фор- му»;
5. в окне «Управление проектом» откажемся от использования си- стемы контроля версий (вариант по умолчанию).
В результате работы мастера будет сгенерирован шаблон прило- жения, содержащий файлы исходного кода main.cpp (главная про- грамма), widget.h (интерфейс класса Widget, представляющего форму
Средства визуального проектирования
27
Рис. 3: Стартовое окно Qt Creator приложения) и widget.cpp (реализация класса Widget), а также файл проекта PhoneBook.pro и форму widget.ui. При этом Qt Creator пе- реключится в режим редактора. Дважды щелкнув по файлу формы,
перейдем в режим дизайнера. Вид окна Qt Creator в режиме дизай- нера показан на рис. 4.
Рассмотрим инструменты, доступные разработчику при работе с дизайнером форм.
Собственно графическая форма, представляющая проектируемый интерфейс находится в верхней части центральной области окна. На рис. 4 форма пуста.
Слева размещается палитра компонентов, представляющая биб- лиотеку виджетов Qt. Для размещения виджета на форме достаточно перетащить его мышью.
Каждый виджет имеет набор свойств, как собственных, так и уна- следованных от базовых классов (вплоть до QWidget и QObject). Про- граммист имеет возможность модифицировать значения доступных для редактирования свойств с помощью редактора свойств. На рис. 4
28
Средства визуального проектирования
Рис. 4: Вид окна Qt Creator в режиме дизайнера редактор свойств находится в правом нижнем углу.
Над редактором свойств на рис. 4 размещается инспектор объек- тов, изображающий спроектированный интерфейс в виде иерархиче- ской структуры (в соответствии с вложенностью виджетов в контей- неры и компоновщики).
В нижней части центральной области на рис. 4 находятся сгруппи- рованные вместе редактор действий (привязка горячих клавиш, под- сказок и т. п.) и редактор сигналов и слотов.
Приступим к созданию интерфейса основного окна нашего прило- жения. Для этого перетащим на форму четыре кнопки (Push Button)
и вертикальный разделитель (Vertical Spacer). Выравнивание элемен- тов не имеет значения (см. рис. 5 слева). Выделим все элементы и применим вертикальный компоновщик (рис. 5 справа).
Для управления компоновкой можно воспользоваться панелью ком- поновки в верхней части окна дизайнера (рис. 6) или выбрать соот- ветствующий пункт контекстного меню, вызванного на выделенных объектах.
Средства визуального проектирования
29
Рис. 5: Добавляем и компонуем набор кнопок
Рис. 6: Панель компоновки
Добавим виджет ListWidget и применим к форме табличный ком- поновщик (рис. 7).
Рис. 7: Завершаем компоновку интерфейса
Используя редактор свойств, установим значения свойств objectName
30
Средства визуального проектирования и text кнопок, задав соответственно «addButton» / «Add», «editButton»
/ «Edit», «deleteButton» / «Delete» и «clearButton» / «Clear All». Ана- логично установим значение свойства objectName для виджета списка в «list».
Все предыдущие операции выполнялись в режиме изменения ви- джетов. Дизайнер форм поддерживает четыре режима:
• режим изменения виджетов, предназначенный для визуального проектирования интерфейса;
• режим редактирования сигналов и слотов;
• режим изменения партнёров, предназначенный для привязки надписей к полям ввода;
• режим изменения порядка обхода, предназначенный для опре- деления порядка передачи фокуса клавиатуры по нажатию кла- виши табуляции.
Для переключения режимов в верхней части дизайнера имеется спе- циальная панель (см. рис. 8).
Рис. 8: Панель режимов
Переключимся в режим редактирования сигналов и слотов. Захва- тив мышью кнопку «Clear All», переведем курсор на виджет списка.
В открывшемся диалоговом окне, для отправителя (кнопки) выбе- рем сигнал clicked(), а для виджета списка слот clear(). Теперь при нажатии на кнопку «Clear All» все данные виджета списка будут уда- ляться.
Переключимся в режим изменения порядком обхода и установим нужный (см. рис. 12).
Добавим в проект еще один файл формы (editdialog.ui) и анало- гично описанному выше спроектируем второе окно. Полям ввода при- своим имена nameEdit и numberEdit.
Средства визуального проектирования
31
Рис. 9: Выполняем связывание сигналов и слотов
Рис. 10: Устанавливаем порядок обхода виджетов
Рис. 11: Диалог редактирования
32
Средства визуального проектирования
Для обеспечения возможности быстрого доступа к полям ввода с клавиатуры, настроим горячие клавиши. Для этого в тексты надпи- сей зададим как «&Name» и «&Phone» и, переключившись в режим редактирования партнеров, свяжем надписи с полями ввода. Теперь полям ввода можно быстро передавать фокус клавиатуры, используя сочетания Alt+N и Alt+P соответственно, а в тексте надписей эти символы будут изображаться с подчеркиванием, подсказывая пользо- вателю комбинации горячих клавиш.
На этом этапе подготовка форм завершена. Заметим, что в про- екте пока еще нет ни одной написанной вручную строки кода!
Сохраненная форма представляет собой XML-файл (*.ui) с описа- нием интерфейса. Файлы описаний интерфейса обрабатываются спе- циальной программой uic (user interface compiler). На выходе uic ге- нерирует файлы исходного кода, которые включаются в проект.
Создадим обработчик нажатия кнопки «Add...». Для этого, нахо- дясь в режиме изменения виджетов, в контекстном меню на кнопке
«Add...» выберем пункт «Перейти к слоту...» и в открывшамся окне выберем сигнал clicked(). Qt Creator переключится в режим редакти- рования файла исходного кода, соответствующего форме (см. пример
??
).
Пример 3.1
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::Widget()
{
delete ui;
}
Средства визуального проектирования
33
void Widget::on_addButton_clicked()
{
}
Первые два метода построены из шаблона проекта, использующе- го форму. Все элементы спроектированной визуально формы доступ- ны через объект ui, например ui->editButton. Третий метод — сге- нерированный слот для обработки нажатия кнопки. Автоматически создаваемые слоты получают имена по схеме on_objectName_signal.
Добавим в тело обработчика код, создающий диалоговое окно для ввода имени и номера.
Пример 3.2
EditDialog dlg( this);
if( dlg.exec() == Qt::Accepted )
ui->list->addItem( dlg.name() + " -- " + dlg.number());
Аналогично построим обработчик кнопки удаления.
Пример 3.3
void Widget::on_deleteButton_clicked()
{
foreach (QListWidgetItem *item, ui->list->selectedItems())
delete item;
}
Заметим, что кнопка «Delete» всегда активна, что не корректно —
кнопка должна быть доступна только, если есть выделенные позиции в списке. Добавим слот updateDeleteEnabled():
Пример 3.4
void Widget::updateDeleteEnabled()
{
34
Средства визуального проектирования ui->deleteButton->setEnabled(
ui->list->selectedItems().count() != 0);
}
Дополнительно необходимо переключиться к заголовочному фай- лу и объявить этот слот в секции private slots.
Соединим созданный слот с сигналом об изменении выделения:
Пример 3.5
connect(ui->list->selectionModel(),
SIGNAL(selectionChanged(QItemSelection,
QItemSelection)),
this,
SLOT(updateDeleteEnabled()));
Аналогично обработчику кнопки «Add» реализуется обработчик кнопки «Edit». Также дополнительно потребуется реализовать мето- ды класса EditDialog, представляющего диалоговое окно редактиро- вания записи телефонной книги. Предоставим читателю завершить работу над этим приложением.
Компоновка и размещение виджетов
35
§4.
Компоновка и размещение виджетов
Типичное окно современного приложения с графическим интер- фейсом может содержать десятки виджетов, некоторым образом раз- мещенных на холсте окна.
Размещение виджетов может быть выполнено вручную. QWidget и производные классы включают метод setGeometry(), обеспечиваю- щий возможность задать фиксированные размеры и позицию виджета
(см. пример 4.1).
Пример 4.1
Window::Window(QWidget *parent) : QWidget(parent)
{
setFixedSize(640, 480);
QTextEdit *txt = new QTextEdit(this);
txt->setGeometry(20, 20, 600, 400);
QPushButton *btn = new QPushButton(tr("&Close"), this);
btn->setGeometry(520, 440, 100, 20);
}
Однако расстановка виджетов вручную приводит к ряду сложно- стей. Например, при необходимости добавить новый виджет, требу- ется модифицировать местоположение остальных. Также возникают проблемы с изменением размеров окна пользователем — при фикси- рованном размещении часть элементов управления может оказаться недоступной, вне видимой области окна.
4.1
Использование компоновщиков
Qt предоставляет простой механизм автоматического размещения виджетов внутри других виджетов посредством компоновщиков. В
частности, копмоновка позволяет разумно использовать пространство окна и реализовать так называемый «эластичный интерфейс», когда виджеты получают возможность адаптировать свои размеры и раз- мещение в зависимости от содержимого и пользовательских настро- ек, например, автоматически растягиваться, чтобы вместить текст с увеличенным шрифтом.
36
Компоновка и размещение виджетов
Qt предоставляет три вида компоновщиков: вертикальный (класс
QVBoxLayout), горизонтальный (класс QHBoxLayout), табличный (класс
QGridLayout) и компоновщик форм (QFormLayout), предназначенный для формирования форм ввода, состоящих из колонки пар надпись —
поле ввода. Непосредственные размеры вложенных виджетов опреде- ляются в ходе выполнения специального процесса, называемого «пе- реговорами».
Рассмотрим пример типичного диалогового окна:
Рис. 12: Пример использования компоновщиков
Надпись «Printer» и выпадающий список в верхней части диало- гового окна объединены с помощью горизонтального компоновщика.
Второй горизонтальный компоновщик объединяет две группы кнопок.
Третий формирует строку кнопок в нижней части окна. Для выравни- вания элементов диалогового окна применены разделители (QSpacer)
— невидимые виджеты-распорки. Все три горизонтальных компонов- щика и один из разделителей выровнены с помощью вертикального компоновщика, примененного ко всему окну.
Следующий пример демонстрирует общую схему построения ин- терфейса, представленного на рис. 12 с использованием вертикальных и горизонтальных компоновщиков.
Пример 4.2
// Создаем внешний вертикальный компоновщик
QVBoxLayout *outerLayout = new QVBoxLayout(this);
Компоновка и размещение виджетов
37
// Создаем верхний горизонтальный компоновщик
QHBoxLayout *topLayout = new QHBoxLayout();
// Добавляем виджеты topLayout->addWidget(new QLabel("Printer:"));
topLayout->addWidget(c=new QComboBox());
// Добавляем в внешний компоновщик outerLayout->addLayout(topLayout);
// Создаем средний горизонтальный компоновщик
QHBoxLayout *groupLayout = new QHBoxLayout();
outerLayout->addLayout(groupLayout);
// Добавляем разделитель outerLayout->addSpacerItem(new QSpacerItem(...));
// Создаем нижний горизонтальный компоновщик
QHBoxLayout *buttonLayout = new QHBoxLayout();
buttonLayout->addSpacerItem(new QSpacerItem(...));
buttonLayout->addWidget(new QPushButton("Print"));
buttonLayout->addWidget(new QPushButton("Cancel"));
outerLayout->addLayout(buttonLayout);
// Применяем внешний компоновщик к окну setLayout(outerLayout);
Каждый виджет наследует от класса QWidget свойства sizeHint,
minimumSizeHint и sizePolicy. Свойство sizeHint определяет, какое про- странство виджет «хотел бы» занимать в нормальных условиях. Свой- ство minimumSizeHint задает минимальный размер, меньше которого виджет не может занимать ни при каких обстоятельствах. Значением свойства sizePolicy является политика, определяющая отношение ви- джета к изменениям его размера и принимающая одно из следующих значений:
• QSizePolicy::Fixed — виджет не может иметь размер, отличный от sizeHint;
38
Компоновка и размещение виджетов
• QSizePolicy::Minimum — sizeHint представляет минимально до- пустимый размер виджета, но он может быть неограниченно растянут;
• QSizePolicy::Maximum — sizeHint представляет максимально до- пустимый размер виджета, но он может быть неограниченно ужат;
• QSizePolicy::Preferred — sizeHint представляет оптимальный раз- мер виджета, но изменения допустимы в обе стороны;
• QSizePolicy::Expanding — аналогично Preferred, но виджет тре- бует предоставить ему любое доступное пространство компонов- щика;
• QSizePolicy::MinimumExpanding — аналогично Minimum, но ви- джет требует предоставить ему любое доступное пространство компоновщика;
• QSizePolicy::Ignored — sizeHint игнорируется, виджету выделя- ется столько пространства, сколько возможно в пределах ком- поновщика.
Помимо варьирования свойств sizeHint, minimumSizeHint и sizePolicy вложенных в компоновщик виджетов, управление размещением мо- жет быть выполнено и средствами самого компоновщика. Метод addWidget()
содержит необязательный второй аргумент (stretch factor), равный по умолчанию нулю. Ненулевые значения коэффициента растяжения за- дают относительный размер пространства, выделенного компоновщи- ком под размещение виджета.
При использовании табличного компоновщика программист зада- ет, по каким границам сетки выравнивается каждый вложенный ви- джет. Такой подход позволяет занимать под виджет любую прямо- угольную подобласть таблицы, а также создавать перекрытия. Если добавляемый виджет занимает ровно одну ячейку таблицы, достаточ- но определить левую и верхнюю границы, в противном случае — все четыре (см. пример 4.3).
Пример 4.3
Компоновка и размещение виджетов
39
QPushButton *button1 = new QPushButton("One");
QPushButton *button2 = new QPushButton("Two");
QPushButton *button3 = new QPushButton("Three");
QPushButton *button4 = new QPushButton("Four");
QPushButton *button5 = new QPushButton("Five");
QGridLayout *layout = new QGridLayout;
layout->addWidget(button1, 0, 0);
layout->addWidget(button2, 0, 1);
layout->addWidget(button3, 1, 0, 1, 2);
layout->addWidget(button4, 2, 0);
layout->addWidget(button5, 2, 1);
Предложенный в примере код создает интерфейс, представленный на рис. 13.
Рис. 13: Пример использования табличного компоновщика
4.2
Контейнерные виджеты
Помимо компоновщиков иерархическая структура графического интерфейса может быть построена с помощью виджетов-контейнеров
— виджетов, которые могут содержать другие виджеты.
Контейнерные виджеты могут применяться как для организации высокоуровневых элементов, например, окон — QWidget, QDialog, а также QMainWindow, так и группировки виджетов внутри окон —
QGroupBox, QTabWidget и т.п.
40
Компоновка и размещение виджетов
Пример 4.4 иллюстрирует применение QMainWindow для органи- зации окна приложения и QTabWidget для создания набора вкладок.
Следует отметить, что в отличие от «пустых» контейнеров QWidget и
QDialog, QMainWindow включает основные элементы типового окна приложения — меню, строку состояния и контейнер для наполнения центральной области (centralWidget).
Пример 4.4
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QMainWindow *window = new QMainWindow();
window->setWindowTitle("MainWindow");
window->resize(250, 250);
QWidget *centralWidget = new QWidget(window);
QTabWidget *tabs = new QTabWidget(centralWidget);
tabs->setFixedSize(245, 245);
tabs->addTab(new QWidget(),"Tab 1");
tabs->addTab(new QWidget(),"Tab 2");
window->setCentralWidget(centralWidget);
window->show();
return app.exec();
}
Обзор библиотеки виджетов Qt
41
§5.
Обзор библиотеки виджетов Qt
В предыдущих разделах уже были приведены примеры виджетов
— основным строительных элементов графического интерфейса поль- зователя.
Далее рассмотрим несколько основных виджетов Qt. В целях эко- номии все примеры построены по общей схеме: имеется приложение,
состоящее из трех следующих файлов исходного кода. В главном фай- ле main.cpp создается экземпляр приложения и собственного виджета
Widget.
Пример 5.1
#include
#include "widget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
Файл widget.h содержит описание класса Widget, при этом в при- мерах будут варьироваться файлы заголовков и приватные компонен- ты.
#ifndef WIDGET_H
#define WIDGET_H
#include
// Необходимые файлы заголовков class Widget : public QWidget
{
42
Обзор библиотеки виджетов Qt
Q_OBJECT
public:
Widget(QWidget *parent = 0);
Widget();
private:
// Необходимые приватные компоненты
};
#endif // WIDGET_H
Файл widget.cpp содержит реализацию методов класса Widget,
при этом пользовательский интерфейс формируется в конструкторе класса. Именно код конструктора будет приведен во всех последую- щих примерах этого раздела.
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
// Конструируем интерфейс пользователя
}
Widget::Widget()
{
}
5.1
Кнопки
Три класса QPushButton, QCheckBox, QRadioButton реализуют соответственно простую кнопку, флажок и радиокнопку. Базовым клас- сом всех кнопок является QAbstractButton.
Основные сигналы для кнопок:
Обзор библиотеки виджетов Qt
43
• clicked() отправляется в момент, когда пользователь отпускает кнопку мыши;
• toggled(bool) отправляется в момент изменения состояния кноп- ки.
Отметим также наиболее полезные свойства:
• checkable — истина, если кнопка может работать как флажок;
• checked — истина, если флажок имеет отметку;
• text — текст кнопки;
• icon — иконка кнопки, которая может отображаться вместе с текстом.
Следующий пример иллюстрирует использование QCheckBox. В
конструкторе создадим флажок, установив отметку как начальное со- стояние.
Пример 5.2
// Создаем флажок checkBox = new QCheckBox("Show Title", this);
checkBox->setCheckState(Qt::Checked);
// Связываем изменение состояния флажка
// с обработчиком connect(checkBox, SIGNAL(stateChanged(int)),
this, SLOT(showTitle(int)));
Код обработчика для изменения состояния флажка приведен ни- же. В данном примере меняем заголовок окна.
void Widget::showTitle(int state)
{
if (state == Qt::Checked) {
setWindowTitle("QCheckBox");
} else {
setWindowTitle("");
}
}
44
Обзор библиотеки виджетов Qt
5.2
Надписи
QLabel отображает текстовый блок или картинку, которые зада- ются свойствами text и pixmap соответственно. Следующий пример демонстрирует использование QLabel для изображения простой тек- стовой надписи.
Пример 5.3
// Создаем компоновщик layout = new QHBoxLayout();
// Создаем надпись label = new QLabel("Hello");
// Добавляем надпись в компоновщик,
центрируя разделителями layout->addWidget(new QSplitter());
layout->addWidget(label);
layout->addWidget(new QSplitter());
// Применяем компоновщик к виджету setLayout(layout);
QLabel распознает некоторое подмножество тэгов HTML-разметки.
Пример 5.4
// Создаем полужирную надпись красного цвета label = new QLabel(
"
1 2 3
Министерство образования и науки Российской Федерации
Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования
ПЕТРОЗАВОДСКИЙ ГОСУДАРСТВЕННЫЙ
УНИВЕРСИТЕТ
А. В. Бородин, А. В. Бородина
СРЕДСТВА РАЗРАБОТКИ
ГРАФИЧЕСКИХ ИНТЕРФЕЙСОВ
ПОЛЬЗОВАТЕЛЯ
Учебное пособие
Петрозаводск
Издательство ПетрГУ
2012
ББК 32.973
УДК 004
Б833
Печатается по решению редакционно-издательского совета
Петрозаводского государственного университета
Издается в рамках реализации комплекса мероприятий
Программы стратегического развития ПетрГУ на 2012–2016 г.г.
Р е ц е н з е н т ы:
канд. техн. наук Р. В. Сошкин;
канд. физ.-мат. наук, доцент К. А. Кулаков
Бородин А. В.
Б833
Средства разработки графических интерфейсов пользовате- ля : учебное пособие / А. В. Бородин, А. В. Бородина. — Пет- розаводск : Изд-во ПетрГУ, 2012. — 77 c.
ISBN 978-5-8021-1537-4
Учебное пособие предназначено для студентов математическо- го факультета первого курса направлений подготовки «Прикладная математика и информатика», «Информационные системы и техно- логии»
ББК 32.973
УДК 004
ISBN 978-5-8021-1537-4
© Петрозаводский государственный университет, 2012
© Бородин А. В., Бородина А. В., 2012
3
Содержание
Введение
4
§1.Графические интерфейсы пользователя
5
§2.Семейство библиотек Qt
14 2.1 Первая Qt-программа . . . . . . . . . . . . . . . . . . . .
14 2.2 Обзор средств Qt . . . . . . . . . . . . . . . . . . . . . .
16 2.2.1 Виджеты и конструирование интерфейсов . . .
16 2.2.2 Класс QObject и метаобъектный компилятор . .
18 2.2.3 Прочие возможности . . . . . . . . . . . . . . . .
23
§3.Средства визуального проектирования
25
§4.Компоновка и размещение виджетов
35 4.1 Использование компоновщиков . . . . . . . . . . . . . .
35 4.2 Контейнерные виджеты . . . . . . . . . . . . . . . . . .
39
§5.Обзор библиотеки виджетов Qt
41 5.1 Кнопки . . . . . . . . . . . . . . . . . . . . . . . . . . . .
42 5.2 Надписи . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44 5.3 Инструменты ввода текста . . . . . . . . . . . . . . . . .
45 5.4 Инструменты ввода чисел . . . . . . . . . . . . . . . . .
47
§6.Создание собственных виджетов
48 6.1 Создание виджета композицией имеющихся . . . . . . .
48 6.2 Отрисовка на поверхности виджета . . . . . . . . . . . .
50 6.3 Пример создания виджета с нуля . . . . . . . . . . . . .
54
§7.Работа с 2D-графикой
59 7.1 Graphics View Framework . . . . . . . . . . . . . . . . . .
59 7.2 Управление элементами сцены . . . . . . . . . . . . . .
62 7.3 Создание собственных элементов сцены . . . . . . . . .
67
§8.Интернационализация приложений
73
4
Введение
Введение
Развитие информационных технологий и рост числа пользовате- лей компьютеров предъявляют новые требования к пользовательским интерфейсам. Перед разработчиками программного обеспечения сто- ит непростая задача обеспечить низкий порог вхождения для широко- го круга не обладающих достаточной квалификацией и не прошедших специальную подготовку в использовании ЭВМ лиц за счет упроще- ния средств диалога с машиной, использования интуитивно понятных графических метафор, унификации интерфейсов.
Таким образом, овладение средствами разработки современных графических интерфейсов пользователя является необходимым эле- ментом подготовки разработчика прикладного программного обеспе- чения.
Пособие представляет собой обзор основных концепций, лежащих в основе графических интерфейсов, и краткое введение в программи- рование с использованием фреймворка Qt, предоставляющего набор библиотек и утилит для создания прикладных программ, в том числе,
приложений с графическим интерфейсом.
Выбор Qt среди широкого круга альтернатив обусловлен двумя основными причинами. Во-первых, Qt является продуктом с откры- тым кодом, таким образом его использование не может быть ограниче- но изменением политики компании-разработчика. Во-вторых, Qt из- начально разрабатывался как кроссплатформенный инструмент: на- писанные с использованием этого фреймворка приложения переноси- мы на уровне исходного кода между всеми популярными настольны- ми операционными системами, более того, Qt можно использовать и для разработки для мобильных устройств, таких как смартфоны и планшетные компьютеры. Немаловажным является факт наличия в свободном доступе весьма подробной документации разработчика (см.
[6]).
Материалы пособия используются в Петрозаводском государствен- ном университете на математическом факультете в модуле «Средства разработки графических интерфейсов» дисциплины «Информатика»,
читаемой студентам I курса направлений подготовки «Прикладная математика и информатика» и «Информационные системы и техно- логии».
Графические интерфейсы пользователя
5
§1.
Графические интерфейсы пользовате- ля
Графическими интерфейсами пользователя (англ. Graphical User
Interface, GUI) считают такие, в которых элементы интерфейса пред- ставлены в виде визуальных объектов, т. е. графических изображений.
Элементы интерфейса, используя метафоры, несут информацию об их назначении и способе использования, обеспечивая обучение поль- зователя в процессе работы. Интерфейсы, обладающие этим свой- ством, принято называть интуитивно понятными.
Ко всем видимым элементам интерфейса обеспечивается произ- вольный достум посредством широкого спектра устройств ввода —
клавиатуры, указателя «мышь», сенсорного экрана и др.
Первые разработки в области графических интерфейсов относят- ся к семидесятым годам XX века и, как правило, связываются с ком- пьютером Alto, разработанным в исследовательских лабораториях ком- пании XEROX в Пало Альто, Калифорния. Тогда же впервые появ- ляется аббревиатура WIMP (Windows, Icons, Menus, Pointing Device),
концептуально характеризующая графический интерфейс пользова- теля.
Коммерческий успех к компьютерам, операционные системы ко- торых предоставляли графический интерфейс, пришел в восьмидеся- тые годы XX века и связан с появлением таких разработок, как Star компании Xerox и Lisa компании Apple.
Тогда же были предпринты первые попытки упорядочить разра- ботку графических интерфейсов пользователя, в частности, следует отметить руководство IBM Common User Access Specification.
Одной из первых оконных систем, обеспечивающей стандартные инструменты и протоколы для построения графических интерфейсов,
стала X Window System, разрабатываемая с 1984 года и используемая по настоящее время в UNIX-подобных ОС. X Window System обес- печивает базовые функции графической среды: отрисовку и переме- щение окон на экране, взаимодействие с устройствами ввода, такими как, например, мышь и клавиатура.
X Window System не определяет деталей интерфейса пользовате- ля — этим занимаются менеджеры окон. С другой стороны, в этой системе предусмотрена сетевая прозрачность: графические приложе-
6
Графические интерфейсы пользователя ния могут выполняться на другой машине в сети, а их интерфейс при этом будет передаваться по сети и отображаться на локальной ма- шине пользователя. В контексте X Window System термины «клиент»
и «сервер» имеют непривычное для многих пользователей значение:
«сервер» означает локальный дисплей пользователя (дисплейный сер- вер), а «клиент» — программу, которая этот дисплей использует (она может выполняться на удалённом компьютере).
Архитектура X Window System проиллюстрирована на рис. 1.
Рис. 1: Архитектура X Window System
X Window System предоставляет программный интерфейс для со- здания приложений с графическим интерфейсом посредством исполь- зования библиотеки XLib. Однако, программирование на уровне XLib слишком многословно и перегружает программиста необходимостью реализовывать вручную даже примитивные детали интерфейса.
Рассмотрим достаточно простую XLib-программу, изображающую на экране простое окно с надписью.
Пример 1.1
Графические интерфейсы пользователя
7
#include
#include
#include
#include
#include
#include
#define X 0
#define Y 0
#define WIDTH 400
#define HEIGHT 300
#define WIDTH_MIN 50
#define HEIGHT_MIN 50
#define BORDER_WIDTH 1
#define TITLE "Example"
#define ICON_TITLE "Example"
#define PRG_CLASS "Example"
/* Сообщает оконному менеджеру параметры окна */
static void SetWindowManagerHints(Display * display,
char *PClass, char *argv[], int argc,
Window window, int x, int y,
int win_wdt, int win_hgt,
int win_wdt_min, int win_hgt_min,
char *ptrTtl, char *ptrITtl, Pixmap pixmap)
{
XSizeHints size_hints;
XWMHints wm_hints;
XClassHint class_hint;
XTextProperty winnm, icnm;
if (!XStringListToTextProperty (&ptrTtl, 1, &winnm)
|| !XStringListToTextProperty (&ptrITtl, 1, &icnm)) {
puts ("No memory!\n");
exit (1);
}
8
Графические интерфейсы пользователя size_hints.flags = PPosition | PSize | PMinSize;
size_hints.min_width = win_wdt_min;
size_hints.min_height = win_hgt_min;
wm_hints.flags = StateHint | IconPixmapHint | InputHint;
wm_hints.initial_state = NormalState;
wm_hints.input = True;
wm_hints.icon_pixmap = pixmap;
class_hint.res_name = argv[0];
class_hint.res_class = PClass;
XSetWMProperties (display, window, &winnm,
&icnm, argv, argc, &size_hints,
&wm_hints, &class_hint);
}
/* Обработчик ошибки */
int MyXlibIOErrorHandler(Display *d)
{
XCloseDisplay(d);
exit(0);
}
/* Основная программа */
int main(int argc, char *argv[])
{
Display *display;
int ScreenNumber;
GC gc;
XEvent report;
Window window;
if ((display = XOpenDisplay(NULL)) == NULL) {
puts ("Can not connect to the X server!\n");
exit (1);
}
ScreenNumber = DefaultScreen(display);
Графические интерфейсы пользователя
9
window = XCreateSimpleWindow(display,
RootWindow(display, ScreenNumber),
X, Y, WIDTH, HEIGHT, BORDER_WIDTH,
BlackPixel(display, ScreenNumber),
WhitePixel(display, ScreenNumber));
SetWindowManagerHints (display, PRG_CLASS, argv, argc,
window, X, Y, WIDTH, HEIGHT, WIDTH_MIN,
HEIGHT_MIN, TITLE, ICON_TITLE, 0);
XSelectInput (display, window, ExposureMask
| StructureNotifyMask);
XMapWindow (display, window);
XSetIOErrorHandler (&MyXlibIOErrorHandler);
while (1) {
XNextEvent (display, &report);
switch (report.type) {
case Expose:
if (report.xexpose.count != 0) break;
gc = XCreateGC(display, window, 0, NULL);
XSetForeground(display, gc,
BlackPixel(display, 0));
XDrawString (display, window, gc, 20, 50,
"Hello, world!", strlen ("Hello, world!"));
XFreeGC (display, gc);
XFlush (display);
break;
default:
fprintf (stderr, "%d\n", report.type);
}
}
return 0;
}
10
Графические интерфейсы пользователя
Используя XLib, программист полностью контролирует свойства оконного приложения, именно этим объясняется большое количество параметров в вызовах функций. Также программист самостоятельно выполняет проверку наступления тех или иных событий, организуя бесконечный цикл опроса обработки. На полотне окна программист также самостоятельно вынужден отрисовывать необходимые элемен- ты.
Из приведенного примера можно извлечь следующие выводы.
• Поскольку любая программа с графическим интерфейсом явля- ется событийно-ориентированной, то есть большую часть време- ни проводит в состоянии «сна», выполняя те или иные функции лишь в ответ на активность пользователя, то она, так или иначе,
будет включать цикл обработки событий, подобный приведен- ному в примере. Следовательно, необходимости в включении этого цикла вручную в каждой программе нет — его можно реализовать в виде отдельной функции библиотеки, предостав- ляющей программные средства создания графического интер- фейса. При этом код обработки событий оформляется в виде от- дельных функций, регистрируемых до входа в цикл обработки событий. Такие функции называют функциями обратного вы- зова (callback), так как в программе отсутствуют явные вызовы этих функций, они вызываются из функции, реализующей аб- стракцию основного цикла при наступлении соответствующего события.
• Большая часть параметров оконного приложения либо контро- лируется оконным менеджером, либо имеет стандартные зна- чения (например, цветовая гамма может определяться темой оформления). Поэтому программный интерфейс можно суще- ственно упростить.
Исходя из представленных соображений, над XLib был разработан набор высокоуровневых библиотек Xt (так называемый «тулкит»),
скрывающий сложность XLib за оберточными вызовами. Рассмотрим пример Xt-программы, предоставляющий ту же функциональность,
что и пример 1.1.
Пример 1.2
Графические интерфейсы пользователя
11
#include
#include
#include
#include
void DrawHelloString (Widget prWidget, XtPointer pData,
XEvent *prEvent, Boolean *pbContinue)
{
Display *prDisplay = XtDisplay (prWidget);
Window nWindow = XtWindow (prWidget);
GC prGC;
if (prEvent->type == Expose) {
prGC = XCreateGC (prDisplay, nWindow, 0, NULL);
XDrawString (prDisplay, nWindow, prGC, 10, 50,
"Hello, world!", strlen ("Hello, world!"));
XFreeGC (prDisplay, prGC);
}
}
void main (int argc, char **argv)
{
Arg args[2];
Widget toplevel, prCoreWidget;
toplevel = XtInitialize(argv[0], "GUI Example",
NULL, 0, &argc, argv);
prCoreWidget = XtCreateWidget ("Core", widgetClass,
toplevel, NULL, 0);
XtSetArg(args[0], XtNwidth, 400);
XtSetArg(args[1], XtNheight, 300);
XtSetValues(prCoreWidget, args, 2);
XtManageChild (prCoreWidget);
XtAddEventHandler(prCoreWidget, ExposureMask,
False, DrawHelloString, NULL);
12
Графические интерфейсы пользователя
XtRealizeWidget(toplevel);
XtMainLoop();
}
Типовой цикл обработки событий заменен единственным вызовом
XtMainLoop(). При этом обработчики событий, реализованные в виде функций обратного вызова, связываются с сигналами событий (реги- стрируются) заранее посредством вызова XtAddEventHandler().
Также в Xt введено понятие виджета (widget = visual gadget) — ти- пового элемента графического интерфейса. Однако, отрисовка стро- ки по-прежнему осуществляется с помощью низкоуровневого XLib- вызова XDrawString().
Современные инструменты разработки графических интерфейсов также реализуют абстракцию основного цикла обработки событий и предоставляют разработчику богатую библиотеку виджетов. Следую- щий пример демонстрирует использование средств библиотеки GTK+.
Пример 1.3
#include
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *label;
/* Инициализируем подсистему GTK+ */
gtk_init(&argc, &argv);
/* Создаем окно */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* Привязываем обработчик нажатия кнопки закрытия окна */
g_signal_connect(G_OBJECT (window), "destroy",
G_CALLBACK (gtk_main_quit), NULL);
/* Создаем кнопку с надписью */
label = gtk_label_new("Hello World");
Графические интерфейсы пользователя
13
/* Помещаем кнопку в окно (контейнер) */
gtk_container_add(GTK_CONTAINER (window), label);
/* Отрисовываем окно и все принадлежащие ему объекты */
gtk_widget_show_all(window);
/* Основной цикл обработки событий */
gtk_main();
return 0;
}
14
Семейство библиотек Qt
§2.
Семейство библиотек Qt
2.1
Первая Qt-программа
По сложившейся традиции руководств по программированию, об- зор возможностей семейства библиотек Qt начнем с программы, вы- водящей на экран приветствие «Hello, world!».
Пример 2.1
#include
#include
int main(int argc, char *argv[])
{
// Создаем экземпляр приложения
QApplication a(argc, argv);
// Создаем надпись
QLabel label("Hello World");
// Отображаем надпись на экране label.show();
// Основной цикл обработки событий return a.exec();
}
Текст программы содержит включения файлов заголовков с опре- делениями необходимых классов (QApplication и QLabel).В Qt на каж- дый класс приходится в точности один заголовочный файл.
Выполнение программы начинается с создания объекта класса
QApplication, представляющего абстракцию графического приложе- ния. Ни одна графическая программа не обходится без QApplication,
так как именно в этом классе реализован цикл обработки событий,
определяющий основной алгоритм работы графического приложения.
Мы передаем управление в цикл обработки событий вызовом мето- да exec() класса QApplication. Именно в exec() программа проводит
Семейство библиотек Qt
15
большую часть времени работы, ожидая пользовательской реакции или другого события.
В конструктор QApplication мы передаем аргументы командной строки. Qt-приложение способно распознавать некоторые параметры,
заданные в командной строке и адаптировать поведение в соответ- ствии с ними.
После создания объекта QApplication создается объект QLabel,
представляющий простую текстовую надпись. Текст для отображения передается через конструктор класса.
Метод show() заставляет Qt-приложение отрисовать надпись на экране, при этом, так как надпись не имеет родительского объекта,
то сама формирует окно.
Цикл обработки событий прекращается при выполнении метода quit() класса QApplication, например, если пользователь закрывает окно. В этом случае приложение завершается.
Так как Qt поддерживается на многих различных платформах, от- личающихся организацией процесса сборки, разработчиками Qt пред- ложен специальный проектно-ориентированный инструмент сборки —
qmake.
Программа qmake генерирует файлы сборки Makefile для каж- дой конкретной платформы на основе специального файла проекта
(*.pro), который описывает приложение в независимой от операцион- ной системы и среды программирования форме.
Сохраним нашу первую программу под именем main.cpp в ката- логе myFirstQtProject и сгенерируем в нем шаблон файла проекта с помощью той же программы qmake:
Пример 2.2
user@linux:/myFirstQtProject> qmake -project
В результате будет построен шаблон файла проекта:
Пример 2.3
#myFirstQtProject/myFirstQtProject.pro
16
Семейство библиотек Qt
#####################################
# Automatically generated by qmake
#####################################
TEMPLATE = app
CONFIG -= moc
DEPENDPATH += .
INCLUDEPATH += .
# Input
SOURCES += main.cpp
Файл проекта состоит из директив, в данном примере директива
TEMPLATE указывает, что проект представляет собой приложение
(app), а SOURCES содержит перечень файлов исходного кода проекта
(в нашем примере только main.cpp).
Повторный вызов qmake (теперь без параметров) создаст зависи- мый от платформы файл сборки (Makefile), который можно исполь- зовать для получения исполняемого файла:
Пример 2.4
user@linux:/myFirstQtProject> qmake user@linux:/myFirstQtProject> make user@linux:/myFirstQtProject> ./myFirstQtProject
2.2
Обзор средств Qt
2.2.1
Виджеты и конструирование интерфейсов
Основу библиотеки графического интерфейса составляют видже- ты — классы, представляющие визуальные объекты. Виджеты обра- зуют иерархию классов. Базовым классом для всех виджетов в Qt яв- ляется QWidget. При этом имеется более шестидесяти производных от QWidget классов, реализующих типовые элементы управления. До- полнительно программист имеет возможность создания собственных виджетов, расширяя функциональность элементов стандартного на- бора.
Семейство библиотек Qt
17
Визуальная структура интерфейсов формируется посредством кон- тейнерных классов, позволяющих организовывать виджеты в иерар- хии. Таким образом, виджеты могут содержать в себе другие видже- ты.
Харахтерными особенностями виджетов являются следующие:
• виджет занимает прямоугольную область экрана;
• виджет может получать сообщения о событиях, например, от устройств ввода;
• виджет может отправлять сигналы об изменении состояния.
Все виджеты имеют набор общих свойств, унаследованных от ба- зового класса QWidget:
• enabled — возможна ли реакция на ввод пользователя;
• visible — видим ли виджет (или скрыт).
Действие этих свойств распространяется на вложенные виджеты.
В примере 2.1 интерфейс представляен единственным виджетом
QLabel, поэтому проблем с его расположением нет. Однако интер- фейс приложения может быть сконструирован из десятков виджетов,
и проблема их взаимного расположения окажется весьма серьезной.
Qt предоставляет так называемые средства компоновки, облегча- ющие процесс взаимной расстановки виджетов в пространстве окна.
Следующий пример организовывает две надписи вертикально:
Пример 2.5
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Создаем окно
QWidget window;
18
Семейство библиотек Qt
// Создаем основной компоновщик окна -
// вертикальный компоновщик
QVBoxLayout* mainLayout = new QVBoxLayout(&window);
// Создаем две надписи
QLabel* label1 = new QLabel("One");
QLabel* label2 = new QLabel("Two");
// Организуем надписи с помощью компоновщика mainLayout->addWidget(label1);
mainLayout->addWidget(label2);
// Отображаем окно window.show();
return a.exec();
}
Подробнее вопросы компоновки и виды компоновщиков рассмот- рены в четвертом параграфе. К рассмотренному примеру следует за- метить, что только для окна был вызван метод show(), надписи, вло- женные в окно, автоматически становятся для него «дочерними» объ- ектами и визуализируются автоматически при отображении родителя.
2.2.2
Класс QObject и метаобъектный компилятор
Базовым классом большинства классов семейства библиотек Qt,
в частности, всех виджетов, является QObject. Исключениями явля- ются только классы, для которых важна легковесность (например,
графические примитивы и контейнеры данных). Именно QObject реа- лизует большую часть возможностей, характерных для Qt: обработку событий, сигналы и слоты, свойства объектов, управление памятью.
Каждый унаследованный от QObject имеет так называемый ме- таобъект, содержащий информацию об имени класса, связях и т. д.
Таким образом Qt вводит механизмы интроспекции в язык C++.
Метаданные собираются на этапе трансляции специальной про- граммой — метаобъектным компилятором moc. Программа moc явля-
Семейство библиотек Qt
19
ется препроцессором, обрабатывающим файлы заголовков проекта и конструирующим вспомогательные файлы исходного кода (moc_*.cpp),
также включаемые в проект. При использовании системы qmake ге- нерация этих файлов осуществляется прозрачно для программиста.
Программа moc извлекает из файлов заголовков специальные мак- росы (например, в следующем примере, Q_OBJECT, Q_CLASSINFO),
а также ключевые слова Qt, не являющиеся элементами языка C++:
signals, slots, emit и др. Обработанные файлы содержат только кон- струкции C++ и могут быть скомпилированы обычным компилято- ром C++.
Макрос Q_OBJECT должен присутствовать в любом классе, на- следуемом от QObject.
Пример 2.6
class MyClass : public QObject
{
Q_OBJECT
Q_CLASSINFO("author", "John Doe")
public:
MyClass(const Foo &foo, QObject *parent=0);
Foo foo() const;
public slots:
void setFoo( const Foo &foo );
signals:
void fooChanged( Foo );
private:
Foo m_foo;
};
Класс QObject позволяет создавать свойства с методами чтения и записи. Свойство не просто представляет некоторое поле класса,
предназначенное для хранения значения. В результате установки зна- чения свойства визуальный объект, возможно должен быть модифи- цирован (например, при изменении цвета). Поэтому свойству соответ- ствует пара методов чтения и установки значения, выполняющие и соответствующий дополнительный код — так называемые «геттер» и
«сеттер». В соответствии с соглашениями имя геттер-метода должно
20
Семейство библиотек Qt совпадать с именем свойства. Имя сеттер-метода начинается со слова set, за которым следует имя свойства с заглавной буквы.
Пример 2.7
class QLabel : public QFrame
{
Q_OBJECT
// Создаем свойство text с геттером text
// и сеттером setText
Q_PROPERTY(QString text READ text WRITE setText)
public:
QString text() const;
public slots:
void setText(const QString &);
};
В примере 2.5 объект класса QWidget, представляющий окно, со- здан на стеке, а под компоновщик и надписи память выделена динами- чески. Это не случайно. Вообще говоря, C++ не предусматривает ав- томатического управления памятью, однако в Qt некоторые подобные возможности введены. Объекты, наследующие от класса QObject (в частности, виджеты) могут организовываться в древовидные иерар- хии, имея родителя (parent object) и дочерних объектов (child objects).
Если такой объект уничтожается (вызывается деструктор), то Qt ав- томатически уничтожает все дочерние объекты. Те, в свою очередь,
уничтожают свои дочерние объекты и так далее. Это освобождает программиста от необходимости отслеживать и освобождать занятую дочерними объектами память вручную.
Однако для того, чтобы автоматическое управление памятью мог- ло быть реализовано, память под дочерние объекты должна быть вы- делена на куче динамической памяти.
Любое нетривиальное графическое приложение должно иметь воз- можность отреагировать на пользовательские действия, а значит долж- на существовать система коммуникации между объектами. В отличие от многих инструментов создания графических интерфейсов, в Qt не используются функции обратного вызова для обеспечения реакции
Семейство библиотек Qt
21
на события, а применяется более совершенная технология сигналов и слотов.
И сигнал, и слот являются методами класса, за тем исключением,
что тело сигнала программист не реализует, его создает метаобъект- ный компилятор. Тип возвращаемого значения сигнала — void. Слот может иметь отличный от void тип возвращаемого значения, но ис- пользовать возвращаемое значение можно только, если слот вызыва- ется как обычная функция, а не по сигналу. Сигналы в определении класса объявляются в секции signals, слоты — в секции slots. Отпра- вить сигнал из кода можно с помощью ключевого слова emit.
Сигнатуры связываемых сигнала и слота должны либо совпадать,
либо в сигнале может присутствовать часть дополнительных парамет- ров, которые будут проигнорировать в слоте. В общем случае верно правило: «Qt может игнорировать часть данных, но не может полу- чить данные из ничего».
В следующем примере сигнал, отправляемый кнопкой в момент клика, связывается с стандартным слотом quit() объекта QApplication.
Пример 2.8
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Создаем и отрисовываем кнопку с надписью "Quit"
QPushButton button("Quit");
button.show();
// Связываем сигнал clicked() и слот quit()
QObject::connect(&button, SIGNAL(clicked()),
&a, SLOT(quit()));
return a.exec();
}
22
Семейство библиотек Qt
Статическая функция connect() класса QObject выполняет связы- вание сигнала и слота. Сигнал — функция, уведомляющая о наступле- нии события, слот — функция, получающая этот сигнал и корректно обрабатывающая его. Функция connect() получает четыре аргумента:
объект-отправитель, сигнал, объект-получатель, слот. При связыва- нии необходимо использовать макросы SIGNAL() и SLOT().
В примере 2.8 никакой дополнительной информации из сигнала в слот не передается. Рассмотрим более реальный пример. Пусть ин- терфейс содержит три виджета: QLabel, QSpinBox и QSlider. Послед- ние два обеспечивают просто различные способы ввода целых чисел.
Свяжем эти виджеты друг с другом таким образом, чтобы все они отображали одно и то же число.
Пример 2.9
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Создаем элементы интерфейса
QWidget window;
QVBoxLayout* mainLayout = new QVBoxLayout(&window);
QLabel* label = new QLabel("0");
QSpinBox* spinBox = new QSpinBox;
QSlider* slider = new QSlider(Qt::Horizontal);
// Выполняем компоновку mainLayout->addWidget(label);
mainLayout->addWidget(spinBox);
mainLayout->addWidget(slider);
// Выполняем связывание сигналов и слотов
Семейство библиотек Qt
23
QObject::connect(spinBox, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)));
QObject::connect(spinBox, SIGNAL(valueChanged(int)),
slider, SLOT(setValue(int)));
QObject::connect(slider, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)));
QObject::connect(slider, SIGNAL(valueChanged(int)),
spinBox, SLOT(setValue(int)));
window.show();
return a.exec();
}
Сигналы valueChanged() отправляются виджетами ввода целых чисел при изменении состояния (хранимого значения), слот setValue()
позволяет установить значение виджета ввода целых чисел вручную,
слот setNum() позволяет заменить текст надписи на заданное число- вое значение.
2.2.3
Прочие возможности
Контейнеры данных — классы, обеспечивающие возможности кон- тейнеров C++ STL (абстракции структур данных), но обладающих большей функциональностью.
В качестве примера можно привести QString — контейнер для хранения строк и манипуляций над ними. В отличие от своего STL- аналога string, QString хранит и обрабатывает строки в Unicode, при- меняет механизм неявного совместного использования и т.д.
Другие примеры контейнеров Qt — QVector, QList, QLinkedList,
QStringList, QMap, QHash, QStack, QQueue и другие.
Различные операционные системы могут иметь различные интер- фейсы доступа к носителям данных. Например, в ОС Windows ком- поненты пути к файлу разделяются символом обратной косой черты,
а в ОС Linux — обычной косой черты; в ОС Windows приложения обычно размещаются в каталоге Program Files, а в ОС Linux могут храниться, например, в /usr/bin.
24
Семейство библиотек Qt
Кроссплатформенная система программирования, такая как Qt,
должна предоставлять программисту некий унифицированный интер- фейс доступа к файлам и каталогам, не зависящий от деталей кон- кретной целевой системы.
Qt имеет набор классов для представления объектов на накопите- лях (QDir, QFile), потокового ввода-вывода (QTextStream, QDataStream),
а также богатый набор функций, значительно упрощающих задачу программиста.
Следует также отметить, что возможности Qt гораздо шире биб- лиотеки функций для создания графического интерфейса пользова- теля. Qt включает несколько модулей, реализующих классы для ор- ганизации обмена данными по сети, обработки структурированных
XML-данных, доступа к базам данных и даже работы с подсистемами мобильных устройств, такими как GPS-приемник или акселерометр.
Средства визуального проектирования
25
§3.
Средства визуального проектирования
Qt предоставляет разработчику не только набор библиотек, но и комплекс утилит, позволяющих, в частности, значительно сократить объем написанного вручную кода за счет использования визуальных средств.
В настоящее время программисту доступны для загрузки как биб- лиотеки и утилиты в отдельности, так и комплект разработчика — Qt
SDK, включающий и то, и другое. Комплект разработчика поставля- ется с интегрированной средой Qt Creator, объединяющей возможно- сти практически всех утилит, входящих в комплект, а также предо- ставляющий редактор кода, средства интеграции с отладчиком и си- стемами управления версиями и т.п. К числу основных утилит Qt сле- дует отнести Qt Designer — визуальный редактор форм, Qt Assistant
— средство организации справочной информации, Qt Linguist — ин- струментарий создания многоязычного интерфейса приложения.
Одним из основных принципов Qt выражен слоганом «пиши мень- ше — создавай больше» (code less — create more). Для наглядности поясним этот принцип на очень показательном примере создания про- стого приложения, заимствованном из [5], попутно рассмотрев основ- ные возможности дизайнера форм.
Разрабатываемое приложение представляет собой простейшую те- лефонную книгу.
Главное окно приложения отображает перечень записей (пар имя–
номер), а также предоставляет интерфейс для управления записями:
кнопку «Add» для добавления новой записи, кнопку «Edit» для ре- дактирования записи, кнопку «Del» для удаления выделенной записи и кнопку «Clear» для удаления всех записей.
По нажатию на кнопку «Add» или «Edit» открывается вспомога- тельное диалоговое окно, позволяющее ввести или отредактировать имя и номер. Диалоговое окно имеет две кнопки — «Ok» для приня- тия изменений и «Cancel» для отмены.
Схематично интерфейс приложения изображен на рис. 2.
Откроем Qt Creator. Внешний вид стартовой страницы показан на рис 3. Помимо доступа к среде разработки, начальная страница открывает программисту богатую коллекцию примеров и докумен- тацию. Для начала работы выберем вариант «Создать проект...». С
помощью открывшегося мастера проекта последовательно устанавли-
26
Средства визуального проектирования
Рис. 2: Набросок интерфейса пользователя ваем следующем параметры:
1. в окне «Новый проект» выбираем вариант «Проект Qt Widget
/ GUI приложение Qt»;
2. в окне «Введение и размещение проекта» задаем имя («PhoneBook»)
и каталог проекта;
3. в окне «Настройка цели» выбираем вариант «Desktop» (прило- жение для настолького компьютера);
4. в окне «Информация о классе» установим QWidget в качестве базового класса, убедимся, что отмечен флажок «Создать фор- му»;
5. в окне «Управление проектом» откажемся от использования си- стемы контроля версий (вариант по умолчанию).
В результате работы мастера будет сгенерирован шаблон прило- жения, содержащий файлы исходного кода main.cpp (главная про- грамма), widget.h (интерфейс класса Widget, представляющего форму
Средства визуального проектирования
27
Рис. 3: Стартовое окно Qt Creator приложения) и widget.cpp (реализация класса Widget), а также файл проекта PhoneBook.pro и форму widget.ui. При этом Qt Creator пе- реключится в режим редактора. Дважды щелкнув по файлу формы,
перейдем в режим дизайнера. Вид окна Qt Creator в режиме дизай- нера показан на рис. 4.
Рассмотрим инструменты, доступные разработчику при работе с дизайнером форм.
Собственно графическая форма, представляющая проектируемый интерфейс находится в верхней части центральной области окна. На рис. 4 форма пуста.
Слева размещается палитра компонентов, представляющая биб- лиотеку виджетов Qt. Для размещения виджета на форме достаточно перетащить его мышью.
Каждый виджет имеет набор свойств, как собственных, так и уна- следованных от базовых классов (вплоть до QWidget и QObject). Про- граммист имеет возможность модифицировать значения доступных для редактирования свойств с помощью редактора свойств. На рис. 4
28
Средства визуального проектирования
Рис. 4: Вид окна Qt Creator в режиме дизайнера редактор свойств находится в правом нижнем углу.
Над редактором свойств на рис. 4 размещается инспектор объек- тов, изображающий спроектированный интерфейс в виде иерархиче- ской структуры (в соответствии с вложенностью виджетов в контей- неры и компоновщики).
В нижней части центральной области на рис. 4 находятся сгруппи- рованные вместе редактор действий (привязка горячих клавиш, под- сказок и т. п.) и редактор сигналов и слотов.
Приступим к созданию интерфейса основного окна нашего прило- жения. Для этого перетащим на форму четыре кнопки (Push Button)
и вертикальный разделитель (Vertical Spacer). Выравнивание элемен- тов не имеет значения (см. рис. 5 слева). Выделим все элементы и применим вертикальный компоновщик (рис. 5 справа).
Для управления компоновкой можно воспользоваться панелью ком- поновки в верхней части окна дизайнера (рис. 6) или выбрать соот- ветствующий пункт контекстного меню, вызванного на выделенных объектах.
Средства визуального проектирования
29
Рис. 5: Добавляем и компонуем набор кнопок
Рис. 6: Панель компоновки
Добавим виджет ListWidget и применим к форме табличный ком- поновщик (рис. 7).
Рис. 7: Завершаем компоновку интерфейса
Используя редактор свойств, установим значения свойств objectName
30
Средства визуального проектирования и text кнопок, задав соответственно «addButton» / «Add», «editButton»
/ «Edit», «deleteButton» / «Delete» и «clearButton» / «Clear All». Ана- логично установим значение свойства objectName для виджета списка в «list».
Все предыдущие операции выполнялись в режиме изменения ви- джетов. Дизайнер форм поддерживает четыре режима:
• режим изменения виджетов, предназначенный для визуального проектирования интерфейса;
• режим редактирования сигналов и слотов;
• режим изменения партнёров, предназначенный для привязки надписей к полям ввода;
• режим изменения порядка обхода, предназначенный для опре- деления порядка передачи фокуса клавиатуры по нажатию кла- виши табуляции.
Для переключения режимов в верхней части дизайнера имеется спе- циальная панель (см. рис. 8).
Рис. 8: Панель режимов
Переключимся в режим редактирования сигналов и слотов. Захва- тив мышью кнопку «Clear All», переведем курсор на виджет списка.
В открывшемся диалоговом окне, для отправителя (кнопки) выбе- рем сигнал clicked(), а для виджета списка слот clear(). Теперь при нажатии на кнопку «Clear All» все данные виджета списка будут уда- ляться.
Переключимся в режим изменения порядком обхода и установим нужный (см. рис. 12).
Добавим в проект еще один файл формы (editdialog.ui) и анало- гично описанному выше спроектируем второе окно. Полям ввода при- своим имена nameEdit и numberEdit.
Средства визуального проектирования
31
Рис. 9: Выполняем связывание сигналов и слотов
Рис. 10: Устанавливаем порядок обхода виджетов
Рис. 11: Диалог редактирования
32
Средства визуального проектирования
Для обеспечения возможности быстрого доступа к полям ввода с клавиатуры, настроим горячие клавиши. Для этого в тексты надпи- сей зададим как «&Name» и «&Phone» и, переключившись в режим редактирования партнеров, свяжем надписи с полями ввода. Теперь полям ввода можно быстро передавать фокус клавиатуры, используя сочетания Alt+N и Alt+P соответственно, а в тексте надписей эти символы будут изображаться с подчеркиванием, подсказывая пользо- вателю комбинации горячих клавиш.
На этом этапе подготовка форм завершена. Заметим, что в про- екте пока еще нет ни одной написанной вручную строки кода!
Сохраненная форма представляет собой XML-файл (*.ui) с описа- нием интерфейса. Файлы описаний интерфейса обрабатываются спе- циальной программой uic (user interface compiler). На выходе uic ге- нерирует файлы исходного кода, которые включаются в проект.
Создадим обработчик нажатия кнопки «Add...». Для этого, нахо- дясь в режиме изменения виджетов, в контекстном меню на кнопке
«Add...» выберем пункт «Перейти к слоту...» и в открывшамся окне выберем сигнал clicked(). Qt Creator переключится в режим редакти- рования файла исходного кода, соответствующего форме (см. пример
??
).
Пример 3.1
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::Widget()
{
delete ui;
}
Средства визуального проектирования
33
void Widget::on_addButton_clicked()
{
}
Первые два метода построены из шаблона проекта, использующе- го форму. Все элементы спроектированной визуально формы доступ- ны через объект ui, например ui->editButton. Третий метод — сге- нерированный слот для обработки нажатия кнопки. Автоматически создаваемые слоты получают имена по схеме on_objectName_signal.
Добавим в тело обработчика код, создающий диалоговое окно для ввода имени и номера.
Пример 3.2
EditDialog dlg( this);
if( dlg.exec() == Qt::Accepted )
ui->list->addItem( dlg.name() + " -- " + dlg.number());
Аналогично построим обработчик кнопки удаления.
Пример 3.3
void Widget::on_deleteButton_clicked()
{
foreach (QListWidgetItem *item, ui->list->selectedItems())
delete item;
}
Заметим, что кнопка «Delete» всегда активна, что не корректно —
кнопка должна быть доступна только, если есть выделенные позиции в списке. Добавим слот updateDeleteEnabled():
Пример 3.4
void Widget::updateDeleteEnabled()
{
34
Средства визуального проектирования ui->deleteButton->setEnabled(
ui->list->selectedItems().count() != 0);
}
Дополнительно необходимо переключиться к заголовочному фай- лу и объявить этот слот в секции private slots.
Соединим созданный слот с сигналом об изменении выделения:
Пример 3.5
connect(ui->list->selectionModel(),
SIGNAL(selectionChanged(QItemSelection,
QItemSelection)),
this,
SLOT(updateDeleteEnabled()));
Аналогично обработчику кнопки «Add» реализуется обработчик кнопки «Edit». Также дополнительно потребуется реализовать мето- ды класса EditDialog, представляющего диалоговое окно редактиро- вания записи телефонной книги. Предоставим читателю завершить работу над этим приложением.
Компоновка и размещение виджетов
35
§4.
Компоновка и размещение виджетов
Типичное окно современного приложения с графическим интер- фейсом может содержать десятки виджетов, некоторым образом раз- мещенных на холсте окна.
Размещение виджетов может быть выполнено вручную. QWidget и производные классы включают метод setGeometry(), обеспечиваю- щий возможность задать фиксированные размеры и позицию виджета
(см. пример 4.1).
Пример 4.1
Window::Window(QWidget *parent) : QWidget(parent)
{
setFixedSize(640, 480);
QTextEdit *txt = new QTextEdit(this);
txt->setGeometry(20, 20, 600, 400);
QPushButton *btn = new QPushButton(tr("&Close"), this);
btn->setGeometry(520, 440, 100, 20);
}
Однако расстановка виджетов вручную приводит к ряду сложно- стей. Например, при необходимости добавить новый виджет, требу- ется модифицировать местоположение остальных. Также возникают проблемы с изменением размеров окна пользователем — при фикси- рованном размещении часть элементов управления может оказаться недоступной, вне видимой области окна.
4.1
Использование компоновщиков
Qt предоставляет простой механизм автоматического размещения виджетов внутри других виджетов посредством компоновщиков. В
частности, копмоновка позволяет разумно использовать пространство окна и реализовать так называемый «эластичный интерфейс», когда виджеты получают возможность адаптировать свои размеры и раз- мещение в зависимости от содержимого и пользовательских настро- ек, например, автоматически растягиваться, чтобы вместить текст с увеличенным шрифтом.
36
Компоновка и размещение виджетов
Qt предоставляет три вида компоновщиков: вертикальный (класс
QVBoxLayout), горизонтальный (класс QHBoxLayout), табличный (класс
QGridLayout) и компоновщик форм (QFormLayout), предназначенный для формирования форм ввода, состоящих из колонки пар надпись —
поле ввода. Непосредственные размеры вложенных виджетов опреде- ляются в ходе выполнения специального процесса, называемого «пе- реговорами».
Рассмотрим пример типичного диалогового окна:
Рис. 12: Пример использования компоновщиков
Надпись «Printer» и выпадающий список в верхней части диало- гового окна объединены с помощью горизонтального компоновщика.
Второй горизонтальный компоновщик объединяет две группы кнопок.
Третий формирует строку кнопок в нижней части окна. Для выравни- вания элементов диалогового окна применены разделители (QSpacer)
— невидимые виджеты-распорки. Все три горизонтальных компонов- щика и один из разделителей выровнены с помощью вертикального компоновщика, примененного ко всему окну.
Следующий пример демонстрирует общую схему построения ин- терфейса, представленного на рис. 12 с использованием вертикальных и горизонтальных компоновщиков.
Пример 4.2
// Создаем внешний вертикальный компоновщик
QVBoxLayout *outerLayout = new QVBoxLayout(this);
Компоновка и размещение виджетов
37
// Создаем верхний горизонтальный компоновщик
QHBoxLayout *topLayout = new QHBoxLayout();
// Добавляем виджеты topLayout->addWidget(new QLabel("Printer:"));
topLayout->addWidget(c=new QComboBox());
// Добавляем в внешний компоновщик outerLayout->addLayout(topLayout);
// Создаем средний горизонтальный компоновщик
QHBoxLayout *groupLayout = new QHBoxLayout();
outerLayout->addLayout(groupLayout);
// Добавляем разделитель outerLayout->addSpacerItem(new QSpacerItem(...));
// Создаем нижний горизонтальный компоновщик
QHBoxLayout *buttonLayout = new QHBoxLayout();
buttonLayout->addSpacerItem(new QSpacerItem(...));
buttonLayout->addWidget(new QPushButton("Print"));
buttonLayout->addWidget(new QPushButton("Cancel"));
outerLayout->addLayout(buttonLayout);
// Применяем внешний компоновщик к окну setLayout(outerLayout);
Каждый виджет наследует от класса QWidget свойства sizeHint,
minimumSizeHint и sizePolicy. Свойство sizeHint определяет, какое про- странство виджет «хотел бы» занимать в нормальных условиях. Свой- ство minimumSizeHint задает минимальный размер, меньше которого виджет не может занимать ни при каких обстоятельствах. Значением свойства sizePolicy является политика, определяющая отношение ви- джета к изменениям его размера и принимающая одно из следующих значений:
• QSizePolicy::Fixed — виджет не может иметь размер, отличный от sizeHint;
38
Компоновка и размещение виджетов
• QSizePolicy::Minimum — sizeHint представляет минимально до- пустимый размер виджета, но он может быть неограниченно растянут;
• QSizePolicy::Maximum — sizeHint представляет максимально до- пустимый размер виджета, но он может быть неограниченно ужат;
• QSizePolicy::Preferred — sizeHint представляет оптимальный раз- мер виджета, но изменения допустимы в обе стороны;
• QSizePolicy::Expanding — аналогично Preferred, но виджет тре- бует предоставить ему любое доступное пространство компонов- щика;
• QSizePolicy::MinimumExpanding — аналогично Minimum, но ви- джет требует предоставить ему любое доступное пространство компоновщика;
• QSizePolicy::Ignored — sizeHint игнорируется, виджету выделя- ется столько пространства, сколько возможно в пределах ком- поновщика.
Помимо варьирования свойств sizeHint, minimumSizeHint и sizePolicy вложенных в компоновщик виджетов, управление размещением мо- жет быть выполнено и средствами самого компоновщика. Метод addWidget()
содержит необязательный второй аргумент (stretch factor), равный по умолчанию нулю. Ненулевые значения коэффициента растяжения за- дают относительный размер пространства, выделенного компоновщи- ком под размещение виджета.
При использовании табличного компоновщика программист зада- ет, по каким границам сетки выравнивается каждый вложенный ви- джет. Такой подход позволяет занимать под виджет любую прямо- угольную подобласть таблицы, а также создавать перекрытия. Если добавляемый виджет занимает ровно одну ячейку таблицы, достаточ- но определить левую и верхнюю границы, в противном случае — все четыре (см. пример 4.3).
Пример 4.3
Компоновка и размещение виджетов
39
QPushButton *button1 = new QPushButton("One");
QPushButton *button2 = new QPushButton("Two");
QPushButton *button3 = new QPushButton("Three");
QPushButton *button4 = new QPushButton("Four");
QPushButton *button5 = new QPushButton("Five");
QGridLayout *layout = new QGridLayout;
layout->addWidget(button1, 0, 0);
layout->addWidget(button2, 0, 1);
layout->addWidget(button3, 1, 0, 1, 2);
layout->addWidget(button4, 2, 0);
layout->addWidget(button5, 2, 1);
Предложенный в примере код создает интерфейс, представленный на рис. 13.
Рис. 13: Пример использования табличного компоновщика
4.2
Контейнерные виджеты
Помимо компоновщиков иерархическая структура графического интерфейса может быть построена с помощью виджетов-контейнеров
— виджетов, которые могут содержать другие виджеты.
Контейнерные виджеты могут применяться как для организации высокоуровневых элементов, например, окон — QWidget, QDialog, а также QMainWindow, так и группировки виджетов внутри окон —
QGroupBox, QTabWidget и т.п.
40
Компоновка и размещение виджетов
Пример 4.4 иллюстрирует применение QMainWindow для органи- зации окна приложения и QTabWidget для создания набора вкладок.
Следует отметить, что в отличие от «пустых» контейнеров QWidget и
QDialog, QMainWindow включает основные элементы типового окна приложения — меню, строку состояния и контейнер для наполнения центральной области (centralWidget).
Пример 4.4
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QMainWindow *window = new QMainWindow();
window->setWindowTitle("MainWindow");
window->resize(250, 250);
QWidget *centralWidget = new QWidget(window);
QTabWidget *tabs = new QTabWidget(centralWidget);
tabs->setFixedSize(245, 245);
tabs->addTab(new QWidget(),"Tab 1");
tabs->addTab(new QWidget(),"Tab 2");
window->setCentralWidget(centralWidget);
window->show();
return app.exec();
}
Обзор библиотеки виджетов Qt
41
§5.
Обзор библиотеки виджетов Qt
В предыдущих разделах уже были приведены примеры виджетов
— основным строительных элементов графического интерфейса поль- зователя.
Далее рассмотрим несколько основных виджетов Qt. В целях эко- номии все примеры построены по общей схеме: имеется приложение,
состоящее из трех следующих файлов исходного кода. В главном фай- ле main.cpp создается экземпляр приложения и собственного виджета
Widget.
Пример 5.1
#include
#include "widget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
Файл widget.h содержит описание класса Widget, при этом в при- мерах будут варьироваться файлы заголовков и приватные компонен- ты.
#ifndef WIDGET_H
#define WIDGET_H
#include
// Необходимые файлы заголовков class Widget : public QWidget
{
42
Обзор библиотеки виджетов Qt
Q_OBJECT
public:
Widget(QWidget *parent = 0);
Widget();
private:
// Необходимые приватные компоненты
};
#endif // WIDGET_H
Файл widget.cpp содержит реализацию методов класса Widget,
при этом пользовательский интерфейс формируется в конструкторе класса. Именно код конструктора будет приведен во всех последую- щих примерах этого раздела.
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
// Конструируем интерфейс пользователя
}
Widget::Widget()
{
}
5.1
Кнопки
Три класса QPushButton, QCheckBox, QRadioButton реализуют соответственно простую кнопку, флажок и радиокнопку. Базовым клас- сом всех кнопок является QAbstractButton.
Основные сигналы для кнопок:
Обзор библиотеки виджетов Qt
43
• clicked() отправляется в момент, когда пользователь отпускает кнопку мыши;
• toggled(bool) отправляется в момент изменения состояния кноп- ки.
Отметим также наиболее полезные свойства:
• checkable — истина, если кнопка может работать как флажок;
• checked — истина, если флажок имеет отметку;
• text — текст кнопки;
• icon — иконка кнопки, которая может отображаться вместе с текстом.
Следующий пример иллюстрирует использование QCheckBox. В
конструкторе создадим флажок, установив отметку как начальное со- стояние.
Пример 5.2
// Создаем флажок checkBox = new QCheckBox("Show Title", this);
checkBox->setCheckState(Qt::Checked);
// Связываем изменение состояния флажка
// с обработчиком connect(checkBox, SIGNAL(stateChanged(int)),
this, SLOT(showTitle(int)));
Код обработчика для изменения состояния флажка приведен ни- же. В данном примере меняем заголовок окна.
void Widget::showTitle(int state)
{
if (state == Qt::Checked) {
setWindowTitle("QCheckBox");
} else {
setWindowTitle("");
}
}
44
Обзор библиотеки виджетов Qt
5.2
Надписи
QLabel отображает текстовый блок или картинку, которые зада- ются свойствами text и pixmap соответственно. Следующий пример демонстрирует использование QLabel для изображения простой тек- стовой надписи.
Пример 5.3
// Создаем компоновщик layout = new QHBoxLayout();
// Создаем надпись label = new QLabel("Hello");
// Добавляем надпись в компоновщик,
центрируя разделителями layout->addWidget(new QSplitter());
layout->addWidget(label);
layout->addWidget(new QSplitter());
// Применяем компоновщик к виджету setLayout(layout);
QLabel распознает некоторое подмножество тэгов HTML-разметки.
Пример 5.4
// Создаем полужирную надпись красного цвета label = new QLabel(
"
1 2 3
Министерство образования и науки Российской Федерации
Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования
ПЕТРОЗАВОДСКИЙ ГОСУДАРСТВЕННЫЙ
УНИВЕРСИТЕТ
А. В. Бородин, А. В. Бородина
СРЕДСТВА РАЗРАБОТКИ
ГРАФИЧЕСКИХ ИНТЕРФЕЙСОВ
ПОЛЬЗОВАТЕЛЯ
Учебное пособие
Петрозаводск
Издательство ПетрГУ
2012
ББК 32.973
УДК 004
Б833
Печатается по решению редакционно-издательского совета
Петрозаводского государственного университета
Издается в рамках реализации комплекса мероприятий
Программы стратегического развития ПетрГУ на 2012–2016 г.г.
Р е ц е н з е н т ы:
канд. техн. наук Р. В. Сошкин;
канд. физ.-мат. наук, доцент К. А. Кулаков
Бородин А. В.
Б833
Средства разработки графических интерфейсов пользовате- ля : учебное пособие / А. В. Бородин, А. В. Бородина. — Пет- розаводск : Изд-во ПетрГУ, 2012. — 77 c.
ISBN 978-5-8021-1537-4
Учебное пособие предназначено для студентов математическо- го факультета первого курса направлений подготовки «Прикладная математика и информатика», «Информационные системы и техно- логии»
ББК 32.973
УДК 004
ISBN 978-5-8021-1537-4
© Петрозаводский государственный университет, 2012
© Бородин А. В., Бородина А. В., 2012
3
Содержание
Введение
4
§1.Графические интерфейсы пользователя
5
§2.Семейство библиотек Qt
14 2.1 Первая Qt-программа . . . . . . . . . . . . . . . . . . . .
14 2.2 Обзор средств Qt . . . . . . . . . . . . . . . . . . . . . .
16 2.2.1 Виджеты и конструирование интерфейсов . . .
16 2.2.2 Класс QObject и метаобъектный компилятор . .
18 2.2.3 Прочие возможности . . . . . . . . . . . . . . . .
23
§3.Средства визуального проектирования
25
§4.Компоновка и размещение виджетов
35 4.1 Использование компоновщиков . . . . . . . . . . . . . .
35 4.2 Контейнерные виджеты . . . . . . . . . . . . . . . . . .
39
§5.Обзор библиотеки виджетов Qt
41 5.1 Кнопки . . . . . . . . . . . . . . . . . . . . . . . . . . . .
42 5.2 Надписи . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44 5.3 Инструменты ввода текста . . . . . . . . . . . . . . . . .
45 5.4 Инструменты ввода чисел . . . . . . . . . . . . . . . . .
47
§6.Создание собственных виджетов
48 6.1 Создание виджета композицией имеющихся . . . . . . .
48 6.2 Отрисовка на поверхности виджета . . . . . . . . . . . .
50 6.3 Пример создания виджета с нуля . . . . . . . . . . . . .
54
§7.Работа с 2D-графикой
59 7.1 Graphics View Framework . . . . . . . . . . . . . . . . . .
59 7.2 Управление элементами сцены . . . . . . . . . . . . . .
62 7.3 Создание собственных элементов сцены . . . . . . . . .
67
§8.Интернационализация приложений
73
4
Введение
Введение
Развитие информационных технологий и рост числа пользовате- лей компьютеров предъявляют новые требования к пользовательским интерфейсам. Перед разработчиками программного обеспечения сто- ит непростая задача обеспечить низкий порог вхождения для широко- го круга не обладающих достаточной квалификацией и не прошедших специальную подготовку в использовании ЭВМ лиц за счет упроще- ния средств диалога с машиной, использования интуитивно понятных графических метафор, унификации интерфейсов.
Таким образом, овладение средствами разработки современных графических интерфейсов пользователя является необходимым эле- ментом подготовки разработчика прикладного программного обеспе- чения.
Пособие представляет собой обзор основных концепций, лежащих в основе графических интерфейсов, и краткое введение в программи- рование с использованием фреймворка Qt, предоставляющего набор библиотек и утилит для создания прикладных программ, в том числе,
приложений с графическим интерфейсом.
Выбор Qt среди широкого круга альтернатив обусловлен двумя основными причинами. Во-первых, Qt является продуктом с откры- тым кодом, таким образом его использование не может быть ограниче- но изменением политики компании-разработчика. Во-вторых, Qt из- начально разрабатывался как кроссплатформенный инструмент: на- писанные с использованием этого фреймворка приложения переноси- мы на уровне исходного кода между всеми популярными настольны- ми операционными системами, более того, Qt можно использовать и для разработки для мобильных устройств, таких как смартфоны и планшетные компьютеры. Немаловажным является факт наличия в свободном доступе весьма подробной документации разработчика (см.
[6]).
Материалы пособия используются в Петрозаводском государствен- ном университете на математическом факультете в модуле «Средства разработки графических интерфейсов» дисциплины «Информатика»,
читаемой студентам I курса направлений подготовки «Прикладная математика и информатика» и «Информационные системы и техно- логии».
Графические интерфейсы пользователя
5
§1.
Графические интерфейсы пользовате- ля
Графическими интерфейсами пользователя (англ. Graphical User
Interface, GUI) считают такие, в которых элементы интерфейса пред- ставлены в виде визуальных объектов, т. е. графических изображений.
Элементы интерфейса, используя метафоры, несут информацию об их назначении и способе использования, обеспечивая обучение поль- зователя в процессе работы. Интерфейсы, обладающие этим свой- ством, принято называть интуитивно понятными.
Ко всем видимым элементам интерфейса обеспечивается произ- вольный достум посредством широкого спектра устройств ввода —
клавиатуры, указателя «мышь», сенсорного экрана и др.
Первые разработки в области графических интерфейсов относят- ся к семидесятым годам XX века и, как правило, связываются с ком- пьютером Alto, разработанным в исследовательских лабораториях ком- пании XEROX в Пало Альто, Калифорния. Тогда же впервые появ- ляется аббревиатура WIMP (Windows, Icons, Menus, Pointing Device),
концептуально характеризующая графический интерфейс пользова- теля.
Коммерческий успех к компьютерам, операционные системы ко- торых предоставляли графический интерфейс, пришел в восьмидеся- тые годы XX века и связан с появлением таких разработок, как Star компании Xerox и Lisa компании Apple.
Тогда же были предпринты первые попытки упорядочить разра- ботку графических интерфейсов пользователя, в частности, следует отметить руководство IBM Common User Access Specification.
Одной из первых оконных систем, обеспечивающей стандартные инструменты и протоколы для построения графических интерфейсов,
стала X Window System, разрабатываемая с 1984 года и используемая по настоящее время в UNIX-подобных ОС. X Window System обес- печивает базовые функции графической среды: отрисовку и переме- щение окон на экране, взаимодействие с устройствами ввода, такими как, например, мышь и клавиатура.
X Window System не определяет деталей интерфейса пользовате- ля — этим занимаются менеджеры окон. С другой стороны, в этой системе предусмотрена сетевая прозрачность: графические приложе-
6
Графические интерфейсы пользователя ния могут выполняться на другой машине в сети, а их интерфейс при этом будет передаваться по сети и отображаться на локальной ма- шине пользователя. В контексте X Window System термины «клиент»
и «сервер» имеют непривычное для многих пользователей значение:
«сервер» означает локальный дисплей пользователя (дисплейный сер- вер), а «клиент» — программу, которая этот дисплей использует (она может выполняться на удалённом компьютере).
Архитектура X Window System проиллюстрирована на рис. 1.
Рис. 1: Архитектура X Window System
X Window System предоставляет программный интерфейс для со- здания приложений с графическим интерфейсом посредством исполь- зования библиотеки XLib. Однако, программирование на уровне XLib слишком многословно и перегружает программиста необходимостью реализовывать вручную даже примитивные детали интерфейса.
Рассмотрим достаточно простую XLib-программу, изображающую на экране простое окно с надписью.
Пример 1.1
Графические интерфейсы пользователя
7
#include
#include
#include
#include
#include
#include
#define X 0
#define Y 0
#define WIDTH 400
#define HEIGHT 300
#define WIDTH_MIN 50
#define HEIGHT_MIN 50
#define BORDER_WIDTH 1
#define TITLE "Example"
#define ICON_TITLE "Example"
#define PRG_CLASS "Example"
/* Сообщает оконному менеджеру параметры окна */
static void SetWindowManagerHints(Display * display,
char *PClass, char *argv[], int argc,
Window window, int x, int y,
int win_wdt, int win_hgt,
int win_wdt_min, int win_hgt_min,
char *ptrTtl, char *ptrITtl, Pixmap pixmap)
{
XSizeHints size_hints;
XWMHints wm_hints;
XClassHint class_hint;
XTextProperty winnm, icnm;
if (!XStringListToTextProperty (&ptrTtl, 1, &winnm)
|| !XStringListToTextProperty (&ptrITtl, 1, &icnm)) {
puts ("No memory!\n");
exit (1);
}
8
Графические интерфейсы пользователя size_hints.flags = PPosition | PSize | PMinSize;
size_hints.min_width = win_wdt_min;
size_hints.min_height = win_hgt_min;
wm_hints.flags = StateHint | IconPixmapHint | InputHint;
wm_hints.initial_state = NormalState;
wm_hints.input = True;
wm_hints.icon_pixmap = pixmap;
class_hint.res_name = argv[0];
class_hint.res_class = PClass;
XSetWMProperties (display, window, &winnm,
&icnm, argv, argc, &size_hints,
&wm_hints, &class_hint);
}
/* Обработчик ошибки */
int MyXlibIOErrorHandler(Display *d)
{
XCloseDisplay(d);
exit(0);
}
/* Основная программа */
int main(int argc, char *argv[])
{
Display *display;
int ScreenNumber;
GC gc;
XEvent report;
Window window;
if ((display = XOpenDisplay(NULL)) == NULL) {
puts ("Can not connect to the X server!\n");
exit (1);
}
ScreenNumber = DefaultScreen(display);
Графические интерфейсы пользователя
9
window = XCreateSimpleWindow(display,
RootWindow(display, ScreenNumber),
X, Y, WIDTH, HEIGHT, BORDER_WIDTH,
BlackPixel(display, ScreenNumber),
WhitePixel(display, ScreenNumber));
SetWindowManagerHints (display, PRG_CLASS, argv, argc,
window, X, Y, WIDTH, HEIGHT, WIDTH_MIN,
HEIGHT_MIN, TITLE, ICON_TITLE, 0);
XSelectInput (display, window, ExposureMask
| StructureNotifyMask);
XMapWindow (display, window);
XSetIOErrorHandler (&MyXlibIOErrorHandler);
while (1) {
XNextEvent (display, &report);
switch (report.type) {
case Expose:
if (report.xexpose.count != 0) break;
gc = XCreateGC(display, window, 0, NULL);
XSetForeground(display, gc,
BlackPixel(display, 0));
XDrawString (display, window, gc, 20, 50,
"Hello, world!", strlen ("Hello, world!"));
XFreeGC (display, gc);
XFlush (display);
break;
default:
fprintf (stderr, "%d\n", report.type);
}
}
return 0;
}
10
Графические интерфейсы пользователя
Используя XLib, программист полностью контролирует свойства оконного приложения, именно этим объясняется большое количество параметров в вызовах функций. Также программист самостоятельно выполняет проверку наступления тех или иных событий, организуя бесконечный цикл опроса обработки. На полотне окна программист также самостоятельно вынужден отрисовывать необходимые элемен- ты.
Из приведенного примера можно извлечь следующие выводы.
• Поскольку любая программа с графическим интерфейсом явля- ется событийно-ориентированной, то есть большую часть време- ни проводит в состоянии «сна», выполняя те или иные функции лишь в ответ на активность пользователя, то она, так или иначе,
будет включать цикл обработки событий, подобный приведен- ному в примере. Следовательно, необходимости в включении этого цикла вручную в каждой программе нет — его можно реализовать в виде отдельной функции библиотеки, предостав- ляющей программные средства создания графического интер- фейса. При этом код обработки событий оформляется в виде от- дельных функций, регистрируемых до входа в цикл обработки событий. Такие функции называют функциями обратного вы- зова (callback), так как в программе отсутствуют явные вызовы этих функций, они вызываются из функции, реализующей аб- стракцию основного цикла при наступлении соответствующего события.
• Большая часть параметров оконного приложения либо контро- лируется оконным менеджером, либо имеет стандартные зна- чения (например, цветовая гамма может определяться темой оформления). Поэтому программный интерфейс можно суще- ственно упростить.
Исходя из представленных соображений, над XLib был разработан набор высокоуровневых библиотек Xt (так называемый «тулкит»),
скрывающий сложность XLib за оберточными вызовами. Рассмотрим пример Xt-программы, предоставляющий ту же функциональность,
что и пример 1.1.
Пример 1.2
Графические интерфейсы пользователя
11
#include
#include
#include
#include
void DrawHelloString (Widget prWidget, XtPointer pData,
XEvent *prEvent, Boolean *pbContinue)
{
Display *prDisplay = XtDisplay (prWidget);
Window nWindow = XtWindow (prWidget);
GC prGC;
if (prEvent->type == Expose) {
prGC = XCreateGC (prDisplay, nWindow, 0, NULL);
XDrawString (prDisplay, nWindow, prGC, 10, 50,
"Hello, world!", strlen ("Hello, world!"));
XFreeGC (prDisplay, prGC);
}
}
void main (int argc, char **argv)
{
Arg args[2];
Widget toplevel, prCoreWidget;
toplevel = XtInitialize(argv[0], "GUI Example",
NULL, 0, &argc, argv);
prCoreWidget = XtCreateWidget ("Core", widgetClass,
toplevel, NULL, 0);
XtSetArg(args[0], XtNwidth, 400);
XtSetArg(args[1], XtNheight, 300);
XtSetValues(prCoreWidget, args, 2);
XtManageChild (prCoreWidget);
XtAddEventHandler(prCoreWidget, ExposureMask,
False, DrawHelloString, NULL);
12
Графические интерфейсы пользователя
XtRealizeWidget(toplevel);
XtMainLoop();
}
Типовой цикл обработки событий заменен единственным вызовом
XtMainLoop(). При этом обработчики событий, реализованные в виде функций обратного вызова, связываются с сигналами событий (реги- стрируются) заранее посредством вызова XtAddEventHandler().
Также в Xt введено понятие виджета (widget = visual gadget) — ти- пового элемента графического интерфейса. Однако, отрисовка стро- ки по-прежнему осуществляется с помощью низкоуровневого XLib- вызова XDrawString().
Современные инструменты разработки графических интерфейсов также реализуют абстракцию основного цикла обработки событий и предоставляют разработчику богатую библиотеку виджетов. Следую- щий пример демонстрирует использование средств библиотеки GTK+.
Пример 1.3
#include
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *label;
/* Инициализируем подсистему GTK+ */
gtk_init(&argc, &argv);
/* Создаем окно */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* Привязываем обработчик нажатия кнопки закрытия окна */
g_signal_connect(G_OBJECT (window), "destroy",
G_CALLBACK (gtk_main_quit), NULL);
/* Создаем кнопку с надписью */
label = gtk_label_new("Hello World");
Графические интерфейсы пользователя
13
/* Помещаем кнопку в окно (контейнер) */
gtk_container_add(GTK_CONTAINER (window), label);
/* Отрисовываем окно и все принадлежащие ему объекты */
gtk_widget_show_all(window);
/* Основной цикл обработки событий */
gtk_main();
return 0;
}
14
Семейство библиотек Qt
§2.
Семейство библиотек Qt
2.1
Первая Qt-программа
По сложившейся традиции руководств по программированию, об- зор возможностей семейства библиотек Qt начнем с программы, вы- водящей на экран приветствие «Hello, world!».
Пример 2.1
#include
#include
int main(int argc, char *argv[])
{
// Создаем экземпляр приложения
QApplication a(argc, argv);
// Создаем надпись
QLabel label("Hello World");
// Отображаем надпись на экране label.show();
// Основной цикл обработки событий return a.exec();
}
Текст программы содержит включения файлов заголовков с опре- делениями необходимых классов (QApplication и QLabel).В Qt на каж- дый класс приходится в точности один заголовочный файл.
Выполнение программы начинается с создания объекта класса
QApplication, представляющего абстракцию графического приложе- ния. Ни одна графическая программа не обходится без QApplication,
так как именно в этом классе реализован цикл обработки событий,
определяющий основной алгоритм работы графического приложения.
Мы передаем управление в цикл обработки событий вызовом мето- да exec() класса QApplication. Именно в exec() программа проводит
Семейство библиотек Qt
15
большую часть времени работы, ожидая пользовательской реакции или другого события.
В конструктор QApplication мы передаем аргументы командной строки. Qt-приложение способно распознавать некоторые параметры,
заданные в командной строке и адаптировать поведение в соответ- ствии с ними.
После создания объекта QApplication создается объект QLabel,
представляющий простую текстовую надпись. Текст для отображения передается через конструктор класса.
Метод show() заставляет Qt-приложение отрисовать надпись на экране, при этом, так как надпись не имеет родительского объекта,
то сама формирует окно.
Цикл обработки событий прекращается при выполнении метода quit() класса QApplication, например, если пользователь закрывает окно. В этом случае приложение завершается.
Так как Qt поддерживается на многих различных платформах, от- личающихся организацией процесса сборки, разработчиками Qt пред- ложен специальный проектно-ориентированный инструмент сборки —
qmake.
Программа qmake генерирует файлы сборки Makefile для каж- дой конкретной платформы на основе специального файла проекта
(*.pro), который описывает приложение в независимой от операцион- ной системы и среды программирования форме.
Сохраним нашу первую программу под именем main.cpp в ката- логе myFirstQtProject и сгенерируем в нем шаблон файла проекта с помощью той же программы qmake:
Пример 2.2
user@linux:/myFirstQtProject> qmake -project
В результате будет построен шаблон файла проекта:
Пример 2.3
#myFirstQtProject/myFirstQtProject.pro
16
Семейство библиотек Qt
#####################################
# Automatically generated by qmake
#####################################
TEMPLATE = app
CONFIG -= moc
DEPENDPATH += .
INCLUDEPATH += .
# Input
SOURCES += main.cpp
Файл проекта состоит из директив, в данном примере директива
TEMPLATE указывает, что проект представляет собой приложение
(app), а SOURCES содержит перечень файлов исходного кода проекта
(в нашем примере только main.cpp).
Повторный вызов qmake (теперь без параметров) создаст зависи- мый от платформы файл сборки (Makefile), который можно исполь- зовать для получения исполняемого файла:
Пример 2.4
user@linux:/myFirstQtProject> qmake user@linux:/myFirstQtProject> make user@linux:/myFirstQtProject> ./myFirstQtProject
2.2
Обзор средств Qt
2.2.1
Виджеты и конструирование интерфейсов
Основу библиотеки графического интерфейса составляют видже- ты — классы, представляющие визуальные объекты. Виджеты обра- зуют иерархию классов. Базовым классом для всех виджетов в Qt яв- ляется QWidget. При этом имеется более шестидесяти производных от QWidget классов, реализующих типовые элементы управления. До- полнительно программист имеет возможность создания собственных виджетов, расширяя функциональность элементов стандартного на- бора.
Семейство библиотек Qt
17
Визуальная структура интерфейсов формируется посредством кон- тейнерных классов, позволяющих организовывать виджеты в иерар- хии. Таким образом, виджеты могут содержать в себе другие видже- ты.
Харахтерными особенностями виджетов являются следующие:
• виджет занимает прямоугольную область экрана;
• виджет может получать сообщения о событиях, например, от устройств ввода;
• виджет может отправлять сигналы об изменении состояния.
Все виджеты имеют набор общих свойств, унаследованных от ба- зового класса QWidget:
• enabled — возможна ли реакция на ввод пользователя;
• visible — видим ли виджет (или скрыт).
Действие этих свойств распространяется на вложенные виджеты.
В примере 2.1 интерфейс представляен единственным виджетом
QLabel, поэтому проблем с его расположением нет. Однако интер- фейс приложения может быть сконструирован из десятков виджетов,
и проблема их взаимного расположения окажется весьма серьезной.
Qt предоставляет так называемые средства компоновки, облегча- ющие процесс взаимной расстановки виджетов в пространстве окна.
Следующий пример организовывает две надписи вертикально:
Пример 2.5
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Создаем окно
QWidget window;
18
Семейство библиотек Qt
// Создаем основной компоновщик окна -
// вертикальный компоновщик
QVBoxLayout* mainLayout = new QVBoxLayout(&window);
// Создаем две надписи
QLabel* label1 = new QLabel("One");
QLabel* label2 = new QLabel("Two");
// Организуем надписи с помощью компоновщика mainLayout->addWidget(label1);
mainLayout->addWidget(label2);
// Отображаем окно window.show();
return a.exec();
}
Подробнее вопросы компоновки и виды компоновщиков рассмот- рены в четвертом параграфе. К рассмотренному примеру следует за- метить, что только для окна был вызван метод show(), надписи, вло- женные в окно, автоматически становятся для него «дочерними» объ- ектами и визуализируются автоматически при отображении родителя.
2.2.2
Класс QObject и метаобъектный компилятор
Базовым классом большинства классов семейства библиотек Qt,
в частности, всех виджетов, является QObject. Исключениями явля- ются только классы, для которых важна легковесность (например,
графические примитивы и контейнеры данных). Именно QObject реа- лизует большую часть возможностей, характерных для Qt: обработку событий, сигналы и слоты, свойства объектов, управление памятью.
Каждый унаследованный от QObject имеет так называемый ме- таобъект, содержащий информацию об имени класса, связях и т. д.
Таким образом Qt вводит механизмы интроспекции в язык C++.
Метаданные собираются на этапе трансляции специальной про- граммой — метаобъектным компилятором moc. Программа moc явля-
Семейство библиотек Qt
19
ется препроцессором, обрабатывающим файлы заголовков проекта и конструирующим вспомогательные файлы исходного кода (moc_*.cpp),
также включаемые в проект. При использовании системы qmake ге- нерация этих файлов осуществляется прозрачно для программиста.
Программа moc извлекает из файлов заголовков специальные мак- росы (например, в следующем примере, Q_OBJECT, Q_CLASSINFO),
а также ключевые слова Qt, не являющиеся элементами языка C++:
signals, slots, emit и др. Обработанные файлы содержат только кон- струкции C++ и могут быть скомпилированы обычным компилято- ром C++.
Макрос Q_OBJECT должен присутствовать в любом классе, на- следуемом от QObject.
Пример 2.6
class MyClass : public QObject
{
Q_OBJECT
Q_CLASSINFO("author", "John Doe")
public:
MyClass(const Foo &foo, QObject *parent=0);
Foo foo() const;
public slots:
void setFoo( const Foo &foo );
signals:
void fooChanged( Foo );
private:
Foo m_foo;
};
Класс QObject позволяет создавать свойства с методами чтения и записи. Свойство не просто представляет некоторое поле класса,
предназначенное для хранения значения. В результате установки зна- чения свойства визуальный объект, возможно должен быть модифи- цирован (например, при изменении цвета). Поэтому свойству соответ- ствует пара методов чтения и установки значения, выполняющие и соответствующий дополнительный код — так называемые «геттер» и
«сеттер». В соответствии с соглашениями имя геттер-метода должно
20
Семейство библиотек Qt совпадать с именем свойства. Имя сеттер-метода начинается со слова set, за которым следует имя свойства с заглавной буквы.
Пример 2.7
class QLabel : public QFrame
{
Q_OBJECT
// Создаем свойство text с геттером text
// и сеттером setText
Q_PROPERTY(QString text READ text WRITE setText)
public:
QString text() const;
public slots:
void setText(const QString &);
};
В примере 2.5 объект класса QWidget, представляющий окно, со- здан на стеке, а под компоновщик и надписи память выделена динами- чески. Это не случайно. Вообще говоря, C++ не предусматривает ав- томатического управления памятью, однако в Qt некоторые подобные возможности введены. Объекты, наследующие от класса QObject (в частности, виджеты) могут организовываться в древовидные иерар- хии, имея родителя (parent object) и дочерних объектов (child objects).
Если такой объект уничтожается (вызывается деструктор), то Qt ав- томатически уничтожает все дочерние объекты. Те, в свою очередь,
уничтожают свои дочерние объекты и так далее. Это освобождает программиста от необходимости отслеживать и освобождать занятую дочерними объектами память вручную.
Однако для того, чтобы автоматическое управление памятью мог- ло быть реализовано, память под дочерние объекты должна быть вы- делена на куче динамической памяти.
Любое нетривиальное графическое приложение должно иметь воз- можность отреагировать на пользовательские действия, а значит долж- на существовать система коммуникации между объектами. В отличие от многих инструментов создания графических интерфейсов, в Qt не используются функции обратного вызова для обеспечения реакции
Семейство библиотек Qt
21
на события, а применяется более совершенная технология сигналов и слотов.
И сигнал, и слот являются методами класса, за тем исключением,
что тело сигнала программист не реализует, его создает метаобъект- ный компилятор. Тип возвращаемого значения сигнала — void. Слот может иметь отличный от void тип возвращаемого значения, но ис- пользовать возвращаемое значение можно только, если слот вызыва- ется как обычная функция, а не по сигналу. Сигналы в определении класса объявляются в секции signals, слоты — в секции slots. Отпра- вить сигнал из кода можно с помощью ключевого слова emit.
Сигнатуры связываемых сигнала и слота должны либо совпадать,
либо в сигнале может присутствовать часть дополнительных парамет- ров, которые будут проигнорировать в слоте. В общем случае верно правило: «Qt может игнорировать часть данных, но не может полу- чить данные из ничего».
В следующем примере сигнал, отправляемый кнопкой в момент клика, связывается с стандартным слотом quit() объекта QApplication.
Пример 2.8
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Создаем и отрисовываем кнопку с надписью "Quit"
QPushButton button("Quit");
button.show();
// Связываем сигнал clicked() и слот quit()
QObject::connect(&button, SIGNAL(clicked()),
&a, SLOT(quit()));
return a.exec();
}
22
Семейство библиотек Qt
Статическая функция connect() класса QObject выполняет связы- вание сигнала и слота. Сигнал — функция, уведомляющая о наступле- нии события, слот — функция, получающая этот сигнал и корректно обрабатывающая его. Функция connect() получает четыре аргумента:
объект-отправитель, сигнал, объект-получатель, слот. При связыва- нии необходимо использовать макросы SIGNAL() и SLOT().
В примере 2.8 никакой дополнительной информации из сигнала в слот не передается. Рассмотрим более реальный пример. Пусть ин- терфейс содержит три виджета: QLabel, QSpinBox и QSlider. Послед- ние два обеспечивают просто различные способы ввода целых чисел.
Свяжем эти виджеты друг с другом таким образом, чтобы все они отображали одно и то же число.
Пример 2.9
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Создаем элементы интерфейса
QWidget window;
QVBoxLayout* mainLayout = new QVBoxLayout(&window);
QLabel* label = new QLabel("0");
QSpinBox* spinBox = new QSpinBox;
QSlider* slider = new QSlider(Qt::Horizontal);
// Выполняем компоновку mainLayout->addWidget(label);
mainLayout->addWidget(spinBox);
mainLayout->addWidget(slider);
// Выполняем связывание сигналов и слотов
Семейство библиотек Qt
23
QObject::connect(spinBox, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)));
QObject::connect(spinBox, SIGNAL(valueChanged(int)),
slider, SLOT(setValue(int)));
QObject::connect(slider, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)));
QObject::connect(slider, SIGNAL(valueChanged(int)),
spinBox, SLOT(setValue(int)));
window.show();
return a.exec();
}
Сигналы valueChanged() отправляются виджетами ввода целых чисел при изменении состояния (хранимого значения), слот setValue()
позволяет установить значение виджета ввода целых чисел вручную,
слот setNum() позволяет заменить текст надписи на заданное число- вое значение.
2.2.3
Прочие возможности
Контейнеры данных — классы, обеспечивающие возможности кон- тейнеров C++ STL (абстракции структур данных), но обладающих большей функциональностью.
В качестве примера можно привести QString — контейнер для хранения строк и манипуляций над ними. В отличие от своего STL- аналога string, QString хранит и обрабатывает строки в Unicode, при- меняет механизм неявного совместного использования и т.д.
Другие примеры контейнеров Qt — QVector, QList, QLinkedList,
QStringList, QMap, QHash, QStack, QQueue и другие.
Различные операционные системы могут иметь различные интер- фейсы доступа к носителям данных. Например, в ОС Windows ком- поненты пути к файлу разделяются символом обратной косой черты,
а в ОС Linux — обычной косой черты; в ОС Windows приложения обычно размещаются в каталоге Program Files, а в ОС Linux могут храниться, например, в /usr/bin.
24
Семейство библиотек Qt
Кроссплатформенная система программирования, такая как Qt,
должна предоставлять программисту некий унифицированный интер- фейс доступа к файлам и каталогам, не зависящий от деталей кон- кретной целевой системы.
Qt имеет набор классов для представления объектов на накопите- лях (QDir, QFile), потокового ввода-вывода (QTextStream, QDataStream),
а также богатый набор функций, значительно упрощающих задачу программиста.
Следует также отметить, что возможности Qt гораздо шире биб- лиотеки функций для создания графического интерфейса пользова- теля. Qt включает несколько модулей, реализующих классы для ор- ганизации обмена данными по сети, обработки структурированных
XML-данных, доступа к базам данных и даже работы с подсистемами мобильных устройств, такими как GPS-приемник или акселерометр.
Средства визуального проектирования
25
§3.
Средства визуального проектирования
Qt предоставляет разработчику не только набор библиотек, но и комплекс утилит, позволяющих, в частности, значительно сократить объем написанного вручную кода за счет использования визуальных средств.
В настоящее время программисту доступны для загрузки как биб- лиотеки и утилиты в отдельности, так и комплект разработчика — Qt
SDK, включающий и то, и другое. Комплект разработчика поставля- ется с интегрированной средой Qt Creator, объединяющей возможно- сти практически всех утилит, входящих в комплект, а также предо- ставляющий редактор кода, средства интеграции с отладчиком и си- стемами управления версиями и т.п. К числу основных утилит Qt сле- дует отнести Qt Designer — визуальный редактор форм, Qt Assistant
— средство организации справочной информации, Qt Linguist — ин- струментарий создания многоязычного интерфейса приложения.
Одним из основных принципов Qt выражен слоганом «пиши мень- ше — создавай больше» (code less — create more). Для наглядности поясним этот принцип на очень показательном примере создания про- стого приложения, заимствованном из [5], попутно рассмотрев основ- ные возможности дизайнера форм.
Разрабатываемое приложение представляет собой простейшую те- лефонную книгу.
Главное окно приложения отображает перечень записей (пар имя–
номер), а также предоставляет интерфейс для управления записями:
кнопку «Add» для добавления новой записи, кнопку «Edit» для ре- дактирования записи, кнопку «Del» для удаления выделенной записи и кнопку «Clear» для удаления всех записей.
По нажатию на кнопку «Add» или «Edit» открывается вспомога- тельное диалоговое окно, позволяющее ввести или отредактировать имя и номер. Диалоговое окно имеет две кнопки — «Ok» для приня- тия изменений и «Cancel» для отмены.
Схематично интерфейс приложения изображен на рис. 2.
Откроем Qt Creator. Внешний вид стартовой страницы показан на рис 3. Помимо доступа к среде разработки, начальная страница открывает программисту богатую коллекцию примеров и докумен- тацию. Для начала работы выберем вариант «Создать проект...». С
помощью открывшегося мастера проекта последовательно устанавли-
26
Средства визуального проектирования
Рис. 2: Набросок интерфейса пользователя ваем следующем параметры:
1. в окне «Новый проект» выбираем вариант «Проект Qt Widget
/ GUI приложение Qt»;
2. в окне «Введение и размещение проекта» задаем имя («PhoneBook»)
и каталог проекта;
3. в окне «Настройка цели» выбираем вариант «Desktop» (прило- жение для настолького компьютера);
4. в окне «Информация о классе» установим QWidget в качестве базового класса, убедимся, что отмечен флажок «Создать фор- му»;
5. в окне «Управление проектом» откажемся от использования си- стемы контроля версий (вариант по умолчанию).
В результате работы мастера будет сгенерирован шаблон прило- жения, содержащий файлы исходного кода main.cpp (главная про- грамма), widget.h (интерфейс класса Widget, представляющего форму
Средства визуального проектирования
27
Рис. 3: Стартовое окно Qt Creator приложения) и widget.cpp (реализация класса Widget), а также файл проекта PhoneBook.pro и форму widget.ui. При этом Qt Creator пе- реключится в режим редактора. Дважды щелкнув по файлу формы,
перейдем в режим дизайнера. Вид окна Qt Creator в режиме дизай- нера показан на рис. 4.
Рассмотрим инструменты, доступные разработчику при работе с дизайнером форм.
Собственно графическая форма, представляющая проектируемый интерфейс находится в верхней части центральной области окна. На рис. 4 форма пуста.
Слева размещается палитра компонентов, представляющая биб- лиотеку виджетов Qt. Для размещения виджета на форме достаточно перетащить его мышью.
Каждый виджет имеет набор свойств, как собственных, так и уна- следованных от базовых классов (вплоть до QWidget и QObject). Про- граммист имеет возможность модифицировать значения доступных для редактирования свойств с помощью редактора свойств. На рис. 4
28
Средства визуального проектирования
Рис. 4: Вид окна Qt Creator в режиме дизайнера редактор свойств находится в правом нижнем углу.
Над редактором свойств на рис. 4 размещается инспектор объек- тов, изображающий спроектированный интерфейс в виде иерархиче- ской структуры (в соответствии с вложенностью виджетов в контей- неры и компоновщики).
В нижней части центральной области на рис. 4 находятся сгруппи- рованные вместе редактор действий (привязка горячих клавиш, под- сказок и т. п.) и редактор сигналов и слотов.
Приступим к созданию интерфейса основного окна нашего прило- жения. Для этого перетащим на форму четыре кнопки (Push Button)
и вертикальный разделитель (Vertical Spacer). Выравнивание элемен- тов не имеет значения (см. рис. 5 слева). Выделим все элементы и применим вертикальный компоновщик (рис. 5 справа).
Для управления компоновкой можно воспользоваться панелью ком- поновки в верхней части окна дизайнера (рис. 6) или выбрать соот- ветствующий пункт контекстного меню, вызванного на выделенных объектах.
Средства визуального проектирования
29
Рис. 5: Добавляем и компонуем набор кнопок
Рис. 6: Панель компоновки
Добавим виджет ListWidget и применим к форме табличный ком- поновщик (рис. 7).
Рис. 7: Завершаем компоновку интерфейса
Используя редактор свойств, установим значения свойств objectName
30
Средства визуального проектирования и text кнопок, задав соответственно «addButton» / «Add», «editButton»
/ «Edit», «deleteButton» / «Delete» и «clearButton» / «Clear All». Ана- логично установим значение свойства objectName для виджета списка в «list».
Все предыдущие операции выполнялись в режиме изменения ви- джетов. Дизайнер форм поддерживает четыре режима:
• режим изменения виджетов, предназначенный для визуального проектирования интерфейса;
• режим редактирования сигналов и слотов;
• режим изменения партнёров, предназначенный для привязки надписей к полям ввода;
• режим изменения порядка обхода, предназначенный для опре- деления порядка передачи фокуса клавиатуры по нажатию кла- виши табуляции.
Для переключения режимов в верхней части дизайнера имеется спе- циальная панель (см. рис. 8).
Рис. 8: Панель режимов
Переключимся в режим редактирования сигналов и слотов. Захва- тив мышью кнопку «Clear All», переведем курсор на виджет списка.
В открывшемся диалоговом окне, для отправителя (кнопки) выбе- рем сигнал clicked(), а для виджета списка слот clear(). Теперь при нажатии на кнопку «Clear All» все данные виджета списка будут уда- ляться.
Переключимся в режим изменения порядком обхода и установим нужный (см. рис. 12).
Добавим в проект еще один файл формы (editdialog.ui) и анало- гично описанному выше спроектируем второе окно. Полям ввода при- своим имена nameEdit и numberEdit.
Средства визуального проектирования
31
Рис. 9: Выполняем связывание сигналов и слотов
Рис. 10: Устанавливаем порядок обхода виджетов
Рис. 11: Диалог редактирования
32
Средства визуального проектирования
Для обеспечения возможности быстрого доступа к полям ввода с клавиатуры, настроим горячие клавиши. Для этого в тексты надпи- сей зададим как «&Name» и «&Phone» и, переключившись в режим редактирования партнеров, свяжем надписи с полями ввода. Теперь полям ввода можно быстро передавать фокус клавиатуры, используя сочетания Alt+N и Alt+P соответственно, а в тексте надписей эти символы будут изображаться с подчеркиванием, подсказывая пользо- вателю комбинации горячих клавиш.
На этом этапе подготовка форм завершена. Заметим, что в про- екте пока еще нет ни одной написанной вручную строки кода!
Сохраненная форма представляет собой XML-файл (*.ui) с описа- нием интерфейса. Файлы описаний интерфейса обрабатываются спе- циальной программой uic (user interface compiler). На выходе uic ге- нерирует файлы исходного кода, которые включаются в проект.
Создадим обработчик нажатия кнопки «Add...». Для этого, нахо- дясь в режиме изменения виджетов, в контекстном меню на кнопке
«Add...» выберем пункт «Перейти к слоту...» и в открывшамся окне выберем сигнал clicked(). Qt Creator переключится в режим редакти- рования файла исходного кода, соответствующего форме (см. пример
??
).
Пример 3.1
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::Widget()
{
delete ui;
}
Средства визуального проектирования
33
void Widget::on_addButton_clicked()
{
}
Первые два метода построены из шаблона проекта, использующе- го форму. Все элементы спроектированной визуально формы доступ- ны через объект ui, например ui->editButton. Третий метод — сге- нерированный слот для обработки нажатия кнопки. Автоматически создаваемые слоты получают имена по схеме on_objectName_signal.
Добавим в тело обработчика код, создающий диалоговое окно для ввода имени и номера.
Пример 3.2
EditDialog dlg( this);
if( dlg.exec() == Qt::Accepted )
ui->list->addItem( dlg.name() + " -- " + dlg.number());
Аналогично построим обработчик кнопки удаления.
Пример 3.3
void Widget::on_deleteButton_clicked()
{
foreach (QListWidgetItem *item, ui->list->selectedItems())
delete item;
}
Заметим, что кнопка «Delete» всегда активна, что не корректно —
кнопка должна быть доступна только, если есть выделенные позиции в списке. Добавим слот updateDeleteEnabled():
Пример 3.4
void Widget::updateDeleteEnabled()
{
34
Средства визуального проектирования ui->deleteButton->setEnabled(
ui->list->selectedItems().count() != 0);
}
Дополнительно необходимо переключиться к заголовочному фай- лу и объявить этот слот в секции private slots.
Соединим созданный слот с сигналом об изменении выделения:
Пример 3.5
connect(ui->list->selectionModel(),
SIGNAL(selectionChanged(QItemSelection,
QItemSelection)),
this,
SLOT(updateDeleteEnabled()));
Аналогично обработчику кнопки «Add» реализуется обработчик кнопки «Edit». Также дополнительно потребуется реализовать мето- ды класса EditDialog, представляющего диалоговое окно редактиро- вания записи телефонной книги. Предоставим читателю завершить работу над этим приложением.
Компоновка и размещение виджетов
35
§4.
Компоновка и размещение виджетов
Типичное окно современного приложения с графическим интер- фейсом может содержать десятки виджетов, некоторым образом раз- мещенных на холсте окна.
Размещение виджетов может быть выполнено вручную. QWidget и производные классы включают метод setGeometry(), обеспечиваю- щий возможность задать фиксированные размеры и позицию виджета
(см. пример 4.1).
Пример 4.1
Window::Window(QWidget *parent) : QWidget(parent)
{
setFixedSize(640, 480);
QTextEdit *txt = new QTextEdit(this);
txt->setGeometry(20, 20, 600, 400);
QPushButton *btn = new QPushButton(tr("&Close"), this);
btn->setGeometry(520, 440, 100, 20);
}
Однако расстановка виджетов вручную приводит к ряду сложно- стей. Например, при необходимости добавить новый виджет, требу- ется модифицировать местоположение остальных. Также возникают проблемы с изменением размеров окна пользователем — при фикси- рованном размещении часть элементов управления может оказаться недоступной, вне видимой области окна.
4.1
Использование компоновщиков
Qt предоставляет простой механизм автоматического размещения виджетов внутри других виджетов посредством компоновщиков. В
частности, копмоновка позволяет разумно использовать пространство окна и реализовать так называемый «эластичный интерфейс», когда виджеты получают возможность адаптировать свои размеры и раз- мещение в зависимости от содержимого и пользовательских настро- ек, например, автоматически растягиваться, чтобы вместить текст с увеличенным шрифтом.
36
Компоновка и размещение виджетов
Qt предоставляет три вида компоновщиков: вертикальный (класс
QVBoxLayout), горизонтальный (класс QHBoxLayout), табличный (класс
QGridLayout) и компоновщик форм (QFormLayout), предназначенный для формирования форм ввода, состоящих из колонки пар надпись —
поле ввода. Непосредственные размеры вложенных виджетов опреде- ляются в ходе выполнения специального процесса, называемого «пе- реговорами».
Рассмотрим пример типичного диалогового окна:
Рис. 12: Пример использования компоновщиков
Надпись «Printer» и выпадающий список в верхней части диало- гового окна объединены с помощью горизонтального компоновщика.
Второй горизонтальный компоновщик объединяет две группы кнопок.
Третий формирует строку кнопок в нижней части окна. Для выравни- вания элементов диалогового окна применены разделители (QSpacer)
— невидимые виджеты-распорки. Все три горизонтальных компонов- щика и один из разделителей выровнены с помощью вертикального компоновщика, примененного ко всему окну.
Следующий пример демонстрирует общую схему построения ин- терфейса, представленного на рис. 12 с использованием вертикальных и горизонтальных компоновщиков.
Пример 4.2
// Создаем внешний вертикальный компоновщик
QVBoxLayout *outerLayout = new QVBoxLayout(this);
Компоновка и размещение виджетов
37
// Создаем верхний горизонтальный компоновщик
QHBoxLayout *topLayout = new QHBoxLayout();
// Добавляем виджеты topLayout->addWidget(new QLabel("Printer:"));
topLayout->addWidget(c=new QComboBox());
// Добавляем в внешний компоновщик outerLayout->addLayout(topLayout);
// Создаем средний горизонтальный компоновщик
QHBoxLayout *groupLayout = new QHBoxLayout();
outerLayout->addLayout(groupLayout);
// Добавляем разделитель outerLayout->addSpacerItem(new QSpacerItem(...));
// Создаем нижний горизонтальный компоновщик
QHBoxLayout *buttonLayout = new QHBoxLayout();
buttonLayout->addSpacerItem(new QSpacerItem(...));
buttonLayout->addWidget(new QPushButton("Print"));
buttonLayout->addWidget(new QPushButton("Cancel"));
outerLayout->addLayout(buttonLayout);
// Применяем внешний компоновщик к окну setLayout(outerLayout);
Каждый виджет наследует от класса QWidget свойства sizeHint,
minimumSizeHint и sizePolicy. Свойство sizeHint определяет, какое про- странство виджет «хотел бы» занимать в нормальных условиях. Свой- ство minimumSizeHint задает минимальный размер, меньше которого виджет не может занимать ни при каких обстоятельствах. Значением свойства sizePolicy является политика, определяющая отношение ви- джета к изменениям его размера и принимающая одно из следующих значений:
• QSizePolicy::Fixed — виджет не может иметь размер, отличный от sizeHint;
38
Компоновка и размещение виджетов
• QSizePolicy::Minimum — sizeHint представляет минимально до- пустимый размер виджета, но он может быть неограниченно растянут;
• QSizePolicy::Maximum — sizeHint представляет максимально до- пустимый размер виджета, но он может быть неограниченно ужат;
• QSizePolicy::Preferred — sizeHint представляет оптимальный раз- мер виджета, но изменения допустимы в обе стороны;
• QSizePolicy::Expanding — аналогично Preferred, но виджет тре- бует предоставить ему любое доступное пространство компонов- щика;
• QSizePolicy::MinimumExpanding — аналогично Minimum, но ви- джет требует предоставить ему любое доступное пространство компоновщика;
• QSizePolicy::Ignored — sizeHint игнорируется, виджету выделя- ется столько пространства, сколько возможно в пределах ком- поновщика.
Помимо варьирования свойств sizeHint, minimumSizeHint и sizePolicy вложенных в компоновщик виджетов, управление размещением мо- жет быть выполнено и средствами самого компоновщика. Метод addWidget()
содержит необязательный второй аргумент (stretch factor), равный по умолчанию нулю. Ненулевые значения коэффициента растяжения за- дают относительный размер пространства, выделенного компоновщи- ком под размещение виджета.
При использовании табличного компоновщика программист зада- ет, по каким границам сетки выравнивается каждый вложенный ви- джет. Такой подход позволяет занимать под виджет любую прямо- угольную подобласть таблицы, а также создавать перекрытия. Если добавляемый виджет занимает ровно одну ячейку таблицы, достаточ- но определить левую и верхнюю границы, в противном случае — все четыре (см. пример 4.3).
Пример 4.3
Компоновка и размещение виджетов
39
QPushButton *button1 = new QPushButton("One");
QPushButton *button2 = new QPushButton("Two");
QPushButton *button3 = new QPushButton("Three");
QPushButton *button4 = new QPushButton("Four");
QPushButton *button5 = new QPushButton("Five");
QGridLayout *layout = new QGridLayout;
layout->addWidget(button1, 0, 0);
layout->addWidget(button2, 0, 1);
layout->addWidget(button3, 1, 0, 1, 2);
layout->addWidget(button4, 2, 0);
layout->addWidget(button5, 2, 1);
Предложенный в примере код создает интерфейс, представленный на рис. 13.
Рис. 13: Пример использования табличного компоновщика
4.2
Контейнерные виджеты
Помимо компоновщиков иерархическая структура графического интерфейса может быть построена с помощью виджетов-контейнеров
— виджетов, которые могут содержать другие виджеты.
Контейнерные виджеты могут применяться как для организации высокоуровневых элементов, например, окон — QWidget, QDialog, а также QMainWindow, так и группировки виджетов внутри окон —
QGroupBox, QTabWidget и т.п.
40
Компоновка и размещение виджетов
Пример 4.4 иллюстрирует применение QMainWindow для органи- зации окна приложения и QTabWidget для создания набора вкладок.
Следует отметить, что в отличие от «пустых» контейнеров QWidget и
QDialog, QMainWindow включает основные элементы типового окна приложения — меню, строку состояния и контейнер для наполнения центральной области (centralWidget).
Пример 4.4
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QMainWindow *window = new QMainWindow();
window->setWindowTitle("MainWindow");
window->resize(250, 250);
QWidget *centralWidget = new QWidget(window);
QTabWidget *tabs = new QTabWidget(centralWidget);
tabs->setFixedSize(245, 245);
tabs->addTab(new QWidget(),"Tab 1");
tabs->addTab(new QWidget(),"Tab 2");
window->setCentralWidget(centralWidget);
window->show();
return app.exec();
}
Обзор библиотеки виджетов Qt
41
§5.
Обзор библиотеки виджетов Qt
В предыдущих разделах уже были приведены примеры виджетов
— основным строительных элементов графического интерфейса поль- зователя.
Далее рассмотрим несколько основных виджетов Qt. В целях эко- номии все примеры построены по общей схеме: имеется приложение,
состоящее из трех следующих файлов исходного кода. В главном фай- ле main.cpp создается экземпляр приложения и собственного виджета
Widget.
Пример 5.1
#include
#include "widget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
Файл widget.h содержит описание класса Widget, при этом в при- мерах будут варьироваться файлы заголовков и приватные компонен- ты.
#ifndef WIDGET_H
#define WIDGET_H
#include
// Необходимые файлы заголовков class Widget : public QWidget
{
42
Обзор библиотеки виджетов Qt
Q_OBJECT
public:
Widget(QWidget *parent = 0);
Widget();
private:
// Необходимые приватные компоненты
};
#endif // WIDGET_H
Файл widget.cpp содержит реализацию методов класса Widget,
при этом пользовательский интерфейс формируется в конструкторе класса. Именно код конструктора будет приведен во всех последую- щих примерах этого раздела.
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
// Конструируем интерфейс пользователя
}
Widget::Widget()
{
}
5.1
Кнопки
Три класса QPushButton, QCheckBox, QRadioButton реализуют соответственно простую кнопку, флажок и радиокнопку. Базовым клас- сом всех кнопок является QAbstractButton.
Основные сигналы для кнопок:
Обзор библиотеки виджетов Qt
43
• clicked() отправляется в момент, когда пользователь отпускает кнопку мыши;
• toggled(bool) отправляется в момент изменения состояния кноп- ки.
Отметим также наиболее полезные свойства:
• checkable — истина, если кнопка может работать как флажок;
• checked — истина, если флажок имеет отметку;
• text — текст кнопки;
• icon — иконка кнопки, которая может отображаться вместе с текстом.
Следующий пример иллюстрирует использование QCheckBox. В
конструкторе создадим флажок, установив отметку как начальное со- стояние.
Пример 5.2
// Создаем флажок checkBox = new QCheckBox("Show Title", this);
checkBox->setCheckState(Qt::Checked);
// Связываем изменение состояния флажка
// с обработчиком connect(checkBox, SIGNAL(stateChanged(int)),
this, SLOT(showTitle(int)));
Код обработчика для изменения состояния флажка приведен ни- же. В данном примере меняем заголовок окна.
void Widget::showTitle(int state)
{
if (state == Qt::Checked) {
setWindowTitle("QCheckBox");
} else {
setWindowTitle("");
}
}
44
Обзор библиотеки виджетов Qt
5.2
Надписи
QLabel отображает текстовый блок или картинку, которые зада- ются свойствами text и pixmap соответственно. Следующий пример демонстрирует использование QLabel для изображения простой тек- стовой надписи.
Пример 5.3
// Создаем компоновщик layout = new QHBoxLayout();
// Создаем надпись label = new QLabel("Hello");
// Добавляем надпись в компоновщик,
центрируя разделителями layout->addWidget(new QSplitter());
layout->addWidget(label);
layout->addWidget(new QSplitter());
// Применяем компоновщик к виджету setLayout(layout);
QLabel распознает некоторое подмножество тэгов HTML-разметки.
Пример 5.4
// Создаем полужирную надпись красного цвета label = new QLabel(
"
1 2 3
Министерство образования и науки Российской Федерации
Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования
ПЕТРОЗАВОДСКИЙ ГОСУДАРСТВЕННЫЙ
УНИВЕРСИТЕТ
А. В. Бородин, А. В. Бородина
СРЕДСТВА РАЗРАБОТКИ
ГРАФИЧЕСКИХ ИНТЕРФЕЙСОВ
ПОЛЬЗОВАТЕЛЯ
Учебное пособие
Петрозаводск
Издательство ПетрГУ
2012
ББК 32.973
УДК 004
Б833
Печатается по решению редакционно-издательского совета
Петрозаводского государственного университета
Издается в рамках реализации комплекса мероприятий
Программы стратегического развития ПетрГУ на 2012–2016 г.г.
Р е ц е н з е н т ы:
канд. техн. наук Р. В. Сошкин;
канд. физ.-мат. наук, доцент К. А. Кулаков
Бородин А. В.
Б833
Средства разработки графических интерфейсов пользовате- ля : учебное пособие / А. В. Бородин, А. В. Бородина. — Пет- розаводск : Изд-во ПетрГУ, 2012. — 77 c.
ISBN 978-5-8021-1537-4
Учебное пособие предназначено для студентов математическо- го факультета первого курса направлений подготовки «Прикладная математика и информатика», «Информационные системы и техно- логии»
ББК 32.973
УДК 004
ISBN 978-5-8021-1537-4
© Петрозаводский государственный университет, 2012
© Бородин А. В., Бородина А. В., 2012
3
Содержание
Введение
4
§1.Графические интерфейсы пользователя
5
§2.Семейство библиотек Qt
14 2.1 Первая Qt-программа . . . . . . . . . . . . . . . . . . . .
14 2.2 Обзор средств Qt . . . . . . . . . . . . . . . . . . . . . .
16 2.2.1 Виджеты и конструирование интерфейсов . . .
16 2.2.2 Класс QObject и метаобъектный компилятор . .
18 2.2.3 Прочие возможности . . . . . . . . . . . . . . . .
23
§3.Средства визуального проектирования
25
§4.Компоновка и размещение виджетов
35 4.1 Использование компоновщиков . . . . . . . . . . . . . .
35 4.2 Контейнерные виджеты . . . . . . . . . . . . . . . . . .
39
§5.Обзор библиотеки виджетов Qt
41 5.1 Кнопки . . . . . . . . . . . . . . . . . . . . . . . . . . . .
42 5.2 Надписи . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44 5.3 Инструменты ввода текста . . . . . . . . . . . . . . . . .
45 5.4 Инструменты ввода чисел . . . . . . . . . . . . . . . . .
47
§6.Создание собственных виджетов
48 6.1 Создание виджета композицией имеющихся . . . . . . .
48 6.2 Отрисовка на поверхности виджета . . . . . . . . . . . .
50 6.3 Пример создания виджета с нуля . . . . . . . . . . . . .
54
§7.Работа с 2D-графикой
59 7.1 Graphics View Framework . . . . . . . . . . . . . . . . . .
59 7.2 Управление элементами сцены . . . . . . . . . . . . . .
62 7.3 Создание собственных элементов сцены . . . . . . . . .
67
§8.Интернационализация приложений
73
4
Введение
Введение
Развитие информационных технологий и рост числа пользовате- лей компьютеров предъявляют новые требования к пользовательским интерфейсам. Перед разработчиками программного обеспечения сто- ит непростая задача обеспечить низкий порог вхождения для широко- го круга не обладающих достаточной квалификацией и не прошедших специальную подготовку в использовании ЭВМ лиц за счет упроще- ния средств диалога с машиной, использования интуитивно понятных графических метафор, унификации интерфейсов.
Таким образом, овладение средствами разработки современных графических интерфейсов пользователя является необходимым эле- ментом подготовки разработчика прикладного программного обеспе- чения.
Пособие представляет собой обзор основных концепций, лежащих в основе графических интерфейсов, и краткое введение в программи- рование с использованием фреймворка Qt, предоставляющего набор библиотек и утилит для создания прикладных программ, в том числе,
приложений с графическим интерфейсом.
Выбор Qt среди широкого круга альтернатив обусловлен двумя основными причинами. Во-первых, Qt является продуктом с откры- тым кодом, таким образом его использование не может быть ограниче- но изменением политики компании-разработчика. Во-вторых, Qt из- начально разрабатывался как кроссплатформенный инструмент: на- писанные с использованием этого фреймворка приложения переноси- мы на уровне исходного кода между всеми популярными настольны- ми операционными системами, более того, Qt можно использовать и для разработки для мобильных устройств, таких как смартфоны и планшетные компьютеры. Немаловажным является факт наличия в свободном доступе весьма подробной документации разработчика (см.
[6]).
Материалы пособия используются в Петрозаводском государствен- ном университете на математическом факультете в модуле «Средства разработки графических интерфейсов» дисциплины «Информатика»,
читаемой студентам I курса направлений подготовки «Прикладная математика и информатика» и «Информационные системы и техно- логии».
Графические интерфейсы пользователя
5
§1.
Графические интерфейсы пользовате- ля
Графическими интерфейсами пользователя (англ. Graphical User
Interface, GUI) считают такие, в которых элементы интерфейса пред- ставлены в виде визуальных объектов, т. е. графических изображений.
Элементы интерфейса, используя метафоры, несут информацию об их назначении и способе использования, обеспечивая обучение поль- зователя в процессе работы. Интерфейсы, обладающие этим свой- ством, принято называть интуитивно понятными.
Ко всем видимым элементам интерфейса обеспечивается произ- вольный достум посредством широкого спектра устройств ввода —
клавиатуры, указателя «мышь», сенсорного экрана и др.
Первые разработки в области графических интерфейсов относят- ся к семидесятым годам XX века и, как правило, связываются с ком- пьютером Alto, разработанным в исследовательских лабораториях ком- пании XEROX в Пало Альто, Калифорния. Тогда же впервые появ- ляется аббревиатура WIMP (Windows, Icons, Menus, Pointing Device),
концептуально характеризующая графический интерфейс пользова- теля.
Коммерческий успех к компьютерам, операционные системы ко- торых предоставляли графический интерфейс, пришел в восьмидеся- тые годы XX века и связан с появлением таких разработок, как Star компании Xerox и Lisa компании Apple.
Тогда же были предпринты первые попытки упорядочить разра- ботку графических интерфейсов пользователя, в частности, следует отметить руководство IBM Common User Access Specification.
Одной из первых оконных систем, обеспечивающей стандартные инструменты и протоколы для построения графических интерфейсов,
стала X Window System, разрабатываемая с 1984 года и используемая по настоящее время в UNIX-подобных ОС. X Window System обес- печивает базовые функции графической среды: отрисовку и переме- щение окон на экране, взаимодействие с устройствами ввода, такими как, например, мышь и клавиатура.
X Window System не определяет деталей интерфейса пользовате- ля — этим занимаются менеджеры окон. С другой стороны, в этой системе предусмотрена сетевая прозрачность: графические приложе-
6
Графические интерфейсы пользователя ния могут выполняться на другой машине в сети, а их интерфейс при этом будет передаваться по сети и отображаться на локальной ма- шине пользователя. В контексте X Window System термины «клиент»
и «сервер» имеют непривычное для многих пользователей значение:
«сервер» означает локальный дисплей пользователя (дисплейный сер- вер), а «клиент» — программу, которая этот дисплей использует (она может выполняться на удалённом компьютере).
Архитектура X Window System проиллюстрирована на рис. 1.
Рис. 1: Архитектура X Window System
X Window System предоставляет программный интерфейс для со- здания приложений с графическим интерфейсом посредством исполь- зования библиотеки XLib. Однако, программирование на уровне XLib слишком многословно и перегружает программиста необходимостью реализовывать вручную даже примитивные детали интерфейса.
Рассмотрим достаточно простую XLib-программу, изображающую на экране простое окно с надписью.
Пример 1.1
Графические интерфейсы пользователя
7
#include
#include
#include
#include
#include
#include
#define X 0
#define Y 0
#define WIDTH 400
#define HEIGHT 300
#define WIDTH_MIN 50
#define HEIGHT_MIN 50
#define BORDER_WIDTH 1
#define TITLE "Example"
#define ICON_TITLE "Example"
#define PRG_CLASS "Example"
/* Сообщает оконному менеджеру параметры окна */
static void SetWindowManagerHints(Display * display,
char *PClass, char *argv[], int argc,
Window window, int x, int y,
int win_wdt, int win_hgt,
int win_wdt_min, int win_hgt_min,
char *ptrTtl, char *ptrITtl, Pixmap pixmap)
{
XSizeHints size_hints;
XWMHints wm_hints;
XClassHint class_hint;
XTextProperty winnm, icnm;
if (!XStringListToTextProperty (&ptrTtl, 1, &winnm)
|| !XStringListToTextProperty (&ptrITtl, 1, &icnm)) {
puts ("No memory!\n");
exit (1);
}
8
Графические интерфейсы пользователя size_hints.flags = PPosition | PSize | PMinSize;
size_hints.min_width = win_wdt_min;
size_hints.min_height = win_hgt_min;
wm_hints.flags = StateHint | IconPixmapHint | InputHint;
wm_hints.initial_state = NormalState;
wm_hints.input = True;
wm_hints.icon_pixmap = pixmap;
class_hint.res_name = argv[0];
class_hint.res_class = PClass;
XSetWMProperties (display, window, &winnm,
&icnm, argv, argc, &size_hints,
&wm_hints, &class_hint);
}
/* Обработчик ошибки */
int MyXlibIOErrorHandler(Display *d)
{
XCloseDisplay(d);
exit(0);
}
/* Основная программа */
int main(int argc, char *argv[])
{
Display *display;
int ScreenNumber;
GC gc;
XEvent report;
Window window;
if ((display = XOpenDisplay(NULL)) == NULL) {
puts ("Can not connect to the X server!\n");
exit (1);
}
ScreenNumber = DefaultScreen(display);
Графические интерфейсы пользователя
9
window = XCreateSimpleWindow(display,
RootWindow(display, ScreenNumber),
X, Y, WIDTH, HEIGHT, BORDER_WIDTH,
BlackPixel(display, ScreenNumber),
WhitePixel(display, ScreenNumber));
SetWindowManagerHints (display, PRG_CLASS, argv, argc,
window, X, Y, WIDTH, HEIGHT, WIDTH_MIN,
HEIGHT_MIN, TITLE, ICON_TITLE, 0);
XSelectInput (display, window, ExposureMask
| StructureNotifyMask);
XMapWindow (display, window);
XSetIOErrorHandler (&MyXlibIOErrorHandler);
while (1) {
XNextEvent (display, &report);
switch (report.type) {
case Expose:
if (report.xexpose.count != 0) break;
gc = XCreateGC(display, window, 0, NULL);
XSetForeground(display, gc,
BlackPixel(display, 0));
XDrawString (display, window, gc, 20, 50,
"Hello, world!", strlen ("Hello, world!"));
XFreeGC (display, gc);
XFlush (display);
break;
default:
fprintf (stderr, "%d\n", report.type);
}
}
return 0;
}
10
Графические интерфейсы пользователя
Используя XLib, программист полностью контролирует свойства оконного приложения, именно этим объясняется большое количество параметров в вызовах функций. Также программист самостоятельно выполняет проверку наступления тех или иных событий, организуя бесконечный цикл опроса обработки. На полотне окна программист также самостоятельно вынужден отрисовывать необходимые элемен- ты.
Из приведенного примера можно извлечь следующие выводы.
• Поскольку любая программа с графическим интерфейсом явля- ется событийно-ориентированной, то есть большую часть време- ни проводит в состоянии «сна», выполняя те или иные функции лишь в ответ на активность пользователя, то она, так или иначе,
будет включать цикл обработки событий, подобный приведен- ному в примере. Следовательно, необходимости в включении этого цикла вручную в каждой программе нет — его можно реализовать в виде отдельной функции библиотеки, предостав- ляющей программные средства создания графического интер- фейса. При этом код обработки событий оформляется в виде от- дельных функций, регистрируемых до входа в цикл обработки событий. Такие функции называют функциями обратного вы- зова (callback), так как в программе отсутствуют явные вызовы этих функций, они вызываются из функции, реализующей аб- стракцию основного цикла при наступлении соответствующего события.
• Большая часть параметров оконного приложения либо контро- лируется оконным менеджером, либо имеет стандартные зна- чения (например, цветовая гамма может определяться темой оформления). Поэтому программный интерфейс можно суще- ственно упростить.
Исходя из представленных соображений, над XLib был разработан набор высокоуровневых библиотек Xt (так называемый «тулкит»),
скрывающий сложность XLib за оберточными вызовами. Рассмотрим пример Xt-программы, предоставляющий ту же функциональность,
что и пример 1.1.
Пример 1.2
Графические интерфейсы пользователя
11
#include
#include
#include
#include
void DrawHelloString (Widget prWidget, XtPointer pData,
XEvent *prEvent, Boolean *pbContinue)
{
Display *prDisplay = XtDisplay (prWidget);
Window nWindow = XtWindow (prWidget);
GC prGC;
if (prEvent->type == Expose) {
prGC = XCreateGC (prDisplay, nWindow, 0, NULL);
XDrawString (prDisplay, nWindow, prGC, 10, 50,
"Hello, world!", strlen ("Hello, world!"));
XFreeGC (prDisplay, prGC);
}
}
void main (int argc, char **argv)
{
Arg args[2];
Widget toplevel, prCoreWidget;
toplevel = XtInitialize(argv[0], "GUI Example",
NULL, 0, &argc, argv);
prCoreWidget = XtCreateWidget ("Core", widgetClass,
toplevel, NULL, 0);
XtSetArg(args[0], XtNwidth, 400);
XtSetArg(args[1], XtNheight, 300);
XtSetValues(prCoreWidget, args, 2);
XtManageChild (prCoreWidget);
XtAddEventHandler(prCoreWidget, ExposureMask,
False, DrawHelloString, NULL);
12
Графические интерфейсы пользователя
XtRealizeWidget(toplevel);
XtMainLoop();
}
Типовой цикл обработки событий заменен единственным вызовом
XtMainLoop(). При этом обработчики событий, реализованные в виде функций обратного вызова, связываются с сигналами событий (реги- стрируются) заранее посредством вызова XtAddEventHandler().
Также в Xt введено понятие виджета (widget = visual gadget) — ти- пового элемента графического интерфейса. Однако, отрисовка стро- ки по-прежнему осуществляется с помощью низкоуровневого XLib- вызова XDrawString().
Современные инструменты разработки графических интерфейсов также реализуют абстракцию основного цикла обработки событий и предоставляют разработчику богатую библиотеку виджетов. Следую- щий пример демонстрирует использование средств библиотеки GTK+.
Пример 1.3
#include
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *label;
/* Инициализируем подсистему GTK+ */
gtk_init(&argc, &argv);
/* Создаем окно */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* Привязываем обработчик нажатия кнопки закрытия окна */
g_signal_connect(G_OBJECT (window), "destroy",
G_CALLBACK (gtk_main_quit), NULL);
/* Создаем кнопку с надписью */
label = gtk_label_new("Hello World");
Графические интерфейсы пользователя
13
/* Помещаем кнопку в окно (контейнер) */
gtk_container_add(GTK_CONTAINER (window), label);
/* Отрисовываем окно и все принадлежащие ему объекты */
gtk_widget_show_all(window);
/* Основной цикл обработки событий */
gtk_main();
return 0;
}
14
Семейство библиотек Qt
§2.
Семейство библиотек Qt
2.1
Первая Qt-программа
По сложившейся традиции руководств по программированию, об- зор возможностей семейства библиотек Qt начнем с программы, вы- водящей на экран приветствие «Hello, world!».
Пример 2.1
#include
#include
int main(int argc, char *argv[])
{
// Создаем экземпляр приложения
QApplication a(argc, argv);
// Создаем надпись
QLabel label("Hello World");
// Отображаем надпись на экране label.show();
// Основной цикл обработки событий return a.exec();
}
Текст программы содержит включения файлов заголовков с опре- делениями необходимых классов (QApplication и QLabel).В Qt на каж- дый класс приходится в точности один заголовочный файл.
Выполнение программы начинается с создания объекта класса
QApplication, представляющего абстракцию графического приложе- ния. Ни одна графическая программа не обходится без QApplication,
так как именно в этом классе реализован цикл обработки событий,
определяющий основной алгоритм работы графического приложения.
Мы передаем управление в цикл обработки событий вызовом мето- да exec() класса QApplication. Именно в exec() программа проводит
Семейство библиотек Qt
15
большую часть времени работы, ожидая пользовательской реакции или другого события.
В конструктор QApplication мы передаем аргументы командной строки. Qt-приложение способно распознавать некоторые параметры,
заданные в командной строке и адаптировать поведение в соответ- ствии с ними.
После создания объекта QApplication создается объект QLabel,
представляющий простую текстовую надпись. Текст для отображения передается через конструктор класса.
Метод show() заставляет Qt-приложение отрисовать надпись на экране, при этом, так как надпись не имеет родительского объекта,
то сама формирует окно.
Цикл обработки событий прекращается при выполнении метода quit() класса QApplication, например, если пользователь закрывает окно. В этом случае приложение завершается.
Так как Qt поддерживается на многих различных платформах, от- личающихся организацией процесса сборки, разработчиками Qt пред- ложен специальный проектно-ориентированный инструмент сборки —
qmake.
Программа qmake генерирует файлы сборки Makefile для каж- дой конкретной платформы на основе специального файла проекта
(*.pro), который описывает приложение в независимой от операцион- ной системы и среды программирования форме.
Сохраним нашу первую программу под именем main.cpp в ката- логе myFirstQtProject и сгенерируем в нем шаблон файла проекта с помощью той же программы qmake:
Пример 2.2
user@linux:/myFirstQtProject> qmake -project
В результате будет построен шаблон файла проекта:
Пример 2.3
#myFirstQtProject/myFirstQtProject.pro
16
Семейство библиотек Qt
#####################################
# Automatically generated by qmake
#####################################
TEMPLATE = app
CONFIG -= moc
DEPENDPATH += .
INCLUDEPATH += .
# Input
SOURCES += main.cpp
Файл проекта состоит из директив, в данном примере директива
TEMPLATE указывает, что проект представляет собой приложение
(app), а SOURCES содержит перечень файлов исходного кода проекта
(в нашем примере только main.cpp).
Повторный вызов qmake (теперь без параметров) создаст зависи- мый от платформы файл сборки (Makefile), который можно исполь- зовать для получения исполняемого файла:
Пример 2.4
user@linux:/myFirstQtProject> qmake user@linux:/myFirstQtProject> make user@linux:/myFirstQtProject> ./myFirstQtProject
2.2
Обзор средств Qt
2.2.1
Виджеты и конструирование интерфейсов
Основу библиотеки графического интерфейса составляют видже- ты — классы, представляющие визуальные объекты. Виджеты обра- зуют иерархию классов. Базовым классом для всех виджетов в Qt яв- ляется QWidget. При этом имеется более шестидесяти производных от QWidget классов, реализующих типовые элементы управления. До- полнительно программист имеет возможность создания собственных виджетов, расширяя функциональность элементов стандартного на- бора.
Семейство библиотек Qt
17
Визуальная структура интерфейсов формируется посредством кон- тейнерных классов, позволяющих организовывать виджеты в иерар- хии. Таким образом, виджеты могут содержать в себе другие видже- ты.
Харахтерными особенностями виджетов являются следующие:
• виджет занимает прямоугольную область экрана;
• виджет может получать сообщения о событиях, например, от устройств ввода;
• виджет может отправлять сигналы об изменении состояния.
Все виджеты имеют набор общих свойств, унаследованных от ба- зового класса QWidget:
• enabled — возможна ли реакция на ввод пользователя;
• visible — видим ли виджет (или скрыт).
Действие этих свойств распространяется на вложенные виджеты.
В примере 2.1 интерфейс представляен единственным виджетом
QLabel, поэтому проблем с его расположением нет. Однако интер- фейс приложения может быть сконструирован из десятков виджетов,
и проблема их взаимного расположения окажется весьма серьезной.
Qt предоставляет так называемые средства компоновки, облегча- ющие процесс взаимной расстановки виджетов в пространстве окна.
Следующий пример организовывает две надписи вертикально:
Пример 2.5
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Создаем окно
QWidget window;
18
Семейство библиотек Qt
// Создаем основной компоновщик окна -
// вертикальный компоновщик
QVBoxLayout* mainLayout = new QVBoxLayout(&window);
// Создаем две надписи
QLabel* label1 = new QLabel("One");
QLabel* label2 = new QLabel("Two");
// Организуем надписи с помощью компоновщика mainLayout->addWidget(label1);
mainLayout->addWidget(label2);
// Отображаем окно window.show();
return a.exec();
}
Подробнее вопросы компоновки и виды компоновщиков рассмот- рены в четвертом параграфе. К рассмотренному примеру следует за- метить, что только для окна был вызван метод show(), надписи, вло- женные в окно, автоматически становятся для него «дочерними» объ- ектами и визуализируются автоматически при отображении родителя.
2.2.2
Класс QObject и метаобъектный компилятор
Базовым классом большинства классов семейства библиотек Qt,
в частности, всех виджетов, является QObject. Исключениями явля- ются только классы, для которых важна легковесность (например,
графические примитивы и контейнеры данных). Именно QObject реа- лизует большую часть возможностей, характерных для Qt: обработку событий, сигналы и слоты, свойства объектов, управление памятью.
Каждый унаследованный от QObject имеет так называемый ме- таобъект, содержащий информацию об имени класса, связях и т. д.
Таким образом Qt вводит механизмы интроспекции в язык C++.
Метаданные собираются на этапе трансляции специальной про- граммой — метаобъектным компилятором moc. Программа moc явля-
Семейство библиотек Qt
19
ется препроцессором, обрабатывающим файлы заголовков проекта и конструирующим вспомогательные файлы исходного кода (moc_*.cpp),
также включаемые в проект. При использовании системы qmake ге- нерация этих файлов осуществляется прозрачно для программиста.
Программа moc извлекает из файлов заголовков специальные мак- росы (например, в следующем примере, Q_OBJECT, Q_CLASSINFO),
а также ключевые слова Qt, не являющиеся элементами языка C++:
signals, slots, emit и др. Обработанные файлы содержат только кон- струкции C++ и могут быть скомпилированы обычным компилято- ром C++.
Макрос Q_OBJECT должен присутствовать в любом классе, на- следуемом от QObject.
Пример 2.6
class MyClass : public QObject
{
Q_OBJECT
Q_CLASSINFO("author", "John Doe")
public:
MyClass(const Foo &foo, QObject *parent=0);
Foo foo() const;
public slots:
void setFoo( const Foo &foo );
signals:
void fooChanged( Foo );
private:
Foo m_foo;
};
Класс QObject позволяет создавать свойства с методами чтения и записи. Свойство не просто представляет некоторое поле класса,
предназначенное для хранения значения. В результате установки зна- чения свойства визуальный объект, возможно должен быть модифи- цирован (например, при изменении цвета). Поэтому свойству соответ- ствует пара методов чтения и установки значения, выполняющие и соответствующий дополнительный код — так называемые «геттер» и
«сеттер». В соответствии с соглашениями имя геттер-метода должно
20
Семейство библиотек Qt совпадать с именем свойства. Имя сеттер-метода начинается со слова set, за которым следует имя свойства с заглавной буквы.
Пример 2.7
class QLabel : public QFrame
{
Q_OBJECT
// Создаем свойство text с геттером text
// и сеттером setText
Q_PROPERTY(QString text READ text WRITE setText)
public:
QString text() const;
public slots:
void setText(const QString &);
};
В примере 2.5 объект класса QWidget, представляющий окно, со- здан на стеке, а под компоновщик и надписи память выделена динами- чески. Это не случайно. Вообще говоря, C++ не предусматривает ав- томатического управления памятью, однако в Qt некоторые подобные возможности введены. Объекты, наследующие от класса QObject (в частности, виджеты) могут организовываться в древовидные иерар- хии, имея родителя (parent object) и дочерних объектов (child objects).
Если такой объект уничтожается (вызывается деструктор), то Qt ав- томатически уничтожает все дочерние объекты. Те, в свою очередь,
уничтожают свои дочерние объекты и так далее. Это освобождает программиста от необходимости отслеживать и освобождать занятую дочерними объектами память вручную.
Однако для того, чтобы автоматическое управление памятью мог- ло быть реализовано, память под дочерние объекты должна быть вы- делена на куче динамической памяти.
Любое нетривиальное графическое приложение должно иметь воз- можность отреагировать на пользовательские действия, а значит долж- на существовать система коммуникации между объектами. В отличие от многих инструментов создания графических интерфейсов, в Qt не используются функции обратного вызова для обеспечения реакции
Семейство библиотек Qt
21
на события, а применяется более совершенная технология сигналов и слотов.
И сигнал, и слот являются методами класса, за тем исключением,
что тело сигнала программист не реализует, его создает метаобъект- ный компилятор. Тип возвращаемого значения сигнала — void. Слот может иметь отличный от void тип возвращаемого значения, но ис- пользовать возвращаемое значение можно только, если слот вызыва- ется как обычная функция, а не по сигналу. Сигналы в определении класса объявляются в секции signals, слоты — в секции slots. Отпра- вить сигнал из кода можно с помощью ключевого слова emit.
Сигнатуры связываемых сигнала и слота должны либо совпадать,
либо в сигнале может присутствовать часть дополнительных парамет- ров, которые будут проигнорировать в слоте. В общем случае верно правило: «Qt может игнорировать часть данных, но не может полу- чить данные из ничего».
В следующем примере сигнал, отправляемый кнопкой в момент клика, связывается с стандартным слотом quit() объекта QApplication.
Пример 2.8
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Создаем и отрисовываем кнопку с надписью "Quit"
QPushButton button("Quit");
button.show();
// Связываем сигнал clicked() и слот quit()
QObject::connect(&button, SIGNAL(clicked()),
&a, SLOT(quit()));
return a.exec();
}
22
Семейство библиотек Qt
Статическая функция connect() класса QObject выполняет связы- вание сигнала и слота. Сигнал — функция, уведомляющая о наступле- нии события, слот — функция, получающая этот сигнал и корректно обрабатывающая его. Функция connect() получает четыре аргумента:
объект-отправитель, сигнал, объект-получатель, слот. При связыва- нии необходимо использовать макросы SIGNAL() и SLOT().
В примере 2.8 никакой дополнительной информации из сигнала в слот не передается. Рассмотрим более реальный пример. Пусть ин- терфейс содержит три виджета: QLabel, QSpinBox и QSlider. Послед- ние два обеспечивают просто различные способы ввода целых чисел.
Свяжем эти виджеты друг с другом таким образом, чтобы все они отображали одно и то же число.
Пример 2.9
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Создаем элементы интерфейса
QWidget window;
QVBoxLayout* mainLayout = new QVBoxLayout(&window);
QLabel* label = new QLabel("0");
QSpinBox* spinBox = new QSpinBox;
QSlider* slider = new QSlider(Qt::Horizontal);
// Выполняем компоновку mainLayout->addWidget(label);
mainLayout->addWidget(spinBox);
mainLayout->addWidget(slider);
// Выполняем связывание сигналов и слотов
Семейство библиотек Qt
23
QObject::connect(spinBox, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)));
QObject::connect(spinBox, SIGNAL(valueChanged(int)),
slider, SLOT(setValue(int)));
QObject::connect(slider, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)));
QObject::connect(slider, SIGNAL(valueChanged(int)),
spinBox, SLOT(setValue(int)));
window.show();
return a.exec();
}
Сигналы valueChanged() отправляются виджетами ввода целых чисел при изменении состояния (хранимого значения), слот setValue()
позволяет установить значение виджета ввода целых чисел вручную,
слот setNum() позволяет заменить текст надписи на заданное число- вое значение.
2.2.3
Прочие возможности
Контейнеры данных — классы, обеспечивающие возможности кон- тейнеров C++ STL (абстракции структур данных), но обладающих большей функциональностью.
В качестве примера можно привести QString — контейнер для хранения строк и манипуляций над ними. В отличие от своего STL- аналога string, QString хранит и обрабатывает строки в Unicode, при- меняет механизм неявного совместного использования и т.д.
Другие примеры контейнеров Qt — QVector, QList, QLinkedList,
QStringList, QMap, QHash, QStack, QQueue и другие.
Различные операционные системы могут иметь различные интер- фейсы доступа к носителям данных. Например, в ОС Windows ком- поненты пути к файлу разделяются символом обратной косой черты,
а в ОС Linux — обычной косой черты; в ОС Windows приложения обычно размещаются в каталоге Program Files, а в ОС Linux могут храниться, например, в /usr/bin.
24
Семейство библиотек Qt
Кроссплатформенная система программирования, такая как Qt,
должна предоставлять программисту некий унифицированный интер- фейс доступа к файлам и каталогам, не зависящий от деталей кон- кретной целевой системы.
Qt имеет набор классов для представления объектов на накопите- лях (QDir, QFile), потокового ввода-вывода (QTextStream, QDataStream),
а также богатый набор функций, значительно упрощающих задачу программиста.
Следует также отметить, что возможности Qt гораздо шире биб- лиотеки функций для создания графического интерфейса пользова- теля. Qt включает несколько модулей, реализующих классы для ор- ганизации обмена данными по сети, обработки структурированных
XML-данных, доступа к базам данных и даже работы с подсистемами мобильных устройств, такими как GPS-приемник или акселерометр.
Средства визуального проектирования
25
§3.
Средства визуального проектирования
Qt предоставляет разработчику не только набор библиотек, но и комплекс утилит, позволяющих, в частности, значительно сократить объем написанного вручную кода за счет использования визуальных средств.
В настоящее время программисту доступны для загрузки как биб- лиотеки и утилиты в отдельности, так и комплект разработчика — Qt
SDK, включающий и то, и другое. Комплект разработчика поставля- ется с интегрированной средой Qt Creator, объединяющей возможно- сти практически всех утилит, входящих в комплект, а также предо- ставляющий редактор кода, средства интеграции с отладчиком и си- стемами управления версиями и т.п. К числу основных утилит Qt сле- дует отнести Qt Designer — визуальный редактор форм, Qt Assistant
— средство организации справочной информации, Qt Linguist — ин- струментарий создания многоязычного интерфейса приложения.
Одним из основных принципов Qt выражен слоганом «пиши мень- ше — создавай больше» (code less — create more). Для наглядности поясним этот принцип на очень показательном примере создания про- стого приложения, заимствованном из [5], попутно рассмотрев основ- ные возможности дизайнера форм.
Разрабатываемое приложение представляет собой простейшую те- лефонную книгу.
Главное окно приложения отображает перечень записей (пар имя–
номер), а также предоставляет интерфейс для управления записями:
кнопку «Add» для добавления новой записи, кнопку «Edit» для ре- дактирования записи, кнопку «Del» для удаления выделенной записи и кнопку «Clear» для удаления всех записей.
По нажатию на кнопку «Add» или «Edit» открывается вспомога- тельное диалоговое окно, позволяющее ввести или отредактировать имя и номер. Диалоговое окно имеет две кнопки — «Ok» для приня- тия изменений и «Cancel» для отмены.
Схематично интерфейс приложения изображен на рис. 2.
Откроем Qt Creator. Внешний вид стартовой страницы показан на рис 3. Помимо доступа к среде разработки, начальная страница открывает программисту богатую коллекцию примеров и докумен- тацию. Для начала работы выберем вариант «Создать проект...». С
помощью открывшегося мастера проекта последовательно устанавли-
26
Средства визуального проектирования
Рис. 2: Набросок интерфейса пользователя ваем следующем параметры:
1. в окне «Новый проект» выбираем вариант «Проект Qt Widget
/ GUI приложение Qt»;
2. в окне «Введение и размещение проекта» задаем имя («PhoneBook»)
и каталог проекта;
3. в окне «Настройка цели» выбираем вариант «Desktop» (прило- жение для настолького компьютера);
4. в окне «Информация о классе» установим QWidget в качестве базового класса, убедимся, что отмечен флажок «Создать фор- му»;
5. в окне «Управление проектом» откажемся от использования си- стемы контроля версий (вариант по умолчанию).
В результате работы мастера будет сгенерирован шаблон прило- жения, содержащий файлы исходного кода main.cpp (главная про- грамма), widget.h (интерфейс класса Widget, представляющего форму
Средства визуального проектирования
27
Рис. 3: Стартовое окно Qt Creator приложения) и widget.cpp (реализация класса Widget), а также файл проекта PhoneBook.pro и форму widget.ui. При этом Qt Creator пе- реключится в режим редактора. Дважды щелкнув по файлу формы,
перейдем в режим дизайнера. Вид окна Qt Creator в режиме дизай- нера показан на рис. 4.
Рассмотрим инструменты, доступные разработчику при работе с дизайнером форм.
Собственно графическая форма, представляющая проектируемый интерфейс находится в верхней части центральной области окна. На рис. 4 форма пуста.
Слева размещается палитра компонентов, представляющая биб- лиотеку виджетов Qt. Для размещения виджета на форме достаточно перетащить его мышью.
Каждый виджет имеет набор свойств, как собственных, так и уна- следованных от базовых классов (вплоть до QWidget и QObject). Про- граммист имеет возможность модифицировать значения доступных для редактирования свойств с помощью редактора свойств. На рис. 4
28
Средства визуального проектирования
Рис. 4: Вид окна Qt Creator в режиме дизайнера редактор свойств находится в правом нижнем углу.
Над редактором свойств на рис. 4 размещается инспектор объек- тов, изображающий спроектированный интерфейс в виде иерархиче- ской структуры (в соответствии с вложенностью виджетов в контей- неры и компоновщики).
В нижней части центральной области на рис. 4 находятся сгруппи- рованные вместе редактор действий (привязка горячих клавиш, под- сказок и т. п.) и редактор сигналов и слотов.
Приступим к созданию интерфейса основного окна нашего прило- жения. Для этого перетащим на форму четыре кнопки (Push Button)
и вертикальный разделитель (Vertical Spacer). Выравнивание элемен- тов не имеет значения (см. рис. 5 слева). Выделим все элементы и применим вертикальный компоновщик (рис. 5 справа).
Для управления компоновкой можно воспользоваться панелью ком- поновки в верхней части окна дизайнера (рис. 6) или выбрать соот- ветствующий пункт контекстного меню, вызванного на выделенных объектах.
Средства визуального проектирования
29
Рис. 5: Добавляем и компонуем набор кнопок
Рис. 6: Панель компоновки
Добавим виджет ListWidget и применим к форме табличный ком- поновщик (рис. 7).
Рис. 7: Завершаем компоновку интерфейса
Используя редактор свойств, установим значения свойств objectName
30
Средства визуального проектирования и text кнопок, задав соответственно «addButton» / «Add», «editButton»
/ «Edit», «deleteButton» / «Delete» и «clearButton» / «Clear All». Ана- логично установим значение свойства objectName для виджета списка в «list».
Все предыдущие операции выполнялись в режиме изменения ви- джетов. Дизайнер форм поддерживает четыре режима:
• режим изменения виджетов, предназначенный для визуального проектирования интерфейса;
• режим редактирования сигналов и слотов;
• режим изменения партнёров, предназначенный для привязки надписей к полям ввода;
• режим изменения порядка обхода, предназначенный для опре- деления порядка передачи фокуса клавиатуры по нажатию кла- виши табуляции.
Для переключения режимов в верхней части дизайнера имеется спе- циальная панель (см. рис. 8).
Рис. 8: Панель режимов
Переключимся в режим редактирования сигналов и слотов. Захва- тив мышью кнопку «Clear All», переведем курсор на виджет списка.
В открывшемся диалоговом окне, для отправителя (кнопки) выбе- рем сигнал clicked(), а для виджета списка слот clear(). Теперь при нажатии на кнопку «Clear All» все данные виджета списка будут уда- ляться.
Переключимся в режим изменения порядком обхода и установим нужный (см. рис. 12).
Добавим в проект еще один файл формы (editdialog.ui) и анало- гично описанному выше спроектируем второе окно. Полям ввода при- своим имена nameEdit и numberEdit.
Средства визуального проектирования
31
Рис. 9: Выполняем связывание сигналов и слотов
Рис. 10: Устанавливаем порядок обхода виджетов
Рис. 11: Диалог редактирования
32
Средства визуального проектирования
Для обеспечения возможности быстрого доступа к полям ввода с клавиатуры, настроим горячие клавиши. Для этого в тексты надпи- сей зададим как «&Name» и «&Phone» и, переключившись в режим редактирования партнеров, свяжем надписи с полями ввода. Теперь полям ввода можно быстро передавать фокус клавиатуры, используя сочетания Alt+N и Alt+P соответственно, а в тексте надписей эти символы будут изображаться с подчеркиванием, подсказывая пользо- вателю комбинации горячих клавиш.
На этом этапе подготовка форм завершена. Заметим, что в про- екте пока еще нет ни одной написанной вручную строки кода!
Сохраненная форма представляет собой XML-файл (*.ui) с описа- нием интерфейса. Файлы описаний интерфейса обрабатываются спе- циальной программой uic (user interface compiler). На выходе uic ге- нерирует файлы исходного кода, которые включаются в проект.
Создадим обработчик нажатия кнопки «Add...». Для этого, нахо- дясь в режиме изменения виджетов, в контекстном меню на кнопке
«Add...» выберем пункт «Перейти к слоту...» и в открывшамся окне выберем сигнал clicked(). Qt Creator переключится в режим редакти- рования файла исходного кода, соответствующего форме (см. пример
??
).
Пример 3.1
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::Widget()
{
delete ui;
}
Средства визуального проектирования
33
void Widget::on_addButton_clicked()
{
}
Первые два метода построены из шаблона проекта, использующе- го форму. Все элементы спроектированной визуально формы доступ- ны через объект ui, например ui->editButton. Третий метод — сге- нерированный слот для обработки нажатия кнопки. Автоматически создаваемые слоты получают имена по схеме on_objectName_signal.
Добавим в тело обработчика код, создающий диалоговое окно для ввода имени и номера.
Пример 3.2
EditDialog dlg( this);
if( dlg.exec() == Qt::Accepted )
ui->list->addItem( dlg.name() + " -- " + dlg.number());
Аналогично построим обработчик кнопки удаления.
Пример 3.3
void Widget::on_deleteButton_clicked()
{
foreach (QListWidgetItem *item, ui->list->selectedItems())
delete item;
}
Заметим, что кнопка «Delete» всегда активна, что не корректно —
кнопка должна быть доступна только, если есть выделенные позиции в списке. Добавим слот updateDeleteEnabled():
Пример 3.4
void Widget::updateDeleteEnabled()
{
34
Средства визуального проектирования ui->deleteButton->setEnabled(
ui->list->selectedItems().count() != 0);
}
Дополнительно необходимо переключиться к заголовочному фай- лу и объявить этот слот в секции private slots.
Соединим созданный слот с сигналом об изменении выделения:
Пример 3.5
connect(ui->list->selectionModel(),
SIGNAL(selectionChanged(QItemSelection,
QItemSelection)),
this,
SLOT(updateDeleteEnabled()));
Аналогично обработчику кнопки «Add» реализуется обработчик кнопки «Edit». Также дополнительно потребуется реализовать мето- ды класса EditDialog, представляющего диалоговое окно редактиро- вания записи телефонной книги. Предоставим читателю завершить работу над этим приложением.
Компоновка и размещение виджетов
35
§4.
Компоновка и размещение виджетов
Типичное окно современного приложения с графическим интер- фейсом может содержать десятки виджетов, некоторым образом раз- мещенных на холсте окна.
Размещение виджетов может быть выполнено вручную. QWidget и производные классы включают метод setGeometry(), обеспечиваю- щий возможность задать фиксированные размеры и позицию виджета
(см. пример 4.1).
Пример 4.1
Window::Window(QWidget *parent) : QWidget(parent)
{
setFixedSize(640, 480);
QTextEdit *txt = new QTextEdit(this);
txt->setGeometry(20, 20, 600, 400);
QPushButton *btn = new QPushButton(tr("&Close"), this);
btn->setGeometry(520, 440, 100, 20);
}
Однако расстановка виджетов вручную приводит к ряду сложно- стей. Например, при необходимости добавить новый виджет, требу- ется модифицировать местоположение остальных. Также возникают проблемы с изменением размеров окна пользователем — при фикси- рованном размещении часть элементов управления может оказаться недоступной, вне видимой области окна.
4.1
Использование компоновщиков
Qt предоставляет простой механизм автоматического размещения виджетов внутри других виджетов посредством компоновщиков. В
частности, копмоновка позволяет разумно использовать пространство окна и реализовать так называемый «эластичный интерфейс», когда виджеты получают возможность адаптировать свои размеры и раз- мещение в зависимости от содержимого и пользовательских настро- ек, например, автоматически растягиваться, чтобы вместить текст с увеличенным шрифтом.
36
Компоновка и размещение виджетов
Qt предоставляет три вида компоновщиков: вертикальный (класс
QVBoxLayout), горизонтальный (класс QHBoxLayout), табличный (класс
QGridLayout) и компоновщик форм (QFormLayout), предназначенный для формирования форм ввода, состоящих из колонки пар надпись —
поле ввода. Непосредственные размеры вложенных виджетов опреде- ляются в ходе выполнения специального процесса, называемого «пе- реговорами».
Рассмотрим пример типичного диалогового окна:
Рис. 12: Пример использования компоновщиков
Надпись «Printer» и выпадающий список в верхней части диало- гового окна объединены с помощью горизонтального компоновщика.
Второй горизонтальный компоновщик объединяет две группы кнопок.
Третий формирует строку кнопок в нижней части окна. Для выравни- вания элементов диалогового окна применены разделители (QSpacer)
— невидимые виджеты-распорки. Все три горизонтальных компонов- щика и один из разделителей выровнены с помощью вертикального компоновщика, примененного ко всему окну.
Следующий пример демонстрирует общую схему построения ин- терфейса, представленного на рис. 12 с использованием вертикальных и горизонтальных компоновщиков.
Пример 4.2
// Создаем внешний вертикальный компоновщик
QVBoxLayout *outerLayout = new QVBoxLayout(this);
Компоновка и размещение виджетов
37
// Создаем верхний горизонтальный компоновщик
QHBoxLayout *topLayout = new QHBoxLayout();
// Добавляем виджеты topLayout->addWidget(new QLabel("Printer:"));
topLayout->addWidget(c=new QComboBox());
// Добавляем в внешний компоновщик outerLayout->addLayout(topLayout);
// Создаем средний горизонтальный компоновщик
QHBoxLayout *groupLayout = new QHBoxLayout();
outerLayout->addLayout(groupLayout);
// Добавляем разделитель outerLayout->addSpacerItem(new QSpacerItem(...));
// Создаем нижний горизонтальный компоновщик
QHBoxLayout *buttonLayout = new QHBoxLayout();
buttonLayout->addSpacerItem(new QSpacerItem(...));
buttonLayout->addWidget(new QPushButton("Print"));
buttonLayout->addWidget(new QPushButton("Cancel"));
outerLayout->addLayout(buttonLayout);
// Применяем внешний компоновщик к окну setLayout(outerLayout);
Каждый виджет наследует от класса QWidget свойства sizeHint,
minimumSizeHint и sizePolicy. Свойство sizeHint определяет, какое про- странство виджет «хотел бы» занимать в нормальных условиях. Свой- ство minimumSizeHint задает минимальный размер, меньше которого виджет не может занимать ни при каких обстоятельствах. Значением свойства sizePolicy является политика, определяющая отношение ви- джета к изменениям его размера и принимающая одно из следующих значений:
• QSizePolicy::Fixed — виджет не может иметь размер, отличный от sizeHint;
38
Компоновка и размещение виджетов
• QSizePolicy::Minimum — sizeHint представляет минимально до- пустимый размер виджета, но он может быть неограниченно растянут;
• QSizePolicy::Maximum — sizeHint представляет максимально до- пустимый размер виджета, но он может быть неограниченно ужат;
• QSizePolicy::Preferred — sizeHint представляет оптимальный раз- мер виджета, но изменения допустимы в обе стороны;
• QSizePolicy::Expanding — аналогично Preferred, но виджет тре- бует предоставить ему любое доступное пространство компонов- щика;
• QSizePolicy::MinimumExpanding — аналогично Minimum, но ви- джет требует предоставить ему любое доступное пространство компоновщика;
• QSizePolicy::Ignored — sizeHint игнорируется, виджету выделя- ется столько пространства, сколько возможно в пределах ком- поновщика.
Помимо варьирования свойств sizeHint, minimumSizeHint и sizePolicy вложенных в компоновщик виджетов, управление размещением мо- жет быть выполнено и средствами самого компоновщика. Метод addWidget()
содержит необязательный второй аргумент (stretch factor), равный по умолчанию нулю. Ненулевые значения коэффициента растяжения за- дают относительный размер пространства, выделенного компоновщи- ком под размещение виджета.
При использовании табличного компоновщика программист зада- ет, по каким границам сетки выравнивается каждый вложенный ви- джет. Такой подход позволяет занимать под виджет любую прямо- угольную подобласть таблицы, а также создавать перекрытия. Если добавляемый виджет занимает ровно одну ячейку таблицы, достаточ- но определить левую и верхнюю границы, в противном случае — все четыре (см. пример 4.3).
Пример 4.3
Компоновка и размещение виджетов
39
QPushButton *button1 = new QPushButton("One");
QPushButton *button2 = new QPushButton("Two");
QPushButton *button3 = new QPushButton("Three");
QPushButton *button4 = new QPushButton("Four");
QPushButton *button5 = new QPushButton("Five");
QGridLayout *layout = new QGridLayout;
layout->addWidget(button1, 0, 0);
layout->addWidget(button2, 0, 1);
layout->addWidget(button3, 1, 0, 1, 2);
layout->addWidget(button4, 2, 0);
layout->addWidget(button5, 2, 1);
Предложенный в примере код создает интерфейс, представленный на рис. 13.
Рис. 13: Пример использования табличного компоновщика
4.2
Контейнерные виджеты
Помимо компоновщиков иерархическая структура графического интерфейса может быть построена с помощью виджетов-контейнеров
— виджетов, которые могут содержать другие виджеты.
Контейнерные виджеты могут применяться как для организации высокоуровневых элементов, например, окон — QWidget, QDialog, а также QMainWindow, так и группировки виджетов внутри окон —
QGroupBox, QTabWidget и т.п.
40
Компоновка и размещение виджетов
Пример 4.4 иллюстрирует применение QMainWindow для органи- зации окна приложения и QTabWidget для создания набора вкладок.
Следует отметить, что в отличие от «пустых» контейнеров QWidget и
QDialog, QMainWindow включает основные элементы типового окна приложения — меню, строку состояния и контейнер для наполнения центральной области (centralWidget).
Пример 4.4
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QMainWindow *window = new QMainWindow();
window->setWindowTitle("MainWindow");
window->resize(250, 250);
QWidget *centralWidget = new QWidget(window);
QTabWidget *tabs = new QTabWidget(centralWidget);
tabs->setFixedSize(245, 245);
tabs->addTab(new QWidget(),"Tab 1");
tabs->addTab(new QWidget(),"Tab 2");
window->setCentralWidget(centralWidget);
window->show();
return app.exec();
}
Обзор библиотеки виджетов Qt
41
§5.
Обзор библиотеки виджетов Qt
В предыдущих разделах уже были приведены примеры виджетов
— основным строительных элементов графического интерфейса поль- зователя.
Далее рассмотрим несколько основных виджетов Qt. В целях эко- номии все примеры построены по общей схеме: имеется приложение,
состоящее из трех следующих файлов исходного кода. В главном фай- ле main.cpp создается экземпляр приложения и собственного виджета
Widget.
Пример 5.1
#include
#include "widget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
Файл widget.h содержит описание класса Widget, при этом в при- мерах будут варьироваться файлы заголовков и приватные компонен- ты.
#ifndef WIDGET_H
#define WIDGET_H
#include
// Необходимые файлы заголовков class Widget : public QWidget
{
42
Обзор библиотеки виджетов Qt
Q_OBJECT
public:
Widget(QWidget *parent = 0);
Widget();
private:
// Необходимые приватные компоненты
};
#endif // WIDGET_H
Файл widget.cpp содержит реализацию методов класса Widget,
при этом пользовательский интерфейс формируется в конструкторе класса. Именно код конструктора будет приведен во всех последую- щих примерах этого раздела.
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
// Конструируем интерфейс пользователя
}
Widget::Widget()
{
}
5.1
Кнопки
Три класса QPushButton, QCheckBox, QRadioButton реализуют соответственно простую кнопку, флажок и радиокнопку. Базовым клас- сом всех кнопок является QAbstractButton.
Основные сигналы для кнопок:
Обзор библиотеки виджетов Qt
43
• clicked() отправляется в момент, когда пользователь отпускает кнопку мыши;
• toggled(bool) отправляется в момент изменения состояния кноп- ки.
Отметим также наиболее полезные свойства:
• checkable — истина, если кнопка может работать как флажок;
• checked — истина, если флажок имеет отметку;
• text — текст кнопки;
• icon — иконка кнопки, которая может отображаться вместе с текстом.
Следующий пример иллюстрирует использование QCheckBox. В
конструкторе создадим флажок, установив отметку как начальное со- стояние.
Пример 5.2
// Создаем флажок checkBox = new QCheckBox("Show Title", this);
checkBox->setCheckState(Qt::Checked);
// Связываем изменение состояния флажка
// с обработчиком connect(checkBox, SIGNAL(stateChanged(int)),
this, SLOT(showTitle(int)));
Код обработчика для изменения состояния флажка приведен ни- же. В данном примере меняем заголовок окна.
void Widget::showTitle(int state)
{
if (state == Qt::Checked) {
setWindowTitle("QCheckBox");
} else {
setWindowTitle("");
}
}
44
Обзор библиотеки виджетов Qt
5.2
Надписи
QLabel отображает текстовый блок или картинку, которые зада- ются свойствами text и pixmap соответственно. Следующий пример демонстрирует использование QLabel для изображения простой тек- стовой надписи.
Пример 5.3
// Создаем компоновщик layout = new QHBoxLayout();
// Создаем надпись label = new QLabel("Hello");
// Добавляем надпись в компоновщик,
центрируя разделителями layout->addWidget(new QSplitter());
layout->addWidget(label);
layout->addWidget(new QSplitter());
// Применяем компоновщик к виджету setLayout(layout);
QLabel распознает некоторое подмножество тэгов HTML-разметки.
Пример 5.4
// Создаем полужирную надпись красного цвета label = new QLabel(
"
1 2 3
Министерство образования и науки Российской Федерации
Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования
ПЕТРОЗАВОДСКИЙ ГОСУДАРСТВЕННЫЙ
УНИВЕРСИТЕТ
А. В. Бородин, А. В. Бородина
СРЕДСТВА РАЗРАБОТКИ
ГРАФИЧЕСКИХ ИНТЕРФЕЙСОВ
ПОЛЬЗОВАТЕЛЯ
Учебное пособие
Петрозаводск
Издательство ПетрГУ
2012
ББК 32.973
УДК 004
Б833
Печатается по решению редакционно-издательского совета
Петрозаводского государственного университета
Издается в рамках реализации комплекса мероприятий
Программы стратегического развития ПетрГУ на 2012–2016 г.г.
Р е ц е н з е н т ы:
канд. техн. наук Р. В. Сошкин;
канд. физ.-мат. наук, доцент К. А. Кулаков
Бородин А. В.
Б833
Средства разработки графических интерфейсов пользовате- ля : учебное пособие / А. В. Бородин, А. В. Бородина. — Пет- розаводск : Изд-во ПетрГУ, 2012. — 77 c.
ISBN 978-5-8021-1537-4
Учебное пособие предназначено для студентов математическо- го факультета первого курса направлений подготовки «Прикладная математика и информатика», «Информационные системы и техно- логии»
ББК 32.973
УДК 004
ISBN 978-5-8021-1537-4
© Петрозаводский государственный университет, 2012
© Бородин А. В., Бородина А. В., 2012
3
Содержание
Введение
4
§1.Графические интерфейсы пользователя
5
§2.Семейство библиотек Qt
14 2.1 Первая Qt-программа . . . . . . . . . . . . . . . . . . . .
14 2.2 Обзор средств Qt . . . . . . . . . . . . . . . . . . . . . .
16 2.2.1 Виджеты и конструирование интерфейсов . . .
16 2.2.2 Класс QObject и метаобъектный компилятор . .
18 2.2.3 Прочие возможности . . . . . . . . . . . . . . . .
23
§3.Средства визуального проектирования
25
§4.Компоновка и размещение виджетов
35 4.1 Использование компоновщиков . . . . . . . . . . . . . .
35 4.2 Контейнерные виджеты . . . . . . . . . . . . . . . . . .
39
§5.Обзор библиотеки виджетов Qt
41 5.1 Кнопки . . . . . . . . . . . . . . . . . . . . . . . . . . . .
42 5.2 Надписи . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44 5.3 Инструменты ввода текста . . . . . . . . . . . . . . . . .
45 5.4 Инструменты ввода чисел . . . . . . . . . . . . . . . . .
47
§6.Создание собственных виджетов
48 6.1 Создание виджета композицией имеющихся . . . . . . .
48 6.2 Отрисовка на поверхности виджета . . . . . . . . . . . .
50 6.3 Пример создания виджета с нуля . . . . . . . . . . . . .
54
§7.Работа с 2D-графикой
59 7.1 Graphics View Framework . . . . . . . . . . . . . . . . . .
59 7.2 Управление элементами сцены . . . . . . . . . . . . . .
62 7.3 Создание собственных элементов сцены . . . . . . . . .
67
§8.Интернационализация приложений
73
4
Введение
Введение
Развитие информационных технологий и рост числа пользовате- лей компьютеров предъявляют новые требования к пользовательским интерфейсам. Перед разработчиками программного обеспечения сто- ит непростая задача обеспечить низкий порог вхождения для широко- го круга не обладающих достаточной квалификацией и не прошедших специальную подготовку в использовании ЭВМ лиц за счет упроще- ния средств диалога с машиной, использования интуитивно понятных графических метафор, унификации интерфейсов.
Таким образом, овладение средствами разработки современных графических интерфейсов пользователя является необходимым эле- ментом подготовки разработчика прикладного программного обеспе- чения.
Пособие представляет собой обзор основных концепций, лежащих в основе графических интерфейсов, и краткое введение в программи- рование с использованием фреймворка Qt, предоставляющего набор библиотек и утилит для создания прикладных программ, в том числе,
приложений с графическим интерфейсом.
Выбор Qt среди широкого круга альтернатив обусловлен двумя основными причинами. Во-первых, Qt является продуктом с откры- тым кодом, таким образом его использование не может быть ограниче- но изменением политики компании-разработчика. Во-вторых, Qt из- начально разрабатывался как кроссплатформенный инструмент: на- писанные с использованием этого фреймворка приложения переноси- мы на уровне исходного кода между всеми популярными настольны- ми операционными системами, более того, Qt можно использовать и для разработки для мобильных устройств, таких как смартфоны и планшетные компьютеры. Немаловажным является факт наличия в свободном доступе весьма подробной документации разработчика (см.
[6]).
Материалы пособия используются в Петрозаводском государствен- ном университете на математическом факультете в модуле «Средства разработки графических интерфейсов» дисциплины «Информатика»,
читаемой студентам I курса направлений подготовки «Прикладная математика и информатика» и «Информационные системы и техно- логии».
Графические интерфейсы пользователя
5
§1.
Графические интерфейсы пользовате- ля
Графическими интерфейсами пользователя (англ. Graphical User
Interface, GUI) считают такие, в которых элементы интерфейса пред- ставлены в виде визуальных объектов, т. е. графических изображений.
Элементы интерфейса, используя метафоры, несут информацию об их назначении и способе использования, обеспечивая обучение поль- зователя в процессе работы. Интерфейсы, обладающие этим свой- ством, принято называть интуитивно понятными.
Ко всем видимым элементам интерфейса обеспечивается произ- вольный достум посредством широкого спектра устройств ввода —
клавиатуры, указателя «мышь», сенсорного экрана и др.
Первые разработки в области графических интерфейсов относят- ся к семидесятым годам XX века и, как правило, связываются с ком- пьютером Alto, разработанным в исследовательских лабораториях ком- пании XEROX в Пало Альто, Калифорния. Тогда же впервые появ- ляется аббревиатура WIMP (Windows, Icons, Menus, Pointing Device),
концептуально характеризующая графический интерфейс пользова- теля.
Коммерческий успех к компьютерам, операционные системы ко- торых предоставляли графический интерфейс, пришел в восьмидеся- тые годы XX века и связан с появлением таких разработок, как Star компании Xerox и Lisa компании Apple.
Тогда же были предпринты первые попытки упорядочить разра- ботку графических интерфейсов пользователя, в частности, следует отметить руководство IBM Common User Access Specification.
Одной из первых оконных систем, обеспечивающей стандартные инструменты и протоколы для построения графических интерфейсов,
стала X Window System, разрабатываемая с 1984 года и используемая по настоящее время в UNIX-подобных ОС. X Window System обес- печивает базовые функции графической среды: отрисовку и переме- щение окон на экране, взаимодействие с устройствами ввода, такими как, например, мышь и клавиатура.
X Window System не определяет деталей интерфейса пользовате- ля — этим занимаются менеджеры окон. С другой стороны, в этой системе предусмотрена сетевая прозрачность: графические приложе-
6
Графические интерфейсы пользователя ния могут выполняться на другой машине в сети, а их интерфейс при этом будет передаваться по сети и отображаться на локальной ма- шине пользователя. В контексте X Window System термины «клиент»
и «сервер» имеют непривычное для многих пользователей значение:
«сервер» означает локальный дисплей пользователя (дисплейный сер- вер), а «клиент» — программу, которая этот дисплей использует (она может выполняться на удалённом компьютере).
Архитектура X Window System проиллюстрирована на рис. 1.
Рис. 1: Архитектура X Window System
X Window System предоставляет программный интерфейс для со- здания приложений с графическим интерфейсом посредством исполь- зования библиотеки XLib. Однако, программирование на уровне XLib слишком многословно и перегружает программиста необходимостью реализовывать вручную даже примитивные детали интерфейса.
Рассмотрим достаточно простую XLib-программу, изображающую на экране простое окно с надписью.
Пример 1.1
Графические интерфейсы пользователя
7
#include
#include
#include
#include
#include
#include
#define X 0
#define Y 0
#define WIDTH 400
#define HEIGHT 300
#define WIDTH_MIN 50
#define HEIGHT_MIN 50
#define BORDER_WIDTH 1
#define TITLE "Example"
#define ICON_TITLE "Example"
#define PRG_CLASS "Example"
/* Сообщает оконному менеджеру параметры окна */
static void SetWindowManagerHints(Display * display,
char *PClass, char *argv[], int argc,
Window window, int x, int y,
int win_wdt, int win_hgt,
int win_wdt_min, int win_hgt_min,
char *ptrTtl, char *ptrITtl, Pixmap pixmap)
{
XSizeHints size_hints;
XWMHints wm_hints;
XClassHint class_hint;
XTextProperty winnm, icnm;
if (!XStringListToTextProperty (&ptrTtl, 1, &winnm)
|| !XStringListToTextProperty (&ptrITtl, 1, &icnm)) {
puts ("No memory!\n");
exit (1);
}
8
Графические интерфейсы пользователя size_hints.flags = PPosition | PSize | PMinSize;
size_hints.min_width = win_wdt_min;
size_hints.min_height = win_hgt_min;
wm_hints.flags = StateHint | IconPixmapHint | InputHint;
wm_hints.initial_state = NormalState;
wm_hints.input = True;
wm_hints.icon_pixmap = pixmap;
class_hint.res_name = argv[0];
class_hint.res_class = PClass;
XSetWMProperties (display, window, &winnm,
&icnm, argv, argc, &size_hints,
&wm_hints, &class_hint);
}
/* Обработчик ошибки */
int MyXlibIOErrorHandler(Display *d)
{
XCloseDisplay(d);
exit(0);
}
/* Основная программа */
int main(int argc, char *argv[])
{
Display *display;
int ScreenNumber;
GC gc;
XEvent report;
Window window;
if ((display = XOpenDisplay(NULL)) == NULL) {
puts ("Can not connect to the X server!\n");
exit (1);
}
ScreenNumber = DefaultScreen(display);
Графические интерфейсы пользователя
9
window = XCreateSimpleWindow(display,
RootWindow(display, ScreenNumber),
X, Y, WIDTH, HEIGHT, BORDER_WIDTH,
BlackPixel(display, ScreenNumber),
WhitePixel(display, ScreenNumber));
SetWindowManagerHints (display, PRG_CLASS, argv, argc,
window, X, Y, WIDTH, HEIGHT, WIDTH_MIN,
HEIGHT_MIN, TITLE, ICON_TITLE, 0);
XSelectInput (display, window, ExposureMask
| StructureNotifyMask);
XMapWindow (display, window);
XSetIOErrorHandler (&MyXlibIOErrorHandler);
while (1) {
XNextEvent (display, &report);
switch (report.type) {
case Expose:
if (report.xexpose.count != 0) break;
gc = XCreateGC(display, window, 0, NULL);
XSetForeground(display, gc,
BlackPixel(display, 0));
XDrawString (display, window, gc, 20, 50,
"Hello, world!", strlen ("Hello, world!"));
XFreeGC (display, gc);
XFlush (display);
break;
default:
fprintf (stderr, "%d\n", report.type);
}
}
return 0;
}
10
Графические интерфейсы пользователя
Используя XLib, программист полностью контролирует свойства оконного приложения, именно этим объясняется большое количество параметров в вызовах функций. Также программист самостоятельно выполняет проверку наступления тех или иных событий, организуя бесконечный цикл опроса обработки. На полотне окна программист также самостоятельно вынужден отрисовывать необходимые элемен- ты.
Из приведенного примера можно извлечь следующие выводы.
• Поскольку любая программа с графическим интерфейсом явля- ется событийно-ориентированной, то есть большую часть време- ни проводит в состоянии «сна», выполняя те или иные функции лишь в ответ на активность пользователя, то она, так или иначе,
будет включать цикл обработки событий, подобный приведен- ному в примере. Следовательно, необходимости в включении этого цикла вручную в каждой программе нет — его можно реализовать в виде отдельной функции библиотеки, предостав- ляющей программные средства создания графического интер- фейса. При этом код обработки событий оформляется в виде от- дельных функций, регистрируемых до входа в цикл обработки событий. Такие функции называют функциями обратного вы- зова (callback), так как в программе отсутствуют явные вызовы этих функций, они вызываются из функции, реализующей аб- стракцию основного цикла при наступлении соответствующего события.
• Большая часть параметров оконного приложения либо контро- лируется оконным менеджером, либо имеет стандартные зна- чения (например, цветовая гамма может определяться темой оформления). Поэтому программный интерфейс можно суще- ственно упростить.
Исходя из представленных соображений, над XLib был разработан набор высокоуровневых библиотек Xt (так называемый «тулкит»),
скрывающий сложность XLib за оберточными вызовами. Рассмотрим пример Xt-программы, предоставляющий ту же функциональность,
что и пример 1.1.
Пример 1.2
Графические интерфейсы пользователя
11
#include
#include
#include
#include
void DrawHelloString (Widget prWidget, XtPointer pData,
XEvent *prEvent, Boolean *pbContinue)
{
Display *prDisplay = XtDisplay (prWidget);
Window nWindow = XtWindow (prWidget);
GC prGC;
if (prEvent->type == Expose) {
prGC = XCreateGC (prDisplay, nWindow, 0, NULL);
XDrawString (prDisplay, nWindow, prGC, 10, 50,
"Hello, world!", strlen ("Hello, world!"));
XFreeGC (prDisplay, prGC);
}
}
void main (int argc, char **argv)
{
Arg args[2];
Widget toplevel, prCoreWidget;
toplevel = XtInitialize(argv[0], "GUI Example",
NULL, 0, &argc, argv);
prCoreWidget = XtCreateWidget ("Core", widgetClass,
toplevel, NULL, 0);
XtSetArg(args[0], XtNwidth, 400);
XtSetArg(args[1], XtNheight, 300);
XtSetValues(prCoreWidget, args, 2);
XtManageChild (prCoreWidget);
XtAddEventHandler(prCoreWidget, ExposureMask,
False, DrawHelloString, NULL);
12
Графические интерфейсы пользователя
XtRealizeWidget(toplevel);
XtMainLoop();
}
Типовой цикл обработки событий заменен единственным вызовом
XtMainLoop(). При этом обработчики событий, реализованные в виде функций обратного вызова, связываются с сигналами событий (реги- стрируются) заранее посредством вызова XtAddEventHandler().
Также в Xt введено понятие виджета (widget = visual gadget) — ти- пового элемента графического интерфейса. Однако, отрисовка стро- ки по-прежнему осуществляется с помощью низкоуровневого XLib- вызова XDrawString().
Современные инструменты разработки графических интерфейсов также реализуют абстракцию основного цикла обработки событий и предоставляют разработчику богатую библиотеку виджетов. Следую- щий пример демонстрирует использование средств библиотеки GTK+.
Пример 1.3
#include
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *label;
/* Инициализируем подсистему GTK+ */
gtk_init(&argc, &argv);
/* Создаем окно */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* Привязываем обработчик нажатия кнопки закрытия окна */
g_signal_connect(G_OBJECT (window), "destroy",
G_CALLBACK (gtk_main_quit), NULL);
/* Создаем кнопку с надписью */
label = gtk_label_new("Hello World");
Графические интерфейсы пользователя
13
/* Помещаем кнопку в окно (контейнер) */
gtk_container_add(GTK_CONTAINER (window), label);
/* Отрисовываем окно и все принадлежащие ему объекты */
gtk_widget_show_all(window);
/* Основной цикл обработки событий */
gtk_main();
return 0;
}
14
Семейство библиотек Qt
§2.
Семейство библиотек Qt
2.1
Первая Qt-программа
По сложившейся традиции руководств по программированию, об- зор возможностей семейства библиотек Qt начнем с программы, вы- водящей на экран приветствие «Hello, world!».
Пример 2.1
#include
#include
int main(int argc, char *argv[])
{
// Создаем экземпляр приложения
QApplication a(argc, argv);
// Создаем надпись
QLabel label("Hello World");
// Отображаем надпись на экране label.show();
// Основной цикл обработки событий return a.exec();
}
Текст программы содержит включения файлов заголовков с опре- делениями необходимых классов (QApplication и QLabel).В Qt на каж- дый класс приходится в точности один заголовочный файл.
Выполнение программы начинается с создания объекта класса
QApplication, представляющего абстракцию графического приложе- ния. Ни одна графическая программа не обходится без QApplication,
так как именно в этом классе реализован цикл обработки событий,
определяющий основной алгоритм работы графического приложения.
Мы передаем управление в цикл обработки событий вызовом мето- да exec() класса QApplication. Именно в exec() программа проводит
Семейство библиотек Qt
15
большую часть времени работы, ожидая пользовательской реакции или другого события.
В конструктор QApplication мы передаем аргументы командной строки. Qt-приложение способно распознавать некоторые параметры,
заданные в командной строке и адаптировать поведение в соответ- ствии с ними.
После создания объекта QApplication создается объект QLabel,
представляющий простую текстовую надпись. Текст для отображения передается через конструктор класса.
Метод show() заставляет Qt-приложение отрисовать надпись на экране, при этом, так как надпись не имеет родительского объекта,
то сама формирует окно.
Цикл обработки событий прекращается при выполнении метода quit() класса QApplication, например, если пользователь закрывает окно. В этом случае приложение завершается.
Так как Qt поддерживается на многих различных платформах, от- личающихся организацией процесса сборки, разработчиками Qt пред- ложен специальный проектно-ориентированный инструмент сборки —
qmake.
Программа qmake генерирует файлы сборки Makefile для каж- дой конкретной платформы на основе специального файла проекта
(*.pro), который описывает приложение в независимой от операцион- ной системы и среды программирования форме.
Сохраним нашу первую программу под именем main.cpp в ката- логе myFirstQtProject и сгенерируем в нем шаблон файла проекта с помощью той же программы qmake:
Пример 2.2
user@linux:/myFirstQtProject> qmake -project
В результате будет построен шаблон файла проекта:
Пример 2.3
#myFirstQtProject/myFirstQtProject.pro
16
Семейство библиотек Qt
#####################################
# Automatically generated by qmake
#####################################
TEMPLATE = app
CONFIG -= moc
DEPENDPATH += .
INCLUDEPATH += .
# Input
SOURCES += main.cpp
Файл проекта состоит из директив, в данном примере директива
TEMPLATE указывает, что проект представляет собой приложение
(app), а SOURCES содержит перечень файлов исходного кода проекта
(в нашем примере только main.cpp).
Повторный вызов qmake (теперь без параметров) создаст зависи- мый от платформы файл сборки (Makefile), который можно исполь- зовать для получения исполняемого файла:
Пример 2.4
user@linux:/myFirstQtProject> qmake user@linux:/myFirstQtProject> make user@linux:/myFirstQtProject> ./myFirstQtProject
2.2
Обзор средств Qt
2.2.1
Виджеты и конструирование интерфейсов
Основу библиотеки графического интерфейса составляют видже- ты — классы, представляющие визуальные объекты. Виджеты обра- зуют иерархию классов. Базовым классом для всех виджетов в Qt яв- ляется QWidget. При этом имеется более шестидесяти производных от QWidget классов, реализующих типовые элементы управления. До- полнительно программист имеет возможность создания собственных виджетов, расширяя функциональность элементов стандартного на- бора.
Семейство библиотек Qt
17
Визуальная структура интерфейсов формируется посредством кон- тейнерных классов, позволяющих организовывать виджеты в иерар- хии. Таким образом, виджеты могут содержать в себе другие видже- ты.
Харахтерными особенностями виджетов являются следующие:
• виджет занимает прямоугольную область экрана;
• виджет может получать сообщения о событиях, например, от устройств ввода;
• виджет может отправлять сигналы об изменении состояния.
Все виджеты имеют набор общих свойств, унаследованных от ба- зового класса QWidget:
• enabled — возможна ли реакция на ввод пользователя;
• visible — видим ли виджет (или скрыт).
Действие этих свойств распространяется на вложенные виджеты.
В примере 2.1 интерфейс представляен единственным виджетом
QLabel, поэтому проблем с его расположением нет. Однако интер- фейс приложения может быть сконструирован из десятков виджетов,
и проблема их взаимного расположения окажется весьма серьезной.
Qt предоставляет так называемые средства компоновки, облегча- ющие процесс взаимной расстановки виджетов в пространстве окна.
Следующий пример организовывает две надписи вертикально:
Пример 2.5
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Создаем окно
QWidget window;
18
Семейство библиотек Qt
// Создаем основной компоновщик окна -
// вертикальный компоновщик
QVBoxLayout* mainLayout = new QVBoxLayout(&window);
// Создаем две надписи
QLabel* label1 = new QLabel("One");
QLabel* label2 = new QLabel("Two");
// Организуем надписи с помощью компоновщика mainLayout->addWidget(label1);
mainLayout->addWidget(label2);
// Отображаем окно window.show();
return a.exec();
}
Подробнее вопросы компоновки и виды компоновщиков рассмот- рены в четвертом параграфе. К рассмотренному примеру следует за- метить, что только для окна был вызван метод show(), надписи, вло- женные в окно, автоматически становятся для него «дочерними» объ- ектами и визуализируются автоматически при отображении родителя.
2.2.2
Класс QObject и метаобъектный компилятор
Базовым классом большинства классов семейства библиотек Qt,
в частности, всех виджетов, является QObject. Исключениями явля- ются только классы, для которых важна легковесность (например,
графические примитивы и контейнеры данных). Именно QObject реа- лизует большую часть возможностей, характерных для Qt: обработку событий, сигналы и слоты, свойства объектов, управление памятью.
Каждый унаследованный от QObject имеет так называемый ме- таобъект, содержащий информацию об имени класса, связях и т. д.
Таким образом Qt вводит механизмы интроспекции в язык C++.
Метаданные собираются на этапе трансляции специальной про- граммой — метаобъектным компилятором moc. Программа moc явля-
Семейство библиотек Qt
19
ется препроцессором, обрабатывающим файлы заголовков проекта и конструирующим вспомогательные файлы исходного кода (moc_*.cpp),
также включаемые в проект. При использовании системы qmake ге- нерация этих файлов осуществляется прозрачно для программиста.
Программа moc извлекает из файлов заголовков специальные мак- росы (например, в следующем примере, Q_OBJECT, Q_CLASSINFO),
а также ключевые слова Qt, не являющиеся элементами языка C++:
signals, slots, emit и др. Обработанные файлы содержат только кон- струкции C++ и могут быть скомпилированы обычным компилято- ром C++.
Макрос Q_OBJECT должен присутствовать в любом классе, на- следуемом от QObject.
Пример 2.6
class MyClass : public QObject
{
Q_OBJECT
Q_CLASSINFO("author", "John Doe")
public:
MyClass(const Foo &foo, QObject *parent=0);
Foo foo() const;
public slots:
void setFoo( const Foo &foo );
signals:
void fooChanged( Foo );
private:
Foo m_foo;
};
Класс QObject позволяет создавать свойства с методами чтения и записи. Свойство не просто представляет некоторое поле класса,
предназначенное для хранения значения. В результате установки зна- чения свойства визуальный объект, возможно должен быть модифи- цирован (например, при изменении цвета). Поэтому свойству соответ- ствует пара методов чтения и установки значения, выполняющие и соответствующий дополнительный код — так называемые «геттер» и
«сеттер». В соответствии с соглашениями имя геттер-метода должно
20
Семейство библиотек Qt совпадать с именем свойства. Имя сеттер-метода начинается со слова set, за которым следует имя свойства с заглавной буквы.
Пример 2.7
class QLabel : public QFrame
{
Q_OBJECT
// Создаем свойство text с геттером text
// и сеттером setText
Q_PROPERTY(QString text READ text WRITE setText)
public:
QString text() const;
public slots:
void setText(const QString &);
};
В примере 2.5 объект класса QWidget, представляющий окно, со- здан на стеке, а под компоновщик и надписи память выделена динами- чески. Это не случайно. Вообще говоря, C++ не предусматривает ав- томатического управления памятью, однако в Qt некоторые подобные возможности введены. Объекты, наследующие от класса QObject (в частности, виджеты) могут организовываться в древовидные иерар- хии, имея родителя (parent object) и дочерних объектов (child objects).
Если такой объект уничтожается (вызывается деструктор), то Qt ав- томатически уничтожает все дочерние объекты. Те, в свою очередь,
уничтожают свои дочерние объекты и так далее. Это освобождает программиста от необходимости отслеживать и освобождать занятую дочерними объектами память вручную.
Однако для того, чтобы автоматическое управление памятью мог- ло быть реализовано, память под дочерние объекты должна быть вы- делена на куче динамической памяти.
Любое нетривиальное графическое приложение должно иметь воз- можность отреагировать на пользовательские действия, а значит долж- на существовать система коммуникации между объектами. В отличие от многих инструментов создания графических интерфейсов, в Qt не используются функции обратного вызова для обеспечения реакции
Семейство библиотек Qt
21
на события, а применяется более совершенная технология сигналов и слотов.
И сигнал, и слот являются методами класса, за тем исключением,
что тело сигнала программист не реализует, его создает метаобъект- ный компилятор. Тип возвращаемого значения сигнала — void. Слот может иметь отличный от void тип возвращаемого значения, но ис- пользовать возвращаемое значение можно только, если слот вызыва- ется как обычная функция, а не по сигналу. Сигналы в определении класса объявляются в секции signals, слоты — в секции slots. Отпра- вить сигнал из кода можно с помощью ключевого слова emit.
Сигнатуры связываемых сигнала и слота должны либо совпадать,
либо в сигнале может присутствовать часть дополнительных парамет- ров, которые будут проигнорировать в слоте. В общем случае верно правило: «Qt может игнорировать часть данных, но не может полу- чить данные из ничего».
В следующем примере сигнал, отправляемый кнопкой в момент клика, связывается с стандартным слотом quit() объекта QApplication.
Пример 2.8
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Создаем и отрисовываем кнопку с надписью "Quit"
QPushButton button("Quit");
button.show();
// Связываем сигнал clicked() и слот quit()
QObject::connect(&button, SIGNAL(clicked()),
&a, SLOT(quit()));
return a.exec();
}
22
Семейство библиотек Qt
Статическая функция connect() класса QObject выполняет связы- вание сигнала и слота. Сигнал — функция, уведомляющая о наступле- нии события, слот — функция, получающая этот сигнал и корректно обрабатывающая его. Функция connect() получает четыре аргумента:
объект-отправитель, сигнал, объект-получатель, слот. При связыва- нии необходимо использовать макросы SIGNAL() и SLOT().
В примере 2.8 никакой дополнительной информации из сигнала в слот не передается. Рассмотрим более реальный пример. Пусть ин- терфейс содержит три виджета: QLabel, QSpinBox и QSlider. Послед- ние два обеспечивают просто различные способы ввода целых чисел.
Свяжем эти виджеты друг с другом таким образом, чтобы все они отображали одно и то же число.
Пример 2.9
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Создаем элементы интерфейса
QWidget window;
QVBoxLayout* mainLayout = new QVBoxLayout(&window);
QLabel* label = new QLabel("0");
QSpinBox* spinBox = new QSpinBox;
QSlider* slider = new QSlider(Qt::Horizontal);
// Выполняем компоновку mainLayout->addWidget(label);
mainLayout->addWidget(spinBox);
mainLayout->addWidget(slider);
// Выполняем связывание сигналов и слотов
Семейство библиотек Qt
23
QObject::connect(spinBox, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)));
QObject::connect(spinBox, SIGNAL(valueChanged(int)),
slider, SLOT(setValue(int)));
QObject::connect(slider, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)));
QObject::connect(slider, SIGNAL(valueChanged(int)),
spinBox, SLOT(setValue(int)));
window.show();
return a.exec();
}
Сигналы valueChanged() отправляются виджетами ввода целых чисел при изменении состояния (хранимого значения), слот setValue()
позволяет установить значение виджета ввода целых чисел вручную,
слот setNum() позволяет заменить текст надписи на заданное число- вое значение.
2.2.3
Прочие возможности
Контейнеры данных — классы, обеспечивающие возможности кон- тейнеров C++ STL (абстракции структур данных), но обладающих большей функциональностью.
В качестве примера можно привести QString — контейнер для хранения строк и манипуляций над ними. В отличие от своего STL- аналога string, QString хранит и обрабатывает строки в Unicode, при- меняет механизм неявного совместного использования и т.д.
Другие примеры контейнеров Qt — QVector, QList, QLinkedList,
QStringList, QMap, QHash, QStack, QQueue и другие.
Различные операционные системы могут иметь различные интер- фейсы доступа к носителям данных. Например, в ОС Windows ком- поненты пути к файлу разделяются символом обратной косой черты,
а в ОС Linux — обычной косой черты; в ОС Windows приложения обычно размещаются в каталоге Program Files, а в ОС Linux могут храниться, например, в /usr/bin.
24
Семейство библиотек Qt
Кроссплатформенная система программирования, такая как Qt,
должна предоставлять программисту некий унифицированный интер- фейс доступа к файлам и каталогам, не зависящий от деталей кон- кретной целевой системы.
Qt имеет набор классов для представления объектов на накопите- лях (QDir, QFile), потокового ввода-вывода (QTextStream, QDataStream),
а также богатый набор функций, значительно упрощающих задачу программиста.
Следует также отметить, что возможности Qt гораздо шире биб- лиотеки функций для создания графического интерфейса пользова- теля. Qt включает несколько модулей, реализующих классы для ор- ганизации обмена данными по сети, обработки структурированных
XML-данных, доступа к базам данных и даже работы с подсистемами мобильных устройств, такими как GPS-приемник или акселерометр.
Средства визуального проектирования
25
§3.
Средства визуального проектирования
Qt предоставляет разработчику не только набор библиотек, но и комплекс утилит, позволяющих, в частности, значительно сократить объем написанного вручную кода за счет использования визуальных средств.
В настоящее время программисту доступны для загрузки как биб- лиотеки и утилиты в отдельности, так и комплект разработчика — Qt
SDK, включающий и то, и другое. Комплект разработчика поставля- ется с интегрированной средой Qt Creator, объединяющей возможно- сти практически всех утилит, входящих в комплект, а также предо- ставляющий редактор кода, средства интеграции с отладчиком и си- стемами управления версиями и т.п. К числу основных утилит Qt сле- дует отнести Qt Designer — визуальный редактор форм, Qt Assistant
— средство организации справочной информации, Qt Linguist — ин- струментарий создания многоязычного интерфейса приложения.
Одним из основных принципов Qt выражен слоганом «пиши мень- ше — создавай больше» (code less — create more). Для наглядности поясним этот принцип на очень показательном примере создания про- стого приложения, заимствованном из [5], попутно рассмотрев основ- ные возможности дизайнера форм.
Разрабатываемое приложение представляет собой простейшую те- лефонную книгу.
Главное окно приложения отображает перечень записей (пар имя–
номер), а также предоставляет интерфейс для управления записями:
кнопку «Add» для добавления новой записи, кнопку «Edit» для ре- дактирования записи, кнопку «Del» для удаления выделенной записи и кнопку «Clear» для удаления всех записей.
По нажатию на кнопку «Add» или «Edit» открывается вспомога- тельное диалоговое окно, позволяющее ввести или отредактировать имя и номер. Диалоговое окно имеет две кнопки — «Ok» для приня- тия изменений и «Cancel» для отмены.
Схематично интерфейс приложения изображен на рис. 2.
Откроем Qt Creator. Внешний вид стартовой страницы показан на рис 3. Помимо доступа к среде разработки, начальная страница открывает программисту богатую коллекцию примеров и докумен- тацию. Для начала работы выберем вариант «Создать проект...». С
помощью открывшегося мастера проекта последовательно устанавли-
26
Средства визуального проектирования
Рис. 2: Набросок интерфейса пользователя ваем следующем параметры:
1. в окне «Новый проект» выбираем вариант «Проект Qt Widget
/ GUI приложение Qt»;
2. в окне «Введение и размещение проекта» задаем имя («PhoneBook»)
и каталог проекта;
3. в окне «Настройка цели» выбираем вариант «Desktop» (прило- жение для настолького компьютера);
4. в окне «Информация о классе» установим QWidget в качестве базового класса, убедимся, что отмечен флажок «Создать фор- му»;
5. в окне «Управление проектом» откажемся от использования си- стемы контроля версий (вариант по умолчанию).
В результате работы мастера будет сгенерирован шаблон прило- жения, содержащий файлы исходного кода main.cpp (главная про- грамма), widget.h (интерфейс класса Widget, представляющего форму
Средства визуального проектирования
27
Рис. 3: Стартовое окно Qt Creator приложения) и widget.cpp (реализация класса Widget), а также файл проекта PhoneBook.pro и форму widget.ui. При этом Qt Creator пе- реключится в режим редактора. Дважды щелкнув по файлу формы,
перейдем в режим дизайнера. Вид окна Qt Creator в режиме дизай- нера показан на рис. 4.
Рассмотрим инструменты, доступные разработчику при работе с дизайнером форм.
Собственно графическая форма, представляющая проектируемый интерфейс находится в верхней части центральной области окна. На рис. 4 форма пуста.
Слева размещается палитра компонентов, представляющая биб- лиотеку виджетов Qt. Для размещения виджета на форме достаточно перетащить его мышью.
Каждый виджет имеет набор свойств, как собственных, так и уна- следованных от базовых классов (вплоть до QWidget и QObject). Про- граммист имеет возможность модифицировать значения доступных для редактирования свойств с помощью редактора свойств. На рис. 4
28
Средства визуального проектирования
Рис. 4: Вид окна Qt Creator в режиме дизайнера редактор свойств находится в правом нижнем углу.
Над редактором свойств на рис. 4 размещается инспектор объек- тов, изображающий спроектированный интерфейс в виде иерархиче- ской структуры (в соответствии с вложенностью виджетов в контей- неры и компоновщики).
В нижней части центральной области на рис. 4 находятся сгруппи- рованные вместе редактор действий (привязка горячих клавиш, под- сказок и т. п.) и редактор сигналов и слотов.
Приступим к созданию интерфейса основного окна нашего прило- жения. Для этого перетащим на форму четыре кнопки (Push Button)
и вертикальный разделитель (Vertical Spacer). Выравнивание элемен- тов не имеет значения (см. рис. 5 слева). Выделим все элементы и применим вертикальный компоновщик (рис. 5 справа).
Для управления компоновкой можно воспользоваться панелью ком- поновки в верхней части окна дизайнера (рис. 6) или выбрать соот- ветствующий пункт контекстного меню, вызванного на выделенных объектах.
Средства визуального проектирования
29
Рис. 5: Добавляем и компонуем набор кнопок
Рис. 6: Панель компоновки
Добавим виджет ListWidget и применим к форме табличный ком- поновщик (рис. 7).
Рис. 7: Завершаем компоновку интерфейса
Используя редактор свойств, установим значения свойств objectName
30
Средства визуального проектирования и text кнопок, задав соответственно «addButton» / «Add», «editButton»
/ «Edit», «deleteButton» / «Delete» и «clearButton» / «Clear All». Ана- логично установим значение свойства objectName для виджета списка в «list».
Все предыдущие операции выполнялись в режиме изменения ви- джетов. Дизайнер форм поддерживает четыре режима:
• режим изменения виджетов, предназначенный для визуального проектирования интерфейса;
• режим редактирования сигналов и слотов;
• режим изменения партнёров, предназначенный для привязки надписей к полям ввода;
• режим изменения порядка обхода, предназначенный для опре- деления порядка передачи фокуса клавиатуры по нажатию кла- виши табуляции.
Для переключения режимов в верхней части дизайнера имеется спе- циальная панель (см. рис. 8).
Рис. 8: Панель режимов
Переключимся в режим редактирования сигналов и слотов. Захва- тив мышью кнопку «Clear All», переведем курсор на виджет списка.
В открывшемся диалоговом окне, для отправителя (кнопки) выбе- рем сигнал clicked(), а для виджета списка слот clear(). Теперь при нажатии на кнопку «Clear All» все данные виджета списка будут уда- ляться.
Переключимся в режим изменения порядком обхода и установим нужный (см. рис. 12).
Добавим в проект еще один файл формы (editdialog.ui) и анало- гично описанному выше спроектируем второе окно. Полям ввода при- своим имена nameEdit и numberEdit.
Средства визуального проектирования
31
Рис. 9: Выполняем связывание сигналов и слотов
Рис. 10: Устанавливаем порядок обхода виджетов
Рис. 11: Диалог редактирования
32
Средства визуального проектирования
Для обеспечения возможности быстрого доступа к полям ввода с клавиатуры, настроим горячие клавиши. Для этого в тексты надпи- сей зададим как «&Name» и «&Phone» и, переключившись в режим редактирования партнеров, свяжем надписи с полями ввода. Теперь полям ввода можно быстро передавать фокус клавиатуры, используя сочетания Alt+N и Alt+P соответственно, а в тексте надписей эти символы будут изображаться с подчеркиванием, подсказывая пользо- вателю комбинации горячих клавиш.
На этом этапе подготовка форм завершена. Заметим, что в про- екте пока еще нет ни одной написанной вручную строки кода!
Сохраненная форма представляет собой XML-файл (*.ui) с описа- нием интерфейса. Файлы описаний интерфейса обрабатываются спе- циальной программой uic (user interface compiler). На выходе uic ге- нерирует файлы исходного кода, которые включаются в проект.
Создадим обработчик нажатия кнопки «Add...». Для этого, нахо- дясь в режиме изменения виджетов, в контекстном меню на кнопке
«Add...» выберем пункт «Перейти к слоту...» и в открывшамся окне выберем сигнал clicked(). Qt Creator переключится в режим редакти- рования файла исходного кода, соответствующего форме (см. пример
??
).
Пример 3.1
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::Widget()
{
delete ui;
}
Средства визуального проектирования
33
void Widget::on_addButton_clicked()
{
}
Первые два метода построены из шаблона проекта, использующе- го форму. Все элементы спроектированной визуально формы доступ- ны через объект ui, например ui->editButton. Третий метод — сге- нерированный слот для обработки нажатия кнопки. Автоматически создаваемые слоты получают имена по схеме on_objectName_signal.
Добавим в тело обработчика код, создающий диалоговое окно для ввода имени и номера.
Пример 3.2
EditDialog dlg( this);
if( dlg.exec() == Qt::Accepted )
ui->list->addItem( dlg.name() + " -- " + dlg.number());
Аналогично построим обработчик кнопки удаления.
Пример 3.3
void Widget::on_deleteButton_clicked()
{
foreach (QListWidgetItem *item, ui->list->selectedItems())
delete item;
}
Заметим, что кнопка «Delete» всегда активна, что не корректно —
кнопка должна быть доступна только, если есть выделенные позиции в списке. Добавим слот updateDeleteEnabled():
Пример 3.4
void Widget::updateDeleteEnabled()
{
34
Средства визуального проектирования ui->deleteButton->setEnabled(
ui->list->selectedItems().count() != 0);
}
Дополнительно необходимо переключиться к заголовочному фай- лу и объявить этот слот в секции private slots.
Соединим созданный слот с сигналом об изменении выделения:
Пример 3.5
connect(ui->list->selectionModel(),
SIGNAL(selectionChanged(QItemSelection,
QItemSelection)),
this,
SLOT(updateDeleteEnabled()));
Аналогично обработчику кнопки «Add» реализуется обработчик кнопки «Edit». Также дополнительно потребуется реализовать мето- ды класса EditDialog, представляющего диалоговое окно редактиро- вания записи телефонной книги. Предоставим читателю завершить работу над этим приложением.
Компоновка и размещение виджетов
35
§4.
Компоновка и размещение виджетов
Типичное окно современного приложения с графическим интер- фейсом может содержать десятки виджетов, некоторым образом раз- мещенных на холсте окна.
Размещение виджетов может быть выполнено вручную. QWidget и производные классы включают метод setGeometry(), обеспечиваю- щий возможность задать фиксированные размеры и позицию виджета
(см. пример 4.1).
Пример 4.1
Window::Window(QWidget *parent) : QWidget(parent)
{
setFixedSize(640, 480);
QTextEdit *txt = new QTextEdit(this);
txt->setGeometry(20, 20, 600, 400);
QPushButton *btn = new QPushButton(tr("&Close"), this);
btn->setGeometry(520, 440, 100, 20);
}
Однако расстановка виджетов вручную приводит к ряду сложно- стей. Например, при необходимости добавить новый виджет, требу- ется модифицировать местоположение остальных. Также возникают проблемы с изменением размеров окна пользователем — при фикси- рованном размещении часть элементов управления может оказаться недоступной, вне видимой области окна.
4.1
Использование компоновщиков
Qt предоставляет простой механизм автоматического размещения виджетов внутри других виджетов посредством компоновщиков. В
частности, копмоновка позволяет разумно использовать пространство окна и реализовать так называемый «эластичный интерфейс», когда виджеты получают возможность адаптировать свои размеры и раз- мещение в зависимости от содержимого и пользовательских настро- ек, например, автоматически растягиваться, чтобы вместить текст с увеличенным шрифтом.
36
Компоновка и размещение виджетов
Qt предоставляет три вида компоновщиков: вертикальный (класс
QVBoxLayout), горизонтальный (класс QHBoxLayout), табличный (класс
QGridLayout) и компоновщик форм (QFormLayout), предназначенный для формирования форм ввода, состоящих из колонки пар надпись —
поле ввода. Непосредственные размеры вложенных виджетов опреде- ляются в ходе выполнения специального процесса, называемого «пе- реговорами».
Рассмотрим пример типичного диалогового окна:
Рис. 12: Пример использования компоновщиков
Надпись «Printer» и выпадающий список в верхней части диало- гового окна объединены с помощью горизонтального компоновщика.
Второй горизонтальный компоновщик объединяет две группы кнопок.
Третий формирует строку кнопок в нижней части окна. Для выравни- вания элементов диалогового окна применены разделители (QSpacer)
— невидимые виджеты-распорки. Все три горизонтальных компонов- щика и один из разделителей выровнены с помощью вертикального компоновщика, примененного ко всему окну.
Следующий пример демонстрирует общую схему построения ин- терфейса, представленного на рис. 12 с использованием вертикальных и горизонтальных компоновщиков.
Пример 4.2
// Создаем внешний вертикальный компоновщик
QVBoxLayout *outerLayout = new QVBoxLayout(this);
Компоновка и размещение виджетов
37
// Создаем верхний горизонтальный компоновщик
QHBoxLayout *topLayout = new QHBoxLayout();
// Добавляем виджеты topLayout->addWidget(new QLabel("Printer:"));
topLayout->addWidget(c=new QComboBox());
// Добавляем в внешний компоновщик outerLayout->addLayout(topLayout);
// Создаем средний горизонтальный компоновщик
QHBoxLayout *groupLayout = new QHBoxLayout();
outerLayout->addLayout(groupLayout);
// Добавляем разделитель outerLayout->addSpacerItem(new QSpacerItem(...));
// Создаем нижний горизонтальный компоновщик
QHBoxLayout *buttonLayout = new QHBoxLayout();
buttonLayout->addSpacerItem(new QSpacerItem(...));
buttonLayout->addWidget(new QPushButton("Print"));
buttonLayout->addWidget(new QPushButton("Cancel"));
outerLayout->addLayout(buttonLayout);
// Применяем внешний компоновщик к окну setLayout(outerLayout);
Каждый виджет наследует от класса QWidget свойства sizeHint,
minimumSizeHint и sizePolicy. Свойство sizeHint определяет, какое про- странство виджет «хотел бы» занимать в нормальных условиях. Свой- ство minimumSizeHint задает минимальный размер, меньше которого виджет не может занимать ни при каких обстоятельствах. Значением свойства sizePolicy является политика, определяющая отношение ви- джета к изменениям его размера и принимающая одно из следующих значений:
• QSizePolicy::Fixed — виджет не может иметь размер, отличный от sizeHint;
38
Компоновка и размещение виджетов
• QSizePolicy::Minimum — sizeHint представляет минимально до- пустимый размер виджета, но он может быть неограниченно растянут;
• QSizePolicy::Maximum — sizeHint представляет максимально до- пустимый размер виджета, но он может быть неограниченно ужат;
• QSizePolicy::Preferred — sizeHint представляет оптимальный раз- мер виджета, но изменения допустимы в обе стороны;
• QSizePolicy::Expanding — аналогично Preferred, но виджет тре- бует предоставить ему любое доступное пространство компонов- щика;
• QSizePolicy::MinimumExpanding — аналогично Minimum, но ви- джет требует предоставить ему любое доступное пространство компоновщика;
• QSizePolicy::Ignored — sizeHint игнорируется, виджету выделя- ется столько пространства, сколько возможно в пределах ком- поновщика.
Помимо варьирования свойств sizeHint, minimumSizeHint и sizePolicy вложенных в компоновщик виджетов, управление размещением мо- жет быть выполнено и средствами самого компоновщика. Метод addWidget()
содержит необязательный второй аргумент (stretch factor), равный по умолчанию нулю. Ненулевые значения коэффициента растяжения за- дают относительный размер пространства, выделенного компоновщи- ком под размещение виджета.
При использовании табличного компоновщика программист зада- ет, по каким границам сетки выравнивается каждый вложенный ви- джет. Такой подход позволяет занимать под виджет любую прямо- угольную подобласть таблицы, а также создавать перекрытия. Если добавляемый виджет занимает ровно одну ячейку таблицы, достаточ- но определить левую и верхнюю границы, в противном случае — все четыре (см. пример 4.3).
Пример 4.3
Компоновка и размещение виджетов
39
QPushButton *button1 = new QPushButton("One");
QPushButton *button2 = new QPushButton("Two");
QPushButton *button3 = new QPushButton("Three");
QPushButton *button4 = new QPushButton("Four");
QPushButton *button5 = new QPushButton("Five");
QGridLayout *layout = new QGridLayout;
layout->addWidget(button1, 0, 0);
layout->addWidget(button2, 0, 1);
layout->addWidget(button3, 1, 0, 1, 2);
layout->addWidget(button4, 2, 0);
layout->addWidget(button5, 2, 1);
Предложенный в примере код создает интерфейс, представленный на рис. 13.
Рис. 13: Пример использования табличного компоновщика
4.2
Контейнерные виджеты
Помимо компоновщиков иерархическая структура графического интерфейса может быть построена с помощью виджетов-контейнеров
— виджетов, которые могут содержать другие виджеты.
Контейнерные виджеты могут применяться как для организации высокоуровневых элементов, например, окон — QWidget, QDialog, а также QMainWindow, так и группировки виджетов внутри окон —
QGroupBox, QTabWidget и т.п.
40
Компоновка и размещение виджетов
Пример 4.4 иллюстрирует применение QMainWindow для органи- зации окна приложения и QTabWidget для создания набора вкладок.
Следует отметить, что в отличие от «пустых» контейнеров QWidget и
QDialog, QMainWindow включает основные элементы типового окна приложения — меню, строку состояния и контейнер для наполнения центральной области (centralWidget).
Пример 4.4
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QMainWindow *window = new QMainWindow();
window->setWindowTitle("MainWindow");
window->resize(250, 250);
QWidget *centralWidget = new QWidget(window);
QTabWidget *tabs = new QTabWidget(centralWidget);
tabs->setFixedSize(245, 245);
tabs->addTab(new QWidget(),"Tab 1");
tabs->addTab(new QWidget(),"Tab 2");
window->setCentralWidget(centralWidget);
window->show();
return app.exec();
}
Обзор библиотеки виджетов Qt
41
§5.
Обзор библиотеки виджетов Qt
В предыдущих разделах уже были приведены примеры виджетов
— основным строительных элементов графического интерфейса поль- зователя.
Далее рассмотрим несколько основных виджетов Qt. В целях эко- номии все примеры построены по общей схеме: имеется приложение,
состоящее из трех следующих файлов исходного кода. В главном фай- ле main.cpp создается экземпляр приложения и собственного виджета
Widget.
Пример 5.1
#include
#include "widget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
Файл widget.h содержит описание класса Widget, при этом в при- мерах будут варьироваться файлы заголовков и приватные компонен- ты.
#ifndef WIDGET_H
#define WIDGET_H
#include
// Необходимые файлы заголовков class Widget : public QWidget
{
42
Обзор библиотеки виджетов Qt
Q_OBJECT
public:
Widget(QWidget *parent = 0);
Widget();
private:
// Необходимые приватные компоненты
};
#endif // WIDGET_H
Файл widget.cpp содержит реализацию методов класса Widget,
при этом пользовательский интерфейс формируется в конструкторе класса. Именно код конструктора будет приведен во всех последую- щих примерах этого раздела.
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
// Конструируем интерфейс пользователя
}
Widget::Widget()
{
}
5.1
Кнопки
Три класса QPushButton, QCheckBox, QRadioButton реализуют соответственно простую кнопку, флажок и радиокнопку. Базовым клас- сом всех кнопок является QAbstractButton.
Основные сигналы для кнопок:
Обзор библиотеки виджетов Qt
43
• clicked() отправляется в момент, когда пользователь отпускает кнопку мыши;
• toggled(bool) отправляется в момент изменения состояния кноп- ки.
Отметим также наиболее полезные свойства:
• checkable — истина, если кнопка может работать как флажок;
• checked — истина, если флажок имеет отметку;
• text — текст кнопки;
• icon — иконка кнопки, которая может отображаться вместе с текстом.
Следующий пример иллюстрирует использование QCheckBox. В
конструкторе создадим флажок, установив отметку как начальное со- стояние.
Пример 5.2
// Создаем флажок checkBox = new QCheckBox("Show Title", this);
checkBox->setCheckState(Qt::Checked);
// Связываем изменение состояния флажка
// с обработчиком connect(checkBox, SIGNAL(stateChanged(int)),
this, SLOT(showTitle(int)));
Код обработчика для изменения состояния флажка приведен ни- же. В данном примере меняем заголовок окна.
void Widget::showTitle(int state)
{
if (state == Qt::Checked) {
setWindowTitle("QCheckBox");
} else {
setWindowTitle("");
}
}
44
Обзор библиотеки виджетов Qt
5.2
Надписи
QLabel отображает текстовый блок или картинку, которые зада- ются свойствами text и pixmap соответственно. Следующий пример демонстрирует использование QLabel для изображения простой тек- стовой надписи.
Пример 5.3
// Создаем компоновщик layout = new QHBoxLayout();
// Создаем надпись label = new QLabel("Hello");
// Добавляем надпись в компоновщик,
центрируя разделителями layout->addWidget(new QSplitter());
layout->addWidget(label);
layout->addWidget(new QSplitter());
// Применяем компоновщик к виджету setLayout(layout);
QLabel распознает некоторое подмножество тэгов HTML-разметки.
Пример 5.4
// Создаем полужирную надпись красного цвета label = new QLabel(
"
Hello");
Для класса QLabel предусмотрен так называемый механизм «при- ятелей» (buddy). На практике надписи часто появляются в связке с виджетами, обеспечивающими ввод данных, например, QLineEdit.
При этом для комфортной работы пользователя разумно предостав- лять возможность передачи фокуса клавиатуры в поле ввода по горя- чей клавише. Однако подсказку о том, какая конкретно комбинация
Обзор библиотеки виджетов Qt
45
клавиш переводит фокус в поле ввода разместить можно только в надписи, обычно, подчеркиванием буквы, клавишу которой необходи- мо нажать, удерживая Control. Таким образом надпись и поле ввода работают в паре. Механизм работает, только в случае, когда в тексте надписи одной из букв (клавиша которой задействована в комбина- ции) предшествует амперсанд.
Пример 5.5
// Создаем надпись и поле ввода для ввода имени
QLineEdit *nameEdit
= new QLineEdit(this);
QLabel *nameLabel
= new QLabel("&Name:", this);
nameLabel->setBuddy(nameEdit);
// Создаем надпись и поле ввода для ввода номера
QLineEdit *phoneEdit = new QLineEdit(this);
QLabel *phoneLabel = new QLabel("&Phone:", this);
phoneLabel->setBuddy(phoneEdit);
5.3
Инструменты ввода текста
Виджет QLineEdit обеспечивает ввод одной строки текста.
Основные сигналы:
• textChanged() отправляется при изменении текста;
• editingFinished() отправляется при потере фокуса ввода;
• returnPressed() отправляется при нажатии клавиши «Enter».
Полезные свойства:
• text — текст в поле ввода;
• maxLength — предельная длина текста в поле ввода;
• readOnly — если истина, редактирование запрещено.
Следующий пример создает простой интерфейс для редактирова- ния визитной карточки.
Пример 5.6
46
Обзор библиотеки виджетов Qt
// Создает набор надписей
QLabel *name = new QLabel("Name:", this);
QLabel *age = new QLabel("Age:", this);
QLabel *occupation = new QLabel("Occupation:", this);
// Создаем набор полей ввода
QLineEdit *le1 = new QLineEdit(this);
QLineEdit *le2 = new QLineEdit(this);
QLineEdit *le3 = new QLineEdit(this);
// Создаем табличный компоновщик
QGridLayout *grid = new QGridLayout();
// Размещаем виджеты в ячейках таблицы grid->addWidget(name, 0, 0);
grid->addWidget(le1, 0, 1);
grid->addWidget(age, 1, 0);
grid->addWidget(le2, 1, 1);
grid->addWidget(occupation, 2, 0);
grid->addWidget(le3, 2, 1);
setLayout(grid);
Классы QTextEdit, QPlainTextEdit обеспечивают возможность ре- ализации инструментов ввода многострочных текстов.
Основные сигналы:
• textChanged() отправляется при изменении текста.
Полезные свойства:
• plainText — неформатированный текст;
• html — текст использует HTML-разметку;
• readOnly — если истина, редактирование запрещено.
Обзор библиотеки виджетов Qt
47 5.4
Инструменты ввода чисел
Все четыре перечисленных в заголовке подраздела класса являют- ся инструментами ввода-вывода целых чисел, но отличаются визуаль- ным представлением. В то время как QSlider реализует бегунок для ввода числового значения, QScrollBar применяется для позициониро- вания виджета большого размера относительно маленькой видимой области. QDial представляется круговой шкалой, а QSpinBox позво- ляет вводить числа вручную или листать с помощью стрелок.
Основные сигналы:
• valueChanged() отправляется при изменении значения.
Полезные свойства:
• value — текущее значение;
• maximum — максимальное значение;
• minimum — минимальное значение.
Пример 5.7
// Создаем экземпляр поля ввода чисел spinBox = new QSpinBox();
// Обрабатываем сигнал изменения значения connect(spinBox, SIGNAL(valueChanged(int)),
this, SLOT(setTitle(int)));
}
В обработчике будем печатать в отладочную консоль новое значе- ние, установленное в виджете:
void Widget::setTitle(int val)
{
qDebug() << QString::number(val);
}
48
Создание собственных виджетов
§6.
Создание собственных виджетов
Несмотря на обилие стандартных виджетов в Qt, программисту может потребоваться особенный элемент управления, не представлен- ный в библиотеке.
6.1
Создание виджета композицией имеющихся
В качестве примера рассмотрим виджет, состоящий из элементов
QDial, QLabel и QSlider, организованных в один столбец вертикаль- ным компоновщиком.
Создадим заголовочный файл ComposedGauge.h, опишем интер- фейс класса (см. пример 6.1). Так как наш класс является видже- том, наследуем его от QWidget. QWidget является потомков QObject,
следовательно мы получим и механизм сигналов и слотов. Для кор- ректной обработки класса метаобъектным компилятором добавляем макрос Q_OBJECT.
Объявляем свойство value, доступ к которому на чтение и запись обеспечивается соответственно функциями value() и setValue(). Функ- ция setValue() объявлена слотом и будет связываться с сигналами. Мы также определяем собственный сигнал valueChanged() для информи- рования о том, что данные виджета изменились.
Для хранения фактического значения виджета требуется объект данных, импользуем в этом качестве приватную переменную m_slider.
Пример 6.1
class ComposedGauge : public QWidget
{
Q_OBJECT
Q_PROPERTY(int value READ value WRITE setValue)
public:
explicit ComposedGauge(QWidget *parent = 0);
int value() const;
public slots:
void setValue(int);
signals:
void valueChanged(int);
Создание собственных виджетов
49
private:
QSlider *m_slider;
};
В конструкторе класса построим интерфейс и свяжем сигналы и слоты.
ComposedGauge::ComposedGauge(QWidget *parent) :
QWidget(parent)
{
// Создаем виджеты, упаковываем в компоновщик
QVBoxLayout *layout = new QVBoxLayout(this);
QDial *dial = new QDial();
QLabel *label = new QLabel();
m_slider = new QSlider();
layout->addWidget(dial);
layout->addWidget(label);
layout->addWidget(m_slider);
// Устанавливаем дополнительные параметры виджетов m_slider->setOrientation(Qt::Horizontal);
label->setAlignment(Qt::AlignCenter);
dial->setFocusPolicy(Qt::NoFocus);
dial->setValue(m_slider->value());
label->setNum(m_slider->value());
// Связываем сигналы и слоты connect(dial, SIGNAL(valueChanged(int)),
m_slider, SLOT(setValue(int)));
connect(m_slider, SIGNAL(valueChanged(int)),
dial, SLOT(setValue(int)));
connect(m_slider, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)));
connect(m_slider, SIGNAL(valueChanged(int)),
this, SIGNAL(valueChanged(int)));
Реализуем методы установки и чтения значения виджета (так на- зываемые «геттер» и «сеттер»).
50
Создание собственных виджетов
// Возвращает константную ссылку
// на хранимое значение int ComposedGauge::value() const
{
return m_slider->value();
}
// Модифицирует хранимое значение
// в соответствии с параметром void ComposedGauge::setValue(int v)
{
m_slider->setValue(v);
}
Сконструированный таким образом виджет можно легко встроить в проектируемый интерфейс.
ComposedGauge *gauge = new ComposedGauge();
layout->addWidget(gauge);
connect(gauge, SIGNAL(valueChanged(int)), ... );
Создание истинно собственного виджета требует от программиста самостоятельно:
• управлять отрисовкой виджета;
• обрабатывать события (клавиатура, мышь, оконный менеджер и другие);
• управлять подсказками и политиками размера виджета (для компоновки).
6.2
Отрисовка на поверхности виджета
Отрисовка реализуется через метод paintEvent(), который должен быть переопределен в классе. В методе paintEvent() необходимо полу- чить экземпляр класса QPainter, соответствующий виджету и выпол- няющий отрисовку.
Создание собственных виджетов
51
Пример 6.2
class MyWidget : public QWidget
{
protected:
void paintEvent(QPaintEvent*);
};
void MyWidget::paintEvent(QPaintEvent *ev)
{
QPainter p(this);
}
Отрисовка выполняется в координатной плоскости относительно левого верхнего угла холста. Для работы с координатами применяют- ся следующие классы Qt:
• QPoint представляет точку (пару координат);
• QSize представляет размеры прямоугольника (ширина, высота);
• QRect представляет прямоугольник (координаты левого верх- него угла, ширина и высота).
Аналогично QPointF, QSizeF, QRectF представляют те же вели- чины, но в формате с плавающей точкой.
Тривиальная отрисовка выполняется с применением стилей кон- тура (QPen), заливки (QBrush), для хранения цвета определен класс
QColor, присутствует набор функций, реализующих простые геомет- рические примитивы. Следующий пример иллюстрирует использова- ние заливки для изображение прямоугольника и эллипса.
Пример 6.3
52
Создание собственных виджетов void RectWithCircle::paintEvent(QPaintEvent *ev)
{
QPainter p(this);
p.setBrush(Qt::green);
p.drawRect(10, 10, width()-20, height()-20);
p.setBrush(Qt::yellow);
p.drawEllipse(20, 20, width()-40, height()-40);
}
Класс QPen обеспечивает широкий набор стилей линий:
• Qt::SolidLine — сплошная линия;
• Qt::DashLine — штриховая линия;
• Qt::DotLine — пунктирная линия;
• и т. п.
Также определены стили соединения и концов линий.
Инструмент заливки также может использовать не только сплош- ной цвет, но и шаблоны, текстуры и градиенты.
Большинство функций рисования перегружены, чтобы обеспечить программисту возможность выбора наиболее удобного программного интерфейса:
Пример 6.4
// Рисуем прямоугольник (три способа)
drawRect(QRectF r);
drawRect(QRect r);
drawRect(int x, int y, int w, int h);
//Рисуем точку (три способа)
drawPoint(QPointF p);
drawPoint(QPoint p);
drawPoint(int x, int y);
Перечислим функции отрисовки некоторых простых примитивов:
Создание собственных виджетов
53
• QPainter::drawPoint() — точка;
• QPainter::drawLine() — отрезок прямой;
• QPainter::drawRect() — прямоугольник;
• QPainter::drawRoundedRect() — скругленный прямоугольник;
• QPainter::drawEllipse() — эллипс;
• QPainter::drawArc() — участок дуги эллипса;
• QPainter::drawText() — произвольный текст.
При выводе текста на экран можно использовать класс QFont для установки параметры вывода текста (гарнитура шрифта, размер, и т. п.).
Пример 6.5
QPainter p(this);
QFont font("Helvetica");
p.setFont(font);
p.drawText(20, 20, 120, 20, 0, "Hello World!");
font.setPixelSize(10);
p.setFont(font);
p.drawText(20, 40, 120, 20, 0, "Hello World!");
font.setPixelSize(20);
p.setFont(font);
p.drawText(20, 60, 120, 20, 0, "Hello World!");
QRect r;
p.setPen(Qt::red);
p.drawText(20, 80, 120, 20, 0, "Hello World!", &r);
Отрисовывая примитивы, можно использовать базовые трансфор- мации QPainter:
• QPainter::scale — масштабирование;
54
Создание собственных виджетов
• QPainter::translate — перенос;
• QPainter::rotate — поворот;
• QPainter::shear — параллельные искажения.
Трансформации применяются к координатной сетке. Используя save() и restore() трансформации можно сохранять на стеке QPainter и позже восстановить.
Пример 6.6
QPoint rotCenter(50, 50);
qreal angle = 42;
// Сохраняем состояние трансформации p.save();
// Выполняет поворот p.translate(rotCenter);
p.rotate(angle);
p.translate(-rotCenter);
// Отрисовываем первый (повернутый) квадрат p.setBrush(Qt::red);
p.setPen(Qt::black);
p.drawRect(25,25, 50, 50);
// Восстанавливаем состояние трансформации p.restore();
// Отрисовываем второй квадрат p.setPen(Qt::NoPen);
p.setBrush(QColor(80, 80, 80, 150));
p.drawRect(25,25, 50, 50);
6.3
Пример создания виджета с нуля
Создадим виджет, позволяющий визуализировать спидометр. По- мимо графического изображения определим реакцию на события кла-
Создание собственных виджетов
55
виатуры и мыши.
Рис. 14: Внешний вид виджета
Следующий код выполняет отрисовку спидометра.
Пример 6.7
void CircularGauge::paintEvent(QPaintEvent *ev)
{
QPainter p(this);
// Отрисовка фона int extent;
if (width()>height())
extent = height()-20;
else extent = width()-20;
p.translate((width()-extent)/2, (height()-extent)/2);
p.setPen(Qt::white);
p.setBrush(Qt::black);
p.drawEllipse(0, 0, extent, extent);
// Отрисовка отсечек p.translate(extent/2, extent/2);