/
Tags: компьютерные технологии
ISBN: 5-8459-0996-1
Text
А.В. ЧЕБОТАРЕВ
БИБЛИОТЕКА QT 4
СОЗДАНИЕ ПРИКЛАДНЫХ
ПРИЛОЖЕНИЙ
В СРЕДЕ LINUX
• Объектная модель Qt 4 — свойства, обработка событий,
сигналы и слоты
• Объект QApplication и структура приложения
• Строки, списки, коллекции, регулярные выражения
• Элементы управления — рисование, состояния, события
• Графика, звук и анимация в Qt
• Ввод-вывод и файловые системы, управление
физическими устройствами
• Серверные и сетевые классы
• Работа с базами тайных
• Поддержка DOM и ActiveX
1В
AQT4
АДНЫХ
1Й
JX
। разработкой переместимых
\indows. Простая в освоении
/универсальная библиотека Qt
пользуется профессионалами
(фической оболочки KDE
а библиотека так же хорошо
lx или FreeBSD. Подуху,
ка и инструменты Qt похожи
языка C++ вместо
упностью исходного кода
I.
++ всем остальным языкам
ibie приложения для Linux,
я большинство самых важных
скорее, базовый курс по
нием Qt 4. Книга
-ие C++ в той или иной мере
тройства и архитектуры
ws) и общей технологии
лной строке и писать
штатный автор журнала
Ф журналов “Сети
liter World Ukraine”. В активе:
ЕС 1055 до... собственно,
лирования и системного
А.В. ЧЕБО"
БИБЛИОП
СОЗДАНИЕ П РЕ
ПРИЛОЖ1
В СРЕДЕ L
• Объектная модель Qt 4 — cboi
сигналы и слоты
• Объект QApplication и структу
• Строки,списки,коллекции, р
• Элементы управления — рисо
• Графика, звук и анимация в Q
• Ввод-вывод и файловые систе
физическими устройствами
• Серверные и сетевые классы
• Работа с базами данных
• Поддержка DOM и ActiveX
А.В. ЧЕБОТАРЕВ
БИБЛИОТЕКА QT 4
СОЗДАНИЕ ПРИКЛАДНЫХ
ПРИЛОЖЕНИЙ
В СРЕДЕ LINUX
Б Г нига предназначена для всех, кто интересуется разработкой переместимых
программных приложений для среды Linux и Windows. Простая в освоении
использовании, хорошо документированная, универсальная библиотека Qt
от голландской компании Trolltech сейчас широко используется профессионалами
во всем мире — она положена в основу известной графической оболочки KDE
и еще нескольких сотен популярных приложений. Эта библиотека так же хорошо
интегрируется с MS Windows и MacOS X, как и с Linux или FreeBSD. По духу,
мощности, удобству и оптимальности кода библиотека и инструменты Qt похожи
на Borland Delphi — с поправками на использование языка C++ вместо
Object Pascal, работу на различных платформах и доступностью исходного кода
самой библиотеки и всех ее инструментов разработки.
Эта книга — для программистов, предпочитающих C++ всем остальным языкам
программирования и желающих создавать эффективные приложения для Linux,
переносимые и на другие платформы. Тут обсуждается большинство самых важных
классов библиотеки Qt, однако это не справочник, а скорее, базовый курс по
освоению методов написания программ с использованием Qt 4. Книга
предназначена для подготовленных читателей — знание C++ в той или иной мере
обязательно. Помимо этого предполагается знание устройства и архитектуры
операционных систем (в частности Linux и MS Windows) и общей технологии
программирования, а также умение работать в командной строке и писать
командные файлы.
ОБ АВТОРЕ
Арсений Викторович Чеботарев — научный редактор и штатный автор журнала
“ Компьютеры+Программы” (PC World Ukraine), автор журналов “Сети
и Коммуникации” и “Шпиль”, а также газеты “Computer World Ukraine”. В активе:
20 лет компьютерной практики, от оператора СМ2 и ЕС 1055 до... собственно,
до автора этой книги, включая многие годы программирования и системного
администрирования.
дидлЕктшсд
www.dialektika.com
гн’ОФ!зональная рабспа
БИБЛИОТЕКА QT 4
СОЗДАНИЕ ПРИКЛАДНЫХ
ПРИЛОЖЕНИЙ
В СРЕДЕ LINUX
i .vpnH 'Профессиональная рабога”
Книги этой серии предназначены для специалистов средней и высокой
квалификации, желающих получить глубокие знания в области практического
использования тех или иных программных средств и технологий. Изложение
материала строится на предоставлении читателю большого объема
специализированной технической информации и углубленном практическом
подходе к вопросам реализации. Как правило, в книгах этой серии приводится
специфическая информаций, которой нет Ии в каких других книгах, а также
присутствует, большое количество реальных примеров, благодаря которым
пользователь даже с незначительным личным опытом сможет быстро достичь
высокого профессионального уровня.
Состав серии
1. Варакин А. С., AutoCAD. Профессиональная работа
2. Галисеев Г.В., Компоненты в Delphi 7. Профессиональная работа
3. КлюшинД.А., Полный курс C++. Профессиональная работа
4. Кнабэ Г.А., Энциклопедия дизайнера печатной продукции. Профессиональная
работа
5. Ковтанюк Ю. С., CorelDraw 12 для дизайнера. Профессиональная робота
6. ЛандэД.В., Поиск знаний в Internet. Профессиональная работа
7. Минько А.А., Статистический анализ в MS Excel. Профессиональная работа
8. Сергеев А.П., HTMLuXML. Профессиональная работа
Книги серии “Решение практических задач”
Книги этой серии ориентированы на пользователей компьютеров из самых
различных сфер — бизнеса, науки, производства, образования. Главное что их
объединяет — это стремление профессионально освоить уже существующие
прикладные компьютерные пакеты и технологии, что позволит им добиться
максимальной эффективности в использовании компьютеров для решения
стоящих перед ними задач. Изложение материала строится на предоставлении
читателю большого объема необходимой теоретической и практической
информации с акцентом на методах решения конкретных прикладных задач,
которые могут его интересовать. Серия предназначена для читателей, желающих
получись глубокие практические знания в области эффективного использования
различных программных средств и технологий в своей повседневной
профессиональной деятельности.
Состав серии
1. Васильев А.Н, Научные вычисления в Microsoft Excel, Решение практических задач
2. Сингаевская Г, И, Функции в Excel, Решение практических задач
ПРОФЕССИОНАЛЬНЛЯ РАБО
А.В. ЧЕБОТАРЕВ
БИБЛИОТЕКА QT 4
СОЗДАНИЕ ПРИКЛАДНЫХ
ПРИЛОЖЕНИЙ
В СРЕДЕ LINUX
дцдлаапикд
Москва • Санкт-Петербург • Киев
2006
ББК 32.973.26-018.2.75
4-34
УДК 681.3.07
Компьютерное издательство “Диалектика”
Зав. редакцией АВ. Слепцов
Выпускающий редактор Н.М. Ручко
По общим вопросам обращайтесь.в издательство “Диалектика” по адресу:
info@dialektika.eom, http://www.dialektika.com
115419, Москва, а/я 783; 03150, Киев, а/я 152
Чеботарев, А.В.
4-34 Библиотека Qt 4. Создание прикладных приложений в среде Linux. Профес-
сиональная работа. — М.: Издательский дом “Вильямс”, 2006. — 256 с.: ил.
ISBN 5-8459-0996-1 (рус.)
Эта книга — для программистов, предпочитающих C++ всем остальным язы-
кам программирования и желающих создавать эффективные приложения для
Linux, переносимые и на другие платформы. Тут обсуждается большинство самых
важных классов библиотеки Qt, однако не рассчитывайте на полное описание их
методов или списки свойств — это не справочник с таблицами классов, их свойств
и функций-членов. Скорее, это базовый курс по освоению методов написания про-
грамм с использованием Qt — универсальной библиотеки (или программной обо-
лочки) для создания мощных переносимых приложений на языке C++.
Книга предназначена для подготовленных читателей — знание C++ в той или
иной мере обязательно. Также предполагается знание устройства и архитектуры
операционных систем (в частности Linux и MS Windows) и общей технологии про-
граммирования — принципов сборки программ, использования компилятора,
сборщика и отладчика, а также умение работать в командной строке и писать ко-
мандные файлы.
ББК 32.973.26-018.2.75
Все названия программных продуктов являются зарегистрированными торговыми марками соответ-
ствующих фирм.
Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы
то ни было форме и какими бы то ни было средствами, будь то электронные или механические, вклю-
чая фотокопирование и запись на магнитный носитель, если на это нет письменного разрешения из-
дательства “Диалектика”.
Copyright © 2006 by Dialektika Computer Publishing.
All rights reserved including the right of reproduction in whole or in part in any form.
ISBN 5-8459-0996-1 (рус.) © Компьютерное изд-во “Диалектика”, 2006,текст,
оформление, макетирование
Оглавление
Предисловие 11
Глава 1. Введение в Qt, или самое главное 27
Глава 2. Базовые классы приложений 39
Глава 3. Общие принципы пользовательского интерфейса Qt 55
Глава 4. Базовые элементы управления 85
Глава 5. Двухмерная и трехмерная графика 117
Глава 6. Мультимедийные возможности Qt 139
Глава 7. Устройства ввода-вывода: класс QIODevice 145
Глава 8. Модуль Network и сетевые возможности 151
Глава 9. Базы данных и модуль SQL 159
Глава 10. Модуль XML 171
Глава 11. Многопоточное программирование в Qt 179
Глава 12. Программирование для различных платформ 185
Приложение А. Компиляция и сборка проектов Qt 193
Приложение Б. Qt Designer: обзор и комментарии 203
Приложение В. Использование Qt Linguist 207
Приложение Г. Параметризация приложений с помощью QSA 211
Приложение Д. Обзор встроенной технологии Qtopia 217
Приложение Е. Программирование в среде Qt и традиция Unix 219
Приложение Ж. Лицензирование продуктов компании Trolltech
и правила использования 223
Предметный указатель 225
Содержание
Предисловие 11
Глава 1. Введение в Qt, или самое главное 27
1.1. Объектная модель и класс QObject 27
1.2. Обработка событий в Qt 27
1.3. Сигналы и слоты 30
1.3.1. Создание сигналов и слотов 31
1.3.2. Сигналы: практический пример 32
1.3.3. Сигналы и слоты: вопросы производительности 33
1.4. Свойства в Qt, краткое описание 34
1.4.1. Типы свойств на примере класса со свойствами 35
1.5. Объект QApplication и структура приложения 36
Глава 2. Базовые классы приложений 39
2.1. Строки и класс QString 39
2.1.1. Поиск и замена 41
2.1.2. Форматирование строк, предназначенных для вывода 4 2
2.1.3. Преобразование чисел и кодировок 43
2.1.4. Работа с внутренним представлением строк 44
2.2. Списки строк: класс QStringList и другие возможности 44
2.3. Регулярные выражения 46
2.3.1. Использование класса QRegExp 48
2.4. Коллекции вообще и Qlist-списки в частности 49
2.4.1. Итераторы 51
2.5. Другие, отличные от QList, коллекции 53
Глава 3. Общие принципы пользовательского интерфейса Qt 55
3.1. Класс QWidget 5 5
3.1.1. Менеджеры размещения, положение и размеры QWidget-объектов 55
3.1.2. Системы экранных координат 59
3.2. Рисование элемента управления 60
3.2.1. Инструменты и механизмы рисования 61
3.3. Цвета и палитры 64
3.4. Текст, шрифты и их начертания 66
3.5. Состояния элемента управления 70
3.6. События элемента управления 71
3.7. Фокус ввода и порядок элементов на форме 74
3.8. Реализация технологии drag-and-drop 76
3.9. Окна верхнего уровня 79
3.10. Общие визуальные характеристики интерфейса: QStyle 81
3.11. Создание собственных элементов управления 83
Глава 4. Базовые элементы управления 85
4.1. Класс QAbstractButton и производные компоненты 85
4.1.1. Классы QCheckBox, QPushButton, QRadioButton, QToolButton 86
4.1.2. Немного “практических” кнопок и групп 88
4.2. Ввод и отображение текста: классы QLabel, QLineEdit, QTextEdit 90
4.3. Команды класса QAction, меню и панели инструментов 96
4.4. Классы Q AbstractSlider, QAbstractSpinBox и их производные 99
4.5. Страничные отображения с закладками 102
4.6. Стандартные и пользовательские диалоги 103
4.7. Оболочка “модель-представление”, класс QAbstractltemView
и его подклассы 107
4.7.1. Классы QListView и QListWidget 109
4.7.2. Класс QComboBox 110
4.7.3. Классы QTableView и QTableWidget 110
4.7.4. Классы QTreeView и QTreeWidget 112
4.7.5. Список файлов и каталогов типа QDirModel 113
4.7.6. Последние замечания по теме “модель-представление” 114
Глава 5. Двухмерная и трехмерная графика 117
5.1. Модуль QCanvas: 2П-графика 117
5.1.1. Работа с поверхностями рисования типа QCanvas 118
5.1.2. Представления типа QCanvasView и дополнительные классы 122
5.2. Класс QCanvasItem: абстрактный примитив рисования 125
5.2.1. Предопределенные графические примитивы 128
5.3. Модуль OpenGL и трехмерная графика 133
Глава 6. Мультимедийные возможности Qt 139
6.1. Графика в Qt 140
6.2. Звук 142
6.3. Анимация 142
Глава 7. Устройства ввода-вывода: класс QIODevice 145
7.1. Реальные экземпляры класса QIODevice 147
7.2. Специфика файловых систем: классы QFilelnfo и QDir 148
7.3. Потоковые классы, связанные с устройствами 149
Глава 8. Модуль Network и сетевые возможности 151
8.1. Класс QAbstractSocket и его подклассы 152
8.2. Серверы, основанные на классе QTcpServer 153
8.3. Класс QHttp и связанные с ним классы 155
8.4. Доступ к файловым архивам с помощью класса QFtp 156
8.5. Дополнительные сетевые классы 157
Глава 9. Базы данных и модуль SQL 159
9.1. Классы физического уровня: QSqlDriver и другие 159
9.2. Класс QSqlDatabase: подключение к базам данных 161
9.3. Метаданные: классы QSqlRecord и QSqlField 163
9.4. Класс QSqlQuery: выдача запросов и получение результатов1 164
9.5. Механизмы “модель-представление” применительно к базам данных 167
Глава 10. Модуль XML 171
10.1. Классы, реализующие SAX2 171
10.2. Реализация объектной модели D0M 174
Глава 11. Многопоточное программирование в Qt 179
11.1. Объекты синхронизации 182'
Глава 12. Программирование для различных платформ 185
12.1. Программирование в среде ActiveX: создание сервера 185
12.2. Создание клиентских приложений ActiveX 187
12.3. Взаимодействие с Motif 189
12.4. Создание плагинов в Qt 189
Приложение А. Компиляция и сборка проектов Qt 193
Приложение Б. Qt Designer: обзор и комментарии 203
Приложение В. Использование Qt Linguist 207
Приложение Г. Параметризация приложений с помощью QSA 211
Приложение Д. Обзор встроенной технологии Qtopia. 217
Приложение Е. Программирование в среде Qt и традиция Unix 219
Приложение Ж. Лицензирование продуктов компании Trolltech
и правила использования 223
Предметный указатель 225
Предисловие
Qt 4: замечания последней минуты
Выход этой книги приурочен к появлению официального релиза Qt 4.0.0, и уже
подготовленная информация была мгновенно адаптирована к новой версии. Весь
материал отражает только последние данные, хотя для пользователей предыдущих
версий, в частности Qt 3, везде есть соответствующие ссылки. Прошу относиться
снисходительно к возможным неточностям, поскольку моим главным приоритетом
была актуальность.
Автор
Время открытых систем пришло!
Сложно сказать, когда возник феномен “открытых систем” — если под откры-
тыми системами подразумевать свободное обсуждение предметной области. Воз-
можно, сама идея связана с возникновением научного метода, когда утвердилась
практика свободного обмена информацией в целях установления истины. Впрочем,
еще раньше возникла идея защиты и коммерческого использования информации.
По крайней мере, первые компьютеры не были ни открытыми, ни даже доступ-
ными для широких масс. История создания вычислительной техники связана с
глобальными проблемами, военными ведомствами, крупным бизнесом и спецзака-
зом, что не способствовало открытости. Конечно, в те времена тоже был определен-
ный круг посвященных, и даже существует мнение, что именно тогда и был
“золотой век” вычислений, но факт остается фактом — только немногие могли по-
лучить доступ к полной и достоверной информации об архитектуре вычислитель-
ных систем и методах создания приложений.
Ситуация изменилась с возникновением системы Unix. Парадокс заключался в
том, что компания AT&T, где была разработана система, в силу антимонопольного
законодательства, не могла производить операционные системы, поэтому первые
версии бесплатно распространялись в университетах и научных лабораториях.
Условно Unix можно считать первой открытой системой, хотя долгое время она
существовала только как поле для экспериментов ограниченной группы из Bell
Labs. Для действительно свободного распространения идей и приложений (в обоих
направлениях) не хватало важных составляющих: дешевых и массовых компьюте-
ров, а также Большой Сети как носителя информации. Первоначально Unix была
открытой в смысле кода, но не в смысле настоящей дискуссии идей. Затем после-
довала эра коммерциализации Unix, порождения различных версий, клонов и за-
вуалированных переделок, в результате чего общая картина Unix стала выглядеть
как вавилонское столпотворение. Это тоже можно назвать “войной идей” —
но, в основном, это было войной денег, адвокатов и корпоративных амбиций: мно-
гие хотели заработать на открытом коде. Где они сейчас? Скажем так: в разных
местах, в основном, снова пытаются впрыгнуть в уходящий трамвай, на этот раз
Linux. На этом фоне независимая инициатива и настойчивость Билла Гейтса вы-
глядят даже своего рода благородно.
Продукты Microsoft тоже сделали свой вклад в открытые системы, и, возможно,
больший, чем кажется: MS DOS, а после и MS Windows 3.11 внесли элемент массо-
вости в само движение “компьюнити”, благодаря чему развилась отдельная
субкультура программистов-любителей. К тому же часто интересно наблюдать аль-
тернативные подходы Microsoft, к, казалось бы, уже давно решенным пробле-
мам — это привносит дух соперничества и эффект “игр с Большим Братом”. Нако-
нец, по сравнению с тогдашними расценками на другие системы, стоимость DOS и
Windows на Момент выхода была чисто символической: в сочетании с доступными
персональными компьютерами это породило волну домашних хакеров (некоторые
к тому времени уже успели взломать первые игровые приставки Commodore и Atari),
что до этого было невозможном. Наконец-то кодированием, в том числе и систем-
ным, мог заниматься каждый желающий: я вспоминаю первую программу, взло-
манную утилитой debug, и признаюсь, что это был один из самых запоминающих-
ся моментов в моей жизни. Впоследствии Linux много унаследовала ,от этого
“персонального” стиля, в том числе ориентацию на доступную платформу IBM PC.
Сегодня система Windows тоже отчасти интересна, но времена меняются, и то,
что раньше было секретом за семью печатями, а именно построение операционных
систем, сегодня является общедоступной информацией. “Раскол” начался, факти-
чески, с BSD1, которая стала первой, официально некоммерческой, операционной
UNIX-системой. Чуть позже появилось понятие открытых систем — и это поло-
жило неотвратимый конец всем остальным системам. Конечно, у них еще есть вре-
мя стать открытыми или, по крайней мере, поделиться идеями.
Говоря об открытых системах, нельзя переоценить значение Linux, но здесь
нужно внести ясность. Многие считают, что Linux является прямым потомком
Unix — и это вводит путаницу. Да, действительно, многие базовые принципы были
позаимствованы из этой системы. Но также многое было позаимствовано из других
систем, в том числе — из MS Windows. Ядро Linux вообще не имеет прямых анало-
гов, в то время как программные интерфейсы соответствуют спецификациям
POSIX с некоторыми расширениями. Ядро Linux может поддерживать почти все из
известных файловых систем. То же относится и к протоколам, интерфейсам и т.д.
Вообще, в силу известного “правила”, если есть какая-то технология, то она либо
реализована под Linux, либо кто-то сейчас этим занимается. Открытые системы
предполагают свободный обмен идеями, в том числе открытыми стандартами, до-
кументами'и кодом (что также является разновидностью документов). *
BSD (Berkeley Software Distribution) — программное изделие Калифорнийского университета,
адаптированная для Internet реализация операционной системы Unix с комплектом ее утилит,
разрабатываемых и распространяемых университетом. — Примеч.ред.
На самом деле более интересен не “диалог” Windows-Linux, поскольку это, в ос-
новном, пока системы для различных рынков. Приятно наблюдать открытость как
раз тех “военных” систем, которые долгое время стояли на страже “серьезного”
компьютинга. Под влиянием открытых систем значительно изменили (или меня-
ют) свой статус такие “черные ящики”, как Sun Solaris, QNX или HP-UX. Значи-
тельную активность в этом же секторе проявляют и Intel, IBM, Oracle и Silicon
Graphics. Суперкорпорации спешат получить выгоду от быстрого и бесплатного ау-
дита кода, который предоставляет сетевое сообщество Open Source. В результате
новые технологии отрабатываются прямо на глазах у заинтересованных сторон, на
что раньше уходили, без преувеличения, десятилетия.
Нельзя сказать, что Microsoft не заимствует открытых идей; при изучении во-
проса можно убедиться, что 90% идей и технологий MS Windows позаимствованы в
открытых источниках — от таких базовых вещей, как протокол IP или Internet
Explorer, и до сотен деталей интерфейса, баз данных, алгоритмов и т.д. “Гений”
Билла Гейтса заключается как раз в игнорировании этого явного факта.
Главная проблема сегодняшнего дня — это отсутствие у разработчиков культу-
ры программирования под Linux, слабое понимание важности создания совмести-
мого кода с помощью универсальных инструментов. Люди с трудом представляют,
что несколько открытых интерфейсов значительно мощнее и проще, чем многочис-
ленные интерфейсы WinAPI или “тулбоксы” MacOS. Притом не только мощнее и
проще, но и дешевле — ведь за использование WinAPI нужно платить на каждом
этапе, от самого Visual Studio до системного и прикладного ПО клиентских машин.
Но деньги — не единственный фактор. Создание совместимых, или, как сейчас
модно говорить, консистентных, программ уже само по себе является достаточной
ценностью — это признак зрелости разработчика, лояльности к другим и уважения
интересов пользователя. Программы, созданные по принципу “потому что VBA
(Visual Basic for Applications) сегодня удобнее”, лишены этих качеств (хотя простое
требование “лицензионного офиса” сразу бы поставило все на свои места: VBA ока-
зался бы дорогим и не таким уж удобным, не говоря о его недостатках). Сколько
миллионов человеко-лет тратится на переход от пакета VBA на что-то более стоя-
щее, думаю, не знает никто — но даже по своему скромному опыту могу предполо-
жить, что большая половина офисов всего мира уже раскаиваются в своей доверчи-
вости.
Хорошая новость: многие инструменты и библиотеки из мира Open Source2,
примыкающие на различных условиях к этой категории программных продуктов,
переносимы на многие платформы, в том числе на MacOS X и MS Windows. Пред-
ставьте себе, как было бы прекрасно, если бы тот же набор бухгалтерских инстру-
ментов “1С Бухгалтерия” и еще многие и многие другие программы (например,
Adobe Photoshop или Macromedia Flash) были бы изначально написаны с расчетом
на многоплатформенность с использованием той же библиотеки Qt, а в качестве
хранилищ данных предполагалось бы задействовать, например, MySQL. Безуслов-
но, эти инструменты также созданы коммерческими компаниями, и их коммерче-
ское использование (точнее — использование без обнародования исходного кода;
Имеются в виду программные продукты с открытым исходным текстом. — Примеч.ред.
согласно GPL3, коммерческое использование программ с открытым кодом не требу-
ет лицензирования используемых библиотек и средств разработки) повлекло бы
лицензионные отчисления — но эти отчисления на пару порядков ниже, чем при
использовании лицензионного ПО от Microsoft. Надежность же таких программ,
благодаря публичному тестированию, велика даже по сравнению с известными
продуктами. Другое дело, что у нас по-прежнему предпочитают скорее пиратские
копии Windows, чем условия “jewel box”4 — но это вопросы экономические и поли-
тические, которые мы не будем здесь обсуждать. А может быть, мы уже живем в
том светлом будущем, когда Microsoft откроет код и позволит загружать, распро-
странять и модифицировать Windows бесплатно? Это было бы разумно, удобно и
хорошо — и кто знает, возможно, наступление открытых систем поможет сделать
это реальностью.
А пока те меры, которые принимает Microsoft, в частности в Windows ХР SP2,
для защиты от взлома собственного ПО и ограничения прав пользователей, могут
серьезно повлиять на настроение любителей нового “софта” от Microsoft. Атмосфе-
ра дискомфорта при взломе очередной версии MS Windows должна оградить мно-
гих пользователей от этой системы — купите Windows один раз за полную цену, и
вы поймете, что переплатили. Еще большим шоком будет покупка MS Office —
проделайте мысленно такую сделку, и Visual Basic уже не будет казаться вам таким
привлекательным.
Кроме стоимости, важен еще и факт, что Windows уже проигрывает почти по
всем параметрам Linux. Существует, конечно, такая область, как компьютерные
игры, — действительно, для многих пользователей (и для меня в том числе) это по-
ка является преградой для полного удаления разделов FAT/NTFS со своих.жестких
дисков. Но, возможно, ситуация и здесь тоже скоро изменится.
В остальных же областях (таких, как пользовательский интерфейс, графика и
возможности мультимедиа) Linux уже сравнялась практически с любой версией
Windows или даже превосходит ее. Исправлена ситуация с драйверами — напри-
мер^ дистрибутивы Fedora Core 4 или Ubuntu легко поднимают всю периферию
моего компьютера, в то время как Windows ХР SP2 без диска с драйверами не на-
шла почти ничего “особенного”. Что, возможно, важнее — сами производители
устройств не без гордости отмечают поддержку Linux как стандартную опцию (это
уже не говоря о серверных возможностях и безопасности любой системы семейства
*nix).
Сегодня работать под Linux уже становится не только выгоднее, но и удобнее.
Задача разработчиков и менеджеров IT-департаментов — разобраться в выгодах и
применять на практике открытые технологии (рис. 1). Разумеется, любая техноло-
гия служит конечным пользователям, и как раз библиотека Qt — прямой путь к их
сердцам. Простая в освоении и использовании, хорошо документированная и
3 GPL (General Public License) — общедоступная лицензия, т.е. право на получение и свободное
распространение программного обеспечения и исходных файлов за право распространения на тех
же условиях модификаций этого программного обеспечения. — Примеч.ред.
4 Jewel box — с англ, футляр компакт-диска (имеется в виду покупка на условиях минимальной
комплектации). — Примеч.ред.
компании Trolltech — основа популярной графической оболочки KDE (сокращение
от “К Desktop Enviroment” — графический пользовательский интерфейс фирмы
Corel) и еще нескольких сотен популярных приложений. В дополнение ко всему,
эта библиотека так же хорошо интегрируется с MS Windows и MacOS X, как и с
Linux или FreeBSD. Вам больше не нужно выбирать операционную систему для
своих приложений — просто не думайте об этом.
Рис. Z. Что отличает настоящее приложение Open Source? Разумеется,
кнопка Show Source (Показать исходный код) на главной форме
По духу, мощности, удобству и оптимальности кода библиотека и инструменты
Qt похожи на Borland Delphi — с поправками на использование в случае Qt языка
C++ вместо Object Pascal, а также на работу Qt на различных платформах. По мере
того как вы будете изучать все большее число классов Qt, вы откроете для себя пре-
красную, стройную архитектуру этой библиотеки. Что важно — чем больше вы бу-
дете знать о Qt, тем проще вам будет казаться программирование в этой среде.
В любом случае вам будет доступен исходный код самой библиотеки и всех инстру-
ментов разработки, так что на любой вопрос из серии “как, в точности, это работа-
ет” вы сможете получить исчерпывающий ответ.
И, я надеюсь, даже если жизнь заставляет вас работать и кодировать под
Windows, вы все равно сможете сделать это с расчетом на будущее и будете исполь-
зовать Qt. Это скоро окупит себя — благодаря быстрому коду, отличному интер-
фейсу и мощным возможностям, а также средствам, повышающим скорость разра-
ботки. А главное — ваши приложения будут жить еще долго после того, как MS
Windows уже станет преданием. Не исключено, мы еще станем свидетелями этого
знаменательногасобытия.
Арсений Чеботарев, научный редактор и штатный автор журнала
“Компьютеры+Программы” (PC World Ukraine), автор журналов “Сети и Комму-
никации” и “Шпиль”, а также газеты “Computer World Ukraine”. В активе: 19 лет
компьютерной практики, от оператора СМ2 и ЕС 1055 до... собственно, до автора
этой книги, включая многие годы программирования и системного администриро-
вания.
Вступление или вопросы, требующие ответа
Для кого написана эта книга
Эта книга написана для программистов, предпочитающих C++ всем остальным
языкам программирования и желающих создавать производительные приложения
для Linux, переносимые, но не в последнюю очередь, на другие платформы. Выбор
вами как программистом именно библиотеки Qt может быть обусловлен несколь-
кими причинами.
Если вы программировали в конце восьмидесятых-начале девяностых под MS
DOS на Turbo Pascal или Turbo С и впоследствии утратили нить в освоении новых
системных интерфейсов, то, возможно, вам будет лучше использовать Qt, чем пы-
таться угнаться за другими технологиями. С помощью Qt совершенно не обяза-
тельно осваивать системные вызовы. Вы можете программировать по-прежнему,
при этом создавая графические приложения, задействовать сетевые интерфейсы и
протоколы, работать с трехмерной графикой и базами данных. Для этого придется
всего лишь освоить несколько (ладно, несколько десятков) новых классов, и ника-
ких системных вызовов. Системные вызовы — против правил Qt, можете забыть о
них надолго (или даже навсегда).
Еще один вариант— это переход на новую операционную систему, в частно-
сти — Linux. Для группы разработчиков, “застрявших” в Visual Basic, эта пробле-
ма может представлять непреодолимый рубикон. Большое количество приложе-
ний, существующих под Microsoft Windows, необходимо или желательно
“портировать” в среду Linux (точнее — то и другое, особенно это касается популяр-
ных компьютерных игр), поскольку эта система стремительно набирает силу, осо-
бенно в области производства, но также давно проникла во многие научные лабора-
тории и кампусы, откуда Linux, собственно, и родом, и даже во многие офисы.
Например, в издательстве, в котором я работаю, около 50% сотрудников рабо-
тают на Х-терминалах с загрузкой по сети. В такой схеме, при гарантированной
системе доступа и резервирования данных, вы практически лишены проблем с
винчестером и другими физическими носителями. Любой сотрудник может поста-
вить свою СП-“болванку” в очередь на прожиг на разделяемом “райтере” и в разум-
ное время, около получаса, получить результат. Аналогично в разумных пределах
выполняются и другие операции, вроде печати на дорогостоящих цветных лазер-
ных принтерах.
Это тенденция, которую нельзя игнорировать. С другой стороны, многие про-
фессиональные приложения созданы в Unix, Linux или FreeBSD — их доступность
для платформы Windows также может принести выгоду создателям.
Еще одна категория потенциальных пользователей Qt — начинающие програм-
мисты, желающие получить как можно более прочный фундамент для будущей
карьеры и профессионального роста. Знание Qt является абсолютной ценностью,
как в академическом мире, так и в промышленности или офисном программирова-
нии. Вы сможете применить свои знания в любой сфере и в любой стране, особенно
там, где сильна субкультура UNIX и Linux — страны Прибалтики, Голландия,
Финляндия, Швеция, Германия, Корея, Китай, США, а в последнее время —
и Россия. Для наших разработчиков быть в авангарде открытых систем — вообще,
редкая, если не единственная возможность заявить о себе в мире программного
обеспечения.
Наконец, эта книга может стать побудительным мотивом для менеджеров,
руководителей программных проектов и руководства софтверных компаний.
Возможно, ваш проект еще не поздно поставить на рельсы Qt, чтобы в дальней-
шем застраховаться от проблем перехода на новую платформу? Или вы чувствуе-
те пристрастие к стремительно быстрым и небольшим программам? Возможно,
вы захотите использовать существующий потенциал опытных разработчиков и
софтвер-архитекторов из старой Unix-гвардии? В таком случае использование
Qt может сэкономить немало времени и средств для получения отличных резуль-
татов.
Что необходимо знать до того, как вы начнете читать эту книгу
Знание С+4- в той или иной мере обязательно. Если вы испытываете проблемы в
понимании классов и объектов — потратьте некоторое время и изучите одну из
фундаментальных книг по этому прекрасному языку программирования (эту фразу
я встречал минимум 20 раз в различных книгах, и было бы невежливо нарушать
традицию).
Также будет нелишним знание устройства и архитектуры операционных сис-
тем, в частности Linux и MS Windows. В частности, вам придется ориентироваться
в устройстве файловой системы, основах сокетов Беркли, графических элементах
управления, и, возможно, пригодятся базовые знания в области баз данных. Есте-
ственно, что отдельное приложение не должно использовать все возможности Qt —
но ведь вы хотите стать универсальным программистом, не так ли? С другой сторо-
ны, поскольку библиотека Qt в значительной степени не зависит от операционной
системы, то глубокие знания в системных API не являются обязательными. Плюс
заключается в том, что зная системные вызовы, вы сможете приблизительно оце-
нить производительность и эффективность различных операций.
Наконец, это знание технологии программирования. В частности, вы должны
понимать принципы сборки программ, их версий, значение'компилятора, сборщи-
ка и отладчика, уметь работать с командной строкой и писать несложные команд-
ные файлы. Разумеется, вы всегда можете “спрятаться” от проблем за некоторой
оболочкой вроде Visual Studio, но это будет только временное и ненадежное решение
проблемы. Кроме прочего, Visual Studio не доступна на платформах, отличных от
Windows, так что лучше заранее отказаться от применения таких “добрых” по-
мощников или, по крайней мере, настроить среду для компиляции проекта в па-
кетном режиме.
Что такое, собственно, Qt
На этот вопрос существует два ответа: короткий объясняет происхождение аб-
бревиатуры Qt как сокращения от Quasar Technologies. Это имя некоторое время
носила голландская компания Trolltech сразу после своего создания в 1994 году.
Если говорить о Qt как о продукте — то это универсальная всеобъемлющая биб-
лиотека, или программная оболочка (framework), для создания универсальных,
переносимых приложений на языке C++. Переносимость включает использование
исходного кода для получения приложений, работающих на платформах MS
Windows, Unix, Linux, MacOS X и Embedded Linux. Нужно понимать, что, в отли-
чие от Java, вы должны скомпилировать тексты программы для каждой из плат-
форм, чтобы получить выполняемый файл нужного формата.
Насколько распространено использование Qt
Библиотека Qt распространена даже в большей степени, чем вы могли ожидать!
Основа такой популярности — двойная лицензия на библиотеку Qt, предусматри-
вающая возможность как коммерческого, так и некоммерческого использования в
проектах Open Source. В результате Qt стала основой KDE, самой популярной на се-
годня графической оболочки для Linux и FreeBSD. Насколько распространена обо-
лочка KDE, надеюсь, объяснять не нужно, хотя приверженцы GNOME (GNU5
Network Object Model Environment) могут с этим не согласиться, правда, без особого
успеха (рис. 2).
В числе коммерческих партнеров Trolltech, использующих Qt в качестве инст-
рументальной среды, можно сразу же привести таких авторитетов в мире бизнеса,
как IBM, Siemens, Sharp, Adobe, Bosh, Boeing, Scania, NEC, HP, NASA, и еще более
четырех тысяч не вошедших в этот список компаний из более чем шестидесяти
стран. Версия Qt Embedded и построенная на ее основе оболочка Qtopia в данный
момент активно продвигаются на рынок мобильных терминалов и PDA6.
Какая версия Qt является текущей
На момент начала написания этой книги рабочей являлась версия 3.3.3-2. После
появления на сайте Trolltech окончательной версии 4.0 книга была проверена и от-
редактирована для отражения изменений, которых оказалось достаточно много, и
многие из них достаточно серьезны. Первое впечатление от нововведений — поло-
жительное, хотя пользователям версии 3.3.3 придется как следует переучиваться.
Эта книга как раз и поможет сделать это в минимальные сроки.
5 GNU — рекурсивное сокращение от англ. “GNU is Not Unix”; имеется в виду проект, предусмат-
ривающий свободное распространение программного обеспечения. — Примеч.ред.
6 PDA (Personal Digital Assistant) —- “карманный” компьютер, предназначенный для выполнения
некоторых специальных функций . — Примеч.ред.
Рис. 2. Хотя оболочка KDE написана на основе Qt, это не значит,
что Qt не работает под управлением других оконных менеджеров
В чем отличие Qt от Java
Действительно, говоря о сфере применения и целевых приложениях, библиоте-
ка Qt достаточно похожа на Java и является мостом между различными платфор-
мами. Различие в среде времени выполнения не так уж существенно: JRE (Java
Runtime Engine) — тоже не более чем библиотека позднего связывания. Ко всему,
наличие JIT-компилятора позволяет Java-приложениям реально конкурировать по
производительности с C++.
Так что различие скорее в философии: Java предлагает экстенсивный подход,
постоянно расширяемое дерево классов, предлагая многочисленные и не всегда со-
вместимые расширения — сразу же всплывает в памяти сосуществование двух гра-
фических библиотек, AWE и Swing.
Библиотека Qt — скорее, интенсивный продукт; иерархия классов и сферы
применения стабильны, расширения происходят как можно реже на основе накоп-
ленных исправлений и предложений пользователей. Основной акцент делается на
повышении надежности и производительности существующего кода, а также более
удобных инструментов разработки. Кроме прочего, это гарантирует надежность
инвестиций в продукты, основанные HaQt.
Специалисты также выигрывают от такой стабильности: вы всегда сможете ох-
ватить Qt полностью и следить за всеми исправлениями и дополнениями. Для язы-
ка Java, как технологии, на современном этапе это вряд ли возможно.
В чем отличие Qt от Gtk
Действительно, многие, особенно любители сетевых дискуссий, часто поднима-
ют вопрос “Qt vs7 Gtk”. Обе системы обладают приблизительно одинаковой мощно-
стью и переносимостью. И у обеих систем достаточно адвокатов.
Gtk расшифровывается как GIMP Toolkit. Аббревиатура GIMP, в свою очередь,
представляет собой сокращение от GNU Image Manipulation Program, т.е. программа,
манипулирующая изображениями (подобная Photoshop). Конечно, впоследствии
система Gtk приобрела свойства, далеко простирающиеся за обработку изображе-
ний (модуль, называемый GDK, GIMP Drawing Kit). В частности, были реализова-
ны такие системные функции, как динамическая загрузка модулей, не зависимое
от платформы многопоточное выполнение и глобальный цикл сообщений. Больше
всего это напоминает реализацию MS Windows 3.11 поверх X-Windows, т.е.
“операционную систему”, лишенную файловой системы, доступа к устройствам и
сетевых соединений.
К преимуществам Gtk следует отнести огромное количество “биндов” (при-
вязок) к различным языкам программирования. Фактически, многие “малые”
языки ставят своей первоочередной целью “бинд” к цакету Gtk, чтобы заполнить
вакуум в собственных библиотеках. На сегодня Gtk-инструментарий “переведен”
на 28 языков программирования.
Кроме того, пакет Gtk имеет некоторое преимущество в плане “действительно
открытого способа разработки” — если для вас это является преимуществом. С дру-
гой стороны, Gtk не использует классы C++, а вместо этого, как и, например,
Win API, реализует классы и систему сигналов вручную, поверх стандартного С.
По моему скромному мнению, программы и проекты, избегающие единого ком-
мерческого начала и планомерного метода, страдают (и будут страдать!) синдромом
“общественного мнения”, поэтому лично мне ближе Qt. Хотя вам ничего не мешает
использовать обе системы одновременно.
В чем отличие Qt от Tk/Tcl
Язык Тс1, с расширением для построения пользовательского интерфейса, назы-
ваемым Тк (можно рассматривать Тк и как отдельный продукт, поскольку он ино-
гда встречается без Тс1), является популярным средством для создания “фронт-
эндов” приложений, т.е. той части приложения, которая отвечает за взаимодейст-
вие с пользователем. Сам язык Тс1 был задуман в качестве “клея” для приложений
командной строки, с тем чтобы дополнить их графическим интерфейсом. Так что
если ваше приложение уже существует в виде консольного приложения, то Tk/Tcl
может оказаться лучшим способом “оживить” его пользовательский интерфейс.
В последние годы, однако, появилась тенденция к расширению Тс1 до уровня
универсального языка программирования, с тем чтобы полностью писать на нем
свои приложения. Уже существуют приложения, содержащие тысячи строк кода
на Тс1 и выполняющие такие существенные операции, как доступ к сетевым ресур-
сам, базам данных и, конечно, взаимодействие с пользователем. Это стало возможно
благодаря значительно возросшей мощности персональных компьютеров, а также с
7 vs — от лат . “versus”, означающего “против”. — Примеч.ред.
тем, что приложения все больше находятся в состоянии ожидания или ввода поль-
зователя, или ответа от сервера.
И все же Тс1 имеет ограниченную область применения, связанную с производи-
тельностью. В том числе этот язык не подходит для вычислительных задач, таких
как обработка видео в реальном времени или расчет трехмерных сцен. Также не
подойдет он и для системного программирования. Не говоря уже о том, что интер-
претируемый открытый текст нелегко оформить и защитить в качестве продукта.
С другой стороны, приложения Qt имеют производительность, сравнимую с
производительностью оптимизированного ассемблерного кода, и способны на 100%
(в рамках, отведенных операционной системой) занять процессор полезной нагруз-
кой. Также, работая в Qt, вы можете полностью сконцентрироваться на одном язы-
ке программирования, C++, в то время как приложения Тс1, как уже было сказано,
часто являются “сборными” из написанных на С консольных приложений, “фронт-
энда” на Тк и приложений Тс1, собирающих все вместе. В результате проектирова-
ние и поддержка приложений Тс1 обычно требуют больше усилий, и результаты
редко выглядят как коммерческие продукты.
Можно ли использовать Qt параллельно с системными вызовами
Безусловно, хотя лучше, пока существует такая возможность, избегать этого.
Классы Qt созданы специально для того, чтобы скрыть от программиста системно-
зависимые участки кода и таким образом сделать ваш проект переносимым между
платформами. Применяя системные вызовы, вы теряете переносимость кода —
к чему тогда использование Qt?
Иногда, однако, системные вызовы — это единственная возможность достичь
нужного результата. В таком случае вы обязательно должны так построить свой
проект, чтобы системные вызовы были компактно представлены в отдельных мо-
дулях, хорошо задокументированы и их вызов был инкапсулирован в соответст-
вующие классы.
С другой стороны, вы можете свободно использовать библиотеки, в переносимо-
сти которых уверены (например, libc или ATL).
Как читать эту книгу
Книга предназначена для последовательного прочтения, от начала и до конца.
По крайней мере вначале прочтите первые главы, посвященные особенностям объ-
ектной модели Qt и классу QWidget. После освоения базиса любая интересная вам
глава будет вполне доступной для понимания.
Перед запуском примеров я бы попросил вас хотя бы кратко ознакомиться с
приложением А, в котором рассказывается о специальном препроцессоре qmake,
обеспечивающем переносимость не только кода, но и процедуры сборки между раз-
личными платформами. Таким образом вы с самого начала сможете строить проек-
ты в правильном ключе, по ходу практикуясь в синтаксисе qmake.
После того как вы в достаточной мере ознакомитесь с элементами управления и
сможете бегло оперировать их свойствами и методами — вы, вероятно, сочтете бо-
лее удобным пользоваться средством Qt Designer, описанным в приложении Б.
Я предостерегаю начинающих разработчиков от использования этого инструмента
до тех пор, пока не сможете уверенно ориентироваться в сгенерированном им коде.
Чем эта книга отличается от документации
Я не хотел бы следовать авторам, которые вместо достоверных фактов и базовых
данных о рассматриваемом предмете подают собственные домыслы. И хотя нет
объективной точки зрения, но к этому все-таки нужно стремиться. В конце концов
мы рассматриваем предметы антропогенного происхождения, так что было бы по
меньшей мере невежливо игнорировать послания “богов” созданного ими мира.
Эта книга в значительной мере опирается на документацию и является ее сво-
бодным изложением — и я горжусь этим. Многие примеры взяты прямо из доку-
ментации, чтобы вы могли прямо их компилировать или копировать из примеров в
свои программы, не набирая новый текст в редакторе. Кроме прочего, я, как уже
было сказано, рассчитываю на подготовленного читателя, который не нуждается в
мотивации вроде “как много и быстро вы заработаете, используя данную техноло-
гию”.
С другой стороны, эта книга значительно сэкономит ваше время, сконцентриро-
вав ваше внимание на более важных вопросах программирования и почти проигно-
рировав те, которые не относятся к первоочередным или являются очевидными.
Я нарочно не обращаю ваше внимание на простые вопросы вроде использования
графических инструментов разработки для получения “быстрых” приложений, а
также на такие изощренные вопросы, как, к примеру, создание собственных гра-
фических стилей, в чем должны быть скорее задействованы дизайнеры, чем про-
граммисты.
Поскольку я не планирую оставлять в покое Qt до конца своих дней (и я, и Qt,
пожалуй, протянем еще немало тысяч дней), то, возможно, в следующих изданиях
появится отдельная вступительная часть, описывающая создание простого прило-
жения в стиле VBA, после чего последует настоящий материал, а также увеличится
количество приложений, освещающих отдельные темы.
Чем эта книга отличается от других книг
Мне часто встречались компьютерные книги нескольких типов. Самыми тяже-
лыми из них, для моего восприятия, являются необъятные “библии”, которые на-
полнены предложениями типа “нажмите на клавиатуре комбинацию клавиш
<Alt+F> и в появившемся меню выберите команду Save”. Эта книга имеет мало
общего с “библиями”, отчасти потому, что Qt — библиотека, а не приложение или
среда разработки, и я просто лишен радости написать “нажмите клавишу <F1> для
получения подсказки” (впрочем, один раз уже написал).
Еще одна распространенная категория литературы — наборы примеров,
“поваренные книги” (cookbooks). В таких книгах вы можете на шестой странице
узнать, как записать число римскими цифрами и в транскрипции на фарси, но
тщетно будете искать азы техники программирования и основы данной иерархии
классов. Эта книга — не набор рецептов, материал изложен полно и методически,
но это не сборник решений. Кстати, возможно, к моему стыду, я даже не знаю всех
римских цифр, не говоря уже о фарси.
Также очень популярна в определенных кругах серия “для чайников”. Хотя
упомянутые книги порой остроумны, все же моя книга предназначена не для чай-
ников, а скорее для обычных людей. Как уже было сказано, эта книга для тех, кто
владеет или, по крайней мере, хотел бы овладеть языком C++ в совершенстве. Эти
люди составляют элиту программистского сообщества и ни в коей мере не являются
“чайниками”. Я бы с удовольствием разместил здесь, как это принято в некоторых
книгах “для чайников”, рисунки моей старшей дочери, но, боюсь, меня не поймут.
Кроме прочего, я не включил в эту книгу привычные из американских перево-
дов разделы “о чем рассказывается в этой главе”. По-моему, это подсказка пользо-
вателю, какие из глав можно пропустить. Я нарочно не писал тех глав, которые вы
должны пропустить. Как хороший современный компилятор, я исключил их еще
на этапе компиляции — остальные написаны для вашего чтения.
В конце концов, это не справочник с характерными для него таблицами клас-
сов, свойств и функций-членов. Хотя тут описано большинство самых важных из
более четырех сот классов Qt, не рассчитывайте на полное описание методов или
списки свойств — для этого обращайтесь к документации.
Эта книга, скорее, представляет собой сборник конспектов по Qt или базовый
курс. Кстати, курс Qt мог бы украсить любую учебную программу, и я рассмотрю
любые предложения, если какой-нибудь из вузов решится на это. Главные вопро-
сы, в том числе построение программ и основные классы, такие как QWidget, рас-
сматриваются в самом начале, и им посвящено основное внимание. Другие классы
упоминаются без детального рассмотрения или же им вообще не уделено внимания.
К примеру, согласно алфавиту, класс QApplication находится далеко от начала
списка классов — но было бы большой ошибкой рассматривать его после QAccel.
Примеры в этой книге носят сугубо иллюстративный характер. В отличие от
книг, построенных вокруг одного большого проекта, каждый фрагмент демонстри-
рует только то, что должен демонстрировать. Можно сказать — в этой книге мало,
т.е. недостаточно примеров для неподготовленного читателя. Опять-таки, возмож-
но, следующее издание будет содержать большее количество авторских примеров.
Исходные тексты, по возможности, взяты из дистрибутива и “туториала” Qt,
чтобы вы могли не набирать весь исходный текст. Я еще раз повторяюсь — приме-
ры нарочно взяты из “туториала” Qt, чтобы вам было удобнее их компилировать, а
не потому, что мне было лень заменить одни фрагменты исходных текстов други-
ми. Обратите внимание: текст каждой программы-примера Qt начинается заголов-
ком, позволяющим использовать его для любых целей.
Благодарности и прочее
Должен признаться, что за последние пять лет я, в основном, променял ремесло
программиста на литературное поприще, поскольку вижу главную задачу не в соз-
дании отдельного приложения, а в популяризации и рекламе открытых систем.
Если вы относитесь к активным программистам на Qt, то прошу относиться снис-
ходительно к возможным ошибкам и опискам в данной книге.
В конце концов, эта книга полностью основана на интеллектуальной собствен-
ности Trolltech, документации и материалах сайта trolltech.com— и было бы
странно, если бы было иначе. Я выражаю свою признательность всем создателям
как самой библиотеки, так и сопровождающей ее документации за неоценимую
услугу как лично мне, так и всему человечеству.
От издательства “Диалектика”
Вы, читатель этой книги, и есть главный ее критик. Мы ценим ваше мнение
и хотим знать, что было сделано нами правильно, что можно было сделать лучше и
что еще вы хотели бы увидеть изданным нами. Нам интересны любые ваши заме-
чания в наш адрес.
Мы ждем ваших комментариев и надеемся на них. Вы можете прислать нам бу-
мажное или электронное письмо либо просто посетить наш Web-сервер и оставить
свои замечания там. Одним словом, любым удобным для вас способом дайте нам
знать, нравится ли вам эта книга, а также выскажите свое мнение о том, как сде-
лать наши книги более интересными для вас.
Отправляя письмо или сообщение, не забудьте указать название книги и ее ав-
торов, а также свой обратный адрес. Мы внимательно ознакомимся с вашим мне-
нием и обязательно учтем его при отборе и подготовке к изданию новых книг.
Наши электронные адреса:
E-mail: info@dialektika.com
WWW: http://www.dialektika.com
Наши почтовые адреса:
в России: 115419, Москва, а/я 783
в Украине: 03150, Киев, а/я 152
Глава 1
Введение в Qt, или самое главное
1.1. Объектная модель и класс QObject
В основе всей иерархии компонентов библиотеки Qt (рис. 1.1), как визуальных,
так и невизуальных, лежит класс QObject. Любой другой класс, который хочет за-
действовать механизмы Qt, должен происходить от этого класса.
На уровне QObject реализованы такие механизмы, как сигналы и слоты, свой-
ства объектов, механизм событий и их фильтров, механизм поддержки многоязы-
ковых приложений, система встроенных таймеров, система безопасных указателей
со встроенным счетчиком ссылок, а также система безопасного приведения типов.
Рассмотрим здесь самое важное из перечисленного, а другие вопросы (например,
возможности интернационализации и многоязыкового интерфейса) рассматрива-
ются далее в этой книге.
1.2. Обработка событий в Qt
Часто при написании интерактивного кода возникает необходимость в передаче
сообщений от одних объектов к другим. Например, кнопка может уведомлять при-
ложение о своем нажатии, поле ввода текста — об изменении содержимого, а ме-
ню — о выборе команды.
Система оповещения программы о внешнйх и внутренних событиях в Qt назы-
вается сигналами. С сигналами связываются функции-обработчики особого типа, в
Qt называемые слотами. И те, и другие реализованы как надстройка над стандарт-
ной объектной моделью C++ в виде макросов.
Сигналы являются естественным способом реакции на внешние события в среде
UNIX (BSD) и Linux, так что если вы программировали в этих средах, То, по всей
видимости, уже знакомы с сигналами. Для всех же остальных, в частности для
программистов в среде Windows, а также для тех, кто не сталкивался с асинхрон-
ным программированием, будет полезно более детальное изложение.
В основе асинхронного выполнения, как и многого другого, лежит понятие о
ссылке на функцию, через которую эта функция может быть вызвана. Такая ссыл-
ка может быть передана в другую подпрограмму в качестве параметра. Одним из
простых случаев такого поведения является библиотечная функция qsort, сорти-
рующая массив из элементов, задаваемых пользователем. Вот как происходит ее
вызов для сортировки массива строк (в данном случае параметров командной строки).
#include <stdlib.h>
#include <string.h>
int compare(const void *argl, const void *arg2) {
return -Stricmp(*(char* *)argl,*(char* *)arg2);
}
int main( int argc, char **argv ) {
argv++; argc--; J J пропускаем имя программы
qsort((void *)argv, (size_t)argc, sizeoffchar *), compare);
}
Как видите, мы передаем функцию как аргумент и “забываем” о ней, т.е. сами
никогда не вызываем ее явно. Такие функции называются функциями обратного
вызова (или callback-функциями), поскольку вызов нашей функции происходит из
библиотечного кода — в отличие от обычно происходящего порядка вещей, когда
наш код вызывает библиотечные или наши собственные подпрограммы.
Аналогичная техника применяется для передачи функций операционной систе-
ме. Например, для того чтобы в MS Windows получить список всех окон верхнего
уровня (здесь не рассматриваются виртуальные рабочие столы), достаточно создать
функцию обратного вызова, а затем указать ее в качестве параметра системной
функции EnumWindows.
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM XParam ) {
return TRUE;
}
BOOL enumResult=EnumWindows((WNDENUMPROC)&EnumWindowsProc, 0);
В данном случае функция обратного вызова EnumWindowsProc будет вызвана
для каждого окна верхнего уровня (включая скрытые — вы можете удивиться, но
таких, как правило, большинство) так же, как сортирующая функция будет вызы-
ваться для каждого попарного сравнения элементов сортируемого массива.
Важное отличие существует между вызовами qsort и EnumWindowsProc: в пер-
вом случае управление не покидает нашу программу, так как qsort хоть и являет-
ся библиотечной функцией, но выполняется в адресном пространстве нашего при-
ложения. Вызов же нашей функции из EnumWindows приводит к переключению
контекста и переходу в системный код и обратно, что приблизительно в 100 раз
медленнее.
Итак, обработка событий в Qt основывается на передаче указателя (пока будем
считать, что это так — на самом деле вместо указателя используется более сложная
сущность) на функцию обратного вызова. В отличие от системных сигналов UNIX,
вызовы сигналов Qt происходят внутри адресного пространства приложения, хотя
некоторые сигналы Qt порождаются системными сигналами (в MS Windows —
системными сообщениями).
Рис. 1.1. Иерархия основных классов Qt 3.3 (на момент написания данной книги иерархия Qt 4 еще не
была готова в окончательном виде)
1.3. Сигналы и слоты
Небольшое терминологическое и техническое отличие от классических функ-
ций обратного вызова — понятие слотов. Слот — это, фактически, та же функция-
член класса, объектный метод, но особого типа, что-то вроде объектного аналога
типа CALLBACK в MS Windows. Таким образом достигается важная цель — безопас-
ность типов, в чем не были сильны функции обратного вызова. При вызове обыч-
ной функции обратного вызова не существует никаких гарантий, что эта функция
воспринимает правильный список параметров. Слоты, как типизированные шаб-
лоны, решают эту проблему. Механизм реализации слотов, однако, не задействует
механизм шаблонов в терминах C++.
Еще одна важная проблема с функциями обратного вызова — сильная зависи-
мость сигналов и функций-обработчиков. Это сокращает возможности полиморф-
ности обработки: для каждого события применяется функция одного и только од-
ного типа. Слоты, в основном, снимают эту проблему: они очень мало зависят от
обрабатываемых сигналов. Конечно, для этого были тщательно проработаны пара-
метры вызова слотов: сигналы и слоты связываются во время выполнения на осно-
ве информации RTTI (Run Time Type Information — информация о типах в процес-
се выполнения).
Поскольку сигналы и слоты реализованы на самом нижнем уровне, любой ком-
понент, происходящий от класса QObject, может генерировать сигналы. Фактиче-
ски, сама генерация сигнала никак не связана с его обработкой — компонент не
должен зависеть от того, будут ли обрабатываться его сигналы. В том числе сигнал
может быть обработан не один, а несколько раз. С другой стороны, вы можете свя-
зать с одним слотом как угодно много сигналов (рис. 1.2). Например, меню,
“горячие клавиши” и панели управления обычно приводят к выполнению одних и
тех же команд. Более того, вы можете связать один сигнал напрямую с другим так,
чтобы вызвать их каскадное выполнение.
Существует определенное ограничение для слотов — они не могут иметь значе-
ний параметров по умолчанию. Поскольку слоты являются обычными функциями,
они также могут быть защищенными или частными (закрытыми), определенными
в разделах protected slots: или private slots: соответственно. Такие слоты
могут быть подключены только из иерархии классов-наследников или даже от-
дельного класса. Показания для применения таких слотов такие же, как и для
применения частных членов класса.
Объект создает сигналы для всех потенциально интересных событий. Если дру-
гой объект заинтересован только в одном сигнале — он регистрирует слот только на
одном сигнале. Потенциально можно зарегистрировать слоты на нескольких или
всех сигналах данного объекта. Если несколько слотов зарегистрировано на одном
сигнале, то порядок их выполнения считается неопределенным. По крайней мере,
вы в своих программах никогда не должны рассчитывать, что данный сигнал уже
обработан каким-либо слотом, поскольку это резко снижает надежность ваших ал-
горитмов.
Рис. 1.2. Приложение, полностью созданное в среде Qt Designer без
единственной строки кода: сигнал valueChanched (int) замкнут
на слот setvalue (int)
1.3.1. Создание сигналов и слотов
На уровне синтаксиса сигналы и слоты создаются специальными служебными
словами signals и slots. Для того чтобы эти инструкции “появились” в языке
C++, вы обязательно должны включить макрос Q_OBJECT в декларацию своего
класса. Препроцессор, транслирующий синтаксис сигналов и слотов в стандартный
код C++, называется тос. Вы можете явно выполнить его и проанализировать вы-
ходной поток для изучения реализации механизма сигналов.
Вот как выглядит классический пример класса, использующего сигналы и слоты.
class Foo : public QObject {
Q_OBJECT
public:
Foo();
int value() const { return val; }
public slots:
void setValue( int );
signals:
void valueChanged( int );
private:
int val;
};
Впоследствии вы создаете обработчик для слота, в котором (для наглядности)
мы генерируем также и наш сигнал.
void Foo::setValue( int v ) {
if ( v != val ) {
val = v;
valueChanged(v);
}
}
Еще одно новое служебное слово, emit, предназначено для явной активизации
сигнала (это похоже на генерацию программных исключений).
После такого определения легко связать два экземпляра. Допустим, мы хотим
синхронизировать два объекта так, чтобы их значения синхронно обновлялись —
в ответ на сигнал valueChanged объекта а будем вызывать слот setValue объекта Ь.
Foo а, Ь;
QObject::connect(&а, SIGNAL((int)), &b, SLOT((int))) ;
b.setValue( 11 ); // а не определено, b = 11
a.setValue( 79 ); // a = 79; b = 79
b.valueO; // возвращает 79
Как видите, для установки “канала сообщений” между двумя объектами служит
метод QObject: : connect (). Аналогично вы можете отключить оповещения мето-
дом QObject::disconnect().
Кроме того, поскольку с одним слотом можно связать любое количество сигна-
лов, в частном случае вы можете связать какой-то сигнал дважды с одним и тем же
слотом. В этом случае одно событие будет дважды вызывать обработчик в слоте —
например, одно нажатие кнопки мыши будет генерировать двойной щелчок.
Обратите внимание на то, что в четвертой версии класс QSignal, служащий для
передачи сигналов произвольным классам, не происходящим от класса QObject,
был признан устаревшим и переименован в класс Q3Signal. Поэтому для создания
собственных классов, воспринимающих сигналы, всегда используйте механизм на-
следования от класса QObj ect.
1.3.2. Сигналы: практический пример
Теперь рассмотрим полный практический пример, который вы сможете отком-
пилировать и проверить. Возьмем небольшую часть “туториала”. Вот полный текст
программы.
#include <qapplication.h>
#include <qpushbutton.h>
#include <qfont.h>
int main( int argc, char **argv ) {
□Application a( argc, argv );
QPushButton quit( "Quit”, 0 );
quit.resizef 75, 30 );
quit.setFont( QFont( "Times", 18, QFont::Bold ) );
::connect( &quit, SIGNAL(clicked()), &a, SLOT(quit()) );
a.setMainWidget( &quit );
quit.show();
return a.exec();
}
He будем пока обращать внимание на изменение внешнего вида кнопки, это
довольно тривиальные операции. Самое главное для нас — это конструкция
QObject::connect. Здесь воедино связываются четыре сущности — элемент
управления (в данном случае — кнопка), некий сигнал, поступающий от этого
элемента (в частности SIGNAL (clicked ())), экземпляр обработчика (в данном
случае — приложение, с которым ассоциируется очередь сигналов, неявно созда-
ваемая при вызове ехес ()) и, наконец, обработчик события (в терминах Qt назы-
ваемый слотом).
Конечно, в качестве обработчика может выступать любой объект, существование
которого вы гарантируете в течение всего жизненного цикла элемента управления.
Часто таким обработчиком выступает контейнер верхнего уровня, или попросту —
форма (страница на многостраничной форме и так далее), на которой расположен
данный элемент управления. Обработка на уровне таких контейнеров среднего
уровня, как группа или фрейм, служит хорошим фундаментом для создания по-
вторно используемых компонентов со встроенной бизнес-логикой.
Обратите внимание на принадлежность метода connect — это статический ме-
тод базового класса QObj ect, так что обработка сигналов является базовой для всей
библиотеки Qt и доступна для любого класса в иерархии, даже до создания любого
экземпляра.
1.3.3. Сигналы и слоты: вопросы производительности
Часто интересуются реальной производительностью механизма сигналов. Ее
можно назвать достаточной. Согласно экспериментам, проведенным Trolltech на
процессоре класса Pentium III 500 мГц, вы можете вызывать два миллиона сигна-
лов в секунду, связанных с одним обработчиком, и около 1 200 000 (пар) сигналов,
связанных с двумя обработчиками.
Как видим, сигналы и слоты достаточно производительны, но, безусловно, не
настолько, как обычные функции обратного вызова. Как можно догадаться, для
гибкого управления сигналы помещаются в. специальные очереди на обработку.
Слоты для одного сигнала тоже должны быть организованы в связанные списки,
чтобы вызываться один за другим, хотя лучше думать об ортогональном, незави-
симом от других слотов, выполнении.
Более того, сам факт вызова, или распространения, сигнала приводит (анало-
гично исключениям) к созданию специального объекта и, следовательно, вызову
конструктора этого объекта. А, как известно, операции new() и delete () вызыва-
ют динамическое перераспределение памяти, что тоже относится к сравнительно
медленным операциям.
Для среднего приложения эти накладные расходы не имеют особого значения,
однако сам вызов сигнала со связанным с ним слотом происходит приблизительно
в десять раз медленнее, чем вызов обычного, невиртуального метода, и примерно в
пять раз медленнее, чем виртуальный вызов по VMT (Virtual Method Table — таблица
виртуальных методов).
В любом случае современные приложения очень мало находятся в собственном
коде, как можно чаще делегируя управление операционной системе. Дополнитель-
но, частое использование динамических связанных списков, строк динамически
изменяемой длины и других “поедателей тактов” нивелирует расходы на обработку
сигналов.
И все же не стоит использовать сигналы и слоты там, где нужна максимальная
производительность и доступны обычные функциональные вызовы.
1.4. Свойства в Qt, краткое описание
Помимо уже указанных сигналов и слотов, в объектной модели Qt также реали-
зованы и свойства. Свойства — это виртуальные поля объектов, для которых опре-
делены методы их чтения, опционально — записи, признак устойчивости значения
stored, функция установки в кбнтекстно-зависимое значение по умолчанию,
а также признак отображения в инспекторе свойств — designable.
Если вы знакомы с другими объектными моделями, использующими свойства
(например, COM, Delphi или Java Beans), то многое вам покажется знакомым.
Отличие, однако, заключается в том, что весь механизм свойств в Qt реализован
через макроподстановки, которые транслируются препроцессором в обычный код
C++. По производительности это более всего соответствует эффективности
свойств, реализованных в Delphi как часть синтаксиса, что эквивалентно макро-
подстановкам.
Для создания и управления свойствами используется несколько макросов, оп-
ределенных в классе Q_OBJECT, который должен быть включен до использования
любых операций со свойствами. Свойства определяются с помощью макроса
О PROPERTY. Который в общем виде выглядит следующим образом.
Q_PROPERTY ( type name READ getFunction [WRITE setFunction]
[RESET resetFunction] [DESIGNABLE bool] [SCRIPTABLE bool]
MSTORED bool] )
Все указанные здесь функции должны быть определены в том же классе функ-
циями-членами с глобальной (public) областью видимости. Метакомпилятор кон-
тролирует тип значения, возвращаемого get-функцией, который должен совпадать
с типом свойства, быть указателем на него или статической ссылкой. Аналогично
должен выглядеть и параметр, передаваемый set-функции, которая не должна воз-
вращать значения.
Функция “сброса” RESET не должна иметь аргументов и не должна возвращать
значения. Ее назначение — устанавливать свойство равным контекстно-зависим-
ому значению по умолчанию. Например, цвет элемента управления может насле-
доваться от родительского, что и будет контекстно-зависимым “умолчанием”.
В качестве элемента DESIGNABLE может выступать как значение TRUE/FALSE,
так и метод, возвращающий логическое значение. Этот элемент обозначает, будет
ли свойство появляться в инспекторе свойств визуальной среды разработки, и
устанавливается равным значению TRUE для перезаписываемых свойств.
Зарезервированное слово STORED определяет, будет ли свойство запоминаться во
время сохранения объекта как целого в процессе сериализации. По умолчанию этот
признак установлен равным TRUE, хотя сохранение свойств “только для чтения”
имеет сомнительный смысл. Аналогично слово SCRIPTABLE определяет, будет ли
свойство доступно извне с помощью таких “скриптовых” языков, как d>SA. Это зна-
чение может быть задано константой или логическим методом.
1.4.1. Типы свойств на примере класса со свойствами
В качестве типа свойства может выступать любой тип, допустимый для QVariant-
подкласса, перечисление, заданное в самом классе (необходима регистрация такого
типа с помощью макроса Q_ENUMS), а также тип QValueList<QVariant>
или QMap<QString,Qvariant>. Последние позволяют создавать множественные
или даже ассоциативные свойства.
Для наглядного представления приведем “родной” пример создания классу с
определенным свойством перечислимого типа, заданного в самом классе.
class MyClass : public QObject {
Q_OBJECT
Q—PROPERTY( Priority priority READ priority WRITE setPriority )
Q_ENUMS( Priority )
public:
MyClass(QObject *parent=0, const char *naihe=0 );
-MyClass();
enwn Priority { High, Low, VeryHigh, VeryLow };
void setPriority( Priority );
Priority priorityO const;
};
Регистрация перечисления, кроме прочего, позволяет задавать в качестве зна-
чений строковые эквиваленты целочисленных значений.
obj->setProperty( "priority", "VeryHigh" );
В заключение нужно сказать, что свойства — это не только “реверанс” в пользу
разработчиков, привыкших к свойствам в других средах разработки, например в
среде VBA. Важное качество свойств — наличие информации времени выполнения,
что является базисом для автоматизации приложений с помощью скриптов
(рис. 1.3).
Для тех же, кто привык к использованию методов C++, все свойства стандарт-
ных классов переопределены также в виде аналогичных по функциональности get-
и set-методов. По соглашению, при создании собственных универсальных классов
и библиотек на основе Qt вам также следует придерживаться этого “двойного стан-
дарта”.
Рис. 1.3. Использование информации
времени выполнения (RTTI) позволяет
исследовать имя, тип и происхождение
свойств Qt — это отличает их от
обычных членов класса C++
1.5. Объект QApplication и структура приложения
Самым основным объектом приложения Qt является объект класса QApplica-
tion. В этом отношении Qt не очень отличается от библиотек MFC (Microsoft
Foundation Classes — библиотека базовых классов Microsoft), Delphi или Visual
Basic. Обратите внимание на префикс Q — это отличительный признак всех классов
Qt, выделяющий классы Qt среди остальных библиотечных классов. В версии Qt 4
классы, существующие для совместимости с версией 3, имеют префикс Q3.
Объект приложения— экземпляр класса ©application— служит для управ-
ления оконным приложением и выступает в качестве контейнера для всех осталь-
ных объектов и глобальных настроек. Когда мы говорим об управлении, то это зна-
чит, что класс QApplication содержит неявный цикл обработки системных, а
также и несистемных (внутренних) сообщений. Кроме этого, класс QApplication
отвечает за инициализацию и завершение приложения, включая такие операции
(в MS Windows), как регистрация класса главного окна, создание объекта окна и
определение главной функции-обработчика сообщений.
Рассмотрим классическую программу Hello World. Ниже приведен полный
текст программы, создающей оконное приложение с единственным элементом в
виде кнопки, на которой написано “Hello World”.
#include <qapplication.h>
#include <qlabel.h>
int main( int argc, char **argv ) {
QApplication app( argc, argv );
QLabel *hello = new QLabel( "Hello World", 0 );
app.setMainWidget( hello );
hello->show();
return app.exec();
}
Многие разработчики, знакомые с другими объектными моделями, сразу же уз-
нают знакомые элементы, такие как объект приложения и метод запуска главного
цикла, в данном случае называемый ехес ().
Обращаю ваше внимание: объект класса QApplication — не то же самое, что и
главное окно приложения. Экземпляр приложения действительно подменяет мно-
гие функции оконной процедуры, а главное окно обычно тесно связано с жизнен-
ным циклом Qappli cat ion-объекта — в частности, закрытие главного окна обыч-
но связано с закрытием приложения. Тем це менее приложение может создавать
любое количество окон верхнего уровня, а также возможна ситуация, когда при-
ложение не связывает себя ни с одним окном верхнего уровня. При этом вы, естест-
венно, должны каким-то другим образом контролировать жизненный цикл при-
ложения, например с помощью сервиса RPC (Remote Procedure Call — удаленный
вызов процедуры) или сокетов. В своем приложении вы можете и должны создать
один и только один экземпляр класса QApplication, независимо от того, сколько
окон верхнего уровня оно будет отображать.
Также важно иметь в виду, что вас, как Qt-программиста, внешний мир в виде
операционной системы не должен интересовать. Как только вы выйдете за рамки
Qt — вы потеряете переносимость вашего кода. Поэтому класс QApplication так-
же выполняет функции посредника между вашей программой и системой, отсле-
живая системные настройки, такие как системный шрифт по умолчанию, настрой-
ки цветовых схем или, например, временной интервал двойного нажатия кнопок
мыши. Если вам нужно анализировать или изменять этй параметры настроек, то
вы должны обращаться к Qappli cat ion-экземпляру.
Кроме того, вам будет полезно знать, что класс QApplication отвечает за от-
слеживание версии Qt, а также за контроль лицензии используемого дистрибутива:
именно конструктор класса QApplication отображает экран с предупреждением о
завершении срока тестирования, и там же происходит вызов всех связанных с ли-
цензией проверок.
Глава 2
Базовые классы приложений
2.1. Строки и класс QString
Строки — один из базовых типов данных, особенно в средах, происходящих от
UNIX, а также в сетевых приложениях. Поэтому вполне закономерно, что мы нач-
нем рассмотрение примитивных классов с типа, описывающего строки в Qt, —
QString.
Этот класс имеет семь перегруженных конструкторов, каждый из которых вос-
принимает “свой” тип данных в качестве основы для создаваемой строки. Вот их
список.
QSting()
QString(const QChar ch)
QString(const QString &s)
QString(const QByteArray &ba)
QString(const QChar *unicode, uint length)
QString(const char *str)
QString(const QLatinlString &str)
Как видите, в качестве основы для строки может выступать все, что угодно, —
отдельный символ, другая строка, массив байт или массив символов, а также стро-
ка в формате Unicode или строка в формате QLatinlString. Также существует
конструктор, создающий пустую строку.
Важное замечание относится к Qt 4. Конструктор на основе типа QByteArray
теперь преобразует весь массив, не рассматривая его как строку с нулевым симво-
лом в конце. Это согласуется с форматом Unicode и общей логикой, поскольку
QByteArray не является массивом байт в привычном смысле. Раньше преобразова-
ние заканчивалось на первом нулевом байте, и такое поведение было заложено еще
BQt 1.
Такая исчерпывающая полиморфность наблюдается и в отношении других методов.
Например, по четыре и больше перегрузок имеют методы QString: : insert (),
append (), prepend (), которые в середину, в конец или в начало строки вставляют
символы, заданные различными строковыми типами. Несколько отличаются че-
тыре перегрузки метода QString: :remove(): кроме использования в качестве
удаляемого образца (например, удалить все вхождения символа или подстроки)
значений примитивных типов, есть вариант, воспринимающий диапазон “от сим-
вола с индексом А и до символа с индексом Б”, а также вариант, “понимающий” ре-
гулярные выражения.
Аналогично существует масса версий перегруженного оператора сравнения
“==”, т.е. строку можно сравнивать с данными шести различных типов, в том чис-
ле, естественно, с другой строкой типа QString. Подобным образом перегружены и
разнообразные операции сравнения, а также оператор “+=“, который добавляет
символы в конец строки (подобно методу QString: : append ()). Все эти методы
возвращают строки типа QString в качестве результата, к которому можно сразу
же применять новые операции.
Здесь нужно подчеркнуть, что строки по умолчанию всегда используют метод
поверхностного копирования (shallow сору), т.е. на самом деле копируются только
ссылки. В терминологии Qt это называется неявным разделением (implicit sharing).
В результате, если вы создаете строку на основании другой строки (в том числе и с
помощью оператора присваивания “=“), то копируются не данные, а только ссылка
на объект. Как можно предположить, это вызывает побочные эффекты, т.е. любые
операции по изменению одного объекта вызовут изменения в другом. Скорость вы-
полнения самого “копирования” при этом значительно возрастает. Поскольку биб-
лиотека Qt создана в расчете на программистов, понимающих, как работает код,
то по умолчанию используется этот быстрый, но чреватый неявными ошибками
метод.
Пару слов о пустых строках. В Qt есть два типа пустых строк — неопределенная,
т.е. та, которой не было присвоено никакое строковое значение. Длина такой стро-
ки равна нулю, а методы isEmptyO и isNullO для нее возвращают значение
true.
В Qt 3 для такой строки было зарезервировано специальное значение
QString: :null. В версии Qt 4 эта константа также существует, но только для со-
вместимости. Теперь такого же эффекта можно достичь, вызывая метод clear ()
вместо использования константы. Итак, вместо кода
strl=QString::null; if (str2==QString::null) exec (QString::null);
нужно использовать следующий вариант:
strl.clear(); if (str2 . isNull () ) exec (QStringO);
Другой вид пустой строки — это строка нулевой длины. Она может быть такой
изначально или может стать пустой в результате каких-либо операций, например
в результате присвоения s=” \0 ’’. Оба типа пустых строк могут быть использованы
в качестве параметров: во время выполнения они будут корректно приведены к
строке нужного типа. В результате сортировки первыми окажутся пустые строки,
затем — обычные и, наконец, неопределенные.
Теперь о длине строки и о самых простых, кроме уже упоминавшихся, операци-
ях с самой строкой. Уже встречавшийся по тексту метод QString: : length() воз-
вращает длину строки, метод truncate () “обрезает” строку до заданного размера
(если до этого она была длиннее), а метод f ill () формирует из заданного символа
строку заданной длины. Методы left О , right () и mid() возвращают указанное
количество символов слева (т.е. в начале строки, а если это “правосторонний”
язык — то справа), справа или в середине строки соответственно.
Два QString-метода предназначены для работы с пробелами, точнее — с про-
бельными символами (такими как пробелы и знаки табуляции). Метод
QString: :stripWhiteSpace() удаляет пробельные символы в начале и в конце
строки, а метод QString: : simplifyWhiteSpace(), в дополнению к этому, также
заменяет любую последовательность пробельных символов в строке одним пробелом.
Несколько более сложные операции выполняют методы QString: : left Justify ()
и QString: : right Justify () — дополняют строку до заданной длины заданным
символом, а возможно (в зависимости от третьего параметра) и усекают ее, если она
превышает эту длину. Методы QString: : upper () и lower () делают то, что и
предполагается исходя из их имен: переводят все символы строки в верхний или
нижний регистры соответственно, по возможности с учетом локальных кодировок.
2.1.1. Поиск и замена
Важное место при обработке строк, до появления регулярных выражений, за-
нимали функции поиска и замены различных подстрок. И хотя любую из этих
функций легко реконструировать собственными силами, однако библиотечные
функции обычно надежнее, быстрее и, что немаловажно, служат знакомыми ори-
ентирами для тех, кто будет читать ваш код.
Самыми простыми средствами поиска являются два метода: QString: : at () и
перегруженный оператор “ [ ] Оба метода делают одно и то же: возвращают сим-
вол, расположенный в заданной позиции строки, причем так, как если бы это был
массив символов. Раньше (до четвертой версии) эти методы были полностью экви-
валентны: метод at () возвращал указатель, или, как говорят, был левосторонним.
Поэтому в версии Qt 3 для замены символа в определенной позиции строки можно
было записать str. at (3) = ' х'. В версии Qt 4 метод at () возвращает константу и
поэтому может стоять только справа от оператора присвоения. При попытке ском-
пилировать запись str. at (3) = ' х ’ будет выдано сообщение об ошибке, так что ее
следует заменить таким вариантом: str [ 3 ] = ’ х ’.
Довольно специфическую проверку осуществляют методы QString:: startsWith()
и endsWithO — как можно догадаться, они проверяют, начинается ли данная
строка другой подстрокой либо заканчивается ею. В качестве необязательного па-
раметра этих функций, как и многих других, можно указать, нужно ли сравнивать
строки с учетом регистра. В Qt 3 для этого параметра использовались значения
true/false, в Qt 4 для мнемоничности введен специальный перечислимый тип
Qt: :CaseSensitivity, состоящий всего из двух констант— Qt: :CaseSensitive
иQt::Caseinsensitive.
Вообще, сравнение строк может выполняться двумя методами: QString:: compare ()
и localeAwareCompare (). Оба метода имеют как статический, так и обычный ва-
рианты, т.е. можно сравнивать две строки, явно не создавая экземпляры класса
QString. Метод QString: : compare () сравнивает строки с помощью встроенных
Qt-методов, а метод QString: : localeAwareCompare () для этих же целей использует
системные вызовы, которые, как правило, более корректно работают с локализо-
ванными кодировками.
Метод QString: : contains () имеет шесть перегрузок и в Qt 4 возвращает логи-
ческое значение, отвечающее на вопрос, содержится ли одна строка в другой.
Количество таких вхождений возвращает метод count (). В Qt 3 метод contains ()
работал как count () в Qt 4, т.е. возвращал количество вхождений — заменяйте его
одним из новых методов в зависимости от того, интересует вас количество вхожде-
ний или сам факт их наличия.
Методы QString: : f ind() и findRev() из Qt 3 заменены методами indexOf ()
и lastlndexOf (). В любом случае они ищут в строке первое вхождение подстроки,
символа или регулярного выражения — с той разницей, что indexOf () ищет в на-
правлении от начала к концу, a lastlndexOf () — в обратном направлении. Нако-
нец» метод QString: :replace(), представленный в шести вариантах, заменяет
часть строки другой строкой или символом. При этом могут быть указаны как гра-
ницы замены (“от и до”), так и подстрока поиска, а также регулярные выражения.
В случае использования регулярных выражений работают “переменные обрат-
ных ссылок” (такие, как в языке Perl), т.е. в строке подстановки можно использо-
вать фрагменты, полученные как совпадения на этапе поиска. Например, следую-
щий код переводит строку из одного гепертекстового формата в другой.
QString t = "A <i>bon mot</i>.";
t.replace( QRegExp("<i>([Л<]*)</i>"),”\\emph{\\1}”);
Команда t.replaceO использует в подстановке символ \1 (символ “\” пред-
ставлен управляющим кодом “\\”), который эффективно заменяется на
rx.cap(l), т.е, на первый образец, ограниченный в регулярном выражении скоб-
ками. Больше о регулярных выражениях вы можете узнать из описания класса
QRegExp.
2.1.2. Форматирование строк, предназначенных для вывода
Важные и часто используемые функции отвечают за форматирование ряда зна-
чений с помощью строки форматирования. Привычный для С-программистов спо-
соб достижения этого результата— метод QString &QString: : sprintf (const
char* cformat, . . .). Это не самый современный способ форматирования, по-
скольку строка формата должна быть представлена в старом ASCII-формате.
Если вы хотите сделать ваше приложение интернациональным, “понимающим”
строки в формате Unicode, то вы скорее должны использовать другой метод,
QString: :arg(), который имеет, ни много ни мало, пятнадцать (!) перегрузок.
Этот метод работает подобно потокам ввода-вывода: после подстановки следующего
параметра в строку метод возвращает экземпляр класса QString, что позволяет
производить подстановку снова. Для примера возьмем конструкцию, которая по-
казывает цепочку полиморфных подстановок с помощью QString: : arg ().
QString status=QString("File %1 of %2: %3").arg(i).arg(total)
'Ъ. arg (fName) ;
Как можно предположить, первые два параметра — числа, в то время как тре-
тий, имя файла, представлен строкой в том или ином формате. Кроме, собственно,
данных, каждая из перегрузок обладает дополнительными необязательными пара-
метрами (такими, как длина поля или, например, для целых чисел — основание
системы счисления, а для вещественных — количество десятичных знаков).
2.1.3. Преобразование чисел и кодировок
Кстати, о числах. В Qt преобразованием строки в число и наоборот занимается
сам класс QString. Преобразование данных различных числовых типов в строку
выполняет метод QString::number() (семь перегруженных вариантов). Десять
вариантов метода QString: : setNum() занимаются тем же, т.е. представляют чи-
словые данные в виде строки практически любого формата. В качестве дополни-
тельных параметров могут выступать основание системы счисления, точность
представления и формат записи действительных чисел. Несложно представить, что
именно эти методы и используются при форматировании строк.
Поскольку при обратном преобразовании из строки в число полученный резуль-
тат часто нужно (или желательно) сразу же использовать в качестве аргумента в
выражении, перегрузить это преобразование не удается — в качестве перегружае-
мых факторов могут выступать типы аргументов, но не тип результата функции.
Поэтому преобразованием из строки в число занимаются сразу десять методов, на-
пример QString::tolnt(), toShort(), toULongLong() и т.д.
Кроме преобразований в числовое представление и из него, также существует
несколько преобразований в различные кодовые страницы или просто кодировки.
По умолчанию все строки внутри самой библиотеки Qt представлены в формате
UTF8, т.е. в восьмибитовой кодировке Unicode1. Если вы знаете, что входная стро-
ка, полученная из внешнего источника, задана в другом формате, то вам нужно
выполнить перекодировку строки. Перекодировкой может заниматься сам конст-
руктор, но обычно строка создается один раз, а затем повторно используется в цик-
ле программы. Чаще всего “старые” строки приходят по электронной почте,
поскольку почта до сих пор “не понимает” ничего, кроме семибитовой кодовой
страницы Latinl. Для преобразования таких “устаревших” форматов служат
несколько методов: QString: : fromAscii (), fromLatinl (), fromLocal8Bit (),
fromUcs2(), fromUtf8(). Подобным образом работают и методы QString::
setUnicode (), setUnicodeCodes (), setAsciiO и setLatinlO, однако они
признаны “недействительными”; в Qt4 вместо них следует использовать from-
методы.
Я, по правде говоря, затрудняюсь прокомментировать все приведенные выше
преобразования — больше информации по перекодировкам содержит описание
класса QTextCodec. Преобразование из внутреннего формата представления строк
Qt в различные внешние строковые типы осуществляют следующие пять методов:
QString::unicode(), utf8(), ascii(),latinl() иlocal8Bit().
UTF — это аббревиатура от Unicode Text Format (текстовый формат Unicode). Схема кодирова-
ния UTF — это альтернативная система кодирования, которая предполагает более короткие, эф-
фективные коды для ASCII-символов. — Примеч.ред.
2.1.4. Работа с внутренним представлением строк
Наконец, есть несколько технических методов, которые “вмешиваются” в ме-
ханизм выделения памяти для строк, хотя это и не совсем корректное поведение.
Если вы уверены в том, что делаете, то можете заранее зарезервировать длину хра-
нилища с помощью метода QString: : reserve (), который распределяет место под
строку указанной длины.
Похожий метод QString: : resize () (в Qt 3 — метод setLength ()) делает еще
более “грязную” операцию — произвольно устанавливает поле длины строки рав-
ным заданному числу. При этом содержимое памяти не очищается, и сама строка
может содержать любые “грязные” данные. Если пользовательский ввод может по-
влиять на новый размер, то недалеко и до “взлома” через переполнение буфера.
Конечно же, правильнее расширять строку до заданного размера, добавляя определен-
ные символы, например, с помощью оператора “+=” или метода left Justified ().
Метод QString: :capacity() возвращает зарезервированное под строку ме-
сто — если строка будет увеличена более этого размера, это вызовет создание нового
буфера и копирование туда данных методом глубокого копирования (deep сору), т.е.
фактических байт, а не только указателя. Метод QString: : squeeze () сокращает
размер хранилища до фактического размера строки. Хочется еще раз отметить, что
применение этих методов оправдано в одном случае из тысячи, если вы точно знае-
те, что данный участок кода требует подобной оптимизации. Часто и проще и вы-
годнее уничтожать строки по мере их использования, чем оптимизировать их по-
вторное использование.
Как видите, строки в Qt проработаны на двенадцать с плюсом по пятибалльной
системе. Как и было обещано, вы не скоро станете пользоваться всеми этими воз-
можностями, а точнее — будете пользоваться ими время от времени, возможно, за
исключением подстановок в строки форматирования. Для поиска и подстановок
подстрок вы, вероятно, скорее захотите использовать значительно более мощные
механизмы регулярных выражений.
2.2. Списки строк: класс QStringList
и другие возможности
Строки, конечно, полезны сами по себе, но очень часто они используются в виде
динамического массива. Безусловно, в простых случаях вы можете и должны ис-
пользовать обычные массивы для хранения ссылок на строки, поскольку массивы
всегда работают быстрее, чем связанные списки. С другой стороны, если вы не
знаете заранее размер этого массива или этот размер будет часто меняться, то луч-
ше и выгоднее использовать в качестве хранилища специальный класс списка
строк QStringList. Классы QStrList (QStrIList), оперирующие ссылками на
строки, в версии Qt 4 вынесены в библиотеку совместимости и совершенно не реко-
мендованы к применению.
Класс QStringList является специализацией класса-шаблона QList, по сути,
это — QList<QString>. Класс QList заменил QValueList из Qt 3, так что проис-
хождение класса QStringList также немного изменилось, хотя интерфейс, в ос-
новном, остался прежним.
Существует пять перегруженных конструкторов класса QStringList, в том
числе для создания пустого списка, списка, образованного в результате копирова-
ния уже существующего, и списка, содержащего только одну строку. Кроме того,
уже созданный объект класса QStringList можно заполнить строками из
QSt г Li st-объекта методом QStringList: : f romStrList ().
Как и для всех потомков шаблонного класса QList, для добавления объектов в
коллекцию может использоваться один из нескольких способов: вызов метода
QStringList: :append(), перегруженный оператор “+=*’ или оператор вывода в
поток “«”. Поскольку в последнем случае оператор возвращает новый объект типа
“список строк”, то можно, как это показано в следующем примере, в одной инст-
рукции сцеплять сразу несколько операций.
QStringList si;
si.append(?uno?);
sl+=?dos?;
sl<<?tres?«?cuatro? ;
Несложные трюки с перегруженными потоками позволяют заполнять списки
при создании приблизительно таким образом.
list= (QStringList () <<strl<<str2«str3 ) ;
Собрать весь список в одну строку легко с помощью метода QStringList;: j oin ();
например, так: QString todo=sl.join(?, ?). Как легко догадаться, в качестве
параметра выступает разделитель элементов. Обратную операцию разбиения стро-
ки на фрагменты, ограниченные разделителями, с записью каждого элемента в
список выполняет метод QString: : split (). Здесь необходимо обратить внимание
на то, что это метод класса QString, а возвращаемое им значение имеет тип
QStringList.
Используемые в Qt 3 методы grep () и gres (), похожие на команды поиска и
замены в Unix, были изъяты из Qt 4 — вместо них следует использовать получен-
ные по наследству от класса QList методы поиска и замены (в различных вариан-
тах) find () и replace () соответственно, а еще лучше — вместо find () использо-
вать метод indexOf (). Обратите внимание на то, что класс QStringList, в отличие
от шаблона QList, имеет версии методов поиска и замены с использованием регу-
лярных выражений.
Для проверки наличия нужной строки служит метод contains (), а для поиска
нужного элемента списка — методы indexOf () и lastlndexOf (). Оператор “ [ ] ”
перегружен как для левостороннего, так и для правостороннего доступа.
2.3. Регулярные выражения
Регулярные выражения относятся к одной из самых сложных и вместе с тем
эффективных технологий программирования. Как правило, система обработки ре-
гулярных выражений рассматривается как внешний модуль, даже если синтаксис
таких йыражений “встроен” в сам язык. Каноническими (эталонными) являются
процессоры регулярных выражений, реализованные в таких командных оболоч-
ках, как bash, для задания групповых имен файлов (wildcards), а также в языке
программирования Perl. Несмотря на различия, оба варианта являются общепри-
нятыми, и класс QRegExp позволяет работать с регулярными выражениями в обоих
режимах. На сегодня поддержку этих диалектов предлагают большинство библио-
тек, в том числе— Microsoft .NET. Common Runtine, что позволяет говорить о
существовании де-факто единого стандарта. Мы рассмотрим не только сам класс
QRegExp и его методы, но и синтаксис использования регулярных выражений.
Регулярные выражения применяются в тех случаях, когда нужно найти часть
строки, причем условия могут быть очень и очень сложными, включая различные
варианты написания, подстановку различных символов и т.д. Первое, самое про-
стое использование регулярных выражений — установка самого факта наличия за-
данного образца в строке. Опционально после обнаружения шаблона поиска (или
одного из нескольких вариантов) найденный участок может быть “захвачен” для
последующего анализа, чтобы вы могли точно сказать, какой именно из вариантов
был найден. Кроме этого, можно заменить найденную часть строки другой, и при
этом также можно использовать найденные в результате поиска подстроки.
Регулярные выражения вообще и в Qt в частности состоят из трех компонентов:
символов и их классов, квалификаторов арности (означающих, сколько раз может
повторяться подстрока поиска) и специальных символов привязки. Символы могут
задаваться буквально, т.е. литерал “строка” — действительное регулярное выра-
жение. Регулярные выражения в Qt корректно обрабатывают Unicode-символы,
так что строка, написанная на русском языке, вполне подходит на роль регулярно-
го выражения. Обратите внимание, что в шаблонах выполняются быстрые совпа-
дения, т.е. шаблон “<Ь>.+</Ь>” в отношении строки “<bxb>inside</bx/b>” не
сможет вернуть содержимое внутреннего тега, причем не важно, будет ли сравне-
ние выполняться в “жадном” или “минимальном” режиме (сами режимы рассмат-
риваются чуть позже).
Невозможность обрабатывать произвольные сбалансированные вложения — это
серьезное ограничение, теоретически не позволяющее строить на основе одних ре-
гулярных выражений универсальные синтаксические анализаторы.
В качестве расширения обычных символов применяются так называемые клас-
сы символов. Выглядит это так: выражение [12] обозначает “символ 1 или 2”, так
что регулярное выражение string [12] будет совпадать со словами stringl и/или
string2.
Классы символов могут задаваться с помощью дефиса: в этом случае в класс бу-
дут включены все символы, от первого до последнего. Например, выражение [A-Z]
обозначает большие латинские буквы. Можно комбинировать оба метода, например
[A-Za-z_&] — такой класс символов хорошо подойдет для обозначения допустимого
начала имени переменной. Символ каретки, “Л”, если он непосредственно следует
за открывающей квадратной скобкой, обозначает “кроме символов, перечисленных
в классе”, в противном случае он не имеет в описании класса особого значения и
представляет сам себя. Вертикальная черта, “ | ”, обозначает операцию ИЛИ, т.е.
совпадение вызывает или один шаблон, или другой (на выбор) — будьте осторожны
с этим вариантом поиска, иначе разбор строки может затянуться надолго.
Некоторые часто используемые классы (например, числа или латинские буквы)
имеют собственные условные обозначения. Так, специальный символ \d обознача-
ет любую десятичную цифру. Обычно, если маленькая буква после косой черты
обозначает какой-то класс, то большая — все что угодно, но кроме того, что обозна-
чает маленькая буква: например, если \s обозначает пробельные символы, то \S —
все, кроме пробелов. Символы \t, \г и \п означают то же самое, что и в языке С, —
символы табуляции, возврата каретки и перехода на новую строку соответственно.
Символ (точка) обозначает любой символ, включая и перечисленные выше
спецсимволы. Будет ли “точка” срабатывать на символ перевода строки — зависит
от режима регулярного выражения.
Квалификаторы задают дополнительные параметры поиска, точнее — количе-
ство символов, заданных предыдущим символом или группой. Например,
[A-Z] {1,5} обозначает “от одной до пяти больших латинских букв”, a /d{ 1, 2} —
все однозначные и двузначные числа. Есть несколько специальных квалификато-
ров, подменяющих часто встречаемые диапазоны: “?” является сокращением от
{0,1}, “+” => {1,MAXINT}, “*” => {0,MAXINT}, {n} => {n,n}, {n, } =>
{n, MAXINT}, { ,n} => {0,n}.
Наконец, специальные символы привязки — это условное обозначение таких
“особых” мест, как начало или конец строки или слова. Символы “л”и “$” пред-
ставляют начало и конец строки соответственно, а \Ь используется для условной
границы слова. Обратите внимание, что эти символы, по сути, не совпадают ни с
каким символом и не переводят условную текущую позицию поиска, что особенно
важно при “захвате” подстрок в результате совпадения.
Существует два символа привязки, которые описывают ситуацию “должно сов-
падать в данном месте, если далее по тексту пОследует/не последует следующее...”.
Записывается это так: (?=regexp) или (?!гёдехр). Например, выражение
" this (?=\s+that) " совпадает со словом " this" только в случае, если за ним идут
один или несколько пробельных символов и затем слово " that" — хотя ни эти про-
белы, ни " that" в совпадение не включается.
Захват, как обычно, обозначается обычными круглыми скобками. Любое вы-
ражение в круглых скобках записывается в переменные с последовательной ну-
мерацией, так что впоследствии их можно использовать в подстановках. Для
ссылок на такие “захваченные” подстроки применяются обратные ссылки в виде
\ 1, \ 2 и так далее — сколько подстрок было захвачено, столько вы потом и мо-
жете использовать.
Можно переключить обработчик регулярных выражений в режим командной
оболочки, вызвав метод QRegExp: : setwildcard (TRUE). При этом будут воспри-
ниматься только самые простые спецсимволы: * как (. *) и ? как (.), а также
классы символов в самом простом виде, без подстановок спецсимволов и привязок.
Точки и косые в этом режиме не имеют никакого особого значения. В заключение
нужно напомнить, что в языке С символ “\” представляется как “\\”, так что везде
для создания спецсимволов нужно использовать две косые черты.
2.3.1. Использование класса QRegExp
Теперь немного о самом классе QRegExp и о его методах. Создается экземпляр,
как правило, с одним параметром — самим регулярным выражением. Это не слу-
чайно — при этом осуществляется его синтаксический разбор и настраивается
конечный автомат, который впоследствии и разбирает все поступающие на вход
строки. Если ваш алгоритм подразумевает большое количество шаблонов и ма-
ленькое количество входных данных-строк, то, вероятно, вы должны пересмот-
реть свой алгоритм. Опционально в конструкторе можно задать режим
“игнорировать различие строчных и прописных”, что соответствует ключу /i в
Perl. Там же, в конструкторе, вы сразу можете задать режим упрощенных bash-
подобных регулярных выражений. То же самое можно сделать и позже, исполь-
зуя методы QRegExp: : setCaseSensitive () и QRegExp: : setwildcard ().
Основной метод поиска — QRegExp: : search (), который в качестве первого и
обязательного параметра принимает строку, подлежащую разбору. В результате ус-
танавливаются свойства QRegExp: :matchedLength (), QRegExp: : capturedText () и
QRegExp: : cap (int). Последний метод возвращает “захваченные” фрагменты, на-
чиная с первого, по его индексу. Если заданная переменная обратной ссылки не
существует (не произошло совпадения), то возвращается значение -1, что, однако,
не совсем удобно.
Метод QRegExp: :searchRev() производит поиск в обратном направлении, а
метод QRegExp: :exactMatch() просто проверяет строку на совпадение с задан-
ным шаблоном и возвращает логический результат да/нет. Статический метод
QRegExp: : escape () воспринимает регулярное выражение и “прикрывает” все
специальные символы, такие как $ или {}, косой чертой, т.е. готовит эту строку
быть обычной строкой в контексте регулярного выражения. Это нужно при дина-
мической генерации регулярных выражений из произвольных строк, например,
вводимых пользователем.
Класс QRegExp не располагает возможностями для замены (подстановки) одних
строк в другие — для этого вы должны использовать метод QString: : replace () с
регулярным выражением в качестве параметра. Работают такие регулярные выра-
жения абсолютно аналогично, но использование нескольких классов для простой
замены нельзя признать особенно удобным.
Еще раз повторюсь — думайте об объекте “регулярное выражение” как о храни-
мой процедуре SQL: их разбор и оптимизация выполнения занимают время, так что
всегда, когда это возможно, избегайте множества регулярных выражений, и вместо
разрушения также постарайтесь кэшировать объекты класса QRegExp, делать их
глобальными или еще как-либо поощрять их повторное использование.
2.4. Коллекции вообще и Qlist-списки в частности
Часто в Qt, как принято в современных языках программирования, для хране-
ния в памяти большого количества данных используются не обычные массивы в
привычном смысле (С-мае с ивы), а более сложные структуры, так называемые кол-
лекции, которые избавляют программиста от выбора способа хранения элементов,
снабжая его набором инструментальных методов. По сути, разработчик не должен
беспокоиться о внутреннем представлении данных в коллекциях — важно только
то, что данные (объекты) могут быть добавлены в такой контейнер, а потом быстро
найдены, извлечены и/или удалены.
На самом деле программист, хотя и особо не включается в механизм работы
коллекции, тем не менее должен иметь представление о способах хранения, по-
скольку это может в большой степени повлиять на производительность работы кода
и эффективность использования памяти.
В целях упрощения кода и, соответственно, увеличения скорости работы про-
грамм часто используются более простые структуры, в частности — настоящие
дважды связанные списки, векторы указателей, а также списки-массивы с воз-
можностью динамического изменения размера и, наконец, очень популярные ассо-
циативные списки, также известные как справочники, хэш-таблицы и словари.
Поверх списков реализуются стеки и очереди, а также еще более сложные классы —
кэши.
Как уже мы видели на примере строковых списков, классы-коллекции Qt де-
лятся на новые, основанные на значениях, и старые, вышедшие из употребления,
основанные на указателях. Первые являются контейнерами хранимых объектов и
рекомендованы для применения (особенно в Qt 4), поскольку помогают избежать
многих ошибок и совместимы со стандартной библиотекой классов STL. Списки,
основанные на указателях, признаны опасными и вышедшими из употребления в
Qt 4, хотя и существуют в библиотеке совместимости.
Начнем рассмотрение с базовых списков, хранящих значения. Устаревший
класс QValueList из Qt 3 заменен двумя новыми классами. Первый и более упот-
ребимый шаблонный класс QList основан на массиве указателей, размер которого
может изменяться динамически. Второй, QLinkedList, реализован как настоящий
дважды связанный список, доступ к элементам которого осуществляется на основе
итераторов.
Специфика класса QList — очень быстрый доступ по номеру элемента в списке,
поскольку используется доступ через массив указателей. Как часто делается в ба-
зах данных, заранее распределенный массив имеет свободные элементы в начале и
в конце, а также внутри каждого из блоков (кластеров), чтобы вставка элементов
или добавление в начало'либо в конец как можно реже вызывали перестройку всего
массива. Поиском места для вставки занимаются специальные алгоритмы, дейст-
вующие почти так же быстро, как массивы в языке С. Если хранимый класс сам
имеет тип указателя, а не хранит значение, то в QList будут храниться указатели
прямо на значения, а не указатели на указатели.
Новые классы порождаются созданием экземпляров шаблона QList, например
QList<int>, QList<QDate>, QList<QString>. Последняя специализация соответ-
ствует классу QStringList. В шаблоне QList нельзя хранить сложные типы, в ча-
стности происходящие от класса QObject, но можно хранить указатели QObject*
или QWidget* по вашему усмотрению. Поскольку список хранит полные копии
объектов, о самих объектах можете особо не беспокоиться — они сами будут удале-
ны, когда на них не будет ни одной ссылки, т.е. когда будет удален сам список. На
объекты, доступные по ссылке, это не распространяется.
Работа с QI is t-элементами проста — для добавления элементов можно исполь-
зовать метод append () или, что еще проще, перегруженный оператор потока “«”.
QList<int> intList;
intList«l<<2<<3 ;
Для вставки в начало списка служит метод prepend (). Как уже было сказано,
для вставки в такие специфические позиции, как начало и конец, задействованы
специальные механизмы, так что вставка “с краев” происходит быстрее, чем массо-
вая вставка в середину с помощью метода insert (). Существуют и другие методы
работы со списком, например removeAt (), replace (), move () и swap (). Для од-
- повременного удаления всех элементов списка используется метод clear ().
Четыре метода(QList: :push_f ront (), pop_front (), push_back () и pop__back ())
в различных комбинациях работают со списком, как со стеком или очередью, до-
бавляют элементы в начало или в конец списка или извлекают дх (возвращают сам
объект, удаляя его из списка). Эти методы явно избыточны и служат только для со-
вместимости с библиотекой STL. Вообще, для STL-совместимости предусмотрено
немало таких дублирующих методов, как empty (), — эта STL-подобная функция
делает то же самое, что и метод isEmpty ().
Также, как и в строках, есть два способа доступа к элементам по их порядково-
му номеру: с помощью перегруженного оператора “ [ ] ” и посредством метода at ().
Оператор “ [ ] ” возвращает реальный указатель на элемент, Т&, так что вы можете
таким образом как получать значения, так и присваивать их. Метод at () возвра-
щает значение-константу, которую нельзя размещать слева от операции присвое-
ния. Метод at () t может работать быстрее, поскольку никогда не использует глубо-
кого копирования объектов.
Часто нужно не только получить элемент, но и извлечь его из списка (например,
так работают очереди и стеки). Для этого служат методы takeAt (), takeFirst () и
takeLast (). Удаление “с краю” — такая же быстрая операция, как и вставка, по-
скольку освобожденные элементы становятся частью резерва на случай последую-
щих вставок.
Для поиска и замены используются уже известные из списков строк методы
indexOf (), contains (), count () и replace (). Конечно, функции сравнения и
поиска заданного объекта зависят от его класса. Чтобы сравнивать два экземпляра,
хранимый класс должен располагать перегрузкой операции сравнения “==”. Также
сравнение может иметь особый смысл для заданного типа Т& — для строк, если
помните, возможно сравнение indexOf () в смысле регулярных выражений. Для
вещественных чисел это может быть сравнение в смысле эпсилон-погрешности: два
числа равны, если они достаточно близки, и т.д.
2.4.1. Итераторы
Со списками и коллекциями других типов связаны экземпляры особых клас-
сов — итераторы. Итератор — это объект, хранящий указатель на текущее поло-
жение в списке или, в общем случае, коллекции, а также позволяющий (умеющий)
осуществлять поиск следующего, предыдущего, первого, последнего, а иногда и за-
данного элемента.
В Qt 4 реализовано два типа таких итераторов, согласно двум распространенным
реализациям. Один тип итераторов, стандартный для Qt 4 (так называемые Java-
итераторы), хранит текущее положение между двумя элементами. Таким образом
итератор указывает на положение “до первого элемента”, “после последнего” или
между двумя элементами. Вначале итератор указывает на положение “перед пер-
вым элементом”, и каждый вызов next () перемещает его на одну позицию вперед.
Проверить наличие следующего элемента можно методом hasNext ().
QStringList si;
sl«" first "«"second" ;
QListIterator<QString> i(sl);
while(i.hasNext())
qDebug() « i.next();
Методы навигации, доступные итератору: toFront (), toBack (), previous (),
next (), hasPrevious (), hasNext (), peekPrevious () и peekNext (). Последние
два метода возвращают предыдущий или последующий элемент, не перемещая
указатель.
Вообще-то, Qt-итераторы не предполагают, что список или коллекция может
изменяться в процессе навигации, и не имеют средств для операций с элементами,
кроме возврата их значения. Для таких операций, однако, в Qt предусмотрены
специальные “редактирующие итераторы” — их классы называются так же, как и
обычные, с добавлением QMutable*. Если обычный итератор списков является эк-
земпляром класса QListlterator, то итератор с возможностью “мутации” списка
называется QMutableListlterator. Аналогично и для других итераторов.
Изменяющие итераторы для удаления элемента списка или изменения его зна-
чения используют методы remove () или setValue (). Интересная логика исполь-
зуется для выбора нужного элемента — действие производится над последним эле-
ментом, через который мы “перешагнули”. При движении слева направо это будет
элемент слева от указателя, при движении назад — тот, что справа. Здесь и прояв-
ляется специфика Java-итераторов: для STL-итераторов, которые указывают пря-
мо на элемент, результат не зависит от направления обхода, поскольку всегда есть
явный текущий элемент.
Также для редактирующих итераторов интересно работают методы previous ()
и next () — они не только перемещают положение в списке, но и возвращают ука-
затель на подлежащий модификации элемент. Указатель позволяет сразу же ис-
пользовать величину слева от оператора присвоения, например, next () = *2 будет
переходить на следующую позицию и умножать на два только что “покинутый”
элемент, находящийся слева от нового положения итератора.
Конечно, слежение за изменяющимся списком требует дополнительных расхо-
дов, так что изменяющие операторы работают всегда медленнее.
STL-совместимые итераторы, в основном, похожи и служат тем же целям, что и
Java-итераторы. Е$ть, однако, и несколько отличий. Первое и главное заключается
в способе указания текущей позиции: STL-итератор указывает прямо на текущий
элемент. За последним элементом в списке находится “несуществующий” элемент,
так что функции begin () и end () перемещают итератор на первый элемент и
“тот”, что расположен “за последним”. Это следует учитывать, поскольку после пе-
ремещения в “конец” (с помощью функции end ()) итератор указывает на несуще-
ствующий элемент, и обращение к нему вызовет ошибку.
Второе важное отличие — стиль обращения к индексам и самим элементам. Для
STL-итератора перегружены обычные операторы, применяемые к индексам масси-
вов: для итератора i переход к следующему элементу записывается i++ и т.д. Если
вы не используете значение итератора в выражении и вам все равно, писать i++ или
++i, то рекомендуется использовать вторую форму, поскольку она работает немно-
го быстрее (не сохраняет старое значение). Переход на п элементов вперед будет за-
писываться как i+=n. Если есть два итератора i и j, то i-j будет обозначать коли-
чество элементов между ними.
Выборка элемента осуществляется с помощью оператора “*” (*i), этот же указа-
тель служит и для присвоения нового значения. Если коллекция состоит из ключа
и значения, то для их получения можно использовать методы key () и value (), хо-
тя значение можно получить, используя выражение *i.
Третье различие — небольшой акцент в наименовании итераторов. Если Java-
итераторы являются немодифицирующими по умолчанию и имеют более длин-
ные имена в модифицирующей версии, то в STL все наоборот: описание
QList<QString>: : iterator описывает модифицирующий итератор, а
QList<QString>: : const_iterator— немодифицирующий. По аналогии с Java-
итераторами, имена STL-итераторов для различных коллекций имеют согласован-
ный вид, так что сразу ясно, что QMapcKey, Т>: : const_iterator относится к не-
модифицирующему итератору ассоциативного массива. Для немодифицирующих
итераторов используйте методы constBeginO и constEndO на классах-коллек-
циях.
Наконец, если вы получили коллекцию (не ссылку) как результат работы функ-
ции, то для применения STL-итераторов нужно сделать копию э^ой коллекции.
Этот небольшой пример демонстрирует многие принципы STL-итераторов.
QMapcint,int> map;
QMapcint,int>::const_iterator i;
for (i=map.constBegin(); i!=map.constEnd(); ++i)
qDebugcci. key () «" : "« (*i);
2.5. Другие, отличные от QList, коллекции
Собственно, от шаблонного класса QList непосредственно происходят коллек-
ции QltemSelection, QQueue и QStringList. Каждый из этих классов предназна-
чен для хранения специфических данных — мы не будем сейчас на этом останавли-
ваться, но в дальнейшем исходите из того, что они представляют собой специали-
зации шаблона QList, со всеми вытекающими последствиями.
Два класса похожи на QList, обеспечивая похожий интерфейс через соответст-
вующие итераторы: это классы QLinkedList и QVector. Основное различие в спо-
собе хранения— QList применяет разреженный массив, плюс специальные ре-
зервные области в начале и в конце. Это хорошая эвристическая предпосылка для
многих типов списков, для которых добавления происходят, в основном, только с
конца, но иногда и в середине.
Класс QLinkedList представляет собой настоящий связанный список, храня-
щий данные вместе с указателями на предыдущий и последующий элементы. Для
этого типа списков не предусмотрена возможность прямого доступа непосредствен-
но по индексу — такая операция возможна, но ее стоимость была бы слишком вы-
сока. Преимуществ у класса QLinkedList два. Во-первых, это устойчивость итера-
торов: итератор будет действительным до тех пор, пока существует элемент. Добав-
ление и удаление элементов не может повлиять на итератор так, как это может
случиться в классе QList. Во-вторых, при массовой вставке в середину списка
класс QList теряет свою привлекательность: понадобится частое расширение мас-
сива, что повлечет массовое копирование указателей. Класс QLinkedList позволя-
ет вставлять элементы рядом с текущим с одинаковой скоростью, независимо от
длины списка.
Еще одна, подобная списку, конструкция, QVector (как и Qlist), хранит дан-
ные в виде массива указателей. Однако, в отличие от QList, этот массив является
монолитным, неразреженным, указатели действительно расположены подряд, как
они следуют логически. В результате такие операции, как вставка в начало или се-
редину, вызывают сдвиг всех остальных указателей до самого конца, т.е. массовое
копирование данных в памяти. С другой стороны, получение элемента по индексу
для класса QVector является самым быстрым, при этом не приходится анализиро-
вать разреженные элементы и резервные области, как в QList. В результате QVector
очень хорошо подходит для статических данных, предназначенных только для
чтения с быстрой выборкой. Кроме того, от класса QVector удобно выводить кол-
лекции с доступом только с одного конца: например, класс QStack реализован
именно как подкласс класса QVector. Полезно знать о существовании и других
производных от QVector классов: QPolygon и QPolygonF, а также класс совмести-
мости Q3ValueVector.
Кроме вторичных — QQueue (несложный потомок класса QList) и QStack
(аналогично для класса QVector), в Qt реализованы также и ассоциативные
списки. Фактически есть две разновидности таких списков: QMap и QHash. Оба типа
выполняют одинаковые функции — сопоставляют пары “ключ-значение” и позво-
ляют извлекать значение по ключу. Разница в том, что класс QMap хранит данные
упорядоченными по ключу поиска, тогда как класс QHash использует ключ только
для быстрого вычисления хэш-функции и в дальнейшем хранит объекты в произ-
вольном порядке (не в произвольном, конечно, но не поддающемся нашему пони-
манию). При итерации по классу QMap вы будете встречать ключевые значения в
определенном порядке. Порядок зависит от того, что вы выберете ключом: есл'и это
строка — то в алфавитном порядке.
Есть небольшая тонкость при доступе к ассоциативным спискам как к массивам
с помощью перегруженного оператора “ [ ] ”. Этот оператор всегда добавляет эле-
мент, если его еще не было, даже если вы хотите только узнать факт его наличия.
Поэтому после проверки if (map [1]) элемент с ключом 1 будет создан, хотя вы со-
всем не это имели в виду. Чтобы избежать таких побочных эффектов, для проверки
наличия ключа и получения его значения используйте пару методов contains () и
value().
Существуют удобные сложные контейнеры, QMultiMap и QMultiHash, сопос-
тавляющие с ключом список значений. Эти классы, конечно, вы могли бы без труда
реализовать и сами.
Наконец, на хэш-таблице основан еще один класс-коллекция ^точнее,
множество) QSet. Фактически, это вырожденный вариант хэш-таблицы; главная
задача QSet — хранить различные ключевые величины и сообщать об их наличии
или отсутствии в списке вызовом метода contains (). С ключами класс QSet не
связывает никаких значений, важно только наличие ключа, поэтому поиск произ-
водится максимально быстро.
Теперь, когда вы знаете о строках и коллекциях в Qt, вы полностью вооружены
для дальнейших баталий.
Глава 3
Общие принципы
пользовательского интерфейса Qt
3.1. Класс QWidget
Перед тем как переходить непосредственно к конкретным классам пользователь-
ского интерфейса Qt, рассмотрим основные принципы его построения. Причина для
этого проста: как всегда в Qt, большинство методов и свойств реализованы на абст-
рактном уровне, а реальные компоненты являются только конкретизацией нужных
параметров и небольшим расширением возможностей абстрактных классов.
Первое, на что мы обратим внимание, — это класс QWidget, от которого про-
исходят почти все остальные визуальные компоненты пользовательского интер-
фейса. Класс QWidget является прямым потомком класса QObject и, кроме того,
потомком класса QPa int De vice, что позволяет обращаться к графической облас-
ти элемента управления, как к самостоятельному полотну рисования. От класса
QPaintDevice, кроме Qwidget, происходят также классы QPixmap, QPicture,
Qlmage и QPrinter.
Все множество методов и свойств Qwidget можно условно разделить на визу-
альные, т.е. те, которые влияют на внешнее представление объекта, и поведенче-
ские — те, которые служат для выполнения конкретных функций в программном
коде. Поскольку визуальных свойств больше и они нагляднее, начнем именно
с них.
3.1.1. Менеджеры размещения, положение
и размеры QWidget-объектов
В последнее время при размещении объектов пользовательского интерфейса
принято абстрагироваться от абсолютных экранных координат. Самый “горячий”
пример— размещение элементов Web-страниц в браузере. Каждый элемент Web-
страницы может иметь как относительные, так и абсолютные координаты и размеры,
выраженные в пикселях или процентах. Причем нередки случаи, когда несколько
элементов претендуют на одну и ту же территорию — например, несложно создать
несколько объектов, которые занимают 100% ширины страницы каждый. В ре-
зультате браузер вынужден включать полосу прокрутки и пользоваться другими
ухищрениями.
Размещение компонентов Qt на форме более упорядочено и больше всего напо-
минает применяемые в Java раскладки (layouts). Раньше, например в Qt 3, для этого
использовались невидимые контейнеры специального типа, которые располага-
лись на форме. Единственной функцией этих контейнеров было размещение дочер-
них элементов управления — по аналогии с Java.
В Qt 4 то же сделано слегка иначе: каждый элемент управления самостоятельно
устанавливает для себя правила размещения дочерних компонентов. Когда возни-
кает проблема размещения, соответствующие действия делегируются встроенному
менеджеру. Вот как, например, выглядит элемент управления с вертикальной
“раскладкой”.
QWidget *vbox=new QWidget;
QVBoxLayout *vbl=new QVBoxLayout(vbox);
vbox->setLayout(vbl);
To, что мы установили в качестве раскладки (а именно экземпляр класса
QVBoxLayout), является потомком абстрактного класса QLayout, от которого происхо-
дят три основных типа раскладок: QBoxLayout, QGridLayout и QStackedLayout.
Для начала обратим внимание на класс QLayout. Он по-прежнему представляет
собой класс-контейнер, внутри которого располагаются элементы управления.
Кроме, собственно, элементов управления, в размещении участвуют также запол-
нитеЛи-“спейсеры” (специальные “невидимые” компоненты, занимающие макси-
мальное, из возможного, место по горизонтали или вертикали), которые, подобно
пружинам, “раздвигают” элементы управления.
Для добавления любого дочернего компонента служит метод QLayout: :
addltem (). Его частным случаем является метод addwidget () (н$ самом деле он в
некоторый момент времени сам вызывает метод addltem ()).
Объект, добавляемый методом addltem (), имеет тип QLayoutItem. Этот класс
служит основой для всех элементов, размещаемых на форме, — как визуальных,
так и служебных. Вы должны порождать от него свои подклассы, если хотите
“заявить о себе” новым представительным типом “арендаторов” площади на форме.
Методы класса QLayoutItem возвращают такие критичные для менеджера разме-
щения параметры, как размер (geometry ()) и желаемое соотношение высоты и
ширины для данного элемента (heightForWidth ()). Все эти размеры — желаемые,
но не гарантированные менеджером размещения. При установке размера и поло-
жения дочерних элементов менеджер размещения имеет последнее слово.
От класса QLayoutltem происходят классы QWidgetltem и QSpacerltem, два
основных типа объектов размещения. Кроме этого, класс QLayout сам происходит
от класса QLayoutltem, что позволяет вкладывать одни менеджеры размещения в
другие. Вы можете легко строить суперпозиции схем размещения и реализовать
свои раскладки как варианты стандартных схем. Получить QWidget-объект,
“спейсер” или раскладку можно простым вызовом метода QWidgetltem: :widget (),
spaceritem () или layout () соответственно.
При расчете расположения элементов управления внутри менеджера размеще-
ния не последнюю роль играют свойства margin и spacing. Эти свойства задают
расстояние от границ контейнера и между дочерними компонентами в менеджере
размещения соответственно. Для начала каждый QLayoutItem-объект возвращает
желаемый размер с помощью метода sizeHint (), а также устанавливает параметры,
соответствующие названиям следующих методов: minimumsize (), maximumsize () и
expanding (). Эти величины учитываются, но не более того — как уже было сказа-
но, менеджер размещения волен делать с управляемыми элементами все, что угод-
но: изменять их размер, положение и т.д.
Метод QWidgetltem: : expandingDireations () возвращает направления, вер-
тикальное и/или горизонтальное, в которых элемент “желает” увеличиваться, если
он вообще “желает” это делать. Так реализуются “спейсеры” и другие “агрессив-
ные” объекты, например таблицы, занимающие всю возможную площадь.
Объект класса QLayout содержит свойство sizeconstraint, которое указыва-
ет, следует ли для главного элемента ограничиваться минимальными и макси-
мальными размерами или их можно проигнорировать. Сам родительский элемент
возвращается методом parentwidget () — это тот контейнер, для которого QLayout
выполняет размещение дочерних компонентов. Как происходит “упаковка” самих
элементов — достоверно не знает никто, это скрыто в коде раскладки. Два метода,
activate () и update (), позволяют пересчитать раскладку, хотя вы редко будете
их вызывать напрямую. Система сама, в основном, знает, когда нужно пересчиты-
вать параметры расположения, например при изменении размера окна и т.д.
Рассмотрим фактические, неабстрактные раскладки, часто используемые в
приложениях. Класс QBoxLayout, от которого происходят классы QVBoxLayout
и QHBoxLayout, располагает дочерние элементы один за другим — по вертикали,
горизонтали, в строку или колонку. Направление размещения описывается пере-
числимым типом QBoxLayout: .-Direction, где присутствуют такие константы,
как LeftToRight и BottomToTop. В основном, кроме указанной специфики,
“ящики” не очень отличаются от QLayout: большинство методов класса QBoxLayout
позволяют вставить или удалить новый элемент.
Аналогичная ситуация и с двумя другими “фирменными” раскладками —
QGridLayout и QStackedLayout. Первая располагает дочерние компоненты в виде
таблицы, вторая — один поверх другого, отображая один компонент из списка.
Класс QStackedLayout используется в реализации страниц с закладками. Сама
раскладка QStackedLayout, как и все остальные, не имеет визуального представ-
ления, так что переключаться между видимыми элементами пользователь должен
с помощью внешнего по отношению к раскладке элемента управления.
Если посмотреть на процесс размещения с другой стороны — а именно со сто-
роны типа QWidget — можно увидеть множество свойств, которые влияют на
размер и положение. Если с элементом не связана раскладка, то вступает в силу
свойство QWidget: : sizePolicy, которое определяет, можно ли (и если да —
то как) автоматически изменять размеры элемента управления. Тип этого свойства
определяется классом QSizePol icy, содержащим рекомендации относительно
возможности (и желательности) растягивания и сжатия объекта (по вертикали и
горизонтали).
Конструктор QSizePolicy: :QSizePolicy () воспринимает пару параметров, оп-
ределяющих политику в отношении горизонтальных и вертикальных размеров. По
умолчанию стандартные элементы управления создаются с параметрами Pref fered/
Preffered/FALSE, где Preferred=MayGrow | MayShrink, т.е. элементы пользова-
тельского интерфейса по умолчанию могут сжиматься и/или растягиваться.
Еще одной составляющей при определении динамического размера является
поле QWidget: .-sizeHint. Это поле имеет объектный тип QSize, который, в свою
очередь, эффективно представляет собой пару “ширина-высота” и с десяток мето-
дов для корректного масштабирования прямоугольника, определяемого типом
QSize.
На самом деле диапазон управления размерами даже шире, чем просто
“возможность их изменять”: характер отношения к параметрам sizeHint; можно
выразить шестью значениями:
Fixed — размеры sizeHint являются абсолютными и неизменными;
Minimum — размер можно только увеличивать, хотя к дополнительной
функциональности это не приведет;
Maximum— размер можно только уменьшать по необходимости, даже если
другие элементы и не требуют этого;
Pref fered — размер sizeHint является желаемым, но его можно сжимать и
растягивать (от растягивания работа элемента не улучшается);
Expanding — элемент в случае необходимости может быть сжат, но при воз-
можности должен быть увеличен до максимального размера;
Ignored — параметр sizeHint игнорируется, элемент занимает столько
места, сколько может, по максимуму.
Наконец, при вычислении окончательного размера используется еще кое-что.
После динамического размещения вступают в силу минимальные и максималь-
ные размеры, заданные явно. Минимальные размеры задаются с помощью методов
QWidget::setMinimumHeight(), setMinimumWidth() или setMinimumSize().
Аналогично существуют методы для задания максимальных размеров:
setMaximumHeight (), setMaximumWidth () или setMaximumSize (). Если зада-
ны и точные размеры, и поле sizeHint, то будет вычисляться сначала динамиче-
ский размер, который после этого проверяется на удовлетворение строгих огра-
ничений.
Функции принудительной установки размера задают максимальные и мини-
мальные значения равными друг другу и таким образом принудительно устанавли-
вают абсолютные и неизменные размеры элемента управления: это методы
setFixedHeight (), setFixedWidth () и setFixedSize () соответственно. Уста-
новка фиксированных размеров практически отключает динамическое масштаби-
рование, что, в общем случае, является нежелательным.
Если несколько элементов управления претендуют на одну и ту же площадь, то
во внимание принимается также фактор растяжимости. Чем больше этот фактор
(по сравнению с другими соседними элементами), тем больше относительных час-
тей получит элемент управления. Факторы растяжимости задаются методами
QSizePolicy: : setHorStretch () и QSizePolicy: : setVerStretch (). Например,
если рядом по горизонтали расположены две кнопки с факторами 1 и 2, то они по-
лучат 73 и 73 от ширины контейнера соответственно (рис. 3.1).
Рис. 3.1. Известная по Java раскладка
“стороны света” в исполнении Qt
Как можно заметить, в Qt существует множество возможностей для придания
нужного размера вашим формам и размещенным на них элементам управления.
Практически любая политика размещения может быть реализована стандартными
средствами, а если нет — вы всегда сможете создать свой класс, производный от
QWidget или Qlayout, с нужными вам функциями.
3.1.2. Системы экранных координат
Мы кратко ознакомились с тем, как менеджер размещения использует свойства
класса QWidget для размещения визуальных объектов. Результатом всех вычисле-
ний (как внутренних, так и внешних), производимых менеджером размещения,
является добровольная или принудительная установка для элемента управления
параметров, возвращаемых методами (или одноименными свойствами) х(), у(),
widthO, height О, а также их собирательными эквивалентами — size О и
pos (). Обратите внимание, что позиция pos () отсчитывается относительно роди-
тельского контейнера, а не окна верхнего уровня.
Тем не менее иногда вы испытываете необходимость получить глобальные коор-
динаты из локальных или наоборот. Для этого служат шесть методов отображения.
Самый общий из них, QPoint QWidget: :mapTo (QWidget *parent, const
QPoint &pos) const, принимает родительский контейнер и точку нашего эле-
мента, а возвращает его глобальные координаты. Метод mapFrom() выполняет об-
ратное преобразование.
Для перевода в координаты конкретного родительского контейнера и обратно пред-
назначены следующие два метода: QWidget: :mapToParent (), mapFromParent (). Два
других метода выполняют аналогичное преобразование относительно экранных
координат; QWidget: :mapToParent (), mapFromParent (). Все эти методы в каче-
стве и параметра, и результата воспринимают координаты точки в виде объекта
(ссылки на объект) QPoint.
3.2. Рисование элемента управления
Когда размер и положение элемента управления “утверждены” родительским
контейнером, он получает команду на отрисовку. В отличие от предыдущих вер-
сий, QWidget-объект получает доступ к полотну рисования только в момент вызова
метода paintEvent (). Все остальные устаревшие методы (например, erase ()) не
выполняют никаких действий, хотя и не удалены в Qt 4 полностью.
Причин для возникновения события paintEvent () может быть множество —
пользователь может скрыть окно на втором плане, а после открыть его. Возможно,
вы сами или какая-то другая часть приложения вызывает методы repaint () или
update (). Метод repaint () непосредственно синхройно перерисовывает элемент
управления, вызывая метод paintEvent (). Этот метод может вызвать бесконеч-
ный цикл, если будет вызываться из метода paintEvent (). Вызов метода
repaint () допустим только в действительно критических ситуациях, например
для анимации, протекающей в реальном времени.
Метод update () помечает элемент как требующий перерисовки. При этом не-
сколько методов update () могут кэшироваться: последовательные их вызовы, вы-
данные за короткий промежуток времени, будут вызывать только один метод
paintEvent () с общим регионом отрисовки, вычисленным как объединение от-
дельных регионов с помощью метода QRegion: : unite (). Вызов update () никогда
не приводит к зацикливанию, оптимизирует скорость отрисовки и поэтому реко-
мендован к использованию.
Оба метода, update () и repaint (), не выполняют ничего, если элемент скрыт
или отключен.
Обратите внимание на два момента, присущих Qt 4. Во-первых, все логические
параметры, методы и свойства, связанные с отрисовкой фона, уже не действитель-
ны: они существуют только для совместимости, но не выполняют никакой работы.
Фон отрисовывается автоматически перед вызовом метода paintEvent (), если не
указан флаг Qt: :QRepaintNoErase.- При этом автоматически реализуется про-
зрачность (альфа-канал).
Во-вторых, раньше при рисовании вы должны были заниматься такими веща-
ми, как двойная буферизация, подавление фликера (мерцания) и т.д. В Qt 4 все эти
проблемы решает сама библиотека, а двойная буферизация включена по умолча-
нию. Кроме подавления фликера, с помощью автономного (off-line) буфера реали-
зуются также альфа-канал и сглаживание по сцене, отчего новые приложения Qt
выглядят не хуже продуктов Flash Animation.
В ответ на запрос “обновление графического представления” простые классы
могут полностью перерисовать свое визуальное представление. Сложные элементы
могут оптимизировать свою отрисовку, исследуя запрашиваемый регион отрисовки
с помощью метода QPaintEvent: : region (). Результаты рисования с отсечением и
без него не будут отличаться, поскольку рисование все равно будет автоматически
отсекаться по этой области, но многие элементы (например, треугольники трех-
мерных сцен при программном рендеринге) могут вообще не рисоваться, если будут
отсечены с использованием какого-либо эвристического метода.
Следует учитывать разницу между регионом (QRegion) и прямоугольником
(QRectangle). Регион может быть образован из прямоугольника, эллипса, полиго-
на или битовой карты. Если вас интересует прямоугольник^ описывающий данный
регион, то вы можете вызвать метод QRegion: :boundingRect (). Также можно
проверить, попадает ли данная точка или прямоугольник в регион, вызвав метод
bool QRegion: : contains () с параметром типа QPoint или QRect соответственно.
Для регионов определены такие логические операции, как объединение, пересечение
и дополнение. При необходимости можно получить декомпозицию региона на непе-
рекрывающиеся прямоугольники: для этого служит метод QRegion:: rects ().
3.2.1. Инструменты и механизмы рисования
Есть несколько встроенных в Qt механизмов рисования. Один из них, основан-
ный на QPainter, относится к рисованию элементов управления — этим мы сейчас
и займемся. Другой, связанный с векторной графикой и объектом QCanvas, описан
в главе, посвященной двухмерной графике.
В Qt 4 рисованием объектов управления занимаются несколько важных клас-
сов, центральным из которых является QPainter. Этот класс позволяет рисовать
большинство графических примитивов, таких как линии, прямоугольники, эллип-
сы, дуги, сектора, а также — текст и битовые карты. Типичное использование этого
класса — создание, установка параметров, рисование и разрушение — происходит
в теле метода paint Event ().
Установка параметров часто занимает большую часть работы: вы можете уста-
новить тип, цвет и размер кисти, пера, фона, настройки шрифта и т.д. Важное
свойство Qt 4 — влияние матрицы отображения, рассмотренной далее, на все на-
стройки (такие, как ширина кисти или ориентация заливки). Это очень важное
визуальное свойство, позволяющее, например, при увеличении изображения про-
порционально увеличивать и толщину линий, в том числе — узора заливки. В соче-
тании с двойной буферизацией, нецелочисленными графическими операциями и
антиалиасингом (сглаживанием) это позволяет выполнять плавное масштабирова-
ние изображений и осуществлять качественную печать при различных разрешени-
ях принтера и экрана. Аналогично это имеет влияние на деформацию и перемеще-
ния (вращение и т.д.). Ни одна из известных библиотек рисования, кроме
Macromedia Flash, не поддерживает такие режимы — все, что нарисовано стан-
дартными механизмами от Turbo Pascal до Windows GDI, выглядит как любитель-
ский коллаж.
К другим нововведениям Qt 4 относятся градиентные заливки, что достигается
передачей экземпляра класса градиента QGradient (вернее — одного из его под-
классов) в метод заливки класса QBrush. Реализованы три готовых типа градиента:
QLinearGradient, QRadialGradient и QConicalGradient. Вы легко можете соз-
дать свой собственный градиент, если знаете, как это сделать в виде формул. Гра-
диенты реализуются весьма гибко: область заливки представляется как изменение
условного действительного параметра от нуля до единицы. В этом диапазоне может
быть определено как угодно много “стоп-поинтов”, т.е. положений, в которых зна-
чение цвета фиксированно. Между этими точками цвет будет вычисляться по пра-
вилам интерполяции. Конечно, координаты будут различными для каждого типа
градиента. Например, следующий код устанавливает радиальный градиент, крас-
ный — в центре, синий — в средней зоне и зеленый — на периферии круга:
QRadialGradient rg(QPointF(100,100),100);
rg.setColorAt(0,Qt: .-red) ;
rg.setColorAt(0.5,Qt::blue);
rg.setColorAt(1,Qt::green); *
Также к настройкам относятся методы преобразования координат: viewport (),
window () и matrix () позволяют наложить методы рисования на матрицу (аффин-
ных) преобразований, в частности, масштабирование с помощью отображения
(сэмплинга) логических размеров, задаваемых методом setWindowO, на физиче-
ское окно просмотра, определяемое методом setviewport (). Простые преобразо-
вания матрицы задаются методами scale (), rotate (), shear () и translate () —
больше о преобразовании матриц рассказывается при рассмотрении двухмерной
графики.
Кроме преобразования матрицы, есть еще два других важных режима: режим
отсечения и режим наложения. Отсечение может быть установлено как методом
setClipPath(), так и методом setClipRegion (), а также в самом простом слу-
чае— методом setClipRect (). В любом случае второй опциональный параметр
позволяет дополнительно задать логическую операцию, согласно которой новый
регион будет добавлен, пересечен или заменит предыдущий. Эти параметры описа-
ны константами Qt: : ClipOperation, значение по умолчанию — Replaced ip.
Мы уже познакомились с объектом класса Qregion. Образно говоря, это битовая
карта, воспринимающая различные примитивы в качестве “заполнителей” и впо-
следствии служащая маской при отображении. Как вы помните, единственная воз-
можная декомпозиция региона состояла в представлении его в виде множества
прямоугольников, несмотря на то, из каких фигур первоначально строился этот
регион.
В Qt 4 введен новый механизм (реализуемый классом QPainterPath), позво-
ляющий собирать воедино различные графические траектории, например линии,
дуги или даже кривые Безье (рис. 3.2). Вы также можете добавить объект типа
QRegion — при этом он будет разложен на множество прямоугольников. В ре-
зультате вы получите сложное векторное изображение, которое можно обрабаты-
вать как одно целое для отрисовки контура, фигуры или для маскирования об-
ласти отсечения. Для рисования пути как он есть достаточно вызвать метод
QPainter: : drawPath (). В качестве декомпозиции пути применяются различные
полигоны, как замкнутые, так и открытые. Пути, как программные сущности,
реализованы в некоторых операционных и оконных системах, так что использова-
ние более сложных (чем регионы) путей и объектов может выполняться даже еще
эффективнее.
Рис. 3.2. Векторная графика Qt
отвечает самым высоким требованиям
интерактивности
Возвращаясь к классу QPainter, упомянем еще два интересных, в плане визу-
альных эффектов, параметра. Константы QPainter: : CompositionMode задают
множество возможных режимов смешивания цветов при наложении полупрозрач-
ных изображений. Возможны различные прямые и инверсные режимы, при кото-
рых изображение переднего или заднего плана получает дополнительные. шансы
“видимости”. Описать эти режимы на словах сложно— поэкспериментируйте с
этим параметром: многие эффекты (например, получение или потеря фокуса ввода
объектом управления) вполне можно реализовать только с его помощью. Наконец,
битовый набор RenderHints служит для включения сглаживания (в том числе би-
линеарного) по всей сцене (области отрисовки), сглаживания шрифтов и т.д.
Три, по-своему интересных, класса происходят от класса QPainter: класс
Q3 Painter служит для целей совместимости с версией Qt 3, QDi rec t Pa inter ис-
пользуется в Qt/Embedded для прямого доступа к графической подсистеме PDA и
смарт-фонов в обход системных вызовов и, наконец, QStylePainter является под-
классом QPainter, реализующим все многообразие draw-методов интерфейса
QStyle. Возможно, вы будете так же реализовать свои классы, сделав их совмести-
мыми со стилями Qt.
3.3. Цвета и палитры
При отрисовке элемента управления вы несомненно столкнетесь с переключени-
ем цветов. При реализации собственных элементов управления создатели Qt на-
стоятельно рекомендуют пользоваться механизмом палитр, заложенным в Qt, а не
использовать абсолютные значения.
Палитра представляет собой набор цветов, точнее несколько наборов (для каж-
дого из состояний элемента управления). В дальнейшем обращение к цветам про-
исходит не по абсолютным координатам в выбранном цветовом пространстве
(например, RGBA1 или CMYK1 2), а по индексу цвета в палитре. Таким образом мож-
но отделить вопросы цвета от функциональности и, кроме того, реализовать допол-
нительные возможности, такие, как придание стилей элементу управления или
даже создание анимации.
В терминах Qt палитра представлена классом QPalette, так что если какой-то
из методов или какое-то свойство использует или возвращает палитру, то тип аргу-
мента или результат всегда представляет собой указатель на экземпляр класса
QPalette. Палитра представляет собой группы цветов для активного (т.е. элемент
имеет клавиатурный фокус ввода), неактивного и “отключенного” состояний. Ино-
гда активное и неактивное состояния неразличимы визуально, но все равно в па-
литре задаются все группы.
Группы цветов представлены, в свою очередь, объектами класса QColorGroup —
после создания группы вы устанавливаете ее в палитру методами QPalette: :
setActive(), QPalette::setinactive() и QPalette::setDisabled().
В свою очередь, группы состоят из отдельных цветов, представляемых классом
QColor. Этот класс уже непосредственно владеет абсолютным представлением цве-
та, задаваемого в кодировках RGB (опционально с альфа-каналом) либо HSV3.
Обратите внимание на то, что автоматического преобразования цветовых коорди-
нат не производится: класс явно содержит тип применяемой кодировки. Если уж
быть совсем точным, то триплет RGB может быть представлен еще одним уровнем
абстракции, типом QRgb, но этот тип представляет собой просто 32-битовое целое,
которое в мире 1386 имеет тип unsigned int.
Не будем в деталях исследовать QColor — это достаточно тривиальный класс,
вместо этого уделим внимание цветовым группам, поскольку именно с ними вам
придется работать. Отдельные цвета представлены в группе свойствами-членами с
такими мнемоническими именами, как Foreground, Background или ButtonText.
Каждый цвет в группе представлен ролью, а сами роли заданы в перечислимом типе
QColorGroup: :ColorRole. Таким образом, группа представляет собой ассоциатив-
ный хэш-массив, отображающий множество ColorRole на пространство QColor.
1 RGBA — система цветопередачи RGB (палитра “red-green-blue”— “зеленый-красный-синий”), расши-
ренная параметром alpha, используемым для управления смешиванием цветов. — Примеч.ред.
2 CMYK — палитра “cyan-magenta-yellow-black” (“голубой-сиреневый-желтый-черный”); использу-
ется в издательских системах как более четко передающая цвета, чем палитра RGB. — Примеч.ред.
3 HSV — сокр. от “hue-saturation-value” (“оттенок-насыщенность-значение”). — Примеч.ред.
Самым общим методом, позволяющим установить цвет в качестве ролевого, яв-
ляется void QPalette::setColor(ColorRole r, const QColor &c). Как вид-
но из синтаксиса, таким способом можно установить любой цвет (используя
“кастинг” — даже для несуществующей роли). Аналогично получить цвет для дан-
ной роли можно с помощью метода QColor& QPalette: :color(ColorRole г).
Для популярных ролей существуют специальные методы, например QPalette: :
foreground(), QPalette: : light (), QPalette: :dark() и т.д.
Помимо цвета, с ролью связана также кисть — метод рисования, задаваемый
экземпляром класса QBrush. Кисть служит для различных вариантов заливок: от
сплошной SolidPattern до NoBrush и включая различные вертикальные, гори-
зонтальные и диагональные узоры. Особый тип кисти — битовая карта, служащая
маской заливки. Кисть задается методом QjPalette: : setBrush (), а опрашивается
через метод QPalette: :brush(). Параметры использования кисти устанавлива-
ются аналогично методам setColor ()/color () — естественно, везде, вместо эк-
земпляра типа “цвет”, указывается экземпляр типа “кисть”.
Отдельно взятый элемент управления совсем не обязан иметь свою палитру:
приложение имеет цветовую палитру по умолчанию (QApplication: :palette), и
все элементы наследуют ее, пока не будет указана специфическая частная палитра.
Метод QApplication: : set Palette () задает палитру для всего приложения.
Свойство QWidget: :palette хранит текущую палитру для элемента управле-
ния: либо общую для всего приложения, либо родительскую, если она переопреде-
лена на каком-то из уровней вложения, либо частную, определенную для данного
элемента.
Установка собственной палитры происходит с помощью команды setPalette ().
Команда unset Palette () служит для сброса частной палитры: палитра устанав-
ливается аналогично палитре родительского контейнера. Логически это равно-
сильно переходу вверх по иерархии контейнеров от нашего элемента до объекта
приложения, в результате чего будет использована первая переопределенная па-
литра. Свойство и метод ownPalette возвращают логическое значение, сигнализи-
рующее о том, является ли палитра элемента частной (true) или унаследованной
(false).
Дополнительно в Qt 3 существовали методы класса QWidget, служащие “лазей-
ками”, обходящими прямое обращение к палитрам, и выполняющие операции с ми-
нимумом параметров. В числе таких методов — setPaletteForegroundColor (),
setPaletteBackgroundColor() и setPaletteBackgroundPixmap(). На самом
деле все равно неявно вызывался метод setPalette (), так что эта техника “хака”
была признана бесполезной в Qt 4, хотя перечисленные методы существуют по-
прежнему.
С методами установки палитр связана еще одна тонкость: режимы фона и пе-
реднего плана. Эти режимы устанавливают связь между понятиями “фон/передний
план” и ролями в цветовой группе. Когда вы устанавливаете, например, фон с по-
мощью метода setPaletteBackgroundColor, то будет модифицирована именно та
роль, которая в данный момент сопоставлена с фоном. Для установки этих режи-
мов предназначены методы setBackgroundRole () и setForegroundRole ().
Вообще, режимы фона служат для заливки элемента перед получением управ-
ления обработчиком paintEvent (). По умолчанию фон при этом окрашивается в
цвет PaletteBackground, и это кажется логичным. Но некоторые элементы
управления могут иметь совсем мало “фона” и, в основном, состоять из рабочей об-
ласти или еще каких-то дочерних элементов. В таком случае их перерисовка в Qt 3
будет сопровождаться значительным “фликером”, т.е. мерцанием. Для таких эле-
ментов можно переопределить режим фона, установив очистку поля, используя
цвет, отличный от цвета фона. Например, поля ввода текста, основной цвет кото-
рых соответствует значению PaletteBase, устанавливают его в качестве цвета за-
ливки. В Qt 4 применяется двойная буферизация по умолчанию, так что никакого
фликера не наблюдается в принципе. В Qt 3 была такая сложная возможность, как
setBackgroundMode, — игры с фоном для сложных компонентов. Теперь это все
уже в прошлом, фон отрисовывается автоматически.
С другой стороны, все “старые” возможности остались в силе — только они
больше не делают ничего полезного.
3.4. Текст, шрифты и их начертания
Одной из основных операций при отрисовк^ элемента управления является вывод
текста. Обратите внимание: вывод текста всецело принадлежит к классу Qwidget.
Таким образом, в Qt вывод текста без определения компонента интерфейса — не
имеющая смысла операция. ВывРд текста производится шрифтом по умолчанию.
Координаты текста задаются относительно системы координат элемента управле-
ния. Собственно печать производится методом drawTextfint х, int у, const
QString &str). Как вариант, координаты могут быть заданы объектом типа
const QPoint &pos — функциональность этих вариантов полностью идентична.
Как и рисование, вывод текста должен производиться только в вызове метода
paintEvent().
При выводе текста обычно возникают две дополнительные задачи: изменение
цвета и шрифтового оформления. Цвет текста полностью определяется выбранной
схемой, за цвет символов и фона, как это описано выше, отвечают две определен-
ные роли в цветовом наборе.
Под шрифтом понимают целый набор характеристик: категория шрифта, назы-
ваемая шрифтовым семейством, размер, измеряемый в пунктах, а также специ-
альные эффекты (начертания) вроде жирности в процентах или наклона букв.
Дополнительные, более изощренные возможности подразумевают изменение ори-
ентации текста на рабочей поверхности, но для элементов управления штатно они
не реализованы. Такие эффекты реализуются преобразованием системы координат
и последующей обработкой изображения в буфере.
Работа со шрифтами во многом напоминает работу с цветовыми схемами: пока
не указано обратное, элемент управления наследует настройки у своего контейне-
ра, вплоть до приложения. Настройки шрифта содержатся в поле font с типом
QFont. Одноименный метод возвращает значение шрифта. Изменить настройку
шрифта для данного элемента можно с помощью команды (метода) setFont ():
QFont f( "Helvetica”, 12, QFont: .-Bold );
setFont( f ) ;
Подобная техника позволяет единожды создать новый шрифт, после чего заре-
гистрировать его для нескольких элементов. Еще одна возможность придать одни
свойства группе элементов — заключить их в один контейнер с нужными свойствами.
С изменением шрифта связано событие типа QEvent: : Fontchange, стандартно
вызывающее перерисовку всего элемента, включая изменение геометрии. Если вы
изменяете этот обработчик, то, как правило, обязательно должны вызвать метод
updateO. Два метода fontinfo() и fontMetrics () возвращают связанные со
шрифтом настройки, а именно указатели на типы QFontInfo и QFontMetrics со-
ответственно.
Основным связанным со шрифтом объектом является QFont. Как это принято,
работа со шрифтами производится динамически: все настройки выполняют роль
рекомендаций; если указанные начертания или эффекты недоступны на устройстве
вывода (это может быть экран, принтер или плоттер), то они заменяются наиболее
близкими. Если объект, ответственный за рисование (QPainter), не может отобра-
зить символ даже после всех возможных попыток, то результатом будет изображе-
ние незакрашенного прямоугольника (рис. 3.3).
Рис. 3.3. Пример Character Мар отображает
символы различных шрифтовых наборов.
Как видите, отсутствующие в наборе символы
отображаются либо пустыми прямоугольниками,
либо вообще не отображаются, если данный
диапазон символов явно не принадлежит шрифту
Основной характеристикой при отображении шрифта является размер. В печат-
ном деле применяются пункты, являющиеся х/12 частью дюйма (если вам кажется,
что на вашем экране это не так — обратите внимание на масштаб, под которым вы
рассматриваете документ). Для более точного управления размером можно указы-
вать дробные части пунктов, хотя обычно деления меньше полупунктов не исполь-
зуются. Для задания размера шрифта в целых и дробных долях пунктов служат
методы QFont::setPointSize(int pointsize) и QFont::setPointSizeFloat
(float pointsize). Размер в пунктах возвращает метод QFont: :pointsize(),
но если размер указан в пикселях, этот метод возвратит значение -1. Похожий ме-
тод QFont: : deci Point Size () возвращает размер в десятых долях пункта.
Иногда размер шрифтов указывается в пикселях, т.е. точках растра. Это не
самая лучшая идея, поскольку во многих случаях размер элемента управления
будет масштабироваться, и ваши усилия не будут вознаграждены. Тем не менее
если вы хотите достичь идеального “попадания” надписей, особенно в миниатюр-
ных масштабах, то вы, вероятно, будете вынуждены работать с пикселями. Уста-
навливать и получать размер шрифта в пикселях можно с помощью методов
QFont: : setPixelSize () и QFont: :pixelsize (). Задание размера в пикселях де-
лает вывод текста зависимым от устройства, что, в общем случае, нежелательно —
то, что читается на экране при 96 dpi может совершенно “исчезнуть” на лазерном
принтере с 1200 dpi. Поэтому везде, где возможно, задавайте размер в пунктах.
Еще одним важным свойством шрифта является его шрифтовая группа. Под
этим понимают “картинку” шрифта, его характерное графическое представление.
Отличают два основных типа начертаний — с засечками и без. К первому типу, на-
пример, относится Times Roman, ко второму— Arial. Также выделяют моноши-
ринные шрифты, каждая буква которых имеет одинаковый размер, такие как
Courier New. Такие шрифты давно вышли из употребления в печатном деле, но по-
прежнему применяются на терминалах, поэтому придают тексту эффект
“техногенности”, в частности они приняты при форматировании текстов программ,
как и в этой книге, или служат для точного позиционирования “букв-спрайтов” в
ASCII-графике.
При указании шрифтов можно только делать предположение о том, установле-
ны ли они на рабочей станции пользователя. Обычно, за редким исключением,
шрифты не распространяются вместе с приложениями и, не в последнюю очередь,
по причинам лицензирования. Так что существует механизм, называемый
“подстановкой шрифтов”. В самой операционной или оконной системе доступны
функции, выполняющие эти операции. Кроме того, Qt также имеет встроенный
поиск шрифтов, если абсолютно идентичный шрифт недоступен.
Qt в своей работе избегает прямого манипулирования именами шрифтов. Вместо
этого к шрифтам обращаются по имени так называемого семейства. Семейство —
это нечто более конкретное, чем тип, и несколько более общее, чем конкретный
шрифт. Скажем, Helvetica — это типичное семейство шрифтов.
Для явной замены семейственных имен конкретными именами шрифтов слу-
жит метод QFont: : substitute (). Он возвращает первый шрифт, представляю-
щий данное шрифтовое семейство. Для добавления и удаления элементов замены
служат методы QFont::insertsubstitution() и removesubstitution().
Можно загрузить сразу целую таблицу шрифтовых соответствий с помощью вызова
QFont::insertSubstitutions(const QString &familyName, const QStringList
& subs ti tut eNames). Как видите, параметрами выступают имя семейства и
список замещающих шрифтов. Тип списка— QStringList, т.е. список строк.
Просмотреть результирующий список можно с помощью статического метода
QFont: : substitutions () — возвращаемый список будет упорядочен по алфа-
виту.
Для установки шрифта по семейству служит метод QFont: : set Family (). Дру-
гие эффекты (например, жирность линий, наклон символов и т.д.) устанавливают-
ся группой следующих однотипных set-методов.
Метод QFont: : setweight () устанавливает жирность шрифта, от 0 до 100.
Нормальная жирность соответствует значению 50.
Метод QFont: : set Bold () устанавливает жирность начертания равной 75.
Метод QFont: :setltalic() устанавливает или сбрасывает наклонное на-
чертание.
Методы QFont:: setOverline (), QFont: : setStrikeOut () и QFont:: setUhderline ()
обеспечивают надстрочную линию, зачеркивание и подчеркивание соответ-
ственно.
Метод QFont:: setstretch () задает фактор горизонтального растяжения
шрифта, выраженного в процентах, т.е. 150 обозначает “в полтора раза ши-
ре, чем обычно”.
Еще более гибкое управление отображением обеспечивается методом
QFont: : setStyleStrategy (Stylestrategy s), который устанавливает страте-
гию стилей. Стратегия определяет, какие шрифты и в каком порядке будут выбра-
ны в качестве рабочих при поиске совпадений. В качестве предпочтений можно
указать шрифты устройства, битовые или векторные шрифты (последние в терми-
нах Qt называются outlined-шрифтами, т.е. контурными). Также можно принуди-
тельно отключить сглаживание (антиалиасинг) либо, напротив, задать его как же-
лаемый режим. И наконец, можно потребовать, чтобы шрифты были совместимы с
интерфейсом OpenGL (Open Graphics Library). По умолчанию никаких предпочтений
не делается, чему соответствует константа QFont: : Pref erDef ault.
Два ключа, QFont: : PreferQuality и QFont: : PreferMatch, отвечают за пред-
почтение качества или точного Совпадения размера — в первом случае будут вы-
браны точные размеры, реализованные в самом шрифте, в ущерб точному следова-
нию дробным размерам. Эти ключи можно комбинировать с перечисленными выше
с помощью побитового OR.
Еще одним методом “тонкой настройки” является QFont: : setStyleHint ().
Этот метод устанавливает не только стратегию, но и задает (через первый параметр)
“подсказку”, чем заменять указанное шрифтовое семейство, если оно не доступно.
Данный метод не работает под X Windows.
Наконец, рассмотрим последний из упоминавшихся важных классов, задейст-
вованных при работе со шрифтами, а именно QFontInfo. Этот класс, а точнее его
экземпляры, служит своего рода обратной связью между программистом, Qt и
операционной системой. Класс QFontlnf о содержит, в основном, те же поля, что и
класс QFont (например, family), однако они отличаются по значению: если класс
QFont определяет желаемые значения, то класс QFontlnfo, напротив, содержит
конкретные значения уже установленного шрифта. Например, если вы задали
только что описанный ключ QFont: :PreferQuality, то реальный размер может
отличаться от задаваемого в классе QFont, если он не совпадает с заранее опреде-
ленными “точными” размерами в самом шрифте.
Возможно, вы заинтересованы в таких точных метрических сведениях о симво-
лах шрифта, как максимальная или средняя ширина символов или размер описы-
вающего прямоугольника. Эти параметры можно получить, исследовав экземпляр
класса QFontMetrics, связанный с данным экземпляром QFont.
Еще один, редко используемый и связанный со шрифтами класс QFontDatabase
служит для исследования шрифтов, доступных на вашей рабочей станции. Типич-
ное применение в ваших программах — создание нестандартного диалога выбора
шрифта, хотя это вряд ли можно поощрять, поскольку существует стандартный
способ решения этой проблемы. В любом случае, такая возможность есть, вы може-
те просматривать доступные шрифты, доступные для каждого размеры, в том чис-
ле “гладкие”, т.е. те, для которых не требуется программное масштабирование.
Таким образом, программисты могли избежать искажений масштабирования при
использовании растровых шрифтов. В современном мире роль растровых шрифтов
невелика, и практически все современные шрифты, Typel, FreeType и TrueType,
могут быть масштабированы без потери качества. Исключение составляют специ-
фические шрифты, например системный или рассчитанные на использование ма-
лых размеров символов.
3.5. Состояния элемента управления
Теперь, когда мы умеем отрисовывать элемент управления, рассмотрим более
содержательные функциональные возможности. Начнем с состояний элемента
управления.
Первым, самым “заметным” состоянием является видимость элемента управле-
ния. Для переключения видимости элемента служат два слота: show() и hide ().
То же самое делает метод setvisible (bool). На видимость элемента управления
также влияет видимость контейнеров до верхнего уровня включительно: если кон-
тейнер невидим, то невидимо и все его содержимое. Будет ли такой невидимый
элемент занимать место на форме — зависит от менеджера размещения.
Когда элемент переключает свое состояние видимости, то он получает уведом-
ления в виде событий showEvent () и hi deEvent (). Такие динамические элемен-
ты, как анимация или видео, могут в целях эффективности отключить пересчет но-
вых сцен в невидимом состоянии. Для анализа видимости приложение может ис-
пользовать свойства visible, shown и hidden.
Также существуют два оставшиеся от Qt 3 метода: setShown () и setHidden ();
оба воспринимают логические параметры и реализуют ту же логику, что и методы
show () и hide (). В Qt 4 аналогичные действия выполняются путем вызова метода
setvisible().
Нужно отметить, что понятие “видимость” не связано с реальным отображением
элемента на экране: он может быть скрыт другим окном, находиться на неактив-
ном виртуальном “десктопе” или принадлежать минимизированному приложению.
Во всех этих случаях свойство hidden может возвращать значение false. Также
важно учитывать, что даже если метод hideEvent () будет вызван спонтанно,
is Visible () -значение по-прежнему будет равным true. К спонтанным событиям
относят системные события, имеющие происхождение за пределами вашего при-
ложения, а также, в силу сходности, repaint (), который тоже вызывается в обход
обычных очередей событий Qt.
Еще одним полезным и распространенным свойством является “доступность”.
Элемент управления, как вы, разумеется, знаете, может находиться в недоступном
(выключенном, “сером”) состоянии. При этом элемент, как обычно, отображается
на форме, но пользователь ставится в известность, что данная возможность в дан-
ный момент по той или иной причине недоступна — обычно все цвета элемента за-
меняются оттенками серого. Для отображения элемента в этом состоянии в палитре
даже предусмотрен специальный набор цветов. Такой элемент никогда не получает
клавиатурный фокус ввода и не реагирует на нажатия кнопок мыши.
За доступность элемента отвечает свойство enabled. Установка этого свойства
производится методом set Enabled (). Если нужно отслеживать, когда меняется
режим доступности, то вы должны переопределить обработчик changeEvent () для
события QEvent: : EnabledChange.
Довольно сложное поведение задается методом bool isEnabledTo (Qwidget
*ancestor): возвращаемое значение устанавливается равным true, если все роди-
тельские контейнеры, до (но не включая) ancestor, не заблокированы.
3.6. События элемента управления
В процессе жизненного цикла элемент управления получает события, влияю-
щие на его поведение. При создании собственных элементов управления вы
можете и/или должны переопределить все или некоторые из этих обработчиков.
Мы уже встречали пару таких событий, как QEvent: : Fontchange () и
QEvent: : EnabledChange (), теперь же посмотрим на эти события в целом.
Центральным обработчиком-диспетчером событий элемента управления является
защищенный виртуальный метод (protected virtual) bool QWidget::event
(QEvent *e). Этот метод получает все события и диспетчеризирует их в ряд других
обработчиков. При создании собственных подклассов вы можете, как это часто бы-
вает с виртуальными защищенными методами, переопределить этот обработчик в
своем подклассе. Однако часто удобнее переопределять более частные методы, о ко-
торых сейчас и пойдет речь. Обратите внимание на то, что обработка событий реа-
лизована на уровне класса QObj ect, а в классе QWidget она только расширена.
Одним из интересных для обработки событий жизненного цикла элемента
управления является событие типа QCloseEvent, возникающее как реакция на
действия пользователя по закрытию формы или всего приложения или на про-
граммный вызов QWidget:: close (). Главный диспетчер QWidget:: event
направляет обработку этого события специальному обработчику closeEvent
(QEevent *е). Стандартная обработка этого события подтверждает выполнение
выхода, вызывая метод e->accept (), что приводит к сокрытию элемента управле-
ния (Qwidget:: hide ()) и, опционально, его разрушению, если он был создан
с ключом WDestructiveClose. Главное окно приложения QApplication: :
ma inWidget при этом дополнительно выходит из главного цикла и возвращает
управление из метода QApplication: : exec (). Обычной практикой при переопре-
делении QCloseEvent-обработчика является запрос на сохранение несохраненных
документов или знакомый по многим приложениям диалог “Вы действительно хо-
тите выйти из приложения?”.
Определенная логика связана с обработкой ввода с клавиатуры: стандартный
диспетчер QWidget: : event () при получении клавиатурного ввода в первую оче-
редь проверяет, не является ли это нажатием клавиши <ТаЬ> или <Shift+Tab>.
В таком случае ищется следующий элемент управления, которому бы следовало
передать фокус клавиатурного ввода. При этом оба элемента управления — тот, ко-
торый был в фокусе, и тот, который получает фокус, — получают события типа
QFocusEvent. Само событие содержит информацию, был ли фокус получен или утра-
чен, а также причину изменения фокуса ввода, в данном случае — нажатие клавиши.
Для получающего ввод это событие транслируется в событие focus InEvent (QEevent
*е), для утратившего — в focusOutEvent (QEevent *е). Стандартная обработка
получения фокуса — перерисовка элемента управления.
Если нажатие на клавиатуре не приводит к переключению фокуса ввода, то эле-
мент управления получает события типа QKeyEvent, после трансляции превра-
щающиеся в события keyPressEvent () и keyReleaseEvent () для каждого нажа-
тия и отпускания клавиши соответственно. Для восприятия клавиатурного ввода
элемент управления должен в данный момент в принципе принимать фокус ввода и
быть в фокусе. Если ваш элемент — не поле ввода, то вы должны игнорировать это
событие, вызывая для него в своем обработчике метод е->ignore (), чтобы роди-
тельский контейнер (окно) мог обработать это событие. Для клавиатурного ввода
нет никаких действий по умолчанию, кроме закрытия всплывающего окна в ответ
на нажатие клавиши <Esc>.
Группа событий связана также перемещениями и нажатиями кнопок мыши.
При перемещении курсора мыши на элемент управления он получает событие
enter Event (). Когда курсор покидает рамки элемента управления вызывается со-
бытие leaveEvent().
Обработка перемещения курсора мыши зависит от установки режима слежения.
Если вы установите режим слежения методом QWidget: : setMouseTracking (TRUE),
то для каждой позиции курсора (с некоторой дискретностью) будут возникать со-
бытия mouseMoveEvent (QMouseEvent *e). Если режим слежения отключен, то
события mouseMoveEvent () будут возникать только в местах, где пользователь
нажимает одну из клавиш. Получить в обработчике координаты курсора мыши от-
носительно элемента управления вы можете с помощью метода e->pos ().
По аналогии с клавиатурными событиями, существуют два события для нажа-
тия и отпускания кнопок мыши, mousePressEvent и mouseReleaseEvent. Стан-
дартная обработка не выполняет никаких операций, кроме закрытия всплываю-
щих окон, если пользователь щелкает мышью за их пределами.
Существует также отдельный обработчик mouseDoubleClickEvent (), который
по умолчанию транслируется в одинарное нажатие. Перед получением двойного
нажатия, тем не менее, будут вызваны обработчики первого нажатия и отпускания
кнопки мыши.
Обработка вращения колесика производится в обработчике wheel Event ().
Положительные значения прокрутки обозначают прокрутку вперед, отрицатель-
ные — назад. Единица прокрутки (notch) определена в 120 единиц и задается кон-
стантой WHEEL_DELTA. Это рассчитано на будущие высокоточные устройства, воз-
можно, без делений. Поддерживаются два колесика прокрутки: вертикальное и
горизонтальное. Также объект события содержит позицию курсора мыши и кла-
виатурные модификаторы (<Shift>, <Ctrl>, <Alt>) на момент события прокрутки,
что важно в асинхронных средах, где с момента события до его обработки может
пройти заметное время, за которое пользователь может переместить курсор или
нажать клавиши-модификаторы. Стандартная реакция на это событие — игнори-
рование события вызовом е->ignore (): вы должны поступать точно так же, если
не обрабатываете это событие, чтобы оно могло быть обработано окном более высо-
кого уровня.
За визуальное представление элемента управления отвечают три события.
Первое, resizeEvent, приходит как результат изменения размера. Экземпляр
события содержит старый и новый размер. Сразу после обработки события
resizeEvent будут вызваны методы очистки и перерисовки, так что в
resizeEvent не нужно заниматься рисованием. Единственной операцией по умол-
чанию остается метод updateMask () для элементов с автоматически устанавли-
ваемой маской.
Вторым важным событием является moveEvent (QMoveEvent *е) — оно возни-
кает при изменении положения компонента и вызывается, когда тот уже находится
в новом положении. Старое положение доступно через e->oldPos ().
Наконец, третье событие, одно из важнейших для формирования графического
интерфейса, — paintEvent (QPaintEvent *е)г. Это событие возникает, когда эле-
мент управления должен быть перерисован по одной из многих причин: он был
скрыт другим элементом или другим приложением или окно было восстановлено
из скрытого состояния. Любая из этих последовательностей эффективно заканчи-
вается вызовом методов repaint () или update ().
В Qt 4 произошла небольшая “зачистка” событий: множество мелких и редко
используемых событий, вроде stylechange (), paletteChange (), fontchange (),
enebledChange () и languagechange (), объединены в один обработчик
changeEvent (). Получаемый им в виде параметра экземпляр класса QEvent со-
держит специфическую для события информацию. События, используемые в тех-
нологии drug-and-drop, рассматриваются в главе, посвященной реализации этой
технологии.
3.7. Фокус ввода и порядок элементов на форме
Когда элемент управления выделен, или, еще как говорят, имеет фокус клавиа-
турного ввода, то последующий ввод с клавиатуры будет изменять его содержимое
(если оно в принципе может изменяться таким образом). Некоторые пассивные
элементы никогда не получают фокус ввода. Подробностями реализации этих по-
нятий в Qt мы сейчас и займемся.
Элемент получает фокус ввода по одной из нескольких причин: при нажатии
клавиши <ТаЬ>, при щелчке кнопкой мыши на элементе, при нажатии “горячей”
клавиши и при прокрутке колесика мыши. Наконец, фокус ввода всегда может
быть переключен программно. Также на форме существует определенный услов-
ный порядок элементов, так что когда форма активизируется в первый раз, то сис-
тема может принять решение, какой элемент управления должен быть в фокусе.
Хотя условно все элементы управления “равны” между собой и было бы удобно
обрабатывать их идентично, тем не менее у пользователей сложились определен-
ные стереотипы, которым должен следовать разработчик. В результате каждый
элемент имеет свою специфику по обработке фокуса ввода.
Самым распространенным методом переключения между объектами формы яв-
ляется нажатие клавиши <ТаЬ>. При этом курсор фокуса перемещается по замкну-
тому циклическому списку объектов. Нажатие <Shift+Tab> приводит к перемеще-
нию в обратном направлении.
В основном, все элементы управления воспринимают клавишу <ТаЬ> в качестве
переключателя фокуса. Обычно исключением являются многострочные поля тек-
стового ввода, которые должны воспринимать <ТаЬ> буквально, как символ табу-
ляции. В Qt, как и в некоторых других системах, в полях ввода текста для пере-
ключения на следующий элемент используется комбинация клавиш <Ctrl+Tab>.
Для того чтобы элемент попал в список посещения при нажатии клавиши
<ТаЬ>, его необходимо зарегистрировать в ТАВ-списке формы. В Qt 4 для хранения
этого списка используется обычный список. Получить следующий за данным эле-
мент можно, вызвав метод QWidget:: nextInFocusChain(). Родительский эле-
мент управления может перемещаться по ТАВ-списку с помощью метода
QWidget: : f ocusNextPrevChild (). Специальный класс QFocusData, хранящий
ТАВ-список в Qt 3, в Qt 4 “вышел из употребления”.
Добавление элемента в список происходит автоматически, в момент вызова ме-
тода QWidget: : setFocusPolicy () с указанием соответствующего параметра, раз-
решающего получение фокуса по клавише <ТаЬ>. Если вы не используете метод
setTabOrder () для перестановки элементов, то их порядок на форме будет совпа-
дать с порядком создания объектов. Метод setTabOrder () вставляет второй эле-
мент после первого в QFocusData-порядке и принимает в качестве параметров два
объекта типа Qwidget. Обратите внимание на то, что в Qt нет явного манипулиро-
вания ТАВ-индексами, как это происходит в Windows.
Также учтите, что любое использование элемента в качестве второго параметра
метода setTabOrder () разрушает существующую цепочку, устанавливая для него
условное поле next в 0. Поэтому если вы выстроите цепочку c->d, a->b, Ь->с, то
связь c->d будет разрушена.
Упомянутый выше метод setFocusPolicy() отвечает за то, какие из методов
получения фокуса ввода будут активизированы для данного элемента формы. По
умолчанию поле не воспринимает фокус ввода. Чтобы изменить эту ситуацию, в
конструкторе и вызывается метод setFocusPolicy(). В качестве его параметров
нужно указать одну из предопределенных констант: QWidget: : TabFocus делает
возможным переход на этот элемент по клавише <Tab>, QWidget: :ClickFocus
позволяет воспринимать в качестве переключателя только щелчки мышью,
QWidget: :StrongFocus включает оба метода воздействия, a QWidget: : NoFocus
вообще отключает получение фокуса. Фокус можно отключить для тех полей, ко-
торые выбираются автоматически, т.е. программно, как результат предваритель-
ных действий пользователя. Кроме того, косвенно исключить поле из ТАВ-списка
можно, просто сделав его недоступным (grayed).
Вторым по порядку, но, возможно, первым по значению является выбор поля с
помощью мыши. Это весьма популярный способ передачи фокуса, но он также мо-
жет оказаться нежелательным. В качестве примера представьте форму с последова-
тельным контролем вводимых данных или форму, где поля (например, списки) ди-
намически заполняются строками в результате ввода предыдущих полей. В этом
случае вы не захотите, чтобы пользователь произвольно переключался и нарушал
логику программы.
Еще один метод переключения — кнопки команд на панелях управления. При
щелчке на одной из этих экранных кнопок пользователь не ожидает, что они полу-
чат фокус ввода, скорее, фокус останется там, где был до этого, — на каком-то эле-
менте ввода, обычно— в поле редактирования текста. В инструкции Qt, таким
образом, рекомендуется переключение фокуса по щелчку мышью для всех полей
текстового ввода и игнорирование переключения для остальных элементов управ-
ления — для них желательно создать переключение по “горячим” клавишам, как
это принято во многих версиях пользовательского интерфейса. Как уже было ска-
зано, режим переключения фокуса задается методом setFocusPolicy.
Метод переключения с помощью “горячей” клавиши также широко использует-
ся на практике. Естественно, что с помощью обработки клавиатурных событий вы
можете производить любые действия, в частности передавать фокус ввода. Однако
не забывайте, что многие комбинации уже зарезервированы в сознании пользова-
теля для часто используемых команд, так что перед назначением комбинации убе-
дитесь, что она не понадобится вам в стандартном контексте.
Метод переключения с использованием колесика мыши, в основном, применя-
ется под управлением X Windows и MacOS X. В среде MS Windows обработкой вра-
щения колесика занимается сам элемент управления, фокус же не передается дру-
гому компоненту.
Особый случай: форма в целом получает фокус ввода, т.е. активизируется. Если
она уже была активна перед этим, то фокус остается на том же элементе — это вы-
полняется менеджером управления Qt автоматически. Если вы хотите передать
фокус какому-то определенному элементу управления во время первого отображе-
ния формы, то выполните метод QWidget: : setFocus () перед тем, как он и, лучше
всего, вся форма получат слот Qwidget:: show (). Таким же образом вы можете
переключать фокус в любой момент, например, обрабатывая данные, вводимые
пользователем.
Если вы не передаете фокус определенному элементу при создании формы, Qt
будет использовать внутренний алгоритм для выбора наиболее подходящего кан-
дидата, обычно — первого создаваемого дочернего компонента, для которого полу-
чение фокуса имеет смысл.
Как уже было сказано, существуют методы, которые позволяют программно
устанавливать и сбрасывать фокус, а также проверять, является ли элемент в фоку-
се и разрешен ли вообще фокус ввода для данного элемента. В качестве средств про-
граммной установки фокуса клавиатурного ввода используются методы setFocus ()
и clearFocus(). Для проверки применяются методы QWidget: :hasFocus () и
isFocusEnabled() соответственно; также эти значения можно получить как свой-
ства focus и focusEnabled.
Нетривиальные методы обработки ввода можно реализовать с помощью “прокси”
(заместитель) фокуса клавиатурного ввода. Основная идея “прокси” — передавать
обработку нажатий клавиатуры другому элементу управления, как правило — до-
чернему. Если такой сложный элемент управления, как комбинированный (combo-
box), содержит в своем составе текстовое поле, то вполне логично, что ввод пользо-
вателя будет переадресовываться этому элементу управления, независимо от того,
на какой части “композита” был произведен щелчок мышью. Установить “прокси”
можно с помощью метода setFocusProxy (), а получить — с помощью метода
focusProxy (). После установки “прокси” вызовы методов setFocusPolicy (),
focusPolicy (), setFocus () и hasFocus() будут эффективно воздействовать на
прокси-элементы управления, а соответствующие свойства— возвращать значе-
ния, относящиеся к ним.
3.8. Реализация технологии drag-and-drop
Пришло время обсудить вопросы, связанные с реализацией популярного поль-
зовательского поведения, называемого drag-and-drop или перетаскиванием. Это
поведение, а также связанные с ним сигналы заложены в класс QWidget, так что
операции перетаскивания доступны для всех производных элементов пользова-
тельского интерфейса.
В операции перемещения задействованы две стороны: источник и приемник
данных. Для создания перемещаемых данных достаточно создать объект, произ-
водный от класса QMimeData. Класс QDragObject, применяемый в Qt 3, больше не
используется — это была, по сути, небольшая и не очень универсальная надстройка
над классом QMimeData. Перемещаемые данные QMimeData служат как при пере-
таскивании, так и при копировании/вставке. Обе операции задействуют системно-
зависимый буфер обмена, так что приложения Qt совместимы по обмену данными с
другими приложениями.
Для выполнения конкретной операции перетаскивания в некоторый момент
времени вы создаете объект типа QDrag. Можно представить использование класса
QDrag в качестве буфера обмена: вы создаете его экземпляр и помещаете туда дан-
ные методом QDrag: : setMimeData ().
QDrag *drag=new QDrag(this);
QMimeData *mimeData=new QMimeData;
mimeData->setText(commentEdit->toPlainText());
drag->setMimeData(mimeData);
Когда пользователь переносит данные, курсор изменяется в зависимости от типа
переносимых данных. Эти пиктограммы устанавливаются по умолчанию, но вы
можете установить любую другую, вызвав метод setPixmap (). Пиктограмма, ука-
занная в виде курсора, должна быть понятна пользователю. Особенно важно визу-
ально выделить особую “горячую” точку, координаты которой и рассматриваются
как координаты курсора. Обычный курсор имеет координаты “горячей” точки
(0,0), т.е. в верхнем левом углу. Для других координат служит метод
QDrag: : setHotSpot (). Размеры установленной пиктограммы доступны через ме-
тоды QDrag: : pixmap (), QDrag: : width () и QDrag: : height (), так что вы легко
можете установить “горячую” точку в центр изображения или в любую другую за-
метную позицию.
При переносе QDrag-объект всегда может получить источник и приемник дан-
ных с помощью методов source () и target (). Это позволяет, например, не обра-
батывать перенос данных объекта на самого себя или обрабатывать эту ситуацию
особым образом.
Когда вы создаете объект типа QDrag — зависит только от вас. Обычно отсле-
живается щелчок кнопкой мыши на созданном вами элементе управления по
возникновению события mousePressEventО, а потом обрабатывается событие
mouseMoveEvent (), чтобы понять, когда можно было начинать перенос. Вы може-
те отключить трекинг мыши, чтобы не обрабатывать событие mouseMoveEvent ()
без нажатой клавиши, и таким образом немного упростить себе задачу. Можно, ко-
нечно, сразу начинать перемещение после щелчка левой кнопкой мыши, однако
(в целях фильтрации ложных срабатываний) существуют временной и “террито-
риальный” критерии начала операции перетаскивания.
Два глобальных свойства QApplication: : startDragTime и QApplication: :
startDragDistace задают настройки для всего приложения, как быстро после
щелчка кнопкой мыши и/или при каком сдвиге курсора вы должны начинать опе-
рацию перемещения. Не игнорируйте эти параметры, иначе ваши элементы будут
вести себя необычно для пользователя. Вот пример того, как следует использовать
свойство QApplication: : startDragDistace в собственном элементе управления
DragWidget.
void DragWidget::mousePressEvent(QMouseEvent *event) {
if (event->button()==Qt.LeftButton)
dragStartPosition=event->pos();
}
void DragWidget::mouseMoveEvent( QMouseEvent *event ) {
if (!(event->buttons() & Qt.LeftButton) return;
if ((event()->pos()-dragStartPosition).manhattanLength()
^QApplication::startDragDistance()) return;
QDrag *drag=new QDrad(this);
QMimeData *mimeData=new MimeData;
mimeData->setData(mimeType,data);
drag->setMimeData(mimeData);
Qt::DropAction dropAction=drop-
>start(Qt::CopyAction|Qt::MoveAction);
}
Создание объекта типа QDrop и размещение в буфере данных — уже знакомый
нам код. “Манхэттенское” расстояние— это американизм, соответствующий на-
шему декартову расстоянию на плоскости. Метод QDrag: : start () собственно на-
чинает операцию переноса. На этом, собственно, действия источника заканчивают-
ся до окончания операции. Чем она закончилась — можно узнать по результату,
возвращаемому методом start (): перечисление Qt: : DropAction и вектор флагов
Qt: .-DropAction, передаваемый как параметр, суть одни и те же битовые маски.
Это логично: выполняется одна из запрошенных операций. Впрочем, существует
одна операция, Qt: : IgnoreAction, со значением нуль (0), которую нельзя запро-
сить.
Теперь перейдем к компоненту приемника данных. Принимающая сторона долж-
на быть готова для “сбрасывания” данных. Это достигается вызовом, предположи-
тельно в конструкторе, метода setAcceptDrops (true). После этого ваш элемент
управления станет чувствительным к “сбросу” на него данных, и для него станут ге-
нерироваться события dragEnterEvent (), dragMoveEvent (), dragLeaveEvent ()
и, наконец, dropEvent (). Эти события обозначают, что данные в виде пиктограм-
мы “занесены” на элемент, перемещаются по нему, покидают его без сброса или,
напротив, были “сброшены”.
По сути, для обработки события переноса данных достаточно обрабатывать только
события dragEnterEvent () и dropEvent (). Остальные два события служат для соз-
дания дополнительных эффектов, таких как прокрутка содержимого в поисках места
вставки. Например, видеофильм может воспринимать вставку текста только в облас-
ти титров внизу экрана, для этого обрабатывая событие dragMoveEvent () и блоки-
руя вставку в других областях. Окно текстового ввода начинает “скроллинг” при
входе “заряженного данными” курсора в зону прокрутки. Прокрутка прекращает-
ся по выходу сбрасываемых данных за пределы элемента управления, для этого об-
рабатывая событие dragLeaveEvent ().
Полезно знать, что класс QDragEnterEvent является подклассом QDragMoveEvent,
который, в свою очередь, является QDropЕvent-подклассом.
При занесении данных вы сначала получите объект класса QDragMoveEvent и
сразу же после этого — QDragEnterEvёnt-oбъeкт, так что достаточно обрабатывать
только один из них. От класса QEvent все эти классы наследуют два главных
“судьбоносных” метода — accept () и ignore (), которые принимают или отвер-
гают сброс данных в данном месте приемника. Вместо этих мнемонических вариан-
тов можно вызывать метод set Ас с ер ted () с логическим параметром.
Обратите внимание на небольшую оптимизацию: класс QDragMoveEvent и про-
исходящий от него класс QDragEnterEvent имеют версию методов accept () и
ignore (), которые принимают прямоугольник. Словами эту ситуацию можно опи-
сать так: здесь возможен (или невозможен) сброс, причем до тех пор, пока курсор
не покинет данный прямоугольник.
Метод dragEnterEvent () служит для раннего обнаружения, сможет ли (“же-
лает” ли) данный элемент управления принимать данные определенного типа. На
самом деле в буфере могут быть данные, представленные в нескольких форматах
(например, текст, гипертекст, еще какой-то похожий формат вроде Adobe PDF и
т.д.). В обработчике dragEnterEvent () вы можете опросить данные посредством
инструкции event->mimeData () ->hasFormat ("text/plain”), например, чтобы
убедиться, что в буфере есть данные в нужном формате, в данном случае — в виде
обычного текста.
Главным обработчиком на стороне приемника, реально извлекающим данные,
является событие dropEvent (). Здесь не нужно делать те же проверки, что и в
dragEnterEvent (), как это было в Qt 3, поскольку в Qt 4 вы получите
dropEvent () тогда и только тогда, если перед этим вы подтвердили возможность
сброса в последнем dragEnterEvent () или dragMoveEvent (). /
Просто исследуйте событие на предмет запрошенной операции, которую и вы-
полняете. Что приемник будет делать с полученными данными — сложно сказать:
это может быть вставка текста в поле редактирования, вставка элемента в список,
а, возможно, и какие-то более важные действия, например выполнение полученно-
го кода.
Ситуация становится еще более неопределенной, если вы обмениваетесь про-
извольными MIME-данными. Это легко можно сделать, вызвав метод
QMimeData: :setData(QString, QByteArray&) с произвольным типом данных,
указав его имя в первом параметре и двоичное представление — во втором. Конеч-
но, перед тем как создать новый MIME-тип, желательно узнать, не существует ли
уже какой-то вполне подходящий и известный широкой общественности.
В результате подтверждаем прием данных: если вы готовы в точности вы-
полнить запрошенную операцию, то завершите обработку вызовом метода
acceptProposedAction (). Если вы не готовы полностью взять на себя ответствен-
ность за полученную копию данных, а хотите выполнить только копирование, то
последним оператором вашего обработчика должен быть вызов метода accept ().
В заключение обращаю ваше внимание, что бывшие в употреблении в Qt 3 клас-
сы QDragObject, QTextDrag, QColorDrag и QlmageDrag теперь не используются.
Всегда используйте базовый класс QMimeData с нужным типом данных. В качестве
вспомогательных класс QMimeData включает'такие специальные методы для рас-
пространенных типов данных, как setlmageData () или hasHtml ().
3.9. Окна верхнего уровня
К окнам верхнего уровня относятся окна приложения, как правило, с заголов-
ком и “широкой” границей для изменения размера пользователем. Также к ним
можно отнести всплывающие окна и окна диалогов. Рабочий стол тоже является
специальным окном верхнего уровня.
Окна верхнего уровня, как и любые элементы управления, могут иметь роди-
тельское окно и повторять его поведение: минимизироваться, раскрываться и пре-
кращать существование синхронно. Родительское окно (контейнер) можно установить,
опросив метод parentwidget (bool sameWindow= false): параметр как раз и оп-
ределяет поведение относительно окон верхнего уровня. Если параметр samewindow
равен значению true, то для окон верхнего уровня всегда будет возвращаться 0.
В противном случае Qt попытается вернуть родительское окно.
Для определения типа окна можно использовать один из методов: isTopLevel (),
isDialog(), isPopup(), isDesktop().
Несколько свойств имеют смысл только для окон верхнего уровня. Во-первых,
окна верхнего уровня имеют заголовок, пиктограмму (“иконку”) и подпись к ней, ко-
торые устанавливаются set-методами setCaption(), setlconO, setlconText ()
соответственно (рис. 3.4). Установленные этими методами значения возвращаются
соответствующими свойствами.
Sie Toolbar Dock Widgets
Qt Qt Qt Qt Qt Qt -
Рис. 3.4. BQt4 перестроен внутренний механизм главного
окна приложения: области “парковки” панелей управления
и “швартовки” (docking) дочерних окон стали частью
самого окна
Во-вторых, для главных окон определено несколько действий, не имеющих
смысла для остальных элементов управления, — это команды show-группы:
showMinimized, showMaximazed, showFullScreenи showNormal. Кроме того, два
метода помогают установить приложение активным или проверить этот факт
(т.е. имеет ли оно клавиатурный фокус ввода): метод setActiveWindwow(true)
устанавливает его активным, а метод/свойство is Activewindow проверяет,
так ли это.
В-третьих, это экстент расширения, т.е. размер единичного приращения, на ко-
торое будет увеличиваться элемент управления при растягивании его пользовате-
лем. Эта величина задается методом QWidget: : setsizeincrement (). Этот пара-
метр имеет смысл только для окон верхнего уровня и не работает в среде Windows.
В качестве начального значения используется величина, задаваемая методом
QWidget: :baseSize(). Можно сказать, что инкрементивное изменение размера
реализует логику “выравнивание по сетке” (align size to grid).
3.10. Общие визуальные характеристики
интерфейса: QStyle
Теперь займемся тем, что составляет гордость начинающих программистов —
изменение внешнего вида элементов управления. Это бросается в глаза и, кроме
прочего, может добавить привлекательности вашему приложению, хотя, по сути,
не относится к функциональным возможностям. Тем не менее нет ничего хуже, чем
несогласованный интерфейс, не позволяющий эффективно реализовать заложен-
ные в приложение возможности.
Часто упоминаемой, но редко действительно полезной возможностью является
настройка так называемых стилей интерфейса. Под стилем понимается опреде-
ленное цветовое решение и особенности оформления. Как можно предположить, в
основе этого механизма лежат цветовые наборы: достаточно переключить цветовой
набор для данного элемента управления, задать обновление всего элемента— и
большая часть работы уже сделана. Кроме цветового оформления, стиль “заведует”
характерными размерами элементов управления и особенностями их поведения.
Как и для цветовых схем, для элементов управления задан стиль по умолчанию,
как свойство всего приложения. Элемент управления будет применять этот стиль
до тех пор, пока для него не будет задан частный стиль. По правде гоцоря, эта до-
полнительная степень свободы кажется излишней: таким образом как бы поощря-
ется создание несогласованных интерфейсов. Наличия единого стиля и цветового
набора для всего приложения могло бы быть достаточно, но возможность изменить
эту ситуацию тем не менее существует (рис. 3.5).
Для установки или запроса стиля для всего приложения служат методы
QApplication: : setstyle () и QApplication: : style (). Аналогичные методы
существуют и для класса QWidget, которые задают и опрашивают стиль для каж-
дого элемента управления в отдельности. Изменение стиля сопровождается собы-
тием QWidget: : changeEvent (QEvent: : sty 1 eChange), вызываемым сразу после
того, как стиль был изменен.
В качестве объекта типа “стиль” выступает экземпляр класса QStyle. Это до-
вольно замысловатый класс, включающий массу настроек для каждого из стан-
дартных пользовательских примитивов.
Многие параметры, такие как тип пользовательского элемента, заданного пере-
числением QStyle: : Primitives lenient (я насчитал 45! таких примитивов— от
обычной кнопки и рамки до выпадающих меню и т.д.), а также дополнительные
факторы (например, QStyle: : StyleFlags и QStyle: : StyleHint) отображаются в
пространство “цвет, размеры и т.д.”.
Кроме того, что класс QStyle хранит все необходимые настройки, он же, что
весьма любезно, занимается и отрисовкой самих элементов управления. Например,
метод QStyle: :DrawPrimitive() отрисует в заданном элементе управления за-
данный фрагмент (например, рамку или надпись), при этом вы даже не будете
знать, как, поскольку стиль сам “знает, что, куда и сколько”. Аналогично рисуют-
ся не только примитивы, но и целые элементы управления, а также “сложные эле-
менты управления”. Еще раз обращаю ваше внимание, что в четвертой версии вы-
зовы этих методов претерпели значительные изменения.
Рис. 3.5. Так может выглядеть ваше приложение,
если вы — поклонник не “пластиковых” или
"стеклянных" мотивов, а более экологически
чистых или природных материалов
Кроме прочего, в “военных действиях” участвуют следующие параметры
(описываемые соответствующими перечислениями):
QStyle: :Complexcontrol — девять типов сложных элементов, по различ-
ному диспетчеризующих события мыши и клавиатуры своим подопечным
дочерним компонентам;
QStyle: :ContentsType — двадцать два типа пользовательских компонен-
тов, отвечающих за то, где, как и что из видимых свойств, вроде подписи или
свойства selected (), будет отрисовано;
QStyle: :ControlElement — сорок две различные детали элементов управ-
ления, каждый из которых должен быть отрисован с соответствии с данным
стилем;
QStyle::PixelElement — метрика в пикселях семидесяти одного парамет-
ра пользовательского интерфейса (например, расстояние между рамкой и
текстом в ниспадающем меню);
QStyle::PrimitiveElement — уже упоминавшиеся сорок пять элементов
пользовательского интерфейса. Чтобы вы примерно могли оценить детали-
зацию проработки, можно только сказать, что там присутствуют такие
“детальки”, как QStyle: : PE_SpinWidgetDown — “символ уменьшения зна-
чения в spin-элементе”;
QStyle: :StyleFlags— двадцать возможных вариантов визуального
оформления элемента управления, вроде “Enabled” (разблокировыванный),
“Raised” (выступающий) или “Sunken” (“утопленный”). К нашему большому
счастью, не все элементы управления реализуют все возможные состояния;
QStyle: :StyleHint— шестьдесят три “подсказки” к стилю, например,
“если пользователь щелкает на полосе прокрутки и не отпускает кнопку
мыши, то включить автоповтор операции прокрутки, но тем не менее остано-
виться в момент, когда бегунок достигнет положения курсора” (QStyle: :
SH_ScrollBar_StopMouseOverSlider).
Надеюсь, вы простите меня, если я прекращу рассмотрение стилей именно на
этом месте — мы не упомянули еще несколько таких же “интересных” параметров.
Как вы уже, надеюсь, поняли — стили не созданы для слабонервных. Конечно, го-
товые стили дают определенные преимущества в плане разнообразия и удобства
пользовательского интерфейса. Но модификация, а особенно разработка новых
стилей (лучше — по одному в год, не торопясь), оправдана только в том случае, ес-
ли заказчик явно» в письменной форме, дает понять, что использование стилей яв-
ляется желаемым, приоритетным и, главное, оплачиваемым заданием. В против-
ном случае вы будете заниматься “красотой” за свой счет, как дизайнер-любитель.
3.11. Создание<?обственных элементов управления
Напоследок условий создадим какой-то простой элемент управления. Создание
“промежуточного уровня” интеллектуальных элементов управления, возможно со
встроенной бизнес-логикой, является обычным делом в Qt. Вот полный текст при-
ложения (“туториал” Qt №4).
#include <QApplication>
#include <QFont>
#include <QPushButton>
class MyWidget : public QWidget {
public: MyWidget(QWidget *parent ='O);
};
MyWidget::MyWidget(QWidget *parent):QWidget(parent){
setFixedSize(200, 120);
QPushButton *quit = new QPushButton (’’Quit’’, this);
quit->setGeometry(62, 40, 75, 30);
quit->setFont(QFont("Times”, 18, QFont::Bold));
connect(quit, SIGNAL(clicked()), qApp, SLOT(quit())) ;
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MyWidget widget;
widget.show();
return app.exec();
}
Нетрудно заметить, что наш класс — всего лишь оболочка вокруг класса кнопки
QPushButton, но какая! Ведь он объявляет сам себя в качестве обработчика сигна-
ла quit ()! Надо сказать, довольно “самонадеянный” класс, позволяющий инкап-
сулировать глобальное поведение всего приложения! Изменение параметров по
умолчанию — распространенный повод для создания собственных компонентов, и
не только в Qt. Здесь мы изменили размер и шрифт кнопки по умолчанию.
Наш элемент управления является потомком класса QWidget — это общее пра-
вило для всех визуальных компонентов. Обратите внимание на вызов метода су-
перкласса w. setGeometry (100,100,200,120), а также на то, что наш компонент
вполне смог выполнять роль главного для всего приложения.
Глава 4
Базовые элементы управления
4.1. Класс QAbstractButton и производные компоненты
Мы проделали большую работу, рассмотрев один из ключевых классов Qt,
QWidget. Описание класса QWidget было лишено примеров, поскольку он не пред-
назначен для создания экземпляров, а служит только прототипом для всех осталь-
ных, в том числе и ваших собственных, элементов управления.
Теперь мы займемся рассмотрением классов, производных от QWidget, и в пер-
вую очередь обратимся к классу QAbstractButton, заменившему класс QButton в
версии Qt 3. Кнопки в различных видах — очень употребимый и полезный элемент
любых прикладных приложений. Даже пример программы Hello World состоит
из одной кнопки, так что отчасти мы уже видели кнопку в действии.
Класс QAbstractButton, как теперь ясно следует из названия, также является
абстрактным, поэтому мы рассмотрим его в первую очередь, а затем уж перейдем к
его потомкам: классам QCheckBox, QPushButton, QRadioButton и QToolButton.
Итак, что отличает кнопки в общем? Кнопка состоит из рамки, текста надписи и
иконки (пиктограммы). Главным элементом кнопки является текст надписи —
обычно нам встречаются кнопки без рисунков, если не считать инструментальные
кнопки на панелях управления.
Для установки текста надписи кнопки служит метод QAbstractButton: :
setText (const QString &), а для получения этого текста — метод и одноимен-
ное свойство QAbstractButton: : text. Обратите внимание на то, что, в отличие от
некоторых других систем, для кнопок в Qt не предполагается надписи по умолча-
нию, при этом метод QButton: : text () возвращает значение QString: : null.
Точно так же, как и надпись, для кнопки может быть установлена пиктограмма,
для чего служит метод seticon (const Qlcon &). Этот метод заменил аналогич-
ный iconSet () в версии Qt 3: теперь метод читается правильно, как “установить
иконку”, а не как “набор иконок”. Кроме того, Qt 4 не использует возможность ус-
тановки изображения методом setPixmap(const QPixmap &) — для вставки в
кнопку изображений вы должны использовать метод set Icon ().
Если текст подписи содержит амперсанд “&”, то символ после амперсанда авто-
матически устанавливается в качестве “горячей клавиши”, переопределяя ранее
заданное значение. Вы можете определить собственный акселератор для кнопки с
помощью метода set Shortcut (const QKeySequence &), метод и свойство
shortcut возвращают значение последовательности “горячих клавиш” для данной
кнопки. Обратите внимание на отличие от Qt 3: там комбинации “горячих клавиш”
задавались методом setAccel (), который больше не рекомендуется использовать.
Коды QKeySequence могут быть заданы как числовыми значениями символов,
так и строками специального формата, которые могут восприниматься в Qt как до-
пустимые. Например, <Ctrl+G> является корректным сочетанием в качестве ком-
бинации “горячих клавиш”.
Кроме визуальных атрибутов, с кнопкой связаны функциональные свойства.
Одним из таких свойств является тип кнопки. Различают кнопки с состоянием
(toggle button)1 и без состояния. В терминах Qt 4, в отличие от Qt3, все типы кнопок
с состоянием задаются методом setCheckable (); в Qt 3 ту же роль выполнял ме-
тод toggleType (). Со свойством checkable связано свойство autoExclusive: оно
определяет, будут ли кнопки в группе поддерживать культуру “не больше одной
нажатой”, как это реализовано в радиокнопках, или же каждая кнопка может быть
выбрана независимо, как это “принято” у кнопок-флажков (check box). Естествен-
но, вы можете реализовать то же самое поведение вручную, вызывая в обработчике
щелчка на кнопке метод setChecked( false) для остальных. Кстати, узнать, к ка-
кой группе относится та или иная кнопка, можно, опросив свойство group.
Главным событием для кнопки является щелчок на ней пользователем. Собст-
венно, существует несколько событий, связанных с этим действием. Событием вы-
сокого уровня, обозначающим, что кнопка была “нажата”, является сигнал
clicked(). Этот “логический” сигнал срабатывает не только от щелчков мыши в
области кнопки, но также и при нажатии “горячей клавиши”, а также при вызове
методов click () и animatеС 1 ick(). Последний метод анимирует щелчок на
кнопке: она будет отрисована в “нажатом”, а через указанное время — в “отжатом”
состоянии. Такой эффект часто реализуется в демонстрационных целях, когда
нужно эмулировать работу пользователя.
Если вы хотите как-то обрабатывать состояние в промежутке между нажатием
кнопки и ее освобождением, то для этого служат два низкоуровневых события
pressed () и released (). Наконец, при переключении кнопки из одного состоя-
ния в другое (если кнопка поддерживает состояния) будет вызван сигнал
toggled (). В качестве параметра будет передано новое состояние. Обратите вни-
мание, что когда кнопка находится в пассивном состоянии (disabled), никакие сиг-
налы от нее не поступают.
4.1.1. Классы QCheckBox, QPushButton,
QRadioButton, QToolButton
Наконец-то мы добрались до конкретных представителей класса QWidget.
Вы можете — и, фактически, будете — создавать реальные экземпляры этих клас-
сов в ваших приложениях.
1 Переключатель, т.е. элемент управления интерфейса, имеющий два фиксированных состоя-
ния. —- Примеч.ред.
Начнем с флажков. Задача объекта типа QCheckBox — возвращать значение ло-
гического типа: да-нет, включено-выключено и т.д. Флажки являются типичными
представителями кнопок с состоянием. Однако обратите внимание: в Qt 4 состоя-
ние не представляет собой однозначное логическое значение; на самом деле здесь
используется перечислимый тип Qt: : ChackState, который предусматривает три
значения: Unchecked, PartiallyChecked, Checked. Значение PartiallyChecked
(“частично отмечен”) применяется в иерархических списках для узла, некоторые
(но не все) дочерние узлы которого отмечены. Вероятно, когда-то, когда компьюте-
ры будут широко использовать нечеткую логику, переключатель будет хранить
вещественное значение.
Собственно установка и проверка состояния объекта типа QCheckBox произво-
дятся методами setCheckState () и checkstate () соответственно. Кроме этого,
каждое изменение состояния сопровождается сигналом stateChanged ().
В общем случае флажки могут находиться в трех положениях: “включено”, “вык-
лючено” и “состояние не определено”. Для переключения двухпозиционного флажка
в трехпозиционный достаточно использовать метод QCheckBox::setTristate().
Получить установленное с его помощью значение можно через соответствующее
свойство tristate. Обычно флажки не объединяются в группу, а представляют со-
бой независимый набор значений, хотя иногда их “заставляют” работать в качестве
радиокнопок.
Самым распространенным, или, по крайней мере, самым нажимаемым, типом
кнопок являются командные кнопки, представленные в Qt классом QPushButton.
Такие кнопки содержат текст или изображение и обычно выглядят как горизон-
тальный (в MacOS — с закругленными углами) прямоугольник. С точки зрения
управления класс QPushButton практически не привносит ничего нового по срав-
нению с классом QAbstractButton: главным, а часто и единственным интересным
событием является clicked (). Специфика кнопок действия состоит в следующем:
одна из них может быть кнопкой по умолчанию, т.е. генерировать сигнал активи-
зации во время нажатия клавиши <Enter> на форме. Обычно такое поведение
используется в окнах диалога. Для установки/проверки свойства “кнопка по умол-
чанию” используются методы QPushButton: : setDefault () и isDefaultO соот-
ветственно.
С кнопками действия в Qt связано еще несколько свойств. Прежде всего,
Qt расширяет функциональность кнопок действия, позволяя связать с ними выпа-
дающее меню. Меню появляется в момент щелчка на кнопке, так же как это проис-
ходит с кнопкой Пуск в Windows. Установить выпадающее меню можно методом
setMenu (QMenu *), а получить — через свойство QMenu *menu. Эти методы заме-
нят своих “коллег” из Qt 3 (setPopup (), popup () и isMenuButton ()), которые исполь-
зуют устаревший класс QPopupMenu. Вместо последнего метода isMenuButton () в но-
вом коде просто проверяйте условие (Ipb.menu ()). Вместо слота openPopupO для
принудительного вызова меню в Qt 4 нужно вызывать метод showMenu (). При этом не
забывайте, что всплывающие меню по историческим причинам (первоначально — для
обеспечения производительности) являются модальными: вы не получите управле-
ния, пока пользователь так или иначе не закроет выпадающее меню.
Как и любой потомок класса QAbstractButton, командные кнопки можно сде-
лать “залипающими”, вызвав метод setCheckable (). Включить режим автопо-
втора можно с помощью метода setAutoRepeat () — кнопка будет генерировать
последовательность нажатий до тех пор, пока пользователь не отпустит ее. Доку-
ментация Qt 4 не рекомендует использовать эти свойства для обычных командных
кнопок, хотя для инструментальных кнопок типа QToolButton это распростра-
ненные свойства.
Следующий рассматриваемый нами потомок класса QAbstractButton тоже
знаком вам — это радиокнопки, основная задача которых — возвращать одно зна-
чение (один вариант) из нескольких. Основная специфика этих кнопок состоит в
том, что они всегда объединяются в группы, так что только одна из этих кнопок
может быть выделена. На уровне кода это выглядит как установленное по умолча-
нию свойство autoExclusive.
Поскольку речь идет об устойчивом выделении, то эти кнопки характеризуются
состоянием, поэтому свойство checkable тоже всегда установлено по умолчанию.
Проверка или установка состояний для QRadioButton-объектов производится па-
рой унаследованных методов isChecked() и setChecked(), хотя в реальной жиз-
ни вы чаще всего будете обрабатывать радиокнопки через их контейнер — группу.
В остальном радиокнопки не добавляют, по сравнению с классом QAbstractButton,
никаких специфических свойств и методов.
Наконец, последний тип предопределенных кнопок — QToolButton, предостав-
ляющий доступ к “быстрым командам”, обычно в составе объекта типа QToolBar.
Эти кнопки, как правило, не воспринимают фокус ввода, так что щелчок на них не
приводит к потере фокуса текущим элементом управления. Также по соглашению
эти кнопки не имеют текста надписи, а только пиктограмму для отображения со-
стояний. Инструментальные кнопки часто являются “залипающими”, с установ-
ленным свойством checkable— например, переключатель наклонного шрифта.
Некоторые из них генерируют повторные сигналы при удержании в нажатом со-
стоянии— обычно это касается различных “перемоток” на пультах управления
мультимедиа или при просмотре записей баз данных.
В отличие от других кнопок, объект класса QToolBar особым образом отзывается
на наведение курсора мыши: при этом эмулируется “приподнятое” состояние кноп-
ки. Установить или отменить этот режим можно через свойство QToolButton: :
set AutoRaise (). Это свойство автоматически включается, если кнопка помещает-
ся на QToolBar-панель.
4.1.2. Немного “практических” кнопок и групп
Вернемся к практике и проиллюстрируем работу различных предопределенных
типов кнопок, в том числе и в составе групп. Этот пример вы можете найти среди
поставки Qt, он называется Widgets/Group Box. Собственно сам main-код выгля-
дит тривиально— создается один сложный компонент управления, содержащий
все остальные кнопки и их группы. На всякий случай приведу полностью текст
файла main. срр.
#include <QApplication>
#include "window.h"
int main (int argc, char **argv) {
QApplication app(argc, argv);
Window window;
window.show;
return app.exec();
}
Как видите, создается и отображается один элемент управления, после чего
приложение входит в главный цикл асинхронной обработки событий. Вся реальная
работа, которой здесь немного, реализована в классе Window. Обратите внимание на
новый формат директивы #include — начиная с версии Qt 4 стандартные модули
следует подключать так, как вы видите в первой строке.
Описание этого класса имеет следующий вид.
#include <QWidget>
class Window:public QWidget {
Q-OBJECT
public:
Window(QWidget *parent=0);
private:
QGroupBox *createFirstExclusiveGroup();
QGroupBox *createSecondExclusiveGroup();
QGroupBox *createNonExclusiveGroup();
QGroupBox *createPushButtonGroup();
}
Как видите, описание нашего “большого” класса не отличается изяществом —
экспортируемый интерфейс включает только один конструктор, причем только с
одним параметром родительского контейнера. Конечно, такой класс не обладает
гибкостью и не предназначен для повторного использования. Также имена методов
в реальной жизни, вероятно, не будут такими бессмысленно мнемоническими. Как
показано выше, при создании своих компонентов в среде Qt не забывайте указы-
вать макрос Q_OBJECT.
Мы не будем рассматривать весь текст, поскольку там все достаточно просто,
рассмотрим только один характерный фрагмент.
#include <QtGui>
#include "window.h”
Window::Window(QWidget *parent):QWidget(parent) {
QGridLayout *grid = new QGridLayout;
grid->addWidget(createFirstExclusiveGroup(), 0, 0);
grid->addWidget(createSecondExclusiveGroup(), 1, 0);
grid->addwidget(createNonExclusiveGroup(), 0, 1);
grid->addWidget(createPushButtonGroup(), 1, 1);
setLayout(grid);
setWindowTitle(tr("Group Box"});
resize(480, 320);
}
QGroupBox *Window::createSecondExclusiveGroup() {
QGroupBox *groupBox = new QGroupBox(tr("E&xclusive Radio Buttons"));
groupBox->setCheckable(true);
groupBox->setChecked(false);
QRadioButton *radiol = new QRadioButton(tr("Rad&io button 1"));
QRadioButton *radio2 = new QRadioButton(tr("Radi&o button 2"));
QRadioButton *radio3 = new QRadioButton(tr("Radio &button 3"));
radiol->setChecked(true);
QCheckBox *checkBox = new QCheckBox(tr("Ind&ependent checkbox"));
checkBox->setChecked(true);
QVBoxLayout *vbox = new QVBoxLayout;
vbox->addWidget(radiol);
vbox->addWidget(radio2);
vbox->addWidget(radio3);
vbox->addWidget(checkBox);
vbox-^addStretch(l);
groupBox->setLayout(vbox);
return groupBox;
}
Как видите, текст не отличается сложностью — он приведен только для того,
чтобы вы могли с чего-то начать свои эксперименты. Есть пару интересных момен-
тов, на которые нужно обратить внимание. Во-первых, повторюсь, это новый фор-
мат директивы #include — вы указываете модуль, а не файл. Во-вторых, (важно!)
обратите внимание на то, что все строковые константы заключены в блок tr (). Это
указание исполнительной системе Qt получать эти строки из файлов национальных
настроек. Сами такие файлы создаются с помощью приложения Qt Linguist, кото-
рое рассмотрим в приложении В. Тем не менее если вы даже в отдаленном будущем
планируете интернационализацию своей программы, с самого начала заключайте
переводимые фразы в блок tr ().
Что касается самих кнопок — то тут нет ничего неожиданного. Обратите внима-
ние на то, как формируется экранное представление: сначала создается менеджер
размещения, в него добавляются вложенные компоненты, после чего менеджер
располагается в контейнере, например, в объекте типа QGroupBox или QWidget.
Другими словами, группа напрямую не является владельцем кнопок, но тем не ме-
нее это не мешает группе руководить их поведением, реализуя autoExclusive-
логику. Для переключателя, находящегося в группе, свойство autoExclusive по
умолчанию не установлено, поэтому на него не влияет состояние других элементов
управления.
И последнее. К группе, как целому, также применимы методы setCheckable ()
и setChecked (), что визуально выглядит как кнопка-флажок (check box) в заго-
ловке группы. В результате “выключения” группы все вложенные элементы стано-
вятся недоступными (рис. 4.1). Этот сравнительно новый подход реализован в обе-
их версиях: Qt 3 и Qt 4.
4.2. Ввод и отображение текста: классы QLabel,
QLineEdit, QTextEdit
Самый простой и в то же время самый незаменимый элемент, позволяющий
отображать фрагмент текста, представляется объектом класса QLabel. На самом
деле это не элемент управления, поскольку вы не управляете с его помощью при-
ложением — скорее, он управляет вами, снабжая полезной информацией. Он не
происходит от класса QWidget, а как последний, происходит от класса QFrame, т.е.
является просто “рамкой”. В результате “текстовая метка” имеет почти те же визу-
альные свойства, что и QWidget-объект, а именно размеры, подсказки по размерам,
палитры и т.д. — но не имеет никаких слотов, сигналов и других “оживляющих”
свойств. Так что, в отличие от некоторых других “псевдометок” (таких, как в
Delphi), которые на самом деле все “видят и чувствуют” и поэтому могут реагиро-
вать на ввод пользователя, метки в Qt являются действительно декорациями на
празднике жизни. Как следствие — это очень “производительные” (хотя и ничего
не производящие) элементы интерфейса, активно используемые как строительный
материал для создания более сложных компонентов.
- Exclusive Radio Buttons---------
® &a£llo button 1,
О Ш1о button 2
0 Radio buttons
Non-Exduslve Checkboxes'
□’Checkbox 1
Checkbox 2
Tri-state button
*ЙЗ Exclusive RadloButtons p® £u$h Buttons
® R«dfa button!
О Rkdto button!
О Radio buttons
Я lnd|^endjHrtch«ckbGX
Рис. 4.1. Так выглядят группы кнопок в Qt. Обратите
внимание на флажок, "выключающий" целую группу
Справедливости ради нужно признать, что у этой простоты QLabel-объектов
есть двойное дно: метки QLabel могут содержать не только строки, но и экземпля-
ры типа QMovie, QPicture, QPixmap. Правда, от этого они не становятся более
“разговорчивыми”, но часто именно метка визуально является центральным объ-
ектом формы.
Еще одна неочевидная возможность — переадресация фокуса ввода “дружест-
венному” элементу управления (buddy). При нажатии на клавиатуре горячей кла-
виши, связанной с меткой, она не получает управления, но передает его другому
элементу формы, для которого фокус ввода имеет смысл. Это работает только в
случае, если метка — текстовая, с установленной для нее “горячей клавишей”, т.е.
в строке попросту должен встречаться амперсанд (“&”). При установке другого, не-
текстового, значения, свойство QLabel: .-buddy сбрасывается. То же происходит и
еще при некоторых операциях, например после вызова метода QLabel: : setNum(),
описанного ниже. Этим механизмом пользуются метки-спутники всех остальных эле-
ментов управления— на самом деле текстовая метка всегда является экземпляром
класса QLabel, вы не можете вывести текст другим способом, не считая рисования.
Существует даже возможность связать метку одного элемента с другим — это мо-
жет понадобиться в сложных элементах управления, состоящих из нескольких
компонентов, хотя того же эффекта можно достичь и более естественным образом,
добавив еще одну “независимую” текстовую метку.
Еще один небольшой сервис класса QLabel связан с выводом чисел — для этого
не нужно пользоваться явным преобразованием, просто воспользуйтесь перегру-
женным методом setNum (), воспринимающим как целые, так вещественные чис-
ла. Кстати, Qt сравнивает новое значение метки со старым и перерисовывает метку
только в случае, если ее “начертания” в новом и старом виде отличаются.
С переходом на Qt 4 метки класса Qlabel, в основном, сохранили свои свойства.
Имели место лишь два небольших изменения: был модифицирован метод
set Alignment (), так что теперь его параметр имеет перечислимый тип
Qt::Alignment, а не является целым числом. Также был выключен режим
setwrap по умолчанию для форматированного текста, поскольку обычно от меток
не ждут такого поведения.
Более “настоящим” элементом управления, на этот раз — прямым потомком
класса QWidget, является класс QLineEdit. Как следует из названия, это поле ре-
дактирования, лишенное таких “радостей”, как перенос строк, полосы прокрутки и
возможности гипертекста. Зато присутствуют: режимы отмены и “отмены отмены”
(undo и redo), функции вырезки, копирования и вставки, а также перетаскивание
текста в поле редактирования и из него.
Как принято в различных пользовательских интерфейсах, поля редактирования
опционально подлежат проверке: с текстовым полем можно связать проверку ввода
через свойство inputMask и метод setValidator (). Максимальную длину можно
ограничить с помощью свойстваmaxLength () (рис. 4.2).
Самым сложным и интересным из перечисленных способов является установка
“валидатора”. “Валидатор” — это экземпляр класса QValidator, а точнее — одного из
таких его потомков, как QIntValidator, QDoubleValidator и QRegExpValidator.
В последнем случае в регулярное выражение не нужно вставлять символы начала и
конца строки (Л и $) — и так ясно, что речь идет об одной строке. Вот как выглядит
техника подключения “валидатора” с регулярным выражением:
QRegExp rx( "-?\\d{l,3}" );
QValidator* validator = new QRegExpValidator(rx, this);
QLineEdit* edit = new QLineEdit( this );
edit->setValidator(validator);
В данном случае регулярное выражение задает необязательный минус и от од-
ной до трех цифр в поле ввода. Пока поле не пройдет “валидацию” — оно не генери-
рует сигнал returnPressed ().
Также в поле ввода можно подавить вывод текста, задав соответствующий ре-
жим с помощью метода echoMode (), — это полезно для полей ввода паролей или в
случае, когда вы хотите “фильтровать” (преобразовывать) вводимые пользователем
символы.
Рис. 4.2. Типизированный ввод, шаблоны
и средства контроля значений позволяют
выполнить большинство технических
проверок вводимых данных
Естественно, что вы по-прежнему можете программно “руководить” элементами
управления. Текст в поле редактирования можно задать или изменить методами
setText () или insert (). Получить его можно с помощью методов text () и
di splay Text () — эти значения могут отличаться, если вы подавили эхо, как ска-
зано выше.
Двумя главными сигналами, которые генерирует текстовое поле, являются
textChanged () и returnPressed(). Мы не будем рассматривать все комбинации,
например копирование или вырезание, обрабатываемые полем ввода, они, скажем,
самые обычные. Заметим только, что элемент текстового ввода может быть создан
пустым, с заданным текстом, а также с заданным текстом и маской ввода — для
этого существует три перегруженных конструктора.
Наконец, последний рассматриваемый нами класс для ввода текста— очень
мощный и порой незаменимый элемент ввода гипертекста, QTextEdit. Это на-
стоящий текстовый редактор гипертекста, работающий в режиме WYSIWYG2 и об-
рабатывающий данные в HTML-подобном формате. Основными единицами форма-
тирования текста являются параграфы и отдельные символы. Кроме того, в тексте
поддерживаются рисунки, списки и таблицы.
2 WYSIWYG — сокр. от “What You See Is What You Get” — что видишь на экране, то и получишь
при печати (известный принцип построения экранного редактора текстов). — Примеч.ред.
Будьте бдительны: в Qt режим RichText3 обозначает собственно формат RTF
(Rich Text Format — расширенный текстовый формат), но внутренне он представ-
лен в формате HTML (HyperText Markup Language — язык, используемый для соз-
дания страниц WWW). Разметка осуществляется тегами, и они же сохраняются в
файле на диске — так что результат сохранения объекта типа QTextEdit с любым
расширением , в том числе *. rtf, всегда будет представлен документом HTML, что
в некоторых версиях Linux даже приводит к срабатыванию системы антивирусной
безопасности: “тип файла не совпадает с расширением”. Так что лучше сохраняйте
RichText-текст в htm-файлах — по крайней мере вы сможете легко открывать их в
браузере.
Программист имеет полный доступ к тексту элемента управления типа QTextEdit —
причем не только как к целому объекту, но и как к отдельным параграфам и сим-
волам. Документ состоит из одного или большего числа параграфов. Символы ну-
меруются начиная с нуля для каждого параграфа. К параграфам, как объектам,
примёнимы такие операции, как выравнивание. Для символов применимы опера-
ции форматирования текста (например, жирное начертание или цветовое выделе-
ние). Класс QTextEdit поддерживает понятие курсора ввода (текущее положение
каретки I-beam4) и, как следствие, текущего параграфа и текущего формата, а так-
же текущего фрагмента выделения.
Немного остановимся на жизненном цикле поля ввода гипертекста. Вначале
вы можете инициализировать поле ввода любым текстом, вызывая метод
setDocument (). При этом текст, находящийся до этого в поле ввода, например
введенный в приложении Designer, удаляется. Если вы вводите текст с разметкой
HTML, то, возможно, считанный впоследствии методом toHtml () код может отли-
чаться в конкретных тегах, хотя визуально будет выглядеть так же, как и при вводе.
Дополнительные возможности программной модификации представляют слоты
insertHTML (), insertPlainText (), append () и т.д. Текст, добавляемый методом
append (), не попадает в буфер'отката, так что именно этот метод рекомендован для
добавления длинных фрагментов.
Аналогично мётоды cut (), сору (), clear () программно выполняют операции
“за пользователя”. Фактически эти методы эмулируют соответствующие клавиа-
турные комбинации, а точнее, они связаны с теми же обработчиками.
В отличие от некоторых других объектных моделей, компонент QTextEdit са-
мостоятельно не занимается сохранением данных — для этого используются до-
полнительные операции. Сам текст извлекается и обновляется через вызовы мето-
дов toHtml () или toPlainText ().
В поле текстового ввода переносы контролируются методом (и свойством)
setWordWrapMode (). Возможные варианты, кроме тривиальных “по ширине эле-
мента” и “не переносить вообще”, включают еще несколько, например “переносить
3 Rich Text означает “обогащенный текст”. Имеется в виду текстовая информация, сохраненная в фор-
мате, доступном для ее чтения и интерпретации множественными приложениями. — Примеч.ред.
4 I-beam — курсор в форме двутавровой балки, определяющий место вставки вводимых с клавиа-
туры символов. — Примеч.ред.
в любом подходящем месте, не обязательно на границе слова”. Ширина
зоны принудительного переноса в пикселях или символах задается свойством
lineWrapColumnOrWidth. Если вы запрещаете перенос или требуете перенос по
словам, но слово не помещается, то ситуацию “спасает” горизонтальная полоса
прокрутки.
Мы не будем подробно останавливаться на всех методах и свойствах класса
QTextEdit — тем более что их около сотни (я насчитал одних методов 60, а в Qt 3
их было еще раза в два больше). Пока достаточно представить, что вы имеете дело с
некоторым текстовым редактором “средней продвинутое™”, таким как KWord или
MS Wordpad, где доступны различные выделения символов: цвет, жирность, на*
клон, подчеркивание и различные шрифты. Доступны уже упоминавшиеся опера-
ции вырезки и вставки в буфер обмена, отмена и “отмена отмены”. Также можно
пользоваться операциями поиска по тексту, выделения фрагмента и такими функ-
циями, как увеличение и уменьшение текста (zoom in/out). Все операции доступны
программно, а многие — и интерактивно, т.е. с помощью горячих клавиш. Имена
методов, как всегда в Qt, являются крайне интуитивными, так что найти нужное —
довольно легко.
Важно отметить, что, несмотря на общую преемственность класса QTextEdit в
версии Qt 4, многие свойства тем не менее были модифицированы. Новый меха-
низм отображения и редактирования текста, Scribe, основан на открытой техноло-
гии отрисовки Unicode-шрифтов.
Главное отличие — это представление текста не в виде строки с гипертекстовой
разметкой типа QString, а в виде документа класса QTextDocument. К документу
возможен доступ и как к последовательному линейному буферу “в духе” класса
QString, и как к иерархии объектов в стиле DOM5. В качестве элементов документа
выступают абзацы, таблицы, списки и другие абстрактные области любой степени
вложения. На уровне блоков текст представляется в виде отдельных символов:
каждый параметр отображения может быть задан на уровне блока или символа.
Блокам соответствуют более общие параметры, такие как выравнивание, цвет фона
абзаца или отступы. В отличие от DOM-интерфейса, управление документом не про-
изводится напрямую. Вместо этого используется интерфейс класса QTextCursor.
Этот интерфейс (экземпляр класса) может быть создан как явно, так и путем по-
лучения интерфейса к классуQTextEdit:
QTextCursor cursor(edit->document());
Все операции, которые вы производите с курсором текста, тут же будут отобра-
жаться в визуальных представлениях, в частности в объекте типа QTextEdit.
В любом случае вы, т.е. программа, и пользователь работаете одновременно с од-
ними и теми же структурами, и вам доступны одни и те же операции, такие как пе-
ремещение абзаца, изменение формата текста и т.д. Например, вы и пользователь
обладаете равными правами по выделению текста и операциям с выделением.
Разумеется, посредством переназначения клавиатурного ввода и операций мыши
5 DOM — сокр. от Document Object Model, т.е. стандарт консорциума WWW, определяющий спо-
собы манипулирования объектами и изображениями на одной Web-странице. — Примеч.ред.
пользователь сам получает такой же программный интерфейс к визуальному пред-
ставлению — откуда и следует общность операций. Для формирования динамической
“заготовки” в поле ввода служит весьма прямолинейный метод QTextCursor: :
insertText().
Несколько иное впечатление производят возможности произвольного формати-
рования класса QTextCursor — пример из документации буквально разочаровыва-
ет, вместо операции “заполнить траекторию текстом”, что доступно во многих сис-
темах, предлагается расчет положения и длины каждой строки. Вы, конечно, легко
сможете реализовать любые эффекты даже с помощью существующего интерфейса,
однако некоторые системы предлагают более высокоуровневые возможности.
Главное, однако, что рендеринг шрифтов теперь полностью отделен от операцион-
ной системы (или, вернее, сделан системонезависимым), так что вы можете опери-
ровать “глифами” как траекториями так же, как это в свое время было сделано в
Windows NT.
4.3. Команды класса QAction, меню
и панели инструментов
Главный объект, связанный с меню, горячими клавишами и панелями инстру-
ментов, — это “независимые” команды QAction. Смысл состоит в следующем. Вы
один раз создайте такую команду, как, скажем, открытие файла, связываете
(встраиваете) ее по собственному желанию в меню, панель инструментов или при-
вязываете к горячим клавишам. Теперь любое из событий (например, выбор пункта
меню или горячей клавиши) будет приводить к выполнению одного и того же ко-
да — раньше в этом не было никакой уверенности.
Это удобно и логично. Однако QAction-команды представляют собой нечто
большее, чем просто обработчики тех или иных событий, — для этого не нужен
объект, достаточно точного указателя на функцию. Экземпляр класса QAction
хранит всю связанную с командой информацию (например, такую, как пиктограм-
ма, текст, всплывающая подсказка и т.д.), а также текущее состояние (например,
такое, как “отмечено” или “недоступно”). Куда бы ни была прикреплена коман-
да — ее подпись, пиктограмма или всплывающая подсказка будут одинаковыми.
Наконец, экземпляры распространяют оповещения об изменении собственного
состояния. В результате, если пользователь, скажем, сделает команду недоступ-
ной, изменит подпись или пиктограмму, связанную с командой, то все связанные
элементы меню и панели инструментов тут же изменят свое состояние согласно но-
вым изменениям.
Существует две возможности создать QAction-объект. Во-первых, можно явно
создать экземпляр, дополнительно настроить его с помощью таких нужных set-
методов, как setCheckable() или setlconText (), а затем прикрепить его к од-
ному или нескольким контейнерам. Во-вторых (это упрощенный вариант), можно
создать команду непосредственно при создании меню, а именно методом
QMenu: : addAct ion (). В принципе команда может быть прикреплена к любому по-
томку класса QWidget через метод addAction ().
Обратите внимание на то, что в прошлой версии 3.3 экземпляр класса QAction сам
прикреплялся к контейнеру методом addTo () и откреплялся методом removeFrom ().
Эта техника уже не актуальна в версии Qt 4. Современный код должен выглядеть
примерно так:
fileOpenAction = new QAction( Qlcon("open.png"), tr("&Open..."),
this) ;
fileOpenAction->setShortcut(tr("Ctrl+O"));
fileOpenAction->setActionTip(tr("Open file"));
connect(fileOpenAction, SIGNAL(triggered()),this,SLOT(open()));
/* Qt 3 connect(fileOpenAction,SIGNAL(activated()),this,
SLOT(openf))); */
QToolBar *fileTools=new QToolBar(this, "file operations");
fileTools->addAction(fileOpenAction);
/* Qt 3 fileSaveAction->addTo( fileTools ); */
QPopupMenu *file=new QPopupMenu( this );
file->addAction(fileOpenAction);
/* Qt 3 fileSaveAction->addTo( file ); */
Важно не забыть привязать с помощью метода connect () активный сигнал ко-
манды к слоту-обработчику, поскольку это единственный шанс сделать что-то по-
лезное в ответ на действия пользователя. Также обратите внимание, что в версии 3
QAction-команда генерировала два сигнала: activated () и toggled (). В версии
Qt 4 сигнал activated () заменен на triggered () и, кроме того, добавлены сигна-
лы changed () и hovered (). Сигнал activated () оставлен только для совмести-
мости и вызывает сигнал triggeredO, так что использовать его нет никакого
смысла.
Обращайте внимание на принадлежность экземпляров QAction: вы можете
прикрепить команду в качестве дочерней к любому компоненту, но обычно коман-
ды имеют глобальную область действия, так что в качестве родительского контей-
нера используйте главную или подчиненную форму.
Теперь рассмотрим оборотную сторону медали, т.е. те контейнеры, к которым
прикрепляются команды. Начнем с панелей управления (или панелей инструмен-
тов): они были значительно изменены по сравнению с третьей версией Qt.
Раньше класс QToolBar происходил от QDockWidget, что приводило к их оди-
наковой обработке и одинаковому поведению. Теперь это два совершенно различ-
ных класса, и при негоциации (распределении) рабочей поверхности они обрабаты-
ваются по-разному. Грубо говоря, панели инструментов имеют преимущество перед
другими компонентами при размещении вдоль рамок главного окна: панели всегда
будут парковаться ближе к рамке или к другой панели, причем так, как если бы это
была неклиентская область. Экземпляры класса QDocWidget при парковке зани-
мают более удаленные от края зоны в клиентской области.
Еще одно отличие состоит в следующем. Класс QDockArea, как подконтейнер,
для размещения панелей управления больше не используется: этот класс вообще
больше не используется. Вместо этого само главное окно приложения типа
QMainWidget с помощью специального менеджера размещения определяет располо-
жение инструментальных панелей. При размещении элементов типа QDockWidget и
QToolBar в главном окне используются достаточно сложные вычисления на уровне
средней школы, хорошо описанные в документации по классу QMainWindow, но мы
не будем сейчас на этом особо останавливаться.
В общем, для создания инструментального меню и управления им вам не надо
ни о чем особо заботиться: главное окно само отвечает за многие полезные функции
панелей инструментов. Вы можете создавать экземпляры класса QAction непо-
средственно встроенными в панель инструментов с помощью метода addAction ().
Несколько перегрузок этого метода позволяют создать сразу кнопку с текстом,
пиктограммой и т.д. Метод возвращает только что созданный экземпляр. В качест-
ве родительского контейнера для него будет установлена сама панель, а значит, и
время жизни у них будет совпадать — имейте это в виду, поскольку после разруше-
ния панели вы перестанете получать и QAction-команды. Кроме того, как только
вы получили указатель на созданный QAction-объект, не откладывая, вызовите
необходимый метод connect ().
Наконец, пару слов о меню. В отличие от Qt3, где меню строились на основе
вспомогательного класса QMenuData, в Qt4 остались только два интерфейсных
класса для построения любых типов меню. Класс QMenuBar служит для создания
главного и других горизонтальных меню, а класс QMenu подходит для всех типов
выпадающих меню, в том числе прикрепленных к главному меню, а также свобод-
ных контекстно-зависимых выпадающих меню. Это упрощает многие вещи. Кроме
того, как уже было сказано, сейчас не QAction-команда решает, к какому меню
прикрепляться, а напротив, само меню располагает методом addAction () для соз-
дания команды и автоматического его связывания с пунктом меню.
Класс QMenuBar был модифицирован по сравнению с Qt3, хотя само имя класса
осталось без изменений. В результате класс имеет как бы два независимых интер-
фейса: новый, упрощенный, которым вы должны пользоваться, и старый, слу-
жащий целям совместимости, но не рекомендованный к применению. К первой
категории относится всего несколько таких мощных методов, как addMenu () и
addActionO, а также addSeparator (). Такой простой набор методов помогает
разработчику “не сходить с пути прямого” и создавать только стандартные гори-
зонтальные меню.
Выпадающие меню, теперь называемые экземплярами класса QMenu, к всеоб-
щей радости, тоже были значительно упрощены; в прошлом остались многочис-
ленные перегрузки метода addltem (), и в общей сложности около пяти десятков (!)
методов и свойств были преданы истории. Главными из оставшихся методов явля-
ются все те же addActionO и addMenu() — для создания отдельных команд и
вложенных выпадающих меню. Кроме-, собственно, команд и подменю, существу-
ют еще и разделители, добавляемые с помощью метода addSeparator ().
При выборе пункта меню соответствующая команда получает сигнал
QAction: : triggered(), точно так же как и при щелчке на кнопке панели инст-
рументов. Обычно вы будете иметь по отдельному обработчику для каждой ко-
манды. Есди, однако, вы захотите обрабатывать любой щелчок или подсветку
любого пункта меню в одной функции, то вам, скорее, придется обрабатывать
сигналы QMenu: : triggered () и QMenu: : hovered ().
В целом можно сказать, что в Qt 4 значительно упростилась работа с командами,
меню и панелями управления, хотя классы по-прежнему перегружены многими
совместимыми методами.
4.4. Классы QAbstractSlider, QAbstractSpinBox
и их производные
В практике вычислений часто приходится выбирать значения из некоторого
диапазона, а также отображать такие значения. Первичным, по историческим при-
чинам, элементом управления такого типа является полоса прокрутки, сопровож-
дающая многие окна верхнего уровня и области редактирования текста.
Библиотека Qt 4 предоставляет базовую функциональность для такого типа зна-
чений в виде класса QAbstractSlider, который заменил собой использовавшийся
в Qt 3 класс QRangeControl. Основной недостаток QRangeControl заключался в
том, что он не являлся потомком класса QWidget, так что производные от него
классы должны были использовать двойное наследование. В двойном наследовании
нет ничего плохого, но в данном случае абстрактный “диапазон”, не имеющий ви-
зуального представления, не делал никакой существенной работы.
Основными характеристиками QAbstractSlider-экземпляра являются макси-
мальное, минимальное и текущее значения. Для установки и опроса этих значений оп-
ределены парные методы, например для минимального значения — это minimum () /
setMinimum () (аналогичные пары методов существуют для максимального и те-
кущего значений).
В классе QAbstractSlider используются два значения, на которые могут дис-
кретно изменяться значения “большого шага”, или страничного, соответствующе-
го, например, нажатию клавиши <PgUp>, и малого, или строчного, соответствую-
щего, например, нажатию клавиши <?>. Эти величины хранятся в свойствах
singleStep и pagestep соответственно, а для их установки можно использовать
один из set-методов. Вы можете программно прокручивать слайдер (бегунок) на од-
но “строчное” значение или на одну страницу, а также устанавливать произволь-
ное, находящееся между максимальным и минимальным, значение свойства
value. Наличие двух величин прокрутки объясняется происхождением всех эле-
ментов этой категории от полосы прокрутки окна, которая изначально “понимала”
прокрутку на строку и страницу.
Кроме того, есть определенная тонкость в получении текущего значения бегун-
ка. Существует два свойства: sliderPosition и value. Есть ли между ними раз-
ница — зависит от значения свойства tracking. Если трекинг-слежение включено
(по умолчанию), то при перемещении бегунка пользователем значение value сразу
будет изменяться в соответствии со значением свойства sliderPosition. Иногда
имеет смысл изменить этот режим так, чтобы не передавать в программу лишние
промежуточные значения.
У самого класса QAbstractSlider существует довольно тонкая “нервная систе-
ма” в виде нескольких сигналов, таких как rangeChanged () или sliderMoved().
Эти сигналы возникают как реакция на действия пользователя. Интересным
сигналом высокого уровня является actionTriggered(): события возникают при
изменении значения бегунка, достижении граничных значений и т.д.
При реализации собственных потомков класса QAbs tract Slider вы должны
переопределить виртуальный метод sliderchange () — этот метод вызывается при
любом значительном событии, начиная от изменения значения и заканчивая таким
“потрясением”, как QAbstractSlider: : SliderOrientationChange. Сами собы-
тия, как вы уже поняли, описаны константами.
Теперь рассмотрим самый интересный, по моему мнению, представитель семей-
ства бегунков и полос прокрутки— класс QDial. Внешне элемент управления
представляет собой нечто среднее между спидометром и ручкой управления стерео-
системой. В любом случае это обычный бегунок с радиальной шкалой. Он имеет та-
кие же, унаследованные от класса QAbstractSlider значения maximum и miminun,
как и другие бегунки и полосы прокрутки (в версии Qt 3 они назывались maxvalue
и minValue).
Для класса QDial существует несколько особых свойств, не предусмотренных в
его родителе QAbstractSlider. Например, на радиальной шкале рисуются засеч-
ки (notches), с которыми связано несколько дополнительных свойств и методов.
Кроме того, свойство wrapping позволяет QDial-“py4Ke” вращаться все время в од-
ном направлении по замкнутому кругу. Графически это отображается замкнутой
круговой шкалой.
Из сигналов можно выделить только valueChanged () — этот сигнал генериру-
ется при изменении значения элемента управления, а его дискретность
(“аккуратность”) зависит от установленного режима setTracking (). Другой унас-
ледованный сигнал, QAbstractSlider: :dialMoved(), генерируется даже при от-
ключенном трекинге.
Раз уж мы рассмотрели класс QDial, то не будет преувеличением сказать, что
мы рассмотрели и класс QSlider. Фактически при отключенном режиме wrapping
эти слайдеры полностью эквивалентны, имеют одинаковые свойства и генерируют
одинаковые сигналы. Естественно, что QSlider-объект выглядит совсем по-
другому, представляя собой линейный градуированный отрезок с бегунком и деле-
ниями, а не круговую шкалу.
Еще один класс происходит от QAbstractSlider — собственно полоса прокрут-
ки QScrollBar. Как правило, это не самостоятельный элемент управления, а одна
из составляющих в таких сложных элементах управления, как поле редактирова-
ния текста или окно (объект класса QScrollView). Созданная независимо, полоса
прокрутки программно не отличается от QSlider- или QDial-объекта.
Обратите внимание на отличие от Qt 3: раньше класс QRangeControl также яв-
лялся суперклассом для QSpinBox. Теперь ситуация изменилась — для различных
окошек счетчика (spin box)6 существует отдельный класс QAbstractSpinBox.
6 Элемент обрамления окна, который представляет собой редактируемое текстовое поле, снабжен-
ное двумя стрелочными кнопками — "вперед" и "назад", — и отображает родственные, но взаи-
моисключающие варианты выбора, — например, дни недели или номера записей в словарной базе
данных. — Примеч.ред.
Базовый класс QAbstractSpinBox является достаточно сложным: он включа-
ет однострочное поле редактирования текста, текстовую надпись, рамку, две
кнопки приращений значения и еще несколько параметров, например упоми-
навшийся уже параметр wrapping, реализующий возможность циклического
изменения значения.
Безусловно, самым важным “проблематичным” отличием от слайдеров явля-
ется поле ввода, куда пользователь может попытаться ввести некорректные, в
терминах слайдера, значения — например, буквы. Кроме этого, нужно контро-
лировать сами величины вводимых значений, пустой ввод и т.д. По сути, класс
QAbstractSpinBox не решает этих вопросов, но создает абстрактные виртуальные
методы (например, validate ()) и предоставляет возможность потомкам самим за-
ниматься проверкой ввода.
Есть также такое “удобство”, как элемент specialValueText, который пред-
ставляет собой специальную строку (подобную "Auto", "Default" или т.п.), заме-
няющую минимальное значение. Программа в результате ее использования полу-
чит просто минимальное значение, а не специальный текст. Выглядит это так:
QSpinBox sb(-1,20,1,this);
mb.setSpecialValueText("Default") ;
От класса QAbstractSpinBox происходит класс QSpinBox— такой же класс
был и в Qt 3, но это — полностью переписанный компонент с совершенно другим
происхождением. Класс QSpinBox немного усложнен по сравнению со своим абст-
рактным суперклассом: в поле ввода находятся три текстовых поля— prefix,
cleanText и suffix. Для редактирования пользователем доступно только поле
cleanText, а поля префикса и суффикса являются декоративными элементами,
например в качестве префикса может использоваться текст " $", а в качестве суф-
фикса — "грн.".
В принципе, сам класс QSpinBox поддерживает только числовые значения, однако
если вы переопределите в своем подклассе методы validateO, textFromValue() и
valueFromText (), то пользователь сможет вводить в поле ввода произвольные стро-
ковые значения, например, "раз", "два", "три". При этом значением QSpinBox все
равно всегда будет целое число.
Небольшой вариацией на ту же тему является класс QDoubleSpinBox, позво-
ляющий вводить вещественные числа типа double. Естественно, что все параметры
(например, minimum, maximum, value и singleStep) имеют тип double. Также об-
ратите внимание на свойство decimals, которое содержит число отображаемых де-
сятичных знаков. Это значение может быть в диапазоне от нуля до четырнадцати
знаков после запятой: вы, конечно, должны установить его так, чтобы было видно
каждое изменение значения на величину singleStep. Например, если значение
singleStep равно 0,01, то decimals должно быть не меньше двух. По умолчанию
значение singleStep равно единице, a decimals — двум знакам после запятой.
Наконец, еще одним вариантом на тему SpinBox является класс
QDateTimeEdit. Этот новый класс заменяет все классы редактирования даты и
времени, существующие в версии Qt 3, хотя старые варианты также сохранены для
совместимости. Редактор QDateTimeEdit представляет собой целую группу око-
шек счетчика (spin box), разделенных символами форматирования. Для управле-
ния максимальными и минимальными значениями служат setMinimumDate и
setMaximumDate, и аналогично для времени.
Дата и время представляются в формате displayFormat, например "hh :mm: ss"
обозначает привычный формат отображения времени. Формат не только служит
внешней маской — установка формата также ограничивает возвращаемые значе-
ния типа QDateTime. Так, установка формата "hh :mm: ss" всегда будет возвращать
установленную дату, независимо от указанных граничных условий:
QDateTimeEdit dte(QDate(2005, 6,18));
dte.setDateRange(QDate(2001,1,1),QDate(2003,12,31));
dte.setDisplayFormat("hh:mm");
Области формата, такие как “часы” или “минуты”, называются в терминах
QDateTimeEdit секциями. Каждый тип секции закодирован однобитовым фла-
гом типа QDateTimeEdit: : Sections. В результате вы можете вызвать метод
currentsection () для определения, в какой области ввода находится пользова-
тель. Метод displayedSections () выводит весь список отображаемых секций в
виде битовой OR-маски.
Как легко догадаться, именно ограничением отображаемых секций и занимают-
ся теперь уже “вторичные” классы совместимости QDateEdit и QTimeEdit.
4.5. Страничные отображения с закладками
Страничное отображение, также известное как диалог с закладками, является
популярным средством “упаковки” множества элементов управления на ограни-
ченной площади. Qt 4 предлагает вам две возможности для реализации закладок.
Инструмент высокого уровня, расположенный в палитре Designer, реализован
классом QTabWidget. Если вы не хотите выходить за рамки стандартного поведе-
ния, то этого вполне достаточно.
Класс QTabWidget имеет несколько “страниц”, представленных закладками.
Форма и расположение закладок задаются методами setTabPosition() и
setTabShape().
Вы создаете закладку с надписью и расположенными на ней элементами управ-
ления (обычно — одним, а именно фреймом, с соответствующим менеджером раз-
мещения, который уже служит контейнером для остальных). Добавление новой
закладки происходит в одно действие, путем вызова метода addTab (). Для данной
страницы можно задать такие параметры, как текст надписи (setTabText), иконка
(setTablcon), режим доступности (setTabEnabled) и т.д. Все эти методы прини-
мают в качестве первого параметра индекс страницы.
Получить текущую страницу можно, вызвав метод cur г ent Index () (в версии
Qt 3 аналогичный метод назывался currentTablndex ()). Соответственно, теку-
щий элемент управления доступен через метод currentwidget (). Обработка эле-
ментов управления производится обычным образом, а именно так, как если бы они
были расположены на одной большой форме. Никакой “страничной” специфики,
кроме визуального эффекта, не существует.
Если вы не удовлетворены стандартным элементом управления, то Qt 4 предос-
тавляет вам “конструктор” из двух вспомогательных компонентов. Один из них,
создаваемый на базе класса QTabBar, представляет собой саму линейку, в которой
расположены закладки. Для каждой закладки, добавляемой с помощью метода
QTabBar: : addTab, определены текстовая надпись, пиктограмма, режим доступно-
сти и т.д. В том числе с закладкой (с помощью метода setTabData ()) вы можете
связать произвольные данные типа QVariant. Иногда это бывает полезным, если
вы формируете строку закладок в одном месте, а используете в другом. Главным и
единственным событием полосы закладок является currentChataged ().
Еще одним вспомогательным классом, доступным в палитре Designer и отдель-
но от класса QTabWidget, является класс QStackedWidget (соответствует классу
QWidgetStack в Qt 3). Это класс-контейнер для списка элементов управления.
В каждый конкретный момент времени отображается только один элемент из спи-
ска (currentwidget), расположенный в списке под индексом current Index. Две
миниатюрные кнопки в верхнем правом углу, наблюдаемые в QTabWidget-объекте,
являются частью QStackedWidget-объекта.
Собственно, класс QStackedWidget обладает очень простым программным ин-
терфейсом: кроме добавления новых элементов методом addwidget () и установки
текущего методом setcurrentindex (), вам вряд ли понадобится как-то взаимо-
действовать с ним. Однако обратите внимание на то, что этот элемент происходит от
класса QFrame, который в свою очередь является представителем класса QWidget,
так что их потомок QStackedWidget наследует все свойства и методы фреймов.
В заключение вспомним удобный, но очень “очевидный” класс QTabDialog,
существовавший в Qt3. Этот класс признан морально устаревшим, хотя и сохранен
в библиотеке совместимости в виде класса Q3TabDialog. В свете последних веяний
моды диалоги с закладками рекомендуется строить самостоятельно, располагая
QTabWidget-объект на QDialog-форме (рис. 4.3).
4.6. Стандартные и пользовательские диалоги
Раз уж мы упомянули класс QDialод, то заодно рассмотрим и остальные диа-
логи, связанные с этим классом. Окна диалога — это элементы управления верх-
него уровня, которые тем не менее имеют родительский контейнер. Диалоги мо-
гут быть как модальными, блокирующими работу приложения до возвращения
управления, так и немодальными, которые могут быть переведены на задний
план (и часто там теряются). Обычно диалоги возвращают значения, а некоторые
могут быть расширены за счет новых компонентов. Как правило, диалоги имеют
фиксированный размер, но иногда могут его изменять (если вызван метод
setSizeGripEnabled(true)).
Самый общий тип диалогов — модальные, с возвращаемым значением целого
типа, которое чаще всего обозначает активную кнопку, вроде Да, Нет, Не знаю.
Обычным способом работы с модальными диалогами является вызов метода
exec (), который блокирует работу приложения (визуального потока) до закрытия
окна диалога и возвращает значение. Класс QDialog имеет три слота закрытия
диалога — accept (), reject () и, более общий, done (), который воспринимает
константы Accepted или Rejected. Указанное значение и будет возвращено в
приложение.
_____________
Permissions-------------------------------------
Readable
Writable
Executable
------------------------------------------------
—Ownership--------------------------------------
Owner
Group
| root
Рис. 4.3. Диалоговое окно с закладками
создается в среде Qt Designer в одно
действие, хотя программно теперь это
выглядит как “диалог + закладки”
Методы возврата управления из диалога могут повлечь за собой нечто большее,
чем простое сокрытие окна, что является действием по умолчанию. Поскольку все
они вызывают метод QWidget: : close (), это может привести даже к разрушению
объекта диалогового окна, если оно создано с ключом Qt: :WA_DeleteOnClose.
Если это главное окно приложения — тогда также завершится все приложение. Ес-
ли это было последнее закрытое окно из множества окон верхнего уровня, то при-
ложение получит сигнал QApplicatipn: : lastWindowClosed(), что тоже обычно
вызывает завершение приложения. Обратите внимание на то, что если вам нужны
значения, встроенные в диалог (например, такие, как код возврата метода
result ()), то вы не должны разрушать окно диалога.
В случае немодальных диалогов, которые отображаются независимо от главного
окна приложения (как диалоги поиска и замены), они активизируются методом
show(), который немедленно возвращает управление (рис. 4.4).
Среди классов кнопок мы уже встречали кнопки по умолчанию— кнопка по
умолчанию выделяется более широкой рамкой и срабатывает при нажатии <Enter>.
Для задания главной кнопки вы вызываете метод QPushButton: : setDefault () —
обычно это кнопка ОК, но в диалогах вроде “Вы действительно желаете отформати-
ровать весь диск?” это кнопка Cancel. Аналогично клавише <Enter> существует
специальная обработка нажатия клавиши <Esc> — это немедленно вызывает вы-
полнение кода возврата со значением Re j ected.
Рис. 4.4. Диалоговые окна штатно
поддерживают “потайные” области
расширения, где отображаются редко
используемые элементы управления
Существует также такая возможность, как расширенные диалоги. Диалог ото-
бражается в краткой форме, но при щелчке, например, на кнопке More диалог расши-
ряется в горизонтальном или вертикальном направлении. Методы setExtension (),
setOrientation () и showExtension () как раз и занимаются реализацией этого
эффекта. Метод setExtension () вызывается в момент, когда окно скрыто, и до-
бавляет к нему элемент управления (фрейм), который и будет расширять данное
окно дополнительными компонентами.
В качестве демонстрации окна диалога выберем пример немодального диалога
поиска, поскольку немодальные окна вызываются немного сложнее, чем простым
вызовом метода ехес ().
void Editorwindow::find{) {
if (!findDialog) {
findDialog=new FindDialog(this);
connect(findDialog,SIGNAL(findNext()), this,SLOT(filndNext()));
}
findDialog->show();
findDialog->raise() ;
findDialog->activateWindow();
}
Как видите, кроме show(), надо вызвать еще пару методов, выдвигающих наше
окно на передний план и передающих ему управление. Дело в том, что окно после
метода show () будет отображено в том состоянии, в котором оно было скрыто —
а это не всегда передний план, а, скорее, наоборот.
Относительно сигналов — диалоговое окно воспринимается как обычный эле-
мент управления, без каких-либо особенностей. Любой такой контейнер, как окно,
диалог или фрейм, должен генерировать содержательные сигналы для оповещения
об изменении своего состояния.
Теперь кратко рассмотрим производные окна диалога, которые выполняют рас-
пространенные операции, например открытие файлов или настройка принтера.
Их иерархия значительно претерпела изменения по сравнению с Qt 3: классов и
уровней стало больше (за счет абстрактных уровней), а методов и свойств — значи-
тельно меньше.
Общий подход для всех стандартных окон диалога — это отказ от явного созда-
ния экземпляра и вызова метода ехес (). Вместо этого вызывается статический
get-метод, который создает окно диалога одним из двух способов, в зависимости от
системы, и поставляет запрошенные данные. Эти статические методы работают по-
разному под управлением Linux и Windows/Мас OS X. В первом случае будет соз-
дан экземпляр QDialод нужного типа, который и будет отображен. В случае MS
Windows или MacOS X будет вызван стандартный диалог, встроенный в оконную
систему. Это редкий случай, когда Qt использует готовые элементы управления
(как известно, потомки класса QWidget имеют совершенно независимые от опера-
ционной системы события, свойства и жизненный цикл).
Таким образом работают все стандартные диалоги, которые поставляют любые
настройки: от имени открываемого файла до цвета в формате RGB или настроек
шрифта. Большинство параметров диалога задается в конструкторе (в роли
“заместителя” которого часто выступают статические методы, о которых шла речь
выше) — именно благодаря этому так уменьшилось количество set-методов в Qt 4.
В качестве самого популярного примера рассмотрим диалог на базе класса
QFileDialog — все остальные похожи на него. Этот диалог служит для выбора
существующего файла или каталога, а также для создания имени нового фай-
ла, например, при выборе команды SaveAs. Главным статическим методом,
являющимся “внештатным” конструктором, “назначен” один из get-методов:
getExistingDirectory (), ge tOpenFil eName (), getOpenFileNames() или
getSaveFileName (). Эти методы получают “увесистый” список параметров, опре-
деляющих, например, фильтрацию файлов по расширению, начальный каталог
поиска, а также строки надписей, появляющиеся в окне диалога.
Вот, например, как можно открыть окно диалога для получения имени одного
файла.
QString s=QFileDialog::getOpenFileName(
this,
”Выберите файл”,
"/home”,
"Изображения (*.gif *.png *.jpg)");
Альтернативно можно создать реальный экземпляр класса QFileDialog, по-
томка классов QDialog и QWidget. В этом случае вы можете делать с окном диалога
все, что доступно для отдельного элемента управления:
QFileDialog fd=new QFileDialog(this);
fd->setMode(QFileDialog::AnyFile);
fd->setDirectory("/home");
fd->setFilter("Изображения (*.gif *.png *.jpg)");
fd->setViewMode(QFileDialog::Datail);
QStringList files;
if (fd->exec())
files=fd->selectedFiles(); // в документации здесь опечатка
Мы не будем особо углубляться во все настройки, использованные в этом фраг-
менте, поскольку они очень просты и интуитивны.
Нерассмотренными остались совсем простые уж элементы управления.
Как всегда, мы упустили несколько (около дюжины) второстепенных элементов
управления и вспомогательных классов. Из визуально броских это, например,
класс QLCDNumber, предназначенный для отображения цифр в виде жидкокри-
сталлического дисплея наручных часов. К сожалению (а фактически — ко все-
общей радости), объем этой книги и наша жизнь не позволяют мне отвлекать вас
такими второстепенными темами — но, освоив метод Qt, вы несомненно легко
поймете назначение и принцип работы любого существующего компонента поль-
зовательского интерфейса, а также создадите и новые.
4.7. Оболочка “модель-представление”,
класс QAbstractltemView
и его подклассы
Обратите внимание на факт, который на первый взгляд может показаться не-
значительным: класс QListBox в Qt 4 уже вышел из употребления. Вместо этого со-
временные списки нужно создавать на основе классов QListWidget или QListView.
Также предан забвению и сложный класс QlconView, занимавший в Qt 3 целый
модуль. Аналогично стал историей и класс QTable — вместо него нужно использо-
вать класс QTableWidget. Похоже на очередное переименование в целях марке-
тинга.
На самом деле все значительно сложнее — затронуты самые основы объектной
модели. В Qt 4 в корне пересмотрено построение всех таких “групповых” элементов
управления, как списки и таблицы, которые могут обрабатывать сотни и тысячи
элементов данных, визуально представленных как строки или иконки (пикто-
граммы), а на уровне программной логики соответствовать файлам, записям базы
данных и любым другим сущностям предметной области.
Механизм Qt 4, реализующий такие “большие” элементы управления, называ-
ется оболочкой “модель-представление” (model-view framework). В материальном
исчислении данная оболочка представлена достаточно полной и гибкой иерархией
классов, начиная, как это принято в Qt, с самых общих и абстрактных.
Для начала обратимся к базовому классу отображения всех списков, таблиц и
деревьев в Qt 4 — классу QAbstractltemView. Конструктив QAbstractltemView
служит основой для всех классов, в которых обрабатывается множество элемен-
тов. Перечислим его предков (в порядке иерархии): Qwidget, QFrame,
QAbstractScrollArea. Даже и не погружаясь в рассмотрение этих классов, стано-
вится ясно, что класс QAbstractltemView, как минимум, является фреймом с
возможностями включения полос прокрутки.
Основная концепция Qt 4, выраженная в оболочке “модель-представление”, как
нельзя лучше демонстрируется на примере списков. Сам класс QAbstractltemView
не хранит отображаемые элементы и не занимается их обработкой— его задача
обеспечить потомкам такие визуальные эффекты, как прокрутка изображения,
а также стандартные действия по вводу текста, редактированию, слежению за те-
кущим положением “курсора” (будь то выделенная строка списка или активный
элемент древовидного представления), реализуя при этом возможности по выделе-
нию одного или нескольких элементов.
Поведение объекта класса QAbstractltemView зависит от того, в каком режиме он
находится. Сами режимы, описанные State-константами (NoState, DraggingState,
EditingState и т.д.), определяют, как представление будет реагировать на
действия мыши и клавиатурный ввод. Понятно, что при редактировании дан-
ных реакция будет иной, чем при перетаскивании. Каждый потомок класса
QAbstractltemView должен исследовать эти режимы и действовать соответственно.
Например, если пользователь не занят никаким “особым” действием, например
редактированием, то он находится в режиме навигации. В этом режиме он переме-
щается между элементами согласно режиму CursorAction. Напротив, другой би-
товый вектор, EditTriggers, являющийся комбинацией флагов EditTrigger,
указывает, какие действия пользователя приводят к переходу в режим редактиро-
вания элемента, начиная от NoEdi tTriggers (нулевой псевдофлаг; используется
только отдельно) и до, например, Doubleclicked (т.е. для начала редактирования
нужно дважды щелкнуть на элементе) или CurrentChanged (редактирование на-
чинается сразу после перехода на новый элемент).
Мы не будем детально исследовать класс QAbstractltemView— надеюсь, вы
проделаете это сами. Здесь только важно определиться, на каком уровне абстрак-
ции находится этот класс и за что он отвечает, а именно: за такие общие принципы
внешнего интерфейса, как отображение и взаимодействие с пользователем.
Еще один абстрактный класс, QAbstractltemModel, является прототипом для
построения хранилищ элементов. Точнее, модель не обязательно хранит данные, но
всегда знает, где их получить: из собственных запасов, у другого объекта, из файла,
базы данных, или их можно сгенерировать на основе формулы, не суть важно. Эле-
менты в общем случае организованы в виде иерархии плоских таблиц — предпола-
гается, что таким образом можно выразить все такие современные схемы представ-
ления данных, как списки, таблицы и деревья. Если вы не нуждаетесь в иерархи-
ческом представлении, то можете считать, что хранилище имеет вид обыкновенной
таблицы или даже списка — это, так сказать, частные, вырожденные случаи.
В качестве указателя на отдельный элемент модели служит экземпляр особого
класса— QModel Index, который возвращается в ответ на обращение к методу
QAbstractltemModel: : index () с заданными строкой и колонкой. В этом классе
описано все, что нужно для нахождения элемента, вплоть до указателя на модель, в
которой он размещен. Характеристики индекса — строка, колонка и родительский
индекс, возвращаемые методами QModellndex: : row(), column () и parent () со-
ответственно. Элементы верхнего уровня не имеют родительского узла и поэтому
возвращают недействительный элемент, для которого метод isValid() возвраща-
ет значение FASLE. Кроме родительского узла, можно также получить соседний
(сестринский) и дочерний, опросив методы sibling () и child ().
Собственно элемент данных возвращается методом data () — это первый метод,
который должен переопределить потомок для хранения данных определенного
типа. Хорошая модель также переопределяет метаданные строки или столбца
(свойство headerData) и еще, как минимум, количество строк (с помощью метода
rowCount ()).
Важно: каждая ячейка обычно хранит целый набор данных (величин), каждое
из которых сопоставлено с одной из ролей, которые кодируются целыми констан-
тами. Вы запрашиваете не только адрес данных, но и нужный тип (роль) возвра-
щаемых данных. Наглядный пример: с ячейкой в электронной таблице могут быть
связаны константа, формула, строка форматирования, данные об используемом
шрифте, режим выравнивания и тип данных по умолчанию. При расчете таблицы
вас интересует формула, а при выводе на экран — значение и, скажем, цвет симво-
лов. Можно использовать как предопределенные роли (например, как самую важ-
ную DisplayRole), так и собственные.
Итак, класс QAbstractltemModel хранит данные, предоставляя указатель на
отдельный элемент с помощью QModelIndex-объекта, a QAbstractltemView-
экземпляр отвечает за интерактивный интерфейс модели, которую возвращает его
метод model (). В принципе, модель и представление не должны быть сильно зави-
симы: каждый из них должен воспринимать экземпляр абстрактного типа. Разуме-
ется, некоторые представления специально созданы и лучше подходят для опреде-
ленных моделей. В дальнейшем мы рассмотрим конкретные реализации этого
механизма.
4.7.1. Классы QListView и QList Widget
Начнем с условно самой простой модели и комплиментарного представления —
одномерного списка типа QListView. Современный QListView-список, построенный
на основе новой технологии model-view, заменил классы QListBox и QlconView
из Qt 3.
Собственно, класс QListView может отображать элементы в двух режимах, за-
даваемых свойством viewMode, — в виде текстового списка и пиктограмм. Другие
возможные параметры описываются остальными свойствами. Так, например, свой-
ство flow отвечает за размещение элементов— слева направо или сверху вниз.
Свойство movement указывает, могут ли перемещаться отдельные элементы и
должны ли они при этом выравниваться по сетке. Значение resizeMode форсирует
или запрещает перестроение элементов после изменения размера контейнера и т.д.
Большинство остальных свойств и методов (например, iconsize, editTriggers
или setTextElideMode ()) класс QListView наследует от своего абстрактного
предка Q Abstract Itemview.
Параллельно существуют две “урезанные” до одного измерения модели, осно-
ванные на классах QAbstractListModel и QStringListModel. Обе представляют
собой реализацию одномерных списков, в последнем случае — списка текстовых
строк. Именно с этой моделью чаще всего и работает класс QListView.
На основе класса QListView построен элемент управления еще более высокого
уровня — QListWidget. Этот элемент создан для сокрытия всех сложностей явной
работы с моделью — модель сокрыта внутри этого класса, и вы общаетесь с ней кос-
венно, посредством методов класса QListWidget. Для добавления элементов,
имеющих специальный тип QListWidgetltem, служит метод QListWidget: :
insertItem(). Однако еще проще сразу создавать элементы, принадлежащие дан-
ному списку:
new QListWidgetltem(tr("Element 1*), listwidget);
Посчитать элементы в списке позволяет метод count (), а удалить — метод
removeitem (). Текущая строка хранится в свойстве currentRow. В общем, все это
напоминает старый класс QListBox, за исключением того, что в основе лежат более
сложные и общие механизмы.
4.7.2. Класс QComboBox
Раз уж мы затронули класс QListView, то по ходу упомянем и основанный на
нем класс QComboBox. Это очень популярный элемент управления, состоящий из
строки ввода текста, а также выпадающего меню, из которого пользователь может
выбрать вариант ввода. Для полноты изложения нужно сказать, что, кроме выпа-
дающего списка и текстового поля, класс QComboBox содержит еще два элемента —
надпись, представленную элементом Label, и мини-кнопку, при щелчке на кото-
рой, собственно, и выпадает список.
В интерфейсе класса QComboBox присутствует настройка всех параметров, соот-
ветствующих полю текстового ввода, — по сути, это более всего напоминает поле
ввода QLineEdit, поскольку в результате значением поля ввода типа QComboBox
всегда является одна строка текста. В частности, вы можете задать шрифт, длину
поля ввода, валидатор или даже получить доступ напрямую к дочернему полю вво-
да через метод QComboBox: : 1 ineEdit (). Впрочем, также вы можете получить и
дочерний список (фактически — не только список) типа QAbstractltemView через
метод view(). Если помните, раньше для выпадающего списка использовался эк-
земпляр класса QListBox, доступный через метод listBox (). Можно даже устано-
вить новые объектные значения для этих свойств, хотя вам это понадобится только
при создании собственных компонентов.
Обратите внимание на то, что, в отличие от элемента типа QLineEdit,
QComboBox-объект возвращает выбранную или введенную пользователем строку
методом QComboBox: :currentText (), а не QComboBox: : text () — последний ме-
тод вообще не существует. Если пользователь выбрал один из пунктов меню, то по-
лучить его индекс можно методом current Index (). Не полагайтесь на постоянство
списка и не храните это значение: индекс заданной строки может измениться.
В области свойств, методов и сигналов встроенного списка класс QComboBox ма-
ло чем отличается от QListView-списков, при этом все предыдущие замечания по
классу QAbstractltemView также остаются в силе.
4.7.3. Классы QTableView и QTableWidget
Совершенно аналогично одномерным классам ведут себя двухмерные:
QTableView и QTableWidget. Как легко догадаться, оба эти класса служат
для отображения табличных данных. Раньше, в Qt 3, то же поведение было
реализовано с помощью класса QTable, но новые классы имеют совсем другое
происхождение.
Если говорить об отличиях от одномерных вариантов, то первое и самое замет-
ное состоит в наличии горизонтальных и вертикальных заголовков, доступных че-
рез методы horizontalHeader () и verticalHeader () соответственно. Поскольку
заголовки сами по себе являются списками типа QHeaderView (потомками класса
QAbstractltemView), можно сказать, что класс QTableView содержит целых три
представления: одно собственно табличное и два экземпляра заголовков. Класс
QHeaderView (который заменяет бывший ранее в употреблении класс QHeader) ис-
пользуется не только при отображении таблиц, но и при отображении деревьев.
К заголовкам можно применять такие привычные действия, как изменение разме-
ра (ширины колонок и высоты строк), изменение порядка строк или колонок или
задание сортировки (для этого достаточно щелкнуть на нужном заголовке). Встро-
енные заголовки автоматически связаны сигналами с контейнером, так что вам не
нужно заботиться об этом. Например, сигнал QHeaderView: :sectionMoved() ав-
томатически связан с таблицей, так что данные сразу же последуют за перемеще-
нием “своего” заголовка.
Из других внешних отличий стоит подчеркнуть наличие сетки, разделяющей
отдельные ячейки. Тип этой сетки определяется свойством gridstyle. Подобно
одномерным спискам, таблица умеет скрывать свои строки и столбцы, а потом сно-
ва их отображать (рис. 4.5).
go Ij1 3 fen» 9:6 ^0‘hem 91 0 item 9:T < > З hem 10:0 0 Item 10:1 0 Item 102 0 Item 11:0 3 •tem ll:1 .0 11:2 $ Rem 12:0 $ 121 $ 12:2 0 Item 13:0 0 Item 13.1 0 Item 132 0 Item 14:0 Rem 14:1 $ Item 142 0 Item 15:0 0 Item 15:1 0 Item 15:2 & Rem 16» & hem 16:1 0 hem 16:2 $ Item 17:0 0 hem 17:1 0 Item 172 0 hem 18:0 $ Rem 18:1 ;0 hem 182 0 Item 19:0 0 hem 19:1 0 hem 192 0 Item 20:0 :0 hem 20:1 0 Rem 202 $ Item 21.0 :0 Item 21:1 $ hem 212 0 hem 22:0 & Rem 22:1 0 hem 222 0 hem 23:0 Item 23:1 i0 Item 232 0 hem 24» $ hem 24:1 0 hem 242 £? r<TQ " г * Al**4 @ 0 hem 1.0 0 hem V ®0 hem 2:0 0hem2: 1$ 31 hem 3:0 0 hem3: ® 0 hem 4:0 0 hem 4: IS 0 hem 5:0 & hem 5: ® 31 hem 6.0 0 *tem 6: Й) 0 hem 7:0 0 •** 2: Ш 31 Item 8:0 31 Item 8; ® 31 hem 9:0 $ hem 9: № $ hem 10:0 hem 1< В 0 hem 0*0 31 hem 0: G 31 hem 0:0 31 hem 0: Ш 31 Item... 31 hem 0: ® 0 hem... 0 Item 1: В 0 horn... 0 hem 2: G 0 hem... 0 hem 3; B 0 hem... 0 Item 4: В 0 hem ... 0 hem В 0 hem... 0 Rem 6: В 0 hem... 0 hem 7: В 0 hem... 0 Item 8££ ® 0 Item... 0 Item 9i«r aa ж. hem 0:0 hem 1:0 Item 2.-0 hem 3:0 hem 4:0 0 0 0 0 0 hem 5:0 hem 6:0 item 7.-0 hem 8:0 hem 9:0 0 0 0 0 hem 10:0 hem 11:0 hem 12» hem 13:0 0 0 0 0 Item 14» hem 15:0 Item 16» hem 17:0 0 0 0 0 hem 18» hem 19:0 hem 20:0 Item 21:0 0 0 0 0 Item 22» Item23:0 Item 24:0 Item 25:0 0 0 0 0 hem 26» Item27:0 hem 28» Item 29:0 0 0 0 0 hem 30» Item 31:0 hem 32:0 hem 33:0 0 0 0 0 hem 34» Item 35:0 term 36» Item 37» 0 0 0 0 g
'%™
а»
’«1 ",j
Рис. 4.5. Табличное представление заменило eQt4 класс IconView: теперь
самые сложные конструкции реализуются как деревья таблиц, а обычные
таблицы и списки являются их частными случаями
Располагающийся в палитре Designer инструмент типа QTableWidget пред-
ставляет собой пользовательский элемент управления высокого уровня, упрощаю-
щий доступ к табличной модели. Например, метод QTableWidget:: setitem()
напрямую устанавливает значение определенной ячейки, а метод QTableWidget: :
setHorizontalHeaderltemf), опять-таки напрямую, явно не обращаясь к экзем-
пляру класса QHeaderView, устанавливает заголовок определенного столбца и т.д.
Если ваша задача — обычное прикладное приложение, то вы, скорее всего, будете
использовать только класс QTableWidget.
Так же, как и со списками, существует специальный тип QTableWidgetItem,
который обычно содержит текст, пиктограмму или флаг выделения. Дополнитель-
но каждый экземпляр класса QTableWidgetItem может иметь собственное цвето-
вое и шрифтовое оформление. Дополнительные флаги flags () определяют, может
ли ячейка быть выбрана или модифицирована.
4.7.4. Классы QTreeView и QTreeWidget
Вот мы и добрались до самых сложных элементов, представленных в
Qt-оболочке “модель-представление”. Скорее, в древовидных представлениях не
так много сложностей, как в древовидных моделях, которые действительно пред-
ставляют собой иерархические таблицы.
Из визуальных особенностей этого представления сразу можно вспомнить свой-
ство expandable, которое указывает, может ли пользователь раскрывать отдельные
пункты меню, чтобы показывать или скрывать дочерние элементы. Как правило,
древовидные представления не позволяют редактировать элементы данных, но это
скорее соглашение, чем правило. Метод editltem() начинает редактирование за-
данного элемента, если он еще раньше не вошел в режим редактирования на основе
существующих правил (рис. 4.6).
Рис. 4.6. Реализация древовидного представления
немного отличается от привычного: фактически,
это дерево и таблица одновременно, т.е. “два в одном”
Конечно, вы уже догадались, для чего предназначен элемент типа QTreeWidget.
Действительно, это элемент управления высокого уровня для отображения деревьев.
Тип используемых элементов — QTreeWidgetItem. С древовидными представления-
ми связана строка заголовка QHeaderView, и это немного отличает QTreeWidget-
представления от тех, что встречаются в MS Windows. Для того чтобы элементы
сразу могли иметь комментарии, пояснения и так далее, сразу после создания
QTreeWidget-объекта определите количество колонок с помощью метода
setColumnCount () — предполагается, что их количество будет постоянным на
протяжении всей жизни элемента управления (рис. 4.7).
В остальном класс QTreeWidget очень похож на QTableWidget: он так же по-
зволяет напрямую работать со встроенной в него моделью, строкой заголовка и, ра-
зумеется, с параметрами представления (с помощью методов, унаследованных от
класса QAbstractltemView).
Si Genins started
8 Designing a Component
Creating a Dialog
Composing die Dialog
Creating a Layout
Signal and Slot Connections
В Using a Component in Your Application
The DlrectApproach
The Single Inheritance Approach
The Multiple taheritance Approach
Ki Automatic Connections
® Font» Editing Mode
How to familiarize yourself with Qt Designer
Creating a GUI foryour application
How to create a dialog
Putting widgets into the dialog example
Arranging widgets on a form
Making widget communicate with each other
Generating code from forms
Using a form without any a<|ustments
Subclassing a form's base dass
Subclassing the form itself
Connecting widgets using a naming scheme
How to edit a form in Qt Designer
General Features Common container features
Frames
Group Boxes
Slacked Widgets
Tab Widgets
Toolbox Widgets
35 Connection Editing Mode
QFrame
QGroupBox
QStackedWIdget
QTabWldget
QToolBox
Connecting widgets together with signals and slots
Puc. 4.7. В Qt сложно провести черту между
древовидным представлением и таблицей
4.7.5. Список файлов и каталогов типа QDirModel
Модель QDirView можно охарактеризовать как “одиноко стоящую”, не имею-
щую особой привязки к какому-то представлению и в качестве данных получающую
список файлов вашего компьютера (а также и соседних, если они “вмонтированы” в
вашу файловую систему). Приведем пример гибкости предложенной системы.
Во-первых, как уже было сказано, данные являются внешними, получаемыми из
операционной системы. Во-вторых, модель вводит большое количество собственных
специфических свойств и методов. Например, по заданному индексу QModelndex
можно получить данные о файле с помощью следующих методов с такими “красно-
речивыми” именами: fileicon(), fileinfo (), fileName () и filePath(). Сюда
даже встроены такие свойства, как lazyChildCount (“ставить на каталогах значок
расширения, не проверяя фактического наличия там файлов”) или resolveSylinks
(“разрешать символические ссылки”).
Модель QDirView хорошо “рифмуется” с древовидным представлением — клас-
сический пример на эту тему выглядит так.
#include <QtGui>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QDirModel *model = new QDirModel;
QTreeView *tree = new QTreeVieW;
tree->setModel(model);
tree->setWindowTitie(QObject::tr("Dir View"));
tree->resize(640, 480);
tree->show();
return app.exec();
}
Если вы замените в приведенном выше примере класс QDirView на QListView,
то результат также будет вполне удовлетворительным. Это прекрасный пример
гибкости и полиморфизма на содержательном уровне.
4.7.6. Последние замечания по теме “модель-представление”
Вы можете вполне успешно писать программы, используя старую технику и
готовые классы из категории *Widget. Однако со временем вы обязательно захо-
тите создать собственное представление для существующей модели или даже соб-
ственную модель данных. В таком случае вам понадобится дополнительная ин-
формация.
Во-первых, обратите внимание на класс QStandardltemModel. Эта модель вы-
ступает как хранилище (т.е. данные хранятся в самой модели) для произвольных
данных типа QVariant. Если вы не хотите получать данные из более сложных ис-
точников, то, возможно, — это все, что вам нужно, например, для реализации
электронных таблиц с особыми свойствами. Фактически, этот класс просто реали-
зует интерфейс QAbstractltemModel на минимальном (для создания экземпляра)
уровне. В любом более сложном случае, например для получения внешних данных,
вы, вероятно, захотите наследовать сам класс QAbstractltemModel и реализовать
получение данных собственным путем.
Во-вторых, вам может пригодиться класс QProxyModel. Эта модель не хранит
данные и даже не получает их из внешних, по отношению к программе, источни-
ков. Вместо этого она получает данные из другой модели, устанавливаемой мето-
дом QProxyModel: : setModel () и возвращаемой методом model (). Основная
задача класса QProxyModel — фильтрация и сортировка получаемых данных перед
их отображением в представлении. Вы также, если все сделаете правильно, сможе-
те воспользоваться этим сервисом.
Наконец, иногда вам придется сталкиваться с обработкой нестандартных дан-
ных или, напротив, стандартных, но нестандартным образом. Например, вы може-
те захотеть для ввода целых чисел в таблице использовать экземпляры класса
QSlider (рис. 4.8).
В таком случае вы должны заинтересоваться классами-делегатами, происходя-
щими от QAbstractltemDelegate. Эти небольшие объекты как раз и отвечают за
представление информации (предоставляя методы paint () и sizeHint () для за-
данного типа данных), а также за создание объектов редактирования по требова-
нию и двусторонний обмен данными “модель-редактор”. Вы можете как полностью
переписать свой класс, отталкиваясь от класса QAbstractltemDelegate, так и
(в случае стандартных данных) взять за основу класс QltemDelegate.
Рис, 4,8, Пример встраивания
нестандартного элемента
управления в табличное
представление
Теперь вы знаете, что собой предоставляет технология “модель-представление”,
а всю остальную информацию можно найти в разделе документации “Model/View
Programming” и в описаниях соответствующих классов.
Глава 5
Двухмерная и трехмерная графика
5.1. Модуль QCanvas: 2В-графика
Внимание! Версия Qt 4.0 относительно встроенной двухмерной графики объяв-
лена “переходной”. Все перечисленные ниже классы уже признаны “снятыми
с производства”, но на смену им еще не пришли новые, которые обещают появиться
в Qt 4.1. Мы будем говорить об этих классах и называть их так, как они изначально
представлены в версии Qt 3, однако имейте в виду, что сейчас они перемещены
в библиотеку совместимости и их имена помечены префиксом Q3, например
Q3Canvas. В следующем издании книги эта глава будет отражать актуальные
данные.
Итак, за двухмерную графику все еще отвечает модуль Canvas, входящий в бес-
платный (Free) и ознакомительный (Evaluation) дистрибутивы. Основным объек-
том рисования является собственно “поверхность рисования”, представленная
классом QCanvas, которая является скорее “виртуальным экраном” и совсем не
обязательно отображается на экране. С одной поверхностью обычно связано одно
или несколько представлений, реализованных классом QCanvasView. Каждое из
представлений отображает независимую область поверхности рисования в произ-
вольных масштабе и ориентации, задаваемых матрицей преобразования, которые
рассмотрим ниже.
В качестве примитивов рисования используются объекты рисования. Базовым
абстрактным классом для таких элементов является класс QCanvasltem. Его по-
томки — QCanvasEllipse, QCanvasLine, QCanvasPolygon, QCanvasRectangle,
QCanvasSprite, QCanvasSpline и QCanvasText — представляют все богатство
изобразительных возможностей Qt. Каждый элемент на поверхности рисования
представляет отдельную сущность и может быть свободно перемещен программно
или в результате действий пользователя (фактически — тоже программно). На ка-
ждом из классов мы остановимся отдельно.
Для реализации двухмерной анимации предназначен класс QCanvasSprite, ис-
пользующий в своей работе классы QCanvasPixmap и QCanvasPixmapArray. Для
создания плавной анимации и выполнения спецэффектов применяется двойная
буферизация, и это мы тоже рассмотрим ниже.
Вы можете создавать также собственные примитивы рисования, наследуя их от
класса QCanvasItem или, что более характерно, от специального производного
класса QCanvas Polygonal Item. Последний представляет собой t произвольный
многоугольник и позволяет эффективно реализовать векторную графику наподобие
той, что используется в Macromedia Flash.
5.1.1. Работа с поверхностями рисования типа QCanvas
Предварительное замечание: в Qt для представления двухмерной прямоуголь-
ной области рисования используется канонический термин “полотно” (canvas),
также используемый, например, в Delphi. В настоящее время (в частности —
в DirectX) чаще используется термин “поверхность рисования” (surface), или
“плоскость рисования” (drawing plane), так что мы будем использовать эти терми-
ны взаимозаменяемо. В любом случае по отношению к двухмерной графике это од-
но и то же.
Процесс рисования начинается с создания экземпляра класса QCanvas. Сущест-
вует три перегруженных конструктора этого класса.
QCanvas(QObject *parent = 0,const char *name = 0)
QCanvas(int w,int h)
QCanvas(QPixmap p,int h,int v,int tilewidth,int tileheight)
Первый конструктор позволяет создать экземпляр без указания размеров, но
снабжается указателем на родительский объект и именем создаваемой поверхно-
сти. Этот вариант предназначен для создания элементов управления, которые впо-
следствии будут полностью контролировать поведение поверхности рисования.
В частности, в какой-то момент времени для задания реальных размеров родитель-
ский контейнер должен вызвать метод QCanvas: : resize ().
Второй конструктор предназначен для создания поверхности заданного разме-
ра, а третий требует задания пяти параметров. С помощью первого параметра ука-
зывается битовая карта, из которой вырезается “плитка”, служащая для заполне-
ния фона. Вторые два целочисленных параметра означают количество плиток по
горизонтали и вертикали, а последние два — ширину и высоту вырезаемой плитки.
Вначале размер поверхности кратен целым размерам плиток, как указано в конст-
рукторе. Если пЬсле создания поверхность будет растянута или сжата, то плитка
будет автоматически заполнять весь фон, дополняя недостающие области нужным
узором.
Те же возможности автоматического покрытия фона “плиткой” доступны и для
поверхностей, изначально созданных без этой опции, т.е. первыми двумя конст-
рукторами. Для заполнения плиткой уже существующего полотна предназначен
метод QCanvas: : setTiles, воспринимающий те же пять параметров, что и конст-
руктор. Если передать нулевые параметры, то режим заливки будет отменен. Если
вы хотите выбрать другую (нулевую) плитку из набора, т.е. вырезать другой фраг-
мент, кроме того что расположен в верхнем левом углу, используйте метод
setTile (int х, int у, int tilenum). Плитки в наборе нумеруются слева направо,
затем — в следующем ряду и т.д. Получить текущую плитку в заданной позиции
(не путать с координатами) можно с помощью метода QCanvas: : tile (int х, int у).
С плитками связано еще несколько методов: tilesHorizontally() и
tilesVertically () позволяют исследовать количество плиток на данной плос-
кости рисования (безотносительно к тому, сколько из них реально отображается
в представлении) по горизонтали и вертикали соответственно. Методы
QCanvas :: tileHeight () и tileWidthO возвращают размеры самих плиток.
Хорошее приложение, позволяющее пользователю заниматься настройкой графи-
ческого фона, должно анализировать эти параметры при установке всех или от-
дельных фоновых фрагментов. При желании техника “плиток” может быть
использована для получения интересных эффектов, в частности при программиро-
вании настольных игр.
Мы несколько отвлеклись от самой поверхности рисования, для которой тоже
характерны размеры. Ширину и высоту поверхности можно получить с помощью
методов QCanvas: : width () и QCanvas : : height (). Метод QCanvas: : size () воз-
вращает обе эти величины в виде объекта класса QSize. Те же значения, но в виде
объекта типа QRect, возвращает метод QCanvas: : rect ().
Четыре метода проверяют, принадлежит ли точка данному полотну. Фактиче-
ски это две пары перегруженных методов: QCanvas: : onCanvas () воспринимает
пару координат или объект типа QPoint для проверки, лежит ли точка на поверх-
ности рисования. Аналогичную операцию выполняют и два перегруженных метода
QCanvas::validChunk().
Существует также возможность динамически изменять размер полотна с помо-
щью метода QCanvas: : resize (), однако это вызывает массовое копирование би-
товых данных для каждого из элементов изображения, что в общем случае — срав-
нительно длительная операция, и вы не должны ею злоупотреблять. Не забывайте,
отображением поверхности рисования занимается класс представления, так что,
возможно, вы просто захотите создать или модифицировать представление вашей
графической композиции, а не создавать копию полотна с новыми размерами.
После изменения размера полотно генерирует сигнал QCanvas: :resized(). Все
представления, связанные с данной плоскостью рисования, должны отрабатывать
это событие и обновлять визуальный ряд.
Как уже было сказано, для создания фона можно использовать “плитку”, т.е.
небольшой повторяемый узор, который будет автоматически повторяться по всему
фону, в том числе при изменении размеров изображения. Две другие возможно-
сти — закрасить фон сплошным цветом или вывести на него изображение. Фоно-
вый цвет задается методом QCanvas : : setBackgroundColor (), а фоновый рису-
нок — похожим методом QCanvas: : setBackgroundPixmap (). Фоновый рисунок,
как и плитка, будет заполнять весь фон по мере необходимости, однако для него не
работают функции размера плитки, выбора одной плитки из набора или замены от-
дельной плитки по ее координатам. Два метода, QCanvas : : background?ixmap () и
QCanvas: :backgroundcolor (), возвращают соответствующие их названиям зна-
чения. Если вы задаете цвет фона, то сбрасывается рисунок фона, и наоборот.
Рассмотренные до сих пор вопросы были тривиальными. Теперь пришло время
для более сложных моментов. При работе с графикой для реентерабельности1 кода
почти всегда применяются блокировки наподобие тех, которые используются в
СУБД. В базе данных вы можете заблокировать всю базу, одну таблицу или одну
запись. Аналогичные градации есть и в графике: здесь можно заблокировать всю
плоскость, одну или несколько строк либо можно оперировать другими примити-
вами, обычно — прямоугольными областями (в последнее время найдены эффек-
тивные алгоритмы блокировки треугольников). Кроме блокировки, фрагменты
разбиения служат областями отсечения, т.е. на основании так называемых
“описывающих (hint) габаритов” вы (или такой механизм рисования, как OpenGL)
можете эффективно ограничить количество примитивных областей, которые будут
модифицированы. В результате область отрисовки сокращается до конечного спи-
ска областей.
В терминах Qt поверхность рисования разбивается на квадратные фрагменты
(chunks). Размер этих фрагментов в значительной мере влияет на производитель-
ность алгоритмов оптимизации. Точнее, не сам размер, а его соотношение с харак-
терными размерами размещенных на плоскости графических примитивов. Если
фрагмент значительно меньше, то каждый объект при модификации будет порож-
дать большие списки модифицируемых фрагментов. Для больших фрагментов в
каждом из них будет размещаться большое количество объектов, так что модифи-
кация одного из них будет вызывать перерисовку и всех остальных. Таким обра-
зом, желательно выбирать размер фрагмента приблизительно равным размеру
характерных для сцены объектов. Первоначально для поверхности 1024x1024
устанавливается размер фрагментов 16x16 пикселей, т.е. сама матрица фрагментов
составляет 64x64 фрагментов. Если вы заполняете фон “плиткой”, то размер фраг-
мента будет установлен равным характерному размеру одной плитки.
Получить размер фрагмента для данной поверхности рисования можно с помо-
щью метода QCanvas: : chunksize (). Настроить размер фрагментов, а также ука-
зать количество одновременно обновляемых областей можно с помощью метода
QCanvas::retune(int chunksze,int mxclusters = 100). Все зависит от ха-
рактера изображения: если вы оперируете большим и, в основном, статическим
изображением, например картой, и хотите более эффективно работать с фрагмен-
тами, то можете оптимизировать отрисовку, установив больший размер фрагмента,
например 32 или 64. С другой стороны, если вы рисуете большое количество ма-
леньких, но изменчивых или подвижных объектов (например, объектов звездного
неба), то для получения более качественных результатов можно увеличить пара-
метр mxclusters.
Итак, в некоторый момент определенные объекты изменяют свое состояние или
(чаще всего) положение на плоскости рисования. Это порождает определенные со-
бытия, что связано с рядом методов. Во-первых, слот QCanvas: : update () приво-
дит к немедленной перерисовке всех областей (фрагментов), которых коснулись
изменения, т.е. тех, которые пересекались или стали пересекаться с активными
1 Реентерабельность (от англ, “reentrance”) — свойство программы, позволяющее повторный вход
в нее. — Примеч.ред.
графичёскими объектами. Перерисовка автоматически касается всех представле-
ний, связанных с данной плоскостью рисования.
Метод QCanvas: :setAllChanged() делает недействительными все фрагмен-
ты изображения, так что они будут все перерисованы. Это бывает полезно,
если вы уверены, что все изображение “пострадало”, например, в результате пе-
рехода к новой сцене анимации. Менее деструктивно выглядит метод
QCanvas: : setChanged(const QRect &area) — он объявляет недействительной
только заданную область. На самом деле перерисовке в этом случае подлежат все
фрагменты, попавшие в “зону недействительности”. Комплиментарная функция,
QCanvas: :setUnchanged(), делает заданную область “неизменной”, т.е. она не
будет перерисована при следующем вызове метода update (). Эффективно это реа-
лизуется в виде команды “сбросить флаг обновления для всех фрагментов, полно-
стью попадающих в область”. Впрочем, последующий код может снова сделать эти
фрагменты обновляемыми.
Существует две возможности асинхронного рисования: во-первых, вы можете
установить промежутки времени для автоматического обновления представлений.
Метод QCanvas: : setUpdatePeriod (int ms) как раз и служит для задания такого
интервала в миллисекундах. Таким образом, вы можете вообще явно не вызывать
метод update (). Это особенно полезно, если вы “гарантируете” регулярные изме-
нения сцены и/или хотите абстрагироваться от обновлений экрана во многих пред-
ставлениях. Во-вторых, можно установить скорость автоматической анимации с
помощью метода QCanvas: : setAdvancePeriod ().
Если уж говорить об анимации, то следует отметить, что некоторые из объектов
типа QCanvasItem являются анимированными. Проверить это можно, исследовав
логическое значение, возвращаемое методом QCanvasItem: : animated(). Для та-
ких объектов вызывается метод QCanvasItem: :advice(0), т.е. с параметром О
(нуль). Это так называемая первая фаза анимации. На этом этапе гарантируется,
что другие объекты еще не изменили своего первоначального положения, так что
можно проверить пересечения объектов (например, снаряда и цели), в том числе и
будущие пересечения, поэтому визуально это не будет выглядеть как “навал” одно-
го объекта на другой. На втором этапе этот же метод будет вызван с параметром 1
(один). В этой, второй, фазе объекты сцены должны изменить свое положение или
состояние безотносительно к другим объектам, положение которых считается не-
детерминированным. На самом деле из-за возможной оптимизаций обработки не-
возможно (точнее — не рекомендуется) предсказать порядок обработки объектов.
На первом этапе ни один из стандартных графических примитивов не выполняет
никакой значимой обработки, но это связано только с их индифферентным поведе-
нием относительно друг друга, а ваши объекты могут вести себя более активно.
Запустить цикл анимации для сцены можно автоматически через определенные
промежутки времени, как показано выше, с помощью метода QCanvas: :
setAdvancePeriod () или вручную, вызывая метод QCanvas: : advance ().
Последнее, о чем мы поговорим в связи с поверхностями рисования, — это ис-
следование столкновений, т.е. пересечений двух объектов на плоскости в так назы-
ваемых коллизиях. Для начала отметим, что список всех дочерних объектов можно
получить, опросив метод QCanvasItemList QCanvas: :allltems(). Существует
целых три перегруженных метода, проверяющих условия пересечения.
QCanvas::collisions(const QPoint &p)const
QCanvas::collisions(const QRect &r)const
QCanvas::collisions(const QPointArray &chunklist,
const QCanvasItem *item,bool exact)const
Каждый из этих методов возвращает список объектов типа QCanvasItembist,
пересекающихся с данной точкой, прямоугольником или списком фрагментов.
Список будет упорядочен по z-глубине: вначале будут находиться объекты передне-
го плана. Последний синтаксис можно прочитать так: “найти объекты, пересекаю-
щие список фрагментов, кроме указанного вторым параметром (предполагается,
что он там находится, как само собой разумеющееся)”. Это, так сказать, метод
“упрощенного столкновения”, который может применяться для оптимизации или
для того, чтобы “защитать попадание при приблизительном попадании”.
Заслуживают некоторого внимания следующие два метода: QCanva,s: :
setDoubleBuff ering () и QCanvas: : drawArea (). Первый устанавливает режим
двойной буферизации, что для экрана действует по умолчанию. Второй позволяет
вывести вашу плоскость на любое устройство вывода, представленное экземпляром
класса QPainter, например на принтер.
5.1.2. Представления типа QCanvasView и дополнительные
классы
Представление — это вторая часть парадигмы “документ-представление” (docu-
ment-view). Как уже было сказано, сами поверхности рисования (графические до-
кументы) не отображаются на экране. Вместо них на экране может отображаться
один или несколько специальных элементов управления, так называемых пред-
ставлений, связанных с данной поверхностью рисования. Каждое представление
может быть связано с любым фрагментом нашего изображения, а также отмасшта-
бировано, перемещено, сдвинуто или еще как-то более сложно преобразовано в
терминах линейных преобразований на плоскости. В качестве практического при-
менения такой системы взаимоотношений образа и представления можно предло-
жить рисование фрагментов больших векторных изображений, например карт ме-
стности, с учетом масштабирования, перемещения по плану местности и т.д.
На самом деле класс QCanvasView — один из самых простых, но, тем не менее,
центральных классов, задействованных в отображении двухмерной графики. Этот
класс происходит от класса QScrollView, поэтому он наследует все свойства эле-
мента управления с возможностью прокрутки по вертикали и горизонтали. Суще-
ствует два перегруженных конструктора этого класса.
QCanvasView(QWidget *parent=0,const char *name=0,WFlags f=0)
QCanvasView(QCanvas *canvas, QWidget *parent=0,
const char *name=0, WFlags f=0)
Оба получают в качестве параметров родительский элемент управления
(контейнер), собственное имя и флаги. Второй вариант конструктора отличается от
первого заданием поверхности рисования, к которой изначально будет “приписан”
данный объект отображения. В противном случае, если это не задано при создании
экземпляра, вы должны определиться с отображаемой поверхностью рисования,
вызвав метод QCanvasView: : setCanvas (). Получить отображаемую поверхность
можно, опросив метод QCanvasView: : canvas ().
Теперь о самом интересном: о матрицах преобразования мира. С каждым экзем-
пляром класса QCanvasView связана матрица отображения, которая занимается
преобразованием координат при выводе изображения, в том числе — векторной и
растровой графики, в данном отображении. На самом деле матрица отображения
может быть не задана — в таком случае производительность такого отображения
будет значительно^выше. Матрица должна быть обратимой, т.е. если первоначаль-
но она служила для увеличения объектов в два раза, то после обращения (инверсии)
она должна соответственно уменьшать объект в два раза.
С матрицами преобразования связаны три метода: QCanvasView:: setWorldMatrix (),
QCanvasView: :worldMatrix () и QCanvasView: : inverseWorldMatrix (). Эти
методы соответственно задают, опрашивают и инвертируют матрицу преобразова-
ния. В качестве параметра или результата этих функций служит экземпляр класса
QWMatrix. На этом классе мы остановимся более подробно.
Класс QWMatrix определяет произвольную трансформацию в двухмерном про-
странстве. Как известно, для этого достаточно задать шесть параметров, которые
занимают такие позиции в результирующей матрице размерностью 3x3:
mil ml2 О
m21 m2 2 О
dx dy 1
При этом новые координаты соотносятся с первоначальными в соответствии со
следующими уравнениями:
х' = mll*x + m21*y + dx
у' = ш22*у + ml2*x + dy
При создании матрицы можно сразу в конструкторе задать все коэффициенты
или же, напротив, вызвать конструктор без параметров, а позже обратиться к ме-
тоду QWMatrix: : setMatrix (). Параметры dx и dy — линейные смещения относи-
тельно прежнего положения на плоскости. Координаты mil и m22 задают фактор
деформации (warp factor), т.е. коэффициент растяжения/сжатия (определяющий
зависимость координаты от себя, но с некоторым коэффициентом). Наконец, па-
раметры ш21 и т12 задают сдвиги относительно различных координат, т.е. зави-
симость одной координаты от другой. На практике существует еще и такое пре-
образование, как поворот относительно произвольной точки (обычно считается
относительно начала координат, в сочетании с перемещением), но при ближай-
шем рассмотрении это не больше, чем суперпозиция указанных трансформаций.
Все коэффициенты могут быть получены с помощью вызова одноименных методов,
например QWMatrix: :mll () и т.д.
По умолчанию при создании матрицы преобразования без параметров она имеет
диагональный вид из одних единиц, т.е. отображает любую точку в самое себя.
В любой момент времени вы можете вернуть матрицу преобразования в это состоя-
ние, вызвав метод QWMatrix: : reset (), а проверить, является ли матрица триви-
альной, можно с помощью метода QWMatrix: : isldentity ().
К счастью, для выполнения операций с матрицей вам совершенно не нужно де-
лать вычисления всех коэффициентов (особенно трудоемкие при повороте) в своей
программе. Более того, задавая коэффициенты вручную, вы можете “испортить”
матрицу так, что она станет необратимой (сингулярной). Также, как и в библиоте-
ке OpenGL, вы можете и должны оперировать содержательными методами, в част-
ности такими, как QWMatrix; : translate (), QWMatrix: : scale (), QWMatrix::
shear () и QWMatrix: :rotate() — для перемещения, масштабирования, дефор-
мации сдвига и поворота соответственно (рис. 5.1).
Рис. 5.1. Масштабирование, смещение и поворот — эти операции легко
реализуются механизмами векторной графики, поскольку объекты
хранятся в векторном виде
Немного остановимся на инвертировании матриц. Для проверки, является ли
матрица обратимой или сингулярной (необратимой), можно использовать метод
QWMatrix: : Invertable (), который проверяет инвертируемость матрицы. Собст-
венно обращение матрицы можно произвести, вызвав метод QWMatrix: : invert ().
Для сингулярных матриц, для которых обращение невозможно, будет возвращена
матрица тривиального преобразования.
Рассмотрев основные характеристики матриц преобразования, также обратимся
к некоторым действиям, которые можно “заказать” у этих матриц. Эти действия
представлены целыми восемью методами с префиксом шар*. Три из них совершен-
но очевидны: принимают точку на входе и выдают новую точку на выходе. Единст-
венное различие состоит в формате представления точки: парой целых координат,
парой вещественных координат или отдельным объектом типа QPoint. Четвертый
метод принимает список точек, с каждой из которых он делает то же, что и описан-
ные выше методы с каждой точкой в отдельности.
За право преобразовывать прямоугольники “спорят” два метода. Старый (и, бо-
лее того, устаревший) называется QWMatrix: :map (const QRect &r). Этот метод
работает неверно, “забывая”, что сдвиг не всегда приводит к новому прямоуголь-
нику. Вы никогда не должны обращаться к этому методу. Новый метод,
QWMatrix: :mapRect (const QRect &r), возвращает описывающий прямоуголь-
ник для преобразований сдвига и/или поворота. Для того чтобы прямоугольник
после любых трансформаций мог все-таки быть правильно представлен хотя бы как
многоугольник (значительно более трудоемкая фигура), используется метод
WMatrix: :mapToPolygon (const QRect &rect). Похожий на предыдущий, метод
WMatrix: :mapToRegion (const QRect &rect) возвращает результат в виде ре-
гиона — прямоугольники и регионы ведут себя несколько различно из-за способов
округления координат. Есть также метод, который отображает одни регионы в
другие (о различии прямоугольников и регионов вы должны уже знать из раздела,
посвященного элементам управления).
Наконец, остановимся на режимах преобразования. Режимы являются доволь-
но полезной подсказкой при принятии решений в спорных ситуациях. Фактически
есть только два режима (точек и областей), задаваемые константами перечислимо-
го типа QWMatrix: : Transf ormationMode, dots и area соответственно.
Режим точек будет оперировать отдельными вершинами и в результате ок-
руглять целочисленные координаты, используемые при вычислениях в целях
эффективности, “внутрь прямоугольников”, т.е. любые границы областей скорее
будут преобразованы в промежутки между объектами. В режиме областей любые
координаты будут рассматриваться как “ширина и высота”, в результате чего
прямоугольники будут аппроксимированы “наружу”, от начала координат,
причем так, что первоначально соприкасающиеся области будут соприкасаться
и в дальнейшем. Как следствие таких различий, преобразования не обладают
коммутативностью: преобразование группы областей или точек не обязательно
даст тот же результат, что и преобразование каждого объекта в отдельности.
Установить и опросить режим трансформации можно с помощью методов
QWMatrix: : setTransformationMode () и QWmatrix: : transformationMode ()
соответственно.
К дополнительным, не рассматриваемым здесь методам относятся: вычисление
детерминанты матрицы преобразования (метод QWMatrix: :det()), а также опи-
санные в документации операции записи и чтения матрицы преобразования в по-
ток ввода-вывода и из него. Вам достаточно знать следующее: матрицу преобразо-
ваний можно вывести или прочитать в поток типа QDataStream или из него, для
чего существуют соответствующие перегруженные операторы “«” и “»”.
5.2. Класс QCanvasItem: абстрактный примитив
рисования
Теперь перейдем к фигурам, называемым графическими примитивами, кото-
рые, собственно, и размещаются на плоскости рисования. Для начала рассмотрим
прародителя всех графических объектов, класс QCanvasItem. Здесь, как и обычно
в Qt, используется значительная “проработка” суперкласса, с тем чтобы последую-
щее кодирование потомков было в значительной мере простой операцией.
Итак, суперклассом для всех графических примитивов является QCanvasItem,
т.е. некий “элемент холста”. От этого класса происходят три потомка:
QCanvasSprite, QCanvas Polygonal Item и QCanvasText. Наконец, уже эти клас-
сы и их потомки реально отображаются на экране.
Для начала, как всегда, поговорим о конструкторах. Фактически, конструктор
у графического примитива всего один и с одним параметром — той поверхностью
рисования, на которой будет отображаться примитив. Вообще-то, есть такая (не со-
всем логически корректная) операция, как “перепрыгивание” примитива на дру-
гую плоскость рисования. Для этого служит метод QCanvasItem: : setCanvas ().
Он может пригодиться при перетаскивании картинок с одного изображения на дру-
гое, например при переносе пиктограмм или фигур в настольной игре за рамки иг-
рового поля (рис. 5.2). В любом случае вы можете запросить, к какой плоскости
“прописан” данный примитив, с помощью метода QCanvasitem: :canvas (). Как
видите, один объект может принадлежать только одной плоскости рисования. Если
вам нужно клонировать объект, то это необходимо делать явно.
Рис, 5.2. У технологии “плавающих” спрайтов
неистощимый потенциал, в частности, для
реализации настольных игр
Теперь несколько комментариев относительно координат отдельно взятого объекта
рисования. Имеется три координаты — несмотря на то что рисование происходит в
двухмерном пространстве. Третья координата, z, является параметром “глубины”.
Большие значения этой координаты соответствуют более “верхним слоям”, т.е.
большие значения координаты z приводят к появлению объекта на переднем пла-
не. Все перечисленные координаты являются вещественными числами, и их значе-
ния возвращаются методами QCanvasitem: :x/y/z() соответственно. Также эти
координаты можно установить соответствующими set-методами.
Если ваша цель— просто “подвигать” объект (не меняя его “слоя”) по оси z,
примените метод QCanvasItem: :move (double х, double у), который волшеб-
ным образом перемещает объект в новую позицию. Для перемещения относительно
текущей позиции служит метод QCanvasItem: :moveBy (double dx, double dy).
Несколько общих замечаний о размещении и перемещении графических объек-
тов. Обычно пользователь не ожидает, что объект будет “перескакивать” между
слоями, т.е. изменять z-координату, кроме особых случаев, например при исполь-
зовании команды “на передний план” (to front). Также не совсем приемлемо вы-
глядят и неожиданные “прыжки” объекта из одной позиции в другую. Для этого
лучше использовать плавную анимацию, о которой будет рассказано отдельно.
Наконец, нужно иметь в виду и тот факт, что большинство классов-потомков зада-
ют точку привязки, отличную от левого верхнего угла. Вы должны отслеживать
этот параметр, чтобы корректно располагать объекты на плоскости рисования, а не
за ее пределами.
Теперь обратимся к такой удобной, если не уникальной, возможности, как
автоматическая анимация. Для начала нужно определиться, что далеко не все,
а точнее очень немногие, объекты являются анимациями. Для того чтобы опреде-
лить данный объект как анимированный, следует вызвать для него метод
QCanvasItem: : set Animated (TRUE). После этого метод QCanvasItem: : animated ()
будет возвращать значение ИСТИНА. И когда вся плоскость получит команду
QCanvas: : advance (), то для каждого анимированного элемента будет вызвана
команда QCanvasItem: : advance (). В качестве параметра этого метода указывает-
ся первая или вторая фаза анимации, как это описано выше.
Обычным типом анимации является перемещение объекта в двухмерной плос-
кости. Как мы только что обсудили, сигналом для перемещения является вызов
метода QCanvasItem: : advance О , который косвенно задает частоту кадров. Для
задания скорости перемещения объекта служат три метода. Метод QCanvasItem: :
setvelocity (double vxz double vy) задает смещения по обеим координатам од-
новременно. Можно задать скорости отдельно, с помощью методов QCanvasItem: :
setXVelocity () и QCanvasItem: : setYVelocity О . Естественно, вы всегда мо-
жете узнать скорость перемещения, вызвав методы QCanvasItem: :xVelocity () и
QCanvasItem::yVelocity().
Переходим к взаимодействию объектов. Под взаимодействием мы подразумева-
ем только одно — пересечение объектов на плоскости. Общим методом этого типа
является QCanvasItemList QCanvasItem::collisions(bool exact) const.
Как видно, этот метод возвращает целый список объектов, с которым и пересекает-
ся данный примитив. Параметр указывает, насколько точно нужно сравнивать пе-
ресечения двух графических областей. В случае, если сравнения выполняются не-
точно, вы можете получить несколько объектов, которые лежат достаточно близко.
Уточнить, пересекается ли данный объект с конкретным другим объектом, причем
в точном значении, можно, вызвав метод QCanvasItem: :collidesWith (const
QCanvasItem *other).
С пересечениями связано несколько нюансов. Во-первых, если вы хотите обра-
батывать объект, с которым в данный момент пересекается ваш объект, то вам
понадобится исследовать его тип. Тип “объект плоскости рисования” для содержа-
тельного “общения” с ним говорит слишком мало. Для корректной обработки этой
ситуации предусмотрена информация, получаемая во время выполнения програм-
мы (RTTI — run time type information), об отображаемых фигурах. В этом случае
используется тип перечисления QCanvasItem: :RttiValues, определяющий кон-
станты {Rtti_Item=O, Rtti__Sprite=l, Rtti_PolygonalItem=2, Rtti_Text=3,
Rtti_Polygon=4z Rtti_Rectangle=5z Rtti_Ellipse=6z Rtti_Line=7z Rtti_
Spline=8}. Для определения типа отображаемого графического примитива слу-
жит метод QCanvasItem: : rtti () — вы должны опросить этот метод и явно при-
вести объект к нужному типу, после чего в конструкциях if или switch обрабаты-
вать данный экземпляр свойственными ему методами.
Во-вторых, для анимированных объектов их координаты или описывающий
многоугольник (см. дальше) вычисляются на момент достижения положения, сле-
дующего за текущим. Таким образом вам не придется рисовать бильярдные шары,
“втиснутые” в края бильярдного стрла только для того, чтобы определить момент
их пересечения (хотя в реальной жизни именно “втискивание”, пусть и незаметное
на глаз, вызывает к жизни физику упругих столкновений).
В заключение я приведу список тривиальных методов, относящихся к классу
QCanvasItem, которые не заслужили более пристального внимания. Методы draw (),
show(), hide(), setvisible(), isVisibleO, setSelected(), isSelected(),
setEnabled(), isEnabledO, setActiveO, isActiveO делают именно то, что
вы от них и ожидаете, т.е. делают объект видимым/невидимым, активным/
неактивным и так далее, комплиментарные методы возвращают нужные значения.
Методы QCanvasItem: :boundingRect () и QCanvasItem::boundingRectAdvanced()
возвращают описывающие прямоугольники, последний — в том случае, если для
объекта будет применена еще один раз операция автоматической анимации. Этот
метод используется для вычисления “будущего, аккуратного, пересечения”.
5.2.1. Предопределенные графические примитивы
Наконец, рассмотрим те предварительно заданные графические объекты, эк-
земпляры которых вы можете реально создать в вашем приложении. Рассмотрим
их в порядке, в котором они описаны в документации, — в принципе, невозможно
выделить более и менее значимые примитивы, так что любой порядок будет право-
мерным.
В качестве общего знаменателя все эти классы одним из параметров конструк-
тора, обычно последним (или, возможно, единственным параметром), принима-
ют плоскость рисования QCanvas. В этом и состоит отличие от некоторых других
систем рисования, где сама плоскость реализует создание объектов или, хуже то-
го, просто рисует объекты в растр, не создавая отдельных объектов. Правила,
применяемые в Qt, создают прочный фундамент для реализации любого уровня
векторной графики, вплоть до профессиональной — объекты векторной или рас-
тровой графики существуют сами по себе, могут быть закреплены или не закреп-
лены за одной из плоскостей рисования, а также перемещаться между такими
плоскостями, хотя последнее и не является самой интуитивно понятной операцией
для пользователя.
Начнем с класса QCanvasEllipse. Особые свойства этого класса — ширина
и высота эллипса (описывающего его прямоугольника), а также начальный угол
и, как говорят математики, раствор угла относительно нулевого положения (в тер-
минах часовой стрелки соответствует трем часам). Все многообразие возможных
операций представлено говорящими за себя методами: widthO, height (),
setsize(int width,int height), setAngles(int start,intlength),
anglestart (), angleLength (). Некоторые из них задают перечисленные выше
параметры, другие — возвращают их значения.
Другой, такой же несложный, класс — QCanvasLine. Этот класс располагает
всего двумя специфическими параметрами — начальной и конечной точкой. Все
допустимые операции ограничены методами setpoints (int ха, int ya, int
xb, int yb), startPoint (), endPoint (). Первый метод задает параметры прямой
(точнее — отрезка на прямой), два остальных — возвращают объекты типа QPoint
для начальной и конечной точек соответственно. Я хочу обратить ваше внимание
на тот факт, что это только вторичные признаки, поскольку эти классы уже насле-
дуют от класса QCanvasitem довольно солидную функциональность. Так что их
простота — это скорее достоинство, чем недостаток.
Аналогичная ситуация и с прямоугольником, описываемым классом
QCanvasRectangle. Кроме трех конструкторов, задающих прямоугольник без
размера, с заданным размером или на основе другого прямоугольника типа QRect,
существует только несколько собственных методов: width (), height (),
setsize(int width,int height), QSize size(), QRect rect().
Как и для всех остальных QCanvasitem-потомков, для класса QCanvasRectangle
определен метод, возвращающий тип примитива в виде числовой константы
(в данном случае метод называется QCanvasRectangle: :rtti ()); для прямо-
угольников он возвращает значение QCanvasitem: :Rtti_Rectangle, или 5 (пять)
в числовом выражении. Мы не будем останавливаться на этом каждый раз, но вы
всегда можете и должны исследовать это значение для предопределенных графиче-
ских объектов, с тем чтобы явно приводить их к нужному типу и вызывать соответ-
ственные методы.
Теперь рассмотрим более сложные объекты, а именно — полигоны. Если смот-
реть на вещи шире, то под полигонами можно понимать что угодно — от точки до
сложной проекции трехмерной каркасной сцены. Поэтому, и в силу такой универ-
сальности, был введен специальный класс QCanvasPolygonalltem, от которого,
в частности, происходят классы QCanvasRectangle, QCanvas Pol у gon, QCanvasLine
и QCanvasEllipse. Сплайны, спрайты и текст, однако, представляют собой значи-
тельно более сложные сущности.
Класс QCanvas Polygonal item определяет два важнейших параметра полигона —
границы и заливку, задаваемые методами setPen(QPen р) и setBrush(QBrush b)
соответственно. Методы реп () и brush () возвращают соответствующие значения.
Методы QPointArray areaPoints() и areaPointsAdvanced() возвращают
списки точек в данном положении полигона и с расчетом на его положение в сле-
дующий момент анимации, если объект относится к анимированным. В классах-
наследниках эти методы должны в обязательном порядке корректно возвращать
эти значения, в противном случае может быть разрушен весь графический интер-
фейс Qt. В классе QCanvasPolygonalitem метод areaPoints определен как чисто
виртуальный, т.е. он должен быть обязательно определен в потомках. Метод QRect
boundingRect () возвращает описываемый прямоугольник в формате QRect.
Различают выпуклые и невыпуклые полигоны. Выпуклым называют такой по-
лигон, любые две точки которого можно соединить отрезком, не пересекающим
границы самого полигона. Например, к выпуклым полигонам относятся прямо-
угольники и эллипсы, которые в машинной графике также аппроксимируются по-
лигонами. Понятие выпуклости полезно при определении того, как закрашивать
полигоны: выпуклые полигоны гарантированно не пересекают себя, так что можно
использовать упрощенный алгоритм определения внутренних точек. В противном
случае применяется алгоритм “чет-нечет”, т.е. отслеживается каждое пересечение
полигоном самого себя, причем направление нормали “внутрь” каждый раз меняет
свою ориентацию. Больше об этих алгоритмах вы можете узнать из книг по компь-
ютерной графике, мы же только отметим, что для полигона можно задать алгоритм
вычисления заливки с помощью метода QCanvasPolygonalitem: : setwinding ().
Если параметр принимает истинное значение, то будет применен более быстрый, но
менее надежный “габаритный” метод. По умолчанию применяется метод “чет-
нечет”, который работает медленнее, но зато корректно обрабатывает любые поли-
гоны.
/ Собственно полигоны представлены классом QCanvasPolygon. Их принципиаль-
ное отличие от эллипсов или прямоугольников — возможность создания невыпук-
лых полигонов. При этом для закрашивания фона такой фигуры нельзя пользоваться
простым “габаритным” методом, а нужно (неявно, механизмами Qt) анализировать
каждый отрезок, точки их пересечения и так далее, что является значительно более
сложной операцией. Полигон описывается массивом составляющих его точек, зада-
ваемых методом QCanvasPolygon: : setpoints (QPointArea pa), а получить список
точек можно методом QCanvasPolygon: :points (). Учтите, что точки полигона за-
даются относительно независимой, собственной системы координат, т.е. полигон
“живет своей жизнью”, независимо от поверхности. Если вы хотите получить
транслированные координаты полигона относительно поверхности рисования
(а полигон, как и все другие потомки класса QCanvasItem, воспринимает поверх-
ность в качестве обязательного параметра конструктора), то вас, скорее, должен
интересовать метод QCanvasPolygon: : areaPbints ().
От полигона как такового (обратите внимание, насколько в Qt аккуратно спла-
нирована иерархия классов) происходит еще один, довольно сложный, класс —
сплайн QCanvasSpline. Как известно, сплайн— это тонкая, предположительно
деревянная линейка, прокладывая которую между колышками архитекторы полу-
чали плавные, гармоничные изгибы. В математике физика упругого материала моде-
лируется специальными функциями, в частности функциями Безье, дающими субъ-
ективно плавные кривые, проходящие через заданные точки (точнее — проходящие
через две точки и использующие дополнительные две точки нормали, для управле-
ния “упругостью” сплайна). Сплайн также описывается массивом точек, как и
полигон, но выглядит в результате это, конечно, совершенно иначе. Хотя нужно
понимать, что компьютер не может оперировать истинными кривыми, так что про-
цесс “сплайнирования” — это замена одного, “грубого” полигона другим, с боль-
шим количеством вершин.
Сплайны типа QCanvasSpline состоят из четырехточечных кривых Безье,
соединенных в сплошную кривую, так что если сплайн замкнут, то он требует 3 *п
точек, и 3*п+1 точек, если сплайн не замкнут, где п — количество отрезков сплай-
на. Замкнутость сплайна определяется при задании списка контрольных точек, а
именно в методе QCanvasSpline::setControlPoints(QPointArray pa,bool
close=TRUE). Получить список точек и проконтролировать, замкнут ли данный
сплайн, можно методами QCanvasSpline: :controlpoints () и QCanvasSpline: :
closed (). Если количество точек задано неверно, то лишние точки будут проигно-
рированы.
В заключение нужно добавить, что далеко не любой сплайн будет красиво
смотреться, т.е. иметь плавные сопряжения. Как получить нужный результат —
вы можете узнать из любой литературы, посвященной кривым Безье.
Наконец, существует два потомка класса QCanvasitem, не являющиеся
QPolygonalItem-потомками и, таким образом, не относящиеся к векторной гра-
фике. Первый, класс QCanvasSprite, представляет собой мини-анимацию, пред-
ставленную последовательностью битовых карт, еще называемых кадрами. Кадры
задаются сразу, в конструкторе, или позже, в методе QCanvasSprite: :
setSequence(QCanvasPixmapArray *a).
Переключение между кадрами осуществляется либо последовательно, методом
QCanvasSprite: :move (), либо в произвольном порядке, методом QCanvasSprite: :
setFrame (). Сколько всего кадров в анимации, можно узнать, опросив метод
QCanvasSprite: : frameCount (). Текущий кадр, точнее его номер, можно получить,
вызвав метод QCanvasSprite: : frame (). Аналогично методы QCanvasSprite::
width () и QCanvasSprite: : height () возвращают размеры текущего кадра.
На самом деле каждый кадр может иметь различные размеры, так что размер и
положение анимации имеют смысл только для текущего кадра. Например, метод
QCanvasSprite: :boundingRect () возвращаёт описывающий прямоугольник для
текущего кадра. Для получения текущего образа или того, который будет отобра-
жен после очередного вызова метода advance (1) — Т.е. второй, действенной фазы
анимации — служат методы QCanvasSprite: : image () и QCanvasSprite: :
image Advanced (). Второй метод служит для предварительной оценки положения
и размеров нового состояния спрайта, чтобы избежать пересечения интуитивно
твердых тел, а особенно — выхода графических примитивов за “рамки приличия”,
т.е. за рамки предполагаемой неклиентской области.
Целых восемь методов заведуют определением рамок изображения. Методы
left Edge (), right Edge (), topEdge () и bottomEdge () возвращают координаты
левой, правой, верхней и нижней границ анимации соответственно. С учетом ани-
мации эти координаты могут изменяться с течением времени. Аналогичные методы с
целочисленным аргументом возвращают те же сведения о кадре в случае, если он будет
перемещен в положение, указанное координатой. Например, вызов leftEdge(100)
означает: “определить левую границу спрайта, если х-координата его точки при-
вязки будет равна 100”. Аналогично работают и остальные три метода. Не упускай-
те из виду, что точка привязки (hot spot) любого объекта, в том числе и спрайта, не
всегда совпадает с левым верхним углом.
Довольно полезной является возможность задать два типа анимации: метод
QCanvasSprite: :setFrameAnimation() устанавливает своим первым парамет-
ром либо QCanvasSprite: :Cycle, что приводит к повторению кадров (начиная
с первого) после того, как анимация достигнет конца списка кадров, либо
QCanvasSprite: :Oscillate— после достижения конца фильма кадры начнут
воспроизводиться в обратном порядке (такое поведение также часто бывает вос-
требованным). Типы анимации заданы константами перечислимого типа
QCanvasSprite: :FrameAnimationType.
Остальные методы и свойства класса QCanvasSprite не представляют для нас
интереса, и единственным нерассмотренным классом остается QCanvasText.
По сути, текст является, возможно, самым сложным и таинственным компонентом
графического интерфейса. Если учесть, что бывают как растровые, так и векторные
шрифты, которые, к тому же, отрисовываются в зависимости от кегля (единица
измерения шрифтов в полиграфии, 1/72 дюйма), так называемый “хинтинг”, т.е.
добавление дополнительных векторов в малых размерах для более четкой прори-
совки, а также то, что в описаниях шрифтов часто участвуют дуги, т.е. вычисляемые
во время растрирования кривые, то в результате получается довольно обширная
наука о рисовании глифов, т.е. начертаний отдельных символов. В строке глифы
также сочетаются по особым правилам: расстояние между символами зависит от
самих символов.
Также довольно таинственным процессом является выбор шрифта в случае,
если не удается найти на 100% такой же, как указан в спецификации. Непростой
задачей является и масштабирование шрифтов — всегда существует дилемма вы-
числения новых размеров либо использования ближайшего “готового” размера.
Разработка латинских и греческих (в том числе — кириллических) шрифтов, пра-
вила их создания — это тайна за семью печатями, семейный бизнес, сосредоточен-
ный в руках трех компаний, уже не говоря о секретах индуистских, китайских и
арабских каллиграфов — у любителей мало шансов создать что-то стоящее.
Мы не будем вдаваться в такие тонкости, только скажем, что класс
QCanvasText имеет методы setText, setFont, setTextFlags и setColor для ус-
тановки свойств рисуемого текста и аналогичные методы, без префикса “set”, для
возвращения этих же свойств. Передаваемые и возвращаемые значения типов
QString, QFont и QColor вы должны были уже встретить где-то раньше в этой
книге — если читаете ее от начала. Флаги, означающие наклонное или жирное на-
чертание, упакованы в целое значение побитовым И+ИЛИ, но это тоже не тема для
особого внимания — вы очень долго можете создавать графические приложения без
этих “красивых” эффектов.
На этом мы заканчиваем обзор заранее предопределенных графических прими-
тивов. Все, что нужно для их практического использования, вы можете обнару-
жить в примере canvas.
5.3. Модуль OpenGL и трехмерная графика
Мы только частично затронем тему библиотеки OpenGL, поскольку это слишком
обширное поле для деятельности. Следует понимать, что OpenGL (как ц, скажем,
сокеты, файловая система или базы данных) относится к системным сервисам, не
имеющим отношения к Qt как к таковому.
Классы Qt осуществляют только доступ к этим сервисам с помощью известных
протоколов, подобно тому, как это делает другая известная библиотека, GLUT
(OpenGL Utility Toolkit). Будем считать, что система OpenGL (доступная для Linux в
нескольких вариациях, как в оригинальной от SiliconGraphics, так и в форме Open
Source проекта “mesa”) уже установлена на вашей системе в такой мере, что вы мо-
жете запускать тестовые приложения и компилировать примеры из комплекта по-
ставки. Также предполагается, что вы настроили производительность OpenGL-
подсистемы, задействовав специальные драйвера видеоадаптера, такие как драйвер
nVidia или Open Source-драйвер DRI, поддерживающий множество акселлераторов
начального и среднего уровня, в том числе и ATI Radeon до 9200 включительно.
Поскольку OpenGL выходит за рамки моих сегодняшних интересов (скорее, ме-
ня “тревожит” распознавание образов), я приношу извинения всем OpenGL-rypy за
возможные неточности в терминологии: единственный мой опыт в трехмерной
графике заключается в запуске тестовых примеров Qt. Могу только засвидетельст-
вовать, что они работают. Тем не менее основные принципы работы с трехмерной
графикой не значительно отличаются от работы с графикой вообще, за исключени-
ем того, что OpenGL-подсистема построена по клиент/серверной технологии.
Конечным результатом вычислительной обработки трехмерной сцены в OpenGL
(как, впрочем, и в любой другой ЗВ-технологии) является двухмерный графиче-
ский пиксельный буфер результирующего изображения, готового к переносу на эк-
ран (рис. 5.3). Область экрана, связанная с таким буфером, называется портом
представления (viewport).
В Qt за отображение буфера OpenGL отвечает класс QGLWidget. Многие про-
граммы будут использовать только этот класс верхнего уровня, хотя существует и
несколько вспомогательных классов: QGLContext представляет контекст выполне-
ния в терминах OGL, QGLFormat занимается форматами (режимами) дисплея, а
QGLColormap хранит OpenGL-специфические цветовые палитры. Хочется особо
отметить, что поддержка OpenGL не претерпела значительных изменений в версии
Qt 4 по сравнению с прошлыми версиями, за исключением нескольких косметиче-
ских исправлений.
Самый главный класс, QGLWidget, является визуальным элементом управле-
ния, как отображающим трехмерные сцены, так и служащим “приемником” для
передачи команд в OpenGL-сервер. Этот класс происходит непосредственно от клас-
са QWidget, а также от вспомогательного класса QGL, содержащего необходимые
константы.
Ш Help
csmsfan— и— -,
<а4,даарх-4ьая«м4»»-«^~-аа~«!-Л'»₽я1и^^ :
ll...:.j....f../.^.л.л..:.*...'.,.‘..:...:....../.•..j
Рис. 5.3. Классические шестеренки OpenGL: в окне
справа показан захват пиксельного буфера. Обратите
внимание на артефакт в левом окне: софт-рендеринг
дает себя знать, и захват экрана может прийтись
на момент обмена
Работа класса QGLWidget отличается простотой и прямолинейностью. Первое
действие, представленное виртуальным методом QGLWidget:: initialized^),
служит для установки таких основных параметров, как контекст отрисовки. Кон-
текст устанавливается как минимум один раз, в начале работы программы, а также
каждый раз, когда устанавливается новый QGLContext-объект. Вы должны пере-
определить этот метод для рёализации собственной логики инициализации, на-
пример, чтобы установить нужные вам флаги (режимы) OpenGL, палитры и т.д.
Вторая обязательная операция — вызов метода resizeGL (). Этот метод делает
немного больше, чем просто меняет размер элемента управления, — здесь задаются
параметры OpenGL-представления (порта просмотра) и проекции (установка каме-
ры). Эта операция выполняется автоматически при первом отображении и, естест-
венно, при каждом изменении указанных параметров. Разместите в своем, переоп-
ределенном, методе вызовы, отвечающие за настройку порта просмотра, например
glViewport().
Наконец, при каждом обновлении представления, которое происходит асин-
хронно, вызывается метод paintGL (). Здесь вызываются реальные “рисователь-
ные” функции OpenGL, т.е. небольшая (частичная) инициализация, например
glMaterialvf (), а также блок методов glBegin () /glEnd (), внутри которого и
располагаются средства реального рисования — например, последовательность вы-
зовов glVertex3 f().
Когда вы реально будете обновлять изображение — зависит только от вас, воз-
можно, вы будете кэшировать изменения, но не так, чтобы пользователь был разоча-
рован медленной реактивностью вашей программы. В любом случае не забывайте,
что метод paintGL () представляет собой “обратный вызов” (callback), т.е. система
(Qt) сама решает, когда обновлять представление.
С обновлением представления связан механизм автоматического сглаживания
анимации: для этого, как известно, используется двойная буферизация. Если
двойная буферизация задана в соответствующем формате (QGLFormat) и, кроме то-
го, был вызван метод QGLWidget: : setAutoBuf ferSwap (TRUE), то каждый
paintGL () -вызов будет приводить к переключению буферов (естественно, уже по-
сле рисования). В противном случае вы должны будете где-то вызывать метод
swapBuf f ers () — конечно, это все нужно только при включенной двойной буфе-
ризации.
По аналогии с командами обновления самого изображения также существуют и
команды для обновления оверлея. Оверлей — это слой графики, расположенный
над (или под) текущим изображением. В этом слое часто размещаются
“плавающие” элементы управления, титры или фон (для нижних слоев). Текущее
изображение считается оверлеем нулевого слоя. Инициализация и обновление
оверлея производятся методами initializeOverlayGL(), resizeOverlayGL() и
paintOverlayGL().
С оверлеями связана определенная логика — “фокуса вывода”. Подобно тому,
как вы можете перенаправить OpenGL-вывод на данный объект типа QGLWidget с
помощью метода makeCurrent (), точно так же вы можете “привязать” OpenGL-
контекст к данному оверлею с помощью метода makeOverlayCurrent (). Для
оверлеев нужно переопределить те же методы обратного вызова (т.е. инициализа-
цию), а также resize- и paint-методы.
Есть и другая возможность для обновления представления, синхронная, т.е.
инициируемая вами, — для этого служит метод updateGL (). Разница такая же, как
и между методами paint () и update () для элемента управления типа QWidget
(рис. 5.4).
Рис. 5.4. BQt4 OpenGL логотип вращается
в пространстве (в прошлой версии он был
нанесен на пивную банку). Движки в нижней
части окна позволяют почувствовать
реактивность системы в реальном времени
Как это часто бывает с “большими” объектами, главные настройки QGLWidget-
объекта происходят в самом конструкторе. Существует три мощные перегрузки
конструктора класса QGLWidget: самый простой выполняет инициализацию для
объекта QGLWidget так же, как если бы это был простой элемент управления, т.е.
задаются имя элемента и указатель на его контейнер. Предполагается, что вас уст-
роят значения по умолчанию. Более сложные конструкторы позволяют сразу
задать такие дополнительные параметры, как OpenGL-контекст или формат нового
элемента отображения. Если формат не задан ни в конструкторе, ни позже, то фор-
мат по умолчанию можно получить с помощью статического метода QGLFormat: :
defaultFormat().
Немного остановимся на формате и контексте, раз уж о них зашла речь. В среде
OpenGL большинство параметров отображения задается с помощью специальных
“флагов” или “режимов отображения”. На самом деле эти ключи совершенно не
влияют на то, что именно отображается, т.е. на заданные примитивы и их комби-
нации: изменению подлежат только те преобразования, которые “задевают” на-
чальный набор данных в конвейерах композиции и растратизации OpenGL, т.е.
ключи влияют на видимое представление, но не на сам виртуальный объект.
Все характеристики отображения условно делятся на формат изображения и
контекст. Форматом изображения занимается класс QGLFormat. Перечень подкон-
трольных параметров включает следующее.
Одиночная или двойная буферизация. Двойная буферизация служит для
“бесшовной” анимации на экране дисплея, но бесполезна при выводе на
принтер.
Z-буфер. Данный параметр определяет наличие у пикселей дополнительной
координаты, которая будет задействована при принятии решения о том, ка-
кой из нескольких конкурирующих пикселей будет отображаться на перед-
нем плане.
Режим RGBA. Как известно, в компьютерной графике распространены два
метода представления цвета: абсолютным значением в цветовом пространст-
ве, например RGBA (“А” обозначает альфа-канал, т.е. прозрачность), и, что
зачастую более производительно, в качестве индексов в цветовой палитре.
• Последний метод еще называют индексированным цветом. Среда OpenGL
может быть настроена для работы в обоих режимах.
Альфа-канал. Как уже было сказано, возможно использование цветовых
схем с учетом коэффициента прозрачности. Однако если данная возможность
не востребована и отключена, то производительность рендеринга можно зна-
чительно повысить.
Буфер аккумулятора. Часто после создания трехмерной сцены изображение,
как целое, подвергается дополнительной обработке (иногда говорят
“фильтруется”). Вы, возможно, знаете такие (относящиеся ко всей сцене)
эффекты, как размытие, сглаживание, эффект дрожащей камеры, “старая
пленка” и т.д. Особенно часто по всей сцене используется сглаживание, или
антиалиасинг, отчего изображение получается более реалистическим. Для
достижения таких эффектов изображение не записывается в результирую-
щий буфер, а сохраняется в аккумуляторе для постпроцессинга.
Буфер отсечения. Дополнительный буфер может быть задействован для от-
сечения некоторых частей изображения. По умолчанию этот режим отклю-
чен. Практическое применение этой техники лежит в запрете вывода в об-
ласти палитр инструментов, консоли игрового процесса и т.д.
Стереобуфер. Позволяет создавать два изображения для левого и правого
глаза в системах, где допускается стереовосприятие. Отключено по умолча-
нию.
Прямое отображение. Позволяет действовать в обход системных вызовов и
рисовать напрямую в аппаратные регистры. По умолчанию включено, но не
всегда доступно.
Наличие оверлея. Если режим разрешен, то поверх плоскости рисования
размещается дополнительная плоскость, где могут быть размещены любые
элементы пользовательского интерфейса, такие как надписи или кнопки.
Оверлеи напрямую поддерживаются последними версиями X-Windows.
Уровень оверлея. Задается уровень, на котором будет проводиться рисование
данного представления. Нормальный уровень равен нулю, первый соответст-
вует первому оверлею, минус один — первому “фоновому” и т.д. Если систе-
ма OpenGL не поддерживает данный уровень, то полученный QGLWidget-
объект будет невалидным.
Во всех остальных случаях неверное задание режима (т.е. когда режим не под-
держивается системой, графической карточкой или средой OpenGL) установки бу-
дут заменены максимально приближенными, но элемент управления будет все рав-
но валидным.
У вас есть несколько стратегий для задания указанных параметров формата: вы
можете создать экземпляр класса QGLFormat с заданными свойствами и установить
его как формат по умолчанию для всего приложения. Также можно задать его как
формат для отдельного QGLWidget-представления.
Обратите внимание, что все методы класса QGLFormat, которые возвращают ло-
гические значения “свойство доступно” (например, QGLFormat:: stereo () или
QGLFormat: : hasOverlay ()), получают свои настоящие значения уже после под-
ключения данного QGLFormat-экземпляра к приложению или отдельному элемен-
ту управления. Таким образом, вы можете проверить, насколько результирующий
формат соотносится с запрошенными вами свойствами.
Подобными вещами, т.е. настройками, занимается и контекст рендеринга,
представленный классом QGLContext. Фактически— это калька со всех ключей,
доступных в OpenGL. Формат является подмножеством этих параметров. Конст-
руктор не занимается конкретными значениями (не связывается с OpenGL-
сервером). Вместо этого вы должны вызвать метод QGLContext: : create () для
установления связи и получения реального формата в виде указателя на экземпляр
класса QGLFormat. Аналогично закрытие (отключение) контекста производится
методом closecontext ().
Вы можете думать о контексте, как о более “реалистичной” версии формата ото-
бражения, хотя контекст не является потомком класса QGLFormat, а использует
экземпляр последнего в качестве источника настроек.
Вот практически и все, что можно сказать о OpenGL-поддержке в Qt. Здесь, как
и во многих других случаях, минимализм на руку программисту: вам не приходит-
ся изучать какие-то сложные и непонятные вещи, достаточно полученных знаний о
вызовах OpenGL. Конечно, для получения достойных результатов вам понадобится
и знание компьютерной трехмерной графики как отдельной науки, а также мате-
матический аппарат построения сцен, поскольку профессиональные инструменты
часто реализуют многие алгоритмы самостоятельно.
Глава 6
Мультимедийные возможности Qt
После обсуждения таких важных тем, как двухмерная и трехмерная графика,
для полноты изложения, рассмотрим также поддержку мультимедиа в Qt, хотя эти
возможности и нельзя назвать богатыми.
Такие мультимедийные возможности, как воспроизведение звука и видео, стре-
мительно ворвались в мир компьютеров в середине восьмидесятых годов прошлого
века и по состоянию на 2005 год стали неотъемлемой частью многих вычислитель-
ных систем, от суперкомпьютеров до мобильных терминалов. Тем не менее нужно
всегда помнить о существовании двух, весьма далеких друг от друга миров: профес-
сионального и потребительского. В мире потребительской техники (портативных
MP3-плейеров или беспроводных телефонов) играют роль цена за единицу товара,
скорость появления на рынке и возможность быстрого производства. Многие дос-
тойные товары и услуги не стали популярными, поскольку не удовлетворяли этим
рыночным критериям, несмотря на высокие потребительские свойства.
Рынок профессиональных инструментов более консервативен: часто профессио-
нальные продукты являются уникальными в своем роде, созданными под конкрет-
ный проект. Конкуренции в профессиональном мире, в привычном смысле, не су-
ществует— часто решающую роль играют имя, репутация, деловые связи и
“правильное” географическое расположение.
В этом смысле Qt занимает важное место именно как инструмент для профес-
сиональной обработки мультимедиа: благодаря эффективному, переносимому и
быстрому С-коду, а также встроенным возможностям интернационализации, уни-
кальные приложения для рендеринга, обработки графики и звука могут легко про-
никать на такие сложные рынки, как европейский, азиатский и восточноевропей-
ский.
С другой стороны, в пользовательском секторе готовые классы Qt могут претен-
довать только на роль инструментария при создании “среднего делового приложе-
ния”. Впрочем, главная роль Qt — именно создание деловых приложений, которые
обслуживают корпоративные бизнес-процессы. Особенно интересны эти возможно-
сти в мире переносных устройств — PDA и смарт-фонов. Многие компании, выпус-
кающие такие устройства, разрабатывают версии под управлением Embedded
Linux+Qtopia: пока неизвестен реальный рыночный потенциал этих устройств, но
прогнозы выглядят обещающими.
Мы рассмотрим те небольшие, но важные классы, имеющие отношение к муль-
тимедиа с точки зрения деловых приложений. Приложения, использующие Qt как
базис для создания профессиональных мультимедийных инструментов, вы можете
найти на сайте Trolltech по адресу:
http: //www.trolltech.com/success/index.html.
6.1. Графика в Qt
Как это ни парадоксально звучит сейчас, но простое отображение графики в од-
ном из распространенных форматов еще пятнадцать лет назад считалось атрибутом
мультимедийного компьютера. Мы уже рассматривали векторную и трехмерную
графику, теперь обратим внимание на форматы растровой графики.
Собственно, за представление растровой графики конкурируют два класса. Один
из них, QPixmap, является теневым буфером для отрисовки растра в оперативной па-
мяти до того, как эти данные будут отображены на экране. Этот класс оптимизирован
для переноса изображения на экран, т.е. данные для отдельных пикселей упакованы
в нужный для быстрого копирования формат. Класс QPixmap является основой как
для растровой, так и для векторной графики: находясь в иерархии между классом
QPaintDevice, с одной стороны, и классами QBitmap/QCanvasPixmap, с другой,
QPixmap является тем “перевалочным пунктом”, через который проходит большая
часть необрабатываемого приложением графического кода, представленного, на-
пример, в виде файлов в форматах JPEG1 или BMP2.
Основной характеристикой QPixmap-объекта является размер изображения,
доступный с помощью метода QPixmap: : size (), а также через методы width () и
height (). Еще одна важная характеристика — наличие альфа-канала, т.е. про-
зрачности, как компонента цветового пространства. И последнее — автоматиче-
ская маскировка, или отсекание изображения по произвольной битовой маске.
В совокупности все это позволяет оперировать непрямоугольными полупрозрач-
ными пиксельными спрайтами.
Основная высокоуровневая операция класса QPixmap — загрузка и отображе-
ние изображения из файла, что можно сделать прямо в конструкторе или позже,
с помощью одной из перегрузок метода QPixmap: : load(). При загрузке “за
кадром” используется экземпляр класса QlmagelO, который создает, в свою оче-
редь, QIODevice-экземпляр для непосредственных операций с файлами. В прин-
ципе, если вы не хотите обрабатывать новый тип файлов, то вам и не нужно созда-
вать экземпляры класса QlmagelO— QPixmap-объект сделает это за вас. Обратите
внимание на то, что класс QPixmap содержит как метод load (), так и метод
JPEG (Joint Photography Experts Group — объединенная группа экспертов в области фотогра-
фии) — разработанный данной группой метод сжатия изображений и соответствующий графиче-
ский формат, который характеризуется компактностью файлов и более быстрой передачей, чем
GIF (Graphics Interchange Format — формат графического обмена), но медленным декодировани-
ем и “потерей” деталей изображения. — Примеч.ред.
BMP (bit map — битовое отображение) — стандартный формат графических файлов, предусмат-
ривающий 4, 8 и 24 бит на точку. — Примеч.ред.
save (), хотя для обработки изображения попиксельно вы, скорее всего, захотите
использовать QImage-экземпляр, обеспечивающий формат для модификации рас-
тровых изображений.
Для переноса данных между форматами существуют (медленные) методы QPixmap::
convert То Image () и convertFromlmage (). Иногда эти методы вызываются даже
неявно, как, например, в случае вызова довольно интересного метода QPixmap: :
createHeuristicMask (), который старается “догадаться” о цвете фона, а потом ис-
ключить (маскировать) его, отсекая пиксели этого цвета по контуру изображения.
Класс QImage создан как раз для пиксельного обращения к растру: каждый
пиксель представлен отдельным элементом массива, так что доступ осуществляет-
ся значительно быстрее. Поддерживаются три формата: 1- и 8-битовый с индекси-
рованным цветом, а также 32-битовый RGBA-формат с данными альфа-канала в
старших разрядах. Для преобразования между форматами можно использовать ме-
тод Qlmage::convertDepth().
Биты в QImage-объекте представлены в виде одномерного массива со сквозной
нумерацией: для доступа к отдельному пикселю по его координатам вам придет-
ся вычислять линейную координату как (uint *) image. scanLine (у) + х.
Изображение Qlmage имеет множество таких свойств, как colorTable (),
bitorder (), depth (), и конечно, width () и height ().
Самой естественной операцией является доступ к отдельному пикселю:
Qlmage: : setPixel (). Однако перед тем как использовать этот метод для триви-
альных операций, посмотрите — возможно, она уже реализована штатно в классе
Qlmage. В частности, реализованы такие примитивные преобразования, как мас-
штабирование, зеркальное отображение, а также отображение с помощью произ-
вольной матрицы преобразования. За это отвечают методы scale (), mirror (),
copy (), xForm () и др.
Также имеются и более интеллектуальные функции: уже упоминавшаяся
функция автоматического создания маски изображения createHeuristicMask (),
на этот раз — настоящая, от класса Qlmage, которая вызывается и из QPixmap-
объекта. Также существует весьма полезный метод масштабирования и сглажива-
ния, Qlmage: : smoothscale (). Этот эффект часто используют современные видео-
процессоры для работы с большим буфером фиксированного размера (обычно
1600x1200) с последующей субдискретизацией (down-sampling) в плоскость экрана
(рис. 6.1).
Как видите, класс Qlmage предоставляет вам почти все необходимое для обра-
ботки изображений, в то время как класс QPixmap — почти ничего. В любом случае
можно значительно оптимизировать приложение, заранее решив, что вам важнее:
быстрый доступ к пикселям и метаданным, например при написании графических
фильтров, или быстрое отображение, например при реализации “домашней” ани-
мации.
Shape:
Pen Width'.
Ben Style:
PenCap:
Pen Join:
Brach Style;
$4№ttasH
Рис. 6.1. Стандартный набор рисования
линий и фигур дополнен eQt4 сглаживанием
и градиентными заливками
6.2. Звук
По аналогии с библиотекой трехмерного рендеринга OpenGL, Qt не делает обра-
ботку звука средствами самой библиотеки. Вместо этого Qt полностью полагается
на библиотеки операционной системы. К сожалению, даже если ОС и располагает
многими аудиофильтрами, Qt может утилизировать только некоторые из них.
Говоря в общем, под произвольной платформой вы можете рассчитывать только на
воспроизведение одного типа файлов, а именно формата WAV (DOS-сокращение 8.3
для расширения WAVE, означающего формат Microsoft Wave Audio).
Воспроизведением звука в среде Qt занимается класс QSound. Вы можете либо
сразу загрузить и воспроизвести отдельный файл, либо загрузить файл в конструк-
торе и впоследствии вызвать метод QSound: :play (). Этот класс позволяет полу-
чить несколько простых эффектов, например с помощью метода setLoops () вы
сделаете звучание повторяющимся или, вызвав метод isFinf shed(), проверите
факт воспроизведения.
В остальном поддержку звука в Qt можно назвать минимальной. Надеюсь, рас-
пространение Qtopia в пользовательском секторе таких продуктов, как смарт-
фоны, вызовет к жизни появление полноценных кодеков и сиквенсера (системы
синхронизации мультимедийных событйй). С другой стороны, возможно, функции
FM-радио и МРЗ-плейера скоро будут полностью возложены на аппаратные средст-
ва, которые уже стали дешевле, чем универсальные вычислительные ресурсы для
выполнения аналогичных функций.
6.3. Анимация
Под анимацией в Qt подразумевается воспроизведение анимированных изобра-
жений в форматах GIF, MNG и PNG. Простейший способ воспроизвести анимацию
под управлением Qt — присвоить метке тип “фильм” с помощью метода QLabel::
setMovie (). Естественно, что в своей работе этот метод использует менее прецизи-
онный контроль за воспроизведением, обеспечиваемый, в частности, классом
QMovie.
В основе класса QMovie лежит уже упоминавшийся экземпляр пиксельной кар-
ты, QPixmap, представленный членом framePixmap. Таким образом, вы можете
получить отдельный кадр в любой момен/ времени и обработать его как обычное
изображение.
Конструктор класса QMovie может воспринимать в качестве источника данных
файл, экземпляры классов QDataSource и QByteArray или другой QMovie-
экземпляр. Сразу после создания фильм начинает воспроизводиться. Данные для
нескольких копий фильма являются разделяемыми, так что все операции, такие
как воспроизведение, пауза, номер текущего кадра и так далее, для всех копий
QMovie будут одинаковыми.
Как можно ожидать, для анимации определены такие методы, как
QMovie: :pause (), unpause () и т.д. Состояние воспроизведения можно проанали-
зировать методами finished () или paused ().
Анимация не представлена отдельным элементом управления: данные же о со-
бытиях анимации передаются (делегируются) другому экземпляру типа QObject.
В отличие от стандартного connect () -механизма, здесь применяются специальные
методы connectstatus(), connectUpadate() и connectResize(). Насколько
обосновано такое отступление от внутреннего стандарта — сказать трудно.
Как видите, под фильмами в Qt подразумевается несложная анимация. Это, ко-
нечно, не позволяет говорить о реализации в Qt заметных мультимедийных воз-
можностей.
С другой стороны, почти все большие и успешные проекты, выполненные с по-
мощью Qt, в значительной степени являются визуальными. Многие имеют непо-
средственное отношение к профессиональному кинематографу. Так что Qt, не
реализуя функций мультимедиа, все же создает прочный фундамент для создания
таких приложений, причем самого высокого уровня.
Глава 7
Устройства ввода-вывода:
класс QIODevice
Перед тем как рассмотреть сетевые возможности, обратим внимание на “обычные”,
локальные средства ввода-вывода. Вообще говоря, C++ обладает всем необходи-
мым для эффективного ввода-вывода, в том числе и для перегруженной сериализа-
ции объектов в виде потоков ввода-вывода. Любое дополнение к этой стройной сис-
теме должно иметь достаточно веские причины.
Конечно, главное преимущество любого класса Qt — происхождение от класса
QObject. Возможно, это для вас пока и не существенно, но таким образом любой
класс может поддерживать свойства, сигналы и слоты, а также пользоваться таки-
ми “системными” (в терминах Qt) сервисами, как безопасные указатели типа
QPointer, сборщик мусора, отложенное (deffered) разрушение объектов, а также
согласованная работа с другими компонентами Qt.
Начнем наш краткий обзор средств ввода-вывода данных с базового класса
QIODevice. Вы можете удивиться, но от этого класса происходит не только класс
QTcpSocket, что еще можно было бы понять, но и класс QProcess — класс, слу-
жащий для запуска приложений и коммуникации с внешними процессами. Класс
QIODevice был значительно упрощен (как и многое другое) по сравнению с Qt 3,
чтобы избавиться от лишних особенностей локального алфавитно-цифрового ввода
и вывода. Хотя имя класса QIODevice упоминает некоторое “устройство”, мы бу-
дем иногда называть ту же сущность “потоком”, как это принято в современном
мире C++.
Вполне естественно, что в “открытом” мире любой поток данных, прежде чем вы
сможете считывать с него или записывать в него данные, должен быть открыт. Ра-
боту с файлами определяют два важных параметра: режим открытия файла и тип
доступа.
Возможные режимы описаны в перечислении QIODevice: :OpenMode. Они зна-
чительно отличаются от режимов “чтение”, “чтение и запись” и “добавление”, ко-
торые вам знакомы по классическим библиотекам. В частности, существует режим
усечения (Truncate) для удаления всех данных в потоке перед началом операций с
данными. Также не совсем обычным в качестве режима открытия потока является
текстовый режим (Text): он указывает, что символы перевода строки должны обра-
батываться особым образом. Наконец, режим “без буфера” (Unbuffered) отключает
любые промежуточные буферы записи и чтения данных. Существует даже нулевой
(вырожденный) ключ NotOpen: этот битовый вектор попросту не содержит ни одно-
го значимого бита, так что операция open () с этим режимом в качестве параметра
обречена на неуспех. Можно сказать, это гарантированный отказ в обслуживании.
Получить режим открытия в виде битового вектора флагов можно, вызвав метод
QIODevice::openMode().
Обратной по отношению к открытию потока (или устройства, если выражаться
буквально) является операция close (). Этот вызов без параметров делает больше,
чем кажется: перед закрытием устройства все буферы записи будут синхронизиро-
ваны, т.е. данные из них будут перенесены в устройство. К сожалению (особенно
хакеров), нигде не предусмотрено механизмов, которые после вызова close () по-
зволяют прочитать что-либо из входных буферов устройства.
Между открытием и закрытием устройства вы можете считывать и записы-
вать данные посредством методов read() и write (). Эти два метода являются
“честными”, в том смысле что вы действительно получаете доступные данные.
“Экстремальная” функция readAll () возвращает все доступные данные в виде эк-
земпляра класса QByteArray. Эта функция не отличает отсутствие данных в уст-
ройстве от ошибки — в любом случае вы получите пустой массив байтов.
Наконец, методы readLine () и getChar () получают одну строку или символ.
Строки обрабатываются правильно, если установлен флаг Text для устройства. Этот
флаг можно установить постфактум, вызвав метод setTextModeEnabled (true).
Для ветеранов libc также предусмотрены putChar () и ungetChar () — смысл
этих методов понятен, но в современном мире “больших” данных этими функция-
ми уже мало кто пользуется.
Еще один важный параметр устройства — тип доступа. Различают прямой и по-
следовательный доступ. В первом случае вы можете позиционироваться на любом
отдельно взятом байте потока и производить чтение с этим смещением. Текущее
положение устанавливается через вызов seek () и опрашивается через pos ().
Из существующих устройств этот режим поддерживают классы QFile и QBuf fer.
Последовательный доступ не позволяет установить текущую позицию с помо-
щью метода seek () или узнать ее с помощью pos () — данные, полученные из по-
тока, необратимо удаляются из входных буферов. Также невозможно узнать раз-
мер данных в последовательном потоке с помощью метода size(). Так работают
классы QTcpSocket и QProcess. Абстрактный метод isSequential () должен
быть переопределен в каждом подклассе и возвращать правильное значение. Тип
устройства нельзя установить программно: устройство, согласно своей природе,
может работать с прямым доступом или нет.
Кроме метода isSequential (), еще несколько логических функций возвраща-
ют различные данные о текущем состоянии QIODevice-объекта: методы isOpen (),
isReadabeO, isWritableO и isTextModeEnabled() позволяют разобраться в
возможностях экземпляра класса QIODevice, который вы, возможно, получаете из
внешних функций или даже других Qt-приложений.
Две функции являются “предвестниками” нового подхода к вводу-выводу в Qt.
В Qt 4 обычным подходом к реализации ввода-вывода является создание отдельного
потока, связанного с очередью ввода-вывода. Этот поток, не “отягощенный” визуаль-
ным представлением, должен, по идее создателей Qt, принимать запросы на ввод-
вывод и последовательно их удовлетворять. При этом поток не должен опрашивать
состояния устройств в цикле: он должен полагаться на системные события для реак-
ции на события ввода-вывода. Методы QlODevice: :waitForBytesWritten() и
waitForReadyRead() ожидают заданное число миллисекунд на предмет отправки
данных или получения очередного пакета данных.
Два асинхронных события (сигнала) делают то же самое, но без блокировки по-
тока (фактически — с созданием отдельного блокирующего потока, поскольку опе-
рационные системы предоставляют блокирующие функции как естественный ме-
тод доступа). Сигналы readyReadO и bytesWritten () сообщают приложению,
что данные готовы для приема или, напротив, были полностью переданы в устрой-
ство. Если вы используете вызовы блокирующих функций в обработчиках этих
сигналов (другими словами, после одного чтения вы обязательно ожидаете другое
или то же в отношении записи), то повторного входа в цикл не будет — обработка
события удаляет это событие из очереди обработки.
При переопределении класса QlODevice вы должны определенным образом реа-
лизовать операции записи и чтения: используя файл, сокет или просто буфер. Для
этого предназначены полностью виртуальные методы readData () и writ eData ().
Именно эти два метода — основа любого QIODevide-подкласса.
7.1. Реальные экземпляры класса QlODevice
Сейчас мы рассмотрим только два класса из четырех, которые являются под-
классами QlODevice: QBuf fer и QFile. Два остальных, QTcpSocket и Qprocess,
будут нами рассмотрены позже.
Как ясно из названия, класс QBuf fer представляет собой фрагмент оперативной
памяти, к которой можно получить доступ так же, как вы получаете доступ к уст-
ройству ввода-вывода. Фактическим хранилищем байтов данных является объект
класса QByteArray, создаваемый во время создания QBuffer-объекта в качестве
частного (открытого) члена.
Фактически класс QBuffer выполняет очень немного действий, выходящих за
рамки интерфейса QlODevice. Существует несколько методов, осуществляющих
прямой доступ к хранимому массиву байтов: методы buffer () и data() возвра-
щают указатель на встроенный массив типа QByteArray, а методы setBuf fer () и
setData () подставляют массив байтов в качестве входных данных. Обратите вни-
мание на то, что после открытия устройства типа QBuffer (isOpen () ==true) set-
методы уже никак не влияют на состояние данных, их вызов просто игнорируется.
В остальном QBuf fer-объект представляет собой обычное устройство с произволь-
ным методом доступа.
Следующим рассматриваемым классом является QFile. Лучше думать о экзем-
плярах этого класса как об обычных файлах, хотя это и не всегда так, учитывая
многообразие современных файловых систем. Обычно вы задаете имя файла в кон-
структоре, хотя можете повторно использовать один и тот же экземпляр, вызывая
метод setFileName ().
Файл имеет все преимущества прямого доступа (например, с помощью методов
seek(), size(), pos ()) и возможность определения конца файла по вызову
atEnd (). Также файлы поддерживают максимальное количество унаследованных
от класса QIODevice методов доступа (таких, как read () или putChar ()). К необ-
щим методам относится проверка существования данного файла с помощью метода
exists () или удаление файла посредством метода remove (). Более специфические
операции с иерархической файловой системой выполняют другие классы, которые
описаны ниже.
7.2. Специфика файловых систем: классы QFilelnfo
и QDir
По сравнению с уйиверсальными устройствами ввода-вывода, файлы, разме-
щенные в иерархической файловой системе (или смонтированные в такую систе-
му), имеют дополнительные свойства. Для доступа к этим свойствам и их исследо-
вания служат несколько дополнительных классов.
На самом деле в гетерогенной среде, в которой могут работать программы Qt, не
существует общих соглашений относительно атрибутов или свойств файлов. Даже
ресурсы, смонтированные по протоколу NFS (Network File System — сетевая фай-
ловая система), имеют свою специфику по отношению к локальным Unix-
аттрибутам.
Тем не менее класс QFilelnfo позволяет, насколько это возможно, усреднить
эти свойства. Этот класс никак не связан с определенным устройством в смысле на-
следования, хотя его экземпляр и может быть создан на основе существующего
QFile-объекта. Обычно вы создаете объект класса QFilelnfo на основе обычной
строковой переменной и впоследствии создаете на ее же основе QFile-объект.
Собственно, имя файла трактуется как полное имя, с учетом пути к файлу.
Так что вы можете получить канонический путь с помощью метода
QFilelnfo: :canonicalPath(), который разрешает относительные ссылки
(символы “. ” и “. . ” в имени файла), а также символические ссылки. Много других
методов занимаются определением пути файла, его каталога и т.д. Например, ме-
тод absolutePath () выполняет только разрешение относительных ссылок.
Кроме проблем с путями к файлу, класс QFilelnfo занимается также и непо-
средственными атрибутами файлов. Общими для всех платформ являются, напри-
мер, свойства isWritableO или isHidden(). Другие свойства не так популяр-
ны— например, isRootO имеет ясный смысл под управлением Unix, но может
быть истолкован неоднозначно в среде MS Windows, поскольку эта система не име-
ет единого корневого каталога. Также и свойство isExecutable () является про-
стым атрибутом для Unix и только косвенно может быть определено для среды MS
Windows.
Наконец, такие атрибуты, как идентификатор пользователя файла и группа
принадлежности, не имеют однозначных соответствий в MS Windows. Так что
метод ownerldO будет всегда возвращать значение (uint)-2 для этой системы,
а метод owner () может возвращать для таких “лояльных” систем пустую строку.
В общем, вы всегда должны исследовать параметры файла, особенно если его имя
получено в результате ввода данных пользователем, причем локально или через Web-
форму. Не исключено, что значение, возвращаемое методом canonicalPath (), будет
указывать на один из критических файлов, т.е. возможно, вы имеете дело с попыт-
кой взлома вашей системы. Выполняйте эти действия до открытия файла с помо-
щью метода QFile: :ореп() (к счастью, класс QFilelnfo никак не связан с уст-
ройством и не требует открытия файла).
Еще одним важным классом, связанным с файловой системой, является QDir.
Как вы, разумеется, знаете, директории, также известные как каталоги или папки,
являются, с точки зрения операционной системы, обычными файлами, хранящими
информацию о других файлах, в том числе — ио других каталогах.
Объект класса QDir— поставщик, в основном, двух списков, entryListO и
entrylnf oList (). Первой возвращает сами имена файлов, второй— свойства
файлов. Также множество команд позволяют, например, просто вернуть количест-
во файлов в каталоге с помощью метода count (). Кроме того, существуют методы
для перемещения по дереву каталогов от текущего положения; например, это мож-
но сделать с помощью метода cdUp ().
Вообще-то, можно создать экземпляр класса QDir, не соответствующий како-
му-то каталогу. Существование каталога можно проверить посредством метода
exists (), так же как это делается для файла.
При получении списка файлов задействованы два встроенных сервиса: фильтры
и порядок сортировки, находящиеся в свойствах Filters и SortFlags. Обращаю
ваше внимание, что обращение к каталогам и получение от них свойств всех фай-
лов в общем случае может занимать заметное время.
К другой, исключительно важной функции класса QDir относится ряд статиче-
ских (доступных без создания экземпляра) методов, которые возвращают несколь-
ко критических для приложения каталогов, например homePath(), rootPath (),
currentPath () и tempPath (). Конечно, расположение этих каталогов зависит не
только от типа операционной системы, но и от настроек, и может быть различным
для Linux, MacOS X и FreeBSD, зщтя формально все они относятся к Unix-
подобным ОС.
Интересно, что каталог, из которого было запущено приложение, не относится к
этим системным вызовам, а передается как параметр командной строки самому
приложению. Так что для исследования домашнего каталога приложения следует
вызывать QApplication: : applicationDirPath () или applicationFilePath ().
Как ясно из названия, последняя функция возвращает также и имя приложения.
7.3. Потоковые классы, связанные с устройствами
Мы рассмотрели три главных класса, связанных с файлами: QFile представляет
собой устройство для доступа к данным файла, QFilelnfo поставляет информацию
для открытого или закрытого файла и, наконец, класс QDir обслуживает специ-
альный тип файлов — директории.
Обратимся еще к нескольким классам, имеющим вторичное, но порой незаме-
нимое значение. Начнем с очень важного класса QTextStream. Этот класс позволя-
ет унифицированно получать строки данных и записывать их в объекты типа
QIODevice, QByteArray и QString. Как видите, сам класс не происходит от класса
QIODevice и является функционально ортогональным к устройствам ввода-
вывода. Кроме всего прочего, класс QTextStream обеспечивает форматированный
и полиморфный вывод текста: вы можете с его помощью помещать в выходной по-
ток или считывать из него строки, отдельные фрагменты (слова), а также числа.
При некотором переопределении базовых методов вы можете расширить эти воз-
можности для любого класса, как встроенного, так и созданного вами.
Кроме полутора десятков перегруженных потоковых операторов “«” и “»”,
класс QTextStream имеет дюжину содержательных методов, влияющих на формати-
рование данных, например методы setFieldWidth() или setFieldAlignment ().
Все эти методы так или иначе влияют на форматирование выходных данных. Напри-
мер, несколько методов устанавливают формат для вывода числовых данных, в том
числе методы setNumberFlags (), setlntegerBase (), setRealNumberNotaton ()
и т.д.
По аналогии с классом QTextStream существует потоковый класс для вывода
неформатированных двоичных данных. Речь идет о классе QDataStream. Именно
посредством его методов осуществляется вся штатная сериализация в Qt. Этот
класс, разумеется, не оперирует такими понятиями, как формат чисел, система
счисления и тому подобное, — используются только двоичные данные определен-
ного типа. Вместо этого в качестве параметров обмена выступают, например, кон-
станты QDataStream: :BigEndian и QDataStream: :LittleEndian. Впрочем, по-
прежнему существует двенадцать перегрузок для различных типов данных —
именно для этого и служат такие константы, как BigEndian.
Кроме того, если вы пишете настоящее совместимое приложение, вам, возмож-
но, будет интересно значение, возвращаемое методом QDataStream: :version().
Дело в том, что за время существования четырех версий Qt система сериализации
(вывода объектов в поток) претерпела семь модификаций. Вы можете принуди-
тельно установить версию сериализации, вызвав метод setversion (): для теку-
щей версии Qt 4.0 значение version () равно семи или константе Qt_4_0. Для рас-
познавания версии сериализации вы можете самостоятельно спроектировать и
записать заголовок файла особого формата.
Глава 8
Модуль Network и сетевые
возможности
Безусловно, работа с сетевыми соединениями — одна из самых востребованных
функций любой библиотеки, претендующей на универсальность. В частности, осо-
бое значение имеют семейство протоколов TCP/IP, а также протоколы более высо-
кого уровня, построенные на базе TCP. И хотя функции работы с этим интерфейсом
“сложились исторически” (например, такие, как connect () или accept ()), тем не
менее стопроцентной совместимости между вызовами и структурами данных в API
различных операционных систем не существует. Эти недостатки и компенсирует
модуль Network.
В модуле Network описаны три группы классов, относящиеся к сетевым прото-
колам. Первые реализуют различные низкоуровневые протоколы, а также опреде-
ляют дополнительные объекты, задействованные при передаче данных. К этой ка-
тегории относятся классы QTcpSocket, QServer, QDns и т.д. Ко второй категории
относятся “готовые к употреблению” высокоуровневые протоколы (реализуемые
классами QHttp и QFtp). И наконец, к третьей категории принадлежит несколько
классов, которые представляют скорее данные, чем операции, например класс
QUrl позволяет выполнять синтаксический разбор полного сетевого адреса на со-
ставные компоненты.
В основном, сетевые возможности не претерпели больших изменений в версии
Qt 4, хотя некоторые улучшения все-таки быди произведены. Кроме повышенной
производительности и исправления нескольких ошибок, была добавлена поддерж-
ка интернациональных доменных имен, улучшена поддержка ПМэ-адресов, а так-
же сокетов UDP (User Data Protocol — пользовательский протокол данных).
В категорию “опальных” попал класс QUrlOperator, позволяющий создавать
виртуальные сетевые файловые системы и обращаться к сетевым ресурсам как к
локальным файлам. Откровенно говоря, идея была совсем не плохая, но теперь нас
призывают использовать только естественные методы доступа с помощью QHttp и
QFtp. Также вышел из употребления класс QSocketDevice: теперь вы можете ис-
пользовать классы QTcpSocket и QUdpSocket для тех же целей, в частности — для
использования в невизуальных потоках для неблокирующего сетевого обмена.
Внутренний частный класс QSocketLayer реализует QSocketDevice-функции,
однако в версии Qt 4.0 он не доступен для непосредственного создания экземпляров.
Для начала рассмотрим базовые классы, а потом кратко остановимся на расши-
рении стандартных возможностей.
8.1. Класс QAbstractSocket и его подклассы
Большинство функций, связанных с IP-сокетами, или сокетами Беркли, зало-
жено в абстрактный класс QAbstractSocket. В частности, от него происходят
классы QTcpSocket и QUdpSocket. Несмотря на различия в работе TCP- и UDP-
сокетов, класс QAbstractSocket реализует общие для обоих функции, по ходу ни-
велируя эти различия.
Например, для протокола UDP не имеет значение операция установки соедине-
ния. Тем не менее метод QAbstractSocket: : connectToHost () выполняется и для
этого типа протокола. При этом UDP-соединение, конечно, не устанавливается, но
вызов этого метода кэширует адрес и порт удаленного сервера, так что последую-
щие методы read () и write () не требуют указания этих параметров.
Сокет типа QAbstractSocket, как это и описано в стандарте для сокетов, всегда на-
ходится в одном из состояний. Текущее состояние можно получить, опрашивая метод
state (). Фазы, или состбяния, сокета при удачном соединении меняются в таком по-
рядке: UnconnectedState, HostLookupState (после вызова connectToHost()),
ConnectingState и ConnectedState. Сигналы hostFound() и connected () сра-
батывают при разрешении символического имени сервера и при установке соеди-
нения. В результате сокет должен установиться в состояние, соответствующее зна-
чению true, возвращаемому методом isValidO,— это значит, что сокет готов
для считывания и записи данных.
Считывание и запись реализуются, в основном, переопределенными методами,
унаследованными от класса QIODevice (например, read (), write (), getChar t),
putChar () и т.д.). Такие вспомогательные методы, как readyRead (), bytesWritten ()
и bytesAvailable (), позволяют понять, когда данные готовы для считывания,
сколько их и были ли они переданы.
Наконец, методы close () и abort () закрывают сокет. Различие в том, что
метод close () обязательно передает все находящиеся в буфере данные, тогда как
abort () не будет выполнять никаких завершающих действий, кроме отключе-
ния от сервера. Пока длится процесс отключения, сокет находится в состоянии
Closingstate. После завершения соединения состояние устанавливается равным
значению ClosedState: сигналы closing () и closed () соответствуют фазам за-
вершения.
В состоянии установленного сеанса (или псевдосеанса) получить адреса и порты
соединения можно с помощью методов local Port (), localAddress (), peer Port ()
и peerAdress().
Чтобы узнать о наличии данных в буфере, можно периодически в цикле опра-
шивать метод readyRead (). Однако такая техника признана неудобной в Qt 4: вме-
сто этого рекомендуется для каждой операции чтения-записи создавать специаль-
ный невизуальный поток приложения. Этот поток предположительно должен
обрабатывать только сетевые операции, так что его блокировка вполне оправдана.
Блокирующее чтение и запись значительно проще программировать, при этом в ре-
жиме ожидания поток не мешает работать остальному приложению. Блокирующим
вводом-выводом с помощью сокетов занимается целая группа методов, при этом ме-
тоды waitForConnected(), waitForReadyRead() и waitForBytesWritten() будут
ожидать установки связи, входных данных или записи в соответствующий сокет.
Эти методы должны вызываться не в главном, а в специальном “сетевом” потоке,
поскольку ожидание будет блокировать все приложение, в частности все элементы
управления.
После такой мощной “проработки” в классе QAbs tract Socket два его подклас-
са, QTcpSocket и QUdpSocket, делают очень мало специфических операций и про-
сто по-разному реализуют унаследованные методы. Класс QTcpSocket (по сравне-
нию с QAbstractSocket) вообще не имеет каких-то отличий в интерфейсе.
Различие проявляется при реализации UDP-сокетов: поскольку каждая опе-
рация не связана с предыдущей, т.е. каждая датаграмма (datagram)1 содержит
собственный адрес и порт получателя, то существуют методы readDatagram () и
writeDatagram(), которые принимают адрес и порт в виде параметров. При час-
той рассылке по разным адресам такой подход экономит одну строчку на каждом
соединении.
Для установки UDP-сервера существует команда bind () — этот вызов, по су-
ти системный, указывает операционной системе посылать пакеты, получаемые
на заданный UDP-порт, в данное приложение. Методы hasPendingDatagram() и
pendingDatagramSize () служат для опроса полученных данных. Как уже было
сказано ранее, для упрощения своих программ вы можете и должны использовать
блокирующие методы.
8.2. Серверы, основанные на классе QTcpServer
Для реализации TCP-серверов, как более распространенных, существует специ-
альный класс QTcpServer. Этот класс делает написание собственных серверов про-
стым делом: вы просто вызываете метод listen () для ожидания поступающих вы-
зовов. Если у компьютера более одного интерфейса или несколько IP-адресов, то вы
можете предоставлять сервис на одном или всех адресах. Последнее достигается
передачей методу listen() параметра QHostAddress: :Апу. Также можно задать
нулевой порт, что приведет к автоматическому выбору порта, хотя, как правило,
это не лучшая идея — клиентские приложения ожидают получить ваш сервис на
определенном порту.
После вызова метода listen () сервер переходит в режим isListening(). При
получении вызова срабатывает сигнал newConnection(). При этом открывается
сокет QTcpSocket, который вы должны получить, опросив метод next Pending-
Connection (). Система создает за вас экземпляр сокета, но не удаляет его, так что
вы должны сами разрушить полученный объект типа QTcpSocket. Конечно, все
Датаграмма, или дейтаграмма,— пакет данных и связанной с ним адресной информации, который
маршрутизируется в сети с переключением пакетов или передается по локальной сети. — Примеч.ред.
сокеты будут разрушены при вызове деструктора класса-контейнера QTcpServer,
но поскольку сервер — программа с длинным жизненным циклом, работающая
иногда не один месяц подряд, то вам следует аккуратно убирать неиспользуемые
объекты, чтобы избежать деградации системы.
Вообще-то говоря, запросы могут приходить быстрее, чем вы будете их обраба-
тывать, даже с учетом многопоточной обработки. Поэтому запросы могут выстраи-
ваться в очередь на обработку. По умолчанию будет принято до тридцати необрабо-
танных запросов — после этого клиентское приложение будет получать печально
известное сообщение “отказ в обслуживании”. Вы можете изменить этот параметр,
установив новое значение с помощью метода setMaxPendingConnections() —
однако учтите, что если в очереди будет всегда много запросов, которые вы не успе-
ваете обрабатывать, то клиент все равно не получит обслуживание с диагностикой
“тайм-аут”. Проверить наличие ожидающих запросов и возможно, как-то на это
среагировать позволяет метод hasPendingConnection ().
Наконец, как и в случае с сокетами, клиентские сокеты могут работать в асин-
хронном или синхронном режиме Первый вариант— через обработку сигнала
newConnectionO. Второй означает работу в режиме ожидания: достаточно вы-
звать метод waitForNewConnection(), который будет блокировать поток на за-
данное количество миллисекунд в ожидании нового запроса. Как и в случае с соке-
тами, здесь предполагается, что вы создали специальный ожидающий поток и не
блокируете интерфейс пользователя в главном потоке.
Подытоживая знакомство с возможностями сокетов (предположительно, кли-
ентских) и TCP-серверов, рассмотрим простое приложение Fortune, реализующее
простой сервер и клиент.
Вот как вкратце выглядит сервер (приведены только интересные строки).
#include <QtGui>
#include <QtNetwork>
#include <stdlib.h>
#include "server.h"
Server::Server(QWidget *parent):QDialog(parent){
tcpServer = new QTcpServer(this);
if (!tcpServer->listen()) {
QMessageBox::critical(...tcpServer->errorString());
close(); return;
}
statusLabel->setText(...tcpServer->serverPort());
fortunes « tr("y бобра добра не ищут”)« ...
connect(quitButton, SIGNAL(clicked()) , this, SLOT(close()));
connect(tcpServer, SIGNAL(newConnection()), this,
SLOT(sendFortune()));
void Server::sendFortune(){
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setversion(QDataStream::Qt_4_0);
out « (quintl6)0;
out « fortunes.at(rand()%fortunes.size());
out.device()->seek(0);
out « (quintl6)(block.size()-sizeof(quintl6));
QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
connect(clientconnection, SIGNAL(disconnected()),
clientconnection, SLOT(deleteLater()));
clientConnection->write(block);
clientConnection->close();
}
Как видите, все происходит, как и обещано: создается и переводится в режим
ожидания экземпляр класса QTcpServer. В данном случае мы не создаем отдельный
поток, так что обработка происходит асинхронно, через сигнал newConnection().
Определенного интереса заслуживает отсылка “фортуны”: по сути, тут реализован
новый, до сих пор не известный NFP (“network fortune protocol”). Данные предва-
рительно форматируются в буфере block, после чего отсылаются клиенту.
Обратите внимание на то, как аккуратно разрушается экземпляр сокета, — ме-
тодом QObject::deleteLater(). Эта операция только помечает объект как
“безопасный для удаления”, но само разрушение происходит отлажено, когда при-
ложение снова войдет в главный цикл. Это позволяет осуществлять такие техниче-
ские операции, как “уборка мусора”, асинхронно, т.е. в тот момент, когда не нужно
выполнять более насущные задачи, например обработку данных, вводимых поль-
зователем.
8.3. Класс QHttp и связанные с ним классы
Данные в формате HTTP (Hypertext Transfer Protocol — протокол передачи ги-
пертекстовых файлов) сегодня вытесняют другие типы данных из мировой сети, и
часто Web-страницы — это все, что люди подразумевают под словом “Интернет”.
Это, конечно, далеко от действительности, но нет никаких причин, по которым
данные любой природы не могут быть переданы в виде гипертекста.
Протокол HTTP реализуется отдельным классом QHttp — точнее, он реализует
клиентскую часть протокола. Этот класс реализован асинхронно, без блокировки,
так что вся обработка является реакцией на сигналы.
Основная операция — установка соединения с сервером. Когда именно проис-
ходит соединение, не суть важно, но, по крайней мере, у вас есть два шанса за-
дать имя удаленного сервера: в конструкторе QHttp или позже, вызвав метод
setHost (). Как уже было сказано, методы классаQHttp не блокируют выполнение
программы и сразу же возвращают управление. Также методы возвращают иден-
тификатор запроса: впоследствии, когда вы получите событие, в качестве парамет-
ра будет указан запрос, к которому это событие относится.
К другим таким же неблокирующим функциям относятся get (), post () и
head (), которые запрашивают или отсылают данные на сервер согласно форматам
запросов HTTP.
Существует также “хакерский” вариант запроса, request (), — этот метод при-
нимает заголовок запроса в формате QHttpRequestHeader, который мо^сет содер-
жать любые заголовки запроса, которые вы туда поместите. Кроме этого, метод
request () принимает два экземпляра класса QlODevice. Первый — это откры-
тый сокет для связи с сервером. Второй, если указан, обозначает устройство, ку-
да непосредственно будут записываться полученные данные. Если этот параметр
равен нулю, то вы будете получать результат вызова readyRead () при получении
каждой порции данных. С запросом request () также связаны два события:
requestStarted() и requestFinished(). Оба сигнала в качестве параметра по-
лучают уникальный идентификатор запроса.
По аналогии с заголовком запроса существует и заголовок ответа — в этом слу-
чае класс называется QHttpResponseHeader. Когда в ответ на запрос приходит за-
головок, то возникает событие responseHeaderReceived(). Иногда заголовок —
это все, что вам нужно получить от сервера.
Во время пересылки данных, особенно больших объемов, вы, возможно, захоти-
те поставить пользователя в известность о том, как продвигается его пересылка.
Обычно для этого служат “линейки” индикации выполнения (progress bar control)
в строке состояния. В любом случае вы можете программно наблюдать за прохож-
дением данных с помощью события dataTransf erProgress (). Это событие полу-
чает как общий объем данных, так и объем уже переданной части — эти числа не
всегда точно соответствуют числу байтов, но, по крайней мере, их соотношение все-
гда правильное, так что вы можете использовать эти числа для инициализации
объекта типа QProgressBar.
Состояния QHttp-объектов очень похожи на состояния обычного сокета:
Unconnected, HostLookup, Connecting и т.д. Все эти константы перечислены в
QHttp: :State. Когда состояние запроса изменяется, то объект получает сигнал
stateChanged(). Вообще-то, вектор состояний прост только в самом “идеальном”
случае, когда все операции выполняются без проблем. Первая же ошибка приводит
соединение в невалидное состояние. Ошибками считаются только сбои сети: если
сервер возвращает какой-то статус, например известную “ошибку 404”, то в терми-
нах QHttp это считается нормальным поведением. Подробности ошибки можно по-
лучить через вызовы error () и error St ring ().
Наконец, когда все запросы завершились с тем или иным состоянием, то QHttp-
объект получает сигнал done (). Именно в этот момент вы можете удалять сам эк-
земпляр — он выполнил определенную работу и не ждет окончания асинхронных
запросов. Разумеется, после окончания одного блока заданий вы можете подавать
новые запросы, используя тот же объект.
8.4. Доступ к файловым архивам
с помощью класса QFtp
Если HTTP представляет собой достаточно скромный интерфейс, содержащий
всего около пяти команд, то FTP содержит множество команд, а именно более три-
дцати. В принципе в этих командах нет ничего сложного: обычно вы должны под-
ключиться к серверу, изменить текущий или удаленный каталог, установить дво-
ичный или текстовый режим закачки и получить файл. Небольшой дивертисмент
связан с мультисессиями и возможностями частичной докачки: если вы можете
(т.е. сервер позволяет) иметь с сервером несколько сеансов и при этом сервер пони-
мает команду REST, т.е. позволяет докапать данные начиная с заданного смещения,
то вы можете организовать многопоточную закачку, подобную той, что можно
наблюдать при выполнении таких программ, как FlashGet, GetRight и многих
других.
Класс QFtp построен по принципу достаточного минимализма: такие часто
употребляемые команды, как connectToHost (), cd(), list () или get (), реали-
зованы как отдельные методы. Для всех же остальных FTP-команд, которые
используются только изредка (REST тоже попала в их число), существует универ-
сальный метод rawCommand (). В принципе протокол FTP так прост, что вы вообще
можете не использовать другие методы. Для обработки “чистого” ответа от сервера
служит специальный сигнал rawCommandReplay ().
В остальном жизненный цикл QFtp-объекта мало отличается от QHttp: все за-
просы также получают идентификаторы, по которым впоследствии можно опреде-
лить, какой запрос вызвал то или другое событие. В частности, доступны все те же
методы commandStarted(), commandFinished(), dataTransferProgress() и,
наконец, done().
Немного внимательности нужно проявить при установке режимов закачки:
TransferMode содержит константы Active и Passive, a Transf егТуре — Binary
и Ascii. Поскольку в обоих перечислениях два значения, 0 и 1, то важно не пере-
путать, что именно вы задаете в данный момент.
В отличие от HTTP, где обычно доступ осуществляется анонимно, FTP-
протокол, как правило, требует аутентификации, даже если вы и входите под име-
нем Anonymous. Для этого служит команда QFtp: : login (), и после входа в систе-
му состояние изменяется на QFtp: : Loggedln. Как ни странно, но эта команда тоже
не блокирует приложение.
8.5. Дополнительные сетевые классы
В принципе Qt 4 сделала шаг назад, ограничив сетевые возможности минималь-
ным набором классов. Это, конечно, не идет ни в какое сравнение с другими из-
вестными библиотеками, такими как Java Net Library или Delphi Indy Networking.
Сложно сказать, как часто вы создаете Gopher-сервер или используете NTP для
синхронизации времени через Интернет, но, по крайней мере, доступ к SMTP
(Simple Mail Transfer Protocol — простой протокол электронной почты), POP3 (Post
Office Protocol— почтовый протокол) или IMAP4 (Internet Message Access
Protocol— протокол доступа к сообщениям в сети Internet) был бы, вероятно,
очень кстати. Конечно, после того, как вы реализуете IMAP через сокеты нижнего
уровня, вам будет известно о протоколе значительно больше, и ваше приложение
будет обладать значительно большим потенциалом как продукт. Но у готовых ком-
понентов тоже есть своя аудитория.
В заключение рассмотрим пару вспомогательных классов, которые служат ва-
шим целям при реализации собственных сетевых надстроек.
Класс, служащий для разрешения символических имен в числовых сетевых ад-
ресах, называется QHostlnfо. Можно сказать, что это клиентский компонент DNS
(Domain Name System — служба имен доменов).
К большому сожалению, существовавший в Qt 3 класс QDns, выполнявший ре-
альные низкоуровневые запросы, признан несовременным и перемещен в библио-
теку совместимости. А жаль, ведь, в отличие от QHostlnf о, этот класс мог возвра-
щать любые произвольные записи из таблиц DNS, а не только A-записи. Новый
класс QHostlnf о просто использует сетевой вызов ge t ho st byname () — это на-
столько простая операция, что ради этого можно было бы и не создавать такой пре-
тенциозный класс: можно подумать, что это чуть ли не front end (внешний интер-
фейс) для команды nmap.
С разрешением имен связан также класс QHostAddress. Это весьма простой
класс — оболочка вокруг одного целого числа без знака. Небольшой сервис связан
с хранением как IPv4-, так и 1Ру6-адресов, но, в общем, этого класса могло бы
с успехом и не быть.
Короче говоря, библиотека Qt 4 предоставляет только самые базовые сетевые
возможности, ограничивая даже то, что было в Qt 3. Вероятно, такому решению
сопутствовали широкие маркетинговые исследования — в Qt 4 вообще многое со-
кращено и немало “плевел” попросту отброшено. С другой стороны, компания
Trolltech обещает “полностью функциональную версию” Qt 4.1 со многими допол-
нениями и расширениями. Может быть, там появятся дополнительные сетевые ин-
терфейсы?
Глава 9
Базы данных и модуль SQL
Модуль SQL содержит набор Qt-инструментов для доступа к базам данных по-
средством SQL-запросов. Возможно, это не самый новомодный метод доступа — но,
по крайней мере, самый известный и универсальный. Конечно, доступность SQL
для “масс” порождает тысячи строк сложного кода, где SQL используется к месту и
не к месту. Часто можно было бы ограничиться текстовым файлом или устойчивы-
ми объектами. Но, по крайней мере, в сознании менеджеров и руководителей, SQL
является признаком профессионализма разработчика и качества вашего проекта.
Так что перейдем непосредственно к сути дела. Предполагается, что вы знаете тер-
минологию баз данных, в частности вам знакомы понятия сеанса, подключения,
запроса, таблицы, курсора и т.д.
В Qt 4 наведен определенный порядок, и, в частности, все классы, относящиеся
к базам данных, разделены на три уровня или категории. Тем, кто активно про-
граммировал под Qt 3 и более ранние версии, придется забыть почти все, что они
знали о доступе к базам данных: Qt 4 сокращает количество классов доступа в три
раза, и программный интерфейс от этого только выигрывает. Этот минимализм в
духе старой школы Unix, и лично я приветствую любые “исчезновения” классов,
без которых можно легко обойтись.
9.1. Классы физического уровня: QSqlDriver и другие
Первый, самый примитивный “слой” классов занимается получением данных
на физическом уровне (насколько SQL вообще имеет доступ к физическому уров-
ню). Этот уровень называется уровнем драйверов — вам редко придется использо-
вать его экземпляры напрямую и тем более строить собственные SQL-драйверы.
Однако возможность, что называется, существует. Более того, код SQL-драйверов
открыт, так что вы можете в деталях ознакомиться с приемами программирования.
Для обычного же разработчика достаточно знать список готовых драйверов, ко-
торые можно использовать без дополнительных затрат: их всего девять, в том числе
для MySQL, PostgreSQL и SQLite. Также напрямую поддерживаются Oracle Call
Interface, IBM DB2 и Borland Interbase. Один драйвер обслуживает как оригиналь-
ный Sybase Adaptive Server, так и MS SQL. Наконец, универсальный драйвер к ис-
точникам данных ODBC (Open DataBase Connectivity — открытый интерфейс дос-
тупа к базам данных) позволяет подключиться к любой базе данных, для которой
существует ODBC-драйвер, в том числе и ко всем перечисленным выше.
Мы не будем заниматься построением собственных драйверов — документация
Qt содержит по этому поводу представительную статью “Writing your own drivers”
(Создание собственных драйверов). Тем не менее немного уделим внимание нижне-
му уровню работы с SQL-сервером.
Базовая абстракция, класс QSqlDriver, как следует из названия, является соб-
ственно драйвером баз данных. Главная задача драйвера — открыть физическое со-
единение с базой данных на основе имени сервера, порта подключения, имени и па-
роля пользователя и имени базы. Для открытия соединения служит метод
open () — это первое, что вы должны переопределить в своем подклассе.
Еще один важный абстрактный метод, hasFeature (), возвращает набор возмож-
ностей базы данных в виде битового вектора, который содержит такие очень важные
флаги, как Transactions, QuerySize, BLOB, Unicode, NamedPlaceholders и еще
пару других. Конечно, часто приложение пишется под определенную базу данных,
и даже версию, но в случае, если база данных открывается другим модулем или на-
страивается через файлы конфигурации, знание этих возможностей является кри-
тическим для вашего приложения: вы можете долго опрашивать метод
QSqlQuery: : size (), но если эта функция не поддерживается самим сервером, вы
всегда будете получать в качестве результата -1. Аналогичный нонсенс будет со-
провождать вас при использовании других неподдерживаемых функций, например
BLOB или Unicode.
Самое важное в классе QSqlDriver — метод createResult (), который возвра-
щает экземпляр класса QSqlResult как результат выполнения SELECT-запроса
к базе данных. Также не бесполезным и часто востребованным является метод
f ormatValue (), формирующий строковое представление для данного значения
поля запроса. Этот метод активно используется при таких запросах-действиях, как
INSERT И UPDATE.
На уровне класса QDriver также реализовано несколько других низкоуровне-
вых операций с таблицами, например получение имени полей или первичного ин-
декса для заданной таблицы (методы record () и primaryindex () соответствен-
но). Объекты уровня запроса и динамического набора используют эти сведения для
своей работы. Например, первичный индекс используется как “ключевое значение”
при обновлении данных по запросу UPDATE, а список полей служит источником
информации для любых обращений к метаданным.
Наконец, методы beginTrasaction(), commitTransAction () и rollback
Transaction () осуществляют транзакции, так что на более высоком уровне вы
можете их только вызвать или вообще не увидеть. Такая реализация связана с тем,
что транзакции часто имеют различный синтаксис, зависящий от базы данных.
Уже упоминавшийся класс QSqlResult представляет собой интерфейс для аб-
страктного доступа к определенной базе данных. Класс QDriver оперирует только
метаданными, в то время как QSqlResult выполняет операции с самими данными.
Вопреки названию класса, QSqlResult заведует не только выборками типа
SELECT, но и остальными SQL-командами. Если вы создаете свой драйвер, то долж-
ны также создать и свой вариант типа QSqlResult, переопределив для него абст-
рактные классы так, чтобы они корректно работали с данным сервером. Класс
располагает такими обычными для SQL методами, как setQuery (), prepare (),
ехес(), fetch() и numRowsAffected() (co множеством различных вариаций).
Вы можете пользоваться экземпляром этого класса для доступа к данным напря-
мую, однако класс QSqlQuery предоставляет несколько более высокоуровневый и
обобщенный интерфейс, так что рекомендуется пользоваться именно им.
При построении SQL-драйверов вам также придется задействовать механизм
“плагинов” (plug-in), т.е. подключаемых модулей, поскольку драйверы реализова-
ны именно так. “Плагины”, в терминах Qt, похожи на все остальные экземпляры
этой категории — это исполняемые модули определенного, зависящего от плат-
формы формата, реализующие определенный программный интерфейс. “Плагины”
располагаются в условленных местах: например, “плагины” баз данных должны на-
ходиться в каталоге plugins/sqldrivers. В обычном случае для создания “плагина”
достаточно создать экземпляр класса QSqlDriverPlugin, метод create () которого
создает нужный экземпляр класса QSqlDriver на основании полученной строко-
вой константы с именем вашего драйвера. “Плагины” в Qt — отдельная интересная
тема, которую мы, тем не менее, отложим до лучших времен.
Как видите, классы QSqlDriver и QSqlResult выполняют массу полезной ра-
боты, так что, располагая только драйвером, уже можно достичь практически лю-
бых эффектов в общении с базой данных. Но это еще только начало.
9.2. Класс QSqlDatabase: подключение к базам данных
Перед любым использованием баз данных в вашем приложении вы должны
подключиться к экземпляру (“экземпляр”— в терминах SQL-сервера базы дан-
ных). При подключении необходимо задать сетевой адрес и порт сервера, имя и па-
роль пользователя, а также имя базы данных, если вы хотите подсоединиться к ба-
зе, отличной от базы данных, заданной по умолчанию для данного пользователя.
Всем этим, а именно — самим подключением, занимается класс QSqlDatabase.
Конечно, как вы уже поняли, этот класс служит “оболочкой” вокруг драйвера
QSqlDriver.
Статический метод QSqlDatabase: :registerSqlDriver() позволяет загру-
зить и сделать доступным драйвер, не присутствующий в дистрибутиве, в том числе
и ваш собственный. Проверить наличие драйвера можно методом (естественно, ста-
тическим) QSqlDatabase: : isDriverAvailable (). Можно также получить спи-
сок зарегистрированных драйверов в виде списка строк методом QSqlDatabase: :
drivers (). Драйвер связывается со строкой-идентификатором, а его экземпляр
создается специальной “фабрикой драйверов”.
QSqlDatabase::regisrterSqlDriver("MYDRIVER",
new QSqlDriverCreator<MyDbDriver>);
QSqlDatabase db=QSqlDatabase::addDatabase("MYDRIVER”);
Мы затронули эти вопросы именно сейчас, поскольку анализ доступных драйве-
ров является статической операцией и предшествует созданию экземпляра класса
QSqlDatabase.
Вся задача класса QSqlDatabase — создать экземпляр QSqlDriver и использовать
его для фактических операций. Это делается в статическом методе addDatabase (),
которому указываются имя драйвера и идентификатор базы данных. Вот как это
происходит.
QSqlDatabase db=QSqlDatabase::addDatabase("QPSQL");
db. setHostName ('’dbhost") ;
db.setDatabaseName("dbname");
db.setUserName("dbuser");
db.setPassword("dbpass•);
bool res=db.open();
В данном случае мы вызвали addDatabase () с одним параметром; при этом второй
параметр по умолчанию принял значение QLatinlString (defaultconnection), т.е.
мы создали соединение по умолчанию, для которого можно каждый раз не указы-
вать идентификатор соединения. Очень часто вы будете пользоваться только одним
соединением по умолчанию.
Обращаю ваше внимание на важный момент: в данном примере мы явно задали
имя пользователя и пароль в тексте программы. Если вам дороги данные в вашей
базе данных, то не стоит рассчитывать, что ваша программа не будет исследована и
эти данные не будут известны заинтересованным хакерам. Храните пароли только
вне программы, а лучше всего примите непопулярное решение: требуйте ввода па-
роля у пользователя при каждом подключении.
Хочется также указать на терминологическую несогласованность: в SQL база дан-
ных обозначает одно подключение. В Qt класс QSqlDatabase служит контейнером
для всех соединений: вы можете неоднократно вызывать метод addDatabase (), а по-
том вызывать статический метод contains () для проверки наличия того или дру-
гого подключения. Другой статический метод, database (), возвращает саму базу
данных (соединение) по заданному имени. Можно сказать, что класс QSqlDatabase
ведет “двойную игру”: в качестве статического контейнера и динамически созда-
ваемых соединений.
С открытием базы данных связаны два метода. Первый, isOpen(), возвращает
значение true, если база в данный момент открыта. Второй, isOpenError (), воз-
вращает значение true, если при открытии возникла ошибка. После вызова
open () эти состояния исключают друг друга, но если база данных еще не открыта
или, напротив, закрыта методом close О — в таком случае оба метода возвращают
значение false — нет открытого подключения, но и нет ошибки подключения.
Если подключение не удалось, а также и после любой другой неудачной опера-
ции, вы можете опросить метод QSqlDatabase: : lastError (). Этот метод возвра-
щает экземпляр особого типа, QSqlError, содержащий описание ошибки. Собст-
венно, класс QSqlError содержит методы databaseText () и driverText () с
сообщениями как от удаленного сервера, так и от локального драйвера. Свойство
QSqlError: : text () возвращает обе эти строки, объединенные в одну.
9.3. Метаданные: классы QSqlRecord и QSqlField
После удачного подключения вы, возможно, захотите исследовать список таб-
лиц в данной базе. Получить этот список можно методом QSqlDatabase: :
tables (). Полный список типов таблиц (в количестве четырех) содержится в пере-
числении QSql: :TableType={Tables, SystemTables, Views, AllTables}.
Вариант без параметра возвращает только “настоящие” таблицы, а вариант с пара-
метром позволяет дополнительно задать и тип возвращаемых объектов:
QStringList list=myDatabase.tables(QSql::Tables|QSql::Views);
QStringList::Iterator it=list.begin();
while (it ’= list.endO) {
myProcessing(*it);
++it;
}
Следующий, по логике, запрос к метаданным касается исследования структуры
данной таблицы или view-запроса. Раньше, в Qt 3, для этого использовался класс
QSqlRecordlnfo, а также запрос QSqlDatabase: :recordinfo(). В Qt 4 тем же
целям служит класс QSqlRecord. Запрос QSqlDatabase: : record (const QString
&tablename) возвращает объект типа QSqlRecord, описывающий структуру запи-
си в заданной по имени таблице. Если таблицы с таким именем не существует, то
возвращается пустой объект.
Нужно понимать, что QSqlRecord-объект представляет “подвешенную” в опе-
ративной памяти копию реальной записи, без механизмов синхронизации с дан-
ными на сервере — обмен информацией происходит на другом, более высоком
уровне. В результате вы можете иметь сколько угодно несинхронных копий одной
и той же записи, и они могут содержать различные данные.
Также нужно иметь в виду, что QSqlRecord-запись в данном случае использует
запрос или таблицу только в качестве исходных данных для структуры и значений
полей — после “закачки” данные в записи могут быть изменены программным пу-
тем (например, вычислимые поля могут вычисляться на стороне клиента). Также,
что довольно опасно, но иногда полезно, — вы можете добавлять и удалять отдель-
ные поля и даже удалить из записи все поля. Такая логика может быть как полез-
ной, так и приводить к тяжело обнаружаемым ошибкам: будете внимательны пе-
ред принятием решения о ее использовании.
Если вы ничего не знаете о структуре записи, то ваш план действий следующий:
опросить количество полей в записи, вызвав метод QSqlRecord: :count(), и по-
следовательно опросить каждое поле с помощью метода field(int) по его число-
вому индексу. Если вас в первую очередь интересуют имена полей, а потом уже вся
остальная информация, то вызываете метод f ieldName (int) для получения имен,
а впоследствии можете обращаться к полям по имени через метод field (const
QString&). Если вы, наоборот, знаете имя поля и хотите получить его индекс, то-
гда вам поможет метод indexOf (const QString&). Проверить наличие поля с за-
данным именем можно с помощью вызова contains ().
Имейте в виду, что порядок полей не определен в теории баз данных, и запоми-
нать индекс поля для последующего использования — не самая лучшая идея, хотя
долгое время это может работать.
С полями можно проделать несколько действий, главное из которых — полу-
чить значение или установить его через методы value () и setvalue (). Также
можно обнулить поле, выполнив метод setNull (), или установить для поля при-
знак “сгенерированное” с помощью метода setGenerated ().
Наконец, вы можете вообще изменить структуру данной записи. При этом вы
создаете новое поле с помощью методов append () или insert () и удаляете его по-
средством метода remove (). Метод clear () очищает все поля, так что вызов мето-
да isEmpty () будет возвращать значение true. Не путайте удаление всех полей с
удалением значений: последнее реализуется методом clearValues ().
Наконец, если вас интересует каждое поле в отдельности и его параметры, то вы
должны исследовать объекты типа QSqlField, возвращаемые, например, методом
QSqlRecord: :field(). Основные характеристики поля возвращаются соответст-
вующими методами (name (), type (), length () и value ()). Более сложные значе-
ния (например, установка свойства NOT NULL, признака вычисляемого
(генерируемого) поля, точности представления чисел и т.д.) также устанавливают-
ся на уровне QSqlField-объекта.
Например, установка для поля признака “обязательное” с помощью метода
setRequiredStatus(QSqlField::Required) приведет к тому, что команда
INSERT без явного задания этого поля будет отвергнута. Аналогично метод
setReadOnly (true) задает поле только для чтения и т.д.
9.4. Класс QSqlQuery: выдача запросов
и получение результатов
SQL-запросы к базе данных осуществляются с помощью специального класса
QSqlQuery. Этот класс служит для выполнения команд-запросов и, что более важ-
но, имеет средства для перемещения по результирующему набору данных, а также
позволяет получать значения отдельных полей. С помощью этого класса можно как
оперировать данными (посредством запросов SELECT, INSERT, DELETE, UPDATE),
так и определять данные с помощью языка определения данных DDL (Data
Definition Language), например с помощью запросов CREATE TABLE, CREATE INDEX
и т.д. Также можно передавать произвольные SQL-команды, понимаемые серве-
ром, но не являющиеся частью стандарта, например из категории SET-команд.
Для выполнения произвольной команды служит метод exec (const
QString&). С его помощью вы можете передать серверу для выполнения любую
корректную SQL-строку.
После удачно выполненного запроса или команды объект класса QSqlQuery
устанавливается в активное состояние, о чем сигнализирует результат выполнения
метода QSqlQuery:: isActive О, возвращающий значение true. И напротив,
пассивное состояние, выражаемое значением false, соответствует таким двум
возможным ситуациям: произошла ошибка или объект еще не был использован
для запроса.
Другим, важным методом опроса является QSqlQuery: : isSelect (). Нетрудно
догадаться, что он возвращает тип последней SQL-команды. Если тип запроса был
“не выборка”, то для такого запроса не имеют значение многие из методов, предна-
значенных для работы с записями данных.
После выполнения запроса позиция курсора в любом случае оказывается на
фиктивной “запасной дорожке”, т.е. на записи, не принадлежащей к набору. Как
вы знаете, в практике баз данных существуют такие “невалидные” записи (или по-
ложения)^ как “до первой записи”, “после последней записи” или “новая запись”.
Для всех этих положений характерно отсутствие уникального значения первичного
ключа, и поэтому теоретически и практически они не являются допустимыми за-
писями. Узнать, находимся ли мы в данный момент на реальной записи, можно,
опросив метод QSqlQuery: : isValid().
Как уже было сказано, в начале, т.е. после выполнения SQL-команды, запрос
находится за пределами допустимых записей. Перемещаться по результирующему
набору данных можно, используя один из методов: QSqlQuery: : next (),
QSqlQuery::prev(), QSqlQuery::first(), QSqlQuery::last() и QSqlQuery::
seek(int i, bool relative=flase). Перемещение перед первой или после по-
следней записи “переместит” вас в невалидное положение.
Последний метод перемещает указатель текущей записи относительно начала
или текущей записи на указанное число строк. С помощью отрицательных или
больших значений перемещения вы можете даже выйти за пределы валидных
записей и переместиться в одно из фиктивных положений, например “до первой
записи”.
Теоретически номера записей и их положение в таблице не должны иметь смыс-
ла, но по традиции, заложенной во времена DBASE, мы можем это делать. Некото-
рые СУБД даже предлагают такой опасный инструмент, как “закладки”, т.е. сред-
ство запомнить текущее положение. Не забывайте, эта техника работает только с
локальными копиями статических данных: любая распределенная, многозадачная
и/или многопользовательская среда может нарушить порядок записей. Иными
словами, номера записей не являются реентерабельным (от англ, reenterable, т.е.
повторно входимым, повторно используемым) ресурсом.
Определить количество записей в результирующем наборе можно с помощью
метода QSqlQuery: : size (). Обратите внимание на то, что это относится только к
SELECT-запросам (используется специальный синтаксис запроса к SQL-серверу).
Для остальных типов запросов (например, вставка или удаление) применяется ме-
тод numRowsAf f ected ().
Если вы только выводите записи, например формируете отчет, то вам может не
понадобиться произвольная навигация “вверх-вниз”. Вполне достаточно будет
перемещаться от начала к концу с помощью методов next () и seek () с положи-
тельными приращениями. При этом производительность значительно возрастет,
поскольку “прокрученные” записи не будут кэшироваться и не будет задействован
алгоритм попадания в кэш. Переключить запрос в этот “однопроходный” режим
можно командой QSqlQuery: : setForwardOnly (TRUE). Текущий режим однопро-
ходности доступен через метод isForwardOnly ().
Обещанный доступ к полям запроса осуществляется по номеру поля с помощью
метода QSqlQuery:: value (i), который возвращает объект типа QVariant.
Поскольку значение, возвращаемое этим методом, имеет тип QVariant, правиль-
ная обработка полей, отличных от текстовых, лежит на вас. Нумерация полей про-
изводится слева направо, начиная с нуля. Поскольку доступ по имени поля не пре-
дусмотрен, вы должны избегать запросов типа SELECT *, так как порядок полей в
результирующем наборе не очевиден (структура таблиц может быть изменена раз-
работчиком БД). Если запрос не активен, неверен номер поля или что-то еще не
так, то данный метод возвратит невалидный объект типа QVariant.
Еще немного остановимся на такой полезной технике, как запросы с парамет-
рами. Как известно, SQL-сервер обладает средствами для предварительного разбора
и оптимизации запросов, причем с кэшированием просчитанных сценариев. К оп-
тимизации относится построение плана запроса, профилировка, фоновое создание
дополнительных индексов и т.д. Этот процесс называется загрузкой, или подго-
товкой запроса. Для загрузки запроса в Qt служит метод QSqlQuery: : prepare ().
Передаваемая строка-запрос может содержать также ряд параметров для после-
дующей параметризации запроса (нужно понимать, что каждый параметр потен-
циально снижает уровень оптимизации). Параметры могут быть заданы в двух
форматах: именованные в стиле Oracle (: parameter) и вопросительные знаки
ODBC (как в MS Access). Смешивание двух нотификаций не допускается.
В зависимости от техники задания параметров (еще иногда называемых за-
глушками), можно подставлять их по имени или по позиции. Подстановка произ-
водится в подготовленный запрос. Для подстановки по имени используется один из
перегруженных методов, из категории QSqlQuery:: bindValue (QString, QVariant,
QSql: : Paramet er Type). Имя переменной указывается вместе с двоеточием, т.е.
так же, как она была задана в запросе.
Для подстановки по порядковому номеру используются методы bindValue (int,
QVariant) или addBindValue (QVariant, QSql:: Paramet er Type). Последний
метод просто формирует список параметров, добавляя их по одному за раз — при
этом нелегко понять, какой параметр добавляется в данный момент. Метод имено-
ванных параметров дает значительно более читабельный код.
Соответственно, есть пару методов для анализа параметров запроса. Самый об-
щий из них— QMap<QString, QValue> QSqlQuery: :boundvalues () — возвра-
щает целый ассоциативный список со всеми именами и значениями параметров за-
проса. Если применяются позиционные параметры, то получить список значений
можно через вызов boundvalues () . values (). Возвращаемое этим методом значе-
ние будет иметь тип QValueList<QVariant>.
Более “спокойные” методы boundvalue (int) и boundvalue (QString) возвра-
щают параметр по индексу или имени соответственно.
Возможно, самым главным, хотя и технически тривиальным моментом после
всех подготовительных работ является выполнение запроса. Это так же просто, как
вызвать метод ехес (). Есть вариант как с явным заданием команды, так и без
параметра — предполагается, что вы уже подготовили команду методом
prepare (). Вызов ехес () сбрасывает все флаги ошибок, валидности и так далее и
устанавливает их заново. В результате также будут установлены значения свойств
lastQuery () и executedQuery () — в общем случае эти значения будут одинако-
выми.
Несмотря на мощность запросов, создаваемых классом QSqlQuery, вы редко
будете их использовать, кроме случаев, когда необходимо отдать нестандартную
SQL-команду или оптимизировать запрос, например, включив однопроходное
сканирование. Для более высокоуровневого доступа к запросам следует исполь-
зовать компоненты “модель-представление”, например QSqlQueryModel или
QSqlTableModel.
Обращаю ваше внимание на следующее: множество таких классов, как
QSqlCursor, QSqlRecordlnfo, QDataSource, QDataSink, QDataTable, QDataView,
QSqlForm или QDataBrowser, были удалены из основной библиотеки Qt и сохра-
нились только в плане совместимости. На сегодня не существует прямых аналогов
этих классов, хотя компания Trolltech и обещает создать несколько похожих
в Qt 4.1. Поэтому единственный приемлемый способ писать элементы управле-
ния— реализовать их самостоятельно или использовать механизмы “модель-
представление” .
9.5. Механизмы “модель-представление”
применительно к базам данных
Мы уже обсуждали в этой книге новую концепцию “модель-представление”
(точнее — новую для Qt и вполне старую, например, для Smalltalk). Было бы стран-
но, если бы табличные представления не были связаны с результатами запросов к
базам данных. Фактически, возможно, и вся история была затеяна именно с этой
целью: подвести строгую объектную модель под то, что в FoxPro реализовалось од-
ной командой BROWSE.
Как вы помните, иерархия моделей занимается предоставлением данных из аб-
страктных источников и базируется на абстрактном классе QAbstractltemModel,
от которого происходит табличная модель QAbs tract Tabi eModel. Вполне естест-
венно, что от этой модели образуются две реальные модели, получающие данные
для таблиц из динамических наборов типа QSqlQuery.
Класс QSqlQueryModel является источником данных, предназначенных только
для чтения, и позволяет быстро просматривать результаты запросов. Подкласс
QSqlTableModel имеет средства для внесения изменений в данные и синхрониза-
ции их с сервером баз данных прозрачно для пользователя. Наконец, класс
QSqlRel at ionalTabl eModel является QSql Tabi eModel-подклассом и вводит ме-
ханизмы для связывания таблиц с помощью вторичных (foreign) ключей. Не суще-
ствует каких-то особых представлений для отображения данных из баз данных:
для просмотра данных вы можете использовать классы QListView или
QTableView или даже написать свое собственное представление.
Работа с моделями данных представляется простой, хотя и задействует многие
механизмы и ресурсы. Рассмотрим более простой случай, а именно с использовани-
ем класса QSqlQueryModel. Для эффективной работы вам понадобится всего не-
сколько методов: сначала вы задаете запрос setQuery (), где указываете SELECT-
предложение. Далее вы можете узнать количество полученных строк через унас-
ледованный табличный метод rowCount (), получить отдельную строку через
record(int) и т.д. Фактически после отдания команды setQuery вы уже работае-
те с обычной таблицей, безотносительно к тому, что данные получены из базы дан-
ных. Поскольку столбцы в таблице (как бы случайно) уже содержат заголовки и
позволяют обращаться к значению по имени, вы вполне можете работать с отдель-
ными значениями.
QSqlQueryModel sqm;
sqm.setQuery("SELECT * FROM emploee");
for (int i=0;i<sqm.rowCount();++i) {
int id=sqm.record(i).value("id").tolnt();
QString name=sqm.record(i) ..value ("name") .toString() ;
qDebug«id«name ;
}
Как можно убедиться — ничего сложно. Конечно, таким образом вы теряете всю
“прелесть” общения с метаданными. Если вы хотите исследовать поля полученного
набора данных или использовать подготовку и параметры в запросах, то можно
предварительно создать QSqlQuery-объект обычным путем, выполнить любые не-
обходимые приготовления, например вызвать метод prepare (), после чего устано-
вить этот запрос в модель с помощью варианта QSqlQueryModel: : setQuery (),
принимающего в качестве параметра QSqlQuery-запрос.
Это что касается программного доступа к запросу как к таблице. Конечно, моде-
ли созданы и удобны также для связывания с представлениями. Вот простой при-
мер такой связи на основе того же запроса.
QSqlQueryModel sqm;
sqm.setQuery("SELECT * FROM emploee");
sqm.setHeaderData(0,Qt::Horizontal, QObject::tr("Name");
sqm.setHeaderData(l,Qt::Horizontal, QObject::tr("Salary");
QTableView view= new QTableView;
view->setModel(sqm);
< view->show();
Если вы хотите переопределить что-то в режиме отображения таблицы или при-
дать данным дополнительные свойства, например отформатировать, преобразовать
в графическую форму и так далее, то лучше унаследовать свой класс от класса
QSqlQueryModel и реализовать в нем дополнительные функции. Выбирать данные
из таблицы программно — сложное и необщее решение.
Класс QSqlTabi eModel позволяет, как уже было сказано, использовать возмож-
ность записи данных обратно в базу данных путем изменения ячеек модели (таблицы).
Методы этого класса слегка отличаются от методов класса QSqlQueryModel: сначала
вы устанавливаете таблицу для внесения изменений с помощью setTableO —
этот метод не вызывает запроса данных, а только получает список полей. Также в
этот момент “за сценой” устанавливается и первичный ключ — без этого невозможно
определить записи, для которых выполняется значение DELETE или UPDATE. Хотя
вы можете принудительно установить его через метод setPrimaryKey () —но
лучше этого не делать, а правильно создавать таблицы, т.е. всегда с ключом
PRIMARY KEY. Также вам доступен и унаследованный метод setQuery (), но в дан-
ном контексте лучше им никогда не пользоваться.
После вызова setTable () и перед началом реальной работы с данными нужно
позаботиться еще о некоторых деталях. Весьма важно установить режим редакти-
рования, который определяет, когда данные будут реально переноситься в базу
данных. Метод setEditStrategy() принимает три возможные константы:
OnFieldChange, OnRowChange или OnManualSubmit. Это позволяет вносить изме-
нения синхронно: при занесении данных в таблицу, при переходе на другую строку
или при программном подтверждении командой submitAlK) соответственно.
В последнем случае вы можете отменить все изменения, вызвав метод revertAll ().
Какая стратегия вам больше подходит — трудно сказать, но традиционно раз-
личные системы визуального представления баз данных придерживаются проме-
жуточной стратегии, а именно OnRowChange. Для этого варианта специально суще-
ствуют методы submit () и revert () — они позволяют закрепить или отменить
изменения еще до перехода на другую строку. Для других стратегий эти методы
ничего не выполняют.
Для проверки данной строки на “чистоту”, т.е. совпадает ли она с записью в базе
данных, существует метод isDirty (int). Точнее, этот метод возвращает значение
true как раз в случае, если строка уже изменена, но еще не синхронизирована.
Также, кроме стратегии, вы можете и должны указать дополнительные пара-
метры фильтрации записей с помощью метода setFilter () и указать порядок их
следования посредством метода setsort (). Эти параметры эффективно приводят к
модификации запроса SELECT, служащего для выборки данных, и могут серьезно
повлиять на реактивность вашего приложения.
Наконец, после всех подготовительных действий вы выполняете метод select ()
для загрузки стартовых данных из таблицы — после этого модель сама отслежива-
ет изменения в данных и в нужный момент синхронизирует их в соответствии с вы-
бранной вами стратегией обновления.
Вы можете вставлять или удалять записи с помощью метода insertRows () или
removeRows (), и все это найдет отражение в данных на диске. Правда, это не со-
всем корректно — в таблице вы оперируете абсолютными позициями строк, и в за-
висимости от режимов фильтрации и сортировки там могут находиться произволь-
ные записи с непредсказуемыми первичными ключами. Удаляйте записи хотя бы
по одной, с помощью метода removeRow (), чтобы держать под контролем то, что
вы делаете.
Существуют также функции низкого уровня, insertRowIntoTable () и
deleteRowFromTable (), действующие в обход выбранной стратегии обновления.
Пользоваться ими не рекомендуется, но иногда приходится — например, вы соз-
даете запись в справочнике для удовлетворения требований целостности, но она
становится ненужной из-за того, что пользователь отказывается от ввода. Такие
записи можно удалять, независимо от решения пользователя об “откате” операции.
Наконец, на самой вершине этих и без того сложных программных компонентов
стоит класс QSql Ra ti опа ITabl eModel. Как понятно даже из названия класса, он
обещает быть серьезным. На самом деле в плане интерфейса этот класс добавляет
только одно небольшое свойство relation (), т.е. связь, устанавливаемую методом
setRelation(). После установки связи новое значение из справочника просто за-
меняет вторичные ключи в табличном представлении.
Тип связи обусловлен классом QSqlRelation, который хранит сведения о том,
в какой таблице (справочнике) искать вторичные ключи, какое поле использовать
в этом качестве и какое поле использовать в качестве “display column” (отобра-
жаемый столбец). В терминах хэш-таблиц тот же смысл выражается значительно
проще, но мы отложим обсуждение проблем SQL до следующей книги.
На этом мы завершаем рассмотрение поддержки баз данных в Qt 4. Как уже не
раз было сказано, по сравнению с Qt 3, в новой версии не стало доброго десятка
классов различной степени полезности. С другой стороны, оставшиеся механизмы
позволяют проделать с данными любые представимые операции, возможно, с не-
много большим дополнительным кодированием. Если говорить о традиции, то ла-
коничность кода и интерфейсов— это очень в духе Unix, так что компания
Trolltech, по-моему, на верном пути.
Глава 10
Модуль XML
Мы завершаем “обязательную” часть Qt обзором модуля XML (Extensible
Markup Language — расширяемая спецификация языка, предназначенного для
создания страниц WWW). Освоение этой технологии, наряду с умением формиро-
вать эргономичный интерфейс пользователя, знанием основ сетевых интерфейсов и
навыками работы с базами данных, составляет тот абсолютный минимум знаний,
который необходим прикладному разработчику. Кроме того, XML является тем
общим знаменателем, который позволяет вашим программам взаимодействовать с
любым другим кодом, написанным для любой другой платформы.
К моей большой радости, Qt 4 не внес никаких изменений в обработку XML по
сравнению с Qt 3 — даже в таких обстоятельствах XML проявляет себя как оплот
стабильности и преемственности кода.
Как известно, обработка XML обычно производится одним из двух методов: SAX
или DOM. Первый метод, SAX (Simple API for XML), основан на событиях и
обратных вызовах. Примитивными операциями SAX являются регистрация
обработчиков таких событий, как “открывающий XML-тэг получен”, “обра-
батывается секция DTD” и т.д. Согласно SAX-концепции, XML представляется
непрерывным потоком данных, в частности тэгов, их значений и атрибутов. SAX
представляет собой открытый “драфт”, и его реализации могут значительно варь-
ироваться. В качестве отправной точки Qt использует реализацию SAX в языке
Java, который является одним из самых распространенных сетевых инструментов
и источником многих сетевых стандартов де-факто.
Второй метод доступа к данным XML, DOM (Document Object Model), представ-
ляет XML-документ как древовидную связанную структуру объектов. Qt поддер-
живает версию DOM Level 2, имеющую статус W3C Recommendation. Мы рассмот-
рим оба метода доступа, реализованные в Qt.
10.1. Классы, реализующие SAX2
Как уже было сказано, SAX— это метод доступа, основанный на событиях.
Приложение регистрирует свой главный обработчик-диспетчер, также часто назы-
ваемый “читателем” (SAX Reader). Когда во входной поток читателя поступает что-
то “интересное”, например открывающий или закрывающий тэг XML, возникает
SAX-событие и ваша программа получает управление. Данные, вызвавшие событие,
будут переданы в качестве параметров. Преимущества такого подхода — возмож-
ность обработки XML-потоков любой, в том числе и бесконечной, длины. Недоста-
ток — необходимость хранения состояния входного потока в вашей программе для
контроля структуры документа. Для сложных документов обработка может потре-
бовать сложного рекурсивного кода.
Главным классом, предоставляющим интерфейс “читателя” SAX, является
QXmlReader. Класс представляет собой скорее интерфейс, т.е. имеет ряд интер-
фейсных “разъемов”-указателей на объекты, которые после подключения (т.е. ре-
гистрации и установки) в дочерних классах будут выполнять те или иные действия
в ответ на предопределенные события. В частности, для полной обработки произ-
вольного XML-документа вам понадобятся собственные версии (подклассы) таких
подключаемых классов.
QXmlDTDHandler. Вызывается, когда “читатель” выполняет DTD-обработку
(Document Type Definition — определение типа документа или описание
шаблона документа). Поддерживаются только определенные в SAX поля,
а именно: определения нотаций и нераспознанных сущностей. Соответст-
вующие методы обратного вызова называются QXmlDTDHandler: :
notationDecl() и QXmlDTDHandler::unparsedEntityDecl(). В качестве
параметров, как уже было сказано, передаются данные, вызвавшие событие
(каждое событие сопровождается различными данными, поэтому не будем
каждый раз перечислять параметры). Еще раз напоминаю, что рассматри-
ваемые классы представляют собой только интерфейсы, не выполняющие
никакой обработки. Вы должны создать свой подкласс и для выполнения ре-
альной работы переопределить все чисто виртуальные методы.
QXmlDeclHandler. Вызывается как реакция на декларации, в том числе опре-
деления атрибутов, внешних и внутренних сущностей. Соответствующие мето-
ды называются QXmlDeclHandler: :attributeDecl (), QXmlDeclHandler::
internalEntityDecl() и QXmlDeclHandler::externalEntityDecl().
QXmlEntityResolver. Если вы хотите сделать собственную обработку (разре-
шение) внешних сущностей, переопределите метод QXmlEntityResolver::
resolveEntity().
QXmlCont ent Handl er. Основной обработчик входного потока, воспринимает
такие события, как QXmlCont ent Handler: : startDocument (), QXmlContent
Handler::startElement(), QXmlContentHandler::endElement() и т.д.
В отличие от более интеллектуальных классов, этот обработчик также полу-
чает события от “читателя” по таким “техническим поводам”, как возникно-
вение событий characters () или даже ignorableWhitespace (), в качестве
реакции на получение фрагмента символов или даже подстроки пробельных
символов, которые обычно игнорируются. Также сообщается о пропущенных
фрагментах, для которых “парсер” (анализатор) не может найти DTD-
опеределение. Существует реализация обработчика “контента” (содер-
жимого), класс QXmlDe fault Handler, на котором вы, предположительно,
должны базировать свои подклассы обработчиков самого нижнего уровня.
Класс QXmlContentHandler позволяет также устанавливать и изменять поло-
жение (обычно это делается автоматически) так называемого локатора, отве-
чающего за текущее положение указателя “парсера” в разбираемом документе.
Для этого служит метод QXmlContentHandler: : setDocumentLocator ().
QXmlErrorHandler. Когда “читатель” обнаруживает ошибку, то будут вы-
званы методы данного обработчика. В качестве параметра методы получают
экземпляр типа QXmlParseError с информацией об ошибке. Существует (в
терминах XML, как определено в п.1.2 спецификации) три уровня ошибок:
восстановимые, невосстановимые, после которых разбор документа должен
быть прерван, и предупреждения. Этим ошибкам соответствуют вызовы
QXmlErrorHandler::error(), QXmlErrorHandler::fatalError() и
QXmlErrorHandler: : warning (). Если обработчик возвращает значение
true, то “парсер” будет пытаться продолжать обработку даже после ошибки.
После фатальной ошибки, впрочем, реальный разбор прекращается — вы бу-
дете получать только технические события “контента”, а также повторные
фатальные ошибки. Поэтому нормальная реакция на фатальную ошибку —
завершение разбора и возврат значения false в вызывающий код “чита-
теля”.
QXmlLexicalHandler. Воспринимает такие события уровня документа, как
QXmlLexicalHandler: : startDTD () или QXmlLexicalHandler: : startCDATA ().
Никакие реальные данные в этих обработчиках не обрабатываются, они слу-
жат только для переключения режимов ввода, например “парсер перешел в
режим CD АТА”. Реальная работа осуществляется в таких специальных об-
работчиках, как QXmlDTDHandler или QXmlDeclHandler.
Собственно подключение вашего обработчика производится рядом однотипных
методов, например QXmlReader: : setDTDHandler () и т.д. Также можно получить
указатель на любой обработчик. Например, на обработчик элементов данных это
можно сделать с помощью метода QXmlReader: : contentHandler ().
Существует базовая реализация QXmlReader-методов в виде класса
QXmlSimp 1 eReader. Этот класс имеет все основные средства для разбора XML-
потока, хотя и не обеспечивает никаких средств верификации.
Надо учитывать, что многие события возникают в зависимости от настроек, за-
данных через метод QXmlReader: : setFeature (). Свойства, в основном, касаются
использования пространств имен, но также управляют генерацией некоторых со-
бытий (см. описание функции для получения полной информации).
Также нужно иметь в виду, что многие события просто заблокированы вне
“правильной” лексической зоны. Например, обработчик QXmlDTDHandler будет полу-
чать сигналы только внутри блока QXmlLexicalHandler: : startDTD()/endDTD().
Аналогично вне блока QXmlContentHandler: : st art Document () /endDocument ()
никакие содержательные события вообще не генерируются.
Еще одно “но” касается правильной классификации событий. Например, непра-
вильно заданный идентификатор тэга будет приводить скорее к ошибке, чем к генера-
ции события начала лексической зоны, например события QXmlLexicalHandler: :
startDTD(). Аналогично, если сущность не может быть квалифицирована с помо-
щью доступных DTD-данных, то событие QXmlContentHandler:: skipped
Entity () будет генерироваться скорее, чем QXmlLexicalHandler:: start
EntityO. Как известно, заданное вами разрешение внешних сущностей можно
определить в классе-потомке QXmlEntityResolver.
С методом SAX связано еще несколько дополнительных классов, например
QXml Input Source. Последний является ультимативным поставщиком входного
потока для объектов класса QXmlReader и всех его потомков. Большое преимуще-
ство этого класса — автоматическое распознавание кодовой страницы входного по-
тока с помощью как явных, так и косвенных признаков, например байт-префикса
файлов в формате Unicode. Вы можете явно подавать данные на вход, задавая их
через метод QXml Input Source: : setData (), или же просто связать источник дан-
ных с устройством класса QIODevice. Читатель осуществляет доступ к следующе-
му символу входного потока, независимо от его происхождения, с помощью метода
QXmlInputSourсе::next().
Как видите, Qt старается и умеет делать сложные вещи простыми.
10.2. Реализация объектной модели D0M
Теперь рассмотрим другой, высокоуровневый, метод доступа к данным XML —
D0M. Идея метода — создавать реальные объекты (экземпляры) для каждой сущ-
ности, атрибута или данных и организовывать их в древовидные связанные струк-
туры в соответствии со структурой документа.
Естественно, что создание полной структуры D0M, дублирующей все данные
(с учетом накладных объектных расходов), требует достаточного объема памяти.
Кроме того, когда нас интересует один или несколько элементов из большого набо-
ра данных, разбор структуры и создание объектов являются пустой тратой време-
ни. К тому же обход DOM-представления в поисках нужного элемента представляет
собой значительно более трудоемкую рекурсивную процедуру, которой можно из-
бежать при последовательном доступе SAX.
Главным базовым классом D0M является QDomNode. Несмотря на большое ко-
личество методов, класс весьма прост. Главными двумя активными атрибутами DOM-
узла является его тип и список дочерних узлов. Тип экземпляра класса QDomNode опре-
деляется на основе специального поля, т.е. реализована типизация времени выпол-
нения, RTTI (вспомните аналогичную технику при реализации графических при-
митивов). Метод nodeType () возвращает тип узла. Также существует две группы
классов, связанных с типами узлов: методы is* (isAttrO, isCDATASection () и
так далее) (всего их 13) проверяют принадлежность узла к заданному типу. Методы
класса to* (toComment (), toDocumentO и так далее) приводят экземпляр к за-
данному типу более высокого уровня. Это довольно необычная техника классообра-
зования, когда суперкласс не только знает о своих потомках, хранит их RTTI-
данные, но и самостоятельно выполняет кастинг к дочерним классам.
Список дочерних узлов обслуживается рядом методов, выполняющих, напри-
мер, очистку списка, проверку наличия в нем элементов, различные варианты
вставки, удаления и замены элементов. Существуют определенные правила, по ко-
торым элементы могут быть подчинены один другому. Так, документ верхнего
уровня состоит из типа документа, комментария, инструкций и одного или не-
скольких элементов. Тип документа, например, не может содержать дочерних эле-
ментов и т.д. Мы не будем рассматривать все варианты, но вы должны иметь это в
виду.
Также существует и указатель на родительский элемент, доступный с помощью
метода parentNode (). Естественно, он имеет значение только для подключенных
к родительскому узлу экземпляров, хотя никто не запрещает вам создавать и
“отдельно стоящие” узлы. Также, благодаря ссылке на родительский узел, можно
получить ссылки на предыдущий или последующий, если таковые имеются, сест-
ринский узел (узел того же уровня).
Другие два базовых, но менее активных атрибута узла — его имя и значение —
возвращаются методами nodeName () и nodeValue (). В отличие от типов и списка
дочерних элементов, эти значения не предоставляют большого поля для фантазии:
для них определены только операции чтения и записи (только для значения).
Имена важны для таких объектов, как атрибуты или тэги. Вместо узлов, имя ко-
торых не имеет большого смысла, используется фиктивная строка, например
"#document" (рис. 10.1).
кЗ QT Resources
® Ф Trolhech Partners
£) ^CommunityResources
Qtf'orum org
Q The independent Qt Tutorial
Q French PROG.Qt
Q German Qt Forum
0 Korean Qt Community Site
Russian Qt Forum
{ft) Digitalfanatics: The QT4 Resource Ce.
Q QtQuesdons
d Qt Quarterly
TroHtech’s home раде
Q Qt4 0 documentator*
Frequently Asked Questions
E (^Online Dictionaries
Q Dictlonary.com
Ц) Merriam-Webster Online
£5 Cambridge Dictionaries Online
Ц) OneLook Dictionary Search
http-.Ztarww.qtforum.org/
htfcj^»ww.digitalfanaSCS.org#rofocts/ql_tutorial/
http.-^>rog.qtfree.fr/
http^www .qtfomm.de/
hop^WwwJioronejieV
httpj5prog.org.ru/fonim/fon«n_14 html
httpjWM.dlgitalfanartcs.org/
http;Ztarww.qtque stions.org/
lrttp^doceollied).com/(|q/
http4Mrww.troMech.com/
hty4tioc.troltech<com/4-0/
hep ^www.tro the ch.com/developer^aq*/
http/Mrww.dictiona ry.com/
http jn-w.com/
httpjWlctfonary.cambddge.org/
Imp jfMrww.onelook.com/
Puc. 10.1. Собственно, no полученным результатам
невозможно сказать, какой из двух методов XML-разбора
был использован, однако внешне древовидное представление
ассоциируется с DOM-методом
От класса QDomNode происходят все остальные типы узлов. Каждый из них,
кроме перечисленных общих характеристик, обладает своими специфическими
методами. Например, метод QDomText:: split Text () разбивает текст на два
фрагмента, заменяя один узел текста двумя.
Особую роль играет класс QDomDocument: поскольку все дочерние узлы имеют
смысл только в контексте документа, этот класс располагает методами (фабри-
ками), создающими элементы всех остальных типов и вызываемыми после выпол-
нения соответствующих конструкторов. Эти новые элементы автоматически не
включаются в иерархию, как можно было предположить, — для вставки нового
узла в нужное место иерархии необходимо явно вызывать метод appentChild(),
insertBefore() или insertAfter().
Три самых распространенных типа узлов — QDomElement, QDomAttr и QDomText.
Последний представляет собой, .собственно, данные, связанные с элементом или
атрибутом. Получить все данные (текст), относящиеся к некоторому узлу, можно,
опрашивая все вложенные узлы и пытаясь привести их к типу QDomText с помо-
щью toText-преобразования. Обратите внимание, что это совсем не то же самое,
что toString-преобразование.
QString text;
QDomElement element = doc.documentElement();
for( QDomNode n = element.firstChild(); !n.isNull();
%> n = n.nextsibling()){
QDomText t = n.toText();
if ( Jt.isNulK) )
text += t.data();
}
He все классы-потомки QDomNode имеют одинаковый DOM-статус. Так, класс
QDomFragmentNode вообще не предусмотрен никакими моделями и представляет
“поддокумент” внутри документа, что в общем случае нарушает саму идею DOM.
Это пример “оптимизации” для борьбы с громоздкостью DOM-подхода.
В заключение просто перечислим все потомки класса QDomNode с краткими
комментариями.
QDomDocument. Главный документ XML включает средства для создания
всех остальных типов узлов.
QDomDocumentFragment. Класс, представляющий поддерево главного доку-
мента, не обладает всеми характеристиками главного документа. Использу-
ется как временное хранилище нескольких узлов, например, при переносе из
одного документа в другой или при конструировании группы для одновре-
менной вставки (в режиме транзакции).
QDomDocumentТуре. Тип XML-документа, DTD. Предоставляет доступ
“только для чтения” к спискам нотаций (notations О) и сущностей
(entities ()), описанных в DTD. Остальные (неквалифицированные) эле-
менты обработаны не будут. Определения представляются специальными
структурами “именованных элементов” типа QDomNamedNodeMap. Экземпляр
QDomDocumentТуре не имеет дочерних узлов.
QDomEntity, QDomEntityReference. Эти классы позволяют представлять
сущность в явной или косвенной форме ссылки. Сущности могут содержать
списки других элементов, текста, комментариев и ссылок на другие сущности.
Ссылки, как и обычные сущности, в зависимости от реализации системных
библиотек XML, могут быть полностью заменены фактическими данными.
QDomElement, QDomAttr. Элементы и их атрибуты. Элемент имеет имя,
QDomElement: : tagName (), и некоторое число связанных атрибутов. Кроме
того, элемент может содержать другие элементы, ссылки на сущности, ком-
ментарии и другие дочерние узлы. Атрибут— это пара “имя-значение”,
которая поставляется методами QDomAttr: : name () и QDomAttr: : value ()
соответственно.
QDomProcessinglnstruction, QDomComment, QDomText, QDomCDATASection,
QDomNotation. Примитивные узлы DOM, не имеющие потомков. Соответст-
вуют таким различным лексическим областям XML, как комментарии,
текст, блоки CDATA (неразмеченные данные) и т.д. Нотации (точнее: декла-
рации нотаций) являются доступными “только для чтения” и, кроме toi*o, не
имеют родительского узла.
Итак, если вы должны использовать язык XML, то вам следует еще раз все взве-
сить перед тем, как использовать SAX или DOM в качестве метода доступа. SAX
характеризуется низкоуровневым протоколом, требующим значительных усилий
для разбора и верификации данных. Модель DOM предоставляет более высокий
уровень сервиса. Особенно незаменима DOM в случае, когда нужно обрабатывать
динамические XML-документы или вносить в них частые изменения. С другой сто-
роны, SAX позволяет без особого труда обрабатывать документы любого объема
при минимальном расходе ресурсов. Особенно заметна розница в необходимом ко-
личестве оперативной памяти. Поэтому SAX применяется во встроенных системах
с ограниченными ресурсами и при обработке очень больших потоков данных. Так-
же SAX выглядит привлекательно для разбора однотипных XML-структур, а также
в задачах типа “найти одну запись из миллиона”.
Из существенных и подробно рассмотренных в документации вопросов мы не
коснулись средств и методов Qt XML, посвященных доменам имен (пространствам
имен). Эта тема относится скорее к общей теории XML, чем к Qt, так что оставим ее
за рамками этой книги. Но если для вас это критично, то могу сказать только одно:
Qt XML поддерживает пространства имен, причем с различными диалектами.
Глава 11
Многопоточное
программирование в Qt
Мы рассмотрели главные аспекты современного программирования в терминах
Qt, а именно графический интерфейс пользователя, сетевые возможности и доступ
к базам данных, а также методы разбора документов XML. Теперь мы переходим к
нефункциональным возможностям, которые, тем не менее, могут значительно по-
влиять на стиль вашего программирования.
Начнем со средств многопоточного программирования, а также механизмов
межпроцессорного взаимодействия, или IPC (interprocess communication). Мно-
гопоточное программирование более свойственно серверным приложениям и в
меньшей степени — клиентским. Но поскольку современные пользователи пред-
почитают работать в режиме “power user” (т.е. с повышенными требованиями к
вычислительной системе) на таких серверных платформах, как Windows NT5,
или персональных станциях Linux, то вы можете с выгодой использовать много-
поточность даже в традиционно пользовательских приложениях.
Основой распределения процессорного времени в современных ОС, как вы знае-
те, является поток (thread). Каждое приложение состоит из одного или нескольких
потоков, один из которых считается главным. Приложение в терминах операцион-
ной системы называется процессом. И хотя кванты времени выделяются каждому
потоку, тем не менее хороший планировщик должен следить, чтобы каждое при-
ложение, даже с низким приоритетом, хотя бы изредка получало управление.
Отличие потоков от процессов в том, что все потоки работают в одном адресном
пространстве и разделяют другие ресурсы, например дескрипторы открытых фай-
лов или открытые сетевые соединения. При переключении потоков одного прило-
жения нужно сохранять совсем немного данных (например, значения регистров),
тогда как переключение на поток другого процесса представляет собой одну из са-
мых трудоемких операций (микро)ядра операционной системы.
Раз уж мы завели речь о приоритетах, то нужно отметить, что общего взгляда на
приоритеты не существует. Понятно, что есть “нормальные” потоки со средним
приоритетом. Кроме того, есть ожидающие потоки, а также потоки повышенной
важности и даже реального времени. Однако в различных системах применяются
различные стратегии для решения важной задачи: что предпринять планировщи-
ку, если высокоприоритетные задания блокируют потоки с низким приоритетом.
Возможно, потоки с низким приоритетом удерживают ресурсы, без которых не мо-
гут выполняться другие потоки, а их обработка могла бы освободить такой ресурс.
Также не совсем ясна стратегия с автоматическим изменением приоритетов.
С давних времен в Unix и даже в более старших системах разделения времени
были механизмы, позволявшие оператору изменять приоритет — как при запус-
ке задания, так и в процессе его работы. Однако иногда система сама может
“догадаться”, что данное приложение требует дополнительного приоритета.
Например, в Windows NT приоритет задания переднего плана автоматически по-
вышается на два пункта. Является ли это закономерным, когда ввод пользователем
данных имеет приоритет над работой серверных приложений? Впрочем, в настрой-
ках есть вариант, при котором плацировщик будет отдавать приоритет процессам
заднего плана.
Еще один вопрос, по которому нет общего мнения, — дилемма между внешними
и внутренними (self-process) потоками-. Потоки, реализованные на уровне операци-
онной системы, являются “внешними” — для переключения таких потоков ис-
пользуются вызовы (на самом деле — аппаратные исключения таймера) операци-
онной системы, что раньше было сравнительно дорогой операцией, поскольку пе-
реход из пользовательского режима приложения в защищенное “кольцо” (ring 0)
вызывает экстенсивное сохранение состояния, известное также как переключение
контекста. Если ваше приложение состоит из сотен процессов, то вы можете ис-
пытывать замедление работы как из-за накладных расходов переключения контек-
ста, так и из-за расхода системных дескрипторов, связанных с каждым процессом.
В последнее время эти вопросы, в основном, утратили актуальность: дефицит
дескрипторов был преодолен, а переключение контекста встроено в аппаратную
часть микропроцессоров. Тем не менее существуют решения в виде библиотек,
которые реализуют “конфиденциальные” потоки. Основная идея состоит в сле-
дующем. При получении управления программа сама решает, какому внутреннему
потоку следует передать управление. Такие современные ОС, как Linux и MS
Windows, оперируют системными потоками, поэтому и Qt использует эти механиз-
мы. Это согласуется с принципом SPOT (Single Point of Truth одни и те же меха-
низмы должны быть реализованы одинаковым образом).
Вернемся к Qt. Потоки в этой библиотеке реализуются так же просто, как в
Java, хотя и являются внешними (в отличие от встроенных потоков Java). Для реа-
лизации потока вы должны построить свой класс на основе класса QThread. Обра-
тите внимание на то, что, в отличие от главного потока, описываемого классом
QCoreApplication, вы не можете в своих потоках создавать или обрабатывать
элементы пользовательского интерфейса.
Главной функцией (точкой входа) для потока является метод run () — все, что
вы запишете в этот метод, и будет составлять работу вашего потока. После того как
вы создадите экземпляр своего потока в приложении, для запуска следует вызвать
метод start (). В качестве параметра start () воспринимает приоритет потока —
от IdlePriority до TimeCriticalPriority. По умолчанию используется кон-
станта Inherit Priority, которая означает “унаследовать приоритет от создающе-
го потока”.
Небольшое замечание: хорошим стилем программирования считается устанав-
ливать для своих потоков (особенно тех, которые не обрабатывают ввод данных
пользователем) по возможности наиболее низкий (слабый) приоритет. С другой
стороны, не все системы корректно ведут себя с низкоприоритетными потоками:
возможно, имея самый низкий приоритет, ваш поток вообще перестанет получать
управление и не сможет, например, обслуживать сетевое соединение.
Когда метод run () возвращает управление, работа всего процесса завершается.
Другим, принудительным, способом завершить выполнение является “внешний”
метод terminate (). Все изменения жизненного цикла потока вполне описываются
тремя сигналами: started (), finished () и terminated ().
Конечно, выполнить ряд действий в “параллельном мире” — это уже что-то,
но не совсем. В асинхронном, управляемом событиями окружении, таком как Qt,
иногда нужно не просто создать объекты и выполнить с ними действия, но и дож-
даться от них некоторых сигналов. По аналогии с самим приложением в каждом
потоке существует своя очередь событий и свой главный цикл. И если метод
QThread: : run() соответствует функции main () в рамках всей программы, то ме-
тод ехес () также служит для входа в главный цикл обработки сообщений (главный
для данного потока). Общая картина такова: вы входите в run (), создаете объекты,
инициализируете обработку сообщений (сигналов) через метод connect () и вызы-
ваете метод ехес (). При этом создается внутренний объект типа QEventLoop, ко-
торый также получает команду ехес ().
Несмотря на существование различных очередей для каждого из потоков,
объект класса QObject может принимать и получать сигналы из любого потока
внутри приложения. Метод connect () достаточно “осведомлен”, чтобы отличать
связь сигналов и слотов из разных потоков и передавать такие сигналы через оче-
редь главного цикла событий. Сигналы по умолчанию передаются напрямую, если
слот и сигнал находятся в одном потоке, и через очередь событий — если в разных.
Вы можете даже изменить это поведение через опциональный параметр метода
connect (), хотя для этого нужны веские основания.
Если вы не завершаете поток по методу terminate (), то выхода из цикла сооб-
щений два: более содержательный метод exit (int) позволяет вернуть код возвра-
та, где нуль по умолчанию обозначает “успех”. Вариант exit (0) можно заменить
вызовом слота quit () — этот метод просто всегда возвращает успешное заверше-
ние. Эти методы вы должны вызвать где-то в теле обработчиков событий. Не путай-
те метод exit () класса QThread со стандартной С-функцией exit () — последняя
завершает все приложение и больше не возвращает управление.
Возникает закономерный вопрос: как обработчик событий в произвольный мо-
мент получает указатель на поток, например, чтобы вызвать функцию exit () для
завершения работы? Вопрос закономерный— для этого существует статический
метод QThread: : currentThread (), который как раз этот поток и возвращает.
Наконец, в обычном случае поток завершает сам себя изнутри и возвращает ре-
зультат. Когда поток завершился, его isFinished() -значение становится равным
true, и вы можете разрушить экземпляр класса QThread. На работающем потоке
это, вероятно, приведет к краху приложения.
Еще один способ синхронизации потоков, подобный pthread_join (), реализу-
ется с помощью метода wait О . Вызывая этот метод из потока-родителя, вы пере-
водите его в режим ожидания дочернего потока в течение заданного количества
миллисекунд, а возможно и бесконечно. Применяйте этот метод своевременно, так
чтобы не блокировать все приложение.
В заключение, говоря о вызове методов в другом потоке, нужно еще раз вспом-
нить уже где-то упоминавшийся метод deleteLater (). Этот метод класса
QObject, наследуемый всеми объектами, служит для безопасного разрушения
объекта в другом потоке. Если вы просто вызовете метод delete (), то, возможно,
объект никогда не обработает сигналов, которые уже стоят в очереди на обработку.
Чтобы обеспечить их надлежащую обработку, разрушать объекты в других потоках
необходимо только с помощью метода deleteLater () — при этом разрушение бу-
дет произведено после обработки всех событий в главном цикле.
11.1. Объекты синхронизации
Как уже было сказано, отличительное свойство потоков одного приложения со-
стоит в том, что они разделяют общее адресное пространство, т.е. объекты могут
вызывать методы друг друга из разных потоков. Я бы очень рекомендовал для этих
целей пользоваться передачей сообщений через цикл обработки и очередь сообще-
ний, а не вызывать методы напрямую. Но, тем не менее, вы можете считать ина-
че— в таком случае вам нужно гарантировать реентерабельность и потоковую
безопасность ваших объектов. Для синхронизации и корректной работы объекта в
многопоточном окружении служат такие объекты синхронизации, как мьютексы,
семафоры и ожидающие конструкции.
Самым простым классом этой категории является QMutex с тремя бесхитрост-
ными методами lock (), tryLock () и unlock (). Если в какой-то момент времени
любой блок кода вызывает метод lock (), то до вызова метода unlock () все после-
дующие lock ()-вызовы будут вызывать блокировку потока. Метод tryLock О
слегка смягчает ситуацию — блокировка не возникает; при этом, если существует
возможность захвата мьютекса (т.е. если мьютекс еще не принадлежит другому по-
току) — она будет реализована.
На мьютексы похожи и семафоры QSemaphore. Разница — в количестве воз-
можных захватов ресурса. Семафор создается с некоторым положительным значе-
нием, еще называемым количеством блокировок в семафоре. При каждом захвате с
помощью метода acquire () из этого числа вычитается единица или другое ука-
занное в параметре число. Если при следующем захвате значение оставшихся бло-
кировок становится меньше нуля, то очередной вызов метода aquire () приведет к
блокировке потока. Возврат в семафор осуществляется методом release () — он
позволяет вернуть более одной блокировки. Этот же метод может выполнять и
креативную роль: если вы вернете больше, чем взяли, то “запас” блокировок в се-
мафоре соответственно возрастет.
Узнать, сколько блокировок еще осталось в семафоре, можно с помощью метода
available (). Неблокирующий метод tryAquire () осуществляет захват только в
случае, если это возможно, и возвращает значение false в противном случае.
Семафоры широко используются при распределении фиксированного числа ресур-
сов — буферов, каналов и, как ни странно, самих потоков.
Более интересным и гибким является класс QReadWr it eLock, позволяющий
реализовать механизм “один писатель — много читателей”. Этот класс, к слову, не
имеет аналогов в операционной системе, хотя ясно, что на самом деле все построено
на очереди мьютексов. Алгоритм этого класса таков: многие потоки могут блоки-
ровать объект для чтения с помощью метода lockForRead(), без особых последст-
вий друг для друга. Когда появляется запрос на метод lockForWrite (), то он
попадает первым в очередь на блокировку: другие запросы на чтение теперь блоки-
руются и уже не приносят успеха, а выстраиваются в очередь (на самом деле — это
просто пул запросов, поскольку порядок удовлетворения блокировок не определен
и зависит от системы).
Как только последний “читатель”, уже владеющий блокировкой, разблокирует
объект — тут же происходит блокировка для записи. С этого момента до вызова
метода unlock () никакой другой поток не получает доступа к объекту. Другие за-
просы на запись попадают в начало очереди, так что несколько записывающих
блокировок могут бесконечно долго блокировать запросы на чтение (как обычно,
возможны варианты вызовов tryLockForRead() и tryLockForWrite (), которые
не блокируют поток).
Поскольку QReadWriteLock-объект намного более содержательный, чем
QMutex-объект, рекомендуется использовать именно его. Множественное чтение и
эксклюзивная запись распространены гораздо больше, чем другие ограничения
доступа. Небольшое предупреждение: в отличие от мьютексов и семафоров, разбло-
кирование методом unlock () незаблокированного объекта типа QReadWr it eLock с
большой вероятностью приведет к краху программы.
Наконец, последним типом синхронизации являются “ожидания”. Мы уже
сталкивались с ожиданием одного потока другим в виде метода QThread: .-wait ().
Другим (похожим) вариантом является объект класса QWaitCondition. Экземп-
ляр этого класса используется для схемы “один поток управляет множеством пото-
ков”.
Для начала поток-“менеджер” создает объект типа QWaitCondition и передает
в дочерний поток. Рабочий поток создает мьютекс, блокирует его и в защищенной
области обрабатывает данные.
После того как рабочий поток оказывается без текущего задания, он вызывает
метод wait (QMutex*, unsigned long) с параметрами блокированного мьютекса
(мьютекс не должен быть рекурсивным). При этом рабочий поток переходит в со-
стояние ожидания, а мьютекс сбрасывается, давая понять, что поток ожидает ин-
струкции к действию и, в частности, не обращается к каким-либо данным.
Когда менеджер получает данные на обработку, он вызывает либо метод
wakeOne () (чтобы запустить один произвольно выбранный поток), либо метод
wakeAHO для запуска всех потоков разом (порядок запуска считается случай-
ным). В результате метод wait () возвращает значение, а соответствующий мью-
текс снова переходит в захваченное состояние “busy”.
Класс QWaitCondition удобно использовать для построения серверных прило-
жений по принципу заказчик-исполнитель, а также для рассылки одного события
нескольким потокам. Но имейте в виду, что пока поток не вошел в wait()-
ожидание, любые вызовы wake* () не будут иметь на него никакого влияния.
Поэтому, если вы хотите, чтобы все потоки разом обработали ваш wakeAllO-
запрос, придется отслеживать количество работающих потоков, например, с по-
мощью семафора и ожидать, пока их количество не станет равным нулю, после чего
можно использовать проверку if (! semaphore->tryAcquire () ) в качестве при-
знака “можно действовать” и выдавать команду wakeAll ().
К слову, в Windows NT, в отличие от других моделей многопоточной обработки,
ожидания представляют собой очень мощный механизм со множеством полезных
вариаций. Не будет преувеличением сказать, что большая часть потоков Windows
большую часть времени проводит в ожидании. Но вот вопрос: в ожидании чего?
Глава 12
Программирование для различных
платформ
Главная цель, поставленная разработчиками Qt, — создание универсальной
библиотеки классов, не зависимой от системных вызовов, операционной и файло-
вой системы. Тем не менее существуют особые случаи, когда приходится выходить
за рамки универсального кода и создавать модули, ответственные за контакт с кон-
кретным системным API.
12.1. Программирование в среде ActiveX:
создание сервера
Конечно, вы будете стремиться избегать взаимодействия с системой в процессе
создания приложений под управлением Qt. Однако вы не сможете игнорировать то
количество приложений, которые созданы для среды Microsoft Windows. Как след-
ствие, вы или, точнее, ваш заказчик захочет видеть в качестве опции взаимодейст-
вие с источниками данных или модулями ActiveX.
Как известно, эта технология является вариацией на тему клиент/сервер. Каж-
дое приложение может быть сервером, клиентом (контейнером) или тем и другим
одновременно. Серверы, дополнительно, подразделяются на DLL-серверы
(dynamic-link library — динамически подсоединяемая библиотека), работающие в
адресном пространстве клиента, и ЕХЕ-серверы (executable — исполняемые), рабо-
тающие в собственном адресном пространстве (процессе). Соответственно, Qt реа-
лизует как функции сервера ActiveX обоих типов, так и клиента. Приложения
ActiveQt могут компилироваться (линковаться) с библиотекой Qt как статически,
так и в виде внешнего модуля DLL.
Создание серверов ActiveX реализовано в Qt в виде внешней библиотеки
qaxserver и специальных ключей конфигурации. Вот как выглядит создание ехе-
сервера в файле проекта . pro:
TEMPLATE = арр
CONFIG += qaxserver
RC_FILE = qaxserver.rc
Для создания dll-сервера потребуется небольшая модификация:
TEMPLATE = lib
CONFIG += qaxserver dll
DEF_FILE = qaxserver.def
RC-FILE = qaxserver.rc
Упоминаемые файлы определений и ресурсов поставляются с дистрибутивом.
Можно скопировать их в каталог приложения и внести в них небольшие измене-
ния, не меняя уже определенные интерфейсы. Ключ qaxserver добавит в процесс
сборки линковку с библиотекой qaxserver. lib. Эта библиотека должна быть
предварительно собрана вами вручную— код находится в QTDIR/extensions/
activeqt/control. Зайдите в этот каталог и выполните команду qmake+make.
Результирующий файл окажется в каталоге библиотек, по умолчанию —
BQTDIR/lib.
В результате применения ключа qaxserver будут внесены такие изменения в
процесс сборки: вместо библиотеки qtmain.lib будет использована уже упоми-
навшаяся qaxserver. lib, приложение запустится с ключом dumpidl для генера-
ции IDL-файла описания интерфейсов и MIDL (компилятор IDL фирмы Microsoft)
скомпилирует эти заголовки в библиотеку типов. На NT-системах результирующая
библиотека может быть присоединена к исполняемому файлу в виде двоичного ре-
сурса. Дополнительно новый сервер будет тут же зарегистрирован на вашей системе
(приложение запускается с ключом regserver), так что вы можете непосредствен-
но начинать тестирование. Под старыми системами 95/98/Ме последняя операция
не работает, но собранный под управлением NT сервер будет работать и на этих сис-
темах. Конечно, я не хочу верить, что вы все еще разрабатываете приложения под
Windows 98.
Фактически, это все, что вам нужно. Все остальное делает Qt: при определении
открытого (public) класса, например, происходящего от класса QWidget, вы соз-
даете его обычным способом, добавляя новые свойства с помощью макроса
О PROPERTY, а также создавая слоты и сигналы. Qt автоматически сгенерирует не-
обходимый COM-интерфейс, заменяя слоты и сигналы методами и событиями СОМ
(Component Object Model — модель компонентных объектов Microsoft). Для
свойств и параметров будут автоматически приведены типы в рамках совместимо-
сти (см. таблицы в документации). Обратите внимание на то, что типы свойств и
параметров приводятся по различным соглашениям.
Серверы-приложения запускаются в режиме программы и, как обычно, запус-
кают на выполнение функцию main (), создают экземпляр класса QApplication и
входят в главный цикл. Однако если указан ключ (параметр командной строки)
activex, то исполняемый модуль используется как библиотека позднего связыва-
ния, т.е. как сервер. Все это делается автоматически, как результат линковки с
другой библиотекой. Вы можете, однако, проверить режим запуска, опросив свой-
ство QAxFactory::isServer().
При установке вашего сервера нужно зарегистрировать его на каждой целевой
машине. Для ехе-варианта вы просто вызываете исполнительный файл с ключом
regserver, а для DLL используйте шаг регистрации с помощью утилиты
regsvr32. exe. He забудьте также все остальные нужные файлы (в частности, файл
qt. dll) разместить в доступных для запуска местах.
Из модуля QAxServer доступны три класса: QAxFactory служит для определе-
ния того, какие ActiveX-интерфейсы могут быть получецы с помощью данного
сервера. Кроме этого, этот класс располагает средствами для обеспечения работы
приложения в качестве сервера (например, isServerO и startserver ()). Для
экспорта данных о вашем сервере существует две возможности — переопределение
методов класса QAxFactory или использование макросов Q С LASS INFO: последний
способ является более понятным и, следовательно, предпочтительным.
Следующий класс, позволяющий открыть свойства вашего класса для внешнего
воздействия, — QAxBindable. Если ваш элемент управления типа QWidget желает
предоставить свои свойства внешней среде (клиенту), то вы также должны сделать
его QAxBindable-подклассом. В процессе, в основном, участвуют два метода:
requestPropertychange() и propertyChanged (). Оба метода вызываются из
write-метода для свойства, например, так:
void MyActiveQt::setText(const Qstring &text) {
if (!requestPropertychange("text")) return;
propertyChanged();
}
Наконец, класс QAxAggregated позволяет создавать новые AxtiveX-интерфейсы.
Для определения нового интерфейса вы должны создать QAxAggregated-подкласс и
определить в нем метод queryinterface () для возврата запрошенного интерфей-
са, если он реализован. Вот как выглядит этот метод.
long Axlmpl::queryinterface(const QUuid &iid, void **iface) {
*iface=0;
if (iid==IID_ISomeComIface) *iface=( ISomeComlface*) this;
else return E_NOINTERFACE;
AddRef();
return S_OK;
}
Обратите внимание на вызов AddRef (): как любой другой COM-интерфейс, ваш
также должен поддерживать интерфейс IUnknown. COM-интерфейс IUnknown реа-
лизует ту же логику подсчета количества ссылок, что и класс QPointer, и состоит
из трех вызовов: Query Interface, AddRef и Release. В своем коде вы должны де-
легировать эти вызовы в стандартный IUnknown-интерфейс, доступный через вы-
зов controllingunknown (), например, так:
ulong Axlmpl::AddRef() {return controllingunknown()->AddRef();}
12.2. Создание клиентских приложений ActiveX
По аналогии с созданием серверов, вы можете создавать и клиентские приложе-
ния ActiveX. Клиентская библиотека называется QAxContainer. Различают визу-
альные и невизуальные ActiveX-компоненты: контейнером для первых служит
QAxWidget, а для вторых — QAxObject. Для использования этих классов вы долж-
ны включить в свой . pro-файл такую строку:
config+=qaxcontainer
Общим классом, служащим для инициализации COM-объектов и получения
указателей на нужные интерфейсы, служит абстрактный класс QAxBase, от кото-
рого и происходят классы QAxObj ect и QAxWidget. Нужный класс можно указать в
конструкторе QAxBase: либо с помощью его числового идентификатора UUID, либо
с помощью символического имени CLSID. Также можно инициализировать объект
полным именем, как указано в реестре, или даже именем файла, который неявно
инициализирует сервер определенного типа. В этой же строке указывается удален-
ный сервер, если объект создается не локально, имя и пароль пользователя, а так-
же ключ лицензии, если предусмотрено лицензирование использования сервера.
После получения указателя на объект вы можете явно запросить любой из ин-
терфейсов через метод queryinterface (), а также опрашивать и устанавливать
его свойства с помощью методов property () и setProperty (). Обратите внима-
ние на то, что свойства являются RTTI-элементами и указываются как строковые
константы. Аналогично можно вызывать любые методы позднего связывания, ко-
торые поставляются интерфейсом IDispatch через вызов dynamicCall (). Здесь
также имена функций передаются как строковые константы:
WebBrowser->setProperty("URL","http://trolltech.com");
WebBrowser->dynamicCall("GoHome() ") ;
Как правило, СОМ-объёкты организованы в виде сложной древовидной иерар-
хии классов, как, например, в MS Office. Для навигации по объектному дереву в
классе QAxBase предусмотрен метод query SubObject (). Работает это приблизи-
тельно так.
QAxWidget outlook("Outlook.Application");
QAxObject *session=outlook.querySubObject("Session");
if (session) {
QAxObj ect=session->querySubObj ect(
"GetDefaultFolder(OlDefaultFolders)”,
"olFolderContacts");
Класс QAxObject реализует абстрактные методы, заложенные в QAxBase, хотя
и не добавляет ничего нового. Если COM-объект является ActiveX-компонентом,
т.е. реализует динамическое связывание через интерфейс IDispatch, то свойства,
методы и события COM-объекта автоматически отображаются на свойства, методы
и сигналы QAxObj ect-объекта. Важным ограничением является невозможность
использовать макрос Q_OBJECT в дочерних QAxObj ect-подклассах, и, как следст-
вие, — невозможность добавлять свойства, сигналы и слоты. Поэтому рекоменду-
ется вместо наследования использовать агрегацию: вы создаете новый класс,
в котором расположен экземпляр класса QAxObject, которому делегируется
ActiveX-обработка.
Класс QAxWidget делает сложное возможным: он является одновременно по-
томком классов Qwidget и QAxBase и, таким образом, предоставляет QWidget-
интерфейс к ActiveX-компонентам. Этот класс сам по себе не привносит особых
новшеств: из заметных можно вспомнить метод createAgregated() для создания
новых интерфейсов или translateKeyEvent. Однако если вы вспомните все мето-
ды и свойства, предопределенные в QWidget, то станет ясно, что QAxWidget на са-
мом деле располагает внушительными возможностями.
Наконец, ActiveQt поддерживает встраивание скриптовых средств автомати-
зации, установленных в системе. Классом-посредником между экземплярами
класса QAxObject (или QAxWidget) и методами связи со сценарием (scripting host)
является класс QAxScriptManager. Выполнение “скриптов” (сценариев) обсужи-
вают всего три метода: сначала вы добавляете объекты в область видимости с по-
мощью метода addobject (), затем загружаете “скрипт” с помощью метода load ()
и выполняете его посредством метода call {) с указанием имени функции и пара-
метров. При этом “скрипт” “видит” ваши объекты и может ими манипулировать.
Сам “скрипт” является экземпляром класса QAxScript. Кроме уже упоминав-
шихся методов load() и call (), вы можете исследовать скрипт, например пере-
числить его функции, опросив метод functions (). Также доступны такие сигна-
лы, как enteredO, finishedO и stateChanged(), позволяющие отслеживать
выполнение скрипта.
Кроме прочего, объект класса QScript возвращает указатель на обслуживаю-
щий его QAxScriptEngine-объект. Этот экземпляр является как раз тем служеб-
ным кодом, который выполняет ваши скрипты. Как и следовало ожидать, сам
“энджин” ^гоже является QAxObject-подклассом. Этот объект хранит состояние са-
мого скриптового процессора, доступное через state (), диалект языка сценариев
scriptingLanguage (), а также доступ к интерфейсам посредством метода
queryinterface (). Конечно, все это предполагает, что вы знаете, какие интер-
фейсы поддерживаются и как ими пользоваться.
12.3. Взаимодействие с Motif
По аналогии со встраиванием ActiveX-компонентов в объекты Qt существуют
также классы, предназначенные для встраивания компонентов Motif. Как извест-
но, Motif — э^о набор визуальных компонентов, которые составляют основу обо-
лочки CDE (Common Desktop Environment — коллективная среда настольных вы-
числительных средств), точно так же как KDE написана на Qt. У вас нет особых
причин использовать эти классы, но если вы программируете для среды CDE
и хотите обеспечить совместимость своего приложения, то такая возможность
у вас есть.
12.4. Создание плагинов в Qt
Плагины — важный атрибут современных приложений. По сути, плагины явля-
ются библиотеками позднего связывания, реализующими специальный интерфейс,
позволяющий подключать, а иногда и отключать их во время работы приложения.
В отличие от обычных динамических библиотек, DLL, плагины могут быть уста-
новлены самим пользователем или удалены в любой момент. Как следствие — пла-
гины должны быть максимально ортогональными, т.е. один плагин никогда (или
крайне редко) не зависит от наличия другого плагина.
Различают два больших класса плагинов в Qt: плагины существующих катего-
рий и совершенно новые плагины. Первые плагины строятся на основе таких суще-
ствующих классов, как QSqlDriverPlugin или QDecorationPlugin. Ваш код до-
бавляет небольшой объем функциональности и должен реализовать стандартный
протокол, описанный в суперклассе. Плагин создает экземпляр необходимого
класса и возвращает его в приложение.
Рассмотрим, например, в качестве наиболее, наглядного плагин стилей
QStylePlagin. Конструктор этого класса никогда не вызывается непосредственно:
вместо этого существует макрос Q_EXPORT_PLUGIN (), экспортирующий точки вхо-
да вашего плагина, что позволяет создавать экземпляры по мере необходимости
основной программы. В коде плагина этот макрос нужно и можно использовать
только один раз.
Специфическая работа QStylePlagin основана на методах keys () и create ().
Первый возвращает список создаваемых стилей, а второй создает и возвращает эк-
земпляр класса QStyle для заданного стиля. В результате полный код плагина
стилей выглядит таким образом.
class MyStylePlugin : public QStylePlagin {
public:
QStringList keys() const { return QStringList()«"mystyle" }
QStyle *create(const QString &key) {
if (key==”mystyle”) return new MyStyle;
else return 0;
}
}
Q_EXPORT_PLUGIN( MyStylePlugin)
Иные предопределенные в Qt плагины требуют других параметров, но почти
всегда — имя создаваемого объекта в виде строки. Как уже было сказано, вы не
создаете в коде экземпляр плагина в явном виде. Вместо этого вы просто запраши-
ваете создание объекта. Qt на основании keys () -информации с помощью хеш-
таблицы быстро найдет ответственный за создание экземпляра плагин:
QApplication::setStyle(QStyleFactory::create("mystyle”))
Вы также не выполняете никаких действий для загрузки плагина — для этого
достаточно просто разместить его в нужном месте. Стандартное “нужное место” —
каталог QTDIR/plugins/<тип плагина>, где тип плагина, например styles.
Вы можете изменить это расположение с помощью метода QCoreApplication::
addLibraryPath(), хотя последнюю часть пути, например, styles, изменить
нельзя — Qt ищет плагины только в определенных каталогах относительно общего
для всех плагинов пути.
Второй тип плагинов — это те, которые вы создаете сами для собственных нужд.
Qt ничего не знает об этих плагинах и их интерфейсах, но тем не менее помогает
упростить их вызов. Для этого служит класс QPluginLoader, загружающий ваши
плагины точно так же, как и плагины Qt. Перед созданием плагина вы создаете
один или несколько интерфейсов — полностью абстрактных классов, посредством
которых вы будете общаться с плагином. Созданные интерфейсы регистрируются в
Qt с помощью макроса Q_DECLARE_INTERFACE.
Ваш класс плагина должен наследовать класс QObject, а также нужные интер-
фейсы. В приложении на основе метаданных вы можете проанализировать,
поддерживает ли данный объект нужные вам интерфейсы, с помощью метода
qobject_cast (). В определении своего плагина для помещения данных о интер-
фейсе в метаданные класса нужно вызвать макрос О ^INTERFACES.
Код времени исполнения производит довольно сложную проверку на предмет
того, может ли данный плагин быть загружен в приложение. Проверяются версии
самой библиотеки Qt, с которыми собраны приложение и плагин, а также типы
лицензий (плагин может рассчитывать на несуществующие в данной версии воз-
можности) и т.д. Также не могут работать совместно приложение и плагин, кото-
рые собраны в разных режимах debug и release. В отличие от обычных библио-
тек, загрузка плагинов безопасна для приложения в смысле контроля версий —
загрузка несовместимой библиотеки, скорее всего, приведет к краху приложения.
Сам класс QPluginLoader имеет совсем немного методов. Главный из них,
instance (), создает и возвращает главный объект плагина. При этом плагин будет
загружен, если не был загружен до этого: вам необязательно вызывать специаль-
ный метод load(). В отличие от стандартных плагинов, выгрузка которых не пре-
дусмотрена, вы можете “выгрузить” свой плагин с помощью метода unload ().
Приложение А
Компиляция и сборка проектов Qt
Хотя вы уже, возможно, знаете, что Qt интегрируется в такие визуальные среды
разработки, как Microsoft Visual Studio и Borland C++ Builder, но, тем не менее, вы
должны отдавать себе отчет, что расплатой за использование одной из этих оболо-
чек может стать потеря переносимости вашего приложения. Представьте себе си-
туацию, когда вы успешно ставите “галочку” о готовности начального релиза и
впоследствии обнаруживаете, что запуск приложения в другой среде потребует еще
нескольких дней (недель). Но ведь библиотека Qt создана с другой целью, так что
лучше с самого начала делать все правильно и использовать независимую от плат-
формы методику компиляции, рекомендованную создателями Qt.
Например, многие компании, заинтересованные в производительности своих
приложений, часто начинают разработку в среде Microsoft Visual Studio, но впо-
следствии применяют оптимизирующие компиляторы Intel в пакетном make-
режиме. В любом случае вы должны использовать для сборки своих приложений
специализированные средства Qt, поскольку только они обеспечивают действи-
тельную переносимость не только кода, но и технологии создания дистрибутивов на
другие платформы.
Самым главным, первостепенным, инструментом для обеспечения переносимо-
сти приложений с одной платформы на другую или даже между компиляторами
является утилита qmake. По своей сути, qmake — препроцессор, генерирующий
make-файлы на основании простого текстового файла типа “проект” с расширением
* .pro. При этом учитываются все особенности платформы и настроек компилято-
ра: разработчик не должен заботиться о специфике среды разработки. Кроме про-
чего, утилита qmake’включает Qt-специфические опции, необходимые для других
таких компонентов (фактически — препроцессоров) Qt, как тос и uic.
Файлы проектов qmake представляют собой обычные текстовые файлы, постро-
енные по особым правилам. Синтаксис qmake отчасти напоминает синтаксис языка
С. Файл проекта должен иметь расширение .pro, а его имя должно совпадать с
именем самого приложения. Создаваемое приложение будет по своему расширению
соответствовать среде разработки (например, под управлением MS Windows оно
получит расширение . ехе) или останется без расширения, но с правами на выпол-
нение под управлением Linux.
Основными записями являются списки исходных текстов, файлов заголовков и
опций компилятора. Для этого зарезервированы служебные слова SOURCES,
HEADERS и CONFIG. Ниже приводится пример, демонстрирующий многие базовые
аспекты qmake-файлов (номера строк проставлены мною и не являются частью
формата).
01 SOURCES=main.срр hello.срр
02 SOURCES+=connection.срр
03 HEADERS=main.h hello.h \
04 connection.h
05 CONFIG+=qt warn_on release
В данном примере компилируется приложение, состоящее из трех исходных
файлов. Два из них определяются в строке 01, третий добавляется в строке 02. Для
каждого из исходных файлов указывается файл заголовка, причем третий перене-
сен на дополнительную строку с помощью знака переноса “косая черта”. Три ключа
компиляции в строке 05 определяют, что это — qt-приложение (а значит, должны
быть подключены соответствующие библиотеки и заголовки; ключ CONFIG обяза-
телен для приложений Qt), которое содержит некритические предупреждения и ге-
нерирует оптимизированный код без включения в него информации для отладки.
Версия для отладки определяется ключом DEBUG. Как видите, по сравнению с
обычным набором ключей компилятора и сборщика формат файла значительно уп-
рощен. Создать make-файл на основе проекта несложно: достаточно сообщить ути-
лите qmake имена исходного и результирующего файлов.
qmake —о Makefile hello.pro
Также утилита qmake позволяет создавать и проекты Visual Studio, для этого
достаточно задать дополнительный ключ:
qmake —t vcapp —о hello.dsp hello.pro
Если вы не хотите, чтобы результирующий исполняемый файл получил имя по
умолчанию, то переопределите его имя с помощью директивы TARGET, например,
так:
TARGET=newname
Задание нового имени не влияет на расширение результирующего файла.
В процессе сборки вы можете использовать специфические для платформы файлы:
Win32 { SOURCES += hellowin.срр }
unix { SOURCES += hellounix.срр }
Иногда вы можете сомневаться в наличии того или иного файла или же позднее
подключение отдельных модулей может быть свойством проекта. В таком случае
этот файл можно заменить другим или, напротив, прекратить сборку с выдачей со-
общения:
!exists( main.срр ) { error( "No main.срр file found" ) }
В данной ситуации при отсутствии главного файла приложения будет выдано
сообщение об ошибке. В реальной жизни вы, возможно, будете использовать этот
механизм в более сложном контексте.
Любые условия могут быть использованы совместно с помощью вложенных ус-
ловных скобок:
Win32 { debug { CONFIG += console } }
На самом деле в использовании утилиты qmake нет особого фокуса. Все пара-
метры задаются профилем, который в свою очередь задается переменной окруже-
ния QMAKESPEC, обычно правильно определяемой по умолчанию во время установ-
ки, если на этот момент среда разработки уже известна. Так, для среды Visual
Studio .NET будет установлен профиль win32-msvc.net, в котором содержится
большинство параметров. Для среды gcc под управлением ОС Solaris эта перемен-
ная будет иметь вид solaris-g++. На основании этого параметра выбирается про-
филь в каталоге qt/mkspecs. К слову, на сегодня там указано 57 профилей — вы
можете только вообразить, насколько переносимой является библиотека Qt! Если
вы абсолютно уверены в том, что делаете, то можете даже изменить соответствую-
щий файл qmake. conf, например, добавив в него часто используемые библиотеки.
В файле конфигурации задано пять шаблонов, предназначенных для различных
типов приложения. Каждому типу приложения соответствует свой набор парамет-
ров, которые вы можете задать в своем .pro-файле. Шаблоны определяются сек-
циями арр, lib, vcapp, vclib, subdirs. Шаблоны, начинающиеся с букв “vs”,
предназначены не для генерации make-файлов, а для соответствующих проектов
Visual Studio, которые, в свою очередь, собирают приложение или библиотеку.
Последний тип шаблона, subdirs, предназначен для перехода в определенный ка-
талог, создания там make-файла и запуска процесса сборки.
Здесь мы рассмотрим только один, самый распространенный шаблон арр (если
не указано иначе, то используется именно этот вариант). Этот шаблон может со-
держать следующие тэги.
TEMPLATE — задает тип проекта, как описано выше.
HEADERS — список файлов-заголовков, для которых автоматически будут
сгенерированы зависимости сборки, если не указан ключ —nodepend. Заго-
ловки для определенных в исходном тексте классов будут найдены автома-
тически.
SOURCES — список исходных файлов.
LIBS — список связываемых библиотек, кроме тех, которые будут добавлены
в обычном порядке, как следствие указанной платформы и других флагов.
FORMS — список файлов типа .ui, созданных приложением Qt Designer. Все
зависимые от этих форм файлы, в том числе файлы исходного текста и заго-
ловков, будут добавлены в проект автоматически.
LEXSOURCES — список lex-файлов. Все зависимые файлы будут добавлены в
проект автоматически.
YACCSOURCES — список уасс-файлов.
TARGET — имя приложения, если оно отличается от имени по умолчанию.
DESTDIR — каталог, в котором будет расположено приложение.
DEFFILE — только для арр-приложений под управлением MS Windows. Имя
def-файла.
DEFINES — список предварительных определений символов (ключей компи-
лятора), необходимых для компиляции и сборки.
DLLDESTDIR — путь, по которому должна быть расположена копия создавае-
мой библиотеки.
INCLUDEPATH — список путей для дополнительных файлов заголовков. В ка-
честве разделителя каталогов поиска допустимы пробелы или знак “точка с
запятой”.
DEPENDPATH — пути для дополнительных файлов, от которых зависит сборка.
VPATH — путь для дополнительных файлов.
DEF_FILE — имя def-файла в Windows.
RC_FILE — имя файла ресурсов в Windows.
RES_FILE — имя файла откомпилированных ресурсов в Windows.
MOC_DIR, OBJECTS_DIR, UI_DIR, UI_HEADERS_DIR, UI_SOURCES_DIR — за-
дание каталогов для различных временных файлов.
SUBDIRS — для типа приложения, определяемого параметром TEMPLATE=subdir,
задает пути поиска вложенных файлов проекта.
VERSION — версия библиотеки в формате х. х. х.
DESTFILE— файлы, включаемые в результирующую сборку программой
UnixMake.
Кроме указанных, существует еще несколько десятков ключей, которые уста-
навливаются самой системой, и вы вряд ли будете их использовать. В основном,
они означают, где будут расположены и как будут поименованы промежуточные
файлы фазы макропроцессора. За дополнительной информацией обращайтесь к
документации.
Вы должны переопределять только те параметры, которые отличаются от за-
данных по умолчанию. Построение библиотеки не очень отличается от построения
приложения, за исключением дополнительного параметра, VERSION, который за-
дает версию собираемой библиотеки, например 3.2.1.
Важным параметром утилиты qmake является переменная CONFIG, поэтому мы
рассмотрим принимаемые этой командой параметры.
release/debug — сборка в режиме релиза/отладки.
warn_on/warn__of f — вывод или подавление предупреждений компиляции и
сборки.
qt — приложение Qt; обязательно для включения препроцессоров шос и uic.
thread — включение механизмов многопоточности (файлы-заголовки и со-
ответствующие реентерабельные версии библиотек будут подключены авто-
матически).
xll — приложение или библиотека для среды XI1; дополнительные заголов-
ки и флаги будут включены автоматически.
windows — оконное приложение для MS Windows; дополнительные заголов-
ки и флаги будут включены автоматически.
console — консольное приложение для MS Windows.
opengl — приложение требует включения файлов-заголовков для OpenGL
(или mesa для открытых систем).
staticlib — создание статически связываемой библиотеки.
dll — построение разделяемой библиотеки DLL.
staticlib — построение статической библиотеки.
plugin — построение библиотеки-плагина; также включает опцию dll.
except ion — включение механизма исключений.
rtti — включение механизма получения информации о типах времени вы-
полнения.
stl — включение информации о классах в формате STL.
flat — опция, “отвечающая” за пути доступа к файлам при сборке проектов
Visual Studio. Если опция указана (по умолчанию), то файлы исходного кода
и заголовков будут включены в соответствующие группы, независимо от ка-
талогов, в которых расположен файл проекта.
Теперь рассмотрим более сложные вопросы, связанные с утилитой qmake, кото-
рые отнесены в документации к категории “Advanced Concepts”. Не будем останав-
ливаться на интуитивно понятных вещах: например, как легко догадаться, опера-
торы “=”, “+=” и “—=” полностью определяют список параметров, добавляют или
удаляют параметр из определения переменной препроцессора соответственно.
Оператор “*=” позволяет добавить параметр, но только в случае, если он еще не до-
бавлен. Более сложным и интересным оператором является он проверяет на-
личие параметров (фактически — ^подстрок), удовлетворяющих заданному регу-
лярному выражению, и заменяет их другим параметром.
Еще одним “продвинутым” механизмом являются блоки кода. Блок кода вы-
полняется, если определен предшествующий ему символ, т.е. выполняется неяв-
ный if-блок. По аналогии с языком С блок кода задается фигурными скобками { }.
Для однострочных блоков можно использовать двоеточие. Таким образом, конст-
рукции Win32 {DEFINES += QT-DLL} И Win32 :DEFINES+=QT__DLL эквивалентны.
Параметры в строке CONFIG также являются определенными символами, так что
их проверку можно записать без особых дополнительных действий.
CONFIG += qt warn_on debug
debug { TARGET = myappdebug }
release { TARGET = myapp }
Здесь имя результирующего приложения изменяется в зависимости от опций ком-
пиляции, а точнее — от наличия или отсутствия отладочной информации. Вполне
прогнозируемо работает суперпозиция двух блоков. Следующая строка кода
Win32 { thread { DEFINES += QT_THREAD_SUPPORT } }
означает, что если опции компиляции включают компиляцию для Windows и под-
держку многопоточности, то используется параметр QT_THREAD_SUPPORT. То же
самое можно записать и с помощью двоеточия, например так:
Win32: thread { DEFINES += QT_THREAD-_SUPPORT }
или даже так: Win32 : thread: DEFINES+=QT_THREAD_SUPPORT, хотя в этом случае
вы ограничены одной строкой “скрипта”. По аналогии с традиционным if-блоком в
вашем распоряжении отрицание условий и сложные конструкции if-elseif-else.
Выглядит это таким образом.
Win32:thread{DEFINES+=QT_THREAD_SUPPORT}
else: J release{DEFINES+=QT_NOTHREAD_DEBUG}
else{ message("Unknown configuration")}
Кроме прочего, вы можете сделать вывод о том, что форматирование в утилите
qmake может быть произвольным, переносы строки не имеют в этом синтаксисе
особого значения.
Наконец, вы можете определять собственные переменные. На самом деле они
ничем не будут отличаться от таких предопределенных переменных, как SOURCES
или CONFIG, за исключением того, что утилита qmake полностью игнорирует их, по-
скольку не знает, как их обрабатывать. Вы можете использовать их как условия для
выполнения блоков кода или как части при формировании значимых переменных.
Можно создать переменную, присвоив ей какое-то значение, например
MY_DEFINES=DEFINES. Эта строка, возможно, введет вас в заблуждение: значение в
правой части присваивания — простая строка "DEFINES". Для того чтобы действи-
тельно выбрать имя другой переменной (что практически реализуется как тексту-
альная макроподстановка), вы должны записать MY_DEFINES=$$DEFINES. Обычно
то же самое записывается как MY_DEFINES=$$ (DEFINES), чтобы макропроцессор
мог корректно соединять части макроподстановки с “окружающим миром”, т.е.
встраивать макрос в другую строку.
Для встраивания строк в переменные существует несколько функций. Функ-
ции, начинающиеся с символов “$$”, выступают как текстовые переменные, т.е.
возвращают текст, который подставляется в то место, где эта функция вызывается.
Другие функции могут возвращать логическое значение или вообще не возвращать
никакого. Вот их краткое описание (в алфавитном порядке).
contains (переменная, значение) — проверяет, содержит ли переменная
некоторое значение. Возвращает логический результат.
count (переменная, число) — проверяет, содержит ли переменная указан-
ное число параметров. Возвращает логическое значение.
error (сообщение) — выдает сообщение об ошибке, после чего обработка
файла сразу же завершается.
exists (имя_файла) —проверяет наличие указанного файла, и как
следствие — вызывается выполнение дальнейшего блока кода.
equals (имя__перемнной, значение) — проверяет, равна ли переменная
некоторому значению, в качестве которого может быть указана другая
переменная в виде $$var.
include (имя_рго-файла) — включает вместо этой строки другой файл с
настройками, после обработки которого продолжается интерпретация со
следующей строки.
infile(имя_файла, имя__перемецной, значение) — проверяет, опреде-
лена ли в данном файле переменная с заданным параметром. Параметр
может отсутствовать: в таком случае просто проверяется определение этой
переменной.
isEmpty (имя_переменной) — проверяет, не пуста ли переменная. К “пус-
тым” также относятся все неопределенные переменные, которым не
присвоено никакого значения.
$$join(переменная, соединитель, перед, после) — соединяет слова
(параметры) в переменной с помощью соединителя. Понять, как работает эта
функция, можно только на собственном опыте.
$$find(переменная, рег_выражение) — возвращает новую переменную-
список, содержащую параметры, удовлетворяющие заданному регулярному
выражению.
$$member (переменная, позиция) — трактует переменную как массив па-
раметров и возвращает слово (параметр) в соответствующей позиции.
message (сообщение) — на консоль выводится строка сообщения и
продолжается дальнейшая обработка файла; используется для отладки в ре-
жиме “Verbose Mode”.
$$prompt (вопрос) — выводит приглашение (вопрос) и возвращает в качест-
ве результата ответ с консольного ввода.
$$system(комм__строка) — запускает программу с параметрами и возвра-
щает стандартный объект вывода. Может использоваться для исследования
среды выполнения и соответствующей настройки, особенно в совокупности с
вашим справочным приложением (“хелпером”).
system(комманда) — выполняется системная команда, и, если она возвра-
щает значение “единица” (exit(l);), то результат выполнения функции
считается истинным (положительным).
Если функция возвращает результат логического типа, то ее можно, так же как
и символы, использовать перед условными блоками кода — фактически, эти функ-
ции для этого и предназначены.
Теперь, когда вы ознакомились с синтаксисом утилиты qmake, осталось только
рассмотреть параметры командной строки, которые вы, я уверен, также будете ис-
пользовать в той или иной мере. Большинство из них предназначено для кросс-
платформенной компиляции. Вот их описание в порядке частоты использования.
-о имя—файла. Заданный файл будет использован в качестве результи-
рующего потока макропроцессора. В противном случае имя make-файла (или
другого формата, в зависимости от типа проекта) будет сгенерировано на
основе имени исходного файла. Если указан параметр ’ - ’, то выходной
поток будет направлен на стандартный консольный объект stdout.
-unix. Предполагается именование файлов и каталогов в формате Unix;
применяется по умолчанию для Unix-подобных систем. Кроме прочего, будет
истинным символ unix, что можно использовать как условие для макро-
процессора.
-тасх. Аналогично предыдущему параметру применяются умолчания для
платформы Mac OS X.
-Win32. То же для платформы Win32— одноименный символ будет
истинным для этой платформы.
-d. Вывод отладочной информации.
-t (шаблон). Символ TEMPLATE будет переопределен указанным значением,
однако только после обработки данного . рго-файла.
-tp (префикс). Для переменной TEMPLATE будет применен заданный
префикс.
-help. Подсказка по указанным опциям (действие требует практического
исследования).
-Wall/Wnone. Выдавать все предупреждения/не выдавать их вовсе.
-Wparser. Выдавать только предупреждения синтаксической фазы макро-
процессора.
-Wlogic. Более тонкая настройка предупреждений макропроцессора, в том
числе предупреждения типа “двойное включение файла” или “включаемый
файл не существует”. Обычно, востребованная опция.
На самом деле, утилита qmake может как генерировать из рго-файла make-
файл, так и, напротив, генерировать сам pro-файл. Режим работы определяется
ключами командной строки-makefile или-project соответственно. Все пара-
метры, как правило, будут достаточно корректно установлены по умолчанию, за
исключением параметров, задающих режим кроссплатформенной компиляции. В
этом случае вы можете задать дополнительные опции командной строки.
-after. Параметры командной строки вступают в действие только после
обработки входных файлов.
-nocache. Игнорировать параметр qmake. cache.
-nodepend. He генерировать любую информацию о зависимостях сборки.
-cache имя_фала. Используется заданный файл кеша, обычно с расшире-
нием .qmake.cache.
-spec (спецификация). Утилита qmake будет использовать указанную
спецификацию как путь к параметрам компиляции, игнорируя переменную
окружения QMAKESPEC. Эффективно реализует кроссплатформенную сборку.
В случае, когда указана опция -pro j ect, вы можете использовать два ключа.
-г. Циклически просматривать каталоги при поиске нужных файлов.
-nopwd. Не просматривать текущий каталог в поисках файлов, отдавая
предпочтение путям поиска.
Наконец, затронем также относящуюся к утилите qmake тему, которая была в
большом почете в качестве ноу-хау полтора-два десятилетия тому назад, но значе-
ние которой до некоторой степени сохранилось и по сей день. Я имею в виду пред-
варительно скомпилированные файлы заголовков. В различных вариациях эта
возможность называлась компиляцией с кэшированием или аддитивной компиля-
цией. Идея проста: если некоторые файлы (интерфейсы классов) не подвергаются
изменениям с момента последней компиляции, то они и не перекомпилируются,
бинарная фаза (т.е. бинарии с неразрешенными внешними ссылками) извлекается
из кэша. В некоторый момент времени разработчики пришли к общему знаменате-
лю, утвердив в качестве технологического стандарта тип файла РСН как формат
для предварительно скомпилированных заголовков. Этот формат поддерживается
многими компиляторами предварительной обработки (“пред-компиляторами”), в
том числе проектами Visual Studio, nmake, Xcode, Makefile (последние два — для
MacOS X), а также кроссплатформенными мультикомпиляторами GCC, начиная с
версии 3.3.
Для того чтобы ваши заголовки могли использовать технику предварительной
компиляции, они должны удовлетворять нескольким условиям — и это не касается
Qt как такового. Во-первых, должны отделить С-заголовки от С4-4--заголовкев,
поскольку они имеют разные форматы. Для этого используйте директиву
#if defined _____cplusplus.
/* Декларации для С */
# if defined _cplusplus /* Декларации для C++ */
#include <stdlib>
#include <iostream>
#include <qapplication.h>
#include <qpushbutton.h>
#include "stable_class.h"
#endif
Во-вторых, вы не должны включать в предварительно скомпилированные заго-
ловки тот фрагмент кода, который еще находится у вас в стадии разработки, — они
все равно будут определены как требующие повторной компиляции. Включайте
только стабильный код, т.е. библиотеки, и тот, который вы уже закончили тести-
ровать.
Ключ PRECONPILED_HEADER в файле проекта “заведует” предварительной ком-
пиляцией. Вы просто указываете файл заголовка, который должен по возможно-
сти использовать предварительную компиляцию. При этом устанавливается
условная переменная CONFIG+=precompile_header, которая может быть про-
анализирована для установки флагов компилятора. На самом деле (в 99% случа-
ев) Qt сделает все как следует, и компилятор получит указание использовать
предварительную компиляцию, независимо от платформы. Более детально с
предварительной компиляцией заголовков вы можете ознакомиться, исследовав
пример /qt/qmake/examples/precompile (поскольку приведенный там текст не
показывает никаких “чудес”, мы не будем на нем останавливаться).
В заключение хочется отметить, что, хотя в мире C++ принято изощренно оп-
тимизировать как сами алгоритмы программ, так и технологию их построения, тем
не менее предварительно компилированные файлы заголовков и прочие оптимиза-
ции при всем желании нельзя отнести к функциональным свойствам вашего проек-
та. Так что если вы решаете дилемму, чему отдать предпочтение: оптимизации
процесса сборки или проработке функциональных возможностей вашей програм-
мы, то первая цель по возможности должна соотноситься со второй как один к ты-
сяче (или приблизительно в таком соотношении).
Приложение Б
Qt Designer: обзор и комментарии
В версии Qt4 приложение Designer было заметно модифицировано. В частности,
Designer для Windows больше не оперирует проектами, а только графическими
формами, окнами диалогов и отдельными элементами управления. Вместо этого в
модуль совместимости с Visual Studio добавлены компоненты (“визарды”) создания
форм и проектов Qt. Также добавлена возможность создания собственных элемен-
тов управления, многие привычные компоненты версии 3 “перекочевали” на спе-
циальную палитру совместимости и т.д. Для уточнения информации об используе-
мой вами версии обращайтесь к документации.
По мере того как программы содержат все больше данные и все меньше — код,
процесс программирования как таковой все больше представляет собой создание
текстовых и мультимедийных ресурсов. Начало было положено среди коммерче-
ских систем в MacOS — первой системе, явно разделившей приложение на код и ре-
сурсы (последние преимущественно были представлены графическими битовыми
картами и текстовыми массивами строк). Еще одним, возможно, наиболее важным
типом ресурсов, связанных с данной программой, являются содержательные опи-
сания окон диалога, а в общем случае — произвольных объектов пользовательского
интерфейса, например, меню.
Существует два принципиальных различия в трактовке таких ресурсов. Первый
подход, которому следуют Qt, а также Java и ряд других систем программирова-
ния, дает возможность пользователю конструировать интерфейс с помощью инте-
рактивных инструментов и сохранять их в виде ресурсных файлов, но в момент
сохранения такого файла (сразу или на болеё поздней стадии) с помощью препро-
цессора (в случае Qt это — uic) генерируется текст программы, который вы впо-
следствии с помощью компилятора превращаете в бинарную форму. Этот метод по-
рождает более быстродействующий код, но внесение в сам текст изменений в обход
интерактивных инструментов не поощряется, т.е. фактически он является не кор-
ректным способом внесения изменений и, ко всему прочему, часто требует особых
познаний в мета-синтаксисе генератора интерфейса.
Второй метод, применяемый в уже упоминавшейся операционной среде MacOS,
а также в оригинальном интерфейсе MS Windows API (только при создании окон
диалога) PalmOS и в таких более новых системах, как Delphi/Kylix, заключается в
хранении ресурсов в символическом виде и интерпретации или восстановлении
объектов интерфейса из текстового или бинарного потока в момент выполнения.
Естественно предположить, что интерпретация обычно работает медленнее, чем
прямое выполнение API-функций, но также понятно, что на стороне интерпрета-
ции серьезные доводы в пользу надежности и многократного использования кода и
данных.
Поскольку на момент написания данной книги производительность процессоров
и удельная емкость носителей оперативной и дисковой памяти дешевеют прибли-
зительно в равной мере, то оба подхода по-прежнему остаются в равной мере вос-
требованными.
Итак, Qt Designer — это приложение, позволяющее в интерактивном режиме
генерировать прототип пользовательского интерфейса с последующей автоматиче-
ской генерацией исходного кода, продуцирующего этот интерфейс во время выпол-
нения программы. Можно долго спорить о том, является ли ускорение разработки
интерфейса хорошей услугой. Некоторые опытные разработчики придерживаются
мнения, что быстро разрабатываемые программы также быстро выявляют свои
слабости и впоследствии требуют длительной доработки очень медленными мето-
дами. В любом случае средства генерации кода являются востребованным инстру-
ментарием, без которого современная система разработки не воспринимается как
законченный продукт.
Чтобы запустить редактор интерфейса, вы должны выполнить приложение
designer, представленное одноименным файлом в среде Linux/BSD или файлом
designer. ехе в среде MS Windows. Если вы работаете в оконной среде, например в
KDE (надеюсь, именно там вы сейчас и находитесь), то при инсталляции в меню
Пуск (К*) в разделе Applications-Development уже создан пункт, запускающий
Qt Designer. Если вы запускаете его через консоль, то поставьте в конце символ
“амперсанд”, чтобы эффективно открепить запущенное приложение от консоли и
не зависеть от ее состояния впоследствии.
После запуска Qt Designer отображает диалог создания нового проекта. Говоря
точнее, в этом же диалоге вы можете отдать предпочтение открытию проекта или
выбору одного из недавно использованных проектов (в зависимости от выбранной
закладки).
В данный момент нас будет интересовать именно создание нового проекта.
На выбор у вас целых девять вариантов: создание проекта приложения, окна глав-
ного приложения, окна диалога и т.д. В случае создания форм графического ин-
терфейса сгенерированный вами файл будет иметь расширение * .ui, означающее
“интерфейс пользователя” (user interface).
К сожалению, под управлением Windows дизайнер интерфейса Qt ведет себя не
всегда предсказуемо (иногда пропадает ввод текста и так далее), поэтому для полу-
чения нужных результатов вам придется в этой среде приложить больше усилий.
Также нужно понимать, что Visual Studio будет конкурировать в процессе разра-
ботки с приложением Qt Designer, так что лучше работать только в одном из этих
инструментов или, по крайней мере, отдавать себе отчет в особенностях их взаимо-
действия. Рекомендуется, насколько это возможно, “оттягивать” переход к коду в
режиме отладки и как можно больше работы по планированию интерфейса выпол-
нить именно в “Дизайнере”. Это сэкономит не один час кропотливого труда.
Если говорить предметно, то я бы посоветовал работать с “Дизайнером” только в
среде Linux. Дистрибутив Qt 4 доступен (как для большинства современных дист-
рибутивов) в виде исходного кода. Например, я использую его совместно с Fedora
Соте 4. Возможно, вам придется использовать не самую последнюю версию ядра
Linux (как, впрочем, и библиотеки Qt) — разработчики знают, что лучше разраба-
тывать приложения в среде двух-трехлетней давности, чтобы быть уверенным в ее
популярности.
Первое, что вы должны сделать, — создать проект. При этом обратите внимание
на расположение результирующих файлов: по умолчанию часто расположение ока-
зывается совсем не тем, что вы ожидаете. Например, под управлением Linux все
файлы попадут в ваш домашний каталог, а это, согласитесь, — не совсем ожидае-
мое поведение. После этого вы можете свободно добавлять формы и диалоги к это-
му проекту: фактически вы можете открыть сразу как угодно много проектов и
переключаться между ними, но, по правде говоря, это требует дополнительной
внимательности, так что лично я почти не использую эту возможность.
Как правило, вы должны создать для приложения одно окно — главное окно
приложения или диалЬга. Кроме того, если вы имеете готовую форму или диалог —
в результате собственной разработки или из внешних источников (вследствие раз-
деления полномочий в рабочей группе и т.д.), — то вы можете просто подсоединить
ui-файл к своему проекту, щелкнув на нем правой кнопкой мыши в окне проектов
(изначально — в правом верхнем углу экрана), и выбрать пункт Добавить файл.
Там же вы можете добавить графические ресурсы и объекты подключения к базам
данных (поскольку это исключительно эмпирические операции, мы не будем их
рассматривать подробно).
Основной работой, выполняемой в “Дизайнере”, является собственно построе-
ние пользовательского интерфейса. Эти действия тоже не будут тут подробно ос-
вещены (если помните, в начале книги я обещал, что мы с вами займемся про-
граммированием, а не доморощенным дизайном в стиле “переместите курсор на
палитру элементов управления...”). Хочу только заметить, что если ваше прило-
жение не слишком общего плана, то вы можете и не создавать главное окно прило-
жения в том смысле, которое подразумевается под пунктом Main Window в мастере
новых объектов. Многие приложения могут вполне ограничиться одним диалого-
вым окном постоянного размера или, как вариант, — с несколькими “закладками”
для увеличения полезной площади. Это давно поняли программисты на Java и
Delphi, и это будет нелишним знанием для начинающих кодировщиков в среде Qt.
Создавая “полноценное” приложение с меню, строкой состояния, панелями кно-
пок-акселераторов, которые в дополнение еще и можно настраивать, вы в значи-
тельной мере следуете по пути создателей MS Office, а это не тот сценарий, на кото-
рый я могу вас благословить. Не нужно путать себя с мега-корпорациями, пусть
ваши программы просто делают то, для чего они предназначены.
Единственный практический совет, который я могу дать по применению при-
ложения Qt Designer, — пройдите в пошаговом режиме “туториал” (начиная с фай-
ла designer-manual-2 .html и далее), расположенный в папке с документацией.
Это очень простое упражнение, которое не должно занять больше часа или двух, —
Qt Designer: обзор и комментарии
205
и поскольку конструирование интерфейса относится к навыкам, а йе знаниям, то я
надеюсь, что вы вскоре сможете освоить этот инструмент.
Немного о конверторе интерфейсных ресурсных файлов *. ui в файлы исходно-
го кода на C++ (обычно с расширением ★ .h). Сам конвертор называется uic и рас-
положен в каталоге $QTDlR/bin. Вы обычно не вызываете его, если пользуетесь
универсальной утилитой qmake и файлами *.рго. По умолчанию uic-вывод на-
правляется в скрытый каталог .ui/, расположенный на уровень ниже каталога
вашего проекта, и не предназначен для просмотра или модификации. Если же вы
все-таки хотите знать, что производит этот препроцессор, то можете запустить его
явно, например так:
/usr/lib/qt3/bin/uic myform.ui —о c_myform.h
Вряд ли вы будете часто использовать этот код, поскольку его модификация,
как уже было сказано, является технологически некорректной. Но поучиться у
“Дизайнера” тому, как строить интерфейсы, может оказаться весьма полезным.
Как вы сможете убедиться, описание интерфейса Qt не слишком отличается от та-
кого же в Delphi, Java или даже Visual Basic, за исключением того, что он даже с
первого раза получится (по моему скромному мнению) значительно привлекатель-
нее и динамичнее, чем в Java и Delphi вместе взятых. Это, впрочем, не функцио-
нально, но очень впечатляет.
Приложение В
Использование Qt Linguist
Очень часто современные приложения нуждаются в динамической загрузке
различных языковых интерфейсов. Ситуация усложняется инкрементивной тех-
нологией программирования: если вы используете наборы ресурсных строк для за-
грузки различных языковых интерфейсов, то вы должны каждое изменение в коде
сопровождать изменениями в ресурсах, после чего эти ресурсы должны быть ском-
пилированы с приложением. Конечно, можно создать файлы с ресурсами отдельно,
например в виде DLL, и загружать их динамически.
Кроме того, существует несколько проблем перевода: одно и то же английское
слово может иметь несколько различных вариантов перевода на другом языке, пе-
ревод может повлечь изменение “горячих клавиш”, а также во многих языках
строки форматирования с числами должны быть различными для различных чи-
словых значений.
Qt старается решить все эти проблемы с помощью гибкой и мощной системы пе-
ревода строковых констант, встречающихся в таких элементах управления, как
меню, надписи, кнопки и т.д. Собственно в самом процессе перевода задействовано
несколько компонентов. В основе всего процесса лежит тот же файл проекта,
* .pro, который используется для сборки приложения. В качестве главного управ-
ляющего приложения в Qt 4 выступает Release Manager. На основании исходных
файлов, упоминаемых в проекте, менеджер релиза строит файл перевода, который
передается на вход программы Qt Linguist. Эта программа, вопреки названию,
ничего не знает о переводе. Переводом занимается человек; задача приложений
Qt — сделать перевод корректным и сократить работу переводчика. В частности,
отслеживаются имена файлов и контекст (имя строки и параметры вызова), поэто-
му если строка уже переведена и строка кода не изменилась, то эту строку уже пе-
реводить не надо.
После перевода с помощью программы Qt Linguist результирующий файл пе-
ревода снова возвращается в менеджер релизов, который генерирует на основании
текстовых файлов перевода их бинарное представление для быстрой и компактной
загрузки в приложение во время выполнения. Побочный эффект от использования
программы Qt Linguist — создание пользовательского словаря, где хранятся все
переведенные фразы. Это позволяет, в частности, использовать согласованную тер-
минологию в нескольких приложениях и, конечно, экономит время (рис. В.1).
Рис, В.1. Естественная поддержка Unicode
обеспечивает возможность встраивать
текст на любом доступном языке, однако
правильнее для создания языковых профилей
использовать приложение Qt Linguist
Немного остановимся на отдельных фазах этого процесса. Первое, что вы долж-
ны сделать для включения переводов как части проекта, — определить *. tr-
файлы в файле проекта. По соглашению вы должны включать мнемонику языка
перевода в имя файла, например, так:
TRANSLATIONS=myapp_dk. tr myapp_.fr. tr myapp_ru. tr
По умолчанию предполагается, что язык оригинала — английский, закодирован-
ный в кодировке Latinl. Если это не так и язык оригинала, например, русский, то
вы должны в тексте программы выдать команду QTextCodec: : setCodecForTr ().
При этом в файле *. pro нужно указать эту же кодировку в предложении
DEFAULTCODEC.
Простая утилита lupdate генерирует исходные файлы переводов на основании
заданного проекта — например, при выполнении команды lupdate myproj ect. pro
будут сгенерированы все файлы переводов, указанные для данного проекта. Полу-
чаемые файлы *. tr — обычные текстовые файлы в формате XML, которые можно
исследовать в любом текстовом редакторе. Когда вызывать утилиту lupdate —
зависит от вашей технологии построения релизов. Возможно, вы захотите сделать
перевод ежедневным занятием и, таким образом, избежать “большой работы” в
этом направлении. Еще одна стратегия может заключаться в том, чтобы игнориро-
вать возможности перевода в прототипах и откладывать перевод всего приложения
до выпуска коммерческой версии. В любом случае вы должны с самого начала ко-
дирования заложить эти возможности в приложение, а именно — создавать все по-
тенциально переводимые строки с помощью tr ().
Другое приложение, возможно, еще более простое (чем утилита lupdate),
lrelease, на основе того же файла проекта генерирует файлы *. qt — как резуль-
тат упаковки файлов *. tr. Когда файл * . qt сгенерирован, он сразу может исполь-
зоваться приложением. Если файла *.qt нет— это тоже не считается Ьшибкой,
приложение будет использовать те строки, которые имеются в приложении. Ана-
логично, если *. tr-файл не помечен как завершенный, то первоначальные строки
из приложения будут использованы как результат перевода. Файлы, которые час-
тично переведены, также могут быть использованы — непереведенные строки бу-
дут заменены первоначальными (английскими) вариантами.
Второй, после генерации *. tr-файлов, этап перевода — собственно, перевод с
помощью средства Qt Linguist. Это очень простое приложение, оперирующее
“контекстами” перевода. Другими словами, каждая фраза переводится в совокуп-
ности с ближайшими фразами и словами, встречаемыми на той же форме или в ок-
не диалога. Вы можете перемещаться от фразы к фразе и переводить их последова-
тельно, а можете выборочно делать переводы в произвольном порядке. Когда вы
заканчиваете перевод блока (контекста) полностью, он весь помечается как фини-
шированный. Есть несколько критериев проверки (валидации) вводимых данных:
например, если вы открыли словарь и ваш перевод не совпадает с переводом слова-
ря, то такой перевод не проходит валидацию. То же самое произойдет, если в ори-
гинальной фразе был указан акселератор, а в переводе — нет; если исходная фраза
заканчивалась каким-то знаком пунктуации, перевод тоже должен заканчиваться
этим же знаком, например вопросительным или восклицательным. Эти проверки
можно отменить в настройках.
Еще одна из несложных функций — область подсказок, которая пытается под-
ставлять фразу, если она уже была переведена. Это не значит, что вы обязаны со-
глашаться: именно поэтому одна и та же фраза (чаще — слово) могут в различном
контексте переводиться по-разному. Также просто формировать и собственные
словари перевода — впоследствии вы можете открыть один или несколько таких
словарей одновременно. Еще один сервис — проверка “горячих клавиш” на уни-
кальность. Также доступна и навигация по “нерешенным” элементам — это проще
освоить на практике, чем описать словами, поэтому не буду долго на этом останав-
ливаться.
Теперь о третьем компоненте перевода: собственно технике программирования.
Как уже, надеюсь, вам давно стало ясно, для этого Нужно все переводимые фразы в
тексте программы заключать в вызов tr (). Это метод класса QObject, так что вы
можете использовать его для любого класса Qt, в том числе и для собственных.
Для вечных любителей оптимизации сообщаем: файл перевода для главной
формы или диалогового окна загружается в бинарном, оптимизированном виде
только один раз, при создании формы. Поскольку большинство форм создается
один раз в процессе работы приложения, а потом только скрывается и отображает-
ся вновь, то процесс загрузки переведенных фраз практически не влияет на ско-
рость работы приложения.
Если бы мы сказали, что перевод происходит совершенно без участия програм-
миста, это, конечно, было бы преувеличением. Вы можете самостоятельно устано-
вить файл перевода на основании параметров, например, файла конфигурации.
Другой подход — исследовать системные настройки, хотя это не всегда то, что
нужно (учтите, что многие работают под оригинальными английскими версиями
систем, устанавливая поверх собственные шрифты и раскладки клавиатуры).
Использование Qt Linguist
209
Наконец, вы можете предоставить настройку языка пользователю. Вот как выгля-
дит загрузка файла перевода на основании системной настройки.
int main(...) {
QApplication арр(...);
QString locale=QLocale::system().name();
QTranslator trans;
trans.load(QString("appj)+locale);
app.installTranslator(&trans);
Еще раз повторяю: системные настройки производят впечатление, но обычно
пользователь лучше знает, какой язык интерфейса он предпочитает.
Некоторая дополнительная работа связана с теми фразами, которые должны
быть переведены по-разному в различных местах программы. Обычно утилита
lupdate “считает”, что в пределах одного класса одинаковые фразы — это одно и
то же. Можно сказать, что класс является контекстом перевода. Если вы так не
считаете, то можете передать в метод tr () второй параметр в виде строки, чтобы
утилита lupdate “поняла”, что это'различные варианты. В лучшем случае можно
сделать хотя бы так: tr("Open", "asInOpenFile"), т.е. указать, в какой фразе
встречается этот вариант перевода, и тогда у переводчика не будет вопросов к про-
граммисту во время выполнения своей части работы.
Вы даже можете переводить фразы за пределами QObject-иерархии с помощью
метода QCoreApplication: : translate (). Конечно, вне любого класса, например
в глобальной функции, lupdate не может догадаться о контексте, так что вы ука-
зываете его самостоятельно в виде строки. Также можно задать и вторую “строку
уникальности”, которая еще называется комментарием к контексту. Наконец,
если вы не используете класс QObject, то для вас в данном случае не работают и
такие методы, как setCodecForTr (), поэтому если оригинальная строка написана
не в кодировке Latini, то дополнительно еще необходимо указать и кодировку
оригинала. В общем, вы должны избегать не-QObj ect-программирования в Qt 4.
Промежуточный вариант — вызвать метод tr () из глобальной функции как
метод конкретного элемента управления, например формы. При этом контекст
можно не указывать — сам элемент управления “возьмет на себя ответственность”
за перевод. В результате вызовы LoginForm: : tr ("Login:") и qApp->translate
("LoginApp", "Login:") будут работать аналогично.
Наконец, если вы хотите переводить строки вообще вне любой функции, то это
также возможно. Макрос QT__TR_NOOP воспринимает один параметр — строку. Счи-
тается, что вы находитесь внутри определения класса и имя класса будет выступать
в качестве контекста. Макрос QT_TRANSLATE_NOOP принимает строку для перевода
и имя контекста — этот макрос можно использовать “вне всего”, например при за-
дании констант в глобальных структурах или массивах. Конечно, после использо-
вания этого макроса это будут уже не константы, но Qt сделает все правильно, и с
точки зрения приложения это будут вполне нормальные статические данные.
Приложение Г
Параметризация приложений
с помощью QSA
Как показала жизнь, успех любого приложения зависит от поддержки пользо-
вателей выше среднего уровня, или так называемых пользователей с повышенны-
ми требованиями к вычислительной системе (power users). Подобно тому как
экономикой “управляет” средний класс — точно так же существует уровень про-
фессионалов, которые принимают, в качестве экспертов, решения относительно
использования той или иной технологии.
Важной, с точки зрения “среднего класса” ИТ-специалистов, возможностью лю-
бого приложения является удобная и простая параметризация приложений. В ка-
честве параметров могут выступать переменные среды выполнения, параметры ко-
мандной строки или данные, получаемые из файлов настройки. Но особенно
“любимым” средством настройки являются встроенные средства программирова-
ния, “усиленные” скриптовыми интерпретируемыми языками. Например, как от-
мечают многие источники, именно наличие мощного скриптового языка
(фактически — нескольких таких языков, как sh, perl и tel) командной оболочки
делает Unix и производные системы такими привлекательными. Другой пример —
MS Office, который, хотя и в другом контексте, также служит удовлетворению
творческих порывов многих миллионов пользователей с помощью встроенного
языка VBA. Наконец, более специфический пример успешного “скриптинга” —
встроенные средства программирования в пакете 1С Бухгалтерия, без которых это
историческое приложение вряд ли бы попало в новое тысячелетие и могло быть
адаптировано к постоянно меняющимся требованиям.
По аналогии с VBA, скриптовым программированием в Qt занимается библиоте-
ка QSA (Qt Scripting for Applications). Обращаю ваше внимание, что QSA является
самостоятельным продуктом, поставляемым отдельно от Qt и имеющим собствен-
ные номера версий. Для Qt 3 использовалась версия QSA 1. Эта же версия работает
с Qt 4, но в конце 2005 года (на момент написания это еще в будущем) должна быть
создана новая версия QSA, специально предназначенная для Qt 4. Иными словами,
наличие средств разработки и выполнения кода Qt автоматически не означает, что
у вас установлена библиотека QSA. На момент написания этой книги последняя
версия QSA — 1.1.2. Также, поскольку QSA поставляется в виде исходных текстов
и компилируется в момент инсталляции, на целевой системе должен быть установ-
лен пакет Qt и компилятор.
В Qt на уровне QObject-иерархии и метаданных времени исполнения уже
встроены все возможности по использованию скриптовых управляющих программ.
Однако для того, чтобы задействовать QSA в своих программах, необходимо вы-
полнить ряд действий. Нужно подключить библиотеку Ibqsa, вставив в файл про-
екта строку load(qsa). Эта библиотека экспортирует основной класс интерпрета-
тора, QSlnterpreter, и еще несколько вспомогательных инструментов. Когда вы
встраиваете объекты для доступа, то вам не нужно самостоятельно создавать эк-
земпляр интерпретатора Qt Script. Однако если вы очень хотите создать экземпляр
интерпретатора без привязки к каким-то объектам и проекту, то можете вызвать
статический метод QSlnterpreter:: defaultinterpreter (), который создает
интерпретатор без какого-либо проекта и контекста.
В приложении вы не должны принимать никаких особых мер для адаптации
объектов под библиотеку QSA: в основе иерархии Qt лежит класс QObject, распо-
лагающий необходимыми метаданными для экспорта сигналов, слотов и свойств.
Когда-то мы называли свойства “уступкой” для кодировщиков на VBA — и, если
это так, то QSA можно признать окончательной уступкой этим вездесущим инди-
видуумам. Вот главная причина, почему вы захотите создавать свойства, а не толь-
ко методы доступа: данные о свойствах хранятся в RTTI-вцде, т.е. во время выпол-
нения приложения вы можете исследовать не только значение свойств, но и их
имя, тип, длину и другие сведения. Как следствие, вы можете экспортировать их
как свойства ActiveX или как свойства QSA. Вспомните служебное свойство
SCRIPTABLE: этот, связанный со свойством, признак служит тем же целям, что и
public в объявлении класса: если не указан этот ключ в предложении PROPERTY,
то в скриптовом окружении свойство будет недоступно.
Для того чтобы сделать объекты “глобальными”, т.е. видимыми для скриптов,
нужно для каждого объекта Qt, т.е. такого, который происходит от класса QOb j ect
и содержит “главный” макрос Q_OBJECT в теле определения, вызвать один из мето-
дов: QSProject::addObj^ct() или QSlnterpreter::addTransientObject().
Последний метод оперирует неустойчивыми объектами, которые будут разрушены
при очистке интерпретатора или “пересчете” проекта. Главным контейнером для
всех объектов QSA служит класс QSProject.
Библиотека QSA имеет специальный механизм разрешения имен объектов: если
объект, “стоящий” первым в иерархии (начиная от уровня приложения), экспорти-
руется для автоматизации, то его имя будет определено методом Application.
QObject: :name(). Если родительские объекты уже присоединены к экспортируе-
мой объектной модели, то дочерний элемент будет доступен как часть этой иерархии.
Четыре главных класса “владеют” скриптовыми возможностями (всего с биб-
лиотекой QSA в версии 1.2 связано тринадцать классов). QSProj ect, как уже было
сказано, служит как контейнер для всех скриптов и интерпретатора, обслуживаю-
щего данный проект. Методы addObject () и removeObject () предназначены для
добавления и удаления объектов в глобальную область видимости. Методы load ()
и loadFromData () позволяют загрузить скрипты из файла или массива байтов:
после загрузки скрипты могут быть вызваны с помощью метода QSInterpreter: :
evaluate () или QSInterpreter:: call (). Также можно использовать методы
save() и saveToData().
Существует два формата записи скриптов: первый, устаревший, — бинарный
формат проекта, созданный для переносимости между платформами. Этот формат
использовался в версии 1.0, и его главным недостатком было отсутствие прозрач-
ности и невозможность редактировать скрипты внешним текстовым редактором.
В последующих версиях применяется второй формат: файл проекта содержит только
указатель на файл скрипта, а сам скрипт находится в отдельном текстовом файле.
Выбрать вариант хранения можно методом QSProj ect: : setStorageMode ().
Тесно связанный с проектом объект интерпретатора QSInterpreter выполняет
главную работу по выполнению скриптовых команд. Главные две команды интер-
претатора, evaluate() и call О, означают “вычислить строку кода” и “вызвать
функцию” соответственно. В коде могут присутствовать любые объекты, сущест-
вующие на момент вызова (переменные, свойства подключенных классов и т.д.).
Если вы генерируете вычисляемую строку на основании данных, вводимых пользо-
вателем, то с помощью метода checksyntax () можете сделать проверку ее кор-
ректности без фактического выполнения.
Класс QSInterpreter обладает такими средствами для исследования объектов в
области видимости, как методы classes (), functions () и variables (): вы мо-
жете исследовать код перед его выполнением для проверки корректности.
Класс QSProj ect предоставляет также специальный редактор скриптов, дос-
тупный в вашем приложении. Метод QSProject: : creat eEdi tor () создает редак-
тор в заданном контексте и открывает в нем заданный скрипт. Методы editors (),
editor () и activeEditor () возвращают список всех открытых редакторов, ре-
дактор, в котором обрабатывается определенный скрипт (если он открыт), и редак-
тор, находящийся в фокусе ввода, соответственно. Методы editorsModif ied() и
scriptModif ied() возвращают значение true в случае, если содержание скрипта
(редактора) было рассинхронизировано с содержанием интерпретатора. Для обнов-
ления данных в интерпретаторе в соответствии с содержимым редактора или, на-
оборот, для отклонения изменений и возврата к текущей версии можно вызвать
один из методов: commitEditorContents () или revertEditorContents ().
Между сигналами и слотами Qt и методами^ЭА можно установить связь, одна-
ко для этого вместо метода QObject: :connect () необходимо использовать метод
addSignalHandler (), а метод removeSignalHandler (), наоборот, отключает об-
работчик от сигнала:
project->addSignalHandler(myButton,SIGNAL(ckieked()),
”a.b.startcalculation”);
При передаче данных между программой и скриптом типы данных будут пре-
терпевать изменения: в основном, типы SQA образуются от типов Qt путем отбра-
сывания начальной буквы “Q”. Например, QRect — Rect, QPixmap — Pixmap и т.д.
Поскольку в скриптах нет “опасных” указателей, то типы-указатели преобразуют-
ся в безопасные дескрипторы, как это принято в современных языках, работающих
в безопасном окружении: QString* — String и т.п.
Сам редактор скриптов, создаваемый методом QSProject: :createEditor(),
является экземпляром класса QSEditor. Это сравнительно легковесный, по срав-
нению с QSA Workbench, элемент управления, который можно непосредственно
встраивать в собственные приложения. Обратите внимание на то, что представляет
собой редактор текста с такими возможностями, как автоотступ, выделение цве-
том, автодополнение служебных фраз и подсказка аргументов функций. Этот класс
обладает методами для выполнения таких операций, как поиск, замена и другие
операции с текстом. С редактором можно связать скрипт типа QSScript для от-
слеживания синхронизации.
Наконец, QSScript, являющийся единицей представления скриптов, распола-
гает такими методами, как createScript (), script (), scriptNames () и
script (), для создания скриптов в заданном контексте и их получения. Методы
setcode (), addCode (), addFunction () делают то, что от них и ожидается: добав-
ляют фрагменты кода или целые функции к вашему скрипту. Кроме того, если
скрипт был создан в связи с каким-то проектом и контекстом, то для него эти зна-
чения можно узнать с помощью методов pro j есt () и context () соответственно.
Мы .рассмотрели те механизмы, которые позволяют загружать и выполнять
скрипты, но не упомянули о самом языке управления. По сути, это диалект
ECMAS, на котором основаны языки JavaScript, JScript и ActiveScript. Мы не бу-
дем отдельно останавливаться на этом диалекте — в QSA нет никаких особых отли-
чий от стандарта: из общих библиотек доступны только Math и System, встроенных
функций около десяти, а также три константы. Вы легко освоите этот язык, обра-
тившись к справочной информации (Language Reference).
В качестве “бесплатного приложения” QSA имеет небольшую библиотеку, кото-
рая позволяет конструировать окна диалога, вызываемые из скриптов. Конечно,
элементы управления QSA не идут ни в какое сравнение с Qt по гибкости использо-
вания: для каждого элемента доступно только значение и, возможно, еще пара
свойств. Вот как может выглядеть текст на Qt Script, создающий окно диалога.
var dial=new Dialog;
dial.captions”Диалог ввода имени”;
dial.okBu11onText ="Done";
dial.cancelButtonText="Abort”;
var first=new LineEdit;
first.label="Имя: ";
dial.add(first);
var last=new LineEdit;
last.label="Фамилия: ";
dial.add(last);
if (dial.exec()) {
var fullName=last.text+" "+first.text;
print(fullName);
}
Окна диалогов QSA доступны через класс QSObj ectFactory, а именно через
подкласс QSInputDialogFactory. Поскольку вызываемые объекты графического
интерфейса в результате (естественно) являются элементами управления Qt, вызы-
вать такие окна диалога можно только из главного “визуального” потока програм-
мы. Вот как выглядит фрагмент кода в главной программе, подключающий эту
возможность к QSA-проекту.
QSProject proj ;
QSlnterpreter *ip = proj-^interpreter();
ip->addObjectFactory( new QSInputDialogFactory );
Обратите внимание на то, что подключение новой фабрики с помощью метода
addObj ectFactory () очищает состояние интерпретатора и удаляет все подклю-
ченные “транзитные” (transient) объекты.
Приложение Д
Обзор встроенной
технологии Qtopia
Как известно, главные “сражения” в области современной вычислительной ин-
дустрии происходят на рынке портативных вычислительных систем, в частности
PDA и, в еще большей мере, смарт-фонов. Для этого сектора компания Trolltech
разработала собственный набор основанных на Qt инструментов, полностью обес-
печивающий работу портативных устройств (предполагается, что на устройстве
уже установлена система Embedded Linux). Важно отметить, что существует как
коммерческая, так и бесплатная версия Qtopia, открытая для добавления функций
сообществом программистов Open Source.
Qtopia— это полноценный набор оконных примитивов, оконная система, а
также набор приложений. В общем, Qtopia функционально напоминает, например,
интерфейс KDE — за исключением того, что Qtopia включает в себя специфические
возможности. Следующие возможности доступны уже сегодня:
оконная система поверх X Windwos;
оболочка синхронизации данных между телефоном (организатором) и персо-
нальным компьютером;
оболочка для разработки приложений;
многоязыковая поддержка и локализация;
поддержка игр и мультимедиа;
приложения PIM (Personal Information Manager — персональный информа-
ционный менеджер, или личная информационная система), например ка-
лендарь, адресная книга, заметки и т.д.;
методы ввода данных для бесклавиатурных компьютеров;
персонализация в виде настроек меню, цветовых схем и т.д.;
приложения класса productivity в виде текстового процессора или
электронной таблицы;
Интернет-приложения, например браузер и почтовый клиент;
интеграция с Java;
поддержа таких беспроводных сетевых технологий, как Wi-Fi или Bluetooth.
И конечно, хотя это и не афишируется, вы можете получить на PDA или
мобильном телефоне настоящий Linux-shell, perl, python или любую другую
оболочку, доступную для Embedded Linux. Конечно, данная возможность скрыта от
рядового пользователя и, возможно, вообще не доступна, если конструкция уст-
ройства не подразумевает установку новых приложений. Это обычно относится к
смарт-фонам.
Говоря более точно, оболочка Qtopia написана не на “настоящем” Qt, а на его
подмножестве Qt/Embedded (Qte). Эта библиотека включает почти все, что относит-
ся к пользовательскому интерфейсу Qt, а также и к некоторым другим библиоте-
кам. Обратите внимание на то, что Qte отстает в развитии от главного релиза Qt,
так что пока вы можете пользоваться только Qte версии 3.
Одним из самых известных проектов Qtopia является PDA Sharp Zaurus, уже
давно использующий Embedded Linux в качестве операционной системы. Послед-
ний и очень многообещающий проект 2005 года — Archos РМА400, мини-
устройство, включающее PDA с жестким диском Hitachi 30Gb. Это устройство, в
первую очередь, предназначено для записи и воспроизведения мультимедиа,
например видео- или аудиозаписи в нескольких распространенных форматах.
Однако данный прибор оснащен также и всем набором Qtopia, т.е. средствами PIM,
а также встроенным браузером Opera. Специально для устройства разработана
система динамической загрузки приложений, в частности игр. Также совершенно
свободно на сайте разработчика распространяется SDK разработчика и средства
кросс-компиляции для сборки приложений, которые могут быть установлены в
РМА400. Вы можете даже в течение нескольких минут переустановить ядро этой
системы: действительно, массовое распространение этого устройства могло бы
стать прорывом не только Qtopia, но и вообще Linux. Будем ждать и надеяться.
Приложение Е
Программирование в среде Qt
и традиция Unix
Это приложение инспирировано прекрасной книгой Эрика С. Реймонда
“Искусство программирования для Unix”, посвященной тому, как традиционно
создавались программы для Unix и как их должны создавать вы, чтобы оставаться
в рамках этих соглашений.
В этом разрезе Qt не совсем удовлетворяет классическому видению, но в боль-
шей степени отошли от канонов другие библиотеки и среды, например GNOME.
Рассмотрим несколько вопросов, которые вам придется решать при написании
приложений.
Главным базовым принципом Unix, нарушаемым Qt или, вернее, легко нару-
шаемым с помощью Qt, является принцип разделения функциональности и ин-
терфейса. Вы имеете все шансы создавать моноблочные “композиты” из интерфей-
са и функциональности, где одним блоком идет, скажем, обновление баз данных,
там же обрисовывается интерфейс и там же устанавливаются сетевые соединения.
Примером такого известного многоплатформенного приложения (написанного без
Qt, с помощью gtWidgets) является клиент сети P2P eMule. Несмотря на популяр-
ность приложения и открытый код — читать этот код без отвращения невозможно.
Почитайте этот код и никогда так не делайте. Разделяйте свой код на логические
уровни, стройте взаимодействие приложений, оперируйте модулями — короче, де-
лайте что-то для повышения прозрачности и управляемости кода.
Небольшой дискуссии требует вопрос многопоточного программирования.
В Unix традиционно нет многопоточного программирования — каждая “единица”
функциональности заключена в отдельный процесс (приложение), и их распарал-
леливание является задачей операционной системы. На это есть достаточно много
причин: некоторые из них, например конвейерное соединение стандартных пото-
ков, не имеют сильного влияния в мире Qt. Более важный довод — многопоточ-
ное программирование не эффективно! Известен важнейший пример — сервер
X-Windows, XFree, реально первое приложение с открытым кодом, вокруг которо-
го образовалась среда GNU. В локальной сети с тысячами компьютеров Х-сервер
может обрабатывать миллионы (!) запросов в секунду. Создатели этого приложе-
ния, разумеется, испробовали все мыслимые варианты многопоточности, но в ре-
зультате сервером управляет большой цикл.
Чтобы еще немного мотивировать вас, хочу обратить ваше внимание вот на что:
процессор компьютера никогда не простаивает! Если вы думаете, что система в мо-
мент возврата ей управления по какому-то методу sleep () или wait () делает что-
то важное, то вы ошибаетесь: система выполняет такие же пустые циклы простоя,
как выполняли бы вы в своем приложении. Любой компьютер находится в вечном
цикле, как находился в нем первый восьмибитовый Nintendo на MOS-процессоре.
С другой стороны, система вовсе не нуждается, чтобы вы отдавали ей управле-
ние, как это было в Windows 3.11. Система сама получает управление, когда ей
“вздумается”, даже не ставя вас в известность. В результате потоки имеют значе-
ние, в частности, для ввода-вывода, но не для повышения производительности.
Иными словами: “слушайте” систему, получайте запросы и удовлетворяйте их не-
медленно и прямолинейно, а именно так, как это делает Х-сервер, — вот метод
Unix.
С многопоточным программированием связан и вопрос взаимодействия потоков
и приложений, так называемый вариант IPC (interprocess communication — меж-
процессное взаимодействие). Важная опасность потоков — они провоцируют вас
использовать такой локальный вариант IPC, как блокировки, общие переменные и
т.д. Если вы создаете потоки как заменители процессов, т.е. для распараллелива-
ния и баланса нагрузки, то, возможно, локальное IPC-взаимодействие является
плохой идеей. Не будем особо останавливаться на сложности отладки 1РС-кода —
хотя это так и есть.
Рассмотрим потенциальный проект сервера приложений. Вы получаете массу
запросов, которые поступают в менеджер нагрузки: это главный поток, который
управляет другими, рабочими потоками посредством локального 1РС-средства.
Вопрос: как модифицировать наше приложение для работы в виде кластера?
Кластеры, блейд-серверы, матрицы (grids) и прочие механизмы массового парал-
лелизма— обычный и самый экономичный метод наращивания мощности и на-
дежности сетевых приложений (вспомните, как кластеры Yahoo предотвратили
DOS-атаку на этот портал), так что вы, возможно, тоже в один момент захотите их
использовать.
Теперь представьте, что ваше приложение написано в виде нескольких процес-
сов (отдельно написанных программ) — одна из них, возможно, управляет осталь-
ными или же они сами “устраивают выборы” для баланса нагрузки. Такие про-
граммы не могут использовать общую память и вынуждены общаться между собой,
например, с помощью сокетов. Это немного повышает стоимость взаимодействия —
но зато все экземпляры рабочего процесса могут находиться на разных компьюте-
рах. Отличная идея! Хороший пример такого подхода — сервер Cyrus, состоящий
из главного процесса и множества процессов POP3, SMTP и IMAP.
Более изысканный способ работы приложения — выбор метода передачи дан-
ных динамически, на основе локальной или удаленной политики. Именно так сей-
час работает сервер XFree, используя “ускоренный” метод для передачи больших
объемов данных в локальном контексте.
Небольшое замечание относительно удаленного взаимодействия. Существует
две основные концепции: классический протокол RPC1 (CORBA, JMI) и сокеты.
Можно сказать, что это протоколы разного уровня, и отчасти так оно и есть, но
конкурируют они на одном уровне. Несмотря на то что некоторые приложения в
мире Linux (NFS, GNOME) используют RPC, в общем случае — это против правил
Unix. Обратите внимание на то, что первая версия KDE, так же как и GNOME, ис-
пользовала CORBA, но “тяжесть” последнего RPC привела к тому, что в версии
KDE 2 этот протокол был заменен на более легковесный. Используйте чужой опыт.
Выбор Unix — текстовые прозрачные протоколы поверх сокетов, как это реали-
зовано в HTTP, POP, IMAP и т.д. Шифрование таких потоков — всегда внёшняя
функция, реализуемая стандартными механизмами, такими как ssl. Существует
несколько известных форматов таких сообщений, например протокол ВЕЕР или
тот же HTML, которые вы можете использовать в своих приложениях. Использо-
вание HTML имеет важное преимущество: вы можете тестировать свой сервер с
помощью любого браузера. Если вам так уж необходимы сложные структуры пере-
даваемых сообщений, то всегда есть возможность задействовать гибридные RPC-
протоколы поверх XML, такие как SOAP или Jabber. Эти протоколы, хоть и
с большим трудом, но также могут быть прочитаны человеком, а значит, вам не по-
надобится создавать специальные приложения отладки.
Наконец, обратим внимание на программирование баз данных. Фактически вы
можете формировать команды SQL прямо в тексте своей программы: так, как это
часто делают в Visual Basic. Также у вас есть все возможности для прямого редак-
тирования таблиц через драйвер баз данных. Но вы должны как следует подумать,
прежде чем “скатиться” к такому стилю программирования. Возможно, лучшим
решением будет создание собственного сервера приложений. Сервер приложений
будет скрывать реализацию доступа к данным: например, имеет ли смысл созда-
вать базу данных пользователей и их паролей, если этих пользователей может быть
около десяти и для этого можно прочитать обычный файл? Вы ничего не слышали о
взломе баз данных пользователей? В случае сервера приложений вы легко создади-
те прототип приложения, используя обычные файлы или даже константы в про-
грамме, а позже перейдете к таким более сложным методам доступа, как SQL.
Кроме того, сервер приложений легко можно интегрировать с шифрованием
данных и такими системами аутентификации, как Cyrus SASL. Только представь-
те, сколько человеко-лет вам понадобится, чтобы реализовать подобный меха-
низм, — при том, что он почти наверняка получится хуже. Как уже было сказано,
для общения с сервером приложений желательно использовать простые текстовые
потоки через сокеты: шифрованием займется SSL (Secure Sockets Layer — протокол
защищенных хюкетов), а гарантированной доставкой — TCP (Transmission Control
Protocol — протокол управления передачей).
1 RPC — Remote Procedure Call (удаленный вызов процедуры). — Примеч.ред.
Программирование в среде Qt и традиция Unix
221
Приложение Ж
Лицензирование продуктов
компании Trolltech и правила
использования Qt
Я решил осветить данный вопрос, поскольку надеюсь, что вы действительно бу-
дете писать реальные приложения на Qt. Как говорят в Америке: “I mean it”, т.е.
именно это я и имел в виду, начиная эту книгу. Поэтому вы должны оценить те
рамки, в которых ваше приложение остается некоммерческим проектом Open
Source.
Версия Qt 4 имеет версии Open Soucre для всех платформ (в том числе и для MS
Windows), доступные для загрузки с сайта по адресу:
http://www.trolltech.com/download/opensource.html.
При использовании этой версии вы соглашаетесь с такими условиями:
вы обязуетесь предоставлять конечным пользователям открытый код своей
программы;
конечный пользователь может повторно использовать этот код, модифици-
ровать и распространять его;
вы не будете требовать компенсации за повторное использование или распро-
странение вашего кода;
к коду должна быть добавлена лицензия GPL (General Public License —
общедоступная лицензия), на основе которой он распространяется.
Может возникнуть закономерный вопрос: что делать, если вы или ваш заказчик
не желает предоставить открытый код программы? Ответ прост: нужно использо-
вать коммерческую версию библиотеки. То же относится к случаю, если вы исполь-
зуете закрытый код хотя бы в одной части своего приложения. Кстати, некоторые
драйверы баз данных, инструменты Qt Visual Studio Integration и поддержка
Trolltech также доступны только в коммерческой версии. С другой стороны, если
вы разрабатываете продукт в коммерческой версии, то не должны использовать
код, написанный с помощью версии Open Source, — это нарушение лицензии GPL.
Согласно терминологий Troltech, вы должны выбрать тйп лицензирования еще до
написания первой строки кода.
Надеюсь, это будет отличный код!
Предметный указатель
2
2О-графика, 117
А
Accepted, константа, 104
acquire(), 182
actionTriggered(), сигнал, 100
activate(), 57
activeEditor(), 213
ActiveX, 185
addAction(), 98
addBindValue(), 166
addDatabase(), 162
addMenu(), 98
addObject(), 212
addObjectFactory(), 215
addSeparator(), 98
addSignalHandlerQ, 213
addTab(), 102
animateClick(), 86
append(), 50; 94; 164
appentChildO, 176
Archos PMA400, 218
areaPoints(), 130
areaPointsAdvanced(), 130
at(), 50
ATL, 23
autoExclusiv$, 88
autoExclusive, свойство, 86; 90
availableO, 182
AWE, 21
В
Background, свойство, 64
bash, командная оболочка, 46
BEEP, 221
beginTrasaction(), 160
bindValue(), 166
Bluetooth, 217
BMP, 140
Borland Delphi, 17
Borland Interbase, 159
BottomToTop, константа, 57
boundingRect(), 130
boundValue(), 166
brush(), 129
BSD, 14; 204
ButtonText, свойство, 64
C
callback-функция, 28
Canvas, 118
CDATA, 173; 177
CDE, 189
changed(), сигнал, 97
changeEventO» событие, 73
checkable, свойство, 86
checkStateO, 87
checkSyntax(), 213
classes(), 213
clear(), 40; 50; 164
clearValues(), 164
click(), 86
clicked(), сигнал, 86
clicked(), событие, 87
cJoseContext(), 137
commandFinished(), 157
commandStartedO, 157
CONFIG, ключ, 194; 196
connect(), 181
connectResize(), 143
connectStatusQ, 143
connectUpadate(), 143
constBegin(), 52
constEnd(), 52
contains(), 50; 54; 162; 163
CORBA, 221
count(), 50; 110
create(), 161; 190
createResult(), 160
currentChanged(), событие, 103
currentlndex(), 102; 110
currentRow, свойство, 110
currentSectionO, метод, 102
currentTablndexO, 102
currentWidgetO, 102
Cyrus, 220
Cyrus SASL, 221
D
database(), 162
databaseText(), 162
dataTransferProgress(), 157
dataTransferProgress(), событие, 156
DEBUG, ключ, 194
decimals, свойство, 101
deleteLater(), 182
deleteRowFromTable(), 169
Delphi, 203
Delphi Indy Networking, 157
Designer, 94; 204
Designer, палитра, 102
DirectX, 118
displayedSections(), метод, 102
displayText(), 93
DNS, 158
DOM, 171; 174
done(), 157
done(), сигнал, 156
Drag-and-Drop, 76
dragEnterEvent(), 79
DraggingState, константа, 108
Drawing plane, 118
driverText(), 162
dropEvent(), событие, 79
E
echoMode(), 92
EditingState, константа, 108
edi tltem(), 112
editor(), 213
editors(), 213
editorsModified(), 213
Embedded Linux, 217
Embedded Linux+Qtopia, 139
emit, 32
enabled, свойство, 71
endPoint(), 129
enebledChangeO, событие, 73
ehterEvent(), событие, 72
EnumWindows, функция, 28
EnumWindowsProc, функция, 28
erase(), метод, 60
error(), 156
errorString(), 156
exec(), 104; 105; 161; 164; 166
expandable, свойство, 112
F
Fedora Core 4,16; 205
fetch(), 161
field(), 163
fieldName(), 163
finished(), сигнал, 181
Flash Animation, 60
FlashGet, 157
flow, свойство, 109
focusProxy(), 76
fontChangeO, событие, 73
fontlnfo(), 67
fontMetrics(), 67
Foreground, свойство, 64
formatValue(), 160
FoxPr, 167
FreeBSD, 20
functions(), 213
G
gcc, 195
GDK, 22
getExistingDirectoryO, 106
gethostbynameO, 158
getOpenFileNameO, 106
getOpenFileNames(), 106
GetRight, 157
getSaveFileName(), 106
GIMP, 22
GIMP Drawing Kit, 22
GIMP Toolkit, 22
GLUT, 133
GNOME, 20; 221
GNU, 20; 22; 219
GPL, 16; 55
grep(), 45
group, свойство, 86
Gtk, 22
н Java Runtime Engine, 21
hasFeature(), 160 hasHtml(), 79 hasNext(), 51 hasPendingConnection(), 154 hasPendingDatagram(), 153 hasPrevious(), 51 HEADERS, ключ, 194 hidden, свойство, 70 hide(), слот, 70 hideEvent(), событие, 70 horizontalHeader(), 111 hovered(), сигнал, 97 JMI, 221 JPEG, 140 JRE, 21 К KDE, 17; 20; 189; 204; 217; 221 key(), 52 keys(), 190 Kylix, 203 L languageChangeO, событие, 73
I lazyChildCount, свойство, 113 Ibqsa, библиотека, 212
IBM DB2,159 iconSet(), 85 IMAP, 220 IMAP4,157 Implicit sharing, 40 indexOf(), 50; 163 initializeOverlayGLO, 135 inputMask, свойство, 92 insert(), 50; 93; 164 insertAfter(), 176 insertBefore(), 176 insertRowIntoTable(), 169 insertRows(), 169 instance(), 191 IPC, 220 IP-сокет, 152 is Active Window, метод, 80 isDesktop(), 80 isDialog(), 80 isDirty(), 169 isEmptyO, 40 isEnabledTo(), 71 isForwardOnlyO, 166 isMenuButton(), 87 isNull(), 40 isOpen(), 162 isOpenError(), 162 isPopup(), 80 isTopLevel(), 80 leaveEventO, событие, 72 leftJustifiedO, 44 LeftToRight, константа, 57 Libc, 23 lineWrapColumnOrWidth, свойство, 95 Linguist, 207 Linux, 14; 204 listen(), 153 load(), 212 loadFromData(), 212 localAddress(), 152 localPort(), 152 lock(), 182 lockForRead(), 183 lockForWrite(), 183 lupdate, утилита, 208 M Mac OS X, 200 MacOS, 15; 203 MacOS X, 201 Macromedia Flash, 61; 118 makeCurrent(), 135 Makefile, 201 makeOverlayCurrent(), 135 margin, свойство, 57 matrix(), 62 maxLength(), свойство, 92 Mesa, 197
J Microsoft .NET. Common Runtine, библиотека, 46
Jabber, 221 Java, 21 Java Net Library, 157 Microsoft Visual Studio, 193 moc, препроцессор, 31 model(), 114
Motif, 189
mouseDoubleClickEvent(), 73
mouseMoveEvent(), событие, 72
mousePressEvent, событие, 73
mouseReleaseEvent, событие, 73
move(), 50
moveEvent(), событие, 73
movement, свойство, 109
MS Access, 166
MS SQL, 159
N
Network, модуль, 151
newConnection(), сигнал, 154
next(), 51
nextPendingConnection(), 153
NFS, 221
Nintendo, 220
шпаке, 201
nodeName(), 175
nodeType(), 174
nodeValue(), 175
NoState, константа, 108
numRowsAffectedO, 161; 165
О
ODBC, 166
open(), 160
OpenGL, 133; 197
Opera, браузер, 218
Oracle, 166
Oracle Call Interface, 159
P
pageStep, свойство:, 99
paintEvent(), 60; 73
paintGL(), 134
paintOverlayGL(), 135
paletteChangeO, событие, 73
PalmOS, 203
parentNodeO, 175
parentWidget(), 57; 80
PCH, 201
PDA, 20
PDA Sharp Zaurus, 218
peekNext(), 51
peekPrevious(), 51
peerAdressQ, 152
peerPort(), 152
pen(), 129
pendingDatagramSize(), 153
Perl, 46; 211
PIM, 217
POP3,157; 220
popup(), 87
PRECONPILED_HEADER, ключ, 202
prepareO, 161; 167
prepend(), 50
previous(), 51
primarylndex(), 160
proper tyChangedO, 137
Q
Q_CLASSINFO, макрос, 187
Q_DECLARE_INTERFACE, макрос, 191
Q_EXPORT_PLUGIN(), макрос, 190
QINTERFACES, макрос, 191
Q_OBJECT, макрос, 31; 89; 212
Q_PROPERTY, макрос, 34; 186
Q3Painter, класс, 63
Q3Signal, класс, 32
Q3TabDialog, класс, 103
Q3ValueVector, класс, 53
QAbstractButton
setText(), 85
text, свойство, 85
QAbstractButton, класс, 85
QAbstractltemDelegate, класс, 114
QAbstractltemModel
index(), 108
QAbstractltemModel, класс, 108; 114; 167
QAbstractltemView, класс, 107; 109
QAbstractListModel, класс, 109
QAbstractScrollArea, класс, 107
QAbstractSlider
dialMoved(), сигнал, 100
SliderOrientationChange, событие, 100
valueChanged(), сигнал, 100
QAbstractSlider, класс, 99
QAbstractSocket
connectToHost(), 152
QAbstractSocket, класс, 152
QAbstractSpinBox, класс, 100
QAbstractTableModel, класс, 167
QAction
triggeredO, сигнал, 98
QAction, класс, 96
QApplication
lastWindowClosed(), сигнал, 104
setPaletteO, 65
setStyle(), 81
startDragDistace, свойство, 77
startDragTime, свойство, 77
style(), 81
Qapplication, класс, 36
QAxAggregated, класс, 187
QAxBase, класс, 188
QAxBindable, класс, 187
QAxContainer, класс, 187
QAxFactory
isServer(), свойство, 186
QAxFactory, класс, 187
QAxObject, класс, 188
QAxScript, класс, 189
QAxScriptManager, класс, 189
QAxWidget, класс, 188; 189
QBoxLayout, класс, 57
QBrush, класс, 65
QBuffer, класс, 146; 147
QButton
text(), 85
QButton, класс, 85
QByteArray, класс, 143; 147
QCanvas
advance(), 121; 127
allltems(), 122
backgroundColor(), 119
backgroundPixmapO, 119
chunkSize(), 120
draw Area», 122
height», 119
onCanvasO, 119
rect(), 119
resize(), 119
resized, сигнал, 119
retune(), 120
setAdvancePeriod(), 121
setAHChangedO, 121
setBackgroundColorO, 119
setBackgroundPixmap(), 119
setChangedO, 121
setDoubleBuffering(), 122
setTiles, 118
setUnchanged(), 121
setUpdatePeriod(, 121
sizeO, 119
update(), слот, 120
validChunk(), 119
width(), 119
QCanvasEllipse, класс, 129
QCanvasItem
advance(), 127
advice(), 121
animated(), 121; 127
boundingRect(), 128
boundingRectAdvanced(), 128
canvas(), 126
collidesWithO, 127
collisionsO, 127
move(), 127
moveBy(), 127
rtti(), 128
RttiValues, перечисление, 128
setAnimated(), 127
setCanvasO, 126
setVelocity(), 127
setXVelocity(), 127
setYVelocityO, 127
xVelocityO, 127
yVelocityO, 127
QCanvasItem, класс, 117; 125
QCanvasLine, класс, 129
QCanvasPolygon
areaPoints(), 130
points(), 130
setPoints(), 130
QCanvasPolygon, класс, 130
QCanvasPolygonalltem
setWinding(), 130
QCanvasPolygonalltem, класс, 118; 129
QCanvasRectangle
rtti(), 129
QCanvasRectangle, класс, 129
QCanvasSpline
closed(), 131
controlPoints(), 131
setControlPoints(, 131
QCanvasSpline, класс, 130
QCanvasSprite
setFrame(), 131
QCanvasSprite
FrameAnimationType,
перечисление, J 32
boundingRect(), 131
frame(), 131
frameCountO, 131
height(), 131
imageO, 131
imageAdvancedO, 131
move(), f$f
setFrameAnimation(), 132
setSequenceO, 131
width(), 131
QCanvasSprite, класс, 117; 131
QCanvasText, класс, 132
QCanvasView
canvas(), 123
inverseWorldMatrix(), 123
setCanvas(), 123
setWorldMatrixQ, 123
worldMatrix(), 123
QCanvasView, класс, 117; 122
QCheckBox
setTristate(), 87
QCheckBox, класс, 87
QCloseEvent, класс, 71
QColor, класс, 64
QColorDrag, класс, 79
QColorGroup
ColorRole, тип, 64
QColorGroup, класс, 64
QComboBox
currentText(), 110
lineEdit(), метод, 110
QComboBox, класс, 110
QConicalGradient, класс, 62
QCoreApplication
addLibraryPathQ, 190
translateO, 210
QCoreApplication, класс, 180
QDataSource, класс, 143
QDataStream
version(), 150
QDataStream, класс, 150
QDateEdit, класс, 102
QDateTimeEdit, класс, 101
QDecorationPlugin, класс, 190
QDial, класс, 100
QDialog, класс, 103
QDir, класс, 149
QDirectPainter, класс, 63
QDir View, класс, 113
QDns,класс, 158
QDockArea, класс, 97
QDockWidget, класс, 97
QDomAttr
name(), 177
value(), 177
QDomAttr, класс, 176; 177
QDomCDATASection, класс, 177
QDomComment, класс, 177
QDomDocument, класс, 176
QDomDocumentFragment, класс, 176
QDomDocumentType, класс, 176
QDomElement, класс, 176; 177
QDomEntity, класс, 176
QDomEntity Reference, класс, 176
QDomFragmentNode, класс, 176
QDomNamedNodeMap, класс, 176
QDomNode, класс, 174
QDomNotation, класс, 177
QDomProcessinglnstruction, класс, 177
QDomText
splitText(), 175
QDomText, класс, 176; 177
QDoubleSpinBox, класс, 101
QDoubleValidator, класс, 92
QDrag
setHotSpot(), 77
setMimeData(), 76
start(), 78
QDrag, класс, 76
QDragEnterEvent, класс, 78
QDragMoveEvent, класс, 78
QDragObject, класс, 76; 79
QDriver, класс, 160
QDropEvent, класс, 78
QEvent
EnabledChange, событие, 71
FontChange, событие, 67
QEventLoop, класс, 181
QFile
open(), 149
QFile, класс, 146; 147
QFileDialog, класс, 106
QFilelnfo, класс, 148; 149
QFocusData, класс, 74
QFont
deciPointSizeO, 68
insertSubstitution(), 68
pixelSizeQ, 68
PreferDefault, константа, 69
setBold(), 69
setFamily(), 69
setltalic(), 69
setOverlineO, 69
setPixelSize(), 68
setPointSize(), 68
setPointSizeFloat(), 68
setStretch(), 69
setStrikeOutO, 69
setStyleHint(), 69
setStyleStrategyO, 69
setUnderlineO, 69
setWeight(), 69
substituteO, 68
substitutions(), 69
QFontDatabase, класс, 70
QFontlnfo, класс, 69
QFrame, класс, 90; 103; 107
QFtp, класс, 157
QGL, класс, 133
QGLColormap, класс, 133
QGLContext
create(), 137
QGLContext, класс, 133; 137
QGLFormat
defaultFormat(), 136
hasOverlay(), 137
stereo(), 137
QGLFormat, класс, 133; 136
QGLWidget
initializeGL(), 134
setAutoBufferSwap(), 135
QGLWidget, класс, 133
QGradient, класс, 62
QGridLayout, класс, 57
QGroupBox, класс, 90
Qhash, класс, 54
QHBoxLayout, класс, 57
QHeader, класс, 111
QHeaderView
sectionMoved(), сигнал, 111
QHeaderView, класс, 111
QHostAddress, класс, 158
QHostlnfo, класс, 158
QHttp, класс, 155
QHttpRequestHeader, класс, 155
QHttpResponseHeader, класс, 156
QlconView, класс, 109
Qlmage
bitOrder(), свойство, 141
colorTable(), свойство, 141
convertDepth(), 141
copy(), 141
createHeuristicMask(), 141
depth(), 141
height0, свойство, 141
mirror(), 141
scale(), 141
setPixel(), 141
smoothScaleO, 141
width(), свойство, 141
xForm(), 141
Qlmage, класс, 141
QtmageDrag, класс, 79
QlmagelO, класс, 140
QIntValidator, класс, 92
QIODevice
openMode(), 146
OpenMode, перечисление, 145
waitForBytesWritten(), 147
waitForReadyRead(), 147
QIODevice, класс, 145; 156; 174
QltemDelegate, класс, 115
QltemSelection, класс, 53
QLabel
setMovie(), 143
setNum(), 91
QLabel, класс, 90
QLayout
addltem(), 56
addWidget(), 56
QLayout, класс, 56
QLayoutltem, класс, 56
QLCDNumber, класс, 107
QLinearGradient, класс, 62
QLineEdit, 92
QLinkedList, класс, 49; 53
QList
popJbackO, 50
pop_frontO, 50
push_backO, 50
push_frontQ, 50
Qlist, класс, 45
QListBox, класс, 107; 109
QListlterator, класс, 51
QListView, класс, 107; 109
QListWidget insertltem(), 110 QListWidget, класс, 107; 109 QListWidgetltem, класс, 110 QMainWidget, класс, 97 QMain Window, класс, 98 qmake, утилита, 193 QMAKESPEC, 195; 201 Qmap, класс, 53 QMenu addActionO, 96 hovered(), сигнал, 98 triggeredO, сигнал, 98 QMenu, класс, 98 QMenuBar, класс, 98 QMenuData, класс, 98 QMimeData setData(), 79 QMimeData, класс, 76; 79 QModellndex child(), 108 columnO, 108 data(), 108 isValid(), 108 parent(), 108 row(), 108 sibling(), 108 QModellndex, класс, 108 QMovie finished(), 143 pause(), 143 paused(), 143 unpause(), 143 QMovie, класс, 143 QMultiHash, класс, 54 QMultiMap, класс, 54 QMutableListlterator, класс, 51 QMutex, класс, 182 QObject connect(), 32; 213 deleteLater(), 155 disconnect), 32 tr(), 209 QObject, класс, 27; 50; 55; 143; 181; 191; QPainter CompositionMode, константа, 63 drawPath(), 63 PaintEvent(), 61 QPainter, класс, 61; 63 QPainterPath, класс, 62 QPaintEvent region(), 61 QPalette brush(), 65 color(), 65 dark(), 65 foregroundO, 65 light), 65 setActive(), 64 setBrush(), 65 setDisabled(), 64 setlnactive(), 64 QPalette, класс, 64 QPixmap convertFromlmageO, 141 convertToImage(), 141 createHeuristicMaskO, 141 height(), 140 load(), 140 save(), 141 size(), 140 width(), 140 QPixmap, класс, 140 QPluginLoader, класс, 190; 191 QPoint, класс, 124 Qpolygon, класс, 53 QPolygonF, класс, 53 QPopupMenu, класс, 87 QProcess, класс, 145 QProgressBar, класс, 156 QProxyModel setModelO, 114 QProxyModel, класс, 114 QPushButton isDefaultO, 87 setDefault(), 87; 104 QPushButton, класс, 87 Qqueue, класс, 53
209; 212 qobject_castO, 131 QPaintDevice, класс, 55 QRadialGradient, класс, 62 QRadioButton, класс, 88 QRangeControl, класс, 99; 100 QReadWriteLock, класс, 183 QRect, класс, 119
QRectangle, класс, 61
QRegExp
cap(int), свойство, 48
capturedText(), свойство, 48
escape(), 48
exactMatch(), 48
matchedLength(), свойство, 48
search(), 48
searchRev(), 48
setCaseSensitive(), 48
setWildcard(), 48
QRegExp, класс, 46; 48
QRegExpValidator, класс, 92
QRegion
boundingRect(), 61
contains(), 61
rects(), 61
unite(), 60
QRegion, класс, 61
QRgb, тип, 64
QSA, 211
QScript, класс, 189
QScrollBar, класс, 100
QScrollView, класс, 100; 122
QSEditor, класс, 214
QSemaphore, класс, 182
Qset, класс, 54
Qsignal, класс, 32
QSInputDialogFactory, класс, 214
QSInterpreter
addTransientObject(), 212
call(), 213
defaultlnterpreter(), 212
evaluate(), 213
QSInterpreter, класс, 212; 213
QSize, класс, 119
QSizePolicy
setHorStretch(), 59
setVerStretchO, 59
QSizePolicy, класс, 58
QSlider, класс, 100; 114
QSObjectFactory, класс, 214
QSocketDevice, класс, 151
QSocketLayer, класс, 151
qsort, функция, 27
QSound
isFinfshedO, 142
play(), 142
setLoops(), 142
QSound, класс, 142
QSpacerltem, класс, 56
QSpinBox, класс, 100; 101
QSProject
addObjectO, 212
createEditorQ, 213; 214
setStorageMode(), 213
QSProject, класс, 212; 213
QSql
TableType, перечисление, 163
QSqlDatabase
drivers(), 161
isDriverAvailable(), 161
lastError(), 162
registerSqlDriver(), 161
tables(), 163
QSqlDatabase, класс, 161
QSqlDriver, класс, 160
QSqlDriverPlugin, класс, 161; 190
QSqlError
text(), свойство, 162
QSqlError, класс, 162
QSqlField, класс, 164
QSqlQuery
bindValueO, 166
bound Values(), 166
first(), 165
isActive(), 164
isSelect(), 165
isValid(), 165
last(), 165
next(), 165
prepare(), 166
prev(), 165
seek(), 165
setForwardOnly(), 166
size(), 160; 165
vahie(), 166
QSqlQuery, класс, 164
QSqlQueryModel, класс, 167
QSqlRationalTableModel, класс, 170
QSqlRecord
count(), 163
field(), 164
QSqlRecord, класс, 163
QSqlRecordlnfo, класс, 163
QSqlRelation, класс, 170
QSqlRelationalTableModel, класс, 167
QSqlResult, класс, 160
QSqlTableModel, класс, 167
QSScript, класс, 214
Qstack, класс, 53
QStackedLayout, класс, 57
QStackedWidget, класс, 103
QStandardltemModel, класс, 114
QStrIList, класс, 44
QString
append(), 39
arg(), 42
ascii(), 43
at(), 41
capacityO, 44
compare(), 41
contains(), 42
count(), 42
endsWith(), 41
fill(), 40
find(), 42
findRev(), 42
fromAsciiO? 43
fromLocal8Bit(), 43
fromUcs2(), 43
fromUtf8(), 43
indexOf(), 42
insert(), 39
lastIndexOf(), 42
latinl(), 43
left(), 41
leftJustifyO, 41
length(), 40
local8Bit(), 43
localeAwareCompareO, 41
lower(), 41
midO, 41
number(), 43
prepend(), 39
remove(), 39
replace(), 42; 48
reserve(), 44
resizeO, 44
right(), 41
rightJustify(), 41
setAscii(), 43
setLatinl(), 43
setLength(), 44
setNum(), 43
setUnicodeO, 43
setUnicodeCodes(), 43
simplifyWhiteSpaceO, 41
sprintf(), 42
squeeze(), 44
startsWith(), 41
stripWhiteSpaceO, 41
tolnt(), 43
toShort(), 43
toULongLong(), 43
truncate(), 40
unicode(), 43
upper(), 41
utf8(), 43
Qstring, класс, 39
QStringList
append(), 45
fromStrList(), 45
join(), 45
split(), 45
QStringList, класс, 44; 53
QStringListModel, класс, 109
QStrList, класс, 44
QStyle
ComplexControl, перечисление, 82
ContentsType, перечисление, 82
ControlElement, перечисление, 82
DrawPrimitive(), 81
PixelElement, перечисление, 82
PrimitiveElement, перечисление, 81; 82
StyleFlags, перечисление, 83
StyleHint, перечисление, 83
QStyle, класс, 81; 190
QStylePainter, класс, 63
QStylePlagin, класс, 190
Qt, 20
Alignment, тип, 92
Caseinsensitive, константа, 41
CaseSensitive, константа, 41
CaseSensitivity, тип, 41
ChackState, тип, 87
DropAction, вектор флагов, 78
DropAction, перечисление, 78
QRepaintNoErase, флаг, 60
Qt Designer, 23; 195
Qt Embedded, 20
QT_TR_NOOP, макрос, 210
QT_TRANSLATE_NOOP, макрос, 210
QTabBar, класс, 103
QTabDialog, класс, 103
QTable, класс, 107; 111
QTableView, класс, 110
QTableWidget
setHorizontalHeaderItem(), 112
setltem(), 111
QTableWidget, класс, 107; 110; 111
QTableWidgetltem, класс, 112
QTabWidget, класс, 102
QTcpServer, класс, 153
QTcpSocket, класс, 146; 152; 153
Qte, 218
QTextCursor
insertText(), 96
QTextCursor, класс, 95
QTextDocument, класс, 95
QTextDrag, класс, 79
QTextEdit, класс, 93; 95
QTextStream, класс, 150
QThread
currentThread(), 181
QThread, класс, 180
QTimeEdit, класс, 102
QToolBar, класс, 88; 97
QToolButton
setAutoRaise(), свойство, 88
QToolButton, класс, 88
Qtopia, 20; 217
QTreeWidgetltem, класс, 113
Quasar Technologies, 20
QUdpSocket, класс, 152; 153
queryin terface(), 187
QUrlOperator, класс, 151
QValidator, класс, 92
QValueList, класс, 45; 49
QVariant, класс, 114
QVBoxLayout, класс, 56; 57
Qvector, класс, 53
Q Wait Condition, класс, 183
QWidget
baseSize(), 81
changeEvent(), событие, 81
clearFocusQ, 76
ClickFocus, константа, 75
close(), 104
drawText(), 66
event(), 71
focusNextPrevChild(), 74
hasFocus(), 76
isFocusEnabled(), 76
mapFromO, 59
mapFromParent(), 60
mapTo(), 59
mapToParentO, 60
nextInFocusChain(), 74
NoFocus, константа, 75
palette, свойство, 65
repaint(), 60
setFocus(), 76
setFocusPolicy(), 74
setMaximumHeight(), 58
setMaximumSizeO, 58
setMaximumWidth(), 58
setMinimumHeightO, 58
setMinimumSize(), 58
setMinimumWidth(), 58
setMouseTracking(), 72
setSizeIncrement(), 80
sizePolicy, свойство, 57
StrongFocus, константа, 75
TabFocus, константа, 75
update(), 60
QWidget, класс, 55; 76; 90; 103; 107
QWidgetltem
expandingDirections(), 57
QWidgetltem, класс, 56
QWidgetStack, класс, 103
QWMatrix
invert(), 124
Invertable(), 124
isldentity(), 124
map(), 124
mapRect(), 125
reset(), 124
rotate(), 124
scale(), 124
setMatrix(), 123
setTransformationMode(), 125
shear(), 124
transformationMode(), 125
translate(), 124
QWMatrix, класс, 123
QXmlContentHandler
endElement(), событие, 172
setDocumentLocator(), 173
skippedEntity(), событие, 174
startDocument(), событие, 172
startElement(), событие, 172
QXmlContentHandler, класс, 172
QXmlDeclHandler
attributeDecl(), 172
extemalEntityDecl(), 172
internalEntityDecl(), 172
QXmlDeclHandler, класс, 172
QXmlDefaultHandler, класс, 172
QXmlDTDHandler
notationDecl(), 172
unparsedEntityDecl(), 172
QXmlDTDHandler, класс, 172
QXmlEntityResolver
resolveEntity(), 172
QXmlEntityResolver, класс, 172; 174
QXmlErrorHandler, класс, 173
QXmllnputSource
next(), 174
setData(), 174
QXmllnputSource, класс, 174
QXmlLexicalHandler
startCDATAQ, событие, 173
startDTD(), событие, 173; 174
startEntity(), событие, 174
QXmlLexicalHandler, класс, 173
QXmlReader
contentHandler(), 173
setDTDHandler(), 173
setFeature(), 173
QXmlReader, класс, 172
QXmlSimpleReader, класс, 173
QCanvas
resize(), 118
tile(), 118
tileHeight(), 119
tileWidth(), 119
QCanvas, класс, 117
R
rangeChanged(), сигнал, 99
rawCommand(), 157
rawCommandReplayO, сигнал, 157
readDatagram(), 153
record(), 160
Rejected, константа, 104
relation(), свойство, 170
release(), 182
remove(), 51; 164
removeAt(), 50
removeltem(), 110
removeObject(), 212
removeRow(), 169
removeRows(), 169
removeSignalHandler(), 213
repaintO, 73
replace(), 50
request(), 155
requestPropertyChangeO, 187
resizeEvent, событие, 73
resizeGL(), 134
resizeOverlayGL(), 135
resolveSylinks, свойство, 113
responseHeaderReceived(), событие, 156
result()T 104
retumPressedO, сигнал, 92; 93
revert(), 169
revertAll(), 169
RGB, 106
RGBA, 136
rollbackTransaction(), 160
rotate(), 62
RPC, 221
RTTI, 174
run(), 180
S
SAX, 171
SAX Reader, 171
scale(), 62
Scribe, 95
scriptModified(), 213
setAccel(), 86
setAcceptDrops(), 78
setActiveWindwow(), 80
setAlignment(), 92
setAutoRepeat(), 88
setBackgroundRole(), 65
setBrush(), 129
setCaption(), 80
setCheckable(), 86; 88
setCheckedO, 86
setCheckState(), 87
SetColumnCount(), 113
setDocument(), 94
setEditStrategy(), 169
setEnabled(), метод, 71
setExtension(), 105
setFileName(), 147
setFilter(), 169
setFixedHeight(), 58
setFixedSize(), 58
setFixedWidthO, 58
setFocusProxyO, 76
SetFont(), 66
setForegroundRole(), 65
setGenerated(), 164
setHidden(), метод, 70
setHost(), 155
setlcon(), 80; 85
setIconText(), 80
setImageData(), 79
setMaxPendingConnections(), 154
setMenu(), 87
setNullQ, 164
setOrientation(), 105
setPaletteO, 65
setPaletteBackgroundColorO, 65
setPaletteBackgroundPixmapO, 65
setPaletteForegroundColor(), 65
setPen(), 129
setPixmap(), 77; 85
setPoints(), 129
setPopup(), 87
setQuery(), 161
setReadOnly(), 164
setRelation(), 170
setRequiredStatus(), 164
setShortcutO, 86
setShown(), метод, 70
setSizeGripEnabled, 103
setSort(), 169
setTabData(), 103
setTabOrder(), 74
setTabPosition(), 102
setTabShape(), 102
setText(), 93
setTextModeEnabled(), 146
setTile(), 118
setValidator(), 92
setValue(), 51; 164
setVisible(), 70
set Word WrapMode(), метод и свойство, 94
sh, 211
Shallow copy, 40
shear(), 62
shortcut, свойство, 86
show(), 104
show(), слот, 70
showEventQ, событие, 70
showExtension(), 105
shown, свойство, 70
signals, 31
singleStep, свойство, 99
sizeConstraint, свойство, 57
sizeHint(), 57
sliderChange(), метод, 100
sliderMoved(), сигнал, 99
sliderPosition, свойство, 99
slots, 31
Smalltalk, 167
SMTP, 157; 220
SOAP, 221
Solaris, 195
source(), 77
SOURCES, ключ, 193
spacing, свойство, 57
SQL, модуль, 159
SSL,221
start(), 180
started(), сигнал, 181
startPoint(), 129
stateChangedO, сигнал, 87; 156
styleChange(), событие, 73
submit(), 169
Surface, 118
swap(), 50
Swing, 21
Sybase Adaptive Server, 159
T
takeAt(), 50
takeFirst(), 50
takeLast(), 50
target(), 77
TARGET, директива, 194
Tel, 22; 211
TCP, 221
terminate(), 181
terminatedO, сигнал, 181
text(), 93
textChanged(), сигнал, 93
textFromValue(), метод, 101
tilesHorizontally(), 119
tilesVerticallyO, 119
tel, 211
toBack(), 51
toFront(), 51
toggled(), сигнал, 86
toHtml(), 94
toPlainText(), 94
tracking, свойство, 99
translateO, 62
triggered(), сигнал, 97
tristate, свойство, 87
tryAquire(), 182
tryLock(), 182
tryLockForRead(), 183
tryLockForWrite(), 183
U
Ubuntu, 16
Unicode, 42
Unix, 13
UnixMake, 196
unload(), 191
unlock(), 182
unsetPaletteO, 65
update(), 57; 67; 73
updateGL(), 135
updateMask(), 73
UTF8,43
V
validate(), метод, 101
value(), 52; 54; 164
value, свойство, 99
valueFromText(), метод, 101
variables(), 213
VBA, 211
verticalHeader(), 111
view(), 110
viewMode, свойство, 109
viewport(), 62
visible, свойство, 70
Visual Studio, 201
Visual Studio Integration, 55
W
waitForBytesWritten(), 153
waitForConnected(), 153
waitForNewConnection(), 154
waitForReadyRead(), 153
wakeAll(), 183
wakeOne(), 183
WHEEL_DELTA, константа, 73
wheelEventO, 73
Win32, 200
WinAPI, 15
window(), 62
WMatrix
mapToPolygon(), 125
mapToRegion(), 125
wrapping, свойство, 100
writeDatagram(), 153
X
Xcode, 201
XFree, 219; 220
XML, 171
A
Анимация, 127; 142
Ассоциативные списки, 53
Атрибут, 177
Б
Библиотека
Microsoft .NET. Common Runtine, 46
Г
Градиент, 62
Градиентные заливки, 62
Графика, 140
д
Двойная буферизация, 135; 136
Диалог, 103
Директива
TARGET, 194
3
Звук, 142
И
Итератор, 51
К
Квалификаторы, 47
Кисть, 65
Класс
Q3Painter, 63
Q3Signal, 32
Q3TabDialog, 103
Q3 Value Vector, 53
QAbstractButton, 85
QAbstractltemDelegate, 114
QAbstractltemModel, 108; 114; 167
QAbstractltemView, 107; 109
QAbstractListModel, 109
QAbstractScrollArea, 107
QAbstractSlider, 99
QAbstractSocket, 152
QAbstractSpinBox, 100
QAbstractTableModel, 167
QAction, 96
QApplication, 36
QAxAggregated, 187
QAxBase, 188
QAxBindable, 187
QAxContainer, 187
QAxFactory, 187
QAxObject, 188
QAxScript, 189
QAxScriptManager, 189
QAxWidget, 188; 189
QBoxLayout, 57
QBrush, 65
QBuffer, 146; 147
QButton, 85
QByteArray, 143; 147
QCanvasEllipse, 129
QCanvasitem, 117; 125
QCanvasLine, 129
QCanvasPolygon, 130
QCanvasPolygonalltem, 118; 129
QCanvasRectangle, 129
QCanvasSpline, 130
QCanvasSprite, 117; 131
QCanvasText, 132
QCanvasView, 117; 122
QCheckBox, 87
QCloseEvent, 71
QColor, 64
QColorDrag, 79
QColorGroup, 64
QComboBox, 110
QConicalGradient, 62
QCoreApplication, 180
QDataSource, 143
QDataStream, 150
QDateEdit, 102
QDateTimeEdit, 101
QDecorationPlugin, 190
QDial, 100
QDialog, 103
QDir, 149
QDirectPainter, 63
QDirView, 113
QDns, 158
QDockArea, 97
QDockWidget, 97
QDomAttr, 176; 177
QDomCDATASection, 177
QDomComment, 177
QDomDocument, 176
QDomDocumentFragment, 176
QDomDocumentType, 176
QDomElement, 176; 177
QDomEntity, 176
QDomEntityReference, 176
QDomFragmentNode, 176
QDomNamedNodeMap, 176
QDomNode, 174
QDomNotation, 177
QDomProcessinglnstruction, 177
QDomText, 176; 177
QDoubleSpinBox, 101
QDoubleValidator, 92
QDrag, 76
QDragEnterEvent, 78
QDragMoveEvent, 78
QDragObject, 76; 79
QDriver, 160
QDropEvent, 78
QEventLoop, 181
QFile, 146; 147
QFileDialog, 106
QFilelnfo, 148; 149
QFocusData, 74
QFontDatabase, 70
QFontlnfo, 69
QFrame, 90; 103; 107
QFtp, 157
QGL, 133
QGLColormap, 133
QGLContext, 133; 137
QGLFormat, 133; 136
QGLWidget, 133
QGradient, 62
QGridLayout, 57
QGroupBox, 90
QHash, 54
QHBoxLayout, 57
QHeader, 111 QPoint, 124
QHeaderView, 111 QPolygon, 53
QHostAddress, 158 QPolygonF, 53
QHostlnfo, 158 QPopupMenu, 87
QHttp, 155 QProcess, 145
QHttpRequestHeader, 155 QProgressBar, 156
QHttpResponseHeader, 156 QProxyModel, 114
QlconView, 107; 109 QPushButton, 87
Qlmage, 141 QQueue, 53
QlmageDrag, 79 QRadialGradient, 62
QlmagelO, 140 QRadioButton, 88
QIntValidator, 92 QRangeControl, 99; 100
QlODevice, 145; 156; 174 QReadWriteLock, 183
QltemDelegate, 115 QRect, 119
QltemSelection, 53 QRectangle, 61
QLabel, 90 QRegExp, 46; 48
QLayout, 56 QRegExpValidator, 92
QLayoutltem, 56 QRegion, 61
QLCDNumber, 107 QScript, 189
QLinearGradient, 62 QScrollBar, 100
QLineEdit, 92 QScrollView, 100; 122
QLinkedList, 49; 53 QSEditor, 214
QList, 45 QSemaphore, 182
QListBox, 107; 109 QSet, 54
QListlterator, 51 QSignal, 32
QListView, 107; 109 QSInputDialogFactory, 214
QListWidget, 107; 109 QSInterpreter, 212; 213
QListWidgetltem, 110 QSize, 119
QMainWidget, 97 QSizePolicy, 58
QMainWindow, 98 QSlider, 100; 114
QMap, 53 QSObj ectFactory, 214
QMenu, 98 QSocketDevice, 151
QMenuBar, 98 QSocketLayer, 151
QMenuData, 98 QSound, 142
QMimeData, 76; 79 QSpacerltem, 56
QModellndex, 108 QSpinBox, 100; 101
QMovie, 143 QSProject, 212; 213
QMultiHash, 54 QSqlDatabase, 161
QMultiMap, 54 QSqlDriver, 160
QMutableListlterator, 51 QSqlDriverPlugin, 161; 190
QMutex, 182 QSqlError, 162
QObject, 27; 50; 55; 143; 181; 191; QSqlField, 164
209; 212 QSqlQuery, 164
QPaintDevice, 55 QSqlQuery Model, 167
QPainter, 61; 63 QSqlRationalTableModel, 170
QPainterPath, 62 QSqlRecord, 163
QPalette, 64 QSqlRecordlnfo, 163
QPixmap, 140 QSqlRelation, 170
QPluginLoader, 190; 191 QSqlRelationalTableMociel, 167
QSqlResult, 160 QXmlDTDHandler, 172
QSqlTableModel, 167 QXmlEntityResolver, 172; 174
QSScript, 214 QXmlErrorHandler, 173
QStack, 53 QXmllnputSource, 174
QStackedLayout, 57 QXmlLexicalHandler, 173
QStackedWidget, 103 QXmlReader, 172
QStandardltemModel, 114 QXmlSimpleReader, 173
QStrIList, 44 QCanvas, 117
QString, 39 кэш, 49
QStringList, 44; 53 Классы символов, 46
QStringListModel, 109 Ключ
QStrList, 44 CONFIG, 194; 196
QStyle, 81; 190 DEBUG, 194
QStylePainter, 63 HEADERS, 194
QStylePlagin, 190 PRECONPILEDHEADER, 202
QTabBar, 103 SOURCES, 193
QTabDialog, 103 Кнопка, 85
QTable, 107; 111 Коллекции, 49
QTableView, 110 Константа
QTableWidget, 107; 110; 111 Accepted, 104
QTableWidgetltem, 112 BottomToTop, 57
QTabWidget, 102 DraggingState, 108
QTcpServer, 153 EditingState, 108
QTcpSocket, 146; 152; 153 LeftToRight, 57
QTextCursor, 95 NoState, 108
QTextDocument., 95 QFont
QTextDrag, 79 PreferDefault, 69
QTextEdit, 93; 95 QPainter
QTextStream, 150 CompositionMode, 63
QThread, 180 Qt
QTimeEdit, 102 Caseinsensitive, 41
QToolBar, 88; 97 CaseSensitive, 41
QToolButton, 88 QWidget
QTree Widgetitem, 113 ClickFocus, 75
QUdpSocket, 152; 153 NoFocus, 75
QUrlOperator, 151 StrongFocus, 75
QValidator, 92 TabFocus, 75
QValueList, 45; 49 Rejected, 104
QVariant, 114 WHEELJDELTA, 73
QVBoxLayout, 56; 57 Копирование
QVector, 53 глубокое, 44
QWaitCondition, 183 поверхностное, 40
QWidget, 55; 76; 90; 103; 107 Кэш, 49
QWidgetltem, 56
QWidgetStack, 103 М
QWMatrix, 123 Макрос
QXmlContentHandler, 172 Q-CLASSINFO, 187
QXmlDeclHandler, 172 Q-DECLAREJNTERFACE, 191
QXmlDefaultHandler, 172 Q_EXPORT-PLUGIN(), 190
(^INTERFACES, 191 createResult(), 160
Q_OBJECT, 31; 89; 212 currentlndex(), 102; 110
Q_PROPERTY, 34; 186 currentSection(), 102
QTTRNOOP, 210 currentTabIndex(), 102
QTTRANSLATENOOP, 210 current Widget(), 102
[атрица отображения, 123 database(), 162
[етод databaseText(), 162
acquire(), 182 dataTransferProgress(), 157
activateO, 57 deleteLater(), 182
activeEditor(), 213 deleteRowFromTable(), 169
addAction(), 98 displayedSections(), 102
addBindValue(), 166 displayText(), 93
addDatabase(), 162 done(), 157
addMenu(), 98 dragEnterEvent(), 79
addObject(), 212 driverText(), 162
addObjectFactory(), 215 echoModeO, 92
addSeparator(), 98 editltemO, 112
addSignalHandler(), 213 editor(), 213
addTab(), 102 editors(), 213
animateClick(), 86 editorsModifiedO, 213
append(), 50; 94; 164 endPoint(), 129
appentChild(), 176 erase(), 60
areaPointsO, 130 error(), 156
areaPointsAdvanced(), 130 errorStringO, 156
at(), 50 exec(), 104; 105; 161; 164; 166
availableO, 182 fetch(), 161
beginTrasaction(), 160 field(), 163
bindValue(), 166 fieldName(), 163
boundingRect(), 130 find(), 45
boundVahieO, 166 focusProxyO, 76
brush(), 129 fontlnfo(), 67
checkStateO, 87 fontMetrics(), 67
checkSyntax(), 213 formatValueO, 160
classes(), 213 functions(), 213
clear(), 50 getExistingDirectoryQ, 106
clearValuesO, 164 gethostbynameO, 158
cllck(), 86 getOpenFileName(), 106
closeContext(), 137 getOpenFileNames(), 106
commandFinishedO, 157 getSaveFileName(), 106
commandStarted(), 157 grep(), 45
connectO, 181 gres(), 45
connectResize(), 143 hasFeature(), 160
connectStatus(), 143 hasHtml(), 79
connectUpadateO, 143 haeNext(), 51
constBegin(), 52 hasPendingConnection(), 154
constEnd(), 52 hasPendingDat agram(), 153
contains(), 45; 50; 54; 162; 163 hasPreviousO, 51
count(), 50; 110 horizontalHeader(), 111
create(), 161; 190 iconSetQ, 85
indexOf(), 45; 50; 163 peekNext(), 51
initializeOverlayGL(), 135 peekPreviousO, 51
insert(), 50; 93; 164 peerAdress(), 152
insertAfter(), 176 peerPortO, 152
insertBefore(), 176 pen(), 129
insertRowIntoTableO, 169 pendingDatagramSize(), 153
insertRows(), 169 popup(), 87
instance(), 191 prepare(), 161; 167
isActiveWindow, 80 prepend(), 60
isDesktop(), 80 previousO, 51
isDialog(), 80 primarylndex(), 160
isDirty(), 169 propertyChanged(), 187
isEnabledTo(), 71 QAbstractButton
isForwardOnly(), 166 setText(), 85
isMenuButton(), 87 QAbstractltemModel
isOpen(), 162 index(), 108
isOpenError(), 162 QAbstractSocket
isPopup(), 80 connaectToHost(), 152
isTopLevelO, 80 QApplication
key(), 52 setPalette(), 65
keys(), 190 setStyle(), 81
lastlndexOfO, 45 style(), 81
lear(), 164 QButton
listen(), 153 text(), 85
load(), 212 QCanvas
loadFromData(), 212 size(), 119
localAddress(), 152 advance(), 121; 127
localPort(), 152 allltemsO, 122
lock(), 182 backgroundColor(), 119
lockForRead(), 183 backgroundPixmap(), 119
lockForWrite(), 183 chunKSize(), 120
makeCurrent(), 135 drawArea(), 122
makeOverlayCurrentO, 135 height(), 119
matrix(), 62 onCanvas(), 119
model(), 114 rect(),
mouseDoubleClickEvent(), 73 reslze(), 119
move(), 50 retune(, 120
next(), 51 setAdvancePeriod(), 121
nextPendingConnectionO, 153 setAllChanged(), 121
nodeNameQ, 175 setBackgroundColor(), 119
nodeType(), 174 setBackgroundPixmap(), 119
nodeValueO, 175 setChanged(), 121
numRowsAffected(), 161; 165 setDoubleBufferingO, 122
open(), 160 setTiles, 118
paintEvent(), 60 setUnchanged(), 121
paintGL(), 134 setUpdatePeriod(, 121
paintOverlayGL(), 135 validChunk(), 119
parentNode(), 175 width(), 119
parent Widget(), 57; 80
QCanvasItem
animated(), 121
advance(), 127
advice(), 121
animated(), 127
boundingRect(), 128
boundingRectAdvancedO, 128
canvas(), 126
collides With(), 127
collisionsO, 127
move(), 127
moveBy(), 127
rtti(), 128
setAnimated(), 127
setCanvas(), 126
setVelocityO, 127
setXVelocity(), 127
setYVelocity(), 127
xVelocity(), 127
yVelocity(), 127
QCanvasPolygon
points(), 130
areaPoints(), 130
setPointsO, 130
QCanvasPolygonalitem
setWinding(), 130
QCanvasRectangle
rtti(), 129
QCanvasSpline
closed(), 131
controlPoints(), 131
setControlPoints(, 131
QCanvasSprite
boundingRect(), 131
frame(), 131
frameCountO, 131
height(), 131
image(), 131
imageAdvanced(), 131
move(), 131
setFrame(), 131
setFrameAnimationO, 132
setSequence(), 131
width(), 131
QCanvasView
canvas(), 123
inverseWorldMatrix(), 123
setCanvasQ, 123
setWorldMatrix(), 123
worldMatrix(), 123
QCheckBox
setTristate(), 87
QComboBox
currentText(), 110
lineEdit(), 110
QCoreApplication
addLibraryPath(), 190
translateQ, 210
QDataStream
versionQ, 150
QDomAttr
name(), 177
value(), 177
QDomText
splitTextO, 175
QDrag
setHotSpotQ, 77
setMimeDataO, 76
start(), 78
QFile
open(), 149
QFont
deciPointSize(), 68
insertSubstitution(), 68
pixelSize(), 68
setBoldO, 69
setFamilyQ, 69
setltalicO, 69
setOverlineO, 69
setPixelSizeQ, 68
setPointSize(), 68
setPointSizeFloatQ, 68
setStretchQ, 69
setStrikeOutO, 69
setStyleHint(), 69
setStyleStrategyO, 69
setUnderlineO, 69
setWeightO, 69
substitute(), 68
substitutionsQ, 69
QGLContext
create(), 137
QGLFormat
defaultFormat(), 136
hasOverlayO, 137
stereo(), 137
QGLWidget
initializeGL(), 134
set AutoBuff erSwap(), 135
Qlmage
convertDepth(), 141
copy(), 141
createHeuristicMask(), 141
mirror(), 141
scale(), 141
setPixel(), 141
smoothScale(), 141
xForm(), 141
QlODevice
openMode(), 146
waitForBytesWritten(), 147
waitForReadyRead(), 147
QLabel
setMovie(), 143
setNum(), 91
QLayout
addltem(), 56
addWidget(), 56
QList
pop_back(), 50
pop_front(), 50
push_back(), 50
pushfront), 50
QListWidget
insertltemO, 110
QMenu
addActionO, 96
QMimeData
setData(), 79
QModellndex
child(), 108
column(), 108
data(), 108
isValid(), 108
parent), 108
row(), 108
sibling(), 108
QMovie
finished(), 143
pause(), 143
pausedO» 143
unpause(), 143
QObject
connect(), 32; 213
deleteLater(), 155
disconnect), 32
tr(), 209
qobj ect_cast 0,191
QPainter
drawPath(), 63
paintEvent(), 61
QPaintEvent
region(), 61
QPalette
brush(), 65
color(), 65
dark(), 65
foregroundO, 65
light(), 65
setActive(), 64
setBrush(), 65
setDisabled(), 64
setlnactiveO, 64
QPixmap
convertFromlmageO, 141
convertToImage(), 141
createHeuristicMask(), 141
height(), 140
load(), 140
save(), 141
size(), 140
width(), 140
QProxyModel
setModel(), 114
QPushButton
isDefault(), 87
setDefault(), 87; 104
QRegExp
escape(), 48
exactMatch(), 48
search(), 48
searchRev(), 48
setCaseSensitive(), 48
setWildcardO, 48
QRegion
boundingRect(), 61
contains(), 61
rects(), 61
unite(), 60
QSInterpreter
evaluate(), 213
addTransientObject(), 212
callO, 213
defaultlnterpreter(), 212
QSizePolicy
setHorStretch(), 59
setVerStretch(), 59
QSound
isFinfshed(), 142
play(), 142
setLoops(), 142
QSProject
addObject(), 212
createEditor(), 213
setStorageMode(), 213
QSqlDatabase
isDriverAvailable(), 161
lastError(), 162
registerSqlDriverO, 161
tables(), 163
QSqlDatabasedrivers(), 161
QSqlQuery
bindValue(), 166
boundValuesO, 166
first(), 165
isActiveO, 164
isSelectO, 165
isValid(), 165
last(), 165
next(), 165
prepareO, 166
prev(), 165
seek(), 165
setForwardOnlyQ, 166
size(), 160; 165
value(), 166
QSqlRecord
count(), 163
field(), 164
QString
append(), 39
arg(), 42
ascil(), 43
at(), 41
capacity(), 44
compare(), 41
contains(), 42
count(), 42
endsWith(), 41
fill(), 40
find(), 42
findRev(), 42
fromAscii(), 43
fromLocal8Bit(), 43
fromUcs2(), 43
fromUtf8(), 43
indexOf(), 42
insert(), 39
lastlndexOf(), 42
latinl(), 43
left(), 41
leftJustifyO, 41
length(), 40
local8Bit(), 43
locale AwareCompar eO, 41
lower(), 41
midO,
numberO, 43
prependO, 39
removeO, 39
replaceO, 42; 48
reserveO, 44
resizeO, 44
right(), 41
rightJustify(), 41
setAscii(), 43
setLatinl(), 43
setLength(), 44
setNum(), 43
setUnicodeO, 43
setUnicodeCodesO, 43
simplifyWhiteSpaceO, 41
split(), 45
sprintf(), 42
squeezeO, 44
startsWith(), 41
stripWhiteSpaceO, 41
tolnt(), 43
toShort(), 43
toULongLong(), 43
truncate(), 40
unicode(), 43
upper(), 41
utf8(), 43
QStringList
append(), 45
fromStrList(), 45
join(), 45
QStyle
DrawPrimitiveO, 81
QTableWidget transformationMode(), 125
setHorizontalHeaderItem(), 112 translateQ, 124
setltem(), 111 QXmlContentHandler
QTextCursor setDocumentLocatorO, 173
insertText(), 96 QXmlDeclHandler
QThread attributeDeclO, 172
currentThread(), 181 externalEntityDeclO, 172
query Interface(), 187 internalEntityDecl(), 172
QWidget QXmlDTDHandler
baseSize(), 81 notationDeclO, 172
clearFocus(), 76 unparsedEntityDecl(), 172
close(), 104 QXmlEntityResolver
drawText(), 66 resol veEntity(), 172
event(), 71 QXmllnputSource
focusNextPrevChild(), 74 next(), 174
hasFocusQ, 76 setData(), 174
isFocusEnabled(), 76 QXmlReader
mapFromO, 59 contentHandler(), 173
mapFromParent(), 60 setFeature(), 173
mapTo(), 69 setDTDHandler(), 173
mapToParent(), 60 QCanvas
nextInFocusChain(), 74 resize(), 118
repaintQ, 60 tile(), 118
setFocus(), 76 tileHeight(), 119
setFocusPolicy(), 74 tileWidthO, U9
setMaximwnHeight(), 58 rawCommand(), 157
setMaximumSize(), 58 readDatagramQ, 153
setMaximumWidth(), 58 record(), 160
setMinimumHeight(), 58 release(), 182
setMinimumSize(), 58 remove(), 51*, 164
setMinimumWidth(), 58 removeAt(), 50
setMouseTrackingO, 72 removeltemO, 110
setSizeIncrement(), 80 removeObjectO, 212
update(), 60 removeRowQ, 169
QWidgetltem removeRows(), 169
expandingDirections(), 57 removeSignalHandler(), 213
QWMatrix repaint(), 73
invert(), 124 replace(), 45; 50
InvertableO, 124 request(), 155
isldentity(), 124 r equestProper tyChange(), 18 7
map(), 124 resizeQL(), 134
mapRect(), 125 resizeOverlayGLQ, 135
reset(), 124 result(), 104
rotate(), 124 revert(), 169
shear(), 124 revertAll(), 169
scale(), 124 rollbackTransaction(), 160
setMatrix(), 123 rotateQ, 62
setTransformationMode(), 125 run(), 180
scale(), 62
scriptModifiedQ, 213 setSizeGripEnabled, 103
setAccel(), 86 setSortO, 169
setAcceptDrops(, 78 setTabData(), 103
setActiveWindwow(), 80 setTabOrder(), 74
setAlignment(), 92 setTabPosition(), 102
setAutoRepeat(), 88 setTabShape(), 102
setBackgroundRole(), 65 setText(), 93
setBrushQ, 129 setTextModeEnabledG 146
setCaption(), 80 setTile(), 118
setCheckable(), 86; 88 setValidator(), 92
setCheckedQ, 86 setValue(), 51; 164
setCheckStateO, 87 setVisibleO, 70
setColumnCount(), 113 setWordWrapMode(), 94
setDocument(), 94 \shear(), 62
setEditStrategy(), 169 show(), 104
setEnabled(), 71 showExtension(), 105
setExtension(), 105 sizeHint(), 57
setFileNameQ, 147 sliderChange(), 100
setFilter(), 169 source(), 77
setFixedHeight(), 58 submit(), 169
setFixedSize(), 58 start(), 180
setFixedWidth(), 58 startPoint(), 129
setFocusProxy(), 76 swap(), 50
SetFont(), 66 takeAt(), 50
setForegroundRole(), 65 takeFirst(), 50
setGenerated(), 164 takeLast(), 50
setHidden(), 70 target(), 77
setHost(), 155 terminateO, 181
setlcon(), 80; 85 text(), 93
setIconText(), 80 textFromValue(), 101
setImageData(), 79 tilesHorizontallyO, 119
setMaxPendingConnections(), 154 tilesVerticallyO, 119
setMenu(), 87 toBack(), 51
setNull(), 164 toFront(), 51
setOrientation(), 105 toHtmlO, 94
setPaletteO, 65 toPlainText(), 94
setPaletteBackgroundColor(), 65 translate(), 62
setPaletteBackgroundPixmapO, 65 tryAquire(), 182
setPaletteForegroundColor(), 65 tryLock(), 182
setPen(), 129 tryLockForRead(), 183
setPixmap(), 77; 85 tryLockForWrite(), 183
setPoints(), 129 unload(), 191
setPopup(), 87 unlock(), 182
setQueryO, 161 unsetPaletteO, 65
setReadOnlyG 164 update(), 57; 67; 73
setRelationQ, 170 updateGLO, 135
set RequiredSt at us(), 164 updateMask(), 73
setShortcutO, 86 validate(), 101
setShown(), 70 value(), 52; 54; 164
valueFromText(), 101 variables(), 213 verticalHeader(), 111 view(), 110 viewport(), 62 waitForBytesWritten(), 153 waitForConnected(), 153 waitForNewConnection(), 154 waitForReadyRead(), 153 wakeAllQ, 183 wakeOne(), 183 wheelEvent(), 73 window(), 62 WMatrix mapToPolygon(), 125 mapToRegion(), 125 writeDatagram(), 153 Модуль Network, 151 Qt DropAction, 78 Плоскость рисования, 118 Поверхность рисования, 118 Полигон, 129 Полотно, 118 Поток, 179 Представление, 122 Препроцессор тос, 31 Прймитив рисования, 125 Процесс, 179 Р Регулярные выражения, 42; 46 Режим переднего плана, 65 • фона, 65 Роль, 64
SQL, 159
Мьютекс, 182 c
О Свойство autoExclusive, 86; 88; 90
Область заливки, 62 Оверлей, 135; 137 Окна диалога, 103 Открытые системы, 14 Background, 64 ButtonText, 64 checkable, 86 currentRow, 110
П decimals, 101 enabled, 71
Палитра, 64 Designer, 102 Перечисление QCanvasItem RttiValues, 128 QCanvasSprite FrameAnimationType, 132 QIODevice OpenMode, 145 QSql TableType, 163 QStyle ComplexControl, 82 ContentsType, 82 ControlElement, 82 PixelElement, 82 PrimitiveElement, 81; 82 StyleFlags, 83 StyleHint, 83 expandable, 112 flow, 109 Foreground, 64 group, 86 hidden, 70 inputMask, 92 lazyChildCount, 113 HneWrapColumnOrWidth, 95 margin, 57 maxLength(), 92 movement, 109 pageStep, 99 QAbstractButton text, 85 QApplication startDragDistace, 77 startDragTime, 77 QAxFactory isServerO, 186
Qlmage
bitOrder(), 141
colorTable(), 1*41
depth(), 141
height(), 141
width(), 141
QRegExp
cap(int), 48
capturedText(), 48
matchedLength(), 48
QSqlError
text(), 162
QToolButton
setAutoRaise(), 88
QWidget
palette, 65
sizePolicy, 57
relation(), 170
resolveSylinks, 113
shortcut, 86
shown, 70
singleStep, 99
sizeConstraint, 57
sliderPosition, 99
spacing, 57
tracking, 99
tristate, 87
value, 99
viewMode, 109
visible, 70
wrapping, 100
Семафор, 182
Сигнал, 27
actionTriggered(), 100
changed(), 97
clickedO, 86
done(), 156
finishedO, 181
hovered(), 97
newConnection(), 153; 154
QAbstractSlider
dialMovedO, 100
valueChanged(), 100
QAction
triggered(), 98
QApplication
lastWindowClosed(), 104
QCanvas
resized(), 119
QHeaderView
sectionMoved(), 111
QMenu
hovered(), 98
triggered(), 98
rangeChangedO, 99
rawCommandReplayO, 157
returnPressedO, 92; 93
sliderMoved(), 99
started(), 181
stateChangedO, 87; 156
terminated(), 181
textChanged(), 93
toggled(), 86
triggeredO, 97
Слот, 27; 30
hide(), 70
QCanvas
update(), 120
show(), 70
Событие
changeEvent(), 73
clicked(), 87
currentChangedO, 103
dataTransferProgress(), 156
dropEvent(), 79
enebledChange(), 73
enterEvent(), 72
fontChangeO, 73
hideEvent(), 70
languageChangeO, 73
leaveEvent(), 72
mouseMoveEvent(), 72
mousePressEvent, 73
mouseReleaseEvent, 73
moveEvent(), 73
paintEvent(), 73
paletteChangeO, 73
QAbstractSlider
SliderOrientationChange, 100
QEvent
EnabledChange, 71
FontChange, 67
QWidget
changeEvent(), 81
QXmlContentHandler
endElement(), 172
skippedEntityf), 174
startDocument(), 172
startElement(), 172
QXmlLexicalHandler
startCDATAO, 173
startDTD(), 173; 174
startEntityO, 174
resizeEvent, 73
responseHeaderReceived(), 156
showEvent(), 70
styleChangeO, 73
элемента управления, 71
Состояние, 70
видимость, 70
Списки
ассоциативные, 53
Сплайн, 130
Стратегия стилей, 69
Строка, 39
неопределенная, 40
нулевой длины, 40
Т
Тип
QColorGroup
ColorRole, 64
QRgb, 64
Qt
Alignment, 92
CaseSensitivity, 41
ChackState, 87
У
Утилита
lupdate, 208
qmake, 193
Ф
Фактор растяжимости, 59
Фильм, 143
Флаг
Qt
QRepaintNoErase, 60
Фликер, 66
Функция
EnumWindows, 28
EnumWindowsProc, 28
qsort, 27
Обратного вызова, 28
ц
Цвет текста, 66
Ш
Шрифт, 66
Шрифтовая группа, 68
Научно-популярное издание
Арсений Викторович Чеботарев
Библиотека Qt 4. Создание прикладных
приложений в среде Linux
Профессиональная работа
Литературный редактор
Верстка
Художественный редактор
Корректор
ЕД.Давидян
МЛ Смолина
В.Г. Павлютин
ЛА. Гордиенко
Издательский дом “Вильямс”.
101509, Москва, ул. Лесная, д. 43, стр. 1.
Подписано в печать 20.02.2006. Формат 70X100/16.
Гарнитура SchoolBookC. Печать офсетная.
Усл. печ. л. 20,64. Уч.-изд. л. 14,49.
Тираж 3000 экз. Заказ № 1522.
Отпечатано с диапозитивов
в ОАО “Печатный двор” им. А. М. Горького.
197110, Санкт-Петербург, Чкаловский пр., 15.