![]()
Дмитрий Осипов
Delphi.
х,·
Проrраммирование дnя
Windows, 05 iOS и Android
Санкт-Петербург
«БХВ-Петербург»
2014
УДК ББК
004.4'2
32.973.26-018.1
0-74
ОсиповД.Л.
О-74 Delphi. Программирование для Windows,
OS Х, IOS и Android.
- СПб.:
БХВ-Петербург,
2014. -464 с.: ил. -(Профессиональное программирование)
ISBN 978-5-9775-3289-1
Книга
посвящена
одному из
самых
совершенных
языков
программирования
Delphi ХЕ5/ХЕ6.
В ней
подробно
рассматривается
новейшая кроссплатформенная
библиотека FM, позволяющая
создавать
полнофункциональное программное
обес печение
для
операционных
систем Windows и OS Х, а
также для
смартфонов
и планшетных
компьютеров,
работающих
под
управлением
Android и IOS. Проекты
примеров из
книги
размещены на
сайте
издательства.
Для программистов
УДКОО4.4'2 ББК 32.973.26-018.1
Группа
подготовки издания:
Главный редактор
Зам.
главного
редактора Зав.редакцией
Редактор Компьютерная верстка Корректор
Дизайн
серии Оформление
обложки
Екатерина
Кондукова
Игорь
Шишигин
Екатерина Капалыгина
Анна
Кузьмина
Ольги
Сергиенко
Зинаида Дмитриева
Инны Тачиной
Марины
Дамбиевой
![]()
Подписано в печать 31.01.14.
Формат 70х1001/16. Печать офсетная. Усл. печ. л. 37,41.
Тираж 1000 экз. Заказ №
65
"БХВ-Петербург", 191036,
Санкт-Петербург, Гончарная ул., 20.
Первая
Академическая
типография
"Наука" 199034,
Санкт-Петербург,
9 линия, 12/28
ISBN 978-5-9775-3289-1 © Осипов Д. Л., 2014
© Оформление, издательство "БХВ-Петербурr", 2014
![]()
Оглавление
Глава 2. Забываем VCL?........................................................................................ 30
Глава 3. Классы-шаблоны...._................................................................................ 41
Перегрузка методов с
параметром обобщенного типа............................................................... 47
Поддержка LiveBindings..............................................._...................................................................... 61
![]()
Глава 6. Меню приложения.................................................................................. 118
Связь с элементом управления........................................................................................................ 129
Глава 8. Управление папками и
файлами.......................................................... 135
Глава 9. Компоненты для работь1 с
текстом....................................................... 142
Глава 10. Компоненты-списки............................................................................. 158
Состояние узла.................................................................................................................................... 179
Глава 12. Сетки..................................................................................................... 185
Колонки сетки.............................................................................................................................................. 185
Глава 13. Окна сообщений и
диалоrи.................................................................. 192
Окна сообщений........................................................................................................................................... 192
Отправка задания на печать TPrintDia/og.................................................................................... 203
Глава 14. Дата и
время......................................................................................... 206
Глава 15. Управление цветом.............................................................................. 212
Глава 16. Двухмерная rрафика............................................................................ 222
Шрифт TFont................................................................................................................................................ 227
Глава 17. Графические эффекты......................................................................... 240
Заливка цветом TFillEjfect и TFillRGBE.ffect............................................................................... 246
Увеличительное стекло TMagnifyE.ffect и TSmoothMagnifyEjfect.................................. 251
Горизонтальная деформация краев текстуры TWrapE.ffect........................................... 254
![]()
в Оглавление
Глава 18. Анимация............................................................................................. 263
Анимированная картинка, компонент TВitmapAnimation............................................. 268
Анимация числовых свойств, компонент TFloatAnimation........................................... 269
Анимация прямоугольной области, компонент TRectAnimation.................................. 269
Глава 19. Мультимедиа........................................................................................ 271
Глава 20. Сенсорный ввод................................................................................... 281
Описание жеста........................................................................................................................................... 281
Глава 21. InterBase ToGo...................................................................................... 287
Глава 22. LiveBindings........................................................................................... 303
Глава 25. Мобильная платформа......................................................................... 347
![]()
10 Оглавление
Глава 27. Проектируем ЗD-сцены........................................................................ 378
Камера,
класс TCamera............................................................................................................................. 381
Глава 28. Геометрическое описание фигур и
mеsЬ-объекты.............................. 385
Глава 29. Матерцал объекта................................................................................ 398
Глава 30. 3D-контекст TContextЗD....................................................................... 406
Приложение 1. Вектор
TVectorЗD......................................................................... 419
Приложение 2. Матрица преобразований TMatrixЗD......................................... 428
Умножение матриц............................................................................................................................. 431
Приложение 3. МодулЬ System.lOUtils.................................................................. 434
Приложение 4. Датчики······································································ 442
Приложение 5. Описание электронного архива.................................................. 445

![]()
![]()
![]()
Совсем
недавно
среда
проектирования Embarcadero RAD Studio
совершила
очеред ной
эволюционный
скачок - в
составе
языков Delphi и C++Builder
появилась
принципиально
новая
возможность
разработки
кроссплатформенных
приложений.
Современные
версии Delphi позволяют
создавать не только
приложения для Win32 и Win64,
но и
полноценные
программные
продукты,
которые
предназначены
для работы
под управлением
операционных
систем,
разработанных
компанией Apple (OS Х 10.7 Lion, OS Х 10.8 Mountain Lion, IOS
начиная с
версии 5.1) и
компанией Google
(речь об Android с
диапазоном
версий от 1.5 до
4.3)! В основу
кроссплат
формы
положена во
всех отношениях
уникальная
библиотека
FireMonkey. Компа ния
Embarcadero
рекомендует
называть
"библиотеку
FireMonkey"
"платформой
FM", но большинству
читателей
привычней
использовать
первый вариант,
поэтому мы
будем
придерживаться
в книге
именно этого
словосочетания.
Книга,
которую вы
держите в руках, в
большей
степени
рассчитана
на подготов
ленного
программиста,
имеющего представление
о языке и
возможностях
так на
зываемых
классических
версий Delphi.
Такого
читателя в
первую очередь
интере сует
ответ на
единственный вопрос: "Чего
такого нового появилось в
языке Del
phi ХЕ5, чтобы я заинтересовался
им?"
Постараюсь во
введении к книге
ответить на этот
вопрос как
можно более
кратко и
формализовано,
разбив ответ
на пункты.
□ Библиотека VCL ни в
коем случае не умерла и
по-прежнему поддерживается
компанией
Embarcadero, но по темпам своего развития
(важнейшему
показателю
для претендующего
на успех
программного
продукта) она
замедлилась.
Вряд ли
раскрою вам
секрет,
утверждая,
что в
IТ-индустрии
остановка равносильна
смерти, а VCL
движется все
медленнее...
Поэтому на
смену VCL
неотвратимо
приходит
инновационная по
своей сути
библиотека
FireMonkey.
□ FireMonkey-
это
по-настоящему кроссплатформенная библиотека, которая на
данный
момент
поддерживает Win32, Win64, OS Х, IOS и Android.
Очень важно,
что
вы можете
использовать
один и тот же код для
компиляции
проекта как
под Windows, так и
под OS Х и Android!
□ FireMonkey обладает непревзойденными·графическими возможностями и
позво
ляет создавать
приложения,
опирающиеся
в первую
очередь на DirectX, OpenGL и GDI+
(напомню, что
проекты VCL
изначально
ориентированы на
устаревший GDI).
![]()
![]()
![]()
![]()
14 Введение
• В продолжение темы с графикой отмечу, что FireMonkey позволяет создавать не только классические двухмерные приложения, но и способные впечатлить самого притязательного пользователя приложения с трехмерной графикой.
• Еще одно замечание касается того, что все визуальные элементы управления FireMonkey в буквальном смысле нарисованы (представляют собой битовые образы), и это позволяет применять к ним как по отдельности, так и в целом самые нетривиальные визуальные эффекты.
□ Еще со
времен Borland язык
Delphi славился
своим высокопроизводительным
компилятором.
Сегодня в Delphi
ХЕ5 их шесть (Win32, Win64, OS Х,
эмулятор iOS
для х86.и iOS АRМ, Android).
Важно
отметить тот
факт, что
компиляторы
iOS АRМ и Android модульные. Модуль фронтального (front-end) компилятора
АRМ переводит исходный код программы на язь1ке Delphi в промежуточный байт-код. Конечный модуль (back-end) компилятора представляет собой низко уровневую виртуальную машину (Low Level Virtual Machine, LLVM), преобра зующую промежуточный байт-код в машиннь1й код целевой платформы. Ком пиляторы LLУМ широко используются в компаниях Apple, Google и Adobe, что подсказывает направление дальнейшего развития Delphi.
□ Язык Delphi
всегда
отличался
своими
возможностями
по
разработке
приложе ний
для БД. Библиотека
FireMonkey вобрала
все лучшее по
работе с БД и
под держивает все
распространенные коммерческие и
бесплатные
системы
управле
ния данными. В этой книге мы поговорим об одной из новых разработок Embar cadero - настольной БД InterBase ToGo.
Одним словом, за библиотекой FireMonkey большое будущее, в какой-то степени это пригодный для всех случаев жизни швейцарский нож, который должен быть всегда под рукой.
![]()
![]()
ГЛАВА 1
Для того чтобы программист, впервые столкнувшийся с платформой FireMonkey (FМХ), сразу вошел в курс дела, предложу обобщенную схему библиотеки (рис. 1.1). Платформа FireMonkey включает в себя многочисленный набор классов и сервисных интерфейсов, написанных на языке Delphi, в их числе элементы управления для 2D- и 3D-приложений, высококачественная графическая подсисте ма, поддержка сенсорного ввода и многое другое. Замечу, что рис. 1.1 существенно упрощает-представление о FireMonkey, на самом деле это гораздо более сложная и многоуровневая система, но для первого знакомства его вполне достаточно.
ЗАМЕЧАНИЕ
Стоит знать,
что у истоков
платформы
FireMoпkey стоит
российский
программист
Евге ний
Крюков.
Появление принципиально новой программной платформы, получившей несколько необычное название FireMonkey, существенно расширило возможности языка Delphi. Теперь, помимо классических приложений VCL, у программиста есть воз можность разрабатывать несколько типов кроссплатформенных проектов:
1. FireMonkey Desktop Application для Windows и OS Х:
• приложения FireMonkey НD Application;
• приложения FireMonkey 3D Application.
2. FireMonkey Moblle Application для IOS.
3. FireMonkey Moblle Application для Android.
Приложения FireMonkey НD Application позволяют создавать программные про дукты с высококачественным двухмернь м графическим интерфейсом для операци онных систем Windows и OS Х. ПрQекты HD в первую очередь окажутся востребо
ваны в качестве б,изнес-приложений (прикладное программное обеспечение и кли
ентские приложения баз дан»ых). С некоторой степенью допущения возможности FireMonkey НD Application можно сравнить с традиционными проектами VCL. Хо тя о знаке равенства между этими платформами речи идти не может. Взвешивая
![]()
![]()
![]()
достоинства
и недостатки
двух
знаковых
библиотек, на
чашу весdв,
оценивающую
преимущества
FireMonkey, стоит
положить две
внушительные
"гири" с названиями
"кроссплатформа"
и "качественная
высокопроизводительная
графика". В
пользу горячо
любимой
программистами
Delphi и C++Builder
платформы VCL я
бы отнес
такие
достоинства, как
исключительная
проработанность
библиотеки,
огромное
число
компонентов
и безусловная
поддержка Win API,
СОМ, ADO и т.
д. (что по
зволяет
писать
весьма
эффективный код
под Microsoft Windows).
.
•


|
Виртуальная |
Графический механизм 1 1 |
|
машина 0alvik |
0 nGL I Surface ре ! Manager |
|
|
|
Рис. 1.1. Платформа FireMonkey
ЗАМЕЧАНИЕ
Для разработчиков, ориентированных на новейшую
операционную
систему Windows 8,
платформа FireMonkey
предоставляет
возможность
создавать
приложения с
интер фейсом
Metropolis - аналогом
интерфейса Windows
8.
![]()
Приложение
FireMonkey 3D Application - это
еще один шаг, сделанный Embar
cadero навстречу
востребованному
сегодня
мультимедийному
направлению.
Нали чие
аббревиатуры
"3D"
подсказывает,
что на этот
раз программист
получает
право
создавать
уникальный
трехмерный
пользовательский интерфейс.
Заметим, что сама
по себе идея 3D не нова, она широко применяется в
общеизвестных системах
DirectX и OpenGL. Но то, что сделали в Embarcadero, без стеснения можно назы вать инновацией. На момент написания этих строк ни одна из коммерчески успеш ных систем разработки ПО не была способна создавать полноценные кроссплат форменные бизнес-приложения 3D путем простого переноса компонентов на фор му! Насколько это стало удобно, вы поймете сразу, если У. вас есть хотя бы небольшой опыт разработки интерфейсной части программных продуктов с по мощью инструментария DirectX или OpenGL...
ЗАМЕЧАНИЕ
Подчеркнем, что Embarcadero не
позиционирует FireMonkey как
"движок" для разра
ботки игровых приложений. Однако пока речь идет о
первых шагах платформы, по
·этому
нельзя
исключить, что в
последующих версиях
библиотеки она
позволит
рабо тать и в
этом
сегменте ПО.
Приложения FireMonkey MoЬile Application предназначены для работы под управ лением операционных систем IOS и Android. Таким образом, благодаря Delphi ХЕ5 вы приобретаете уникальную возможность писать программное обеспечение для iPad и iPhone корпорации Apple и для многочисленных устройств, использующих операционную систему Android компании Google!
Для создания приложения на базе библиотеки FireMonkey следует обратиться к элементу меню File I New I Other и в появившемся диалоговом окне New Items следует выделить необходимый значок с названием типа приложения (рис. 1.2).

Рис. 1.2. Диалоговые окна
выбора типа проекта и создания проекта
Обратите
внимание (см. рис. 1.2),
что в составе
шаблонов
имеется
заготовка
проек та FireMonkey Metropolis UI Application. Это разновидность
проектов НD с
модным
сегодня
"плиточным"
интерфейсом
Metropolis,
применяемым в Windows 8.
Выбор
целевой
платформы
для проекта
Раз основная
заслуга FireMonkey
заключается
в поддержке
не только
Windows, но и OS Х, то
изучение
приложения
FireMonkey начнем с
определения
целевой плат
формы для
реализации приложения.
Создайте новый проект. Для этого воспользуйтесь пунктом меню File I New
1
FireMonkey Desktop
Application.
После
появления на
свет нового
проекта
обратитесь к
окну менеджера
проекта (рис. 1.3).
В дереве
менеджера
проекта
найдите узел Target Platforms и, воспользо
вавшись
услугами
контекстного
меню узла,
добавьте
интересующую
вас платфор
му
(32-разрядная Windows,
64-разрядная Windows
или OS Х).
В результате
у узла Target Platforms появится
дочерний
элемент с
названием вновь
добавленной
плат формы.
Proj«tl.dproj - Praject М•
-- ·-·-·-----·-- -
Ъ)3 Тt:э 1 11 •
Fh
DProje(.tGraцH
е 6iJProjt!Ct1
1 4 •
-о -•
---
••• 1: 1 "-t
004 аА1с1 Configuratians (Del)uQ}
S O Tiw,;etl'letforms (OSX32)
· 1'132.МWhdows
s
64-hlt Wi'ldow$
1 llosx
•
е t:i Conf\iµ'•aon
W ,t,pgla11on
SIDre
!& • U11tl,pa
![]()
![]()
Рис. 1.3. Выбор
целевой nлатформы для nроекта FlreMonkey
Выпуск приложения для 32- и 64-разрядной Windows не вызывает никаких затруд нений. Программист указывает предпочтительную платформу и просто нажимает клавишу <F9>. Если же вы планируете создать релиз для OS Х, то придется еще немного потрудиться.
1. По
возможности
полностью
отладьте приложение
под
управлением
Windows (это
возможно,
если вы не
используете
функции API OS Х).
Это исключит
мно гие
проблемы при
создании
исполняемого
бинарного
кода под OS Х и в
целом
ускорит
работу над
выпуском
релиза.
2. Соедините
в сети два
компьютера.
На первом
должна быть
установлена
опера ционная
система Windows и
развернуто
ваше
программное обеспечение
Delphi. Второй компьютер должен работать под управлением операционной системы
osx.
3. Обращаемся
к компьютеру
с FireMonkey. Найдите в
каталоге C:\Program Files\
Embarcadero\RAD Studio\n.n\PAServer (или C:\Program Files (x86)\Embarcadero\ RAD Studio\n.n\PAServer,
если вы
работаете в
64-разрядной Windows)
файл RADPAServerXE5.pkg (в версиях ХЕ2 и ХЕ3 это был архив setup_paserver.zip, в ХЕ4- RADPAServerXE4.pkg).
Можете
перенести
файл на МАС с
помощью флешки,
а лучше всего
предоставьте
к данному
каталогу
сетевой
доступ, так
чтобы папку с
файлом
смогла
увидеть
станция Мае
(рис. 1.4).

Рис. 1.4. Сетевой доступ к
файлам папки PAServer
со станции Мае
4. Разверните программное обеспечение на рабочей станции с OS Х, в р'езультате
в папке с
приложениями
вы увидите
файл RAD PAServer
ХЕ5.арр. Это
прило жение
(в официальной
документации
именуемое Platform Assistant) возьмет
на себя
обязанности
по компиляции
приложения
FireMonkey.
5. Запустите в терминальном
окне компьютера
с OS Х установленное программное
обеспечение
(рис. 1.5).
![]()
![]()
![]()
![]()
![]()

Рис. 1.5. Запуск RAD PAServer XES в
терминальном окне
6. Возвращаемся
к компьютеру
с Windows. В менеджере
проектов Delphi создайте
целевую
платформу (Target Platforms) OS Х и, дважды
щелкнув по
узлу, сде
лайте ее
активной (см.
рис. 1.3).
7. Нажмите
"священную"
для
программиста
Delphi клавишу <F9>. И если с
актив ной платформой OS Х это делается впервые, то перед вами
появится окно мас
тера создания профиля (рис. 1.6). Придумайте для профиля имя, введите
IР-адрес
станции Мае, при желании
поменяйте номер порта и
(если вы верите
в теорию заговора) придумайте пароль. Нажав кнопку Test Connection, протес
тируйте соединение и
завершите работу помощника, нажав кнопку Finish. На
строив профиль, вновь "давим" <F9> - в ответ вы увидите свое первое прило жение для Мае (рис. 1.7). Откомпилированное приложение
для
платформы OS Х вы обнаружите
на станции Мае в папке /Users/uмя_noлыoвameля/RADPAServer/
scratch-/uмя_профайла.
ВНИМАНИЕ!
Компания
Embarcadero постоянно
улучшает
свое
программное
обеспечение,
это утвер
ждение также
относится и к PAServer. Поэтому
после любого
обновления Delphi обя
зательно
переустановите
и PAServer.
![]()
![]()


(:} с,,..,,•Conn,ct,on P;;,file 1-- .1 1181IIIZlrdМ ..-iYoUа Cl'N comкtio,\ profle. ComecЬon pro•aretequred :nordt, to r drdov and n.l't • project on а /'О'№ rnactw. Мqс: юfum;.,Wf! <ю виfrо Р\т-а: 11 11 ! 1 1 10 Cr •t а Connectюn Profi;e Reмot мadliм. tiof-t ------------------------------------------------------------------------------------------ --. C)edfyci the:rtmoteroadWNll'Dr.ot hretrott- ';:IP Acom«ьonp-oflt ,,_,,..,,._,,..,_c,,getpotfo<mo,yy-PASo<-,w ";,stoled,nd""'11ngonll,c,_., mad'll,e. ма :еvЩrm:ФonооМО1!!9 P№:tm: &emc,tl:_(IP_r,_,,..,.), 192,168.0,1
i
r

Рис. 1.6. Масте"р создания удаленного профиля
Рис. 1.7. Приложение FireMonkey
для платформы OS Х
![]()
![]()
![]()
ЗАМЕЧАНИЕ
Если в
вашем
распоряжении
нет
компьютера
Мае, но
необходимо
осуществить
тести
рование приложения,
то можно
воспользоваться
услугами
облачного сервиса
macincloud, который расположен по адресу http://www.macincloud.com/.
Выпуск приложения для iOS Moblle
Для выпуска приложения, предназначенного для работы под управлением мобиль ной платформы IOS, нам вновь потребуется рабочая станция Мае с развернутой на ней операционной системой OS Х 10.7 Lion или OS Х 10.8 Mountain Lion. Мобиль ное устройство (с операционной системой IOS 5.1 и выше) стоит подключить к компьютеру Мае через USB-nopт, для того чтобы ускорить процесс тестирования приложения.
Если вы намерены испытать свои силы в разработке приложений для мобильной платформы IOS MoЬile, то в самом начале пути для настройки компьютеров реко
мендую воспользоваться услугами специализированного помощника. Для его вы зова достаточно, обратившись к пункту меню File I New I FireMonkey Moblle Application, создать свое первое мобильное приложение. После того как вы выбе
рите
шаблон
приложения и
укажете
папку, в которую
следует
сохранить
файлы
проекта, среда
разработки
выведет на
экран окно iOS Moblle Help Wizard.
С этого момента всеми остальными вашими действиями станет руководить помощ ник. Во-первых, он попросит вас установить на станции Мае среду разработки Xcode, позволяющую разрабатывать приложения для Мае, iPhone и iPad. На момент написания этих строк программное обеспечение Xcode 5 можно было свободно скачать со страницы https://developer.apple.com/xcode/ компании Apple.
Во-вторых,
вам придется
развернуть Xcode
Cornrnand Line Tools. Для
этого следует
запустить
установленное
на предыдущем
этапе
программное
обеспечение
Xcode, выбрать
пункт меню Preferences и
щелкнуть по
расположенной
на главной панели
команде Downloads. В окне
загрузок
выбираем
вкладку Components и щелкаем
по кнопке Install.
ВНИМАНИЕ/
Для
разработки
приложений
для Мае вы
должны
обладать
учетной
записью Apple Developer.
В-третьих, вам следует развернуть на станции Мае программу-ассистент Platform Assistant, которую вы обнаружите на жестком диске компьютера с Ernbarcadero RAD Studio XES. Интересующий нас файл RADPAServerXES.pkg расположен в папке C:\Prograrn Files\Embarcadero\RAD Studio\n.n\PAServer\. Завершив инстал ляцию программы-ассистента, наЙдите в папке Applications приложение RAD PAServer XES.app и запустите его на выполнение.
В-четвертых,
вам предстоит соединить
компьютер с Ernbarcadero RAD Studio XES и
станцию Мае и сконфигурировать профиль
соединения
(connection profile). Для
вы зова
мастера конфигурации обратитесь к
IDE Delphi и
выберите пункт меню Tools 1
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
Options. В появившемся окне настройки опций выберите узел Environment Options I Connection Profile Manager. Щелчок по кнопке Add вызовет на экран уже знакомый нам помощник (см. рис. 1.3).
Процесс выпуска приложения для операционной системы Android (в сравнении с работой с iOS) имеет ряд существенных отличий, ведь на этот раз нам предстоит работать с операционной системой, разработанной не в компании Мае, а в Google.
Если вы планируете написать приложение для конкретного мобильного устройства, то целесообразно установить на ваш персональный компьютер USВ-драйвер этого смартфона или планшетного компьютера. Все необходимые драйверы вы обнару жите в Интернете, в первую очередь рекомендую ресурсы:
http://developer.android.com/sdk/win-usb.html https://developer.amazon.com/sdk/fire/connect-adb.html http://developer.android.com/tools/extras/oem-usb.html
Если же вы нацелены на разработку ПО для разнотипных устройств, то запустите Android SDK Manager (кнопка Пуск I Все программы I Embarcadero RAD Studio I Android Tools) и отметьте "галочкой" в окне менеджера строку Google USB Driver (рис. 1.8). Щелчок по кнопке Install packages автоматически
отправит
запрос на
соотве!ствующий
сайт, с которого
будет осуществлено
бес платное
скачивание
драйверов.

Рис. 1.8. Установка USВ-драйверов для устройств Android
Хотя
существует возможность
непосредственной отладки
приложения
на реальном устройстве
(подключаемом
к
компьютеру
через
интерфейс USB),
лучшим решени
ем
станет развертывание
на
компьютере
эмулятора Android - говоря
языком про
граммиста, Android Virtual Device (AVD).
Для лучшей
формализации
процесса раз
вертывания эмулятора, вновь
разобью наши
действия по
пунктам.
1. Установите
на компьютер
образ
системы Android (Android system
image). Для этого:
•
нажмите кнопку Пуск Windows и выберите пункт Все проrраммы 1
Embarcadero RAD Studio ХЕ5 1 Android Tools;
•
в
появившемся на
экране окне Android SDK Manager отметьте
"галочкой" элемент
АRМ EABI v7a System Image в
интересующей вас
версии ОС Android и нажмите
кнопку Install
пп package. Подтвердите
свой выбор в
оче редном окне Choose Packages
to Install (рис. 1.9) и
дождитесь установки с сайта Google виртуальной
машины, SDK и других бесплатных
модулей.
f f н ;1_<!! 1' ,.._
i SOI( Plth: - •f,:_blк-.W.vм '..JJ.At"JStuФб".i.J. Pt ..-5€Ж$',<tlrt- -,...t\".М' -Jf/6-X113C .21'is,di
! Pa.c.Jo:196
,,t, №me
Tooi,
J Andro16 SDKLktn$C:
АР!
-
P-кfage Descriptlon 81.
Li«мt
у' ДncffoidWkTeok, ,.-лtiorтU.D.S
у" д11dтс,d SO,: PlattarrrH , 1ion
Oocume-,t.t:ion fOJ t..rнkoid SDK, АР! SOJ; fo.rm.дti(ltoid4.3.AP11S,'C\I\ v:- l«sforSDKдPl!!, J
V ММ[AВlv71Sya-emtm.ge.д.Мt-CIO
..,- Googlt.;$ Android /Jlfll, tmsюn
,,1" Sou1c.e-:f1YAndroed.SIЖ.APl1В,,eoм
.,,/ дndtoкl Support Lihr-Ny. , 18
.,/GooфttSIOriv«,fМ'OOf'l8
h'кdдм LКеtШ
!ntd.S Atorn S)lltem
lm;,gt, droll
- Aцdf"OIO SОК Tools, 1wi\ion 12.О
•
.Aмkoid stж
Pi,ttom,-took, lМЧ/1'1,18.0J
•Ooc:1.1merrt•ticn for Anctro1d SOt:;. дPIJI, revиn1
, 'S1Ж Pilrtorrn, Androicf 43, АР!11. ision1
-s.,npiк forsm: APJU, f'Мlon1
-AR.MEA611171 em&N9t.AndtoidдAJ&_ф"$Cl!,1
· Gocig APls,AmfrwAPJ18, 1
•Soufo;;esfo,AndrotdSDt(.APlli, 1
· Andtoid 5црро11 lO-,y, rr,,mon18
usв •
This isthtAndюid'SoftweaOt11do l(it Lkenи t.
![]()
Рис. 1.9. Установка виртуальной машины для приложений Android
2. Завершив
инсталляцию необходимых библиотек, приступим к
созданию
вирту ального
устройства. Для
этого:
•
при посредничестве кнопки Пуск Windows вновь доберитесь до элемента
меню Android
Tools;
•
![]()
в меню появившегося на экране Android SDK Manager найдите элемент
Tools I Manage АVDs;
•
нажав
кнопку New в
окне Android
Virtual Device Manager, приступаем
к оп
ределению
свойств
виртуального
устройства
(рис. 1.1О).
В
обязательном
по рядке
конкретизируйте
тип
устройства
(раскрывающийся
список Device) и
целевую
платформу
(раскрывающийся
список Target). Рекомендую
устано вить
флажок Use Host GPU, это
заставит
эмулятор при
работе с OpenGL ис
пользовать
функционал
вашего
графического
процессора.

Рис. 1.10. Соэдание виртуального устройства
3. Проверим
эмулятор
устройства.
Для этого найдите
его в перечне
сконфигуриро-
ванных
нами
устройств и
нажмите
кнопку Start (рис.
1.11).
ЗАМЕЧАНИЕ
Запуск эмулятора устройства на
базе
платформы Android - достаточно трудоемкое
мероприятие
и может
занимать несколько
минут!



fi Android Virtllat Devicc МАмgеr
------'----------------------------- �
Android Virtuol Devicos Device Definiticюs
List of msting Android Virtual Devicos located 11 C:\Users\dmn\.1ndroid\avd
|
AVD№mo v' AVD_дndroid_... |
Target№me Android 4.2.2. |
Pl1tform 4.2.2 |
APILcvel 17 |
СРU/АВ! ARM (1rmcabi•v.., |
|
|
(:;;;AVD 4.3 |
Android43 |
4.3 |
18 |
ARM (armeaЫ-v•.. |
,,;1 А valid Android Virtual Device. А repoiraЫe Android Virtual Device.
Х An Android Virtual Devicethat fail•d to lood. Click 'Dct1ils' to seethc error.
Рис. 1.11.
Запуск виртуального устро ства
Рис. 1.12. Выбор
эмулятора устройства для проверки приложения для Android
4. Подготовив
к работе
эмулятор Android,
мы можем приступать
к
программиро
ванию. Для этого создайте новое приложение для мобильной платформы и в обязательном
порядке в
дереве
менеджера проектов
выберите
узел с
целевым
эмулятором
(рис. 1.12).
Осталось н ать клавишу
<F9>...
Что делать, когда код зависит от платформы?
Несмотря на то, что библиотека FireMonkey предлагает нам универсальный инст румент, позволяющий создавать приложения, предназначенные для работы на раз ных программных платформах, надо понимать, что ни одна из библиотек не в со стоянии полноценно заменить родной API целевой операционной системы. Поэто W уже в данной главе позволю дать вам совет - если в вашем проекте потребуется воспользоваться функциями API, принадлежащими исключительно Windows, OS Х или iOS, то перед обращением к ним необходимо явным образом указать компилятору об их использовании так, как предложено в листинге 1.1.
![]()
prooedure TFoпnl.FoпnShow(Sender: TObject);
Ьegin
{$IFDEF MSWINDOWS}
//код дпя операционной системы Windows
Labell.Text:='Hello, Windows!';
{$ELSE} {$IFDEF МАСОS}
//код дпя операционной системы OS Х
Labell.Text:='Hello, Мае!';
{ $ELSE} { $IFDEF IOS}
//код дпя мобильной платформы iOS Labell.Text:='Hello, iPhone (iPad) ! ';
{$ELSE} {$IFDEF ANDROID}
//код дпя мобильной платформы Android Labell.Text:='Hello, Android! ';
{$ENDIF}
{$ENDIF}
{$ENDIF}
{$ENDIF}
end;
Во время компиляции вашего приложения Delphi разберется с подключением к проекту необходимых библиотек и создаст бинарный код для требуемой опера ционной системы.
Еще одна задача, которую, возможно, придется решать разработчику кроссплат форменного приложения, связана с определением базовых характеристик операци онной системы, под управлением которой запускается его творение. Если речь идет о настольном приложении, предназначенном для работы под Windows или OS Х, то
наиболее
универсальным
помощником
станет
интеллектуальная
запись тosversion, объявленная в программном модуле System. SysUtils. С помощью этой записи мы в два
счета выясним
тип ОС и
процессора (листинг 1.2).
![]()
procedure TForml.FormShow(Sender: TObject);
var s:string;
Ьegin
![]()
// os
case TOSVersion.Platform of
pfWindows pfМacOS
pfiOS pfAndroid pfWinRT pfLinux
end;
s:='Windows';
s :='OS
Х';
s:=' ios•·;
s : = 'Android' ;
s:='Win RT';
s:='Linux';
LаЬеll.Теft:='Операционная система '+s; Label2.Text:=TOSVersion.ToString;
![]()
// CPU
case TOSVersion.Architecture of
TOSVersion.TArchitecture.arintelXBб s:='IntelX86';
TOSVersion.TArchitecture.arintelX64
TOSVersion.TArchitecture.arARМ32
else s:='Неопределена';
end;
s:='IntelX64'; s:='ARМ32';
![]()
LаЬеlЗ.Техt:='Архитектура
процессора '+s; Label4.Text:=Format('Ядep %d', [ TThread.ProcessorCount]); end;
Если речь идет о мобильной
платформе IOS, то для получения
сведений об
устрой стве
и его ОС
можно воспользоваться
услугами API
операционной
системы, под ключив
к проекту
модуль iOSapi.urкit (листинг 1.3).
![]()
/.. и8'Имr 1.3.
uses iOSapi.UIKit;
{$R*.fm.x}
procedure TForml.FormShow(Sender: TObject);
var MobileDevice: UIDevice; s:string;
Ьegin
MobileDevice := TUIDevice.Wrap(TUIDevice.OCClass.currentDevice);
![]()
![]()
![]()
![]()
![]()
![]()
![]()
case MobileDevice.userinterfaceidiom of UIUserinterfaceidiomPhone: s:='iPhone'; UIUserinterfaceidiomPad s: =' i·Pad' ; else s:='Other';
end;
LаЬеll.Техt:='Устройство '+s;
Label2.Text .- Format('OC: %s %s', [MobileDevice.systemName.UTF8String,
MobileDevice.systemVersion.UTF8String]);
end;
•
![]()
ГЛАВА 2
Идея платформы FireMonkey основана на и.цее объектно-ориентированного про граммирования (ООП), поэтому изучение недр FireMonkey стоит начать с обзора опорных классов библиотеки. И здесь нас ждет первый сюрприз. Одного взгляда на иерархию наследования окажется достаточно для того, чтобы понять, что новейшая библиотека проектировалась не с "нуля". В начале цепочки наследования вы обна ружите хорошо знакомые по классической библиотеке VCL классы тоьjесt, TPersistent и, конечно же, тcomponent (рис. 2.1), и только затем, на четвертой сту пени иерархии вам встретится первый "кроссплатформенный" класс TFmxObj ect. Так что даже если вы решили окончательно и бесповоротно перейти на разработке своих приложений исключительно на FireMonk y, классическую библиотеку VCL вам забыть не удастся! ·
Вернемся к VCL. На рис. 2.1 представлен фрагмент иерархии наследования классов Delphi. Здесь вы обнаружите наиболее важные классы платформ VCL и FireMonkey (FМХ). Почему поставлен акцент на слово "важные"? Потому что именно эти клас сы (за счет абстрагирования, инкапсуляции, наследования и полиморфизма и дру гих механизмов, составляющих концепцию ООП) определяют основные родовые черты своих многочисленных кроссплатформенных потомков. Изучив опорные классы, мы получим представление о ключевых особенностях всех классов библио теки.
ВНИМАНИЕ!
Несмотря
на то, что
библиотека
FireMonkey базируется
на
библиотеке
визуальных
компонентов
VCL, в проектах FireMonkey
невозможно
задействовать
компоненты этой
библиотеки.
Опорный класс VCL - TObject
На вершине древа наследования расположен класс TObj ect. Отчасти это небожи тель, выше него нет никого. Класс тоьject не без гордости взирает с Олимпа на свое многочисленное потомство, ведь в каждом из потомков класса есть его родо вые черты.
![]()
(А)
ТОЬjесt (System) l
Q)
Q)
Q)
ф
TPersistent (Clasвes) р
3:
....,
Платформа
VCL Платформа FMX
Контейнер д.1Я
юображе1111й,
об.1асть
для
рис,юания, 11рю,итив11ые
граф11ческие фигуры, траектория
Глаsиоеи
нrплывающ е
,.\l(!Ш()
TComponent (Classes)
TFmxObject
(FMXTypes)
ТApplication (FMX.Forms)
TControlЗD (FMX.TypesЗD)
TCommonCustom Form (FMX.Forms)
TControl (FMX.Controls)
Ко.ибш,ировшшые
с11иски,
б,101,,,·1ют, калеидари, naueJlu, и11струлfентш,ьные 1иа11ки, блокноты
ТТextControl (FMX.Controls)
Метки, к11опю, выбора,
фла:жжи,
об.шсти
группировки
TStyledControl (FMX.Controls)
![]()
TCustomForm (FMX.Forms)
Сm,ски, .1111огострочный Строки ввода
текстовый редактор.
дерево, сетки,
ФорАtыРМХ
KDAиw11eJ1mы
TCustomFormЗD (FMX.FormsЗD)
ЗD, _.
TEffect (FMX.Filter.Effects)
TCustomButton (FMX.StdCtrls}
В11зуа.1ьные эффекты
К11011ки
Рис. 2.1. Иерархия наследования VCL-FMX в
Delphi ХЕ5
.(.А...)
![]()

К задачам, решаемым классом тоьject, в первую очередь стоит отнести:
□
создание, поддержку и
уничтожение объекта; распределение,
инициализацию и освобождение
памяти,
необходимой
для этого
объекта;
□
возврат всей информации .об экземпляре класса, в том числе данных
RTТI
об опубликованных
свойствах и
методах объекта;
□ поддержку взаимодействия объекта с внешней средой с помощью сообщений и
интерфейсов.
Управление жизненным циклом объекта
Исследование
вклада
класса 1:0bject в классы VCL
начнем с
конструктора
и дест
руктора.
Это наиболее
важные
методы для любого из класса VCL - они управляют
существованием объекта.
constructor Create; //создание объекта
destructor Destroy; virtual; //разрушение объекта
Пока мы видим наиболее простое объявление конструктора и
деструктора, в
ряде
· более поздних классах методы усложнятся и дополнятся параметрами.
Вам
наверняка
приходилось
размещать на
форме
проекта Delphi
различные
компо ненты
(среди них
кнопки,
строки ввода,
метки). Это
очень удобно:
для создания
элемента
управления достаточно
взять
компонент с
палитры и положить
его на форму. А
вы пробовали
создавать
элементы
управления и
размещать их на
форме, не
обращаясь к
палитре
компонентов?
Если нет, то
следующий
пример
окажется интересным.
ыбрав
пункт меню File I New I FireMonkey Desktop Application, создайте новое
приложение HD.
Убедитесь,
что в строке uses проекта
упомянут
модуль FМX.StdCtrls (в
нем
расположен
код стандартных
элементов
управления).
В переч не
событий
формы Fonnl
(на
вкладке Events Инспектора
объектов)
найдите собы
тие onshow.
Двойной
щелчок
левой
кнопкой мыши
в строке
напротив
названия со бытия заставит Delphi
создать код
обработчика
события,
вызываемого
в момент
показа формы
на экране.
Вам
осталось
лишь
повторить
несколько строк
кода, про
веденных в листинге
2.1.
![]()
,; Rис-тинr 2.1. .....,..,.....,.•••••• s
······-·· ··········•~ - ..................""'·••·····"···· ..,,.;. ·······•·•·•·•····......··-···· " ............ ,,._._,, .............................,.......,.ь,.,................. А,' ···········"·••'>•;·············
uses ... , FМX.StdCtrls;
procedure TFonnl.FonnShow(Sender: TObject);
var Btn: TButton; //переменная класса кнопки TButton Ьegin
Btn:=TButton.Create(Fonnl); //вьвов конструктора класса TButton
Btn.Parent:=Fonnl; //размещаем кнопку на форме
end;
![]()
![]()
![]()
![]()
После
запуска
приложения в
левом
верхнем углу
формы
появится
кнопка. Она
воз никла не из воздуха,
это
следствие
вызова
конструктора
create () для
экземпляра
класса твutton.
Обязательно
проведите
небольшой
эксперимент - замените
в коде
листинга 2.1
класс твutton
на TEdit (или
ТМеmо,
TLabel,
тсоmЬоВох). Это
придется
сделать в
двух местах
кода события onshow ( ) : в секции
локальных
переменных и
в строке вызова
конструктора.
Вновь
запустите
проект на выполнение.
В результате
на свет появит
ся элемент
управления
именно того класса,
который вы
указали. Как
видите, унас
ледованный
от тоьject конструктор
выполняет
свою задачу
во всей
иерархии
клас сов.
пишите всего одну строку кода (листинг 2.2).
Проведем еще один пример. На этот
раз он посвящен исследованию
деструктора класса - методу Destroy (). Для опыта нам понадобится помощь нового проекта
НD Application.
Разместите
две кнопки твutton (вы их
найдете на
странице Standard
палитры
компонентов)
на форме.
Научим одну
из кнопок
уничтожать своего "коллегу". Для этого дважды щелкните левой кнопкой мыши по кнопке
Buttonl и в появившемся в
окне редактора кода обработчике события OnClick на
-•••"'•·-•...,..:.••-u::• ••..............,,,q.,•P\'1',IY--•••·--•• ,,_.,,,.........,...... J-r ...............,••,,.,. •"'"-НJ""'''' f-•-•-4••••.. •t,!,::,C•fH•••·•,•.•• JV,-" •••••< ••" • r , s1' • ;, .-1,< 'f-0 -1
.......... «.................,•, •• .................. ..... ..................--.,--,-·-·--·w.е--.--'<"..д ·"""-........,..,i,,..............,,,.,...- .............,.,......... i:
procedure
TForml.ButtonlClick(Sender: TObject);
Ьegin
Button2.Destroy; //уничтожение экземпляра кнопки Button2
end;
Запустив
проект,
щелкните по
кнопке вuttonl. Это
действие
вызовет
деструктор
объекта Button2, и
элемент
управления
исчезнет с
поверхности
формы. Но на
этом
эксперимент
не завершен.
Вновь
щелкните по
кнопке Buttonl. В
ответ
разгневан
ный отладчик
Delphi выведет на
экран
примерно
такое
сообщение,
как на рис. 2.2.
Причина
возмущения
отладчика в том,
что мы
потребовали
от Delphi
невозможно
го - повторно
уничтожить
уже
несуществующий
объект. Для
того чтобы
впредь
избежать подобных исключительных ситуаций, в
наших программах вместо
· Destroy () для уничтожения экземпляра объекта следует использовать метод
procedure Free;
|
Debuggt!r Exc_eption Notification |
|
|
|
|
Pro t Project:1.exe rl!ised
exceplion dass $COOOOOOS wi!h message 'access lliolaltion at Ox00at0dle3: reвd af address ОхОООООООО'. CJ lgnor!! this excepiion !,уре о-- JI Jl Wф J |
|
Рис. 2.2. Сообщение об ошибке доступа EAccessViolation к несуществующему объекту
![]()
Эго
более
осторожный
метод. Перед
тем как вызвать
деструктор
объекта, он убе дится в
факте
существования
этого объекта.
Для проверки
возможностей
метода замените в
листинге 2.2
обращение к
деструктору Destroy на
Free и вновь
повтори те
опыт. На этот
раз повторный
щелчок по
кнопке не
приведет к исключительной
ситуации.
Механизм учета ссылок в
мобильных проектах
Мобильные приложения для контроля жизненного цикла объектов ведут автома тический подсчет ссылок (Automatic Reference Counting, ARC) на удаляемый объ ект. В момент создания объекта счетчику ссьшок присваивается единичное значе ние, а в момент ухода объекта из области видимости компилятора (например, по завершению локальной процедуры, внутри которой был объявлен и создан объект) значение счетчика уменьшается на единицу, и если он оказывается равным нулю объект уничтожается.
Благодаря подсчету ссьшок существенно расширяются наши возможности по управлению объектом. В простейшем случае об уничтожении локальных объектов можно просто не думать - они удалятся сами. При необходимости программист имеет возможность самостоятельно уменьшить счетчик ссьmок. В современной версии Delphi ХЕ5 вы имеете возможность прекратить использовать этот объект, не дожидаясь его выхода за область видимости (листинг 2.3).
ЗАМЕЧАНИЕ
ARC ни в
коем случае не является эквивалентом сборщика мусора, используемого
в платформе
.NЕТ. В отличие
платформы от
.NET (в которой
постоянно
трудится фо
новый процесс,
очищающий
освободившуюся
память спустя
какое-то время после
"уничтожения"
объекта) в ARC
память освобождается
в тот
же момент,
как
обнулится счетчик.
![]()
XE50bject:=TXE50bject.Create(); XE50bject.XE50bjectMethod;
XE50bject:=nil;
Передав объектной переменной неопределенное значение nil, мы говорим новому компилятору Delphi ХЕ5, что объект нам больше не нужен. После этого компиля тор самостоятельно позаботится о том, чтобы объект уничтожился после того, как завершится выполнение его метода.
ВНИМАНИЕ!
![]()
В новом
компиляторе
с поддержкой
ARC деструкторы
Destroy () ,
Free () и FreeAndNill
() не смогут
явным
образом
удалить
объект, а лишь
уменьшат
счетчик ссылок. Таким образом, в мобильных
проектах вместо вызова классических
деструк торов достаточно
обнулить
ссылку - присвоив объекту nil.
![]()
У всех потомков тоьject предусмотрено доступное только для чтения свойство
property RefCount: Integer; //только дпя чтения
с помощью которого мы сможем узнать число ссылок на объект.
Что делать, если программисту (скорее не просто программисту, а коварному про граммисту) требуется расправиться с объектом, невзирая на состояние сч тчика ссылок? Для этого на уровне тоьj ect в Delphi ХЕ5 появился весьма кровожадный деструктор
procedure DisposeOf;
который уничтожает объект самым тоталитарным способом. С точки зрения чисто ты программирования это не очень хорошо, т. к. после работы Disposeof () память после объекта не освобождается. Подобный объект будет вести себя неадекватно, с одной стороны его уже нет, а с другой вы можете обратиться к его свойствам (ведь в памяти объект остался). Если логика программы предполагает использова ние Disposeof (), то, по крайней мере, перед обращением к объекту проверяйте со стояние свойства
property Disposed: Вооlеаn;//только дпя чтения
и если свойство возвратит true, лучше этот объект более не задействовать.
В составе класса тоьject имеется ряд полнофункциональных обычных методов .и методов класса (class function), позволяющих получить подробную метаинформа_ цию о классе. Ключевые методы тоьjесt представлены в табл. 2.1.
ЗАМЕЧАНИЕ
Методы класса разрешено вызывать без создания экземпляра самого класса.
Таблица 2.1. Получение сведений о классе
|
Метод |
Описание |
|
class function ClassName: string; |
Функции возвратят текстовую строку с названием
класса |
|
function ToString: string; |
|
|
class function UnitName: string; |
Имя модуля, в котором описан класс |
|
function ClassType: TClass; |
Возвратит описание класса |
|
class function InstanceSize: Longint; inline; |
Размер в
байтах, необходимый для создания
экземпляра
класса |
|
class function ClassNameis(const Name: string): Boolean; |
Проверка, является ли объект экземпляром
класса
Name |
|
class function InheritsFrom(AClass: TClass-) : Boolean; |
Проверка, является ли объект потомком класса AClass |
|
class function ClassParent: TClass; |
Возвратит непосредственного предка объекта |
![]()
![]()
![]()
![]()
Таблица 2.1 (окончание)
|
Метод |
Описание |
|
class function MethodName(Address:
Pointer): string; |
Возвращает
имя метода
по
указателю. Метод
должен быть
опубликован в секции
puЬlished |
|
class function MethodAddress(const Name: string): Pointer; |
Возвращает указ.атель на метод по его имени.
Метод
должен быть
опубликован в
секции puЬlished |
|
function FieldAddress(const Name: string): Pointer; |
Возвращает
указатель
на поле с
именем Name. Поле
должно быть
опубликовано в
секции puЬlished |
Все пере исленные в табл. 2.1 методы являются мощным оружием, позволяющим подготовленному программисту не только создавать эффективный код, но и при необходимости проводить исследовательскую работу (листинг 2.4).
![]()
var C:TClass;
Ьegin C:=Memol.ClassType; while C<>nil do Ьegin
Memol.Lines.Add(C.ClassName+' -> '+C.UnitName);
C:=C.ClassParent;
end; end;
В листинге предложен код, демонстрирующий порядок получения полной цепочки наследования любого объекта из библиотек VCL и FireMonkey. В нашем примере. в качестве "подопытного" мы взяли класс мноrострочноrо текстового редактора тмеmо. Результат работы отражен на рис. 2.3.
-ТМ-еm-о ·>-FМ-Х.Моmо Т&:tollВox ·> FМX.Layou1$ ТS!yledConlrol->
FМX.Controls ТConlrol->FМX.Conltols ТFmxOЬject ·> FМХ.Туреа TComponent ·> System.Classes ТP01$1sten1 •> System.Cluses TT)t)jec;t•>System ·- .. . -·- ·- J •
'
•n.c. ---.-.

Рис. 2.3. Перечень предков класса 'ГМеmо
![]()
![]()
![]()
Одну
из наиболее
значимых
ветвей
классов начинает
ближайший
сподвижник TObject- класс TPersistent
(модуль
System.Classes). Основное
предназначение
класса -' научить своих потомков работать с
памятью, что позволит им загружать
данные
из области в памяти и выгружать данные в другую область.
Как
и в случае с тоьject, основу
рассматриваемого
класса
составляютолько_мето
ды. Среди них
наибольшего
внимания заслуживают
виртуальные
методы
присвое ния
данных:
procedure Assign(Source: TPersistent); virtual;
procedure AssignTo(Dest: TPersistent); virtual;
Перечисленные процедуры
позволяют
разделить
одну и ту
же область
памяти меж
ду несколькими объектами
- потомками TPersistent. Метод Assign () позволяет
одному
объекту
подключиться к
ресурсу
другого,
метод Assignтo
() - передать ре-· суре
от одного
компонента
к-другому.
Для демонстрации
этой полезной возможности
создайте
новый проект и
разместите
на его
главной
форме два
компонента Timage (
они
предназначены
для хранения
и отображения
векторной и
растровой
графики) и
кнопку твutton. Воспользовавшись
свойством Bi tmap
компонента
Imagel, загрузите
в контейнер
любой файл с
изобра жением.
ЗАМЕЧАНИЕ
После
установки Embarcadero RAD
Studio на
компьютер
файлы с
рисунками
вы обна
ружите в
каталоге c:\Program
Files\Embarcadero\RAD Studio\x.x\lmages\,
если вы
рабо таете в 32-разрядной
Windows, или в каталоге
c:\Program Files (x86)\Embarcadero\RAD Studio\x.x\lmages\ при
работе в
64-разрядной
ОС.
Осталось написать одну строку кода в
обработчике события OnClick кнопки вuttonl
(листинг 2.5).
![]()
procedure
TForml.ButtonlClick(Sender: TObject);
Ьegin
Image2.Bitmap.Assign(Imagel.Bitmap);
end;
Щелчок
по кнопке
приведет к
тому, что во
втором
компоненте Image2 отобразится
та же самая
картинка, что
и в Imagel.
Еще
раз
подчеркну,
что метод Assign () не создал
еще одну
копию
картинки, а
разделил
одну и ту же
область
памяти между
двумя потомками
класса TPersistent.
Надо
понимать, что
методы Assign () и Assign'Гo () позволяют
разделять
ресурс
только между
совместимыми
компонентами,
способными
прочитать
этот ресурс. Встретив
даже такую,
абсолютно
абсурдную строку
кода
Buttonl.Assign(Imagel.Bitmap);
![]()
среда
проектирования
не станет
сопротивляться.
Но
полученный
исполняемый
код окажется
неработоспособным, т. к. мы
попытались
передать
кнопке
неприемлемую
область
памяти.
Научив
несколько
объектов
работать с
общей
областью
памяти, класс
TPersistent позаботился
еще об одной
важной
детали - при
необходимости
любой из
совме стно
работающих
объектов
имеет право
уйти в
автономный
режим рабо:rы,
при этом не
нарушая
состояния
своих коллег.
Допустим, что
у нас
имеются
текстовый
редактор Memol : тмеmо, хранящий какой-то перечень
текстовых
строк, и его коллега меmо2 : тмеmо. После вызова строки кода
Memo2.Lines.Assign(Memol.Lines);
оба
компонента
разделят
общую
область памяти
и станут
отображать
одинаковые
текстовые строки.
Но как только
какой-то из
компонентов
захочет
изменить хо.тя бы запятую
в тексте,
то для
него
моментально
вьщеляется
отдельная
область памя
ти, в которую
переносятся
все
изначальные
строки и
новый текст.
Приступим
к рассмотрению
третьего по счету
в цепочке наследования
(см. рис. 2.1)
класса - класса тcomponent. Наш
очередной
знакомый
построен на
основе только
что изученного
класса TPersistent (модуль System. Classes).
TComponent
= class(TPersistent, IInterface, IInterfaceComponentReference)
Обсудим
вклад тcomponent в библиотеки
VCL и FireMonkey.
Во-первых, от него бе рут
начало
все
компоненты, которые
вы найдет на
палитре
компонентов Delphi.
Во-вторых, в
логику
класса
заложено
понятие
"частной
собственности".
Другими словами,
в тcomponent предусмотрено
право владения,
когда один объект выступает
владельцем другого
объекта.
В-третьих,
класс
расширяет
возможности
по хране нию
и
обслуживанию
данных. В-четвертых,
в отличие от
своих
предков (опери
рующих
только методами) класс тcomponent обладает
свойствами.
Самое главное свойство, подаренное тcomponent своим наследникам, - имя.
property Name: TComponentName; // type TComponentName: string;
Каждый
компонент,
задействованный
в проекте Delphi,
обязан иметь
уникальное
имя. Имя присваивается автоматически в
момент переноса элемента управления
с палитры
компонентов
на поверхность
рабочей
формы
проекта.
Механизм
выбора имени
достаточно прост: берется
имя класса
(допустим, твutton), убирается
первый
символ "Т" и к
окончанию имени добавляется
порядковый
номер
элемента
управ ления этого типа
на форме.
Так первая
кнопка,
размещенная
на форме,
получит на звание
Buttonl, вторая - вutton2 и т. д.
У каждого компонента опубликовано свойство, никогда не используемое системой
prope ty Tag: Longint;
![]()
![]()
![]()
![]()
Это свойство часто применяется программистами для дополнительной иденти фикации объекта или хранения ассоциированных с объектом целочисленных зна чений.
Владение объектами
При разработке проекта на основе формы (кл сс TForm) мы располага м на поверх ности формы кнопки, строки ввода, метки и другие компоненты, необходимые для работы приложения. Форма (дальний потомок тcontrol) становится владельцем (owner) этих компонентов. В свою очередь, каждый из компонентов знает, кому он принадлежит, для этого следует опросить свойство
property Owner: TCornponent;
Для того чтобы любой компонент прямо в момент своего рождения приобре тал владельца на уровне класса тcornponent, несколько усовершенствован конст руктор
constructor Create(AOwner: TCornponent);
В объявлении появился
параметр AOwner, благодаря
ему устанавливается связь ком
понента с его владельцем
в момент
вызова
конструктора.
Если вы
вернетесь к пер
вому примеру
из этой главы
(см. листинг 2.1), то увидите,
что при создании 'кнопки
мы указали, что ее
владельцем
становится
форма Forml.
ВНИМАНИЕ!
Уничтожение объекта-владельца
влечет за
собой уничтожение всех
принадлежащих
ему объектов.
Владелец компонентов имеет представление о числе принадлежащих ему компо нентов и в состоянии обратиться к каждому из них. Для этого предназначены соот ветствующие свойства:
property CornponentCount: Integer; //число компонентов
property Cornponents[Index: Integer]: TCornponent; //список компонентов
Каждый компонент
знает свой индекс в
списке
компонентов
своего владельца:
property
Cornponent!ndex: Integer;
Ряд методов позволяет манипулировать списком компонентов, для которых тcontrol выступает владельцем. Удаление компонента из списка обеспечивается процедурой
prooedure RernoveCornponent(AComponent: TComponent);
Полная очистка списка с одновременным уничтожением компонентов:
prooedure DestroyComponents;
Вставка в конец списка нового компонента:
![]()
procedure InsertCornponent(AComponent: TCornponent);
![]()
Поиск компонента-потомка в
списке:
function FindComponent(oonst AName: stri.ng.):
TComponent;
ЗАМЕЧАНИЕ
Для
создания
группы из
нескольких
элементов
управления с возможностью
их совме
стного
использования
(перемещения,
выравнивания,
вращения и т. п.)
лучше всего
воспользоваться
помощью компонента
TLayout.
Завершая эту главу, вновь повторюсь, что VCL не отвергнут, и фундамент "древ ней" визуальной библиотеки компонентов задействуется в интересах сверхновой библиотеки FireMonkey. Вместе с тем надо знать, что у FireMonkey интерес к VCL угасает сразу после прохождения уровня класса тcomponent. Дальше начинают ра ботать исключительно классы новой IЧJатформы.
ГЛАВА 3
Современный
язык Delphi
приобрел
возможность работы
с шаблонами
или, говоря
точнее, с обобщенными
типами (generics
types) данных1• Своим
появлением
на свет
шаблоны
обязаны
желанием
разработчиков
развить
возможности
ООП. Именно в
языках
программирt,вания,
основанных
на ООП, код приобрел
истинную
универ
сальность и
стал позволять
программистам
повторно
использовать
первоначально
разработанный
алгоритм для
решения
новых задач.
Создав исходный
класс для
применения в
какой-то прикладной
задаче, мы
имеем
возможность
перенести
свой класс
(или его
потомков) в
другой
проект. Однако
обычные
классы, пусть
даже
усиленные за счет наследования,
инкапсуляции
и полиморфизма,
далеко не универ
сальны, ведь
их методы и
свойства нацелены
на
обслуживание
строго
определен
ных типов
данных.
Концепция
обобщенного
типа данных
как раз и
предназначена
для
устранения
этого
недостатка - она
позволяет
программисту
абстрагироваться от типа
данных.
Благодаря
шаблонам
разработчик
получает
возможность
создавать
алгоритмы, не указывая
типы данных,
с которыми он
работает.
Услуги
обобщенно го
типа данных
окажутся
весьма
кстати при
описании
схожих алгоритмов,
зани
мающихся
обработкой
разноплановых
данных.
Например, это
позволяет
реализо вать
классические
алгоритмы
сортировки
массивов
самых
разнообразных
типов данных
в рамках
одного и того же
метода
класса.
ЗАМЕЧАНИЕ
Благодаря шаблонам мы получаем удобную возможность отделения алгоритма от
конкретных
типов данных,
с которыми он
работает.
Обобщенный тип данных в
полях записей
Основные
приемы
работы с
обобщенным
типом данных
наиболее
просто проде
монстрировать
с помощью
записи Delphi. Еще
в первых
версиях
языка Pascal по
явилась запись - структурный тип,
специализирующийся
на
предоставлении
дос-
![]()
1 В некоторой литературе вместо термина "обобщенный тип"
применяют термин "родовой тип".
![]()
![]()
![]()
![]()
42 Глввс1 З
тупа
к
некоторому
перечню
разнотипных
полей. В
современных
версиях Delphi
программисту
представилась
уникальная
возможность
включать в состав
записи
не только строго
типизированные
поля, но и
поля-обобщения,
т. е.
поля, тип данных которых
заранее не
определен.
Описание шаблона
записи с
полями
обобщенного
типа
выглядит
следующим
образом:
type имя_типа_записи <имя_обобщенного_ типа [ , .•. ] > = record
имя_поля: имя_обобщенного_ти 1с1;
[Другие поля]
end;
Наличие
угловых
скобок уведомляет
нас, что
запись
намерена
работать с обобщен
ным типом данных. Внутри угловых скобок перечисляются
параметры
типов (type parameters). С одной
стороны,
параметр типа- это имя
переменной,
которое мы задействуем
внутри
исходного
кода класса в
тех местах,
где
используется
соответ ствующий
тип данных. С
другой стороны,
параметр
типа - это
псевдоним типа данных,
которым
воспользуется программист
при
обращении к
классу извне.
ЗАМЕЧАНИЕ
В синтаксических конструкциях языка Delphi угловые скобки <... > являются визитной
карточкой обобщенного типа данных.
Отметим,
что при
определении
имени обобщенного
типа нет
необходимости
вспо минать о
существовании
предопределенных
типов данных
Delphi. Имя обобщенно
го типа - это всего
лишь
псевдоним
какого-то
заранее
неизвестного
типа данных,
во
время
выполнения
программы им
может оказаться
целре или вещественное
чис ло,
строковый
или символьный
тип либо
какой-то
другой тип
данных.
Листинг
3.1
демонстрирует
порядок
объявления
шаблона
записи с
полями обоб щенного
типа. Переч нь
имен,
используемых
в записи
обобщенных
типов данных,
приводится
внутри
угловых
скобок, сразу
за названием
типа записи.
![]()
type TGenericRec<T,R>= record
|
А в,с |
char; Т; |
//в любом случае это символьное поле //поля обобщенного типа Т |
|
D |
R; |
//поле обобщенного типа R |
|
end; |
|
|
var GRecl:TGenericRec<integer, douЫe>; //объявление переменной GRecl GRec2:TGenericRec<boolean, string>; //объявление переменной GRec2
Ьegin
GRecl.B:=15; //поле В
хранит целое значение
GRecl.D:=0.675; //поле
D хранит вещественное значение
// ...
GRec2.B:=true; //поле в хранит булев тип данных
GRec2.D:='Hello, World! '; //в поле D находится строка с
текстом
// ...
![]()
end.
![]()
Во время
инициализации
переменных
имена обобщенных
типов
заменяются
назва ниями реальных типов данных. В
нашем примере при объявлении GRecl ( см. лис
тинг 3.1) вместо обобщения т
мы задействуем
целочисленный
тип integer,
вместо
R - вещественный
тип douЫe. При объявлении
GRec2 мы меняем
правила игры:
теперь обобщенный
тип т замещается
типом boolean,
R - string.
Обобщения в процедурах и функциях
Если
абстрагироваться
от типа
данных, окажется,
что логика
очень многих
алго ритмов
абсолютно
одинакова.
Например,
алгоритм
быстрой
сортировки
идентичен как для
массива
целых, так и для
массива
вещественных
чисел. В
подобных
ситуа циях
благодаря
созданию
обобщенной
функции
(функции с
параметром
обобщен ного
типа) мы
сможем
определить
смысл
алгоритма, не
отвлекаясь
на обслужи
ваемый тип
данных. Поэтому
обобщенный
тип данных
может
оказаться
весьма
полезным при
разработке
шаблонов
процедурных
типов данных,
способных
вос принимать
параметры
любых типов.
По
аналогии с объявлением
обычной
процедуры
или функции,
при создании
шаб лона
процедуры
или функции
сначала
указывают
ключевое
слово type, а затем
имя типа.
Сразу за
именем сле·дует
синтаксический
элемент,
характерный
для обобще
ний - пара
угловых
скобок <... > с названием
обобщенного
типа данных.
type тип_функции <имя_обобщенного_типа[, ... ]> = ,
funсtiоn([параметр_обобщенного_типа [, ... ]]) :имя_обобщенного_типа;
type тип_процедуры <имя_обобщенного_типа [, ... ]> =
procedure
([параметр_обобщенного_типа [, .. : ] ]
Листинг 3.2 демонстрирует
порядок
создания
шаблона функцци.
В примере
объяв лена
функция класса
Getмaxvalue () с двумя
параметрами,
тип которых
не опреде
лен. Задачей
функции
является сравнение
двух
значений и возврат
наибольшего
из них.
uses System.SysUtils, System.Generics.Defaults; type TGetMax
= class
class function
GetMaxValue<T>(X, У: Т)
:Т;
end;
class function
TGetMax.GetMaxValue<T>(X, У: Т): Т;
var Comparer:
IComparer<T>;
Ьegin
{к сожалению, нельзя написать такой код: if Х>У th�n Result:=X else Result:=Y;
ведь мы не знаем тип данных сравниваемых параметров}
Comparer:=TComparer<T>.Default;
44 Глава 3
![]()
if Comparer.Compare(X,Y)>O then Result:=X
else Result:=Y;
end;
var А: integer;
B:extended;
C:ansichar;
Ьeg;i.n
A:=TGetMax.GetMaxValue<integer>(l0,20); //сравниваем целые числа Wri teLn (А) ;
B:=TGetMax.GetMaxValue<extended>(l/3,2/3); //вещественные числа WriteLn (В);
C:=TGetMax.GetMaxValue<ansichar>('A',
'В');//сравниваем символы WriteLn
(С) ;
ReadLn;
end.
Реализация обобщенной функции требует ряда комментариев. К сожалению, срав нивая данные неизвестного типа, мы не имеем возможности воспользоваться обыч ными операторам,и сравнения (например: if Х<У then ... ). Вместо этого мы выну ждены обратиться за услугами к интерфейсу rcomparer (он описан в модуле System.Generics.Defaults). Этот интерфейс (кстати, также основанный на идее шаблонов) является профессионалом в области сравнения двух величин, для этого у него есть метод Compare (). Все остальное дело техники - в листинге 8.2 мы срав нили значения трех различных типов данных.
Обобщенные
типы данных в
шаблонах
классов
Синтаксическая конструкция, объявления шаблона класса, способного работать с обобщенным типом данных, не многим отличается от унаследованной еще со времен языка Object Pascal конструкции описания обычного класса. Здесь также присутствуют ключевые слова type и class, и класс-шаблон, так же как и его обыч ный "коллега", содержит поля и методы. Однако внимательный читатель обнару жит отличительную черту шаблона - пару угловых скобок <... > сразу после име ни класса:
type имя_класса<параметр_обобщенного_ типа [, ... ] > = class
//поля и
методы
end;
![]()
Простейший
пример
использования
обобщенного
типа при
объявлении
шаблона
класса предложен
в листинге 3.3.
![]()
![]()
![]()
unit generics_unit;
interface
type TGener�cClass<T>=class
fT:T;
procedure SetT(Value:T);
function GetT:T;
//объявление шаблона класса
//поле обобщенного типа
//метод записи в
поле
//метод чтения значения из поля
![]()
![]()
end; implementation
function TGenericClass<T>.GetT: Т;
Ьegin
Result:=fT; //узнаем содержимое поля
end;
procedure TGenericClass<T>.SetT(Value: Т);
Ьegin
fT:=Value; //записываем данные в поле
end; end.
Для объявления параметра типа мы воспользовались символом т и заключили его в угловые скобки. Класс содержит единственное поле fT универсального типа т и два метода, позволяющих записывать и считывать значение из поля.
Разобравшись с
объявлением и
реализацией шаблона класса,
способного работать
с обобщенным типом данных, попробуем воспользоваться его услугами. Лис
тинг 3.4 демонстрирует порядок обращения к
экземпляру класса TGenericClass с указанием того,
что он
необходим нам в роли
хранилища целого числа.
![]()
var gObject: TGenericClass<integer>; i:integer;
Ьegin
gObject:=
TGenericClass<integer>.Create;
gObject.SetT(99); //записываем значение 99 i:=gObject.GetT; //считываем значение из класса
. gObject.Destroy; end;
Ключевой
элемент
листинга 3.4 - это
его первая
строка. В ней
указывается,
какой тип данных должен будет задействован после создания экземпляра класса
![]()
46 Глава З
TGenericClass. Название типа записывается в угловых скобках после имени класса, в нашем случае это целое число integer. С таким же успехом мы можем создать объект любого типа, за небольшим исключением. В параметр типа не стоит переда вать статистические массивы, строки старого образца (short string) и записи, со держащие разнотипные поля.
Любой, построенный на основе концепции обобщенного типа данных, шаблон класса вправе выступать в качестве предка для своей цепочки классов-наследников. Объявляя дочерний класс, программист принимает решение о статусе параметра обобщенного типа. Предусмотрены два варианта наследования (листинг 3.5):
□ наследование с сохранением обобщенного типа данных, другими словами, класс-наследник остается шаблоном;
□ наследование с явной конкретизацией типа данных, который следует использо вать при работе дочернего класса.
![]()
type TGenericParent<T>=class' {родительский класс с
открытым параметром Т}
protected
fGP:T;
puЫic
procedure SetGP(Value:T); function GetGP:T;
end;
(1 вариант наследования - объявляется новый класс-шаблон}
TGenericChild<T>=class(TGenericParent<T>)
//поля и методы класса TGenericChildl
end;
{2 вариант - объявляется обычный класс} TNotGenericChild<T>=class(TGenericParent<integer>)
//поля и методы класса TNotGenericChild
end;
В нашем примере описываются два дочерних класса. Первый из них, класс TGenericChild, допускает, что при обращении к свойствам и методам дочернего и родительского классов могут использоваться любые типы данных, поэтому TGenericChild сохраняет все черты шаблона.
Напротив, класс TNotGenericChild перестал быть шаблоном, он явным образом ука зывает, что класс-предок в состоянии воспринимать только целочисленные данные.
При описании дочернего класса-шаблона разрешается вводить дополнительные параметры, способные работать с обобщенным типом данных, например:
TGenericChild2<T, C>=class(TGenericParent<T>)
![]()
![]()
![]()
![]()
Таким образом,
в дочернем
классе появился
дополнительный параметр
обобщенно го типа.
Перегрузка методов с
параметром обобщенного типа
Наличие
у метода класса параметра обобщенного
типа не служит препятствием
при
перегрузке
одноименных
методов. Листинг
3.6 демонстрирует
порядок
объявления и перегрузки
обычного
метода и шаблона
метода с
параметром
обобщенного типа.
• '•" lil ,,;1t::: •;'.ff"'-"9\', I ,,/,r.•r,ь "'N'."' •, "C' ' . ;,!'• 1 1, 1, 1 . ,
._ ,о,;, " 1 •, ..,. • JII"', • • " 4,,.,, r, , • ',.... ,::• ,.. "•""•t _, • ' '• , 1 !i/,
type TDemoClass = class
procedure DemoMethod(X: douЬle); overload; //обычный метод
prooedure DemoMethod<T>(X: Т); overload; //шаблон метода
end;
В распоряжении
демонстрационного
класса имеются
два метода Demoмethod () . Па раметр
первого метода задан
явным
образом в
виде
действиtельного
типа douЬle,
а
параметр
второго
представлен
в виде обобщенного
типа т.
Листинг 3.7
показывает порядок вызова перегружаемых
методов.
![]()
var SC:TDemoClass;
Ьeqin
SC:=TDemoClass.Create;
SC.DemoMethod(З.1426); //обращение к
DemoMethod(X: douЬle)
SC.DemoMethod<Integer>(SOO); //обращение к
шаблону DemoMethod<T>(X: Т)
// ...
Обратите
внимание на
то, что при
вызове шаблона
метода с
обобщенным
парамет ром
следует
определиться
с типом
обрабатываемых
данных,
указав его в угловых скобках
после
названия
метода.
ЗАМЕЧАНИЕ
Основные
обобщенные
типы данных Delphi
объявлены в
модуле Generics. Collections. Здесь вы
обнаружите
шаблоны для
таких структур
данных, как
списки TList<T>, очереди TQueue<T>, стеки TStack<T>, словари
TDictionary<TKey,TValue>
и
т. д.
Шаблон массива, класс TArray<>
В Delphi
вы найдете
немало
классов,
работающих с
данными обобщенного
типа.
Давайте
познакомимся
с
одним из
наиболее
показательных
классов - шаблоном
массива TArray<>. Класс-шаблон TArray<> описан в модуле System.Generics.
![]()
48 Глава 3
Collections и
предназначен для
оказания помощи программистам при
решении задач
упорядочивания
элементов
массива по
возрастанию
значений и
поиску дан ных в массиве.
Фрагмент объявления класса TArray<>
предложен в
листинге 3.8.
![]()
type TArray = class
private
class prooedure QuickSort<T>(var Values: array of Т;
const Cornparer:
ICornparer<T>; L, R: Integer);
puЬlic
class prooedure Sort<T>(var Values: array of Т); overload;
//другие версии процедуры сортировки Sort<T>
class function BinarySearch<T>(const Values: array of Т;
const Item: Т;
out Foundlndex: Integer;
const Comp�er: IComparer<T>;Index,
Count: Integer): Boolean;
overload;
//другие версии процедуры бинарного поиска BinarySearch<T>
end;
Обратите внимание на то·,что класс не предназначен для хранения данных, для это
го у него
нет ни одного
поля. Зато в
распоряжении
TArray имеются
две группы
од ноименных методов-шаблонов. Методы Sort<T> ( J позволяют сортировать данные
в
массивах любого простого типа, а
методы Binarysearch<T> () найдут в упорядо
ченном
массиве
индекс
ячейки с
заданным элементом.
Все про'цедуры и
функции
объявлены в
качестве методов класса (напомню, что об этом
можно судить по сло
ву class вначале объявления метода), а
это означает, что методы подлежат вызову
без
создания экземпляра
класса.
Воспользуемся
услугами
класса
TArray<> для
упорядочивания
значений в трех
раз нотипных
массивах
(листинг 3.9). Для
этого
задействуем
его метод sort<T> ( J.
![]()
program TArrayDemo;
{$АРРТУРЕ CONSOLE}
uses System.SysUtils, System.Math, Generics.Collections;
.
const Size=l0; //размер массивов
var A:Array[0 .. Size] of Integer; //объявляем
массивы B:Array[0 .• Size]
of Real;
![]()
C:Array[0 .. Size] of AnsiChar;
i:Word;
![]()
![]()
Ьegin
Randornize;
//инициализируем генератор псевдослучайных чисел for i:=0 to Size do {заполним массивы случайными значениями}
Ьegin
A[i] :=Randorn(l00); //генерируем целое число
B[i] :=Randorn(l00)/RandornRange(l,100); //генерируем действительное число C[i]:=AnsiChar(RandornRange(ЗЗ,255)); //генерируем символ
end;
TArray.Sort<integer>(A); TArray.Sort<Real>(B);
TArray.Sort<AnsiChar>(C);
//упорядочим значения в
массивах
![]()
for i := О to Size do //проверим результат сортировки
Ьeqin
Write ('А[',i, '] =',#9,А [i],#9);
Wri te ( 'В
[ ' , i, ' ] =' , # 9, В[i] , # 9) ;
Write ('С [ ', i, '] =', #9, С [i],#9);
WriteLn; end; Read.Ln;
end.
ЗАМЕЧАНИЕ
При
разработке класса TArray<> программисты
Embarcadero
использовали
одни из
наиболее эффективных
алгоритмов
сортировки и
поиска. Так
упорядочивание
эле ментов
осуществляется
с помощью
метода,
получившего
название быстрой
сорти ровки (quick sort), а поиск реализован
методом бинарного поиска
(Ьiпагу search).
Оба метода
относятся к классу
сложности алгоритмов
с оценкой
N • Log(N), это
визитная
карточка
наиболее производительных
алгоритмов.
Шаблон
списка
объектов,
класс TObjectList<>
Рассмотрим очередной шаблонный класс, входящий в состав модуля Systern. Generics. Collections. Класс тоьjectList<> легко справится с хранением лю бого заданного программистом перечня объектов и предоставит доступ к любому из объектов по его индексу.
По своей сути класс представляет собой список, способный выступать владельцем объектов. Именно поэтому, при создании экземпляра класса конструктор
constructor
Create(AOwnsObjects: Boolean = True);
по умолчанию устанавливает аргумент владения AOwnsObj ects в состояние true.
Впрочем, если по какой-то причине в момент создания списка вы передали в кон-
![]()
50 Глава З
структор аргумент false, никто не запретит вам передумать и сделать список вла дельцем объектов. Для этого обратитесь к свойству
property OwnsObjects: Boolean;
из кода вашей программы.
Среди примеров к книге есть небольшая программа-игра, демонстрирующая работу тoьjectList<>. Суть игры заключается в том, что пользователь должен ловить ле тящие вниз шарики- экземпляры класса тcircle (рис. 3.1).
![]()
![]()
![]()
Не трудно догадаться,
что главная
роль в коде отводится
списку тobjectList<>, ко торый возьмет на себя
обязанности по обслуживанию падающих с
"небес" объек тов. Работа приложения начинается
с создания списка (листинг 3.10).
![]()
var Foпnl: TFoпn;
List: TObjectList<TCircle>; //список объектов TCircle
implementation
{$R *. fmx}
prooedure TFoпnl.FoпnCreate(Sender: TObject);
Ьegin
List:=TObjectList<TCircle>.Create; //здесь будут храниться шарики
end;
Как видите, в
момент объявления объектной переменной List: тoьjectList<TCircle>
мы
явным
образом
указываем,
что список
намерен
дружить
исключительно
с ша риками
TCircle.
![]()
![]()
В программе задействованы два таймера. Первый из них, компонент Timerl, отве чает за создание очередной порции шариков, это происходит каждый раз, как у таймера генерируется событие отсчета времени (листинг 3.11).
![]()
procedure
TForml.TimerlTimer(Sender: TObject);
var Circle:TCircle;
Ьegin
Randomize;
Circle:=TCircle.Create(nii); //очередной шарик List.Add(Circle); //добавляем шарик в
список Circle.Parent:= Forml;
Circle.Position,Y:=o;· //место появления шарика на форме Circle.Position.X:=Random(Round(Forml.ClientWidth-Circle.Width));
end;
В коде программы для добавления очередного объекта к списку задействуется метод
function Add(const Value: Т): Integer;
Однако это не единственны\i·способ пополнения списка, кроме метода Add (), до бавляющего объект самым последним в список, в арсенале класса тoьjectList<T> имеется метод
procedure Insert(Index: Integer; const Value:
Т);
благодаря
которому мы
сможем явным
образом
указать
позицию
объекта,
передав
значение в
параметр Index.
Кроме того, у класса предусмотрен целый ряд перегружаемых методов
procedure AddRange(const Values: array of Т);
procedure InsertRange(Index: Integer; const Values: array of Т);
умеющих за один присест добавить/вставить целую партию объектов.
Возвращаемся к нашей программе, а точнее, ко второму таймеру Timer2. В круг обязанностей этого компонента входит:
□ управление процессом постепенного опускания шариков от места их создания (верхней грани формы) вниз формы, где их попытается поймать пользователь;
.□ удаление пойманных и пропущенных шариков.
Очевидно, что для решения этих задач мы должны уметь обращаться к элементу списка. Для этого стоит узнать общее количество элементов
property Count: Integer;
и передать индекс интересующего нас элемента в свойство
property Items[Index: Integer]: Т;
![]()
52 Глава 3
Пойманным считается шарик, попавший на поверхность биты, реализованной на основе компонента TRectangle, пропущенным - шарик, пролетевший мимо (лис тинг 3.12).
![]()
prooedure TForml.Timer2Timer(Sendeт: TObject); var i:integer;
Ьegin
for i :=О
to List.Count-1 do
//перебираем все объекты
with List.Items[i] do
Ьegin
Position.Y:=Position.Y+0.5; //опускаем шарик вниз
//проверяем, не попал ли шарик на биту Rectanglel:TRectangle
if (Position.Y+Height>=Rectanglel.Position.Y) and (Position.X>=Rectanglel.Position.X) and (Position.X<=Rectanglel.Position.X+Rectanglel.Width) then
Ьegin
//элемент пойман List.Delete(i); //удаляем шарик
// ... подсчет пойманных шариков
Break; //прерываем цикл
end else
if (Position.Y+Height>=Rectanglel.Position.Y) then
Ьegin
//элемент не пойман List.Delete(i); //удаляем шарик
// ... подсчет пропущенных шариков
Break; //прерываем цикл
end;
end;
end;
ЗАМЕЧАНИЕ
В Delphi
ХЕ5
конструкция with.. do вполне
приемлема,
однако,
возможно,
очень скоро
ее перестанут
рекомендовать
к
использованию.
Причина
такого
предположения
в том, что wi th
.. do изначально
проектировалась для
устаревших
объектов.
В любом случае пойманный или пропущенный шарик подлежит удалению. Для этого мы воспользовались методом
procedure
Delete (Index:
Integer) ;
Важно запомнить,
что если·список является
владельцем
объектов, то вызов
метода Delete (), во-первых,
уничтожит
объект, а
во-вторых, упакует
список
(удалит осво
бодившиеся
ячейки и
уменьшит
значение Count).
![]()
![]()
Если бы логика программы предполагала не·уничтожение объекта, а лишь изъятие ero из списка, то лучше было бы воспользоваться методом
function Extract(const Value:
Т): Т;
Заметьте, что на этот раз в функцию передается не индекс объекта, а собственно подлежащий изъятию объект, и если изъятие возможно, этот же объект окажется на выходе функции.
На этом функционал списка TObjectList<T> далеко не заканчивается. Класс шаблон способен еще на многое. В частности, он умеет перемещать элемент из по зиции Curlndex в позицию Newlndex с помощью процедуры
prooedure
Move(Curlndex, Newlndex:
Integer);
Чтобы поменять элементы местами, обращаемся к методу procedure Exchange(Indexl, Index2: Integer); Сортировка объектов в списке производится методами
procedure Sort; overload;
procedure Sort(const
AComparer: IComparer<T>); overload;
При желании весь список может быть представлен в формате массив.а-шаблона
function ToArray: TArray<T>;
И наконец, список обладает событием
PFoperty OnNotify:
TCollectionNotifyEvent<T>; TCollectionNotifyEvent<T> = procedure(Sender: TObject;
const Item: Т;
Action: TCollectionNotification) of object;
rенерирующимся
в
момент
пополнения
списка новым
объектом или
удаления объ екта из
списка. О
характере
события
сигнализирует
параметр Action, принимая
зна чения cnAdded, cnRemoved или cnExtracted.
Шаблон словаря TDictioп·ary<>
В заключительном разделе главы познакомимся с весьма неординарным шаб лоном - словарем TDictionary<>. Словарь описан в модуле System. Generics. collections и, как и положено любому словарю, предназначен для обслуживания пар "ключ- значение", поэтому в ero объявлении присутствуют два обобщенных типа TDictionary<TKey,тvalue>. Ключ в словаре должен быть уникален, благода ря этому осуществляется быстрый и однозначный поиск соответствующего ему значения.
Класс вооружен несколькими конструкторами, простейший из них
constructor Create(ACapacity: Integer = О); overload;
при создании экземпляра класса в аргументах не нуждается (он создаст пустой сло варь), передав в конструктор любое положительное значение. Мы заранее распре делим объем словаря, чем ускорим процесс ero заполнения.
![]()
54 Глава З
Для добавления в словарь новой пары "ключ - значение" используют метод
prooedure Add(const Кеу: ТКеу; const Value: TValue);
ВНИМАНИЕ/
В
соответствии
со своим
предназначением словари
недолюбливают
совпадение
клю чей,
поэтому при
вставке
новой пары с помощью
метода Add () вероятно
возникнове
ние
исключительной
ситуации!
·Если вы намерены просто изменить значение для определенного ключа, то реко мендую процедуру
prooedure AddOrSetValue(const Кеу: ТКеу; const Value: ТValue);
Метод находит ключ Кеу и заменяет его значение на value, если же заданный ключ отсутствует, то процедура заработает как рассмотренный ранее метод Add () - до- бавит в словарь новую пару. ·
Свойства с традиционными названиями
property Count: Integer;
property Iterns[const Кеу: ТКеу]: TValue;
позволят соответственно узнать число элементов в словаре и получить доступ к элементу по его индексу, но заметьте, что на этот раз индекс кеу универсален и может быть задан практически любым типом данных.
Изъятие из словаря пары по ключу осуществит процедура
prooedure Remove(const Кеу: ТКеу);
Метод
function ExtractPair(const Кеу: ТКеу): TPair<TKey,TValue>;
также исключит пару из словаря, но на этот раз даст возможность программисту прочитать ключ/значение.
Полная очистка словаря осуществляется процедурой
prooedure Clear;
Пара очень быстрых методов проверит наличие в словаре ключа
function ContainsKey(const Кеу: ТКеу): Boolean;
и значения
function ContainsValue(const Value: TValue):
Boolean;
и в
случае успеха они возвратят true.
Для быстрого доступа к значению по его ключу вызываем функцию
function TryGetValue(const Кеу: ТКеу; out Value: TValue):
Boolean;
В случае наличия в словаре пары, описываемой ключом Кеу, в выходной параметр
value поступит соответствующее ключу значение.
![]()
Клвссы-швблоны 55
![]()
Традиционное для большинства классов-шаблонов свойство
function ToArray: TArray<TPair<TKey,TValue>>;
позволит представить словарь в
виде массива.
ЗАМЕЧАНИЕ
Класс тoictionary<T> в разь1 быстрее, чем полюбившийся
многим программистам
список строк тstringList! Поэтому в своих новых программах
по возможности
отда вайте
предпочтение
TDictionary<T>.
ГЛАВА 4
В главе,
посвященной
базовым классам библиотеки
FireMonkey, нам предстоит еще
глубже погрузиться
в иерархию наследования
классов. На этот раз нашими
героями
станут
характерные
представители
новой
платформы
(см. рис.
2.1):
□ прародитель всей цепочки классов библиотеки - класс FМХ. Types. TFmxObject;
□ опорнь1й класс для двухмерных элементов управления FМХ. Controls.
тcontrol;
□ опорный класс для трехмерных элементов управления FМХ. controlsЗD.
TControlЗD.
ЗАМЕЧАНИЕ
Если вы по какой-то причине на стали читать главы 2 и З, то обязательно запомните
их основной
тезис - классы
FireMonkey (FMX)
построены не на пустом
месте. В
их основу положены фундаментальные классы более "древней" библиотеки VCL (речь
идет о
цепочке "TObject- TPersistent - тcomponent").
Так что
если вы
рассчиты ваете работать
исключительно
на платформе
FMX, то у вас
это не получится... Знания
VCL по-прежнему остаются актуальными.
В
джентльменском
наборе опорного класса TFmxObj ect мы
найдем все
неоqходимое для обеспечения
работы классического
программного
объекта. В первую очередь
это
конструктор Create () и деструктор
Destroy () экземпляра
класса.
Если вам необходимо
создать точную
копию уже существующего
объекта, то вызо
вите метод
function Clone(const AOwner: TComponent): TFmxObject;
Функция возвратит клон
объекта и передаст его во
владение объекту, определен
ному в
параметре AOwner.
Наряду с
классическим для
объектов Delphi
деструктором Destroy () предусмотрен
необычный
метод
procedure Release(Delay: Single = 0.1);
![]()
удаляющий
объект не
сразу, а
через
некоторый
интервал
времени,
давая
экземпля ру
класса
корректно
завершить
свой
жизненный цикл,
например,
закончить
опера цию
перерисовки.
Примерный
период
времени (по
умолчанию О,1
секунды),
отво димый
на
исчезновение
объекта,
передается в
параметр Delay. Сразу
замечу, что
бессмысленно
использовать
метод Release () в
качестве
"часовой
бомбы",
уничто
жающей объект
через
полчаса
после вызова,
поэтому
направляйте
в параметр
зна чение, не
превышающее
секунду.
В
современном,
даже
невысокой
степени сложности
приложении
одновременно
взаимодействуют десятки,
а то и сотни
объектов.
Зачастую жизненный
цикл одних
объектов
состоит в
прямой
зависимости
от "жизни"
или "смерти"
других. Для
того чтобы о
факте уничтожения
одного
объекта
узнал другой,
в состав
методов
класса TFmxObj ect включена
процедура
procedure FreeNotification(AObject: TObject); virtual;
Единственный
параметр
метода
указывает на объект,
которому будет
автоматиче
ски
направлено
уведомление
об
уничтожении
нашего
компонента.
Обратная
задача (вычеркивание
объекта из перечня
объ ктов,
подлежащих
уведом
лению)
решается методом
procedure RemoveFreeNotify(oonst AObject: IFreeNotification);
Отметим, что уведомление выбранных объектов осуществляется автоматически,
для этого предназначен метод Notification о, объявленный на уровне класса
Управление дочерними объектами
Возможность
одного
объекта
обладать
другим бьша
предусмотрена
еще на уровне
класса т�ontrol из
библиотеки VCL.
Здесь
принадлежащие
объекту
компоненты
хранились в
свойстве cornponents, и доступ к
ним осуществлялся по их индексу.
В базовом
классе TFmxObject имеется еще
один,
альтернативный
(а может, даже и правильнее сказать основной) подход к
обладанию объектами. На
этот раз
доступ к принадлежащему
владельцу
списку
дочерних
объектов
осуществляется
при по
средничестве
свойства
property
Children: TFmxObjectList;
которое построено
на основе списка-шаблона
TFmxObjectList = System.Generics.Collections.TList<TFmxObject>
Благодаря
такому
подходу мы
приобретаем дополнительные
рычаги
управления
дочерними
элементами,
главный из них- универсальность,
т. е.
способность
хра нить
любой объект,
построенный
на основе TFmxObject.
Общее число подчиненных объектов предоставит свойство
property ChildrenCount: Integer; //только для чтения
![]()
58 Глава 4
Дочерний
объект также
не остается
безучастным
к отношению
"главный - подчи
ненный", в
частности он
знает свой родительский
компонент
property
Parent: TFrnxObject;
и порядковый номер в
списке родителя
property Index: Integer;
//только мя чтения
Стоит
отметить, что
объект может
быть вполне
самостоятельным и не
иметь роди
теля, об этом
сигнализирует метод
function
HasParent: Boolean;
Любой объект, имеющий право быть родительским,
по отношению к
другим
вправе
провести
экспертизу с
помощью
метода
function IsChild(AObject: TFrnxObject): Boolean;
Передав
в метод
ссылку на
произвольный объект
AObj ect, мы узнаем,
является ли
он дочерним
по отношению
к объекту,
вызвавшему
метод.
Все потомки
TFrnxObj ect имеют
право
выступать
владельцами
объектов,
имеющих в своей иерархии наследования
класс TFrnxObj
ect. Для добавления
дочернего
объек та
вызываем метод
procedure
AddObject(AObject: TFrnxObject);
или
procedure InsertObject(Index: Integer; AObject: TFrnxObject);
Разница между
методами
заключается
в том,
что AddObj ect ( ) добавит
объект в ко нец списка
дочерних
объектов, а InsertObj
ect - в позицию,
определенную
в пара метре Index.
С
методами AddObj ect
() и InsretObj ect () мы еще
неоднократно
столкнемся
на страницах
этой книги,
ведь они
своего рода
визитная
карточка
всех
объектов биб
лиотеки FireMonkey (ничего подобного
в VCL нет). Небольшой
пример
рассмотрим
прямо сейчас.
Действующими
лицами
примера
окажутся один
объект-аниматор
TFloatAnimation, позволяющий
в
динамике
воздействовать
на одно из
свойств сво
его
объекта-владельца,
изменяя его состояние,
и произвольное
число фигур TRectangle (страница Shapes). Не
стоит искать
объект-аниматор
на палитре
компо нентов - мы его
создадим
автоматически, правда,
стоит
позаботиться
о подключе
нии к проекту
модуля FМX.Ani. Разместите
на форме
проекта
несколько компонен
тов TRectangle
и
перейдите к обработчику
события oncreate ()
формы
(листинг 4.1).
![]()
uses ..., FМX.Ani;
var
Forml : TForml;
FA: TFloatAnimati�n;
procedure
TForml.FormCreate(Sender: TObject);
Ьegin
FA:=TFloatAnimation.Create(nil);
FA.PropertyName:='RotationAngle'; FA.Trigger:='IsMouseOver=true';
FA.Triggerinverse:='IsMouseOver=false'; FA.StartValue:=90;
FA.StopValue:=0;
end;
Сейчас мы не станем
комментировать код из листинга 4.1, т. к.
в этой книге ани
мации
посвящена
отдельная глава 18. Пока
достаточно
знать, что
после созда
ния формы проекта в
нашем распоряжении появился экземпляр класса FA : TFloatAnimation, у
которого нет
владельца
(конструктор
аниматора
был вы зван с
аргументом nil).
Для
завершения
примера нам
осталось
написать
всего одну
строку кода в
обработ чике события OnМouseEnter () у любого из прямоугольников TRectangle (лис
тинг 4.2) и
сделать это
событие
общим для остальных
компонентов TRectangle.
![]()
procedure TForml.RectanglelMouseEnter(Sender: TObject);
Ьegin
TFrnxObject(Sender) .AddObject(FA);
end;
Таким
образом, в момент
появления
над любым из
прямоугольников
TRectangle указателя
мыши он становится
владельцем
объекта-аниматора FA : TFloatAnimation. Что произойдет дальше,
вы увидите,
повторив
представленный выше
код или найдя
этот пример в
прилагаемом
к книге электронном
архиве (см.
прwюжение 5).
При необходимости дочерние объекты могут быть упорядочены, для этого вызыва ется метод
procedure Sort(Compare: TFrnxObjectSortCompare);
Два объекта могут просто поменяться местами, для этого следует обратиться к ме тоду
procedure Exchange(AObjectl, AObject2: TFrnxObject);
Удаление дочернего объекта из списка осуществляет перегружаемый метод
procedure RemoveObject(AObject: TFrnxObject); overload; prooedure RemoveObject(Index: Integer);
overload;
В
первой
нотации
производится
удаление конкретного
объекта AObj ect, во
втором
случае
удаляется
объект с
индексом Index.
![]()
60 Гпава 4
Объект-владелец может провести полную очистку списка дочерних объектов, для этого он обращается к методу
procedure DeleteChildren;
Отметим, что объекты не подлежат физическому уничтожению, а просто изымают ся из списка.
Во время визуального проектирования важнейшим инструментом программиста выступает окно управления структурой проекта Structure. В данном окне, переме щая компонент мышью, мы получаем возможность переподчинять объекты друг другу. Например, на экранном снимке (рис. 4.1) видно, что компоненту тaьcontroll принадлежат три страницы (тaыteml, таыtеm2, таЫtеmЗ), которые в свою очередь являются владельцами других компонентов.
l,. lluttonl
.,J
1
&il • +
1 ·s□Form1
, '4StyieSookl
е T.tlControll
•
•• 1785:
18 lnlert
i¾)
StyleВool(1
"f_ode) . ---
8 taЬiteml 1o)Вuttor11 i;jfdtl
ii)Edit2
■
1\1
1111 •
Ю Т..Ь!t.11112
(ii raыtemз
Рис. 4.1. Управление
дочерними объектами с помощью окна Structure
Сопоставление дополнительных данных
У объектов VCL на уровне тcomponent объявлено неиспользуемое системой свойст во таg, позволяющее программисту связывать с объектом произвольное целочис ленное значение. В классах FМХ на сопоставлении с объектом дополнительных данных специализируется целая группа дополнительных свойств:
property TagFloat:
Single; //вещественное значение
property TagObject: TObject; //внешний объект
property TagString: string; //текстовая строка
Задействуйте перечисленные свойства для дополнительной идентификации эле ментов управления и созданных динамически объектов.
Одной
из визитных
карточек
новой Delphi стала
технология
живого
связывания
LiveBindings, позволяющая
двум (и более)
программным
объектам осуществлять
взаимодействие
друг с другом
на уровне RTТI. В
интересах LiveBindings в рамках
класса TFmxObject заложен
ряд базовых
методов и
свойств,
нацеленных на
под держку LiveBindings.
Более
подробно об особенностях
технологии
LiveBindings мы
поговорим в главе
17.
Анимация - это еще одно нововведение FireMonkey. Суть анимации заключается в способности практически любого визуального элемента управления изменять во времени одну или несколько своих .характеристик (размеры, цвет, масштаб, поло жение и т. п.) по внешней команде. Анимированный объект в буквальном смысле оживает, чем достигается более дружественный интерфейс приложения.
В
рамках
класса TFmxObject объявлено
более
десятка
методов (в их
названиях
име ется
слово Animation или Animate),
предназначенных
для
управления
эффектами
анимации, их
изучению мы
посвятим главу
18.
У всех визуальных элементов управления, базирующихся на классе TFmxObject,
имеется свойство
property Touch: TTouchМanager;
позволяющее
подключить к
компоненту
менеджер
жестов - компонент
TGestureManager. Благодаря менеджеру жестов компонент
приобретет возможность
реагировать
на поддерживаемые современными
экранами
операции
сенсорного
ввода (см.
главу 20).
Уже давно существовавшая в VCL система централизованного управления прило жением с помощью командных объектов TAction (см. главу 6) в FireMonkey появи лась только вместе с выходом ХЕЗ. Для взаимодействия с командами на уровне класса TFmxObj ect объявлено свойство
property Action: TBasicAction;
Для хранения командных объектов в состав FMX введен хорошо знакомый нам список команд TActionList, в основном повторяющий возможности своего одно именного коллеги из VCL.
![]()
2D-элементы управления, класс TControl
Все
пользовательские
элементы
управления
платформы FireMonkey
берут начало от
объявленного
в модуле FМХ. Controls класса тcontrol. Несмотря
на
одинаковые
имена, знак
равенства
между
классическим
тcontrol из
состава VCL и
классом тcontrol ставить
преждевременно.
По сравнению
со своим
"коллегой"
наш новый
знакомый
обладает
существенно
возросшим
функционалом.
Если
вы намерены
досконально
разобраться
с классом, то сразу
стоит
взглянуть на
его
определение:
type TControl class(TFmxObject,. IControl, IContainerObject,
IAlignRoot, IAlignaЬleObject, IEffectContainer)
Как видите, тcontrol является не просто наследником опорного класса TFmxObject. В объявлении
родоначальника всех
элементов
управления
FireMonkey
присутству ют
пять
интерфейсов.
□
Благодаря
rcontrol потомки класса тcontrol приобретают право получать фокус ввода и
позволяют
пользователю
передавать
потомкам FМХ. Types .
тсоntrol фокус
ввода (например, с
помощью клавиши <ТаЬ>). Кроме того, интерфейс
rcontrol обеспечит
взаимодействие
с
клавиатурой и
мышью (в. VCL
такими
чертами об ладали только
оконные
элементы
управления - потомки
тwinControl).
□ Благодаря
интерфейсам IAlignRoot и IAlignaЬleObject
реализовано
улучшенное
выравнивание элемента управления на поверхности родительского контейнера.
А интерфейс rcontainerObj ect в любой
момент
времени
готов предоставить
ин формац,по
о размерах
родительского
контейнера.
□
Интерфейс
IEffectContainer позволяет
применять к
элементу
управления
разно-
образные
графические эффекты
(см.
главу 19).
В
классе. тcontrol принципиально
изменш: ся
способ
хранения
местоположения
элемента управления. Теперь допускается
описывать
по?ицию
элемента управления
в двух- и
трехмерном
пространствах
с
использованием
векторных
координат.
ВНИМАНИЕ!
Все
визуальные
элементы
управления
FireMonkey в буквальном
смысле слова
нари сованы,
т. е.
основаны на
растровых
образах.
Благодаря
этому к
кнопкам,
панелям, строкам
ввода можно
применять
все мыслимые
(а если сможете,
то и
немыслимые)
графические
эффекты! В этом вам
помогут
многочисленные компоненты
со странички
Effects.
· Отметим
еще тот
немаловажный
факт, что на уровне
тcontrol осуществляется
управление
не только видимостью
элемента
управления,
но и его прозрачностью,
масштабом и
вращением.
Кроме того,
на тcontrol возложен
фирменный
функцио нал FireMonkey по
контролю за
прорисовкой,
анимацией и
другими
визуальными эффектами.
![]()
![]()
![]()
Базовые классы FireMonkey 63 ·
Размещение 2D-элемента управления
Несмотря на внешнюю похожесть компонентов FMX и VCL отличий между ними больше, чем сходства. Одна из особенностей элементов управления FireMonkey связана с подходом к вопросу размещения и выравнивания элемента управления на поверхности родительского контейнера. Первое новшество заключается в отказе от целых величин при описании размеров и местоположения элемента управле ния. Хотя высота и ширина объекта определяются свойствами с привычными на званиями
property
Width: Single; //ширина объекта
property Height: Single;
//высота объекта
Но теперь свойства ориентированы на действительные числа. Такой подход суще ственно упрощает пересчет величин при проведении различных графических опе раций, поворотах, масштабировании и других преобразованиях объектов.
ВНИМАНИЕ!
При
работе в
двухмерном
пространстве
координаты
объекта FMX
(свойство Position) определяются
в формате
записи TPosition. В
трехмерном
пространстве
местополо
жение
объекта
описывается
структурой TPositionЗD.
Еще большие изменения постигли способ описания координат местоположения объекта. Очередное новшество FМХ спрятано в недрах свойства
property Position:
TPosition;
отвечающего за определение местоположения компонента в двухмерном или трех мерном пространстве. Теперь это не просто свойства Тор и Left (как было в VCL), а свойства
property Х: Single;
property У: Single;
Если копнуть еще глубже, то выяснится, что интеллектуальный объект TPosition
определяет позицию компонента как вектор (листинг 4.3).
![]()
type TVector = re?ord
//метоЩ,I записи ТVector
case Integer of
О: (V: TVectorArray;); //type TVectorArray = array [0.. 2] of Single; 1:
(Х: Single;
У: Single;
W: Single;);
end;
Переход к
векторной алгебре
- это весьма существенное новшество библиотеки,
в
особенности
для
трехмерных проектов
(см.
прwюженuя 1 и 2).
![]()
ВННМАННЕ!
В
трехмерном
пространстве
(проекты на
основе TFormЗD) размещаемые
на форме элементы позиционируются не относительно
левого
верхнего
угла (как это было в VCL или в
двухмерных
проектах FireMonkey
НО), а
относительно
своего
центра.
Основные свойства и
методы класса TPosition представлены в
табл. 4.1.
Таблица 4.1. Основные свойства и методы класса TPosi tion
|
Свойство/метод |
Описание |
|
property DefaultValue: TPointF; |
Возвращает значения координат по умолчанию -
обычно (О,
О) |
|
property Х: Single; |
Горизонтальная координата |
|
property У: Single; |
Вертикальная координата |
|
property Point: TPointF; |
Представление
координат в
форме
записи TPointF.
Запись TPointF кроме
хранения
коорди- нат (Х, У) предоставляет
услуги по
сравнению,
смещению,
приращению
координат |
|
property Vector: TVector; |
Представление координат в
форме
вектора |
|
function Empty: Boolean; |
Возвращает true, если координаты
х и У уста-
новлены в О |
|
procedure Reflect(const Normal:
тvector); |
Отражает
текущий вектор перпендикулярно
ли- нии, проведенной из точки (О, О, О) в точку Noпnal |
|
property OnChange: TNotifyEvent; |
Событие, генерируемое в
момент изменения
координат |
У объектов
FМХ имеется
весьма обширный
набор
возможностей
по выравниванию
элемента
управления на
поверхности
родительского
контейнера.
Для этого
преду
смотрено
свойство
property Align: TAlignLayout; //по умолчанию alNone
но вариантов выравнивания больше- целых 20 (против 6 в VCL):
type TAlignLayout = (alNone,
alTop, alLeft, alRight,
alBottom, alMostTop,
alMostBottom, alMostLeft, alMostRight, alClient,
alContents, alCenter, alVertCenter, alHorzCenter, alHorizontal, alVertical,
alScale, alFit, alFitLeft, alFitRight);
К вопросу выравнивания объекта имеют отношение еще два свойства:
property Margins: TBounds; //по умолчанию (О,
О, О, О}
property Padding: TBounds;
//по умолчанию (0, О,
О, О}
Свойство
мargins определяет
отступы
дочернего
объекта от границ
родительского
контейнера.
Отступы
выступают в качестве
запрещенной зоны для выравниваемого
дочернего объекта. Например, если вы установите свойство мargins. тор располо-
женной на форме панели в значение 20, то после этого она не сможет приблизиться к верхнему краю формы более чем на 20 пикселов.
Свойство Padding развивает идею запретных зон альтернативным способом - со стороны контейнера (например, формы). Свойство определяет отбивку всех дочер них объектов, указывая, на какое число пикселов им разрешено приблизиться к границам своего контейнера-владельца (рис. 4.2).
![]()
![]()
Рис. 4.2. Демонстрация свойств Margins и Padding
Группировка объектов и
компонент TLayout
Для объединения нескольких объектов в группу с последующей возможностью их совместного перемещения, вращения, выравнивания (см. свойства Align, Anchors, Padding, Margins и ряд других свойств) проще всего воспользоваться услугами компонента TLayout, который вы обнаружите на страничке Layouts палитры ком понентов. •
Класс
FМХ. Layouts. TLayout является
прямым
потомком
рассматриваемого
в этой главе класса тcontrol и обладает одной важной особенностью - экземпляр TLayout
![]()
Рис. 4.3. Подчинение кнопок компоненту Layoutl
![]()
бб Глава 4
выполняет функционал невидимого во время выполнения приложения контейнера для других элементов управления.
Для того чтобы элемент управления, допустим, кнопка вuttonl, стал принадлежать контейнеру Layoutl: TLayout, недостаточно расположить кнопку на поверхности контейнера. Вместо этого следует воспользоваться отвечающим за структуру про екта редактором Structure и перетащить мышью узел кнопки в подчинение узла Layoutl (рис. 4.3).
Масштабирование и
вращение объекта
Размеры любого потомка класса тcontrol могут быть пропорционально изменены.
Коэффициент масштабирования задается в свойстве
property
Scale: TPosition;
Напомним, что класс TPosition способен работать с координатами, заданными в форме вектора, поэтому свойство Scale способно управлять масштабом в трех измерениях одновременно.
Любой из потомков класса тcontrol может быть подвергнут вращению. Угол пово рота в градусах (-180...+180) задается в свойстве
property RotationAngle: Single;
Объект поворачивается относительно центра вращения, который назначается в свойстве
property RotationCenter: TPosition;
Заметим,
что точка
центра
вращения
задается
относительно
клиентских
координат объекта, которые нормируются к
1. По умолчанию центр находится в
точке (0.5, 0.5)- это
не что иное,
как
геометрический
центр вращаемого объекта. Если
мы зададим в
качестве
центgа точку (1, 1) - центр
вращения
переместится
в пра вый нижний
угол объекта,
(О, 0)- в левый
верхний.
Для разработчиков компонентов будет полезно знать, что на уровне тcontrol объ явлено свойство
property Skew:
TPosition;
позволяющее деформировать объект. Указанное свойство еще активно применяется в классе ттransform, отвечающем за осуществление различного рода преобразо ваний.
Благодаря вращению элементов управления можно легко достичь весьма неорди нарных результатов. Воспользовавшись нехитрым набором компонентов:
□ таймером TTimer для отсчета времени;
□ слоем .TLayout, выравненным по центру (Align:=alCenter) формы приложения
для iPhone,
слою будут
принадлежать
все визуальные
элементы
управления;
□ тремя линиями TLine, которые превратятся в
стрелки;
□ несколькими эллипсами, кругами и
текстовыми етками, создающими антураж
циферблата
(рис. 4.4),
мы напишем приложение "Часы".
Таймер
Timerl:TTimer
Слой
Layoutl:TLayout
Часовая стрелка
LineHour:TLine
Минутная стрелка
LineMin:TLine
Секундная стрелка
LineSec:TLine
Рис. 4.4. Мобильный проект "Часы"
Во время
визуального
проектирования
можно не
беспокоиться
о размещении
часо вь1х стрелок.
В момент
создания
формы
указываем стрелкам, что их
центр враще ния будет располагаться ровно по центру слоя Layoutl (листинг 4.4).
.... , • •····•·•·.,,м•-••·..·····•·•·•····•·•··•••••·•"0 ....................._. •••••••••••_,.,••,•. ·••••···•·•·•,. ••._ ..........,. ..мf.!'., .....,,..,м_,.,...,.,......, • • •,.,,,..,,.".............
,, ..............,_ i
"''••••• •••••••••• ,,._,,,..,••·ч,,...............................................................,,' -r1. - .............,., , ••- ...........'Fi.. •••••••..........w...............v......•,•••••••••••••, •• 1· • ··•••••••••• .. ,., .. ,,.,.,.,,.,•••,,,
prooedure TfrmWatch. FormCreate (Sender: TObject) ;·
Ьegin
LineSec.RotationCenter.X:=0;
LineSec.RotationCenter.Y:=0; LineMin.RotationCenter.X:=0;
LineMin.RotationCenter.Y:=0;
![]()
LineHour.RotationCenter.X:=0;
LineHour.RotationCenter.Y:=0;
end;
С каждым отсчетом
таймера осуществим перерасчет времени в углы
поворота со ответствующих
стрелок
(листинг 4.5).
![]()
prooedure TfпnWatch.TimerlTimer(Sender: TObject);
var H,M,S,MS:word;
Ьegin
DecodeTime(Time,H,M,S,MS); //декодируем время
//переводим время в
углы
поворотов стрелок
LineSec.RotationAngle :=S*6+180; //секунды
LineMin.RotationAngle :=М*б+180; • /
/минуты LineHour.RotationAngle:=H*З0+lB0+M/2; //часы
end;
1
. !·
![]()
Нам осталось убедиться, что таймер Timerl активен EnaЫed:=true, и запустить про ект на выполнение.
Видимость и
прозрачность элемента управления
У всех визуальных компонентов из состава FMX сохранилось традиционное свой ство
property VisiЬle: Boolean;
делающее компонент невидимым при установке в состояние false. Но это далеко не все. Программисты Embarcadero свои компоненты наделили уникальной воз можностью - постепенно становиться прозрачными. По умолчанию свойство
property Opacity: Single;
//по умолчанию 1
принимает значение 1, что свидетельствует о полной непрозрачности компонента. Уменьшая значение до О, мы добьемся требуемой степени прозрачности объекта вплоть до полной невидимости.
Грани, фаски и
визуальные эффекты
Программисту, хорошо знающему элементы управления библиотеки VCL, при пер вой встрече с элементами управления FМХ может показаться несколько удиви тельным отсутствие у визуальных компонентов FireMonkey свойств, определяю щих внешний вид граней и фасок компонента (напомним, что в VCL для оформле ния компонента широко использовались свойства BorderStyle, Bevelinner, вevelKind, BevelOuter и т. п.). Вместо этого в проектах FMX для оформления эле ментов управления используются альтернативные подходы - стили, анимация и
![]()
![]()
визуальные
эффекты. В частности,
для имитации
граней и фасок
можно
воспользо
ваться
услугами компонентов
со страницы
Effects палитры
компонентов
Delphi.
На
странице Effects вы
qбнаружите
несколько
десятков
компонентов,
в первую
очередь
специализирующихся
на создании
разнообразных
графических
фильтров,
оказывающих
воздействие
на растровые
изображения.
Но среди
них имеются
клас сы,
способные
преобразить
обычные
элементы
управления
(кнопки,
строки ввода,
панели и т. п.).
В
первую
очередь рекомендуем познакомиться с
компонентами:
□ TBevelEffect - эффект фаски;
□ TReflectionEffect - отражение;
□ TGlowEffect - наружное свечение;
□ TinnerGlowEffect - внутреннее свечение;
□ TShadowEffect - тень;
□ TВlurEffect - размытие.
Возможности шести
перечисленных
компонентов
отражает рис. 4.5, на котором
визуальные эффекты применены к
обычным панелям TPanel. Для сравнения в цен
тре
формы оставлена панель, не использующая визуальный эффект.
r 1-- ,....... _.
EJ
l sr;i ffю1
cc•t::)Pan,13
-1R.e-REcbanE:ff-.!ct1
,,;,.r;]p..,.,1
1
Е !;] Pane!S
8',!мerG\owEl!m!
l:;]P.-..11;
4ShodlмEffюl
;,,G] Ponof7
tr,\Styi,!lool:1
St,'e!loot1
TReflectionE t
Рис.
4.5. Применение эффектов для
управления внешним видом визуального эл_емента
ЗАМЕЧАНИЕ
Для
задействования
того или
иного эффекта
достаточно
перетащить
компонент на
форму и с
помощью окна
управления
структурой
проекта Structure "подчинить"
вы бранный
эффект тому
или иному
визуальному
элементу
управления.
![]()
![]()
![]()
![]()
На
уровне
класса тcontrol объявлен
ряд
информационных
свойств,
позволяющих
определить
текущее
состояние
элемента
управления
(табл. 4.2).
Таблица 4.2. Свойства, описывающие состояние элемента управления
|
Свойство |
Описание |
|
property IsMouseOver: Boolean; |
Информирует
о том,
что над элементом
управления
расположен
указатель
мыши |
|
property IsDragOver: Boolean; |
Объект
участвует в операции
перетаскивания drag and drop |
|
property IsFocused: Boolean; |
Проверка
наличия фокуса ввода |
|
property IsVisiЫe: Boolean; |
Проверка видимости объекта |
|
property ParentedVisiЫe: Boolean; |
Проверка видимости родительского объекта |
Программисту,
имеющему
опыт
разработки проектов
в VCL, не составит
никакого
труда
разобраться
с основными
обработчиками
событий,
описанных на
уровне
класса тcontrol платформы
FMX. События тcontrol во многом
повторяют
опорные
события, имеющиеся
в
распоряжении
классов тcontrol и TWinControl
из
дружеской
библиотеки VCL.
Основные
отличия
сопряжены с
изменениями
в
позиционирова
нии
элементов
управления (напомню,
что FMX перешел
на
вещественные
числа и
способен
описывать
координатную
точку в
трехмерном
пространстве).
В
любой
операционной
системе,
поддерживаемой
FMX, самыми
востребованными
событиями
были и
остаются
одинарный и двойной
щелчки по
элементу
управле ния.
property OnClick: TNotifyEvent; //одинарный щелчок
property OnDЫClick: TNotifyEvent; //двойной щелчок
Напомним,
что
инициатором
щелчка могут
стать левая
кнопка мыши,
а также на
жатие
клавиши <Enter>
или <Пробел>
при условии,
что объект
управления
нахо дится в
фокусе ввода.
Все события, в том числе и простейшее из них
TNotifyEvent = procedure(Sender: TObject) of object;
обладают
хотя бы
одним
параметром sender,
благодаря которому программист мо жет
идентифицировать
объект-источник
события.
Вряд ли вы еще не встречались с
простейшими примерами обработки события
OnClick (), поэтому мы сразу рассмотрим более усложненный пример, демонстри-
рующий
возможность
разделять
один обработчик
события
сразу
несколькими
эле ментами
управления и
раскрывающий
скрытые возможности
параметра sender. Для реализации примера разместите на главной форме проекта кнопку Buttonl: твutton,· многострочный
редактор Memol: тмеmо и
несколько
любых других
визуальных
эле ментов управления, которые вы найдете на
палитре компонентов Delphi.
Выберите
кнопку Buttonl и
напишите
всего одну
строку кода в
обработчике
собы тия
(листинг 4.6).
![]()
procedure TForml.ButtonlClick(Sender: TObject);
Ьegin
Memol. Lines. Add ( ( Sender as TControl) . Name) ;
end;
Щелчок
по кнопке
приводит к появлению
в многострочном
редакторе Memol строки с
именем
компонента,
вызвавшего
событ е (в
данном
случае
кнопки вuttonl). Предвижу вопрос: "А не проще ли было
написать строку мemol. Lines .Add ( 'вuttonl') ?" Нет, не проще. В особенности,
если мы намерены
научиться
создавать
разделяемый
между
несколькими
компонентами
код.
Выберите на форме любой другой элемент управления (например, CheckBoxl). Пе
рейдите на вкладку Events (События) в
Инспекторе объектов, в
перечне событий
найдите
событие onclick и сопоставьте
с ним событие
ButtonlClick (рис. 4.6). Наши
действия
приведут к
тому, что два
компонента
станут
разделять
между собой
один общий
обработчик
события.
Аналогичным
образом
подключите к
событию все
ос тальные компоненты (включая форму) и
запустите проект на выполнение. Щелкни
те по любому из компонентов - его мя
сразу появится в
отдельной строке много
строчного редактора Memol.
ВНИМАНИЕ/
Параметр
Sender имеется
во всех
обработчиках
событий,
благодаря
ему
программист
сможет
идентифицировать
вызвавший
событие элемент
управления.
Благодаря
наличию
параметра Sender во всех
обработчиках
событий
элементов управления·Delphi
и возможностью
совместного
использования
нескольких
объек тов одного
и того
же события
можно
создавать
весьма
эффективный
код. Хорошим
примером
такого кода
может стать приложение
"Калькулятор".
У любого калькулятора есть 1О
кнопок, отвечающих за ввод цi.фровых значений.
Для того чтобы пользователь смог визуально идентифицировать назначение каж
дой из кнопок, в
их заголовки (техt) помещается соответствующий вводимой циф ре символ (рис. 4.7). При
нажатии на каждую из
цифровых кнопок (в моем примере
это
кнопки тcornerButton) генерируется событие OnClick (), в
результате которого
в метке
Labell:TLabel калькулятора
отображается
выбранная цифра.
Как правило,
начинающий программист решает подобную задачу в
лоб и пишет для каждой из
десяти кнопок примерно следующий код (листинг 4.7).
L.ммl\
0 fl.>d.c,l!цtto l
0 Rмi,e9ut1<> l
••• 21: 20 hwt Мocifled
Pr 1ieS
E-'==r:===;:=====;-t
.
Ltti
! t-,4,t.cckц)
!OnCanFocus
iOnChiwige
»!OnClclt
1:::ор
!QO
10n0rilgfnter
\°"°""'-мvе
i0n0rllg()ve"
jOnenllr
1=
l.мencfng...
1пс"' 08 Fiold.••
![]()
![]()
Рис. 4.6. Разделение одного обработчика события между несколькими компонентами
![]()
Рис. 4.7. Пользовательский интерфейс Калькулятора
f, ••••••••••••••••••t,,••••••••....•••••.. ••••••••••........ .,• -•••••.. ·•..•..•••.,.•••••••••• ·•.'••••••••••••.........9'•••••••••••••.. J _.,_, , .. ,,,,,., •••-••U'-°"',,.. .е,•••••••,Ц,•!•!.р,,1;
- ...•,_ ....,, ··-·--·............. .....'!!..!"... !!"- ':! ···"·""·•..-•···-·"···•...-.-....................................... i
procedure
TForml.CornerButtonlClick(Sender: TObject);
Ьegin
if Labell.Text='0' then Labell.Text:= '1' else Labell.Text:=Labell.Text+'l';
end;
Более
опытный
разработчик
поступит
гораздо
хитрее,
обойдется
услугами
всего одного
общего
события onclick () для всех
кнопок,
отвечающих
за цифровой
ввод (листинг
4.8), и разделит
это событие
между оставшимися
9-ю кнопками.
Но те перь,
вместо явного
указания
цифры,
программист
воспользуется
услугами
пара метра Sender, содержащего
ссьшку на
кнопку, по
которой был произведен
щелчок.
![]()
procedure
TForml.CornerButt0nlClick(Sender: TObject);
Ьegin
if Labell.Text='0' then
Labell.Text:=(Sender as TCornerButton) .Text else
Labell.Text:=Labell.Text +(Sender
as TCornerButton) .Text;
end;
.. Процедура
самостоятельно
разберется,
какая из
кнопок
послужила источником со
бытия, и
добавит в
заголовок
метки Labell
необходимую
цифру.
Для
того чтобы ни
один из
визуальных
элементов
управления FMX
не остался
без участным к нажатиям пользователем
клавиш, на уровне тcontrol реализованы два
самых важных клавиатурных события:
property OnКeyDown: TKeyEvent; //нажатие клави!Ш1
property OnКeyUp: TKeyEvent; //отпускание клави!Ш1
Благодаря
OnКeyDown ( ) и OnKeyup ( ) мы
получаем
возможность
отследить
как нажа тие, так и
отпускание клавиши. Оба события типизированы одинаково:
type TKeyEvent = procedure(Sender: TObject;
var Кеу: Word;
var KeyChar: WideChar;
Shift: TShiftState) of object;
Здесь: sender - ссылка на объект, вызвавший событие; Кеу - числовой код нажа
той клавиши; KeyChar - параметр-переменная, с
помощью которого можно считы
вать (а при необходимости и
редактировать) символ нажатой клавиши; Shift обес печит контроль состояния служебных клавиш.
Листинг 4.9 демонстрирует
порядок
применения
события OnKeyDown () - в его рам
ках форма отслеживает
коды клавиш.
Если
пользователь
нажимает
клавиши
управ-
ления курсором и при этом удерживает клавишу <Ctrl>, то форма изменяет свое положение на экране.
![]()
procedure TForml.FoпnКeyDown(Sender: TObject; var Кеу: Word;
var KeyChar:
Char; Shift: TShiftState);
Ьegin
if (ssCtrl in Shift) then
case Кеу of
37
38
39
40
end; end;
Forml.Left:=Forml.Left-1; Forml.Top:=Forml.Top-1; Forml.Left:=Forml. Left+l; Forml.Top:=Forml.Top+l;
//курсор влево
//курсор вверх
//курсор вправо
//курсор вниз
![]()
![]()
В примере из листинга 4.9 мы отслеживали цифровой код нажатой клавиши с по мощью параметра Кеу. А теперь переключим свое внимание на параметр KeyChar, который отслеживает не код клавиши, а выбранный пользователем символ. Благо даря этому можно проверять корректность вводимого текста (листинг 4.10).
![]()
prooedure TForml.EditlKeyDown(Sender: TObject;
var Кеу: Word;
var KeyChar: Char; Shift: TShiftState);
Ьegin
i0f ((KeyChar<'0') or (KeyChar>'9') or (Length(Editl.Text)>=ll))
and (КЕУ<>8) and (КЕУ<>46) then {8 и 46 - код клавиш Backspace
и Del}
Ьegin
KeyChar:=#0;
if (Length(Editl.Text)>=ll)=false then
ShоwМеssаgе('Допускаются только цифры!')
else
ShowMessage('Длинa текста превысила 11 знаков!')
end; end;
Событие осуществляет контроль верности ввода телефонного номера в строку Editl. Процедура допускает ввод только цифр, причем длина номера не должна превышать 11 символов. В противном случае ввод ошибочного символа отменяется (параметру-переменной KeyChar присваивается значение #О) и выдается сообщение об ошибке.
Разработка
удобного
пользовательского интерфейса
современного
приложения
не возможна без активного использования мыши. Именно поэтому базовый класс
![]()
тcontrol
стал
счастливым
обладателем
полудюжины событий,
ориентированных
на работу с
мышью.
В первую
очередь
выделим
события,
связанные с
фактом
появления
указателя мы
ши над областью
элемента
управления и
с выходом из
этой области
property OnМouseEnter: TNotifyEvent; //вхождение в
область
property OnМouseLeave: ТNotifyEvent; //выход из области
Вторая категория событий позволяет отслеживать события нажатия и
отпускания
кнопок мыши
property OnМouseDown: ТМouseEvent; //нажатие кнопки
property OnМouseUp: TMouseEvent; //отпускание кнопки
Перечисленные события типизируются процедурой
type TMouseEvent = procedure(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; Х,
У: Single) of oЬject;
Благодаря параметру вutton мы получаем возможность
узнать, какая именно кноп ка мыши
была задействована в
операции. Параметр Shift позволяет отследить на жатия служебных клавиш. Параметры х
и У информируют о
координатах мыши.
В
табл. 4.3
представлен
список
параметров,
позволяющих
описывать
реакцию эле
мента
управления
на событие.
Таблица 4.3. Описание параметров тмouseEvent
|
Параметр |
Возможные значения |
Описание |
|
Sender |
Ссылка на объект |
Ссылка на источник сообщения |
|
Button |
mЬLeft |
Щелчок левой кнопкой мыши |
|
mЬRight |
Щелчок правой кнопкой
мыши |
|
|
mЬMiddle |
Щелчок центральной кнопкой мыши |
|
|
Shift |
ssShift |
Удерживается в
нажатом состоянии клавиша <Shift> |
|
ssAlt |
Удерживается в
нажатом состояt:tии клавиша <Alt> |
|
|
ssCtrJ. |
Удерживается в
нажатом состоянии клавиша
<Ctrl> |
|
|
ssLeft |
Нажимается/отпускается левая кнопка мыши |
|
|
ssRight |
Нажимаетсяfотпускается правая кнопка мыши |
|
|
ssMiddle |
Нажимается/отпускается центральная кнопка мыши |
|
|
ssDouЫe |
Двойной
щелчок
любой кнопкой |
|
|
х,
у |
Single |
Координаты указателя
мыши |
Для
того чтобы
получить
возможность
отследить
движение
указателя
мыши, стоит обратить
внимание на
событие
property OnМouseMove: TMouseMoveEvent;
type TMouseMoveEvent = procedure(Sender: TObject;
Shift: TShiftState; Х,
У: Single) of object;
позволяющее
контролировать
местоположение
указателя.
Назначения
параметров нам уже известны,
поэтому не
станем
повторяться.
И сразу
перейдем к заключи
тельному событию
мыши,
описанному в
рамках
класса тcontrol.
Воспользуемся
услугами OnМouseMove ( ) и напишем
приложение,
позволяющее
пользователю
перемещать
компоненты
по форме с помощью
мыши. Для
этого нам
понадобится
новый проект с
любым
визуальным
элементом
управления на главной
форме (свой
выбор я остановил
на обычном
прямоугольнике
Rectanglel:TRectangle).
В
разделе
частных
объявлений
опишем две переменные
- xOffset
и yOffset, они
позволят нам хранить
отступы
указателя
мыши
относительно
левого
верхнего
угла
перемещаемого
компонента
(листинг 4.11). Значения
отступов
запоминаются
в мо
мент нажатия
кнопки в
событии OnМouseDown () . Перемещение
осуществляется
в рамках события OnМouseMove () (при условии, что пользователь
удерживает в нажа
том
состоянии
левую кнопку
мыши).
![]()
var Forml: TForml; xOffset,yOffset: Single;
implemenation
{$R *.dfm}
procedure TForml.RectanglelMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; Х,
У: Single);
Ьegin
xOffset:=X; //нажатие кнопки мыши
yOffset:=Y; //запомнили отступы xOffset
и yOffset
end;
procedure TF�rml.RectaпglelM9useMove(Sender: TObject;
Shift: TShiftState; Х, У:
Single);
Ьegin
//перемещение мьш и
с нажатой левой кнопкой
if (ssLeft in Shift) then
with (Sender as TControl)
do
Ьegin
Position.X:= Position.X+X-xOffset;
Position.X:= Position.X+X-yOffset;
end;
end;
Благодаря
тому, что в
параметре Sender находится
ссылка на
объект, над
которым движется
указатель
мыши, мы вновь написали универсальный
обработчик
события,
способный
перемещать
любой экземпляр
класса
тcontrol.
Контроль
вращения
колесика
мыши осуществляет
единоличный
представитель
- событие
![]()
![]()
![]()
property OnМouseWheel: ТМouseWheelEvent;
type TMouseWheelEvent = prooedure(Sender: TObject; Shift: TShiftState;
WheelDelta: Integer; var Handled: Boolean) of object;
Благодаря
параметру WheelDelta· мы
получаем
сведения о
направлении
вращения
колесика:
отрицательное
значение - вниз,
положительное
- вверх.
Передав зна
чение true в
переменную Handled, мы
уведомим
систему, что
программная
логика
отработала
полностью и
про событие
можно забыть,
в противном
случае обработка
события
продолжится за счет
вызова
обработчика
события по
умолчанию.
События получения и
потери фокуса ввода
Среди
компонентов
FireMonkey нет строгого
разграничения
на оконные
и графиче
ские
элементы
управления,
подобного
тому, которое
существует у
потомков
клас сов TWinControl и TGraphicControl
платформы
VCL. Строго
говоря, все
визуальные компоненты
FМХ являются
графическими
элементами
управления,
обладающими
чертами оконных.
Одно из
подтверждений
тому - способность
всех
наследников FМХ. Controls. тcontrol
реагировать
на получение
фокуса ввода.
У компонентов
сохранена
традиционная
(как и у компонентов
VCL) реакция на по
лучение и потерю
фокуса ввода,
осуществляемых
событиями
property OnEnter: ТNotifyEvent; //получение фокуса ввода
property
OnExit: ТNotify�vent; //потеря фокуса ввода
Кроме того, в FMX предусмотрено
событие, позволяющее
программисту
запретить
объекту
реагировать
на фокус
ввода:
property OnCanFocus: TCanFocusEvent;
type TCanFocusEvent = procedure(Sender: TObject;
var ACanFocus: Boolean) of object;
Разрешение или запрет выдается с
помощью параметра-переменной ACanFocus.
Изменение
геометрических
размеров
любого
потомка
класса TC6ntrol незамедли
тельно
приводит к
генерации события
property OnResize: ТNotifyEvent;
Данное
событие
окажется
весьма
полезным в тех
ситуациях,
когда логика
програм мы
предполагает
"подгонку"
элементов
пользовательского
интерфейса
под размер
формы. Одним
из примеров
такого подхода
может стать
код,
предложенный
в лис
тингах 4.12 и 4.13.
В
листинге 4.12
отражен
подготовительный
этап примера,
здесь в
момент
генера ции
события oncreate ()
у
главной
формы
проекта мы
динамически
создаем 64 эк
земпляра
объекта TRectangle (сразу
раскроем
секрt:т, что
прямоугольники
позднее
станут
клетками
шахматной
доски).
![]()
const N=B;
procedure
TForml.FormCreate(Sender: TObject);
var i:integer;
Ьegin
for i :=1 to N�N do
with TRectangle.Create(Forml) do Parent:=Forml;
end;
Обратите
внимание на
то, что мы
пока не определяем
местоположение
созданных
прямоугольников TRectangle, это мы сделаем в
момент изменения
размеров
формы
(листинг 4.13).
![]()
procedure TForml.FoпnResize(Sender: TObject);
var i, sWidth, sHeight, х,
у :integer;
Ьegin
sWidth :=Forml.ClientWidth
div N; //ширина прямоугольника
sHeight:=Forml.ClientHeight div N; //высота прямоугольника
x:=-sWidth+l; у:=1;
for i :=О to Forml.CornponentCount-1 do
if Forml.Components[i] is TRectangle then
with TRectangle(Forml.Components[i]) do
Ьegin
Height:=sHeight;
Width :=sWidth; inc (х, sWidth) ;
if x+З<Forml.ClientWidth then //размещаем вдоль одной линии
Ьegin
Position.X:=x;
Position.Y:=y;
end else
Ьegin //новый ряд фигур х:=1;
Position.X:=x; inc(y, sHeight); Position.Y:=y;
end; end;
end;
![]()
![]()
Запустив программу, вы убедитесь, что теперь за размеры и расстановку шахмат ных клеток отвечает форма. Любое изменение геометрии формы приводит к немед ленному перестроению экземпляров класса TRectangle. Если еще немного поколдо вать над кодом, то окажется несложным и раскрасить прямоугольники в черно белые цвета, совсем как у шахматной доски (рис. 4.8).
![]()
Рис. 4.8. При изменении размера формы "шахматные клетки" подстраиваются под новые условия
События перетаскивания drag and drop
Платформа FireMonkey поддерживает удобный интерфейс перетаскивания объек тов мышью. В операции участвуют два объекта: компонент - источник данных и компонент - получатель данных.
Операцию перетаскивания инициирует компонент-источник. В его распоряжении имеется свойство, определяющее порядок инициализации процесса.
property DragMode: TDragMode; // по умолчанию drnМanual
Если свойство установить в состояние dmAutomatic, то любое перемещение над компонентом-источником указателя мыши (у которой удерживается в нажатом со стоянии левая или правая кнопка) автоматически активирует механизм drag and drop. В ручном режиме перетаскивания (drnМanual) команду на перетаскивание дол жен дать программист.
· Процесс перетаскивания последовательно сопровождается цепочкой событий (табл. 4.4).
![]()
Таблица 4.4. Последовательность событий в операции drag and
drop
|
События |
Инициатор |
Описание |
|
OnDrag.Enter ( ) |
Элемент-
источник |
Над
элементом-источником
нажата
кнопка мыши,
и начато
движение с
удержанием
кнопки в
нажатом со- стоянии |
|
OnDragLeave() |
Элемент-
источник |
Указатель
мыши
покидает
область над
элементом- источником |
|
OnDragEnter ( ) |
Элемент-
приемник |
Над приемником
появилась
мышь с
"данными", пере-
таскиваемыми
от
элемента-источника |
|
OnDragOver () |
Элемент-
приемник |
Многократная
генерация
события,
пока перетаскивае-
мый элемент
находится
над приемником |
|
OnDragDroup () |
Элемен,-
приемник |
Над элементом-приемником отпущена кнопка мыши |
|
OnDragEnd () |
Элемент-
источник |
Сигнализирует
о
завершении
перетаскивания, исполь-
зуется для
освобождения
используемых
ресурсов, от-
мены
операции и т.
п. |
В минимальной нотации для 'осуществления перетаскивания достаточно услуг все го двух событий, генерируемых на стороне компонента-приемника: onoragOver () и onoragDrop (). Первое из событий осуществляет проверку приемлемости поступаю щих данных и дает согласие на получение данных от компонента-источника.
property OnDragOver:
TDragOVerEvent;
type TDragOverEvent = procedure(Sender: TObject; const Data: TDragObject; const Point: TPointF; var Accept: Boolean)
of object;
Здесь Sender - компонент-получатель, вызвавший событие. Ссылка на объект, вы
ступивший
инициатором
операции
перетаскивания, находится
в параметре
Data. Это запись, состоящая из трех
полей, ключевое из которых Source - именно в
нем
находятся все
сведения об объекте-источнике
операции drag and drop
(лис тинг 4.14). Координаты указателя мыши можно прочитать в
параметре Point. Свое
согласие на
получение
данных
компонент-получатель
дает, передав
в параметр Accept значение
true.
•-с.,4.
г------·•·· .....,...... . ....·····- -..
![]()
type TDragObject = record
Source: TObject; //объект-источник операции
Files: array of string; //массив строк
Data: Variant; / /дополнительные данные
end;
Второе
обязательное
событие
механизма drag and drop
отвечает за
обработку по
ступивших
данных. Соб_ытие
генерируется
в момент отпускания
кнопки мыши над компонентом-приемником.
![]()
![]()
![]()
property OnDragDrop: TDragDropEvent;
type TDragDropEvent = procedure(Sender: TObject;
const Data: TDragObject; const Point: TPointF) of object;
Параметры
события вам
уже знакомы,
поэтому в
отдельных
комментариях
не нуж- даются. *
Для
подтверждения
теории на
практике создайте
новое
приложение FireMonkey и
разместите
на форме
произвольное
количество
компонентов тrmage, специашrзи
рующихся на
хранении и
отображении
рисунков. У всех
компонентов
установите
свойство DragMode в
автоматический
режим. В
любой из
компонентов тrmage загру
зите
произвольный
рисунок.
Листинг 4.15
содержит код,
общий для
всех
компонентов. Здесь
описаны оба
рас
смотренных
ранее
обработчика
событий.
![]()
prooedure
TFonnl.ImagelDragOver(Sender: TObject;
const Data: TDragObject;
const Point: TPointF;
var Accept: Boolean);
Ьegin
(проверка допустимости операции}
Accept:=(Data.Source is Timage) and (Data.Source<>Sender) and
(Tlmage(Data.Source) .Bitmap.IsEmpty=false);
end;
procedure TFonnl.ImagelDragDrop(Sender: TObject;
const Data: TDragObject;
const Point: TPointF);
Ьegin
Tlmage(Sender) .Bitmap.Assign(Timage(Data.Source) .Bitmap); (передаем
картинку в
приемник}
Tlmage(Data.Source) .Bitmap.Destroy; (удаляем исходную картинку
у источника}
Timage(Data.Source).Bitmap:=TBitmap.Create(0,0);(coздaeм
новый пустой
Bitmap}
Tlmage(Data.Source) .Repaint;
end;
Завершая
разговор о drag and drop, упомянем
о наличии еще
трех
вспомогательных
событий,
имеющих
отношение к
перетаскиванию.
В момент
появления
указателя
мыши над
компонентом,
принимающим
участие в процессе
перетаскивания,
гене
рируется событие
property OnDragEnter: TDragEnterEvent;
В момент ухода указателя мыши вызывается еще одно событие
property OnDragLeave: TNotifyEvent;
![]()
![]()
![]()
82 Глввн 4
О завершении процедуры перетаскивания сигнализирует событие
property OnDragEnd:
TNotifyEvent;
Особенности прорисовки элемента управления
Визитной карточкой FireMonkey являются выдающиеся графические возможности. Немудрено, что каждый потомок класса тcontrol обладает способностью вывода своего изображения в любом подходящем для этого месте. Процесс вывода ини циируется методом
prooedure PaintTo(const ACanvas: TCanvas;
const ARect: TRectF;
const AParent:
TFrnxObject = nil);
Здесь, помимо ссьmки на холст ACanvas, на поверхности которого будет осуществ лен вывод, следует указать прямоугольную область ARect, ограничивающую уча сток вывода (листинг 4.16).
![]()
Forml.Canvas.BeginScene;
Buttonl.PaintTo(Forml.Canvas,
RectF(0,0,Buttonl.Width * 2,Buttonl.Height*2));
Forml.Canvas.EndScene;
Еще одна особенность класса - наличие двух событий, контролирующих вывод на экран изображения элемента, управления:
property OnPainting:
TOnPaintEvent;
property OnPaint:
TOnPaintEvent;
Оба события типизированы одинаково:
type TOnPaintEvent = procedure(Sender: TObject; Canvas: TCanvas;
const ARect: TRectF) of object;
Благодаря
параметрам
события, мы
сможем получить
доступ к
холсту Canvas и узнать координаты
прямоугольной
области ARect.
Завершая речь об операции прорисовки, отметим полезный метод
function MakeScreenshot: TBitmap;
позволяющий создать экранный снимок элемента управления и
представить его в
формате
растрового
объекта твi tmap.
ЗАМЕЧАНИЕ
Метод MakeScreenshot () может
пригодиться,
если вы
хотите
сохранить в растровом
образе твi tmap результат
воздействия
графических
эффектов
(палитра
компонентов Effects) на
элементы
управления (см.
главу 19).
![]()
![]()
Стилевое оформление, класс TStyledControl
Одним из достоинств библиотеки FireMonkey является возможность индивидуаль ного стилевого оформления элементов управления. Ответственность за это возло жена на описанный в модуле FМX.Controls класс тstyledcontrol. Ключевое свойст во класса
property StyleLookup: string;
предоставляет разработчику возможность управлять внешним видом компонента одним щелчком мыши в Инспекторе объектов (рис. 4.9).
![]()
Рис. 4.9. Примеры
стилевого
оформления кнопки TButton
для приложения iOS
ЗD-элементы управления, класс TContro/ЗD
Желание научить проекты Delphi работат в трех измерениях заставило програм мистов Embarcadero пересмотреть всю концепцию построения элементов управле ния. Во-первых, возникла необходимость модифицировать способ позиционирова ния объекта - теперь кроме координат Х и У приходится учитывать и координа ту Z. Во-вторых, в трехмерных проектах изначально рассчитанная на двухмерные сцены библиотека GDI здесь однозначно неприменима, поэтому принципиально
изменилась
механика
графического
вывода.
В-третьих, амбициозное
решение соз дать
кроссплатформенный
язык
программирования,
способный
создавать
програм мы не
только
для Windows,
вынудило
разработчиков
принципиально
перекроить
внутренний
механизм
работы приложения
уже на уровне
RTТI. При
желании к приведенному
перечню
проблем
можно добавить
и
"в-четвертых",
и "в-десятых".
Одним словом,
библиотека
FireMonkey - это
абсолютно
новый, не
имеющий ана
логов
инновационный продукт.
В
последних
разделах главы, посвященной
базовым
классам FireMonkey, мы
уделим внимание ключевым чертам компонентов, предназначенных для работы в ЗD-приложениях - трехмерных
элементов
управления,
построенных
на основе класса
EМX.ControlsЗD.-TControlЗD.
Размеры объекта
Появление у элементов управления третьего измерения отражается в дополнении традиционных свойств
property Width: Single;
property Height: Single;
отвечающих соответственно за ширину и высmу объекта еще одним свойством
property Depth: Single;
позволяющим разработчику определить глубину объекта.
Трехмерный объект может вращаться относительно каждой из своих осей, значение поворота в градусах направляется в свойство
property RftationAngle: TPositionЗD;
Координаты точки объекта, относительно которой осуществляется вращение, на значает свойство ·
property RotationCenter: TPositionЗD;
По умолчанию в качестве точки вращения выбирается центр объекта (точка (0.5, 0.5, 0.5)), при необходимости программист сможет изменить точку вращения, выбирая значения из диапазона от О до 1.
При необходимости элемент управления может выяснить
у другого объекта AObject
значения его углов поворота и применить их к себе
prooedure CopyRotationFrorn{oonst AObject: TControlЗD);
Возврат повернутого элемента управления в исходное состояние осуществляет метод
procedure ResetRotationAngle;
![]()
![]()
У
трехмерного
объекта
наиболее
сложны (а
поэтому и
весьма
интересны)
события мыши.
Сразу
заметим, что
набор событий
ничем не
отличается
от перечня "мыши
ных" событий для элемента управления VCL или
FireMonkey НD, таким образом,
у потомков
класса тcontrolЗD вы
обнаружите:
□ события нажатия OnМouseDown ( ) и опускания OnМouseUp ( ) кнопок
мыши;
□ событие перемещения мыши OnМouseMove ();
□ событие вращения колесика мыши OnМouseWheel ();
□
события появления OnМouseEnter () и исчезновения OnМouseLeave () указателя
мыши над
объектом.
Из представленного выше перечня
существенным
доработкам
подверглись
события OnМouseDown (), OnМouseUp () и OnМouseMove () . Например,
события,
связанные с нажа тием и
отпусканием
кнопок мыши
property OnМouseDown: ТМouseEventЗD; property OnМouseUp: TMouseEventЗD;
типизированы процедурой
TMouseEventЗD = proaedure(Sender: TObject;
Button: ТМouseButton;
Shift: TShiftState; Х,
У: Single; RayPos, RayDir:
TVectorЗD) of oЬject;
Здесь нам встречаются
как уже знакомые по табл.
4.3 параметры
sender, Shift
и х, У, так и пара новых векторных параметров (RayPos и RayDir), собственно благодаря
которым событие и становится "трехмерным".
То же самое можно сказать и об об работчике события, обеспечивающем
перемещение указателя мыши
property OnМouseMove: ТМouseМoveEventЗD;
TMouseMoveEventЗD = proaedure(Sender: TObject; Shift: TShiftState;
Х, У:
Single; RayPos, RayDir:
TVectorЗD) of object;
В нем нам вновь встречаются параметры RayPos и RayDir.
Каково
предназначение
RayPos и RayDir? Это
векторы,
соответственно
цредостав ляющие
программисту
сведения об
исходном положении
и
направлении
движения
указателя
мыши над
трехмернЬiм
элементом
управления.
Поп'робуем разработать приложение, позволяющее перемещать потомка класса
тcontrolЗD
(в
нашем
примере куб cubel:TCuЬe). Первым
шагом станет
объявление
трех глобальных
переменных
(листинг 4.17).
![]()
var
_RayPos, _RayDir: TVectorЗD; //начало вектора и
направление.вектора
WorkPlane: TPositionЗD; //определяет рабочую плоскость
![]()
В
векторах _RayPos и _RayDir
мы
намерены
хранигь
промежуточные
значения па
раметров
вектора.
Рабочая
плоскость
определиг
координатные
оси, в
пределах ко
торых
пользователь
сможет
изменять
положение
трехмерного
объекта. В
нашем
примере
(листинг 4.18)
ограничим
пользователя
плоскостью
осей абсцисс
и ор динат.
![]()
procedure TForm2.Foпn3DCreate(Sender: TObject);
Ьegin
WorkPlane:=TPositionЗD.Create(PointЗD(O, О,
1)); //создаем плоскость
end;
prooedure TForm2.Foпn3DDestroy(Sender: TObject);
Ьegin
WorkPlane.Free;
end;
Перед
началом
перемещения
объекта
следует
запомнить
исходные
значения
пози ции и направления
вектора
(листинг 4.19).
Позднее эти
значения станут
точкой от
счета для
движения
объекта мышью.
![]()
prooedur• TFoпn2.CubelMouseDown(Sender: TObject; Button: ТМouseButton; Shift: TShiftState; Х, У:
Single; RayPos, RayDir:
TVectorЗD);
Ьegin
_RayPos := CuЬel.LocalToAЬsoluteVector(RayPos);
_RayDir .- CuЬel.LocalToAЬsoluteVector(RayDir) .GetNoпnalize;
end;
Наконец подготовигельные операции завершены, мы присrупаем к самому важно
' -
му (листинг 4.20).
,·-·· ..........
prooedure TForm2.CuЬelMouseMove(Sender: TObject; Shift: TShiftState; Х, У: Single; RayPos, RayDir: ТVectorЗD);
var V, Vl, V2,
М: ТVectorЗD;
Ьegin
if ssLeft in Shift then
Ьegin
RayPos : = CuЬel. LocalToAЬsoluteVector (RayPo_s) ;
RayDir .- CuЬel.LocalToAЬsoluteVector(RayDir) .GetNoпnalize;
![]()
![]()
//учитываем рабочую плоскость
V := WorkPlane.Vector;
V.W := О;
V := Cubel.LocalToAЬsoluteVector(V);
if (RayCastPlaneintersect(_RayPos, _RayDir,
CuЬel.AЬsolutePosition,V,Vl)=false)
or
(RayCastPlaneintersect(RayPos, RayDir,
CuЬel.AЬsolutePosition, V, V2)=false) then
Exit;
М := V2 - Vl; //расчет вектора перемещения
//перемещаем объект
Cubel.Position.Vector := CuЬel.Position.Vector + М;
_RayPos := RayPos;
_RayDir := RayDir; end;
end;
Как
видите,
перемещение объекта
в трехмерном пространстве
- не такая
простая
задача,
однако если
проявить
немного
настойчивости,
то она вполне
решаема.
ГЛАВА 5
При проектировании компонентов FMX специалисты Embarcadero постарались со хранить привычные по классической библиотеке визуальных компонентов VCL названия, функциональное назначение и, по возможности, набор свойств и методов новых классов. Поэтому вы вряд ли удивитесь, узнав, что приложение FireMonkey, будь это двухмерное приложение HD или трехмерное 3D, описывается классом
TApplication.
Приложение FМХ. Forms. TApplication несколько выбивается из стройных рядов классов FireMonkey. Несмотря на то, что в полном имени класса присутствует при ставка "FMX" (уведомляющая программиста, что он имеет дело с кроссплатфор менной библиотекой), описанное в модуле FМХ. Forms приложение строится на осно ве традиционного класса библиотеки УС( system. Classes. тcomponent (см. рис. 2.1).
·ЗАМЕЧАНИЕ
При
знакомстве с
классами FireMonkey
любой
программист
станет
сравнивать
их с аналогами из VCL. На взгляд автора, в
сравнении с
классическим
TApplication из
состава VCL, класс FМХ. Forms. TApplication несколько проигрывает. Впрочем, это объясняется
объективной
причиной.
Нацеленная
исключительно
на Windows библио
тека VCL позволила
разработчикам
Delphi
спроектировать
исключительно
качествен
ный
специализированный класс,
наделенный
внушительным
набором сервисных
воз
можностей.
В свою очередь,
при работе
над универсальным
приложением,
способным одинаково
хорошо трудиться
как под управлением Windows, так и под контролем
OS Х, IOS и
Android,
специалисты
Embarcadero были
связаны
обязательством - од
новременно
удовлетворить
требования очень
непохожих
операционных
систем. Так что по объективным причинам
FМХ. Forms. TApplication - это своего рода компро
мисс между
современной
Delphi и
подцерживаемыми
операционными
системами.
Для организации работы с приложением не стоит вызывать его конструктор. Объ ект приложения создается автоматически. Чтобы в этом убедиться, выберите пункт
меню Project I View Source, и в редакторе кода откроется головной модуль проекта FireMonkey (листинг 5.1).
![]()
![]()
program Projectl;
uses FМХ. Forms,
Unitl in 'Unitl.pas' {Foпnl};
{$R *. res}
Ьegin
Application.Initialize; //инициализация
Application.CreateFoпn(TFoпnl, Foпnl); //создание главной формы
Application.Run; //старт приложения
end.
![]()
Как видно из листинга, доступ
к экземпляру
приложения
предоставляет
глобальная
(объявленная
в модуле
FМХ. Foпns)
переменная
Application. В
головном
модуле про
екта приложение
проходит
инициализацию, создается
главная форма и производит
ся старт
программного продукта.
Приложение обязано знать
свою главную форму.
Ссылка на
эту форму
находится в
свойстве
property MainFoпn: TConmonCustomFoпn;
Для экстренного завершения приложения можно воспользоваться методом
prooedure Teпninate;
Но следует понимать, что это слишком
прямолинейный метод, поэтому им не стоит
злоупотреблять.
Если
вы
разрабатываете
настольное
приложение для Windows
или OS Х, то вос
пользуетесь
пунктом меню Project I Options и получите
доступ к
редактору
опций проекта (рис. 5.1), в
котором сможете выбрать значок приложения.
Заметьте,
что
настольное
приложение
FireMonkey способно
оперировать
как со
значками в стиле
Windows (это файл с
рисунком с
расширением ico), так и
с фай лом,
содержащим
значки в духе
OS Х (расширение
icns).
При
проектировании
приложения
для мобильных
платформ
помощник
выбора
значков
несколько
меняется
(рис. 5.2).
При необходимости с
приложением может быть сопоставлено текстовое название
property Title: string;
Однако
данное
свойство
оставлено
разработчиками
новой
платформы в
составе TApplication скорее по
привычке и в
современных
версиях Windows и OS
Хне ото
бражается.
,,,
![]()
![]()
![]()
Рис. 5.2. Раздел Application в редакторе опций проекта для мобильных приложений Android
Еще одно связанное с
названием приложения свойство
property DefaultTitle: string; //только для чтения
заполняется
автоматически
в момент запуска
приложения.
Здесь может
оказаться
строка "application",
название
связанного с
приложением
сервиса или
свойство во
обще может
быть пустым.
![]()
![]()
![]()
Расположение исполняемого файла приложения
При многолетней работе с классической библиотекой VCL у многих из нас сложи лись собственные предпочтения и привычки. Например, в своих проектах для вы яснения расположения приложения автор зачастую обращался к свойству ExeName, имевшемуся у класса TApplication из VCL. К сожалению, аналогичного свойства у приложения FireMonkey нет, посему из сложившейся ситуации приходится выкручиваться подручными средствами. В данном случае простейшим решением может стать задействование определенной в модуле system. sysUtils функции
function GetCurrentDir: string;
Функция возвращает текущий относительно выполняемого приложения каталог. Если к ней обратиться в момент запуска приложения или в момент создания глав ной формы, мы получим полный путь к папке, в которой располагается исполняе мый файл.
Приложение FMX не в состоянии похвастаться богатым перечнем обработчиков событий - их всего три. В минимальный джентльменский набор вошло событие, позволяющее осуществить централизованную обработку исключительных ситуа ций (ИС):
property OnException: TExceptionEvent;
type TExceptionEvent=procedure(Sender: TObject; ·Е: Exception) of object;
Здесь Sender - приложение, в котором возникла ИС; Е - экземпляр исключитель ной ситуации. Так как в этот обработчик события станут стекаться сведения обо всех произошедших в приложении исключительных ситуациях, то OnException ( ) обычно не специализируется на обработке частного вида ошибок. Наоборот, в его рамках описывается общая концепция защиты программного продукта.
В
качестве
примера
рассмотрим
вариант применения
события OnException () для
ведения
журнала
ошибок
приложения
(листинг 5.2).
Анализируя
этот
протокол,
программист
получит
бесценную
информацию о
своих
недоработках.
Обратите
внимание на. то, что наш код сосредоточен в
головном модуле проекта (в
файле с расширением
имени dpr). Для
того чтобы
добраться до этого
модуля,
воспользуй
тесь главным
меню Delphi: Project I View Source.
program Projectl; uses
FМX.Forms, System.SysUtils, System.IOUtils, Unitl in 'Unitl.pas' {Forml};
{$R *.res}
type TAppExcept= class(TObject) //объявляем новый класс ИС
private
procedure ExceptionsControl(Sender:TObject; Е:
Exception);
end;
procedure TAppExcept.ExceptionsControl(Sender: TObject;
Е: Exception-);
var s:string;
Ьegin
s:=Foпnat('%s'+#9+'%s',
[DateTimeToStr(Now),E.Message]); //сообщение
TFile.AppendAllText('errors.log',s+#l0#lЗ); //запись
в файл
Application.ShowException(E); //информируем пользователя об ошибке
end;
var AppExcept: TAppExcept;
Ьegin
Application.Initialize;
AppEx�ept:=TAppExcept.Create; //создание нашего объекта
Application.OnException:= AppExcept.ExceptionsControl; Application.CreateForm(TForml, Forml);
Application.Run;
end.
Главным
действующим
лицом
листинга
выступает
класс TAppExcept, единственной
особенностью
которого является
процедура ExceptionsControl() - по параметрам
абсолютный
аналог
события OnException () . Единственный
метод класса TAppExcept решает простейшую задачу - сохраняет содержимое свойства Message исключи
тельной ситуации в
файл протокола и
выводит сообщение об ошибке. Обратите
внимание,
каким
образом
будет вызываться
процедура TAppExcept. ExceptionsControl(). Сразу
после
инициализации
объекта Application мы
создаем
объект AppExcept и накрепко
связываем
его метод Ex.ceptionscontrol () с процеду
рой обработки события OnException () . Теперь при возникновении в
приложении
ИС ее обработка переходит в
описанный нами метод и
будет запротоколирована
в файл
errors.log.
Второе событие приложения
property Onldle: TidleEvent;
type TidleEvent=procedure(Sender: TObject;
var Done: Boolean) of object;
позволит
программисту
вызывать код
фонового
режима в
момент
простоя
прило жения.
На этом
список
событий
исчерпывается.
ВННМАННЕ!
Событие
Onidle () генерируется
только у
приложения,
выполняющегося под
управле нием
Windows.
В листинге
5.3 представлен
фрагмент
кода,
демонстрирующий
порядок
подключе ния
к собьпию Onidle () .
![]()
type
TForml class (TForm)
procedure FormCreate(Sender: TObject); private
''""'t•·•······ ..··i
![]()
i
...,.....................-, ··•···..···"' ........~.,,... " ....,.,......... '-
procedure IdleThread(Sender: TObject; var Done:
Boolean); puЫic
{ PuЫic declarations } end;
var Forml: TForml; implementation
{$R*.frnx}
procedure
TForml.FormCreate(Sender: TObject);
Ьegin
Application.Onidle:=IdleThread; end;
procedure
TForml.IdleThread(Sender: TObject; var Done: Boolean); Ьegin
//код nриложения, выполняемый в
фоновом режиме end;
В блок кода фонового режима целесообразно включать небольшие программные конструкции, выполнение (или невыполнение) которых не является критичным для основной программной логики приложения. Кроме того, в фоновом режиме можно устанавливать в актуальное состояние элементы пользовательского интерфейса (пункты главного меню, кнопки быстрого доступа и т. п.), например, активировать пункт меню Сохранить, если обрабатываемый документ был модифицирован, и, наоборот, выключать этот пункт меню, если в нем нет необходимости.
Благодаря тому, что во второй версии библиотеки FireMonkey появилась возмож ность создавать командные объекты TAction, позволяющие обеспечить централи зованное управление приложением, у TApplication имеется еще один обработчик событий
property
OnActionUpdate: TActionEvent;
Данное
событие
генерируется
при каждом обновлении
состояния
командного
объ екта (см.
главу 7).
Контроль активности пользователя
Заключительная особенность приложения FMX, на которой стоит заострить внима ние разработчика, заключается в возможности обеспечения контроля активности
пользователя. Пока это сводится к появлению двух новых, доступных только для чтения свойств
property LastKeyPress TDateTirne;
property LastUserActive: TDateTime;
Первое из свойств хранит сведения о том, когда в последний раз пользователь на жал любую клавишу в активном приложении. Второе свойство знает, когда пользо ватель в последний раз осуществлял любые действия с программой (нажатие кла виш, движение мыши, прикосновение к сенсорному экрану).
Характеристики дисплея, класс TFormFactor
Кроссплатформенное приложение FireMonkey регулярно сталкивается с задачей выяснения характеристик устройства, на котором оно должно выполняться. Масло в огонь подливает тот факт, что приложение может оказаться развернутым не толь ко на персональным компьютере под управлением Windows или OS Х, но и на мо бильном устройстве iOS или Android. В данном случае нашим помощником может стать свойство
property
FormFactor: TFormFactor;
дающее доступ к инкапсулированному в приложение Application объекту
TFormFactor.
ЗАМЕЧАНИЕ
Свойство
FormFactor может
быть
настроено не
только для
приложения,
но и индиви
дуально для
каждой из форм,
входящих в
проект.
Класс TFormFactor предоставит сведения, на каких устройствах может быть ото бражено приложение, для этого предназначено свойство
property Devices: TDeviceKinds;
способное возврат.ить множество значен11й, элементами которого выступают
dkDesktop, dkiPhone
И dkiPad;
Владелец смартфона или планшета может как угодно повернуть свое устройство.
Поддерживаемые ориентации экрана устройства хранятся в свойстве
property
Orientations: TFormOrientations;
Здесь вновь речь идет о множестве, значениями которого могут выступать: soPortrait, soLandscape, soinvertedPortrait и soinvertedLandscape. Перечисленные значения также могут быть настроены и в редакторе опций мобильного проекта (пункт меню Project I Options), в этом вы можете убедиться, взглянув на рис. 5.3.
Завершая разговор о TFormFactor, обязательно упомянем традиционные свойства
property Width: Integer;
property Height: Integer;
знающие все о разрешении устройства отображения.
![]()
![]()
![]()
![]()
![]()
|
6) Project Options |
for |
liЬProj«tl.so |
(Android • OeЬug) |
|
|
|
,. Coщ,kor ! !···Ccщ,iling Нi'I ilnd Wairnngs , ... Linlcing , i.. tput-C/C++ " Resowae Compiler < l- O..ecto,if,s IJnd CondibONls I
!Ud Ev<!nts : Use P!!rmissions ·•Forms ' Appic,stlon V!!rsion lnfo ,.р ·-Runtin<,р ..
Dehщig« ·. '-Syml,olTa№s ; ! Envi'onme1t В1осk Provisl<n,g |
I.-vet: ОеЬ<,о conng,sAtlon - Android p!Atfurm |
||||
|
lalnl Oril!ntalion Preview 1-Check==-cus=tom=.iorientabon and -tlhe--- c,-.,,ogrom fies ···'fМJ.вuncht-rlcon_Збx36.png orienaitlon(s) lhat you want your ■ t.-,d,er
icon _(36 36- : _ appkJttioo !о support, !'l) \;.ustom orientabon Eo,tralt down L nome(iфt Е)Londscзpe home!eft lf]Adual!IZI!
|
|||||
|
|
ок |
11с.а111 |
|||
![]()
![]()
![]()
Рис.
5.3. Настройка ориентации экрана в редакторе опций
проекта для мобильных приложений
Основу приложений FireMonkey с графическим интерфейсом пользователя состав ляет форма. Приложения FireMonkey могут использовать две разновидности форм:
□ форму FМХ. Forms • TForm, предназначенную для построения деловых приложений с высококачественным двухмерным визуальным интерфейсом (в том числе и с интерфейсом Metropolis, предн наченным для Windows 8);
□ форму FМХ. FormsЗD. TFormЗD, реализующую всю мощь трехмерной графики.
ЗАМЕЧАНИЕ
Для
обеих
разновидностей
форм
основным родительским
классом
выступает FМХ. Forms. TCommonCustomForm. Затем цепочка наследования разделяется на две ветви (см. рис. 2.1)- тcustomForm и тcustomFormЗD, соответственно отвечающие
за построение
двухмерной и
трехмерной
форм.
Основное назначение формы - служить контейнером для визуальных элементов управления, предоставляя программисту возможность создавать пользовательский интерфейс приложения, просто перенося подходящие компоненты со страниц па литры. После старта программы форма приобретает все необходимые качества полноценного окна приложения для Windows, OS Х, IOS или Android.
Число форм в проекте не ограничено, но в любом случае в приложении может быть только одна главная форма (main form) проекта. По умолчанию главной формой проекта назначается самая первая созданная форма - форма с названием Forml. При запуске приложения на выполнение именно эта форма будет создана и выве дена на экран первой.
![]()
При
необходимости
у проекта,
обладающего несколькими
формами, на
роль глав
ной может
быть
назначена
другая форма.
Для этого
следует
воспользоваться
пунк том меню Project I Options и в
появившемся дереве настройки
опций проекта
вы
брать
узел Forms (рис. 5.4).
Главная
форма
вьrбирается в
раскрывающемся
списке Main form. Стоит заметить,
что на роль
главной
формы могут
претендовать
только формы из списка Auto-create forms (Автоматически создаваемые формы). Форма
из списка AvailaЫe forms (Доступные формы) стать главной
не сможет, т.
к. она
не
подлежит автоматическому созданию
в момент старта приложения.
![]()
![]()
![]()
--1
!![]()
![]()
М,.,fonn: 1
!i:-1 ·1
1 1
![]()
![]()
i\lJll><re.111! forms: Avillllble {oons:
L<.J
w
!·• Envronment В1осk
![]()
в
![]()
- --
1.
'
..,,- (); 1 1 с.ке1 ! ! н.1R 1
Рис. 5.4. Окно опций со страницей выбора главной формы и перечнем доступных форм
При разработке
пользовательского интерфейса наших приложений
мы всегда будем
помнить, что закрытие
главной формы приводит
к автоматическому закрытию
всех
остальных
форм проекта
и прекращению
работы
приложения.
Добавление к
проекту формы сопровождается созданием файла ресурсов формы
с
расширением
fmx. Именно
в этом
файле и
хранится
текстовое
описание
формы и принадлежащих
ей
компонентов.
Для того
чтобы взглянуть
на описание
во время
визуального проектирования, следует
воспользоваться контекстным
меню формы
и
. выбрать
пункт View
as Text (Представить
как текст)
или нажать
комбинацию
кла виш <Alt>+<Fl2>.
Код
с текстовым
описанием
ресурса
формы с рис. 5.5
представлен
в листинге 5.4.
Обратите
внимание на то, что
кроме
определения
основных
свойств
формы ресурс
fmх-файла содержит
определения
всех
принадлежащих
форме
элементов
управле ния
(в нашем
случае метки Labell и кнопки вuttonl).
![]()
![]()
![]()
![]()
I
Рис.
5.5. Обращение к контекстному меню формы
во время визуального проектирования
![]()
object f!'orml : TForml Left = О
Тор= О
Caption = 'Forml'
ClientHeight = 192
ClientWidth = 362
VisiЫe = False
OnClose = FormClose
StyleLookup = 'backgroundstyle'
oЬject Labell: TLabel Position.Point = '(40,24)'
Width = 120.000000000000000000
Height = 15.000000000000000000
TabOrder = 1
Text =
'Labell'
end
oЬject
Buttonl: TButton Position.Point = '(144,104)'
Width = 80.000000000000000000
Height = 22.000000000000000000
TaЬOrder = 10
Text =
'Buttonl'
end
end
ВНИМАНИЕ!
Для того
чтобы
вернуть
форму к
обычному
представлению, нажмите
комбинацию
кла виш
<Alt>+<F12>.
Общие черты форм
Все используемые в FMX формы опираются на фундамент родительского класса
type TCoпrnonCustomFonn = class(TFmxObject, IRoot, IContainerObject,
IAlignRoot,
IPaintControl, IStyleBookOwner,
IDesignerStorage, IOriginalContainerSize)
Помимо уже знакомого нам класса TFmxObj ect в объявлении упоминается несколько интерфейсов, которые оказывают форме все необходимые услуги автономно и не требуют вмешательства со стороны обычного программиста.
ЗАМЕЧАНИЕ
Интерфейс
высокоуровневых
контейнеров IRoot оказывает
форме
всестороннюю
поддержку
по доступу к
активным
объектам и
элементам
управления,
находящимся
в фокусе ввода. Интерфейсы
IContainerObject и IAl;i.gnRoot упрощают выравнивание
объектов на поверхности
контейнера. Интерфейс
IPaintControl
определяет,
какой из
элементов
управления
формы
нуждается в
перерисовке.
Элементы
управления
фор мы,
способные
выступать
владельцем
книги стилевого
оформления
(компонент TStyleBook), обслуживаются
интерфейсом IStyleBookOwner. При
разработке
форм для
мобильных
проектов
задействуется
интерфейс IDesignerStorage. Наконец,
ин терфейс IOriginalContainerSize устанавливает
корректные
размеры для
элемен тов управления.
На уровне тcoпrnonCustomFonn класса заложен минимально необходимый функцио нал:
□ объявлены базовые конструкторы и деструктор формы;
□ осуществляется у : равление размерами и местоположением формы;
□ реализуется отображение формы (в том числе в модальном режиме) и скрытие формы с экрана.
Создание, отображение и уничтожение форм
По умолчанию каждая подключаемая к проекту форма (пункт меню File I New 1 FireMonkey НD Form) при старте программы заносится в список форм, подлежа щих автом тическому созданию. При запуске приложения первой на свет появля ется главная форма проекта, все остальные формы создаются согласно очередно сти, указанной программистом. Для изменения очередности создания форм надо заглянуть в опции проекта (Project Options) и расставить формы в надлежащем порядке в списке Auto-create forms (см. рис. 5.4). За создание форм из перечця Auto-create forms несет ответственность приложение Application. Воспользовав
шись пунктом меню Project I View Source, вы -получите доступ к головному моду лю проекта и увидите, что для автоматически создаваемых форм приложение ис
пользует метод createFonn().
![]()
Для отображения формы на экране компьютера обычно применяют метод:
procedure Show;
Такой
способ
вывода формы
на экран
называется немодальным.
Это
означает, что
пользователь
получает
право
свободно
переключаться
между окнами
приложения.
Среда
проектироваНИ5!
позволяет
нам отказаться
от
автоматического
создания
форм, такие
формы переводятся в
разряд доступных (список AvailaЫe forms).
Но
в этом
случае перед
обращением к
такой форме
программист
должен ее
создать.
По
законам ООП
для создания
объекта
требуется
воспользоваться
его
конструкто
ром, и форма
не является
исключением
из правил:
constructor
Create(AOwner: TComponent);
Допустим,
что описанная
в
программном
модуле child.pas
форма TfrmChild
не
явля ется
автоматически
создаваемой и
входит
в список доступных
форм нашего
проек та. В
этом случае
для создания
экземпляра
формы можно
воспользоваться
листин гом 5.5.
![]()
var frmМain: TfrmМain; //главная форма implementation
{$R *. frnx}
uses child;
procedure TfrmМain.ButtonlClick(Sender: TObject);
Ьegin
with TfrmChild.Create(Application) do //создание дочерней формы Ьegin
Caption:=Caption+'$'+IntToHex(Handle,8);//тeкcт заголовка Show; //вывод на экран
end; end;
ВНИМАНИЕ!
При ссылке
одного
модуля на
другой не
забудьте
добавить в
строку uses имя нового
модуля. Для
этого
выберите
пункт меню File I Use
unit (<ALT>+<F11>) и в
списке мо
дулей
найдите
нужный модуль.
Форма
TfrmChild создается
по щелчку на
кнопке вuttonl, расположевной
на глав ной форме проекта frmМain. Основная особенность предложенного в
листинге 5.5 кода в
том, что будет создано ровно столько клонов формы TfrmChild, сколько вы сделаете щелчков по кнопке. Чтобы формы отличались друг от друга, мы выводим
в заголовке создаваемых форм их дескриптор нandle.
![]()
ЗАМЕЧАНИЕ
Когда
проект
содержит
несколько
форм, хорошей
практикой
является
присвоение
формам
осмысленных
имен. Например,
главную
форму
проекта
можно
называть frmМain, а
соответствующий
ей
программный
модуль - Main.pas.
Полноэкранный вывод
При
работе на планшетных
компьютерах,
смартфонах и телефонах
приложение
бу дет нуждаться
в максимальном рабочем
пространстве.
Индикатором
того, что фор ма
находится в полноэкранном
режиме, может
выступать свойство
property FullScreen: Boolean;
//по умолчанию false
Если
приложение
предназначено
для работы
под управлением
OS Х, то стоит поза ботиться
об
отображении
значка
полноэкранного
режима (две расходящиеся
по диагонали
стрелки). Для
этого
предназначено
свойство
property ShowFullScreenicon: Boolean;
Замечу,
что в приложениях
для OS Х переход
в полноэкранный
режим при пассив
ном состоянии
свойства
ShowFullScreenicon невозможен,
а для Windows это
свойст во не
имеет никакого
значения.
Вывод формы в
модальном режиме
Для вызова формы в
модальном режиме (режиме диалога) используйте метод
function ShowМodal: TModalResult;
Модальная
форма
отличается
строптивым нравом.
Она
отображается
поверх всех
открытых
окон. Более
того,
пользователь
не сможет
получить
доступа к
другим
формам
проекта, пока
не будет
закрыта
модальная
форма.
Закрываясь,
модальная
форма возвращает целочисленное
значение, называемое модш ьным результатом.
При
необходимости
в модальном
результате
можно
закодировать
решение
пользо
вателя,
нажавшего ту
или иную
кнопку на
форме.
type ТМodalResult = Low(Integer) .. High(Integer);
Возможные значения модального результата предложены в табл. 5.1.
Таблица 5.1. Стандартный модальный результат ТМodalResul t
|
Константа |
Описание |
|
mrNone или
О |
Значение п_о умолчанию |
|
mrOk ИЛИ
idOK |
Пользователь нажал кнопку ОК |
|
mrCancel или idCancel |
Пользователь нажал кнопку
Отмена (Cancel) |
|
mrAЬort или idAЬort |
Пользователь нажал кнопку Прервать (Abort) |
|
mrRetry или
idRetry |
Пользователь нажал кнопку Повторить (Retry) |
|
mrignore или idignore |
Пользователь нажал кнопку Пропустить (lgnore) |
|
mrYes или
idYes |
Пользователь нажал кнопку Да (Yes) |
![]()
Таблица 5.1 (окончание)
|
Константа |
Оnисание |
|
mrNo или idNo |
Пользователь нажал кнопку
Нет (No) |
|
mrAll или
mrNo + l |
Используется для определения последней константы |
Для
передачи
результата
модальной
форме обычно
задействуют
свойство ModalResult кнопок. Анализируя
модальный
результат
закрывающейся
формы, про
граммист
получает
возможность
направлять
поведение
программы в определенное
русло
(листинг 5.6).
![]()
if Foпn2.ShowModal=mrOK then
Ьegin
{код для результата mrOK)
end else {код для результата, отличного от mrOK};
Результат модальной операции заносится в
свойство формы
property ModalResult: ТМodalResult;
По
умолчанию
это свойство
установлено в О
(константа mrNone). Присвоение
свой ству ModalResult ненулевого
значения
приводит к
закрытию формы
и
возвращению
значения
этого свойства
в качестве
результата
метода ShowModal. Если
получено
ненулевое значение ModalResult, осуществляется вызов метода закрытия формы.
Закрытие формы
Простейшим
способом
спрятать (не
уничтожая)
форму может
стать
присвоение
свойству VisiЫe значения false. Этот же результат достигается при обращении
к процедуре
proaedure Hide;
Кроме того, у
формы имеется метод
proaedure
Close.;
предназначенный
для закрытия
формы. Если этот
метод
примещется к
главной
форме проекта,
то он
приводит к
закрытию
всех выведенных
на экран форм
и за
вершению
работы приложения.
Если метод Close () вызывается
для
вторичных
форм проекта,
то форма
просто скрывается
с экрана.
ЗАМЕЧАНИЕ
С методом Close () связаны два важных собьiтия - OnGloseQuery () и OnClose () ,
о них мы поговорим отдельно (см. табл. 5.3).
![]()
![]()
![]()
Уничтожение формь1
Хотя форма поддерживает классич ский для всех компонентов деструктор Destroy () и метод Free (), для уничтожения экземпляра формы целесообразно вы зывать метод
prooedure Release;
Это ближайший соратник деструктора, который добивается тех же результатов - очищает память компьютера от формы, но в отличие от деструктора делает это са мым деликатным образом - дожидается завершения обработки событий, иниции рованных этой формой или ее компонентами.
Пояснительная надпись в заголовке формы назначается при обращении к свойству
property
Caption: string;
Местоположение формы на экране компьютера зависит от значения, установленно го в свойстве
property
Position: TFormPosition;//пo умолчанию poDefaultPosOnly TFormPosition = (poDesigned, poDefault, poDefaultPosOnly,
poDefaultSizeOnly,poScreenCenter, poDesktopCenter,
poMainFormCenter, poOwnerFormCenter);
При
создании
новой формы
свойство Position принимает
значение poDefaultPosOnly. Это
означает, что
вертикальный
и горизонтальный
размеры фор
мы (свойства Width и Height) назначаются
разработчиком,
а место вывода
формы на
экран
определяется
операционной
системой.
Обратная
ситуация
сложится в том
случае, если свойство Position
установлено в
poDefaultSizeOnly. Теперь операци
онная система станет
выводить
форму в месте, указанном
программистом
(свойства тор и
Left), но
ответственность
за установку
размеров
сторон
заберет в
свои руки. Форма
появится
точно в
центре
экрана при
передаче в Position значения
poScreencenter, а для
центровки
относительно
рабочего стола
подойдет
значение poDesktopCenter. На
поведение
вторичных
форм проекта
влияют poMainFormcenter и
poOwnerFormcenter. В первом
случае
дополнительные
формы позиционируются
по центру
главной
формы, во втором - по
центру
формы-владельца.
Интересно
пове дение
формы при
установке
этого
свойства в poDefault. При
первом
запуске при
ложения
форма отображается
в левом
верхнем углу
экрана, а при каждом
после дующем
выводе на
экран форма
станет
автоматически
смещаться
вправо и
вниз. Высота
и ширина
формы определяются
операционной
системой и не
зависят от
указаний
программиста.
Если вы
намерены
самостоятельно управлять
как размера
ми, так и
местом вывода
формы, то
установите свойство
в состояние poDesigned.
ЗАМЕЧАНИЕ
Мы уже привыкли, что свойство с названием
Position предназначено
для опреде
ления
местоположения
объектов FireMonkey
на поверхности
контейнера-владельца.
Однако в форме
это название
уже
задействовано,
поэтому
точное
позиционирование
формы на экране
осуществляется
за счет
свойств Left и Тор.
![]()
Перечень стандартных кнопок, располагающихся в правой части заголовка окна Windows или в левой стороне окна OS Х, задается свойством
property
Bordericons: TBorder�cons;
Различают: кнопку закрытия окна ЬiSystemМenu; кнопки сворачивания ЬiMinimize и
разворачивания ЬiMaximize окна; кнопку обращения к
справочной системе ЬiHelp.
Щелчки пользователя по кнопкам сворачивания и разворачивания окна изменяют значение свойства
property WindowState: TWindowState; //по умолчанию TWindowState.wsNormal TWindowState = (wsNormal,
wsMinimized, wsMaximized);
Состояние wsNormal говорит о том, что форма в нормальном состоянии; wsMinimized - форма свернута; wsMaximized - форма развернута до максимально возможного размера. Свойство Windowstate не только информационное, оно позво ляет сворачивать, разворачивать и нормализовать размеры формы во время выпол нения приложения.
Еще одно свойство
property FormState: TFmxFormStates; //только дпя чтения
позволит
программисту
контролировать,
не находится
ли форма в
процессе
созда ния (тFmxFormState. fsRecreating)
и не
станет ли
после
создания
модальным окном
(TFmxFormState. fsModal).
Существенное влияние на внешний вид и поведение формы оказывает стиль об рамления окна
property BorderStyle: TFmxFormВorderStyle; //по умолчанию bsSizeaЬle;
TFmxFormВorderStyle = (bsNone, //размеры неизменяемые, границы невидимы
bsSingle, //размеры неизменяемые, простой бордюр
bsSizeaЫe, //обычная форма с
настраиваемыми размерами
bsToolWindow, //размеры неизменяемые, уменьшенный заголовок
bsSizeToolWin); //размеры изменяются, уменьшенный заголовок
Жизненный путь формы начинается в момент ее создания. В табл. 5.2 приведена последовательность событий, возникающих при создании и выводе на экран ком пьютера новой формы.
Таблица 5.2. Процесс создания и
вывода на экран форм
|
Событие |
Описание |
|
OnCreate () |
Создание
формы.
Обычно этот обработчик
события
используется
для ини- циализации
глобальных
переменных
формы и
других
подготовительных операций, необходимых для дальнейшей ра.боты с формой |
|
OnActivate () |
Форма становится активной |
|
OnPaint () |
Осуществляется перерисовка формы |
Не меньший
интерес
представляют
события, возникающие
при закрытии
формы. Для
запуска
процесса
закрытия формы
необходимо
закрыть
форму или
вызвать метод
procedure Close;
Последовательность событий, возникающих у ф'ормы в процессе закрытия, пред- ставлена в табл. 5.3.
Таблица 5.3. Процесс закрытия и уничтожения формы
|
Собь1тие |
Описание |
|
OnCloseQuery () |
Запрос разрешения на закрытие формы |
|
OnClose () |
Форма закрывается |
|
OnDestroy ( ) |
Форма уничтожается |
|
OnDeactivate () |
Форма перестает быть
активной |
Перед закрытием формы производится вызов обработчика события:
property
OnCloseQuery: TCloseQueryEvent;
type TCloseQueryEvent = procedure(Sender: TObject;
var CanClose:
Boolean) of oЬject;
Присвоив
параметру-переменной
canclose значение
false, программист
отменяет
процесс закрытия
формы. В
листинге 5.7 предложен
пример,
осуществляющий
контроль за изменением
текста в многострочном
редакторе Memol.
В случае если в
элементе
управления текст
модифицировал я, то
при попытке
закрытия
формы
отображается
диалоговое
окно, предлагающее
сохранить
изменения.
![]()
var filename:striпg; //переменная с
именем обрабатываемого файла
TextModified:boolean; //признак, что текст подвергался изменениям
procedure TForml.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
Ьegin
if TextModified=true then
CASE MessageDlg('Coxpaнить изменения в
тексте?', TMsgDlgType.mtConfirmation,
[TMsgDlgBtn.mЬYes] + [TMsgDlgBtn.mЬNo]+ [ТМsgDlgBtn.mЬCancel],
-1) OF
mrYes : if FileExists(FileName)=false then Ьegin
{файл
не определен, вызываем диалог Сохранить как... )
//
end else Memol.Lines.SaveToFile(FileName);
![]()
![]()
![]()
![]()
![]()
mrNo: CanClose := true;
else CanClose
:= false;
END;
end;
Если форма получила разрешение на закрытие, то генерируется событие
property OnClose: TCloseEvent;
TCloseEvent = procedure(Sender: TObject;
var Action: TCloseAction) of oЬject;
Изменяя
значение
переменной
Action (табл. 5.4),
программист
сможет управлять
поведением
закрываемой
формы.
Таблица 5.4. Возможные значения параметра Action: TCloseAction
|
Значение |
Описание |
|
caNone |
Ничего не происходит |
|
caHide |
Форма
не закрывается,
а только
становится
невидимой, к
данной форме
при- ложение
имеет
полный
доступ |
|
caFree |
Форма закрывается и
освобождает занимаемые ресурсы |
|
caMinimize |
Вместо
закрытия форма сворачивается |
Немного
поозорничав
с параметром Action в С<?бытии 6nClose (), можно
создать
очень
вредную
программу, которая
ст_анет
активно
сопротивляться
своему закры
тию. Для этого достаточно
переписать
обработчик
события главной формы проекта
так, как
предложено в листинге
5.8.
![]()
procedure
TForml.FormClose(Sender: TObject;
var Action: TCloseAction);
Ьegin
Action:=TCloseAction.caNone;
with TForml.Create(Application) do Show(};
end;
Реальное
уничтожение формы и освобождение
всех задействованных
в ее
интере сах системных
ресурсов
произойдет
только в случае,
если
присвоить
переменной Action значение caFree. Только тогда будет
вызвано
событие
property OnDestroy:
TNotifyEvent;
При
этом форма
и все объекты·,ей
принадлежащие,
удаляются
из памяти
компью
тера.
Доступ к
элементу управления ·по его координатам
Помимо уже оговоренных ранее способов доступа к принадлежащим форме дочер ним объектам, реализованных с помощью свойств components и Children, форма предоставляет программисту еще один способ обращ·ения к расположенным на ее поверхности визуальным элементам управления. Для этого задействуется метод
function ObjectAtPoint(P: TPointF): IControl;
В
результате
мы получим
интерфейс
объекта,
находящегося
в точке
с координата-.
ми, заданными
параметром Р.
Совмещение
форм для разных мобильных
устройств в
одном
приложении
Одна из задач, с которой рано или ПОЗДНО столкнется разработчик проектов для мо бильных платформ, связана с адаптацией приложения к экранам с различным раз решением. Возьмем хотя бы размеры экранов у "наладонника" iPad и смартфона iPhone - они существенно отличаются. Как быть в таком случае?
Поставленная задача в Delphi имеет весьма элегантное решение. Нам только надо вспомнить·о существовании у формы следующих свойств:
property FormFactor: TFormFactor;
property FormFamily: string;
Со свойством FormFactor мы уже встречались при рассмотрении приложения TApplication, благодаря ему мы могли определить, на каком устройстве может вы полняться приложение. Свойство FormFamily просто содержит строковое значение, идентифицирующее, какому семейству принадлежит форма.
ЗАМЕЧАНИЕ
Свойства
FormFactor и FormFamily позволяют
не только
объединять в
одном прило
жении
различные
формы IOS, но и
создавать настольное
приложение,
предназначен
ное для
работы на
разных
операционны_х
системах.
Для демонстрации возможностей FormFactor и FormFamily даже не нужно програм мирования. Достаточно просто запомнить порядок действий.
1. Создайте
новое
мобильное
приложение,
которое (как
мы надеемся)
будет со
вместимо и с iPhone,
и с iPad.
2. Переименуйте
пока
единственную
формы проекта
в frm_iPhone - это
главная
форма для iPhone.
3. Обратитесь
к Инспектору
объектов и в
свойстве FormFactor. Devices
формы
(вы брав
элемент dkiPhone и
сняв флажки с
dkDesktop и dkiPad) укажите,
что она
предназначена
для работы
только с iPhone
(рис. 5.6).
А в свойство FormFamily передайте
строковое
значение мain.
ВНИМАНИЕ!
Хранящееся в
свойстве FormFamily значение Main указывает на то, что форма явля
![]()
ется главной для определенной в
FormFactor
мобильной платформы.
![]()
![]()
,,
Приложение FireMonkey 107
![]()
4.
Добавьте к проекту еще одну форму iOS, на этот раз назовите форму frm _ iPad.
5.
Воспользовавшщ:ь Инспектором объектов, укажите, что fпn_iPad рассчитана на
работу С iPad (FormFactor. Devices:
= [dkiPadJ ).
Теперь
приложение, проанализировав, на
какой из
мобильных
платформ оно
бьmо запущено,
самостоятельно
определит,
какую из форм
задействовать.
|
DevlCeC 1 (dflil'-hwle] 'lrJFe _ -· J J Orlenta\iont мdscllpe, soPortrillt Т1uе 10L1nd1c1pe True IO!nverll!<f>artrillt Тnм, solnvertecl.andsclp,, Тnм, jWidth '3:Ю ;wnly Ни, t'!ЗF- . Desq,er mn_Fhone (Пoands) cxSyst8!'0efalt
|
|
|
|
|
|
11.shown |
|
Рис. 5.6. Настройка свойства FormFactor для формы iPhone
У формы имеется свойство, управляющее качеством
графического вывода:
property
Quality: TCanvasQuality;
По
умолчанию
свойство
установлено
в состояние ccsystemDefault, указывающее
на то, что
порядок
прорисовки определяется
текущими
системными
настройками.
Кроме этого,
предусмотрены еще
два варианта поведения: повышение производи-
тельности
за счет снижения
качества
вывода ccHighPerfonnance и высокое
качество
графики за
счет
ухудшения
производительности ccHighQuality.
Форма
НD (High Definition) во
многом
похожа на
обычную форму
VCL, поэтому
если вы имеете
хотя бы
небольшой
опыт программирования
в предыдущих
версиях Delphi, то быстро
разберетесь
со всеми
нововведениями. Ключевых
изменений
все го два.
Во-п_ервых, при позиционировании и
определении размеров форма НD
окончатель
но перешла на
вещественные
числа.
Во-вторых,
у формы FМХ. Fonns. TFoпn принципиально
изменен
порядок
работы с графикой. Теперь
в своей работе форма НD
по умолчанию
не задействует
механизм
графического вывода
Windows GDI. Вместо
этого новая
форма нацелена
на работу с DirectX
(если приложение
выполняется
под управлением
Windows) или Quartz
2D (для
приложения OS Х).
Доступ к
универсальному
графическому
механизму
пре
доставляет
интерфейс rscene. Вполне
естественно,
что смена
графического
"движ ка" привела к
кардинальным переменам в
недрах класса-холста тcanvas (см. главу 16).
ЗАМЕЧАНИЕ
Если по
каким-либо
причинам
работающее
под
управлением
Windows приложение
FireMonkey теряет
возможность
осуществлять
графический вывод под
управлением
DirectX, оно
переключае:гся
на библиотеку
GDI+.
Трехмерная форма FMX.FormsЗD. TFormЗD
Трехмерная форма FireMonkey - это без всякого преувеличения инновационный ,
класс, позволивший нам создавать управляемые ЗD-сцены несколькими щелчками
мыши. Код реализации трехмерной формы сосредощч н в рамках класса
тcustomFoпnЗD (модуля FМХ. FonnsЗD). Объявление класса
type TCustomFoпnЗD =
class(TCommonCustomFoпn, IContextObject, IViewportЗD)
содержит
очень важный
интерфейс rviewportЗD, несущий
ответственность
за пред
ставление
трехмерной
сцены
наблюдателю - позволяющий
управлять
освещени ем,
камерой
(точкой
наблюдения за
сценой),
рендерингом
модели и т. д.
ЗАМЕЧАНИЕ
Будьте
внимательны
при
переработке
своих старых
проектов. В Delphi
ХЕ2/ХЕЗ трех
мерная форма
описывалась
в модуле FМХ. Foпns, с
выходом ХЕ4
форма TFormЗD перекочевала в
модуль FМХ.
FonnsЗD.
ЗАМЕЧАНИЕ
На страничке
Viewports
палитрь1 компонентов Delphi
имеется
компонент тviewPortЗD. Это
область
просмотра,
способная
самостоятельно создавать
30-сцену и
решать все
вопросы,
связанные с
ее прорисовкой.
Благодаря тviewPortЗD вы
сможете
создавать
трехмерные
области на
поверхности
двухмерной
формы.
![]()
![]()
![]()
Форма TFormзo призвана работать в трехмерных прямоугольных координатах, нача ла которых находятся в центре клиентской области формы. Ось абсцисс х направ
]Jена из точки (О, О,
О) вправо, ось
ординат у - вниз, виртуальная ось аппликат z
уходит в глубину формы (рис. 5.7).
![]()
Рис. 5.7. Система координат TFormЗD
Системы координат ЗD-приложений FireMonkey делятся на два вида: глобальная и локальная. Глобальные координаты начинаются в центре сцены в точке (О, О, О), которая называется началом координат. Локальными называются координаты во круг области объекта, который мы разместим на сцене.
ВНИМАНИЕ!
Размещая
на
трехмерной
форме
ЗD-элементы
управления,
помнит.е, что
эти элемен
ты позиционируются не относительно своего левого верхнего угла (как это было в двухмерных проектах FireMonkey), а
относительно своего
центра (свойство
Position).
Еще одной важной особенностью ЗD-формы является то, что графический вывод осуществляется с помощью трехмерного контекста графического устройства FМХ. TypesЗD. тcontextЗD. Доступ к контексту предоставляет свойство
property Context: TContextЗD;
Процесс графического вывода сопровождается генерацией события
property OnRender:.
TRenderEvent;
type TRenderEvent =
procedure(Sender: TObject; Context: TContextЗD) of object;
В числе параметров события вы вновь обнаружите контекст context.
![]()
![]()
![]()
![]()
ВНИМАНИЕ/
Для
построения
трехмерных
сцен вместо
прямого
обращения к
контексту тcontextЗD проще
воспользоваться
услугами компонентов 3D
(страницы палитры
компонентов ЗD Scene, ЗD
Shapes и ЗD Layers).
В простейшем случае цвет заливки формы назначается свойством
property Color: TAlphaColor;
Для просмотра трехмерных сцен форма инкапсулирует камеру (объект тcamera). Камера - это не что иное, как удобная абстракция наблюдателя. Вь брав ту или иную позицию наблюдения, вы сможете взглянуть на одну и ту же сцену с разных ракурсов. Интегрированная в состав формы камера установлена прямо перед сце ной. По умолчанию камера включена, для ее отключения установите в false свой ство
property UsingDesignCamera: Boolean;//пo умолчанию true
ЗАМЕЧАНИЕ
Если вас не устраивает
камера по умолчанию,
то обратитесь
к странице
палитры ком
понентов ЗD Scene. Здесь вы
найдете
самостоятельный
компонент тcamera и ряд
других
компонентов,
предназначенных
для дизайна
трехмерных
сцен (см. главу 27).
От состояния свойства
property Мultisample: ТМultisample; //по умолчанию Multisample.rns4Samples TMultisample = (rnsNone, rns2Samples, ms4Samples);
зависит порядок устранения неприятного ступенчатого эффекта (antialiasing), воз никающего у накл >Нных линий и поверхностей. По умолчанию у формы установ лен режим rns4Samples, возвращающий наилучшую результирующую картинку. Однако если вместо показателя качества рендеринга вашему приложению важнее производительность, то можно уменьшить (ms2Samples) или даже отключить (rnsNone) сглаживание.
Создайте новый проект ЗD. Для этого следует воспользоваться пунктом меню File 1 New I FireMonkey Desktop Application и в открывшемся диалоге FireMonkey Desktop Application выбрать 3D FireMonkey Application. Сохраните проект под любым именем и перейдите к главной форме приложения.
Сразу раскроем секрет нашего примера - мы создадим модель вымышленной пла нетарной системы. Для этого разместите на главной форме следующие компоненты (рис. 5.8):
□ сфера Sphere_sun: TSphere возьмет
на себя функции единственной
звезды, вокруг
которой станут двигаться планеты. Расположите сферу точно в
центре формы
(свойство Position= (О,
о, О)). Воспользовавшись свойствами wictth, нeight и oepth, придайте
светилу
достойные
размеры (например, 4, 4, 4);
□ для того чтобы наша звезда смогла светиться, нам понадобится источник света
![]()
Light:TLight, который вы найдете на странице 3D Sсепе. Поместите компонент
![]()
![]()
![]()
на форму и присоедините к
Spherel (врезультате его положение
Position совпа дет с
положением
"звезды").
Переключите
свойство LightTyp@ в
состояние ltPoint, это заставит
источник
света испускать свои "фотоны" во всех направле
ниях;
□ разместите на
форме несколько сфер-планет TSphere. В примере использованы
три
компонента, но
вашу
фантазию я
ограничивать
не собираюсь;
□ компонент таймер Timerl: TTimer возьмет на себя
управление процессом движе-
ния планет. Установите
интервал отсчета равным 100-300 мс.
Задача
планет - вращаться
по эллиптическим
орбитам
вокруг
звезды.
Чтобы вы не
утруждали
себя
поисками
уравнения
эллипса в
учебниках
геометрии
или астроно
мии, сразу
предложу
вашему вниманию
скромное
параметрическое выражение:
f х =acost,
LY = bsint,
где О t 21t.
Здесь а и Ь описывают большую и
малую полуоси эллипса.
![]()
Рис. 5.8. Макет формы 3D с планетарной системой
![]()
Дело
осталось за
малым - изучить
листинг 5.9, в
котором
содержится
исходный код программы, моде!'ирующей
планетарную систему
из звезды и
трех планет.
![]()
var Foпnl: TFoпnl; tl,t2,t3:single;
const аl=З; Ь1=4; //полуоси эллиптических орбит а2=4; Ь2=8;
аЗ=lО; Ь3=5;
implementation
{$R *.frnx}
proaedure TFoпnl.FoпnЗDCreate(Sender: TObject);
Ьegin
tl:=0; //инициализация исходных значений
t2:=pi/2;
tЗ:=pi/4;
end;
proaedure TFoпnl.TimerlTimer(Sender: TObject);
Ьegin
Sphere2.Position.X:=al*cos(tl); //орбита планеты 1 Sphere2.Position.Y:=Ы*sin(tl);
Sphere3.Position.Z:=a2*cos(t2); //орбита планеты 2 Sphere3.Position.Y:=b2*sin(t2);
Sphere4.Position.X:=a3*cos(t3); //орбита планеты З Sphere4.Position.Z:=ЬЗ*sin(t3);
//приращения определяют скорость движения по орбитам tl:=tl\0.1; t2:=t2+0.15; tЗ:=tЗ-0.25;
end;
Основные расчеты
осуществляются
в рамках
события onтimer () . Самостоятельно
подберите
интервал
срабатывания
таймера,
чтобы
получить
реалистичную
картину
перемещения
планет.
Теперь проявим заботу о
внешнем виде
планет и звезды (компонентов TSphere). С помощью MaterialSource трехмерные компоненты Delphi могут подключаться
к
специализированным
невизуальным
компонентам,
способным
служить источни
ками данных ДЛЯ заливки их видимой поверхности. Источники заливки
вы обнару
жите на
вкладке Materials
палитры
компонентов
FireMonkey, в их число
входят: способный управлять цвето заливки компонент TColorMaterialSource; текстура
sшлиRки 'Г'ГF>xtлrF>MaterialSource; компонент
TlightMaterialSource, отвечающий
за отражение
света от поверхности
объекта (см. главу 29).
Редактор
структуры Structure (который
на рис. 5.9
запечатлен
поверх
главного ок
на нашего приложения)
раскрывает
суть подхода
к визуальному
проектированию.
Для создания
фона
звездного
неба мы
задействовали
источник
текстурной
заливки TextureMaterialSourcel, занесли в
его свойство тexture файл с
подходящей
фото графией
и подключили
компонент к
заднему
плану нашей
сцены Planel.
Для
ими тации звезды, находящейся в
центре планетарной системы, мы присоединили
к свойству MaterialSource сферы Sphere_Sun
компонент,
отвечающий
за текстуру,
взаимодействующую
со светом LightMaterialSourcel, в которую
также была
загру жена
подходящая
текстура.
Точно так же
можно
поступить и с
другими
сферами
планетами и
текстовыми надписями.
Совместное применение
2D- и ЗD-компонентов
Клиентская область формы TFoпnЗD нацелена на работу с трехмерными объектами (потомками класса тcontrolЗD). Если.программист попробует расположить на по верхности формы любой двухмерный элемент управления, то он не встретит осо бых возражений, но и элемента управления форма не отобразит. Причина такой избирательности проста - двухмерный компонент не в состоянии объяснить ЗD форме ни места своего размещения, ни способа прорисовки.
Для решения указанной проблемы обычно применяют компонент TLayerЗD, это специалист по совмещению несовместимого - 2D-объектов и трехмерной формы. Способ решения проблемы очень прост. С одной стороны, компонент TLayerЗD по нятен трехмерной форме, ведь он опирается на фундамент родного для ЗD-проектов класса тcontrolЗD и легко позиционируется в трехмерной системе ко ординат формы TFoпnЗD. С другой стороны, клиентская область компонента TLayerЗD имитирует привычную для 2D-компонентов плоскую двухмерную систему координат. Начала координатных осей (О, О) находятся в левом верхнем углу ком понента, ось х направлена слева направо, а ось у - сверху вниз. Подобный подход устраивает кнопки, строки ввода, метки, списки и другие двухмерные визуальные компоненты со страниц палитры FireMonkey.
Один из примеров совместного применения 2D- и ЗD-визуальных элементов управ ления предложен на рис. 5.9. Компонент LayerЗDl: TLayerЗD со свойством Projection, установленным в состояние pj Screen, предлагает свою поверхность в распоряж ние классических двухмерных элементов управления.
Обратите внимание на еще одну особенность проекта с рис. 5.9. При размещении ЗD-компонентов, отвечающих за обслуживание модели биплана времен Первой мировой войны, мы отказались от 'Прямых услуг формы. Вместо этого оставшаяся свободной клиентская область окна бьmа передана в распоряжение второго компо нента Layer3D2: TLayerЗD. На Layer3D2, в свою очередь, расположилась область просмотра ViewPortЗDl: TViewPortЗD. В результате функционал формы (связанный с размещением и рендерингом трехмерных моделей) перешел к области просмотра
ViewPortЗDl.
![]()
![]()
![]()
!/11/ffl1/l1fl,'l!//ll lllllfllll,'lllilflllffll/l/lllffl/l!lfff//lll,,,,..,ЮD,,.,и,,,,,,,....._...,m,.;""N,ш•aw1•,,.,,,.;..,..,.,,..,_.........."...".".'""'w•-1',1, -·-·---·-----·--·-·-·--·-·---·-·--·-·----·---·-·-----
Рис. 5.9. Форма TFormЗD с двухмерными и трехмерными компонентами
Стили
оформления
формы,
компонент TStyleBook
На странице Standard палитры компонентов FМХ находится компонент тstyleBook, позволяющий осуществить индивидуальную настройку стиля оформле ния формы. Двойной щелчо'к по компоненту или обращение к свойству
property Resource:
TStrings;
активирует
редактор
стиля. В
самом простейшем
случае можно
воспользоваться уже
подготовленными
стилями
оформления. Для
этого
достаточно в
редакторе
щелкнуть по
кнопке Load... и загрузить
один из
файлов с
расширением
имени style из каталога
C:\Users\PuЬlic\Documents\RAD Studio\n.n\Styles,
а затем
нажать
кнопку Apply and Close. В итоге
редактор
закроется, и изменения
стиля
применятся к ком
поненту TStyleBook.
ЗАМЕЧАНИЕ
Вместе с Delphi поставляется
несколько
заранее
подготовленных
стилей
оформления формы.
По умолчанию
файлы стилей
расположены
в каталоге
C:\Users\PuЫic\Documents\ RAD Studio\n.n\Styles.
При желании вы можете внести правки в правила оформления того или иного типа элемента управления, воспользовавшись утилитой Bitmap Style Designer, доступ к которой вы найдете в меню Tools среды проектирования. ·
Теперь следует подключить компонент стилей тstyleBook к форме. Для этого·дос таточно обратиться к свойству формы
property StyleBook: TStyleBook;
У компонента 'PStyleBook предусмотрено свойство
property
FileName: string;
которое
позволяет
легко
изменять
стилевое
оформление
формы даже во
время ра
боты приложения
под управлением Windows,
для этого в
него следует передать
имя файла *.style. Здесь
нам
понадобится
лишь помощь
диалога
открытия
файла OpenDialogl: TOpenDialog и кнопки, по
щелчку
которой этот
диалог будет
вызван
(листинг 5.10).
![]()
uses System.IOUtils;
// ...
procedure
TForml.FormCreate(Sender: TObject);
const dir='C:\Users\PuЬlic\Documents\'RAD Studio\11.0\Styles';
Ьegin
Forml.StyleBook:=TStyleBook.Create(Forml);
if TDirectory.Exists(dir) then OpenDialogl.InitialDir:=dir;
end;
prooedure TForml.ButtonlClick(Sender: TObject);
Ьegin
if OpenDialogl.Execute then Ьegin
Forml.StyleBook.FileName:=OpenDialogl.FileName;
Label2.Text:=TPath.GetFileName(OpenDialogl.FileName); end;
end;
ЗАМЕЧАНИЕ
В одном
и том же
приложении FireMonkey допускается
раздельная настройка стилей
оформления
для каждой из
форм проекта.
Подключение ресурсов и изображений
Библиотека
FireMonkey
предоставляет
программисту
уникальные
возможности
по
проектированию
графического
интерфейса
пользователя,
однако на
взгляд
автора ей все
еще есть чему
поучиться у VCL. В частности,
в VCL
имеется
очень
удобный
компонент тrrnageList, способный
выступать
централизованным
хранилищем
для всех используемых
в проекте пиктограмм.
В FireMonkey
пока аналогичного
компо нента
нет... Очень
жаль, но и это
не беда.
Если
в нашем
приложении
мы
намереваемся
использовать
большое
количество
изображений,
предназначающихся
для
повышения дружелюбности
интерфейса
пользователя, то стоит воспользоваться услугами редактора ресурсов проекта.
С этой целью выбираем пункт меню Project I Resources and Images, затем в по-
![]()
явившемся на экране редакторе подключаем к приложению все необходимые кар тинки и нажимаем кнопку ОК. В результате присоединенные пиктограммы немед ленно отобразятся в дереве Инспектора проекта (рис. 5.10).
, · а -l;-jj
-• t5 ! ··(-,_h.,; Тt Ъэ 1 il•it;!4• с. Rle •:@ Projec!Groupl i В i) ch19_01.exe · (fJ4 Вuik! Configurations (PeЬug) $ С Target Platforms (Win32) 6t E'J bltmaps 1111 фJm'!v,l'fl9 @mov.png ri)mpз.png Щ§mp4.png @wav.png @wma.png @v.mv.png ffiifjJ aЬout.pas _, ffi ifj} Unitl,pas , , ,._, , ,_,_,_,_, ---,-·--·- 01\_Вooks\_Areмonkey XESВII h , 1 ii?i'Qch19_01.d;i"oJ •ProJ... ф Reюurces for ch19_01.dP")j Resource !les Filename -..:::::.J png png @mpЗ.png @mp4.png Туре !dentifler . дV! m+, j! mov mрЗ . mp4 1 ! в.--е j 1 ок ) 1 Pr Resource igent!fier: -- AVI Resource typ,.!: RCDATA с.1С111 1 1 ... RCDATд RСDдТд RCDATA RCOATA .1 RCDATA 1 lldd-.. 1
ch19_01.dproj - Project
Maмger
'
![]()
--
![]()
![]()
![]()
![]()
![]()
Рис. 5.10.
Менеджер проектов и окно подключения ресурсов к
приложениtq
Теперь нам предсто т вновь попрактиковаться в программировании и научиться извлекать картинки из ресурса во время работы приложения (листинг 5.11).
••••••••••••••.. ••••••••••••••••••••••••••.. ••••......., ·•¼'"'<·- ,''"""" •··•', ''-•-••00,0,,,.,.,.._,.'ff.-- .. •••••••= ••-•- ''- •·--•••..•..•••••••·.. •••••••••••••••••••,,•,. •••••••••••••••,-•••••••••.. •••••••!.!• ....•••••..
•••••••,,.,,..,.•.,...... _...,.,,,•••
-1.... ..............
!nиc... 1.11.-........,.s111111-•p11•••.....,1 А ,
........,.. .............,., ·· ...- ••••••••••• .......................,..-····'"•··.........................,.. .......................................... ..............................................................,. .......\о... '"4 .-.,.-...
vars_ravi, rs_mpЗ, ... : TResourceStream;
procedure
TForml.FormCreate(Sender: TObject);
Ьegil'l
if FindResource(0, 'AVI', PChar(RT_RCDATA)) <> О then rs_avi:=TResourceStieam.Create(0, 'AVI',PChar(RT_RCDATA));
if FindResource(0, 'mрЗ', PChar(RT_RCDATA)) <> О then
rs_mpЗ:=TResourceStream.Create(0, 'mpЗ',PChar(RT_RCDATA));
// ...
end;
В коде примера мы создаем глобальные (досrупные всем модулям приложсttю1) потоки-ресурсы TResourceStream и загружаем в них изображения. В тот момент, когда нам потребуется вывести пиктограмму на экран или передать ее в какой-то элемент управления, достаточно просто считать ресурс из потока (листинг 5.12).
,......•.. •J.•· ',r,:J"-••••••.............................................. H• ••••••••••••..
., ,,,,r'/l'ff-Hlf,,lfUlhilH//ClcRll;l./iU:i..,,;i................................... 1,f;-,,,-h.t•;-,.,_:,,;,,i;,;,,·,,·, •-••••••.. •;••·•••>">i,;
Irnagel.Bitrnap.LoadFromStrearn(rs_avi); Irnage2.Bitrnap.LoadFromStrearn(rs_mp3);
ЗАМЕЧАНИЕ
При
подключении
изображения
(или другого объекта)
к ресурсу
проекта
старайтесь
присваивать
ему
осмысленный
текстовый
идентификатор,
такой подход
существенно
облегчит
написание
кода
программы. ·
ГЛАВА 6
Без всякого ттреувеличения можно сказать, что меню приложения выступает наибо лее важной частью пользовательского интерфейса современного делового прило жения. По сравнению с большинством элементов управления, меню представляет одно из наиболее удачных дизайнерских решений програм истов - его достоин ство заключается в том, что, практически не занимая места на форме, меню спо собно предоставить в распоряжение пользователя весь функционал программы.
В FireMonkey различают три класса меню:
□ главное меню, создаваемое на основе компонента тмainМenu;
□ планка
меню (тмenuBar);
□ контекстное меню (тPopupMenu).
Если речь идет о приложении Windows, то реализованное на основе класса TMainМenu главное меню размещается сразу под заголовком формы и, как правило, на самом верхнем уровне содержит пункты Файл, Правка, Окно и Помощь. При выборе пункта верхнего уровня из него "выпадают" связанные с ним элементы нижнего уровня. В OS Х поведение главного меню несколько изменяется - глав ное меню активного приложения сливается с меню рабочего стола операционной системы, а название первого пункта меню замещается названием исполняемого файла (рис. 6.1).
![]()
Рис. 6.1. Поведение главного меню в OS Х
Планка тмеnuваr отличается от главного меню универсальностью и может быть размещена в любом месте формы. При желании планка меню может взять на себя функционал главного меню приложения, что в приложениях OS Х позволит созда-
![]()
![]()
Рис. 6.2. Поведение планки меню TMenuBar в OS
Х
вать интерфейс,
схожий с традиционным
интерфейсом
Windows, в котором главное
меню
располагается
сразу под заголовком
окна (рис. 6.2).
В отличие от главного
меню и планки меню, контекстное
меню возникает на экране
только после
щелчка
правой
кнопки мыши
по связанному
с ним
элементу
управле ния.
Программисты
стараются не
перегружать
этот тип меню
избыточными
пунк тами и заполняют
его операциями,
относящимися
именно к
конкретному
элементу
управления,
которому
принадлежит
это меню.
Несмотря
на то, что для
создания
меню написано
целых три
класса, у них
много общего:
□
все компоненты меню предназначены для хранения управляющих элементов,
создаваемых на основе класса тмenuitem;
□ для контроля над элементами в
распоряжении всех компонентов меню преду
смотрены
интерфейсы IIternsContainer и INativeControl, интерфейсы
исполь
зуются компонентом самостоятельно и
не требуют вмешательства со стороны программиста.
Компоненты
меню
снабжены
специализированным
редактором Items Designer, значительно
упрощающим
процесс
построения
меню. Для
вызова
редактора
меню
достаточно
дважды
щелкнуть
левой кнопкой
мыши по
компоненту
меню (рис. 6.3) или
выбрать
пункт Items
Editor в
контекстном
меню
компонента.
Для создания
пункта меню следует
нажать
кнопку Add
Item и настроить
параметры
появившего
ся элемента
меню в Инспекторе
объектов.
Элементы
меню способны
обладать под
меню, для создания
которого
достаточно
воспользоваться кнопкой
Add Child
Item. При
необходимости
можно
переупорядочить
пункты меню - для
этого
предназна
чены кнопки с
изображениями
стрелок. Для
удаления
лишнего
пункта
просто на
жмите кнопку Delete.
![]()
120 Глава 6
Рис. 6.3. Редактор меню ltems Designer
Вне зависимости о
того, какой из компонентов меню (тмainМenu, тмеnuВаr или
TPopupMenu) вы
задействуете в
своем приложении, в
любом случае меню будет
состоять из
элементов,
основанных на
классе тмenuitem. Поэтому рассмотрение
по рядка
работы с меню
мы начнем с
изучения их
основного
"строительного"
блока -
класса TMenuitem.
ВНИМАНИЕ!
Хотя
элемент меню тмenuitem в состоянии
самостоятельно обрабатывать
событие
щелчок, в
наших проектах
целесообразно
подключать к
элементу
меню команду TAction. Для этих целей
элемент меню вооружен
свойством Action. Более
подробно о командах
мы поговорим
в главе 7.
Как и большинство уже изученных элементов управления, пункт меню обладает заголовком, определяемым свойством
property Text: string;
Но по сравнению со всеми остальными компонентами Delphi, у свойства техt эле
!!!' !!!9 сть }'!Цl;J TB1;JHHЫ1;J особенности. Во-первых, если R ягrнтп1ю11" f>уд,,,., введен один-единственный символ "тире" (-), то пункт меню превр тится в разде литель. Обратите внимание на рис. 6.3. Здесь между элементами Вставить из буферй... и ВыбрйТL всё вставлена горизонтальная черточка. Это и есть пункт ме-
![]()
![]()
![]()
ню "разделитель". Он не способен нести функциональную нагрузку и реагировать па Щ!Ш'ЮК пользователя, его з дача - улучшение наглядности приложения. Втор я особенность свойства техt - возможность определения клавиш-акселераторов, ускоряющих доступ пользователя к пункту меню при одновременном нажатии
<Alt> и клавиши-акселератора. На экране компьютера символ, со тветствующий клавише-акселератору, выводится с подчеркиванием. Для назначения акселератора во время набора заголовка меню необходимо воспользоваться символом &. Ампер санд устанавливается перед символом, который программист предполагает сделать акселератором: "&Файл" или "&Открыть".
ЗАМЕЧАНИЕ
Если на
роль
главного
меню
приложения был
выбран
компонент TMenuBar, то
клави
ши-акселераторы
использовать не
следует.
•
Помимо
акселераторов,
с каждым
пунктом меню
можно
связать так
называемые
"быстрые 1rnавиши".
Отличие
быстрых
клавиш от
клавиш-акселераторов
заклю а
ется в том;
что выбор
акселератора
только
позволит
добраться до
необходимого
пункта меню, а
нажатие
комбинации
быстрых
клавиш заставит
приложение
выпол нить
сопоставленный
с ними пункт
меню. Быстрые
клавиши
определяются
свой ством
property ShortCut : TShortCut;
С каждым элементом меню могут быть сопоставлены небольшие картинки, даю щие визуальное пояснение о функциональном назначении того или иного элемента.
Картинка загружается в свойство
property Bi trnap: TBitrnap;
Оформление
текстовой
надписи
опирается на
свойства и
методы,
унаследованные
от класса ттextControl.
Элемент меню может быть выделен программным способом или вручную, инфор мация об этом хранится в свойстве
"property IsSelected:
Boolean;
Основное назначение пункта меню - среагировать на щелчок пользователя, в этом он практически ничем не отличается от обычных кнопок. Соответственно ничем не отличается и ключевой для элемента тмenurtem обработчик события
property
OnClick: TNotifyEvent;
При необходимости отдельный пункт меню можно превратить в элемент управле ния, напоминающий флажок (тсhесkВох) или кнопку выбора (тRadioButton). Таким чудесным возможностям тмenurtem в первую очередь обязан свойству
property IsChecked: Boolean;
Установив это поле в состояние true, мы увидим "галочку" слева от заголовка. Этим самым пункт меню сигнализирует нам, что он отмечен. Контролируя это
свойство (листинг 6.1), программист получит превосходную возможность приме
нять
конструкцию if .. then.. else в
обработчике события onclick
() .
![]()
procedure TFoпnl.MenuitemlClick(Sender: TObject);
Ьegin
if Menuiteml.IsChecked=true then
else
//операция 1
//операция 2
end;
Для
того чтобы
пункт меню
при щелчке по
нему
автоматически
помечался
галоч кой,
следует
перевести в true свойство:
property AutoCheck:
Boolean;
в противном случае придется делать это вручную внутри события OnClick () . В про-
•
стейшем случае это будет всего-навсего одна строка кода
Menuiteml.IsChecked:= NОТ Menuiteml.IsChecked;
инвертирующая предыдущее состояние пункта
меню.
Вторым
действием по превращению
элементов
меню в переключатели
станет уста
новка (у всех
входящих в
группу
элементов)
свойства
property Radioitem: Boolean;//пo умолчанию false
в состояние true.
Несколько элементов
меню не сложно объединить в группу, превратив ее элементы в,переключатели
(по поведению
повторяющие кнопки
TRadioButton).
Для тбfб чтобы научиться превращать несколько пунктов меню в
группу выбора,
расс отрим
крохотный пример
(рис. 6.4).
Создайте новый проект,
разместите на нем
компонент тмainМenu. Создайте
пункт меню
верхнего
уровня с
заголовком
"Цвет формы"
и четыре
подчиненных
ему пункта:
□
элемент с
заголовком "Белый". Имя элемента - miWhite, свойство tag=0;
О элемент "Красный", tag=l;
□
элемент "Зелёный", tag=2;
□ элемент "Синий", tag=З.
Наша задача - при щелчке по пункту меню перекрашивать фигуру Rectanglel:
тRectan9ie в соответствующий цвет. Теперь одновременно выделите все четыре
только что создаппых пупкта меню и
в Инспекторе объектов найдите свойство
propert.y Groupindex: Byte;
![]()
![]()
|
Красный шj!ныi\ С11н11А ,/ Бсп111А |
|
|
□ |
|
Рис.
6.4. Группа выбора из элементов меню
Это свойство предназначено для создания логических групп пунктов меню. По умолчанию, каждый вновь создаваемый элемент тмenuitem не входит ни в одну группу (Groupindex=0), но если атрибуту Groupindex двух (или более) пунктов меню присвоить отличное от нуля значение, то мы получим возможность объединять элементы в некоторые товарищества, что сейчас собственно и сделаем. Введите в это свойство любое положительное число, например 1.
Следующим шагом по превращению четырех пунктов меню в группу выбора будет установка в true свойства:
property Radioitem:
Boolean;
Перевод свойства
Radiortem в
значение true приведет к тому,
что пункт меню
ста нет
вести себя
аналогично
компоненту TRadioвutton. Для
того чтобы
выяснить, не
отмечен ли
элемент меню
флажком, надо
обратиться к уже
знакомому
нам свойст
ву IsChecked, а Delphi
позаботится
о том, чтобы в
одной группе
не могло быть
по мечено
более одного
элемента тмenuitem одновременно.
И в
завершении
убедитесь, что
свойство AutoCheck всех
четырех
элементов
установлено в true.
Нам осталось описать щелчок по любому из пунктов меню, например по miWhite (листинг 6.2), и сделать это событие общим для всех оставшихся пунктов меню, входящих в группу выбора.
![]()
procedure TForml.miWhiteClick(Sender: TObject);
Ьegin
case TMenuitem(Sender) .Tag of 0:Rectanglel.Fill.Color:=TAlphaColorRec.White; //белый 1:RectangleJ.Fill.Color:=TAlphaColorRec.Red; //красный 2:Rectanglel.Fill.Color:=TAlphaColorRec.Green; //зеленый
3:Rectanglel.Fill.Color:=TAlphaColorRec.Blue; //синий
end; end;
Доступ к
дочерним элем нтам м'еню
Любой элемент меню имеет возможность обладать дочерними элементами. Для программного доступа к этим элементам следует восподьзоваться свойством
property Children[Index: Integer]: TFrnxObject; //только для чтения
единственный параметр метода определяет индекс интересующего нас пункта
меню (в
диапазоне значений от О до ChildrenCount-1).
ВНИМАНИЕ!
Все
рассматриваемые
в главе
компоненты также
способны
обратиться к
дочерним элементам меню с
помощью
свойства Children.
Главное меню приложения класса тмainМenu специализируется на хранении и пре доставлении доступа к элементарным пунктам меню (реализуемым из класса
TMenuitern).
Обычно меню формируется программистом на этапе визуального проектирования, однако никто не запрещает нам создавать пункты меню в динамическом режиме.
В последнем случае из кода программы следует вьIЗвать метод
procedure AddObject(AObject: TFrnxObject);
В библиотеке FireMonkey планка меню тмеnuваr выступает своего рода усиленной версией главного (и не только главного) меню приложения. Кточевое отличие планки меню тмеnuВаr от его коллеги - компонента TMainМenu в том, что планка может быть размещена в любом месте формы, она может перемещаться и менять • размеры. Главное меню TMainМenu такими возможностями похвастаться не в со стоянии.
ЗАМЕЧАНИЕ
В Delphi
ХЕ2/ХЕЗ планка
меню была
способна
легко
принять "обличие"
главного
меню
приложения с
учетом
особенностей
поведения и
отображения меню
текущей
опера
ционной
системы, для
этого
требовалось
передать
значение true в
свойство UseOSMenu. Начиная с Delphi ХЕ4, у
TMenuBar свойство UseOSMenu исчезло...
Существенная
часть
построенных
на основе
класса тcontrol визуальных
элементов
управления
обладает
правом
показа
контекстного
(всплывающего) меню.
Для этого
элементы
оснащены
свойством,
предназначенным
для
подключения к ним компо
нента TPopupMQnu
property PopupMenu: TCustornPopupMenu;
![]()
![]()
![]()
После подключения контекстное меню автомати'Iески вызываете» на :жр11u в МО•
менr щелчка правой кнопкой мыши по элементу управления.
Если у элемента управления не предусмотрено свойство PopupMenu, то это не явля ется препятствием для работы с компонентом TPopupмenu. В данном случае про граммисту следует взять управление процессом вызова меню в свои руки и вос пользоваться методом
procedure Popup(X, У: Single);
Единственное, о чем следует позаботиться - это указать экранные координаты (х, У) вывода меню (листинг 6.3).
![]()
![]()
procedure TFoпnl.FoШIМouseDown(Sender: TObject; Button:
TMouseButton; Shift: TShiftState; Х, У: Single);
var P:TPointF;
Ьegin
if Button=TMouseButton.mЬRight then
Ьegin
P:=Foпnl.ClientToScreen(PointF(X,Y));
PopupМenul.Popup(P.X,P.Y);
end;
end;
Наличие метода, отображающего контекстное меню, предполагает и существование метода-антипода
procedure CloseMenu;
закрывающего меню.
![]()
ГЛАВА 7
Пользовательский интерфейс хорошо продуманного приложения позволит пользо вателю компьютера прийти к одному и тому же результату абсолютно разными пу тями. Например, для сохранения проекта Delphi программист может остановить
свой выбор, по крайней мере, на одном из трех возможных вариантов действий: выбрать пункт главного меню File I Save АН; щелкнуть по соответствующей кнопке на панели инструментов; нажать сопоставленную операции сохранения проекта комбинацию быстрых клавиш <Shift>+<Ctrl>+<Alt>. Наличие трех счособов со хранения проекта не означает, что создатели ШЕ Delphi во время написания среды проектирования трижды повторили одни и те же строки кода. Такое решение было
бы нелепым. На самом деле для сохранения • проекта была создана одна единственная процедура, вызов которой осуществляется несколькими способами. В простейшем случае программистам Embarcadero достаточно было описать щел чок по пункту меню и затем подключить к этому событию все остальные элементы управления.
Обратите внимание на еще одну особенность поведения элементов управления ви зуальной среды проектирования Delphi. После полного сохранения проекта отве ЧilЮЩИ:Й 'Ш :пу_операциюэлемент меню и кноuка элементов управления псрсспuот быть активными - это свидетельствует о том, что все изменения в исходном коде успешно отправлены на жесткий диск компьютера. Но как только программист внесет малейшее исправление в листинг программы, соответствующие элементы управления вновь перейдут в режим готовности выполнить свою задачу - сохра нить изменения в коде.
Элементы
пользовательского
интерфейса современного
приложения
должны не просто
обладать
способностью вызывать
ту или иную
процедуру, но и
уметь отра жать текущую обстановку.
Для достижения
поставленной
цели в состав FireMonkey были
введены
командные
объекты. В
настоящей
главе мы
познакомимся
с наибо лее
распространенной
реализацией команды
- классом
TAction.
Командный объект TAction создается и хранится в специализированном контейне ре, в роли которого имеет право выступать список команд TActionList. Список команд TActionList обычно задействуется в приложениях, интерфейс которых
![]()
![]()
![]()
![]()
![]()
строится на основе
обычных
компонентов
меню (тмainМenu и TPopupMenu) и стан
дартных
элементах
управления
(кнопок твutton).
Наиболее востребованный тип командного объекта создается на основе класса TAction. Кроме решения основной задачи, поставленной перед командой (открытие файла, сохранение данных, вызов метода и т. п.), команда выполняет ряд дополни тельных заданий:
□ централизация управляющего кода в
рамках события OnExecute ();
□ предоставление связанным с командой элементам управления права вызова управляющего кода;
□ установка подключенных к команде элементов управления в актуальное состоя- ние.
Для того чтобы сразу ощутить все преимущества, которые приобретает приложе ние, разработанное на основе командных объектов, рассмотрим небольшой пример НD. Создайте новый проект и на его главной форме расположите следующие эле менты управления:
□ список команд TActionList;
□ главное меню TMainМenu;
□ контекстное меню TPopupMenu;
□ три кнопки любого типа, например твutton;
□ многострочный редактор тмеmо.
Наша задача- написать код, позволяющий копировать (или вырезать) текст из многострочного редактора в буфер обмена и вставлять текст из буфера в редактор.
Дважды щелкните по менеджеру команд и
трижды нажмите кнопку New Action
в появившемся редакторе. Настройте свойства тройки новых команд:
□ Name=' асСору'; Caption=' Копировать'; ShortCut=' Ctrl+С';
□ Name='acCut'; Сарtiоn='Вырезать';
ShortCut='Ctrl+x•;
□ Name,=' acPaste'; Caption= 'Вставить'; ShortCut=' Ctrl+V'.
Мы подошли к самому главному - опишем управляющий код в событиях
OnExecute () каждой из команд (листинг 7.1).
![]()
![]()
......."' ................,,..................,.. ...............· .......
........... ··". ,i.
.................--...............................,................... ,
procedure
TForml.acCopyExecute(Sender: TObject);
Ьegin
Memol.CopyТoClipboard; //копируем текст в
буфер end;
procedure
TForml.acCutExecute(Sender: TObject);
Ьegin
Memol.CutToClipboard; //вырезаем текст в буфер
end;
procedure
TForml.acPasteExecute(Sender: TObject);
Ьegin
Memol.PasteFromClipboard; //забираем текст из буфера
end;
Создайте три пустых элемента в главном и всплывающем меню проекта и, восполь
зовавшись свойством Action, подключите пункты меню к командам. То же самое сделайте и с кнопками. Сразу после установления связи между командой и пунктом
меню с последним происходят чудесные· метаморфозы. Заголовок и всплывающая подсказка переключаются на показ соответствующих свойств ассоциированного командного объекта (рис. 7.1). Но самое главное в том, что с этого момента щелчок по пункту меню и кнопке станет вызывать метод OnExecute ( ) связанной с ними команды. Таким образом, вместо "размазывания" управляющего кода по многочис ленным элементам управления пользовательского интерфейса нашего приложения он сосредотачивается в руках командного объекта, а все подключенные к нему элементы управления получают право на вызов этого кода.
ЗАМЕЧАНИЕ
Командные
объекты могут быть
легко подключены
к большинству элементов
управле ния,
необходимым
условием
подключения
является
наличие у
элемента
управления
(пункта меню,
кнопки)
свойства Action.
![]()
Рис. 7.1. Список команд TActionList с командами
Нам осталось сделать цоследний важный штрих - научить наши команды оцени вать окружающую обстановку. Например, команды копирования и вырезки текста в буфер обмена не могут быть активны до .тех пор, пока в текстовом редакторе не выделено ни одного символа. Команда вставки текста из буфера обмена в поле ре дактирования должна активироваться только в том случае, если в буфере находится текстовая строка. Для установки командных объектов в актуал ное· состояние предназначены их события OnUpdate (листинг 7.2).
![]()
uses FМX.Platfoпn; //модуль для работы интерфейса IFМXClipboardService
/ / ...
procedure TFoпnl.acCopyUpdate(Sender: TObject);
Ьegin
acCopy.EnaЫed:=(Memol.SelLength>0);
end;
procedure TFoпnl.acCutUpdate(Sender: TObject);
Ьegin
acCut.EnaЬled:=(Memol.SelLength>0);
end;
procedure TFoпnl.acPasteUpdate(Sender: TObject); var ClipService: IFМXClipboardService;
s: string;
Ьegin
if TPlatfoпnServices.Current.SupportsPlatfoпnService(
IFМXClipboardService, IInterface(ClipService)) then
Ьegin s:=ClipService.GetClipboard.ToString;
acPaste.EnaЬled:=s.Length>0;
end else acPaste.EnaЫed:=false;
end;
Демонстрационный проект готов. Запустите приложение и проверьте поведение пунктов меню и кнопок.
Связь с
элементом управления
Теперь мы знаем, что любой командный объект умеет взаимодействовать со стан дартными элементами управления, берущими свое начало от класса тcontrol. В первую очередь к таким элементам относятся пункты меню тмenuitem и различ ного рода кнопки.
Внешним признаком того, что элемент управления не прочь связать свою судьбу с командным объектом, служит наличие свойства
property Action: TBasicAction;
Свойство
доступно в
Инспекторе
объектов, так
что для
подключения
элемента управления
к команде
программисту
достаточно
пару раз щелкнуть кнопкой мыши. Визуально
появление
связи
отразится на
свойствах техt, Checked,
EnaЫed, нelpContext, Hint, Shortcut и VisiЫe нашего
элемента
управления.
Содержимое
перечисленных
полей
обновится в
соответствии
со
значениями в
аналогичных
свойствах командного объекта TAction. Но самое главное, что с
этого момента эле
мент управления получит право на вызов метода Execute () команды. Как правило,
для этого автоматически переопределяется обработчик события
onclick () . Так что
теперь щелчок
по кнопке или пункту
меню вызовет <.0бытие
OnExecute () командно
го
объекта..
Выполнение команды
Ключевым методом любого командного объекта выступает функция
function Execute : Boolean;
Метод вызывается автоматически в момент щелчка .по элементу управления, свя занному с командой. Выполнение команды сопровождается каскадом весьма схо жих по функциональной нагрузке событий у имеющих отношение к командному объекту элементов управления (табл. 7.1).
Таблица 7.1. Генерирование событий при выполнении команды
|
Источник |
Событие |
Описание |
|
|
TActionList |
property OnExecute : TActionEvent; |
Событие
генерируется
у списка
ИЛL/! менед-
жера команд в
зависимости от того, кому
из |
|
|
|
type TActionEvent = procedure(Action: TBasicAction; |
них принадлежит команда. В первом
параметре Action окажется
ссыл- ка
на команду, чей метод Execute () сейчас |
|
|
|
var Handled: Boolean) of object; |
вызывается. Манипулируя параметром Handled, мы сможем
разрешить (false) или
запретить (true) дальнейшее выполнение |
|
|
|
|
команды |
|
|
TAction |
property OnExecute TNotifyEvent; |
: |
Событие произойдет,
если команда
не была
обработана в двух предыдущих
событиях |
Установка команды в
актуальное состояние
В перечне событий команды второе по значимости место занимает событие обнов ления статуса команды
property OnUpdate:
TNotifyEvent;
Благодаря OnUpdate () мы сможем установить команду (и,
конечно же, связанные
с ней элементы
управления)
в актуальное,
отвечающее текущей
обстановке
состоя ние.
Важно знать,
что событие
обновления
вызывается
именно в тот
момент,
когда
связанный с
командой элемент готовится к
выводу на экран. Благодаря этому
команда способна контролировать текущую обстановку с минимальным расходом системных ресурсов, такое решение называется обновлением по требованию (update-on-demand approach).
Командный объект должен храниться в специальном компоненте-контейнере TActionLisiё. Для идентификации контейнера, которому принадлежит команда, предназначено свойство
property
ActionList:
TCustomActionList;
Индекс команды в списке контейнера находится в свойстве
property Index : Integer;
Исключительно для удобства программиста во время визуального проектирования команды могут группироваться по категориям. Название категории присваивается свойству
property Category: String;
Анализируя хранящиеся в этом свойстве текстовые данные, контейнер TActionList способен быстро отфильтровать команды определенной категории, тем самым зна чительно упростив поиск требуемой команды.
Чтобы командный интерфейс оказался еще более удобным, . разработчики FireMonkey предоставили в наше распоряжение целую коллекцию предопределен ных команд, благодаря применению которых существенно сокращается время и упрощается процесс разработки приложения. Для того чтобы быстро научиться за действовать предопределенные команды, предлагаем написать небольшой пример.
Создайте мобильное приложение для iPhone и разместите на рабочей форме сле дующие элементы управления:
□ список команд TActionList;
□ контейнер для изображений тrmage;
□ кнопку TBu ton.
Этих компонентов вполне достаточно для создания простейшего приложения, по зволяющего пользователю делать фотографии.
Выберите кнопку и в Инспекторе объектов найдите свойство Action. Р!13верните комбинированный список и, пройдя по цепочке New Standard Action 1 Media Library, выберите предопределенную команду TTakePhotoFromCameraAction (рис. 7.2).
В окне Инспектора объектов перейдите на вкладку Events и найдите событие
OnDidFinishTaking () для только что созданного командного объекта (рис. 7.3).
![]()
Рис. 7.2. Использование предопределенной команды TTakePhotoFrornCameraAction
Object lnspector
§(_Action
Uve!Jrdr,gs
Oc:Ca!1AdionExec a.odeance!Тalo:,ng
» OrOIA.t,T
te
iтшPhotofromC.t.-.Adiont "
UvelJrmgs
![]()
u
OnApplySt)'lei.ookц)
OnCмFows
Q1CSd(
OnDblCldc
0,0-фор
OIO'.gEt,d
-v.uly-...-
Рис. 7.3.
Выбор события
OnDidFinishTaking()
![]()
Tilldng
![]()
![]()
Alshown
![]()
Двойной
щелчок по
строке
события
перенесет
нас в
редактор
кода, в
котором нам
придется
написать
всего одну
строку
(листинг 7.3).
![]()
![]()
prooedure
TForrnl.TakePhotoFrornCameraActionlDidFinishTaking(Irnage: TBitrnap);
Ьegin
Irnagel.Bitrnap.Assign(Irnage);
end;
Как видите, за счет предопределенных командных объектов можно весьма уско рить процесс разработки приложений в Delphi.
Список команд TActionList - невизуальный элемент управления, который вы най дете на страничке Standard палитры компонентов. Компонент обычно используют при проектировании приложений малой степени сложности с незначительным чис лом командных объектов и построенных на основе обычных компонентов меню и стандартных кнопок. Все свойства, методы и обработчики событий списка команд унаследованы от родительского класса тcustomActionLis� и поэтому нам уже хоро шо знакомы.
По
умолчанию
список
команд Actions пуст.
Для
заполнения
списка
командами
дважды
щелкните по компоненту
левой
кнопкой мыши.
В результате
такого дейст
вия на экран компьютера выводится окно специализированного редактора (см. рис. 7.1). Для
добавления
команды TAction следует
вызвать
пункт New Action, а
для
добавления
стандартных
команд из заранее
подготовленного
перечня
выбираем пункт
New Standard
Action.... КомандьJ
могут
группироваться
по щ1ределяемым
программистом
категориям.
Выбор в
левом
списке
редактора
той или иной
катего рии отфильтрует
только
команды
указанной
группы.
При желании
можно
отказать ся
от
фильтрации команд,
для этого
в списке
категорий Categories надо
выбрать
строку АН Actions.
ЗАМЕЧАНИЕ
Список
команд TActionList предназначен
для
совместной
работы с обычными
меню (тмainМenu,
TMenuBar и TPopupMenu) и со стандартными
элементами
управления, на
пример
кнопками TButton.
Число хранимых в списке команд выясняется благодаря свойству
property ActionCount: Integer;
Доступ к коллекции команд обеспечивает свойство
property Actions[Index: Integer]: TContainedAction;
Обращение
к отдельной
команде
производится
по ее
индивидуальному
индексу
(начиная от О и
заканчивая ActionCount-1).
Список обладает некоторым перечнем прав по отношению к своим подопечным. Например, он способен отдавать приказ на выполнение принадлежащей ему команды.
function ExecuteAction(Action: TBasicAction): Boolean;
Выполнение метода приведет к генерации события OnExecute () . Функция вернет true в случае, если в рамках вызванного обработчика события команда была успешно отработана.
Ко всему прочему, список способен запретить всем принадлежащим ему командам реагировать на запрось\ пользователя. Для этого предназначено свойство
![]()
![]()
![]()
property State : TActionListState;//no умолчанию asNormal
type TActionListState = (asNormal, asSuspended, asSuspendec!EnaЫed);
В момент
создания
свойству
присваивается
значение asNormal. Это
нормальное
со стояние,
разрешающее
содержащимся
в контейнере
командам
реагировать
на дей ствия
пользователя.
В состоянии asSuspended и asSuspendedEnaЫed все
принадле
жащие
элементу
управления
команды
перестают
выполнять
запросы пользователя.
Разница
между
последними
двумя
состояниями
заключается
в
оказываемом влия
нии на
свойство EnaЫed компонентов TAction. Так
перевод TActionList в
состояние asSuspended или asSuspendec!EnaЫed просто запрещает компонентам
TAction выпол
нять запросы
пользователя.
В дополнение
к этому
состояние assuspendedEnaЫed устанавливает
все свойства
команд EnaЫed в true.
Ключевые события TActionList перечислены в табл. 7.2.
Таблица 7.2. Основные события TActionList
|
Событие |
Описание |
|
property OnExecute: TActionEvent; � TActionEvent =
prooedure (Action: TBasicAction; var Handled: Вoolean) of object; |
Событие
генерируется у сnиска
(или менеджера)
команд nри обращении к команде. Параметр
Action хранит ссылку на команду. Параметр Handled позво-
ляет
прервать
выполнение команды |
|
property OnChange: TNotifyEvent; |
Генерируется nри любом изменении в списке команд |
|
property
OnStateChange: TNotifyEvent; |
Вызывается сразу за событием
OnChange () , уведом-
ляет об изменении
состояния
менеджера |
|
property OnUpdate: TActionEvent; |
Событие для
централизованной установки всех
команд в
актуальное
состояние.
Генерируется во время
простоя приложения. В его рамках програм-
мист переводит
команды в состояние,
соответствую-
щее сложившейся
обстановке |
ГЛАВА 8
Одна из самых распространенных задач, с которой рано или поздно сталкивается разработчик программного обеспечения, - это управление файлами и каталогами компьютера. Основная трудность этой области программирования связана с высо кой вероятностью возникновения различного рода исключительных ситуаций (вы званных некорректным совместным использованием файлов и папок, ограничением свободного пространства, некорректным доступом к ресурсу, ошибками операций· чтения и записи и т. п.). Понимая, что для начинающего программиста задача рабо-. ты с файлами и папками может оказаться неразрешимой, создатели Delphi реализо вали очень удобный модуль System. routils. В нем расположились кроссплатфор менные классы TDirectory, TFile и TPath, специализирующиеся на управлении пап ками и файлами.
ЗАМЕЧАНИЕ
Основные
функции
кроссплатформенной среды разработки
Delphi, связанные
с управ
лением
файлами,
каталогами и
дисками, сосредоточены
в модуле
System. IOUtils (см.
приложение 3).
При изучении файловой системы компьютера с Windows первое, что нас может за интересовать, - это состав логических дисков на компьютере. Предусмотрено не сколько способов сбора этих сведений. Наиболее простой заключается в задейство вании возможностей интеллектуальной записи TDirectory. Среди многочисленных методов записи имеется функция класса
сlавв function GetLogicalDrives: TStringDynArray;
создающая динамический массив строк, заполненный именами дисков в формате:
<буква_диска;•: <симвс1л_разделитель>. Меньше десяти строк кода (листинг 8.1) по зволят передать названия дисков в комбинированный список.
![]()
![]()
![]()
![]()
![]()
uses System.IOUtils, System.Types;
prooedure
TForml.ButtonlClick(Sender: TObject);
var SDA:TStringDynArray;
i: integer;
Ьegin
ComЬoBoxl.Items.Clear;
SDA:=TDirectory.GetLogicalDrives;
for i:=0 to High(SDA) do ComЬoBoxl.Items.Add(SDA[i]);
end;
Полезные сведения о размере диска и его свободном пространстве возвращают функции
function
DiskSize(Drive: Byte): Int64; function DiskFree(Drive: Byte): Int64;
Параметр
Dri ve
требует
передачи
номера
накопителя.
Например: О - текущий,
1 - А:, 2 - В:, 3 - С: и т. д.'Если
метод не в
состоянии выяснить размер
диска, то он
возвратит -1.
Сбор сведений о
каталогах и
файлах
У
описанной в
модуле routils записи
TDirectory предусмотрен
ряд методов,
суще ственно
упрощающих
процесс
сбора
сведений об
имеющихся на
компьютере
фай дах и каталогах.
Наиболее
показательным
из них является
перегружаемая
функция GetDirectories (), способная
построить
список
подкаталогов,
принадлежащих
ката логу
Path.
class function
GetDirectories(const Path,
SearchPattern: string;
const SearchOption: TSearchOption): TStringDynArray;
Перегружаемая функция
class function GetFiles(const Path, SearchPattern: string;
const SearchOption: TSearchOption): TStringDynArray;
нацелена на поиск файлов.
Универсальная функция
class function
GetFileSystemEntries(const Path,
SearchPattern: string): TStringDynArray;
соберет
сведения как
о каталогах,
так и о файлах.
Все функции
обладают
идентич ным перечнем параметров:
Path указывает
путь к
родительскому
каталогу; searchPattern назначает
маску поиска;
SearchOption определяет
опции поиска (soTopDirectoryOnly- сбор
сведений
только в текущем
каталоге, soAllDirectories -
![]()
![]()
сбор
сведений во
всех
вложенных
каталогах). В
результате
выполнения
собранные. сведения
помещаются в
динамический
массив строк
(листинг 8.2).
![]()
var SDA:TStringDynArray; i: integer;
Ьegin
SDA:=TDirectory.GetFileSystemEntries('C:\', '*.*');
fori:=0 to High(SDA) do ListBoxl.Items.Add(SDA[i]);
end;
Проверка существования файла и
каталога
Для
того чтобы
сразу отсечь
достаточно
большой
объем ошибок,
перед
обращени ем к
файлу или каталогу всегда проверяйте факт их существования. При работе
с файлом
задействуем
метод •
class function Exists(const Path: string;
FollowLink:
Boolean = True):
Boolean;
принадлежащий TFile.
Для проверки наличия папки вспоминаем о
записи TDirectory и имеющейся в
ее арсенале
одноименной
функции
class function Exists(const Path: string;
FollowLink:
Boolean = True):
Boolean;
Функции возвращают true, если файл (каталог) действительно существует.
Для полноты
картины
упомянем имеющийся в
распоряжении TPath метод class function DriveExists(const Path: string): Boolean;
проверяющий факт существования диска с
именем Path.
Расположение системных каталогов
Дополнительные сведения
о размещении
каталогов сможет предоставить
специали
зирующая на обслуживании имен файлов и
папок запись TPath (модуль routils).
В арсенале TPath имеется метод
class function GetHomePath: string;
готовый поделиться сведениями о
папке с данными приложений.
Еще
одна тайна, которой владеет TPath, связана с расположением папки
для вре
менных
файлов
class function GetTempPath: string;
![]()
![]()
![]()
Путь к каталогу с документами откроет метод
class function GetDocumentsPath: string;
Упомянем
важное
понятие "текущий каталог"
- это
каталог, в
котором
произво
дятся текущие
операции
ввода/вывода.
Для работы с
текущим
каталогом
предна
значены
методы TDirectory:
class function GetCurrentDir: string;
class function
SetCurrentDir (const Dir: string): Boolean;
позволяющие
выяснить,
какой из
каталогов является
текущим, и
назначить
новый текущий
каталог.
Создание,удаление,копирование
и перемещение
В FireMonkey основным специалистом по созданию и удалению папок считается
TDirectory. Так, для создания новой папки следует вызвать метод
class prooedure
CreateDirectory(Path: string);
За процесс копирования папки отвечает процедура
class prooedure Copy(conвt SourceDirName, DestDirName: string);
Здесь SourceDirName - путь к копируемой папке, DestDirName местоположение по лучателя.
Близкий по духу метод
class prooedure Move(const
SourceDirName, DestDirName: string);
осуществит перенос папки в новое место.
Класс TDirectory обладает методом
class prooedure Delete(const Path: string;
const Recursive: Boolean);
решающим задачу удаления папки со всеми вложенными элементами. Для активи зации рекурсивного удаления надо не забыть установить в состояние true параметр
Recursive.
Когда речь заходит об управлении файлами, то на сцену выходит класс TFile. Ко пирование файла осуществляет метод
class proaedure Copy(const SourceFileName, DestFileName: string);
Перенос файла на новое место выполнит метод
class prooedure'Move(SourceFileName, DestFileName: string);
Для замены одного файла другим с одновременным созданием резервной копии задействуйте метод
class prooedure
Replace(const SourceFileName,
DestinationFileName, DestinationВackupFileName: string);
Наконец,
самое
разрушительное
оружие класса
TFile class procedure
Delete(const Path: string);
удалит файл.
Запись в
файл и чтение из файла
Создание нового файла средствами TFile - это уже многоходовая комбинация, которая может стартовать с вызова конструктора
class function
Create(const Path: string):
TFileStream;
создающего доступный для записи файловый поток TFileStream. После этого все остальные действия над файлом возлагаются на экземпляр TFileStream. Например, представленный в листинге 8.3 код демонстрирует процесс сохранения в файл с именем FileName координат левого верхнего угла главной формы приложения.
![]()
procedure TFoпnl.SaveToFile(FileName: String);
var fs:TFileStream;
Buf: integer;
Ьegin
if TFile.Exists(FileName) then TFile.Delete(FileName); f s:=TFile . Create (FileName) ;
try
Buf:=Application.MainFoпn.Left;
fs.Write(Buf,SizeOf(Buf)); Buf:=Application.MainFoпn.Top;
fs.Write(Buf,SizeOf(Buf)); finally
fs.Free;
end; end;
![]()
Обратная
задача,
связанная с
чтением данных
из файла,
также может
быть решена
непосредственно
силами
класса TFile и файлового
потока TFileStream так,
как это
предложено в
листинге 8.4.
procedure
TFoпnl.LoadFromFile(FileName: String);
var fs:TFileStream;
Buf:integer;
Ьegin
if TFile.Exists(FileName) then Ьegin
fs:=TFile.Open(FileName,TFileMode.fmOpen);
![]()
try
fs.Read(Buf,SizeOf(Buf));
end;
Application.MainForm.Left:=Buf;
fs.Read(Buf,SizeOf(Buf)); Application.Ma·inForm.Top:=Buf;
finally
fs.Free;
end; end;
![]()
На этот раз нам помог метод класса
class function Open(const
Path: string;
const Mode: TFileMode}: TFile&tream;
позволяющий загружать файл с
именем Path в поток TFil�St:t:eam.
Помимо
умения
осуществлять
операции
чтения и записи
при
посредничестве фай ловых потоков
класс TFile
способен
работать с
текстовыми и
бинарными
файлами
опираясь
только на свои
силы.
Например, для
чтения всего
содержимого
текстово го
файла проще
всего
обратиться к
функции
class function ReadAllLines(const Path: string): TStringDynArray;
Результат будет возвращен в динамический строковый массив в формате
TStringDynArray.
Наиболее универсальный метод чтения
class function
ReadAllBytes(const Path: string): TBytes;
загрузит любой файл в
массив байт твуtеs.
Для записи данных в файл проще всего воспользоваться методами
class prooedure
WriteAllLines(const Path: string;
const Contents:
TStringDynArray);
class procedure WriteAllBytes(const Path: string; const Bytes: TBytes};
первый
метод
нацелен на
работу
исключительно
с текстовыми
данными,
второй
всеяден и в
качестве
источника
данных
воспринимает
массив
байтов.
В современном
языке
программирования
Delphi основными
специалистами
по ра боте с атрибутами файла и папки считаются классы TFile, тoirectory и TPath.
У всех перечисленных классов предусмотрены одинаковые методы
class function GetAttributes(const Path: string;
FollowLink: Boolean
= True): TFileAttributes; class procedure SetAttributes(const Path: string;
![]()
const Attributes:
TFileAttributes);
![]()
![]()
![]()
![]()
Управление папками и
файлами 141
![]()
Стоит
особо
отметить, что
перечень
атрибутов TFileAttributes определяется
опе рационной системой и
различается у
Windows и OS Х.
Дата и
время создания файла и
каталога
Самый
простой
способ для
знакомства с
возрастом
файла заключается
в использо
вании
возможностей
класса TFile, а если
речь идет о
каталогах, то нашим
помощ ником станет TDirectory. Оба класса располагают набором одноименных метqдов. В частности,
за выяснение
даты и
времени
создания
файла (папки)
отвечает
метод
class
function GetCreationTime(const
Path: string):
TDateTime Дату/время последнего обращения к
файлу (папке)
знает метод
class function GetLastAccessTime(const Path: string): TDateTime; О
том, когда
произошли
последние
изменения,
все известно
методу class function GetLastWriteTime(const Path: string): TDateTime;
У только
что
рассмотренных
методов есть коллеги,
решающие обратную
задачу по
изменению
атрибутов
файла (папки),
связанных с
датой и
временем.
class prooedure
SetCreationTime(const Path:
string;
const CreationTime: TDateTime);
class prooedure
SetLastAccessTime(const Path: string;
const LastAccessTime: TDateTime);
class procedure
SetLastWriteTime(const Path: string;
const LastWriteTime: TDateTime);
На
этот раз,
кроме
параметра Path (в
который
следует
направить
пуrь и имя
файла), в
процедурах
имеется
второй
параметр
типа TDateTime, в который
в зависимости
от
назначения
функции
следует
направить дату/время
создания,
дату/время
последне го
доступа или
дату/время
последнего
изменения.
![]()
ГЛАВА 9
для работы с
текстом
Сложно привести пример современного приложения, которое было бы способно обойтись без услуг компонентов отображения и редактирования текстовых данных, ведь область применения текстовых данных практически необъятна. Она начинает ся с простейших окон регистрации пользователя и форм ввода и заканчивается сложными текстовыми редакторами.
Библиотека FireMonkey предоставляет широчайший набор компонентов, специали зирующихся на отображении и обработке текстовых данных. Базовым элементом управления, способным выводить на экран пояснительные надписи, выступает мет-
• ка TLabel. Список предназначенных для редактирования текста компонентов воз главляют строка ввода TEdi t и строка с возможностью быстрой очистки текста тclearingEdi t. Эти элементы управления позволят пользователю ввести простей шую строку текста. Элементы управления TNшnЬerBox, TSpinВox и тcornЬoTrackBar представляют различного рода вариации на тему ввода числовых значений. Еще один герой этой главы - многострочный текстовый редактор тмеmо, компонент способен хранить и обрабатывать неформатированные текстовые строки.
Иерархия классов, на основе которых созданы рассматриваемые в главе компонен ты, предложена на рис. 9.1.
ЗАМЕЧАНИЕ
У всех компонентов
(за исключением
TLabel) предусмотрена
возможность
обращения к интерфейсам ITextServiceControl
и IVirtualKeyboardControl. Первый из
ин терфейсов
осуществляет
подцержку всех
базовых
операций с
текстовой
областью данных,
а второй
интерфейс
разрешает
элементу
управления
взаимодействовать
с сенсорной
клавиатурой,
применяемой
в IOS.
ЗАМЕЧАНИЕ
Концептуально
компонент тcornЬoEdit ближе к
комбинированным
спискам, чем
к стро кам
ввода,
поэтому он будет
рассмотрен в главе
10.
![]()
![]()
TScrollBox (FMX.Layouts)
TCustomEdit (FMX.StdCtrls)
ТТextControl
-,
(FMX.StdCtrls)
......:;;._......._..-:а....._ \ /------------------ +
◄----
------'-----... \
\ ( TEdit
11 (FMX.Edit)
TClearingEdit \ 1
(FMX.Edit) 1 1
------1/ \, ,,, ,,,
\ ._ ,,, I
'------ , _
.... --метка--/
Многострочнь1й
редактор
Строки ввода
TComboEditBase (FMX.Edit)
.----+
/
+-----
:"\
TCustom EditBox (FMX.Edit)
\
.----+ ◄ ,
/
f TComboEdit
------
: (fMX.Edit)
\
TComboTrack- \
Ваг 1
------------ /
(FMXEdit) 1
/
1 Tl'-lumberBox TSpinBox 1
(FMX.Edit) (FMX.Edit) )
' /
Комбинированные строки ввода
..._ Строки выбора значения -
![]()
Рис. 9.1. Иерархия текстовых компонентов
Класс ттextC.ontrol выступает системообразующим классом по отношению к суще ственному количеству элементов управления, отвечающих за вывод текстовых надписей. Особо отметим, что, как правило, потомки ттextcontrol не наделяют полl\зователя правом редактирования текста. Поэтому такие элементы управления (в частности метка TLabel) специализируются на выводе пояснительных надписей.
Для работы с потомками ттextcontrol достаточно запомнить менее десятка свойств, в первую очередь это свойство
property
Text: string;
определяющее
содержание
текстовой
надписи.
Гарнитуру, начертание и
шрифт определит свойство
property Font: TFont;
![]()
144 Гпава 9
Цвет
шрифта и
особенности
вывода
текстовой
надписи назначаются
кистью, дос
туп к которой
предоставит
свойство
property FontColor: TAlphaColor;
Порядок
выравнивания
текстовой
надписи в клиентской
области
компонента
зави сит от
состояния
свойств
property VertTextAlign: TTextAlign; //тто умолчанию TTextAlign.taCenter; property TextAlign: TTextAlign; //по умолчанию TTextAlign.taLeading;
TTextAlign = (taCenter, taLeading, taTrailing);
За
вертикальное
выравнивание
отвечает свойство
vertтextAlign, за
горизонталь
ное -
TextAlign.
Если
текстовая
строка очень
велика и физически
не может быть
отображена
цели ком, то
установив в
состояние true свойство
property WordWrap:
Boolean; //по умолчанию False;
можно обеспечить многострочный вывод.
В
ряде
наследников ттextcontrol для
управления
атрибутами
(влияющими на
осо бенности
отображения
текстовых
данных) можно
обратиться к
свойству
property
TextSettings: TTextSettings;
Здесь
вы найдете
описание
шрифта,
установки по
горизонтальному и
вертикально
му
выравниванию
надписи,
особенности
переноса на
новую строку
и ряд других параметров.
Некоторые
особенности
стилевого
оформления
потомков ттextcontrol, связанные
с шрифтом,
настраиваются
свойством
property StyledSettings: TStyledSettings;
Свойство
представляет
собой
множество тstyledSetting. Включая
(или отключая)
элементы множества мы указываем - стоит ли при прорисовке элемента управле
ния
учитывать
гарнитуру (ssFamily), размер (ssSize), начертание
(ssStyle), цвет (ssFontColor) и другие
параметры (ssOther) шрифта.
Из всех
рассматриваемых
в этой главе
компонентов
метка TLabel
является
единст
венным
элементом
управления,
не способным
получить
фокус ввода в
результате
нажатия
клавиши <ТаЬ>
и не
позволяющим
редактировать
свой текст
пользовате
лю. Дело в том,
что задача метки
существенно
проще, она
заключается в
выводе
пояснительной
надписи.
Все
базовые
свойства и методы
метки (в
том числе и ключевое
свойство техt) унас
ледованы от
уже
знакомого
нам родительского класса
ттextControl.
rвоих CQQ '!'!l Ш"1-!J?.!X "фирменных" СВОЙСТВ И
методов у метки практич<::ски пет. Вы
делим свойство
property AutoSize:
Boolean; //по умолчанию False;
позволяющее
компоненту
самостоятельно
подогнать
свой размер
под размер
под лежащего
выводу
текста.
3АМЕЧАНnЕ
Если
логика
программы не
предполагает
задействование
методов или
обработчиков
событий
метки TLabel и
вам особо
не интересно
стилевое
оформление формы,
то вместо
метки стоит
задействовать менее
ресурсоемкий
компонент
ттехt.
Чтобы
вы совсем не
заскучали,
предлагаем научиться
задействовать
ме1'ку в каче стве гиперссьmки. Для этих целей мы сохраним в
метке URL-aдpec интересующего
нас сайта
(листинг 9.1).
prooedure TFoпnl.FoпnCreate(Sender: TObject);
Ьegin
Labell.Text:='Caйт компании EmЬarcadero'; Labell.TagString:='http://www.emЬarcadero.ru';
end;
W"Jl!'!""""''U
...... !
![]()
![]()
![]()
При
обработке
события-щелчка по метке
(листинг 9.2) обязательно
учтем, что
для вызова
интернет-браузера
в Windows. и
OS Х
потребуется
задействовать разный
исходный код.
![]()
prooedure TFoпnl.LabellClick(Sender: TObject);
var Url:string;
{$IFDEF МАСОS}
Workspace: NSWorkspace; Url: NSURL;
{$ENDIF} '
Ьegin
Url:=TLabel(Sender) .TagString;
{$IFDEF
MSWINDO�S}
// uses Winapi.ShellApi, Windows;
ShellExecute(0, 'open', PChar(url), nil, nil, SW_SHOWNORМAL);
{$ELSE}
{$IFDEF МАСОS}
Workspace := ТNSWorkspace.Wrap(TNSWorkspace.OCClass.sharedWorkspace);
_Url
:=
TNSUrl.Wrap(TNSUrl.OCClass.URLWit�String(NSStr(Url)));
Workspace.openURL(_Url);
{$ENDIF}
{$ENDIF}
end;
![]()
![]()
ЗАМЕЧАНИЕ
Честно
говоря, у
метки TLabel есть
некоторые
проблемы с
реакцией на
событие OnClick () (речь
о RAD Studio ХЕ4 Version 18.0.4854.59655 и RAD Studio
ХЕ5 Version 19.0.13476.4176). Если
вы
столкнетесь с
аналогичной
ситуацией, то
можете заменить
в примере
метку другим
компонентом,
например ттехt.
Интерфейс /Virtua/KeyboardControl
У
всех
рассматриваемых
в главе
текстовых
компонентов
(за
исключением
метки TLabel)
предусмотрен
доступ к
интерфейсу IVirtualKeyboardControl. Указанный
интерфейс
позв.оляет
элементу
управления
взаимодействовать
с
виртуальной
кла виатурой операционной системы
IOS, устанавливаемой на смартфонах
и планшетах
от Apple (рис.
9.2). Таким
образом, если
вашими
целевыми
устройствами
являются iPhone и iPad,
то благодаря
свойству
property KeyboardType: ТVirtualKeyboardType; TVirtualKeyboardType = (vktDefault, vktNurnЬersAndPunctuation,
vktNurnЬerPad, vktPhonePad, vktAlphabet, vktURL,
vktNamePhonePad, vktEmailAddress);
вы
сможете не
только
определить,
какая именно
виртуальная
клавиатура
подклю чена
к компоненту) но
сменить
клавиатуру.
![]()
Рис. 9.2. Смена виртуальной клавиатуры
![]()
![]()
R нем инюшсулированы свойства и методы, обеспечивающие:
□ хранение и
редактирование текста;
□ выделение части текста с
возможностью редактирования только
этой части;
□ реагирование
на любые изменения в
содержании текста.
С основным свойством всех текстовых компонентов вас вряд ли н Q ;)Ш\КQМПТII,
Это свойство ·
property Text: ■tring;
Именно со свойством
техt прямо или косвенно связана деятельность подавляющего
большинства свойств
и методов тcustomEdi t.
nРНМЕЧАННЕ
В текстовые
компоненты
FireMonkey
инкапсулирован
класс ттextservice,
именно он
содержит ниэкоуровн1;1вые методь, обработки текстовь,х даннь,х. На долю
классов бо лее
вь,сокого
уровня
(например,
строк ввода) остается
лишь ставить
классу ттextService задачи
по вводу/выводу
данных.
С процессом редактирования текста тесно связаны два события. Возможность от реагировать на изменение текста предоставляет событие
property OnChange: ТNotifyEvent;
Это событие генерируется в тот момент, когда пользователь (например, завершив ввод текста) нажмет клавишу <Enter>.
Если нам надо обрабатывать любое изменение текста (будь это ручной ввод или модификация текста программным образом), то следует описывать код для обра ботчика события
property OnChangeTracking: ТNotifyEvent;
Это событие обычно задействуется для уведомления других элементов управления о процессе ввода или в качестве последней линии обороны от пользователя, пы тающегося передать приложению некорректные данные. Например, листинг 9.3 демонстрирует, как можно уведомить пользователя о том, что введенная им тексто вая строка достигла ограничения в 1О символов.
![]()
procedure TForrnl.ForrnCreate(Sender: TObject);
Ьegin
Editl.MaxLength:=10;
end;
procedure TForrnl.EditlChangeTracking(Sender: TObject);
Ьegin
if Editl.Text.Length>=Editl.MaxLength then ShоwМеssаgе('Достигнут предел длины стро:ки!
');;
end;
![]()
Предложенный
код
контролирует
длину текстовой
строки. Как
только длина
дос тигнет 1О символов,
на экран
выводится
предупредительное сообщение.
Класс
тcustomEdit позволяет
наложить
некоторые
ограничения
на вводимые
тек стовые
данные. Самое
серьезное
ограничение (абсолютный
запрет на
редактирова
ние текста)
накладывает
свойство
property ReadOnly:
Boolean; //по умолчанию false
при установке его в
true.
Ограничение на максимальное количество символов, хранящихся в
свойстве техt,
поможет назначить свойство
property MaxLength: Integer; //по
умолчанию О
-
ограничений нет
Нулевое значение
говорит о том, что длина строки не ограничена.
Если
планируется
применение
потомка
класса тcustomEdit для
ввода
конфиденци
альной
информации (например,
пароля
доступа) и
пользователь
программного
обеспечения
не планирует
ознакомить с
нею случайно
заглянувшего
в монитор
прохожего, то для
скрытия
вводимого
текста рекомендуется
воспользоваться свой ством
property Password: Boolean; //по умолчанию false
Если
перевести
свойство в
состояние true, то при
отображении
содержимого
стро ки реальные
символы
станут подменяться
служебным
символом,
например
точкой.
Если строка
ввода должна допускать
ввод только определенное
подмножество
сим волов, то
это
подмножество
следует
передать в
свойство
property FilterChar: string;
Всего
одна строка
кода из
листинга 9.4
ограничит
права
пользователя
на ввод тек
ста, теперь
ему
разрешается
использовать
только
цифровые
символы.
' •• ·-;г···•·;;,, -"· ·,1,1,-.1· ·•·•·
f . . . • ![]()
· .................................................................·-···········..••·••••...........,.......... ········"'····· ···········•....................................,••_,..............._,,,.,i'I......... .....................-.................................................... .._,
Editl.FilterChar:= '0123456789';
Чтобы отключить фильтр ввода, очистите свойство FilterChar.
У потомков класса тcustomEdi t имеется возможность выделять часть текста и ре
дактировать
эту часть.
Так,
благодаря свойству
property
seiText: string;
возможно обратиться только к
выделенному фрагменту текста.
![]()
![]()
Прt>с 111,.-слйпии выделение текста производится не только мышью или клавишами управления курсором, но и программным способом. Для этого объявлены два свойства:
property SelStart: Integer; property SelLength: Integer;
Они определяют порядковь й номер первого выделяемого символа и всю длину вы деляемого текста соответственно.
Для выделения целого слова в текущей позиции вызывают процедуру
prooedure
SelectWord;
Для вьщеления всего текста воспользуйтесь методом
prooedure SelectAll;
Для удаления выделенного текста пригодится процедура:
prooedure ClearSelection;
Для того чrобы при получении элементом управления фокуса ввода автоматически выделялся его текст, убедитесь, чrо свойство
property AutoSelect: Boolean; //по умолчанию true;
установлено в
состояние true.,
Благодаря выделению части текста мы можем помочь пользователю не только вы делять текст, но и быстрее завершить ввод текста. Предположим, чrо в нашем рас поряжении имеется большой список упорядоченных по алфавиту строк (в нашем примере хранящихся в компоненте тмеmо) и строка ввода Editl (рис. 9.3).
·Н·ор&еrия
--- ,
_j 88ОД8 ТIЖСПI
ооущ1СП111А8ТСА ПОМ!Ж
М181более noдxo,QflЩ8til
Объедкнвнмые Арабскмв
Эмираты Оман
Пакж:та1t Палау
Панама
Паnуа • Новая Г8МН8А Параrвай
Перу Поnьwа Портуrалия
Россмя
строки ПОДСТ8НО111G1
Строkа: 130
![]()
![]()
Рис. 9.3. Применение выделения части текста при поиске и подстановке
При вводе пользователем текста в строку ввода надо обеспечить быстрый поиск наиболее подходящей строки (строки, чь_ипервые символы совпали с символами, введенными пользователем) и подставить ее в компонент Editl (листинг 9.5).
![]()
procedure TFoпnl.EditlKeyUp(Sender: TObject;
var Кеу: Word;
var KeyChar: Char; Shift:
TShiftState);
var х,
i : word;
s:string;
Ьegin
if DWord(KeyChar)>47 then {контролируем только символы с
кодом больше 47}
Ьegin
Editl.ClearSelection; x:=Length(Editl.Text);
//снимаем выделение
//запоминаем число введенных знаков
for i:=0 to Memol.Lines.Count-1 do //перебираем строки в
Memol
Ьegin
s:=Copy(Memol.Lines.Strings[i],1,x);
//сравниваем
if (LowerCase(Editl.Text,loUserLocale)=LowerCase(s,loUserLocale)) then
Ьegin //если нашли совпадение - помога� пользователю
Editl.Text:=Memol.Lines.Strings[i];//пepeнocим строку в Editl Editl.SelStart:=x; //выделяем добавленную часть строки
Editl.Se1Length:=Editl.Tex4.Length-x;
break;
end; end; end;
end;
Как вы
понимаете, в роли
источника строк
может
выступать не
только многостроч
ный редактор тмеmо,
но и
различные компоненты-списки
(см. листинг 10.10), спи
ски, хранящиеся в
памяти, файлы и даже поля из таблиц баз данных.
Взаимодействие с буфером обмена
Взаимодействие с
буфером обмена производится с
помощью процедур:
proaedure CopyToClipboard;
procedure
CutToClipboard;
//копировать в буфер
//вырезать в
буфер
procedure
PasteFromClipboard; //вставить из буфера
ПQ
УМQЛчанию
строки ввода не
воспринимают
управляющие
символы (символ
табуляции,
символ
перевода
строки и т. п.).
Чтобы
изменить
ситуацию, воспользуй
тесь
свойством
property Typing:
Boolean; //по умолчанию false
установив его в состояние true. Дополнительный контроль за процессом ввода не
!! '!!!'!' !_ f!!М!ШЛ9 99 спечит событие
property OnTyping:
ТNotifyEvent;
![]()
![]()
![]()
![]()
![]()
Например,
это событие
поможет
описать реакцию
компонента
на нажатие
пользо
вателя управляющих
клавиш <Backspace>, <Del>
и т. п.
во время
редактирования
текста в
строке.
Разрабатывая
библиотеку FMX,
программисты
Embarcadero особое
внимание уде
ляли
качеству
графического
вывода
элементов
управления.
Строки ввода
не стали
исключением
из этого правила.
У этих
компонентов,
помимо
стандартных
для FireMonkey возможностей,
есть методы,
позволяющие
программисту
совершенст
вовать
внешний вид компонента.
Свойства
property
FontColor: TAlphaColor;
и
property SelectionFill: TBrush;
соответственно отвечают
за параметры
кисти,
которой
выводится
обычный
текст и
текст,
попавший в
область
выделения.
Координаты
прямоугольника, в
рамках
которого
выводится
текст, поможет
узнать метод
function ContentRect: TRectF;
Метод
function GetCharX(a: Integer):
Single;
возвратит
горизонтальную
позицию
символа с
индексом а.
Эти сведения
могут
пригодиться,
например, при
визуализации
процедуры
поиска
подстроки в
строке.
Разместите
на форме две
строки ввода:
□ строка Editl:TEdit будет содержать какой-то произвольный текст;
□ в строке Edi t2: TEdit пользователь может ввести символ, сочетание символов
или слово, которое он хочет обнаружить в
компоненте Editl.
Собственно
с поиском
подстроки
никакой проблемы
нет, в Delphi уже
давно суще
ствует
функция Pos (), возвращающая
индекс
найденного
символа. Но в
VCL было
проблематично
дать
пользователю
визуальную
подсказку,
например,
подчеркнуть
найденное
слово. В FМХ
такой
проблемы не существует.
Для этого
достаточно
воспользоваться
событием DnPaint ()
строки
ввода Editl
(листинг
9.6).
![]()
procedure
TForml.EditlPaint(Sender: TObject; Canvas: TCanvas;
const ARect: TRectF);
var a:integer;
APtl, APt2: TPointF;
![]()
![]()
Ьegin
a:=Pos(Edit2.Text,Editl.Text); //индекс первого найденного символа
if а=О then exit; //если О, значит, подстрока не найдена
//в GetCharX() отсчет начинается с
О, поэтому вычтем
1 APtl.X:=Editl.GetCharX(a-1); //координаты первого символа
APtl.Y:=ARect.Bottom-3;
//рассчитываем
координаты
последнего
символа APt2.X:=APtl.X+Canvas;TextWidth(Edit2.Text);
APt2.Y:=ARect.Bottom-3;
with Canvas do //подчеркиваем найденное слово if BeginScene(nil) then
Ьegin
Stroke.Color:=TAlphaColorRec.Crimson; DrawLine(APtl, APt2, 1);
EndScene;
end;
end;
Для того чтобы подчеркнуть найденное слово, нам потребуются две координаты - начала и окончания подстроки в строке. Первая координата выясняется благодаря методу GetCharx (), для расчета второй координаты мы прибавляем к первой результат выполнения метода тextWidth () . Дело сделано - нам осталось провести линию (рис. 9.4).
![]()
Рис.
9.4. Подчеркивание найденного слова
Строки ввода TEdit и TC/earingEdit
Из всех компонентов, способных редактировать текст, у программистов наиболь шей популярностью пользуются строка ввода TEdit и ее коллега - строка ввода с возможностью быстрой очистки тclearingEdi t (для этого пользователю достаточ но щелкнуть по кнопке с изображением крестика в правой части компонента).
Начиная с Delphi ХЕ4, у компонента TEdit появилась возможность подключения дополнительных элементов - кнопок, для этого следует вызвать контекстное меню компонента и выбрать элемент с названием подходящего класса. Подключаемая кнопка представляет собой самостоятельный элемент управления (наследник це-
![]()
![]()
![]()
![]()
почки классов "тcustomВutton- TEditButton"), который располагается в
правой части строки ввода и
снабжается соответствующей картинкой (рис. 9.5).
3АМЕЧАННЕ
Набор свойств и методов TEdit, TClearingEdi t й ввода ничем не отличается от пе речня
свойств и
методов
опорного
класса тc;:ustomEdi t.
![]()
Рис. 9.5. Подключение управляющих кнопок к компоненту TEdit
Многострочный редактор тмеmо предназначен для ввода и
редактирования много
строчного неформатированного текста и
представляет собой логическое объедине
ние визуального элемента управления и
класса тstrings, специализирующегося на · хранении
списка строк.
Ядром компонента выступает свойство
property Lines: TStrings;
предоставляющее построчный доступ к
тексту.
Предусмотрен и
альтернативный способ
обращения к
обслуживаемому
тексту, его
предоставляет
свойство
property Text: string;
На этот раз мы сможем работать не построчно, а
со всем текстом
одновременно.
Основными обработчиками событий элемента управления являются
property OnChange: TNotifyEvent; •
property OnChangeTracking: ТNotifyEvent;
Оба
события
реагируют на
изменения
текста в
компоненте тмеmо, с
той лишь раз ницей, что onchange ( ) генерируется
при условии утраты элементом управления фо куса
ввода, а onchangeTracking () будет
вызвано при
любых
обстоятельствах.
![]()
Хотя многострочный редактор тмеmо не является потомком рассмотренного в нача ле главы класса тcustomEdi t, тем не менее, он вооружен тем же набором методов, позволяющих работать только с выделенной частью текста.
proaedure SelectAll;
property SelStart: Integer; property SelLength: Integer; property
SelText: string;
proaedure ClearSelection;
//выделить весь текст
//индекс первого выделенного символа
//длина выделения
//доступ к
выделенному тексту
//очистка выделенной области
Взаимодействие с буфером обмена производится с помощью методов:
prooedure CopyТoClipboard;
prooedure CutToClipboard;
//копировать в буфер
//вырезать в
буфер
prooedure PasteFrornClipboard; //вставить из буфера
В любом текстовом редакторе, в том числе и в тмеmо, место, в котором осуществля ется набор текста, отмечается мигающей вертикальной чертой - кареткой. Для выяснения позиции каретки следует воспользоваться свойством
property CaretPosition: TCaretPosition;
Свойство возвращает запись из двух полей, идентифицирующих номер строки и позицию каретки в строке.
TCaretPosition = record
Line, Pos: Integer; //номер строки, позиция каретки в
строке
end;
Номер строки и позиция символа в строке применяются в ряде методов много строчного редактора. В частности, метод
funotion PosToTextPos(APostion: TCaretPosition): Integer;
nо.тrучив координаты APost:i.on, подсчитает число символов от начала документа до обозначенной позиции. Обратная задача - преобразование числа символов в коор динаты тcaretPosition - решается методом
funotion TextPosToPos(APos: Integer):
TCaretPosition;
Еще один метод
function GetPositionPoint(ACaretPos: TCaretPosition): TPointF;
произведет пересчет координат символа в экранные координаты.
Основной
сервис по редактированию текста в компоненте
тмеmо,
безусловно,
пре
доставляет
интегрированный
в компонент
класс тstings. Но
кроме
методов тstings нам разрешено пользоваться
и методами,
встроенными в тмеmо.
![]()
![]()
![]()
IIa nставкс блока тс:ксга в многострочный редактор специализируется метод
procedure InsertAfter (Position: TCaretPosition;·
const S: string; Options:
TinsertOptions);
В результате в позицию, указанную в аргументе Position, будет вставлен текст s. Особенности вставки определяют в опциях Options (табл. 9.1).
Таблица 9.1. Опции вставки текста в
тмето
|
Tinsertoption |
Описание |
|
ioSelected |
К вставленному
тексту применяется эффект
выделения |
|
ioMoveCaret |
Перемещает курсор в позицию после
последнего вставленного
символа |
|
ioCanUndo |
Допускает восстановление удаленного текста |
|
ioUndoPairedWithPrev |
Определяет, что
данная
операция
вставки является
продолже-
нием
предыдущей и должна рассматриваться как единое
целое |
Обратную задачу по удалению фрагмента текста решает метод
procedure DeleteFrorn(Position: TCaretPosition;
ALength: Integer; Options: TDeleteOptions);
Начало области удаления определяется в первом параметре функции Position, длина удаляемой строки назначается в аргументе ALength. Опции предложены в табл. 9.2.
Таблица 9.2. Опции удаления текста в
тмето
|
ТDeletaOption |
Описание |
|
ioMoveCaret |
Перемещает курсор
в позицию после
последнего вставленного символа |
|
doCanUndo |
Допускает восстановление
удаленного текста |
Весьма полезное качество компонента тмеmо заключается в возможности отмены последних изменений в тексте, Для этого следует вызвать метод
procedure UnDo;
Метод анализирует состояние специальной структуры TEditActionStack, которую можно сравнить со стеком, в котором сохраняются все правки в тексте. И если от кат изменений возможен, то он осуществляется.
Для организации быстрого перемещения по тексту в состав класса введены четыре метода. Процедуры
procedure GoToTextBegin;
procedure GoToTextEnd;
соответственно осуществят переход к началу или к концу текста.
![]()
![]()
У методов
prooedure GoToLineBegin;
prooвdure GotoLineEnd;
расстояния
для " рыжка"
поменьше, они
переведут
каретку
соответственно
к нача лу или
к окончанию
строки.
Ввод чисел TNumberBox, TSpinBox
и ТСотЬоTrackBar
Среди
текстовых
компонентов
есть специалисты,
оказывающие
пользователю
по мощь при
вводе
целочисленных
или вещественных
числовых значений.
Это компо
ненты TNшnЬerBox, TSpinВox и TComЬoTrackBar. По своему функционалу компоненты
очень похожи
(табл. 9.3)
и отличаются
лишь внешним
видом и способом
ввода зна
чения (рис. 9.6).
![]()
Рис. 9.6. Текстовые компоненты
для ввода числового значения
Таблица 9.3. Общие свойства компонентов ТNитЬеrВох, TSpinBox и TComЬoTrackBar
|
Свойство |
Описание |
|
property ValueType: TNurnValueType; type TNшnValueType = (vtinteger, vtFloat); |
Тип вводимых
данных
может быть
как целочисленным,
так и
вещественным |
|
property Value: Single; |
Числовое значение |
|
property DecimalDigits:
Integer; |
Число знаков, отображаемых
после запятой |
|
property Min:'Single; |
Диапазон допустимых значений |
|
property Мах: Single; |
Конечно
же, у каждого
из
рассматриваемых
элементов управления
имеются
и спе
цифичные
черты. У
компонентов ТNшnЬеrвох и тspinBox шаг
приращения
величины Y.!:l }!!!Шf !' 9!'99Н9ШШЯ С.ВQЙСТВ
![]()
![]()
![]()
![]()
![]()
![]()
![]()
property
Horzincrement: Single; property Vertincrement: Single;
Параметры приращения играют роль при управлении значением value с помощью мыши. Попробуйте, удерживая в нажатом состоянии левую кнопку мыши, переме щать указатель мыши над поверхностью элемента управления. Если вы станете со вершать горизонтальные движения, то шаг приращения будет взят из сво ства Horzincrement, на вертикальную траекторию перемещения указателя откликнется
СВОЙСТВО Vertincrement.
При желании программист сможет получить доступ к ползунку компонента
тcomЬoTrackBar, для этого в коде программы стоит упомянуть свойство
property TrackBar:
TTrackBar;
Из событий компонента следует упомянуть событие
property OnChangeTracking: ТNotifyEvent;
генерируемое при изменении положения ползунка, и
событие
property OnChange: ТNotifyEvent;
вызываемое при редактировании
значения value.
ГЛАВА 10
Библиотека FireMonkey предлагает программисту набор компонентов-списков, специализирующихся на хранении некоторого набора текстовых строк (и, при же лании, любых других объектов) и позволяющих пользователю выбирать один или несколько элементов с данными.
Список выбора строится на основе класса TListBox. Кроме того, существуют два типа комбинированных списков - нередактируемый тсоmЬовох и с редактируемой строкой ввода тcomЬoEdit. Самое существенное различие между рассматриваемыми элементами управления в том, что обычный список выбора представляет собой ок но, в котором одновременно отображаются все его элементы. Если из-за ограни ченного размера окна текстовые элементы не могут быть выведены на экран в пол ном составе, то окно списка выбора снабжается полосой прокрутки. Комбиниро ванные списки по умолчанию отображают всего один свой элемент, щелчок по кнопке в правой части компонента заставляет его вывести на экран список с переч нем элементов, один из которых выберет пользователь (рис. 10.1). После выбора выпадающий список вновь свернется. Редактируемый список тcomЬoEdit обладает еще одной сервисной возможностью. В отличие от своего статического собрата тсоmЬоВох, список тcomЬoEdi t позволяет пользователю вводить текст в строку ввода.
Основными родовыми классами для списка выбора выступают FМX.Layouts. тscrollBox и FМХ. ListBox. TCustomListBox. Благодаря первому список TListBox при обретает способность выступать в роли контейнера, обладающего полосами про крутки, а благодаря второму - список выбора получает возможность управлять своими элементами.
Нередактируемый комбинированный список тсоmЬовох создается на основе комби нации двух базовых классов (именно поэтому его и называют комбинированным). Непосредственный предок, класс TCustomComЬoBox, определяет всю программную, логику элемента управления, кроме того, здесь описана верхняя часть элемента управления, соответствующая его свернутому состоянию. При развертывании ком
DИИИ{)ОIШlШОrО списка И3 него "выпадает" список выбора, который создастся па
основе TComЬoListBox.
![]()
![]()
![]()
|
О Комnоне"'1,1-сnис1ем FnМonlte), |
|
||
|
С,ране< Соеродs><нс Н&ХОДIСЪ Сарюул · Ct&tP< ' Нe:SHHtiOMЫCC Саръ,о, ,C,eprм<!II Посц Нефтеимск Сеееродаи ,СJ< 1Cepnyxo1 Нефrеюrана Сн;ерос 1 CмoJ>t>!C< Ниж.нееартоеск СерrиеаГlси;\ .сочи НЮЮ1"а:1ыск Cepr,yxoe С!ЮЛ < • Н1<ж,;ийТаrм |
|||
|
|
|||
|
|
|
||
![]()
Рис. 10.1. Внешний вид списка выбора и комбинированного списка в проекте для Windows
ВНИМАНИЕ/
Важной
объединяющей
чертой
списков TListBox
и ТСоmЬоВох выступает
тот факт, что хранимые
в них
элементы
строятся на
основе
класса TListBoxitem.
Редактируемый комбинированный список тcomЬoEdi t также объединяет в
себе два класса: строку ввода (созданную на фундаменте класса тcustomEdit) и список выбо
ра TComЬoEdi tListBox.
Базовый элемент списка TL/stBoxltem
Сами по себе незаполненные
данными списки малоинтересны
для пользователя.
По своей сути
список
представляет
собой
интеллектуальный
футляр для
главных дей ствующих
лиц - элементов
списка.
Именно
манипулируя
с элементами
списков,
пользователь
оруществляет
свой выбор,
который
позднее
определит
поведение приложения.
Основной
информационный элемент
списка (как
обычnого, так
и комбинированно
го) строится на
основе
экземпляра класса
TListBoxitem.
ЗАМЕЧАНИЕ
TListBoxitem - не единственный класс, представленный в списках. Кроме него спи ски
способны
работать с
элементами: TMetropolisUIListBoxitem, TListBoxHeader,
TSearchВox,
TListBoxGroupHeader и TListBoxGroupFooter.
Ключевое свойство
элемента
property Text: string;
хранит
текстовое
описание
элемента.
Кроме текста
с элементом
списка
разрешено
сопоставить
любой другой
объект,
ссьmку на этот
объект следуе! передать в свой ство
property Data: TObject;
![]()
![]()
В качестве идентификатора отдельного элемента списка может выступать его ин декс
property Index: Integer;
Заметим, что индекс элемента не статичен и_может измениться в результате удале ния или переупорядочивания принадлежащих списку элементов.
Элемент списка может быть выбран пользователем или программным образом, в этом случае в его свойстве
property IsSelected: Вoolean;
окажется значение true.
Кроме того, элемент может быть отмечен "галочкой" благодаря свойству
property IsChecked: Щlean;
ВНИМАНИЕ!
Элемент списка TListBoxitem может выступать в
роли контейнера для других эле
ментов
управления.
С каждым элементом списка может быть связана дополнительная детализирующая информация. Огветственность за ее подключение возлагается на свойство
property
ItemData: TListBoxitemData;
Данные детализации описываются четверкой свойств, которые представлены в табл. 10.1.
Таблица 10.1. Основные свойства ТListBoxitemData
|
Свойство |
Описание |
|
property Text: string; |
Основное текстовое описание элемента |
|
property Detail: string; |
Дополнительное текстовое
описание . |
|
property Accessory: TAccessory; //по умолчанию aNone . |
aNone - нет значка; ' .• аМоrе - значок продолжеt-lия; aDetail - значок детализации; • aCheckmark - значок "галочки" |
|
property Bitmap: TBitmap; |
Дополнительная картинка |
Чтобы проиллюстрировать возможности свойства детализации, лучше всего вос
пользоваться проектом для мобильной платформы File I New I FireMonkey Moblle
Application, разместить на пустой форме компонент TListBox и поэкспериментиро вать со свойствами ItemData и StyleLookup элементов списка (рис. 10.2).
ВНИМАНИЕ!
Предусмотрено несколько способов отображения информации детализации элемен
ТОМ TListHoxitmn. Для того чтобы выбрать наиболее подходящий опоооб дrrn D(Щ следует поэкспериментировать со свойством StyleLookup элемента списка. В
проек-
![]()
![]()
![]()
![]()
![]()
тах
для
мобильной
платформы iOS и
Android наиболее востребованными
стилями окажутся: listboxitemnodetail, listboxitemЬottomdetail, li�tboxitemrigh�d���i� И listboxitemleftdetail.
Рис. 10.2. Элементы списка
в состоянии
детализации
В простейшем случае список выбора обеспечивает пользователю возможность вы бора одного или нескольких элементов из набора строк тstrings, доступ к которо му реализуется при посредничестве свойства
property Items: TStrings;
Манипуляции текстовыми строками осуществляются с помощью стандартных ме-
• тодов, предоставляемых в
наше распоряжение классом тstrings (листинг 10.1).
В момент появления новой текстовой строки для ее визуализации автоматически
создается
очередной
экземпляр
класса TListBoxitem.
![]()
,
ListBoxl.Clear;
ListBoxl.Items.LoadFromFile('c:\city.txt');
ListBoxl.Sorted:=true;
О числе элементов в списке проинформирует доступное только для чтения свойство
property Count: Integer;
Индекс текущего элемента списка (последнего элемента, выбранного пользовате лем) доступен благодаря свойству
property Itemindex:
Integer;
Если
в списке
не выделен
ни один
элемент, то об
этом
свойство
отрапортует
значе нием -1. Свойство
доступно не
только для
чтения, но и
для записи,
поэтому оно
позволяет
программисту
указать,
какой из
элементов
списка должен
стать теку щим.
Если
задействованный
в проекте
компонент-список
должен
обладать
заранее из
вестным
статическим
набором
элементов, то
для построения
перечня
элементов
достаточно
воспользоваться
контекстным
меню компонента
и обратиться
к пункту меню
Items Editor (Редактор
элементов). В
результате
на экране
отобразится
ре дактор,
позволяющий
добавить/удалить
элемент из
списка.
В
ситуации,
когда состав
списка
динамичен и
может
подвергаться
изменениям
во время выполнения
приложения,
можно пойти
по одному из
двух
альтернативных
путей.
Наиболее
распространенный
случай
основан на
возможностях
свойства Items.
Свойс1во
инкапсулирует
набор строк тstrings, что
позволяет
добавлять
(листинг 10.2) и
удалять
текстовые
элементы методами
класса тstrings.
![]()
ListBoxl.Items.Clear;
ListBoxl.Items .Add ('Москва');
ListBoxl.Items.Add('Caнкт-Пeтepбypг');
Традиционный
способ
редактирования
состава
элементов не
раскрывает
все воз
можности
компонента-списка.
Дело в том,
что элемент
списка TListBoxitem не ог
раничивается
обработкой
текстовых данных
и обладает
весьма
серьезными
воз можностями,
в том числе
он может
выступать в роли .контейнера
для других
эле ментов
управления.
Элементы
управления
(кнопки,
графические фигуры,
изображения
и т. п.)
могут
размещаться
на
поверхности
элементов
списка как во
время
визуального
проектирования,
так и
программным
способом. Однако
в по следнем
случае от
разработчика
приложения
потребуется
больше
усилий - ему
придется
самостоятельно в
коде программы
создавать
элементы
списка и добавлять
их в
контейнер.
Для добавления объекта в
список следует воспользоваться методом
procedure
AddObject(AObject: TFmxObject); override;
или
procedure
InsertObject(Index: Integer;
AObject: TFmxObject); override;
Разница
между
процедурами
заключается
лишь в том,
что AddObj ect ()
помещает
новый
элемент в
конец списка, а InsertObj
ect () вставляет в
позицию Index.
Для удаления ненужного элемента следует обратиться к
методу
procedure RemoveObject(AObject: TFmxObject); override;
Кардинальная очистка списка осуществляется методом
procedure Clear; virtual;
!"!V Ш!9 !!!JЫЙ !! 9Ш!!Г 10.3 код демонстрирует порядок сбора в спиооr оподс
ний о файлах-картинках в
формате JPEG.
![]()
![]()
![]()
![]()
![]()
лТТSI nn"Rтnpe,яи.v примера вам потребуются три компонента:
□ СПИСОК ListBoxl:TListBox;
□ строка ввода Edi tl: TEdit, сюда пользователь внесет пугь к каталогу с
карт ми:
□ кнопка Buttonl:твutton, щелчок по которой инициирует процесс построения
списка.
|
|
•-' |
"1 rJ:::.:,,,,,··- ,,, J |
\
.. ; |
,,,; |
•' |
|
|
••• |
1
1''"t'' |
|
.,,,1/ i ; 7/: |
|
|
" ;,..v' |
,,, • |
_,,. |
|
|
• |
•
·' |
,- ._ |
._ |
, |
w1,.,1;i,"'•,.... |
;J, |
•i:•"- },"'{,1!,;i |
prooedure
TForml.ButtonlClick(Sender: TObject);
var SDA:TStringDynArray; Item:TListBoxitem; Image:Timage; LЫ:TLabel; i:integer;
Ьegin ListBoxl.BeginUpdate; ListBoxl.Clear;
if DirectoryExists(Editl.Text) then Ьegin
SDA:=TDirectory.GetFiles(Editl.Text, '*.jpg');
for i :=О to High(SDA)
do Ьegin
Itern:=TListBoxitem.Create(ListBoxl);
Item.TagString:=SDA[i]; Itern.Height:=40;
Irnage:=Tirnage.Create(Item); //миниатюра изображения
Irnage.Parent:=Itern; Irnage.Position.X:=0;
Irnage.Position.Y:=0;
Irnage.Width:=40; Irnage.Height:=40; . Irnage.Bitrnap.LoadThurnЬnailFrornFile(SDA[i],Width,Height);
-LЫ:=TLabel.Create(Item); //метка с именем файла
LЫ.Parent:=Itern;
LЫ.Position.X:=50; LЬl.Position.Y:=0;
LЬl.Text:=System.IOUtils.TPath.GetFileNarne(SDA[i]);
ListBoxl.AddObject(Item);
end;
end;
ListBoxl.EndUpdate;
end;
Если
вы повторите
код без
ошибок, то
увидите (рис. 10.3), что
каждый из
элемен тов
списка
станет обладателем
миниатюры
изображения
(об этом позаботится
экземпляр
класса Timage) и
названия
файла (оно
отобразится в
метке TLabel).
![]()
Рис. 10.3. Элементы TListBoxitem списка с дополнительными объектами
Доступ к
выделенному элементу списка
Ссылка на выделенный
пользователем
элемент, а в
ситуации,
когда
разрешен
одно
временный выбор
нескольких
элементов - ссылка
на последний
выделенный
эле мент находится
в свойстве
property Selected: TListBoxitem;
Если в
списке
выбора не
выделен ни
один из элементов,
то в свойстве
окажется не определенное
значение nil.
Порядковый номер выбранного элемента цоступен благодаря свойству
property Itemindex: Integer;
Это свойство также может выступать в качестве индикатора наличия выбраннь х элементов, если таковых нет, то в свойстве окажется значение -1.
ЗАМЕЧАНИЕ
Свойства Selected
и Itemindex моrут
применяться для выделения элемента
списка из
кода
программы.
![]()
![]()
![]()
![]()
Доступ к
произвольному элементу списка
Для доступа к любому (необязательно выделенному пользователем) элементу спи ска из кода программы проще всего воспользоваться методом
function IternВyindex(const·Idx: Integer): TListBoxitem;
Функции необходим всего один аргумент - индекс интересующего нас элемента списка.
Точно такого же результата можно добиться благодаря свойству
property Listitems[Index: Integer]: TListBoxitem;
К числу альтернативных способов доступа к
элемеmу TListBoxitem можно ОТН СТИ
метод
function IternВyPoint(const Х, У: Single): TListBoxitem;
позволяющий идентифицировать объект по его координатам. Последний метод очень удобен, когда вы хотите подсказать пользователю, какой именно элемент списка в данный момент расположен под указателем мыши (листинг 10.4).
![]()
procedure TForml.ListBoxlMouseMove(Sender: TObject;
Shift: TShiftState;
Х, У: Single);
var Item:TListBoxitern;
Ьegin
Itern:=ListBoxl.IternВyPoint(X,Y);
if Itern<>nil then Labell.Text:=Itern.Text else
Labell.Text:=' ';
Выбор нескольких элементов
Для того чтобы компонент-список разрешил пользователю производить одновре менный выбор нескольких строк, следует перевести в режим true свойство
property MultiSelect: Boolean;
//по умолчанию false
ЗАМЕЧАНИЕ
Для одновременного выбора нескольких элементов списка пользователь. должен
удерживать в
нажатом
состоянии клавишу
<Ctrl> или <Shift>.
Листинг 10.5 демонстрирует
порядок сбора названий выделенных элементов спи
ска, для повторения примера не забудьте установить у
списка свойство MultiSelect
в
состояние true.
![]()
![]()
procedure TForml.ListBoxlClick(Sender: TObject); var i:integer;
Ьegin
Labell.Text:='';
for i :=О to ListBoxl.Count-1 do
if ListBoxl.IternВylndex(i) .IsSelected=true then
Labell.Text:=Labell.Text+#0#lЗ+ListBoxl.IternВyindex(i) .Text;
end;
Для выбора диапазона элементов предназначен метод
procedure
SelectRange(Iternl, Itern2:
TListBoxitern);
Для одновременного
выбора сразу всех элементов стоит обратиться за помощью к
методу
prooedure SelectAll;
Обратную задачу - снятие выделения легко решит процедура
procedure ClearSelection;
Представление элементов в
виде кнопки выбора
В той ситуации,
когда
пользователю
следует отметить
в списке
несколько .элемен
тов, на
помощь
приходит
свойство
property ShowCheckboxes:boolean; //по умолчанию false
Активация
свойства
приведет к появлению
на каждом
из элементов
кнопки
выбора
(кнопки с
флажком), щелчок
по которой
позволит
пометить
элемент
"галочкой".
Для перемещения элементов внутри списка следует обращаться к
методу
proc;,ec:i'IЦ'$ Exchange(Iteml, Itern2: TListBoxitern);
Процедура поменяет местами элементы Iternl и Itern2.
Наиболее востребованная сортировка заключается в упорядочении элементов спи ска
по текстовым
значениям.
Такая
сортировка
производится
при
посредничестве
свойства
property Sorted:boolean; //по умолчанию false
По умолчанию,
при переводе
свойства в состоящ1е
true осуществляется алфавитная
сортировка элементов списка по их свойству техt. Однако повеление списю1 нР.-
![]()
![]()
![]()
![]()
![]()
С!Т()ЖН() и И МР.НИП,, д.шr этого следует вспомнить о сущtспюшшии GЩШ66flfifif.a ёо:
бытия
property
OnCompare: TOnCompareListBoxitemEvent;
TOnCompareListBoxitemEvent = procedure(Iteml, Item2: TListBoxitem;
var Result: Integer)·
of
object;
Событие
(а точнее
череда
событий) oncompare
() генерируется
в момент
попарного
сравнения
элементов Iteml и Item2 списка.
Результат
сравнения, от которого зави сит очередность
элементов списка,
нам следует определить
самостоятельно в
пара- метре Result. '
Допустим,
что в нашем
распоряжении
имеется
список Listвoxl и пара
кнопок вы
бора - RadioButtonl и RadioButton2,
определяющих порядок
сортировки элементов
ts
\;Нискt: (нu
tsuзрастанию и по убыванию).
В этом случае
для решения
задачи сор
тировки нам подойдет
пример,
предложенный
в листинге 10.6.
![]()
procedure TForml.RadioButtonlClick(Sender: TObject);
Ьegin
//общее событие для кoмпoнeнтoв·RadioButtonl и RadioButton2
ListBoxl.Sorted:=false;
ListBoxl.Sorted:=true;
end;
procedure TForml.ListBoxlCompare(Iteml, Item2:
TListBoxitem;
var Result: Integer);
Ьegin
if RadioButton2.IsChecked then
if Iteml.Text>Item2.Text then Result:=l
else if Iteml.Text<Item2.Text then Result:=-1
else Result:=0
else
if RadioButtonl.IsChecked then
if Iteml.Text<Item2.Text then Result:=l el •
if Iteml.Text>Item2.Text then Result:=-1
else Result: =О;
end;
ЗАМЕЧАНИЕ
Благодаря событию OnCompare () элементы
списка могут быть
упорядочены не только по текстовым, но и по любым другим типам данных.
Текстовый поиск, элемент TSearchBox
Огслеживая развитие Delphi с самой ее первой версии, постепенно приходишь к выводу, что, возможно, в обозримом будущем разработчики языка программиро-
вания
вообще отучат
нас от придумывания
исходного
кода. Не станем
полемизиро
вать на тему
хорошо это или
плохо. Просто
предлагаем
щелкнуть по компонеmу
TListBox правой
кнопкой мыши
и добавить к
содержащему
произвольные
тексто вые элементы
списку
элемент
поиска тsearchBox (рис. 10.4).
![]()
Рис. 10.4. Добавление элемента поиска TSearchВox
В общем-то,
и все - на
этом
"программирование"
завершилось ... При
вводе поль
зователем
текста в
строку поиска
список
постепенно
сокращает
число
отображае
мых
элементов,
выводя на
экран только
те, которые
совпадают с
введенным
шаб лоном.
Список
позволяет
изменить
размеры
принадлежащих
элементов и
назначить
цен трализованно при
посредничестве
свойств
property ItemWidth: Single;
//ширина элемента
property ItemНeight: Single;//выcoтa элемента
ЗАМЕЧАНИЕ
При
желании не сложно
присвоить
индивидуальньiе
размеры для
каждого из
элемен тов
списка, в
таком случае в свойствах ItemWidth и ItemНeight списка
отобразятся нули.
Расположение элементов списка определяется состоянием свойства
property ListStyle: TLi;tStyle; //по умолчанию TListStyle.lsVertical;
По
умолчанию
оно
предполагает
классическое
размещение
элементов
списка один
над другим.
Изменив
состояние
свойства в lsHorizontal, мы
добьемся
того, что
элементы развернутся на 90° против часовой стрелки и
расположатся
слева направо.
Сво ство
property Colшnns: Integer; //по умолчанию 1 колонка
позволит создать много.колоночный список.
![]()
Наряду с классическим набором событий, которыми обладают все элементы управ ления FireMonkey (потомки класса тcontrol), компонент-список вооружен рядом эксклюзивных событий, основные из которых предпожены в табл. 10.2.
Таблица 10.2. События списка ТListBox
|
Событие |
Описание |
|
property OnCh�nge: ТNotifyEvent; |
Генерируется в
момент выбора элемента списка |
|
property OnChangeCheck: TNotifyEvent; |
Генерируется
в момент щелчка по кнопке выбора |
|
property OnCompare: TOnCompareListBoxitemEvent; |
Событие, сравнивающее леменп,1 списка в момент сортировки |
Нередактируемый комбинированный список
ТСотЬоВох
В сравнении
с
обычным
списком
выбора у
нередактируемого
комбинированного
списка
тсоmЬоВох есть
два важных
отличия.
Первое
отличие - визуальное:
по умолчанию
комбинированный список
свернут и
практически
не занимает
места на
форме. Это
весьма
важное
преимущество,
особенно в
ситуации,
когда
пользова
тельский
интерфейс
разрабатываемого
приложения перенасыщен
элементами
управления.
Второе
отличие:
компонент тсоmЬоВох не
предназначен
дпя одновре
менного выбора
нескольких значений, это бы
противоречило
самой логике
работы
' комбинированного списка, ведь в свернутом состоянии список-компонент отобра жает всего один элемент. В остальном компоненты очень похожи, это подтвержда ет табл. 10.3, в которой представлены наиболее важные свойства и методы комби нированного списка.
Таблица 10.3. Основные свойства и
методы тсотЬоВох
|
Свойства и
методы |
Описание |
|
property Items.: TStrings; |
Набор
текстовых
строк,
отображаемых
в списке.
Отметим, что
компонент
не умеет
хранить
свои
элементы,
поэтому их следует
загружать/сохранять
в файле методами
LoadFromFile () и SaveToFile ( ) |
|
property Count: Integer; |
Число элементов в
списке |
|
property Selected: TListBoxitem; |
Выбранный элемент |
|
property Itemlndex: Integer; |
Индекс выбранного элемента |
|
property Listltems[Index: Integer]: TListBoxitem; |
Доступ к
любому элементу списка по его индексу |
|
procedure Clear; |
Очистка списка |
Из наиболее интересных черт комбинированного списка отметим
щюйство
property DropDownCount: Integer;
определяющее
число
элементов,
отображаемых
в выпадающем
списке,
однако сра
зу заметим,
что в
проектах для
iOS и Android вместо
списка вы
увидите
вращаю щийся "барабан", ко:горый появляется
внизу экрана в
момент выбора пользователем
интересующего
его элемента
(рис. 10.5).
Допускается
изменить
место вывода
списка на
экран. В
простейшем
случае для
этого следует
"поколдовать"
со свойством
property Placernent:
TPlacernent;
TPlacernent
= (plBottorn, plTop, plLeft, plRight,
plCenter, plBottornCenter, plTopCenter, plLeftCenter, plRightCenter, plAЬsolute, plMouse, plMouseCenter);
По умолчанию
список
выпадает под элементом
управления (plBottorn). Проявив
еще немного
настойчивости и
воспользовавшись
свойством property
PlacernentRectangle: TBounds;
можно более точно настроить область вывода списка.
![]()
Рис.
10.5. Поведение комбинированного списка ТСоmЬоВох в проектах для мобильной платформы
![]()
![]()
![]()
Чтобы заставить компонент показать выпадающий список в ко.де ваl!! .!!
!!}?О!'J? !:-! мы,
обратитесь к
методу
prooedure DropDown;
В завершение недолгой истории комбинированного списка напомним ключевое событие компонента
property OnChange: ТNotifyEvent;
Событие вызывается в момент смены элемента списка.
В составе компонентов FMX имеется комбинированный список тcolorCornЬoBox, позволяющий пользователю выбрать один из заранее предопределенных цветов. Интересно, а сможет ли с подобной задачей справиться классический тсоmЬоВох? Листинг 10.7 доказывает, что никаких проблем в этом нет.
.' , "' '1" · " , . ·· ()V , 1..
' ', ,,,..,.,)'!!f, i!i,,/·'f,i:,"11,,J;!"''
prooedure TForml.FormCreate(Sender: TObject);
var Item:TListBoxitem;
Prooedure CreateColorRect(Parentitem:TListBoxitem);
var R:TRectangle;
Ьegin
R:=TRectangle.Create(Parentitem); R.Parent:=Parentitem; R.Fill.Color:=Parentitem.Tag; R.Position.X:=l; R.Position.Y:=l;
R.Height:=Parentitem.Height-2; R.Width:=R.Height;
end;
Ьegin
Item:=TListBoxitem.Create(CornЬoBoxl); Item.Text:='Kpacный';
Item.Tag:=claRed; CreateColorRect (Item);
Item.TextAlign:=ТTextAlign.taCenter;
CornЬoBoxl.AddObject(Item);
// и т. д.
end;
В
момент
создания
формы мы
динамически заполняем
комбинированный
список
элементами
TListBoxitem. На
поверхности
каждого из
элементов
размещаем
окра шенный
в
соответствующий
цвет прямоугольник TRectangle.
Для того чтобы воспользоваться услугами нашего комбинированного списка, на пример, чтобы перекрасить поверхность формы, выбираем собьпие onchange (лис тинг 10.8).
![]()
procedure TFoпnl.ComЬoBoxlChange(Sender: TObject);
Ьegin
if ComЬoBoxl.Itemindex<>-1 then
Foпnl.Fill.Color:=ComЬoBoxl.Selected.Tag;
end;
Редактируемый комбинированный список
TComboEdit
В отличие от обычного
комбинированного списка
тсоmЬоВох
его
собрат тcomЬoEdi t в качестве
своего
опорного
класса
избрал специалиста
по обработке
текста - класс тcustomEdit. Благодаря
этому
поступку тcomЬoEdit приобрел
полезную
способность
редактировать
текст в
строке ввода.
Мы не станем повторяться и вновь перечислять свойства и методы класса
. '
тcustomEdit (вы их найдете в
главе 9), отметим лишь то, что основным свойством,
унаследованным
редактируемым
комбинированным
списком от
строки ввода,
вы ступает
свойство техt.
Если вы внимательно прочитали страницы главы, посвященные списку TListвox и
_ комбинированному
списку тсоmЬоВох,
то
уже готовы
работать с
компонентом тcomЬoEdi t. Несмотря
на то, что тcomЬoEdi t имеет
несколько
иную цепочку
пред ков, создатели
компонента
оснастили
его набором свойств
и методов как
по назва нию, так функционально
повторяющих
свойства и методы уже рассмотренных
спи сков.
Единственное,
о чем стоит напомнить,
так это
свойство
property Items: TStrings;
в котором хранится список строк компонента.
Полагаем, что уже настало время рассмотреть пару примеров, раскрывающих осо б ННQСТИ компонента
TComЬoEdi t. Листинг 10.9
демонстрирует один
из способов
заполнения списка
во время
выполнения приложения
··········•,. .., ···-······· ,··,i
i
•·· .i.
procedur� TFoпnl. ComЬoEdi tlKeyDown ( Sender: TObj ect; var Кеу :· Word;
var KeyChar: Char; Shift: TShiftState); var s:string;
щin.
1f (Кеу=lЗ) then //код клавиши <Enter>
Ьeg1n
s:=Trim(ComЬoEditl.Text); //введенная пользователем строка
1f s<>'' then //если строка не пуста, Ьegin //добавим строку в список
ComЬoEditl.Items.Add{s);
![]()
![]()
![]()
end;
ComЬoEditl.Text:='';
end;
![]()
Если
вы пробовали
работать со
списком выбора
или с обычным
комбинированным
списком, содержащим большое число элементов, то уже наверняка уоедились в том, что на поиск требуемого элемента может уйти много времени. Несколько
строк кода
из листинга 10.1 О
значительно
упростят
жизнь
пользователя. Теперь для
выбора
текстового
элемента из
отсортированного
по алфавиту
списка достаточно
набрать
первые
символы
искомого
текста. В ответ
на это список
самостоятельно
отыщет
нужный
элемент и
подставит
недостающие символы.
![]()
prooedure
TFonnl.ComЬoEditlKeyUp(Sender: TObject;
var Кеу: Word;
var KeyChar: Char; Shift: TShiftState);
var i,x:integer;
s:string;
Ьegin
if Кеу<>lЗ then Ьegin
//пользователь вводит первые символы в
строку ввода
x:=Length(ComЬoEditl.Text); //число введенных символов
if х>О then
for i := О to ComЬoEditl.Count-1
do Ьegin s:=Copy(ComЬoEditl.Items[i],1,x);
if UpperCase(s,loUserLocale)=
UpperCase(ComboEditl.Text,loUserLocale) then //начало слова совпало
Ьegin
//копируем
недостающую часть слова s:=Copy(ComЬoEditl.Items[i],x+l,Length(ComЬoEditl.Items[i])-x); ComЬoEditl.Text:=ComЬoEditl.Text+s; //в строку ввода ComЬoEditl.SelStart:=x; //выделяем скопированную часть ComЬoEditl.SelLength:=Length(ComЬoEditl.Text)-x;
ComЬoEditl.CaretPosition:=x; //сохраним позицию каретки
break;
end; end;
end else
Ьegin
//пользователь нажал <Enter> и подтвердил свой выбор s:=UpperCase(ComЬoEditl.Text,loUserLocale);
//еще раз убедимся, что такое слово есть
for i:= О to ComЬoEditl.Count-1 do
if s=UpperCase(ComЬoEditl.Items[i],loUserLocale) then
end; end;
Ьegin
ComЬoEditl.Itemindex:=i; //выбираем элемент
break;
end;
Компонент выбора значения ТРорирВох
Основу компонента выбора значения TPopupBox составляет свойство
property
Items: TStrings;
предоставляющее
доступ к
списку строк,
из которых
пользователь
выберет необ
ходимое
текстовое
значение.
Результат выбора
окажется в
свойстве
property Text:String;
Порядковый номер вь1бранной строки отобразится в
свойстве
property Itemindex: Integer;
В момент смены элемента генерируется событие
property OnChange: TNotifyEvent;
Как видите,
элемент
управления TPopupBox по своему
духу очень
близок к класси
ческому
комбинированному
списку. Разница
заключается
лишь в том,
что на этот раз
список
текстовых
элементов не
выпадает из компонента,
а всплывает
над ним (рис. 10.6).
![]()
|
|
|
о Дl!МОН ЦИR 11'.ОМ Т |
|
,- TPopupВo>t |
|
|
Рис.
10.6. Внешний вид компонента TPopupBox
![]()
![]()
ГЛАВА 11
В
окружающем
нас мире
иерархические
структуры
распространены
весьма широ
ко: это и
структура
предприятия,
и дерево
каталогов на жестком
диске компьютера,
и иерархия
наследования
классов FireMonkey.
Можно
продолжать
приводить
мно гочисленные
примеры
иерархически
организованных
данных, но
лучше сразу
за острить
свое внимание
на их
объединяющей
черте - наличии
между
элементами
данных
отношения
"главный - подчиненный"
(родительский - дочерний).
Еще один
важный
отличительный
признак
иерархических
структур
заключается
в том, что родительский
узел дерева
способен обладать
неограниченным числом дочерних
узлов. В свою
очередь
любой дочерний
узел имеет
право
выступать
владельцем
целой ветви
подчиненных
узлов. Тот
факт, что
структура
дерева
заранее неизвест
на, значительно
затрудняет
хранение
иерархии в памяти
и осуществлеflие операций
с узлами дерева. К
счастью, в
составе FireMonkey имеется элемент управления,
с легкостью
решающий
задачу
обслуживания
иерархических
данных:
главный и
единственный
герой этой
главы - компонент
ттreeView.
Компонент
ттreeview (если
следовать
дословному
переводу - дерево
просмотра)
представляет
собой
логическое
объединение
двух классов
и интерфейса:
□
компонент-дерево
ттreeView, обеспечивающий
визуализацию
иерархической
структуры и
пользовательский
интерфейс;
□ класс ттreeViewitem, на котором строится узел иерархии;
□
интерфейс
Пtemscontainer позволяет
как дереву,
так и узлу
дерева выступать
в роли
контейнера
для других
элементов.
Дерево ттreeView является
прямым вла
дельцем
узлов, описывающим
самый
верхний
уровень
иерархии. Узлы
верхнего
уровня
способны
выступать в роли
главного
узла по отноше ию к подчиненным
узлам,
которые, в свою
очередь,
владеют
узлами
следующей ступени.
Если
дерево
изначально
статично и
состав его
узлов не
должен
изменяться в
ходе выполнения
приложения,
то для
создания
иерархии
элементов
проще всего
вос
пользоваться
встроенным редактором Items Designer,
вызываемым
из контекстного
меню
размещенного на
форме
компонента ттreeView (рис. 11.1).
Однако мы с
вами не станем
искать
простых путей и в
этой главе
напишем код для дерева,
способного
динамически
создавать
свои узлы.
![]()
![]()
![]()
Рис.
11.1. Редактор элементов ltems Designer
Все элементы иерархической структуры создаются на основе класса ттreeViewitern, который в свою очередь является потомком текстового класса ттextcontrol. От своего текстового предка элемент иерархическо·й структуры унаследовал базовое свойство
property Text: string;
благодаря которому узел приобретает возможность хранить и отображать тексто вую надпись и ряд (уже знакомый нам по главе 9) свойств и методов, нацеленных на обслуживание текстовой надписи.
Все иерархические структуры строятся по принципу "главный - подчиненный", который определяет степень зависимости между узлами дерева. Каждый узел дере ва имеет право владеть произвольным набором дочерних элементов, доступ к кото рым реализуется с помощью свойства
property Items[Index: Integer]: TTreeViewitem;
Точно так.ой же результ т можно получить, передав индекс в метод
function IternВyiвdex(const Idx: Integer): TTreeViewitem;
В ответ функция возвратит ссьmку на запрошенный узел. Сведения о количестве подчиненных узлов хранит свойство
property Count: Inte�er; //r9П�К9 для чтения
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
Иерархическая структура 177
l:SHHMAHHE!
Свойство
Items и метод ItemВyindex () отвечают
за
предоставление
доступа толькок
непосредственно
подчиненным узлам
(находящимся на
следующем
уровне nосле ро дительского
узла). Это же
замечание
справедливо
и для
свойства count.
При необходимости видимый узел можно идентифицировать по его экранным ко ординатам (х, У), для этого потребуется помощь метода
function ItemВyPoint(const Х, У: Single): TTreeViewitern;
Дочерние узлы владеют информацией о своем родительском узле, она доступна благодаря методу
function Parentitem:
TTreeViewitem;
Благодаря свойству Parentitern несложно проконтролировать всю цепочку РQАИ тельских узлов для текущего узла. Например, предложенная в листинге 11.1 функ ция GatPath () может применяться в приложении, которое использует компонент ттreeView в качестве дерева каталогов (р с. 11.2). Получив в качестве параметра ссылку ·на выбранный пользователем узел Item, функция (перебирая последова тельность предков узла) построит полный файловый путь.
![]()
Рис. 11.2. Компонент TTreeView в качестве дерева каталогов
![]()
![]()
function
TForml.GetPath(const Item: TTreeViewitern): string;
var Temp:TTreeViewitem;
Ьegin
Ternp: =Itern; Result:=' '; repeat
![]()
![]()
![]()
if temp.Parentitem<>nil then //пока есть родительский узел
Result:=temp.Text+TPath.DirectorySeparatorChar+Result //уровень папок
else Result:=temp.Text+Result; //уровень дисков
Temp:=Temp.Parentitem;
until Temp=nil;
end;
Узлы
самого
верхнего
уровня
являются прямой
собственностью
дерева ттreeView, впрочем,
каждому узлу
известно,
какому компоненту
он
принадлежит.
Такие све
дения можем
получить и
мы, запросив
помощь у
метода
function
TreeView: TCustomTreeView;
Для
добавления
нового узла
самого
верхнего
(первого)
уровня
следует
задейство
вать метод
prooedure AddObject(AObject: TFmxObject); override;
Изъятие дочернего узла осуществит метод
prooedure RemoveObject(AObject: TFmxObject); override;
Существует несколько взаимодополняющих способов определения положения узла
в дереве. Во-первых, узел характеризуется уровнем,
о котором нам расскажет
метод
function Level: Integer;
При
обращении к
узлу самого
верхнего
уровня
функция
возвратит 1,
при обраще
нии к узлу
первого
вложения
2, и т. д.
Все дочерние
узлы знают свой
порядковый
номер в списке
узла-владельца. Об этом
заботится
свойство
property Index: Integer;
Свойство Index сохраняет работоспособность и
для узлов самого верхнего уровня
(их владельцем выступает компонент TTreeView).
l<:роме локального индекса узел обладает и
глобальным номером, определяемым
свойством
property Globalindex: Integer;
Свойство
доступно не
только для
чтения, но и
для записи,
что
позволяет
переме щать узел. Стоит отметить, что глобальный индекс отражает порядковый номер
видимого
узла, если же
уз л невидим
(например,
свернут в
составе
родительского
узла), то он
исключается
из нумерации.
ВННМАННЕ!
После изменения положения узла (например, после процедуры сортировки) его ин
дексы
изменяются!
![]()
![]()
![]()
Иерархическая структура 179
Узел дерева
обладает
рядом важных характеристик, описывающих
его текущее 1,о
стояние. В первую
очередь, это
сведения о том,
развернут узел или
свернут:
property IsExpanded: Boolean;
По умолчанию узел свернут,
а развернуться
способен
только узел, обладающий
до черними узлами.
Если в компоненте TTreeView активировано
свойство ShowCheckЬoxes (заставляющее
каждый
узел дерева
отображать
кнопку-переключатель), то
стоит
обратить
внима ние на
свойство
property IsChecked: Boolean;
позволяющее
получить
сведения о
том, отмечен
ли
"галочкой" узел, и, при жела нии,
изменить
состояние
узла
программным
способом.
Если компонент
ттreeView позволяет
пользователю
одновременный
выбор несколь
ких узлов
(см. свойство
MultiSelect), то
проверку
факта выбора узла позволит
осу ществить
свойство
property IsSelected:
Boolean;
Хотя
на
основе
класса ттreeViewitem можно
создать
вполне
самостоятельный
объ ект,
он не сможет
в полной мере выполнять
возложенные
на него задачи до тех пор, пока не
попадет в
распоряжение
компонента ттreeView. Благодаря
дереву
ттreeview экземпляр
класса
ттreeViewitem визуализируется
и
превращается в
узел,
к которому
сможет
"прикоснуться"
пользователь.
Дерево
способно
централизованно
назначить
вертикальный
размер всем
своим элементам,
для этого
предназначено
свойство
Выделение узла
По умолчанию
дерево позволяет
пользователю
вьщелить только один узел, если вас
это не устраивает,
то
переведите в
состояние true свойство
property MultiSelect: Boolean;
//по умолчанию false
Для выяснения, какой из узлов выбран, следует проконтролировать свойство
property Selected: TTreeViewitem;
Оно
возвратит
ссылку на
выделенный узел.
Если дерево
допускает
одновременное
выделение
нескольких узла , то для определения,
выделен ли узел, следует
контро
лировать
состояние
свойства IsSelected у каждого
из узлов
дерева.
![]()
![]()
![]()
Если
вы хотите,
чтобы при
утрате
фокуса ввода
компонент ттreeView
снимал
визу альное
выделение
узла,
то установите
в true свойство
property HideSelectionUnfocused: Boolean;
//по 1,1Молчанию false
В момент выбора узла (как вручную с
помощью мыши и клавиатуры, так и про граммным
способом)
генерируется
самое
главное событие
компонента
property OnChange:
TNotifyEvent;
Компонент
ттreeView на
правах
владельца
иерархической
структуры
способен об
ратиться к
любому
принадлежащему
ему узлу.
Если речь
идет об узлах
самого
верхнего уровня, то
нашим
помощником
станет
свойство
property
Items[Index: Integer]: TTreeViewitem;
или метод
function ItemВyindex(const Idx: Integer): TTreeViewitem;
Как
свойство,
так и метод
предоставят
программисту
ссылку на узел
первого уров
ня по его
индексу. Если
число узлов
верхнего
уровня
неизвестно,
то справку по
этому
вопросу
предоставит
свойство
property Count: Integer;//тoлькo для чтения
Если
нам
следует
работать с
узлами более
глубоких
уровней вложения,
то следует
воспользоваться
методом
function ItemВyGlobalindex(const Idx: Inte�er): TTreeViewitem;
Функция
возвратит
экземпляр
узла по его глобальному
индексу, но с
одной суще
ственной
оговоркой - этот узел должен быть видимым.
Число видимых; элементов
в дереве
известно свойству
property GlobalCount: Integer;
Кроме
того,
доступ к
видимому
узлу по его
экранным
координатам
позволит осу
ществить
метод
•
functio.n ItemВyPoint(const Х, У: Si.ngle): TTreeViewitem;
Перечень способов доступа
к узлу завершает метод
function ItemВyText(const AText: string):
TTreeViewitem;
На этот раз поиск узла
производится по содержимому его свойства техt.
Если логика создаваемого приложения предполагает необходимость динамического
изменения
состава
узлов, то
разработчику
программы не
обойтись без
услуг ме тодов
procedure
AddObject(AObject: TFmxObject); override;
f�o�uro f§ill9Y§9Dj@Gt(д0bj@ct: TFmxObjectJ; override;
![]()
![]()
![]()
![]()
![]()
Иепапхuчеr.кая структура
Первая процедура
добавит узел AObj ect в самый
верхний
уровень
иерархии, а
вто- рая - удалит
указанный
узел. '
ВННМАННЕ!
Методы AddObject () и RemoveObject () универсальны и способны работать не только
с узлами TTreeViewitern, но и с любыми другими объектами из библиотеки FMX.
Наиболее кардинальными возможностями обладает метод
procedure Clear;
Он позволяет полностью
освободить дерево от узлов.
P1;;шt:нut:
uднuй из
задач, демонстрирующей
процесс
динамического
заполнения
узлами дерева TreeViewl, предложено в
листинге 11.2. В данном примере мы
соби раем сведения о
доступных логических дисках компьютера с Windows или папках
верхнего уровня для станции с OS Х и передаем сведения в
иерархическую струк
туру.
![]()
procedure TfrmМainDuЫicate.UpdateTopLevelFolders;
var SDA:TStringDynArray;
Item:TTreeViewitem; i:integer;
Ьegin. try
TreeViewl.BeginUpdate;
TreeViewl.Clear;
{$IFDEF MSWINDOWS}
{/ ДИСКИ WINDOWS
SDA:=TDirectory.GetLogicalDrives;
{$ELSE}
{$IFDEF МАСОS}
//----------------- КОРНЕВЫЕ ПОПЬЗОВАТЕЛЬСКИЕ ПАПКИ OS Х------------------------------
SDA:=TDirectory.GetDirectories(TPath.GetHomePath, '*.*',
TSearchOption.soTopDirectoryOnly);
{$ENDIF}
{$ENDIF}
for i :=О to High(SDA) do Ьegin
Item:=TTreeViewitem.Create(TreeViewl);
Item,Text:=IncludeTrailingPathDelimiter(SDA[i]); Item.Parent:=TreeViewl;
TreeViewl .AddObject (Item);
end;
finally
TreeViewl.EndUpdate;
end; end;
ВНИМАНИЕ!
•
Чтобы
исключить
лишние
операции
перерисовки
дерева,
операции
добавления и уда
ления узлов
целесообразно
заключать в
программный
блок,
начинающийся
с вызова метода
BeginUpdate () и
заканчивающийся
обращением к
методу EndUpdate ().
Воспользовавшись функцией GetPath () из листинга 11.1, мы сможем развить успех и научить дерево не только строить перечень дисков (или папок верхнего уровня OS Х), но и собрать сведения о каталогах, размещенных на этих дисках. В листин ге 11.3 предложен пример функции, на вход которой поступает ссылка на ассоции рованный с диском или папкой узел Parentitem, в ответ на это функция GetChildFolders () собирает сведения о подчиненных каталогах и заносит их в де рево.
![]()
procedure TForml.GetChildFolders(Parentitem: TTreeViewitem);
var SDA:TStringDynArray;
Path:String; Item:TTreeViewitern; i: integer;
Ьeqin
TreeViewl.BeginUpdate;
while Parentitern.Count>0 do //удалим все дочерние узлы
Parentitem.Iterns[0] .Destroy;
Path:=GetPath(Parentitern); //узнаем файловый путь (см. листинг 11.1)
if TDirectory.Exists(Path) then Ьeqin
SDA:=TDirectory.GetDirectories(Path); //собираем сведения for i:=0 to High(SDA) do //создаем дочерние узлы-каталоги
Ьeqin
Item:=TTreeViewitem.Create(Parentitem);
Itern.Parent:=Parentitern;
Item.Text:= TPath.GetFileNarne(SDA[i]);
Parentitem.AddObject(Item);
end;
end;
TreeViewl.EndUpdate;
end;
Для
того чтобы
компонент TreeViewl смог
превратиться
в иерархию
дисков и ката
логов, нам осталось лишь научить приложение воспользоваться функциями из лис-
тингов 11.1-11.3· tтcтt:т!.т.я-
![]()
это-го_ -п.n,я,я_тт.,nбится описать несколько об
раб oгr,r.1\.-oD ,;;0601-
тий формы и
компонента-дерева (листинг 11.4).
![]()
Иерархическая структура 183
![]()
![]()
//создание формы-владельца компонента TreeViewl
prooedure TForml.FormCreate(Sender: TObject);
Ьegin
UpdateTopLevelFolders; //сбор узлов верхнего уРовня (см. листинг 11.2) end;
//двойной щелчок по узлу
дерева
prooedure TForml.TreeViewlDЫClick(Sender: TObject);
var Item.:TTreeViewitem;
.r.-.,,.Ln
Item:=TreeViewl.Selected;
if Item<>nil then //если есть выделенный узел
Ьegin
Item.IsExpanded:=NOТ Item.IsExpanded;
if TreeViewl.Selected.IsExpanded=true then
GetChildFolders(TreeViewl.Selected); (см. листинг 11.3)
end;
Узел в
роли флажка
При
желании все узлы
дерева можно
вооружить
дополнительным
функционалом
-
возможностью играть роль флажков, в
которых пользователь
сможет поставить ил.и
снять
"галочку". Для реализации задуманного достаточно осуществить всего
одно
действие - установить в
состояние true свойство
property ShowCheckЬoxes: Boolean; //по умолчанию false
ЗАМЕЧАНИЕ
Для проверки состояния переключателя следует обратиться к свойству IsChecked
узла дерева TTreeViewNode.
В момент включения/отключения переключателя у дерева вызывается
событие
property OпChangeCheck: TNotifyEvent;
в коде
которого
можно описать
реакцию
компонента ттreeView на обращение
поль
зователя к
переключателю.
Свертывание и
развертывание узлов
В
арсенале методов компонента ттreeView предусмотрены две процедуры,
способ
ные
развернуть и
свернуть всю
иерархию узлов.
prooedure ExpandAll;
prooedure CollapseAll;
![]()
Картину дополняет свойство дерева
property CountExpanded: integer;
позволяющее программисту узнать число развернутых узлов.
Компонент
ттreeView обладает
способностью
упорядочивать
свои узлы. В
про стейшем
случае элементы дерева
размещаются
в алфавитном
порядке своих
тек стовых
заголовков. Для этого следует
установить в
состояние true свойство
property Sorted: Boolean;//пo умолчанию false
Если
требуется
задать более
сложные
правила сортировки,
то стоит
обратить вни
мание
на
событие
property OnCompare: TOnCompareTreeViewitemEvent;
TOnCompareTreeViewitemEvent = .
function(Iteml, Item2: TTreeViewitem): inteqer
of object;
В
рамках события следует
сравнить
параметры
двух узлов, Iteml и Item2,
и сделать
вывод об их порядке
следования
по
возвращаемому
функцией
значению.
![]()
![]()
![]()
![]()
ГЛАВА 12
Очень многие совреt-1.енные программные продукты нуждаются в табличном пред ставлении данных, среди них бухгалтерские приложения, статистические и анали тические программы, таблицы спортивных турниров, специализированные прило жения для различных отраслей научных знаний и, конечно же, базы данных.
В составе элементов управления FireMonkey имеются два компонента, специализи рующихся на представлении данных в табличном виде. Это компоненты TGrid и тstringGrid. Оба элемента управления объявлены в модуле FМX.Grid и являются
, наследниками одного и того же опорного класса сеток
type TCustomGrid = class(TScrollBox, IItemsContainer)
Основная
разница
между
компонентами
в том,
что сетка тstringGrid нацелена
на
обслуживание
исключительно
текстовых
данных, а
сетка TGrid - более
универ
сальна и позволяет работать не только с
текстом, но и булевыми значениями, гра
фическими
объектами,
списками
строк. Такое
функциональное
разделение
не сколько
напоминает
ситуацию с
сетками тstringGrid и тorawGrid
из
библиотеки VCL,
однако
принцип
построения
сеток TGrid и тstringGrid
абсолютно
не похож на архитектуру их коллег из состава VCL.
ВНИМАНИЕ!
Компоненты-сетки
FireMonkey
предназначены
для визуализации
и
редактирования
данных, но они не способны
обеспечить
их хранение.
Эта проблема
ложится на плечи
разработчика
программного
обеспечения.
Основной строительный элемент сеток FМХ - колонка. Колонка - это экземпляр класса тcolumn или экземпляр одного из потомков этого класса. Например, сетка строк тstringGrid специализируется исключительно на текстовых колонках тstringColumn, а сетка TGrid помимо колонок строк умеет обслуживать весьма экзо тические колонки (табл. 12.1).
![]()
![]()
Таблица 12.1. Колонки и
ячейки сеток
|
Тип колонки |
Тип ячейки . |
Описание |
|
TStringColumn |
TTextCell = class(TEdit) |
Текст |
|
TCheckColumn |
TCheckCell = class(TCheckBox) |
Флажок |
|
TProgressColumn |
TProgressCell = class(TProgressBar) |
Шкала |
|
TPopupColurnn |
TPopupCell = class(TPopupBox) |
Всплывающая панель |
|
TimageColurnn |
TimageCell = class(TimageControl) |
Изображение |
Для
создания
колонок во время
визуального
проектирования проще
всего
восполь
зоваться контекстным меню
компонента-сетки
и выбрать там
пункт Items
Editor. В
ответ на это действие
Delphi отобразит
на экране
окно дизайнера
Items Designer (рис. 12.1).
Дальше
программист
выбирает тип
колонки и добавляет
колонку в сет
ку. Колонка
является
вполне самостоятельным объектом,
обладающим
свойствами,
методами и
обработчиками
событий.
![]()
Рис. 12.1. Редактор колонок компонента-сетки TGrid
Каждая
из ячеек
колонки во
время
редактирования
данных
интерпретируется
как элемент
управления
определенного
класса (см. табл. 12.1).
Например, в
колонке TStringColumn, специализирующейся на обслуживании текста, каждая из ячеек фак тически
является
строкой
ввода TEdit,
а
колонка TirnageColurnn предоставляет
дос тупк ячейкам
TimageCell, способным
обслуживать
графические
данные.
ВНИМАНИЕ/
Жи ненный цикл соответствующих ячейкам элементов управления находится в nря
мои зависимости от в димости ячейки. Если ячейка исчезает с экрана (например,
скрывается
за раницеи
сетки после
скроллинга), элемент
управления
удаляется,
при появлении яч ики.Л
.1\1! .1-!Т оссоэдается. Надо лонимап,, что вместе с нсчоа11000,,.,о,.,
ассоциированного.с ячейкой элементом управления исчезают и
все его данные...
![]()
![]()
![]()
Доа мс1u.ц11v 1юз ол!ют обратиться к элементу управления, ассоциированному с конкретнои ячеикои колонки. .
function CellControlByPoint(X, У: Single): TStyledControl; function CellControlByRow(Row: Integer): TStyledControl;
Какv видите, для идентификации достаточно передать экранные координаты (х,
У)
ячеики или номер строки Row в колонке.
Опорный класс всех колонок TColuпm предоставляет своим наследникам минималь ный базовый набор свойств и методов. В первую очередь это свойство, описываю щее заголовок колонки
property Header: string;
колонка может быть переведена в режим "только Д/IЯ чтения" с помощью сво ства
property ReadOnly: Boolean; //по умолчанию false
и скрыта с экрана с помощью свойства
property VisiЫe: Boolean; //по умолчанию true
Сетка TGrid
Размер сетки можно уточнить благодаря свойствам
property RowCount:
Integer;
property ColuпmCount: Integer;
//только дпя чтения
Обратите внимание на то, что свойство RowCount позволяет изменять число строк
в сетке. А
свойство coluпmCount доступно
только для
чтения, т. к.
количество
коло нок в
сетке
определяется
числом
объектов тcolumn.
Доступ к экземпляру колонки проще всего производить по ее индексу
_property Columns(Index: Integer]:
TColumn;
Кроме того, существует метод, идентифицирующий колонку по экранным коорди натам
function ColumnByPoint(conet Х,
У: Single): TColuпm;
Для доступа к интегрированному в ячейку элементу управления следует вызвать метод, возвращающий ссылку на объект
function CellControlByRow(Row: Integer): TStyledControl;
Заметьте,
что в данном
случае
возвраща тся
обезличенный
тип
данных тstyledControl, который
следует
привести к требуемому
виду при посредничестве
классов TTextCell,
TCheckCell, TProgressCell,
TPopupCell или TimageCell.
Во время создания пользовательского хранилища данных окажутся полезными со бытия
property
OnSetValue: TOnSetValue;
type TOnSetValue
= prooedure(Sender: TObject; const Col, Row: Integer;
const Value: TValue) of oЬject;
![]()
property
OnGetValue: TOnGetValue;
type TOnGetValue
= prooedure(Sender: TObject; const Col, Row: Jnteger;
var Value: ТValue) of oЬject;
Событие
OnSetValue () генерируется
в
момент
получения
ячейкой с
координатами
Col и Row значения Value. Обратное
событие onGetValue
() вызывается
во время
чте- ния
значения Value из ячейки.
3АМЕЧАННЕ
Используемый
в
обработчиках
событий OnSetValue
() и OnGetValue () тип
данных system.Rtti.тvalue можно
рассматривать
как
усовершенствованный
аналог типа
данных Variant, обладающий большим набором сервисных методов.
Упомянем еще одно событие сетки, связанное с
обслуживанием данных
property OnEdititingDone: TOnEdititingDone;
type TOnEdititingDone = prooedure(Sender: TObject;
const Col, Row: Integer)
of object;
На этот раз речь идет о
реакции сетки на любое изменение содержимого ячейки.
ВННМАННЕ!
Сетка TGrid не
обладает
навыками по
хранению
данных -
эту задачу
программист
должен
решить
самостоятельно,
создав
собственное
хранилище.
Полезное
преимущество
сетки
строк тstringGrid
над
обычной
сеткой TGrid за ключается
в том, что
разработчики
компонента
сохранили в нем
традиционное
(для сетки тstringGrid из
состава VCL)
свойство
property Cells[ACol,
ARow: Integer]:
string;
Благодаря
этому свойству
сетку можно
рассматривать
как двухмерный
текстовый
массив, в
котором
каждая
ячейка адресуется
по индексу
колонки и
ряда.
Пример обслуживания текстовых данных
Предположим;
что в нашем
распоряжении
имеется типизированный
файл
area.dat, строки
которого соответствуют
записи TArea, позволяющей
хранить
порядковый номер, название
страны и сведения о ее площади
(листинг 12.1).
![]()
type TArea=packed. record
num:byte; //номер записи
country:string[40]; //название страны
aiea:s.ingie;
end;
//площадь, занимаемая страной
![]()
Попробуем научить сетку TGrid отображать и
редактировать содержимое файла.
Для этого (воспользовавшись редактором колонок Items Editor)
создайте три ко
лонки тstringCol=, специализирующиеся на обслуживании текстовых данных
(рис. 12.2).
![]()
4 Соеди11ен11ые Штаты Амери 9372610 s Бi>а.,-ю,н,,: '8547000 6 Аlsстралия 7Ш850 7 Индия 3287590 а Арrенти11а 2760990 9 l<a311 CТ/lti 2717300 10 Судан 2505810 u Дл)Кl(р 2381740 12 Конго, демо ратическая РС!< 2345410 13 CayдOBCIGl!A Аравия 2218000 14 Мексиu 1972550 15 ИНДОl<е3ИА 1919440 1" 1 n-u• 17 a-4n
Рис.
12.2. Сетка TGrid с тремя
колонками TStringColШNl
Для
работы риложения
нам
потребуется
объявить
одну
глобальную
переменную и
одну
глобальную
константу (листинг
12.2).
![]()
![]()
var Forml: TForml;
FS: TFileStream; //файловый поток const filename='area.dat'; //vrмя файла
Файловый
поток FS появляется
на свет в
момент
создания
формы (листинг
12.3). При
вызове
конструктора
потока
укажем на то,
что поток должен
допускать как
чтение, так и
запись
данных в
файл.
![]()
![]()
procedure
TForml.FormCreate(Sender: TObject);
Ьegin
if FileExists(filename) then Ьegin
FS:=TFileStream.Create(filename,fmOpenReadWrite);
Gridl.RowCount:=FS.Size
div SizeOf(TArea);
end else raise Exception.Create('Фaйл '+filename+' не обнаружен!');
end;
![]()
Для
упрощения
кода
предположим,
что файл
с данными
расположен в том
же
ката логе,
что и исполняемый
файл
приложения.
Если это не так,
то вы можете
развить идею подключения файла, например, воспользовавшись диалогом TOpenDialog (см.
главу 13).
Правила хорошего тона программирования предполагают, что разработчик прило жения не забудет уничтожить созданный вручную объект. Для этой цели восполь зуемся событием OnDestroy () формы (листинг 12.4).
![]()
prooedure TFoпnl.FormDestroy(Sender: TObject);
Ьeqin
FS.Destroy;
end;
После того как мы научили файловый поток загружать данные, перейдем к работе с сеткой. Нам предстоит решить две небольших задачи. В первую очередь, подго товим сетку к отображению данных из файлового потока. Для этой цели нам при годится событие сетки OnGetValue () (листинг 12.5). Обратите внимание, что для поиска требуемой строки в файловом потоке нам понадобился параметр Row, имен но благодаря ему мы позиционируем курсор в необходимом месте потока.
![]()
procedure TFoпnl.GridlGetValue(Sender: TObject;
const Col, Row: Integer;
var Value: TValue);
var A:TArea;
Ьegin
FS.Seek(Row*SizeOf(TArea),soBeginning);//noзициoниpyeм
курсор FS.Read(A,SizeOf(TArea)); //читаем данные из файла
case Col of
О: Value:=TValue.FromVariant(A.nurn);
1: Vslu : ТV3lU@.FromVariant(A.country);
2: Value:=TValue.FromVariant(A.area);
end;
end;
Вторая задача связана с обеспечением возможности редактирования данных в сетке
и сохранения их в файл, на э1:от раз воспользуемся событием onsetValue () (лис тинг 12.6).
![]()
prooedure TFoпnl.GridlSetValue(Sender: TObject;
Вба•t C6l, Row: Integer;
const Value: TValue);
var A:TArea;
![]()
![]()
Ьeqin
FS.Seek(Row*SizeOf(TArea),soBeginning); //переходим в
позицию записи FS.Read(A,SizeOf(TArea)); //считываем старые данные строки
FS.Seek(Row*SizeOf(TArea),soBeginning); //переходим в
позицию записи case Col of //считываем исправления
О:
A.num:=Byte(Value.Asinteger); 1: A.country:=Value.AsString;
2: A.area:=Value.AsVariant;
end;
FS.Write(A,SizeOf(TArea)); //записываем новое значение
end;
Программирование завершено. Мы научили сетку МJУМШИ}J9D11Тв П PЭJIIJfПtpOJШrt
данные, хранящиеся в типизированном файле.
![]()
![]()
ГЛАВА 13
Любой интегративный программный продукт нуждается
и в обратной связ.ис поль зователем. В первую очередь этот контакт необходим для уведомления пользовате
ля о завершении определенной операции или для подтверждения
какой-либо команды.
В
большинстве
случаев для
общения с
оператором
компьютера
приложение
выво дит на экран
диалоговые окна.
Диалоговое
окно
обладает
весьма навязчивым
ха рактером, и для
того чтобы
гарантированно
досrучаться
до пользователя,
в подав
ляющем числе случаев
оно
выводится на
экран в
модальном
режиме
поверх всех
окон приложения.
Такой
на первый
взгляд
назойливый
способ
общения гарантиру
ет, что даже самый
невнимательный
пользователь
уделит
диалоговому
окну хотя бы
немного своего драгоценного
времени, тем
более что
пока пользователь
не за кроет
окно, он не сможет вернуться
к работе с
программой.
В этой
главе мы обсудим
несколько
категорий
диалоговых о'Кон,
от
простейших
окон сообщений
и окон ввода текстовой информации
до сложных диалогов доступа
к файлам.
3АМЕЧАННЕ
BUBRUВfBl(B Fir@MBrtk@y n rраммный
код окон сообщений и диалогов сосредоточен
в модуле ЕМХ. Dialogs.
Окна сообщений
Самое
простое, что мож сделать
программа - оповестить
пользователя
O каком
либо событии текстовой строкой. Окно с
текстовым сообщением выводится про цедурой);
prooedure ShowMessage(oonst Msg: stri.ng
В результате,
если речь идет о Windows, то ровно в
центре
рабочего
стола появится
оюю с текстом, квторый вы ранее
передали в параметр Мsg, в случае OS Х окно со общения
отобразится
под
заголовком
окна приложения.
![]()
![]()
Окна сообщений и
диалоги 193
![]()
Если вы пишете
приложение
для Windows, то,
возможно, вместо
ShowМessage () вам
больше
понравится
процедура
procedure ShowМessagePos(const Msg: string; Х, У: Integer);
Процедура
позволяет
управлять
местом
вывода сообщения.
Для этой
цели в
распо
ряжение программиста
передаются
параметры (х, У), задающие
координаты
левого
верхнего угла
окна.
Если
текстовое
сообщение
окна
достаточно
сложное,
например
содержит
разно типные
значения,
то
следует
обратиться к
процедуре
prooedure ShowМessageFmt(const Msg: string; Params: array of const);
Форматирование
осуществляется
в соответствии
с правилами
форматирования
строк, применяемыми
в функции Format () : в первый
параметр
передается
шаблон сообщения
с форматирующими символами,
во второй
парамеlJ>
направляется
мас сив с данными.
Пример
работы с
процедурой
предложен в листинге.13 .1.
![]()
resourcestring Мsg='Сложное сообщение содержит:'+
#lЗ+'текст - %s'+#13+'целое - %d'+#13+'вещественное - %f';
var s:string;
i:integer; r:real;
Ьegin
s:='строка текста';
i:=555;
r:=111/13;
ShowMessageFmt(Msg, [s,i,r]);
end;
В
результате
выполнения
листинга на
экране
компьютера
появится диалоговое
окно,
представленное на рис. 13.1 (слева для OS Х,
справа для Windows).
![]()
Рис.
13.1. Пример
окон
сообщений, полученных
с помощью
процедуры ShowMessageFmt ()
Все рассмотренные ранее процедуры ShowMessage (), ShowMessagePos (), ShowMessageFmt () предназначены лишь для уведомления пользователя о том или ином событии и никак не влияют на дальнейшую логику выполнения приложения. В том случае, когда операция требует, чтобы пользователь подтвердил ее выполне ние, следует искать помощи у функции MessageDlg ()• В Delphi предусмотрены две перегружаемые версии этой функции, мы рассмотрим самую сложную:
funation MessageDlg(aon■t 'Мsg:
■trinq; DlgType:
TMsgDlgType; Buttons: TMsgDlgButtons; HelpCtx: Longint; DefaultButton:
TMsgDlgBtn): Integer; OV8rload;
В результате обращения к функции в системе Windows на экран выводится диало rовое окно, представленное на рис. 13.2.
Параметр Msg содержит
текст сообщения
Параметр - а.
DlgType определяет
значок и текст заголоека окна
с»о1
rнт •h16ос)и
MIJUT• cocnиrc,yioщy,o _.11.i
, . ..
Параметр
Buttons назначает кнопки
окна
"
Параметр
DefaultBut
ton указывает на кнопку по умолчанию
Рис. 13.2. Окно выбора действия, вызванное функцией MessageDlg () в Windows
В OS Х внешний вид окна несколько отличается (рис. 13.3), однако функциональ ная нагрузка остается прежней.
В параметре Msg задается текст сообщения. Параметр DlgType определяет внешний вид диалогового окна в соответствии с типом TMsgDlgType (табл. 13.1).
![]()
Рис. 13.3. Окно выбора действия, вызванное функцией MessageDlg () в OS Х
![]()
![]()
Окн1з сообщений и диалоги
Таблица 13.1. Оформление окнв вь1борв тмsgDlgТype
|
Значение |
Особенности оформлении окна |
|
mtWarning |
Тревожное оповещение |
|
mtError |
Оповещение об ошибке |
|
mtinformation |
Информационное сообщение |
|
mtConfirmation |
Запрос подтверждения |
|
mtCustom |
Пользовательский вид |
Любое диалоговое окно как минимум содержит хотя бы одну кнопку, нажав кото рую пользователь известит программу, что он ознакомился с сообщением. Еще ча ще в окне диалога располагается несколько кнопок, нажатие которых определяет дальнейшее поведение программы. Например, окно, спрашивающее у пользователя подтверждение на удаление файла, должно содержать кнопки Да (Yes) и Нет (No). Нажатие кнопки Да подтверждает операцию, кнопка Нет отвергнет. В код про граммы диалоговое окно возвращает модальный результат, соответствующий на жатой пользователем кнопке: кнопка Да- константа rnrYes, кнопка Нет- rnrNo, и т. д. Задачей программиста является обработка этого результата. Какие имен но кнопки будут размещены в нижней части окна, определяет параметр
Buttons:TMsgDlgButtons.
type TMsgDlgBtn = (mЬYes, rnЬNo, mЬОК, mЬCancel, mЬAЬort, mЬRetry,
mЬignore, mЬAll, mnNoToAll, mЬYesToAll, mЬHelp);
TMsgDlgButtons = set of TMsgDlgBtn;
Параметр функции HelpCtx предназначен для взаимодействия со справкой прило жения, в нем задается индекс соответствующей диалоговому окну страницы справ ки. Если справка отсугствует, то в параметр передается -1. Параметр DefaultButton определяет, какая из кнопок окна будет находиться в фокусе ввода в момент выво да окна на экран.
В листинге
13.2 приведен
пример
процедуры, удаляющей
файл с именем
FileName. Файл исчезнет с
жесткого диска только
в том случае,
если пользователь подтвердит
удаление
нажатием
кнопки Да.
![]()
prooedure
FileDelete(FileName : string);
Ьegin
if MessageDlg('Yдaлить файл '+TPath.GetFileName(FileName)+' ?. ' ,
TMsgDlgType.mtConfiпnation,
[ТМsgDlgBtn.mЬYes] + [TMsgDlgBtn.mЬNo], 0) = rnrYes then
TFile.DeleteFile(FileName);
end;
![]()
По умолчанию
в Windows
диалоговое
окно выбора
позиционируется в
центре экра
на. Для того чтобы
нарисовать
окно в альтернативном месте, используйте
функцию
function MessageDlgPos(const
Msg: string; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons; HelpCtx: Longint; Х,
У: Integer): Word;
Все
параметры
метода нам
уже знакомы,
единственная
новость - координаты
ле вого
верхнего
угла окна определяются
аргументами х
и У.
Еще одна похожая функция
functiori
MessageDlgPosHelp(const Msg: string; DlgType:
TMsgDlgType; Buttons: TMsgDlgButtons; HelpCtx: Longint; Х,
У: Integer;
const HelpFileName: string; DefaultButton: TMsgDlgBtn): Integer;
позволяет
динамически
подключать
файл со
справкой
прямо к окну
диалога в мо мент
его создания.
- ,
Очень часто для работы приложения недостаточно получать односложные ответы
пользователя:
"Да", "Нет"
или "Отмена".
Программе
могут
потребоваться
фамилия
пользователя,
адрес электронной
почты, пароль
или другие данные. В
таком
случае
окажется
полезной
функция
function InputBox(const ACaption,
APrompt, ADefault: string):
string;
![]()
В результате вызова Inputвox () создается форма, содержащая строку ввода и две
кнопки: ОК и Отмена (Cancel). В
параметре ACaption определяется
заголовок
диалогового
окна, APrompt
содержит
надпись над
строкой
ввода, в ADefault пере
дается значение по
умолчанию. Пример работы
с функцией предложен в
листин ге 13.3.
var s: string;
Ьegin
s:= InрutВох('Создание файла', 'Имя файла', 'Новый файл.tхt');
// ...
Еще более удобная, на взгляд автора, функция
function
InputQuery(const ACaption, APrompt:
string;
. var Value: string): Boolean;
возвращает
не только
содержимое
текстовой
строки (для
этого
предназначен
пара- метр Value), а еще и
логическое
значение,
соответствующее
нажатой
пользователем
кнопки: ОК- true, Отмена
(Cancel) - false. Эта особенность
функции
позволяет
улучшать код
наших
программ
(листинг 13.4).
![]()
![]()
var s: string;
Ьegin
s := 'Новый пользователь';
if InputQuery('Peгиcтpaция', 'Фамилия', s} = true then
{действия, если нажата кнопка ОК}
else {действия, если нажата кнопка Cancel}; end;
Открыв
страницу Dialogs палитры
компонентов
FireMonkey, вы
обнаружите
пять
компонентов-диалогов.
Два
компонента, TOpenDialog и тsaveDialog, вызывают
диа логовые
окна
открытия/сохранения
файлов.
Диалоги TPrinterSetupDialog_ и TPrintDialog соответственно
помогут
подготовить
принтер к
печати и
отправить
задание на
печать. Настройку
размера,
полей и
ориентации
страницы
документа
осуществит
компонент TPageSetupDialog.
Все компоненты-диалоги построены на основе класса тcommonDialog, важнейшим методом которого считается
function Execute: Boolean;
Вызов
потомками тcommonDialog функции
Execute (} приводит
к выводу на
экран
стандартного диалогового
окна, работая
с которым
пользователь взаимодействует с
операционной
системой. При
нажатии кнопки
ОК в
окне диалога
функция воз
вращает значение true, в
противном случае
- false.
В классе описаны два базовых обработчика событий
property OnShow:
TNotifyEvent; property OnClose: TNotifyEvent;
вызываемых соответственно в момент показа и закрытия диалогового окна.
TOpenDia/og и TSaveDia/og
Для
вызова
стандартных
диалоговых
окон, осуществляющих
выбор имени
файла для
обеспечения
дальнейшего
чтения или
записи, предназначены
компоненты TOpenDialog И TSaveDialog.
ЗАМЕЧАНИЕ
Не
стоит
понимать
названия
диалогов
открытия и сохранения
файлов
буквально.
Все эти
компоненты
всего лишь
позволяют пользователю
выбрать имя
файла для его
от крытия
(сохранения),
собственно
программную
логику
открытия
(сохранения)
файла следует
реализовывать
самостоятельно.
![]()
198 Главв 13
При описании диалога открытия или сохранения файла первым действием про граммиста должно быть определение ограничений на имя допустимых файлов. При отображении диалоговое окно на основе фильтра
property Filter: string;
произведет отбор только необходимь х пользователю файлов. Фильтр может быть настроен как в Инспекторе объектов, так и во время выполнения программы. До пустим, для приложения, повторяющего функционал Блокнота, фильтр диалогов открытия и сохранения файлов можно подготовить в момент создания главной формы приложения (листинг 13.5).
![]()
prooedure TFoпnl.FoпnCreate(Sender: TObject);
Ьegin
OpenDialogl.Filter:='Teкcтoвыe
файлыl*.tхt) '; OpenDialogl.DefaultExt:='txt';
SaveDialogl.Filter:=OpenDialogl.Filter; SayeDialogl.DefaultExt:= OpenDialogl.DefaultExt;
end;
Обратите внимание на то, что текстовая строка фильтра включает два раздела: тек стовое описание фильтра и допустимое расширение в имени файла, а между разде лами устанавливается специальный символ 1 .
Допустимо определение сразу нескольких типов файлов в одной строке фильтра:
OpenDialogl. Filter := 'Графические файлы1 *. jpg; *. j peg; *. prig' ;
Если
требуется
программным
образом описать
несколько
строк
фильтра, то
отде ляйте строку от
строки все
тем же
символом-разделителем - вертикальной чер-
той ( 1): .
OpenDialogl.Filter:='Teкcтoвыe файлыl*.tхtlВсе файлыl*.*) ';
В листинге 13.5 упоминается еще одно свойство диалога:
property DefaultExt:
string;
Предполагается,
что при вводе
имени файла в
окне диалога
пользователь
должен ввести
не только
имя, но и
расширение
имени файла.
В случае если
этого не было
сделано, диалог подставляет расширение по умолчанию, заранее определяемое
в свойстве DefaultExt.
ВНИМАНИЕ!
При вводе
расширени_я имени
по умолчанию помните,
что оно
включает только сим
волы
расширения,
точку-разделитель
указывать не
следует.
Если используемый фильтр - многострочный, то следует определить, какая из строк фильтра станет использоваться по умолчанию при появлении диалогового окна. Индекс этой строки передается в свойство
property Filterindex: Integer;
Для определения имени папки, открываемой в
момент первого запуска диалога,
воспользуйтесь свойством
property InitialDir: strirщ;
При
повторных запусках выбор каталога зависит от флага ofNoChangeDir в свойстве
Options диалогового окна
(табл. 13.2).
Свойство
property HistoryList: TStrings;
позволяет
хранить историю о
предыдущих прочитанных/записанных
файлах.
Текст, подлежащий
выводу в
заголовке
диалога,
определяется в
свойстве property Title: string;
Наиболее широкий спектр по
настройке внешнего вида
диалогового окна
предос тавляет свойство
property Options: TOpenOptions;
выступающее множеством опций TOpenOption, представленных в табл. 13.2.
Таблица 13.2.
Основные опции диалогов открытия и
сохранения файлов
|
Значение ТOpenOption |
Описание |
|
ofReadOnly |
Открывает окно в режиме ''только для чтения" |
|
ofOverwritePrompt |
Играет
роль в диалогах
записи
файлов,
запрашивает
разре- шение на перезапись
при совпадении
имени сохраняемого
и
существующего
файлов |
|
ofHideReadOnly |
Скрывает флажок ReadOnly |
|
ofNoChangeDir . |
Если флаг установлен,
то при повторных
запусках
диалога он
открывает папку,
определенную
в свойстве InitialDir. Иначе будет открыт каталог, с
которым велась работа в последний раз |
|
ofShowHelp |
Дополняет
диалог
кнопкой помощи |
|
ofNoValidate |
Отключает контроль за вводом недопустимого символа в
имя файла |
|
ofAllowMultiSelect |
Допускает одновременный выбор нескольких файлов |
|
ofExtensionDifferent |
Наличие
флага
обычно
контролируется
программистом
во время выполнения
приложения.
Он автоматически устанавли-
вается
приложением в
случае, когда расширение
выбранного файла,отличается от заданного в
свойстве DefaultExt |
|
ofPathМustExist |
Вызывает сообщение
об ошибке,
если пользователь
указал
неверный
путь к файлу |
|
ofFileMustExist |
Используется
только в
диалогах
открытия файла.
Вызывает
сообщение
об ошибке,
если пользователь
указал имя несуще-
ствующеrо
файла |
|
ofCreatePrompt |
Работает
совместно с ofFileMustExist, запросит
подтвер-
ждение на
создание
несуществующего
файла |
![]()
![]()
Таблица
13.2 (окончание)
|
Значение 'l'CpenCption |
Описание |
|
ofShareAware |
Разрешает совместный
доступ к одному и тому
же файлу.
Если флаг
отключен, то
запрещает
открывать
файл, уже
используемый
другим процессом |
|
ofNoReadOnlyReturn |
Инициирует
сообщение
об ошибке
при попытке
обратиться
к файлу с
атрибутом
''только для
чтения" |
|
ofNoTestFileCreate |
Опция
применяется
при работе с
разделяемым
сетевым
ресурсом, в котором пользователь
обладает
правом
создания
новых
файлов, но не
может
модифицировать
существующие.
Флаг
отключает
проверку
воз ожности
записи в
сетевом ресурсе |
|
ofNoNetworkButton |
Скрывает
кнопку
доступа к сетевому
ресурсу (используется
только
совместно с
флагом ofOldStyleDialog) |
|
ofNoLongNames |
Показывает
файлы с
форматом
имени 8.3 (используется
только
совместно с
флагом ofOldStyleDialog) |
|
ofOldStyleDialog |
Флаг обратной
совместимости, создает
диалоговое
окно
старого
стиля |
|
ofNoDereferenceLinks |
Определяет
порядок
взаимодействия с
ярлыками
файлов. Если
флаг
отключен, то
диалог
возвращает
реальное
имя файла,
определенное
в ярлыке. Иначе диалог возвращает
имя ярлыка |
|
ofEnaЫeinclu�Notify |
Используется для активизации обработчика события Onincludeitem () |
|
ofEnaЫeSizing |
Разрешает изменять размеры диалогового окна |
|
ofDontAddToRecent |
Пока
не используется,
в будущих
версиях Delphi будет
управ- лять
добавлением
ссылки на файл в
список
недавно
исполь-
зованных
документов |
|
ofForceShowHidden |
Пока не используется,
в будущих
версиях Delphi станет
при-
нудительно включать показ скрытых файлов в
окне диалога |
Стоит напомнить, что фундаментальным методом диалогов открытия и сохранения файла является функция Execute ( ) . Она осуществляет вызов диалогового окна, в котором пользователь производит выбор файла для открытия или указывает имя сохраняемого файла. Если после работы с диалогом была нажата кнопка ОК, метод вернет true. После успешного вызова диалогового окна имя выбранного файла окажется в свойстве
property FileName: TFileName;
Допустим, что мы разрабатываем проект "1'екстового редактора. На главной форме проекта Forml расположены диалоги открытия и сохранения файлов, редактор мно гострочного текста Memol: тмеmо. За вызов диалога открытия файла отвечает собы тие-щелчок по пункту меню miOpen, диалог сохранения вызывается щелчком по кнопке miSave (листинг 13.6).
![]()
![]()
![]()
![]()
![]()
![]()
![]()
var FileName:string; //переменная для хранения имени файла
prooedure TFoпnl.miOpenClick(Sender: TObject); //открытие файла
Ьegin
if OpenDialogl.Execute then
Ьegin
Memol.Lines.Clear;
Memol.Lines.LoadFromFile(OpenDialogl.FileName); FileName:=OpenDialogl.FileName;
Техtl.Техt:='Документ: '+ExtractFileName(FileName);
end; end;
prooedure TFoпnl.miSaveClick(Sender: TObject);
Ьegin
if SaveDialogl.Execute then Ьegin
FileName:=SaveDialogl.FileName;
if FileName<>'' then Memol.Lines.SaveToFile(FileName);
end;
end;
Щелчок по меню miSave вызывает метод Execute () диалога открытия файла, в ре зультате на экране вашего компьютера отобразится окно, позволяющее пользовате лю выбрать текстовый файл.
Теперь уделим немного времени обработке событий в компонентах открытия и со хранения файла. Оба компонента унаследовали события OnShow () и OnClose ( ) от своего предка- класса тcomonDialog. Помимо этого во всех диалогах имеется ряд вспомогательных событий (табл. 13.3).
Таблица 13.3. События диалогов открытия и
сохранения файлов
Событие Описание property OnFolderChange: TNotifyEvent; Происходит
при открытии
или закрытии
папки в
диалоговом
окне property OnSelectionChange: TNotifyEvent; . Возникает
при выборе
пользователем
но- воrо
файла в
списке файлов,
применении
нового
фильтра,
создании
новой папки property OnTypeChange: TNotifyEvent; Вызывается
при
усrановке
пользователем
нового
фильтра property OnCanClose: TCloseQueryEvent; type TCloseQueryEvent = prooedure(Sender: TObject; var CanClose: Boolean) of aЬject; Происходит
при попытке
закрыть
диалога- вое
окно (без
отмены, т. е. без
нажатия
кнопки Отмена
(Cancel)),
параметр CanClose разрешает
(true) или
З'1прещает (false) закрытие
окна
.
![]()
Параметры страницы TPageSetupDialog
Диалог
настройки
параметров
страницы предоставляет
удобный
интерфейс
управ ления
основными
параметрами
бумажной
страницы и
может
пригодиться
в проек тах
текстовых и
графических
редакторов,
при
определении
основных
характери
стик бумажных
отчетов в проектах
баз данных
и при
подготовке
документа к печа
ти. Обращение
к стандартному
для всех
диалогов
методу Execute () вызывает
окно
настройки
страницы
перед печатью.
Работу
с
параметрами
страницы
следует начинать
с
определения
размеров
страни цы. Для
удобства
пользователя
диалоговое
окно
обладает
раскрывающимся
спи ском Размер, в
котором
хранятся
элементы с
заранее предустановленными
разме рами
(АЗ, А4, А5 и т. п.),
ориентация
страницы
выбирается в
группе Ориентация.
Настроенные пользователем
высота и ширина страницы передаются
в пару свойств:
property
PageHeight: Single;
property PageWidth: Single;
Используемые в
диалоге единицы измерения определяются
свойством
property Units: TPageMeasureUnits;
type TPageMeasureUnits = (pmDefault,
prnМillimeters,
pminches);
//локальные установки системы
//миллиметры
//дюймы
![]()
![]()
Кроме размеров•страницы диалог позволяет пользователю определять поля. Для
этого
предназначена
четверка
свойств:
property
MarginLeft: Integer; //левый отступ
property MarginRight: Integer; //правый отступ
property MarginTop: Integer; //верхний отступ
property MarginBottom: Integer; //нижний отступ
Минимально допустимые значения полей ограничиваются соответствующими
свойствами:
property
MinМarginLeft: Integer; property MinМarginRight: Integer; property MinМarginTop: Integer; property MinМarginBottom: Integer;
Особенности
диало,гового
окна определяет
классическое
свойство property
Options: TPageSetupDialogOptions;// [psoDefaultMinМargins] Опции
представлены
в табл. 13.4.
Таблица 13.4. Опции диалога TPageSetupDialogOptions
|
Опция . |
Описание |
|
psoDefaultMinмargins |
Определяет ограничение
на минимальное
значение полей
страницы, которые могут быть назначены пользователем.
Значения
определяются установками
текущего
принтера |
![]()
![]()
Таблица
13.4 (окончание)
|
Опц•ия |
Описание |
|
psoDisaЫeMargins |
Запрещает пользователю настраивать поля страницы |
|
psoDisaЬleOrientation |
Запрещает изменять ориентацию страницы |
|
psoDisaЬlePagePainting |
Отменяет режим прорисовки диалога по умолчанию |
|
psoDisaЬlePaper . |
Запрещает изменять размер бумаги и настраивать
особен-
ности ее
подачи |
|
psoDisaЬlePrinter |
Отключает кнопку вызова дополнительного диалога настройки
принтера |
|
psoMargins |
Устанавливает все поля страницы в
1 дюйм |
|
psoмinмargins |
Устанавливает
минимальные
поля из
свойств MinмarginLeft,
MinмarginRight, MinмarginBottom и MinмarginTop, в
против- ном случае эти поля определяются
параметрами принтера |
|
psoShowHelp |
Показывает кнопку помощи |
|
psoWarning |
Отключает сообщение об ошибке при отсутствии установлен- наго
принтера |
|
psoNoNetworkButton |
Скрывает и отключает кнопку сетевых
устройств |
Настройка печати TPrinterSetupDia/og
Класс TPrintersetupDialog вызывает стандартное окно настройки параметров прин тера. Из всех существующих в Delphi компонентов-диалогов диалог настройки принтера самый неприхотливый в программировании. После выбора компонента
• в Инспекторе объектов мы обнаружим 5 опубликованных свойств (Name, таg, HelpContext, BindingName и StyleName) и 2 события (onclose и OnShow). Столь скром ный список свойств объясняется тем, что все сделанные пользователем настройки принтера не возвращаются в наше приложение, а автоматически обрабатываются системой и учитываются при отправке задания на печать.
Отправка задания на печать TPrintDialog
В сравнении с диалогом настройки принтера диалог печати задания отличается су щественным спектром параметров. Как и у всех диалогов, любимым методом диа лога печати является функция Execute () . В результате на экране компьютера воз никнет стандартное диалоговое окно, позволяющее выбрать принтер, установить диапазон распечатываемых листов и число копий.
ВНИМАНИЕ/
Диалог
печати
только
формирует
параметры
задания для.
печати,
сам процесс
печати должен быть реализован силами программиста. Помощь в
этом ему сможет оказать
программный
модуль FМХ. Printer.
![]()
![]()
Особенности
поведения
диалогового
окна определяются
опциями,
устанавливае
мыми в
свойстве
property Options:
TPrintDialogOptions;
Значения флагов свойства приведены в табл. 13.5.
Таблица 13.5. Опции диалога печати TPrintDialogOptions
|
Значение |
Описание |
|
|
poPrintToFile |
Выводит флажок перенаправления задания печати в
файл |
|
|
poDisaЬlePrintToFile |
Запрещает
печать
в файл
(делая
флажок Печать в файл неак-
тивным) |
|
|
poHelp |
- |
Отображает кнопку помощи в окне
диалога |
|
|
||
|
poPageNшns |
Разрешает пользователю
выбирать диапазон страниц,
отправ-
ляемых на
печать. В
противном
случае
будет
отправлен
весь
перечень страниц |
|
|
poSelection |
Разрешает пользователю
печатать только выделенный
фрагмент текста |
|
|
poWarning |
Генерирует
сообщение
об ошибке
при попытке
отправить
зада- ние на
неустановленный
принтер |
|
Большинство
свойств
диалога тем
или иным образом
взаимодействует
с опциями
окна печати.
При использовании
флагов poPageNums и poSelection пользователь
по лучает
право
отправлять
на печать
несколько
страниц или
фрагмент
задания. Ре
зультат
выбора
пользователя
отразится в
свойстве
property
PrintRange: TPrintRange;
При выборе для печати только фрагмента текста свойство PrintRange примет значе- · ние prSelection,
при
выборе
нескольких
страниц - prPageNums.
Если
на печать
от правляется весь док
ент, то
результат
будет
соответствовать prAllPages.
Совместно с опцией диалога печати poPageNшns трудятся свойства
property FrornPage: Integer;
property ToPage: Integer;
Значения свойств определяют начальную и
конечную страницы диапазона печати. В
случае если
оба свойства установлены в О, то
на печать
направляется весь
доку мент.
При особом
желании
допустимо
ограничить
возможности
пользователя
по опреде
лению
диапазона
направляемых
на печать
страниц.
property MinPage: Integer;
property MaxPage: Integer;
По умолчанию значения свойств равны
О, т. е.
ограничения отсутствуют. При вы
ходе за
границы
генерируется
сообщение об
ошибке.
![]()
![]()
![]()
![]()
![]()
Если допустимо
перенаправление 1адания печати в
файл и
пользователь воспользо
вался такой возможностью, то свойство
property PrintToFile: Boolean;
примет значение tru�.
Количество копий
предназначенного для
печати
документа вы
обнаружите в
свой стве
property
Copies: Int�ger;
Если число соответствует О
или 1; то будет отпечатан только один экземпляр.
При печати нескольких
копий имеет значение свойство
property Coliate:
Boolean;
Благодаря ему вы укажете
принтеру порядок подбора листов в
копии документа.
![]()
![]()
ГЛАВА 14
Сейчас нам предстоит изучить компоненты FireMonkey, связанные с обработкой таких специфичных данных, как дата и время. Но прежде чем мы приступим к рас смотрению героев этой главы, на палитре компонентов нам предстоит получить ответ на самый важный вопрос: каким образом Delphi хранит значения даты и вре мени?
Дата и
время TDateTime
Разработчики Delphi для работы с датой и временем стали использовать обычный вещественный тип данных DouЬle. Правда, для того чтобы акцентировать внимание программиста на то, что все-таки речь идет о именно дате и времени, а не о рядо вом действительном числе, в модуле System бьm qб явлен базовый тип данных
type TDateTirne = type DouЬle;
Идея хранения значений даты и времени в TDateTirne заключается в следующем: целая часть числа предназначена для запоминания даты, а значение после запя той - времени. Нулевому значению соответствует время: 30.12.1899 г. 00 ч. 00 м. 00 с. ООО мс. Для того чтобы "сдвинуть" дату ровно на сутки вперед и получить значение 31.12.1899 г. 00:00:00:000, достаточно прибавить к переменной типа TDateTirne единицу, вычитание единицы повернет дату вспять, и мы окажемся в 29.12.1899 г. Значению 36526,0417 соответствует 01.01.2000 г. 01:00:00.
ЗАМЕЧАНИЕ
Верхний. предел типа данных TDateTirne соответствует значению 31.12.9999 г. 23:59:59:999. Кроме того, TDateTirne допускает работу и
с отрицательными значениями.
В дополнение к классу тоаtетi�е в Delphi прилагаются производные типы данных:
type TDate type TDateTirne; //целая часть, только для хранения даты
![]()
type TTirne = type TDateTirne; //дробная часть, только для хранения времени
![]()
Д1зт1з и время 207
Тип данных тoateTime является основным, но далеко не единственным способом описания даты и времени. Например, для представления времени с точностью больше чем тoateTime (до миллисекунд) предназначена запись
type TTimeStamp = record.
Time: �nteger; //миллисекунды от полуночи
Date: Integer;
//количество дней, начиная с
01.01.0001 г.
end;
При работе с интервалом времени пригодится еще более сложная структура
TTimeSpan.
В той ситуации,
когда
программная
логика предполагает
работу не с текущим
вре менем, а с
временным
интервалом
(например,
при оценке
текущей
позиции вос
производимого
трека в
медиаплеере тмediaPlayer из
состава
компонентов FMX), то
стоит
воспользоваться
услугами
структуры TTimespan.
Структура TTimeSpan определена в модуле System. Timespan и представляет собой не просто хранилище полей, а интеллектуальный класс, с помощью которого про граммист с легкостью осуществит все основные преобразования данных временно го интервала.
Для инициализации структуры допускается воспользоваться конструктором. В про стейшем случае следует обратиться
constructor Create(ATicks: Int64); overload;
•
Единственный параметр конструктора - значение временного интервала, едини
цей измерения которого служит 100 наносекунд. Кроме того, предусмотрено не сколько перегружаемых версий конструкторов, в которых протяженность интерва ла можно задавать в часах, минутах, секундах и миллисекундах:
constructor Create(Hours, Minutes, Seconds: Integer);
overload; constructor Create(Days, Hours, Minutes, Seconds:
Integer); overload;
constructor
Create(Days, Hours, Minutes, Seconds,
Milliseconds: Integer); overload;
В
состав
класса
входит
несколько
десятков
операторов,
свойств и методов,
позво ляющих
осуществлять
манипуляции
с интервалом
времени.
Например, для
увели чения
интервала
следует
просто воспользоваться
операцией
сложения (+).
Ряд доступных только для чтения свойств позволят нам декомпозировать и возвра тить хранящееся в структуре значение интервала:
property Ticks: Int64; //значение в
тактах property Days: Integer; . //значение в
сутках property Hours: Integer; //значение в
часах property Minutes: Integer; //значение в
минутах property Seconds: Integer; //значение в
секундах
property Milliseconds: Integer; //значение в миллисекундах
208 • Глава 14
Невиз)'ЗЛьный компонент ттirner мы обнаружим на странице System палитры ком понентов, компонент описан в модуле ЕМХ. Types и построен на базе класса TFrnxObject. Таймер предназначен для генерации особых данных- сообщений об изменении времени. Дпя этой цели таймер с заданной периодичностью вызывает с0бытие
property OnTirner:
TNotifyEvent;
в котором следует описать реакцию нашей программы на изменение времени.
Ключевое свойство таймера
property Interval: cardinal; //по умолчанию 1000 миллисекунд
определяет периодичность срабатывания таймера.
Таймер управляется
ещшственным
свойством property EnaЫed: Boolean;
Установив EnaЫed в true, мы заставим компонент отсчитывать миллисекунды, пе ревод свойства в состояние false остановит таймер.
Пример из листинга 14.1 демонстрирует процесс создания секундомера. На этот раз, кроме метки и таймера нам понадобится еще пара кнопок твutton. Кнопка btnStart возьмет на себя ответственность за старт, а кнопка btnStop - за останов секундомера (рис. 14.1).
На этот раз для реализации секундомера предлагаю задействовать структуру интервал TTirneSpaп, для это,го не забудьте подключить в строку uses модуль
Systern. TirneSpan.
......-..,._..........
_,.. . ...........,.....,_ ....."' ...·--•-"'""······ ·..
![]()
• -······"'·"' --···---······..,.. ............. ................... '"--········••«...........-........_........ ..,,........ ..-
uses ... , Systern.TirneSpan;
var Forrnl: TForrnl;
TS:TTirneSpan; //интервал времени
implementation
{$R *. frnx}
prooedure
TForrnl.ForrnCreate(Sender: TObject);
Ьegin
Tirnerl.EnaЫed:=false; //таймер отключен
//инициализация
![]()
Tirnerl.Interval:=50; //период генерации события - 50 мс Labell.Text:='00:00:00.000';
end;
prooedure TForrnl.btnStartClick(Sender: TObject); //Старт
Ьegin
TS:=TTirneSpan.FromМilliseconds(0); //сброс значения интервала
Дата и время 209
Labell.Text:='00:00:00.000'; //сброс текста метки
Tirnerl.EnaЬled:=True;
end;
//старт таймера
![]()
![]()
procedure TForml.TimerlTimer(Sender: TObject); //Такт таймера
Ьegin
TS:=TS+TTirneSpan.FromМilliseconds(Timerl.Interval); //приращение
Labell.Text:=Format('%.2u:%.2u:%.2u.%.3u',
[TS.Hours,TS.Minutes,TS.Seconds,TS.Milliseconds]);
end;
procedure TForml.btnStopClick(Sender: TObject); //Стоп
Ьegin
Tirnerl.Enabl'ed:=false; //стоп таймера
end;
![]()
Рис.14.1. Секундомер для мобильной платформы IOS
Календари TCalendar и TCalendarEdit
На странице
Additional палитры
компонентов
FireMonkey
обосновались
два
ком понента,
способные
исполнять
роль календарей.
Это простой
календарь тcalendar и элемент
управления тcalendarEdit, представляющий
собой
симбиоз
комбиниро ванного
списка
и
выпадающего из него
календаря.
![]()
![]()
210 Глава 14
Оба наших компонента построены на фундаменте общего предка - классе
тstyledControl, отвечающего за стилевое оформление визуальных компонентов.
Целевая
функция
календарей - позволять
пользователю
производить
выбор даты,
поэтому
большинство
методов и свойств
календарей
направлены
на
обеспечение
этой цели.
Значения
выбранных в календаре
даты и времени
мы обнаружим
в свой
стве
property Date: TDate;
Свойство
oate может применяться
и для установки
текущей даты
календаря из кода программы.
Два
базовых
события,
связанных с
деятельностью
календаря,
обеспечивают
реак цию
элемента управления
на
выбор
пользователем
даты
property OnDateSelected: TNotifyEvent;
и на изменение текущей даты
property OnChange: TNotifyEvent;
Под
выбором даты
следует
понимать
выделение
пользователем даты
на календаре с
помощью указателя
мыши
или выбор
даты на
сенсорном
экране.
В
России
трудовая
неделя
стартует в
понедельник,
в США и
Англии - в воскре
сенье. Для того чтобы
исключить
путаницу,
программисты
Embarcadero предложи
ли нам
самостоятельно определиться,
с какого дня
должна
начинаться
неделя в на
ших
программах.
Для этого в праотце
всех
календарей
объявлено
свойство
property FirstDayOfWeek: TCalDayOfWeek;
type TCalDayOfWeek = (dowMonday, dowTuesday, dowWednesday, dowТhursday,
dowFriday, dowSaturday, dowSunday, dowLocaleDefault);
Здесь: dowMonday - понедельник, ..., dowsunday- воскресенье, dowLocaleDefault -
день недели определяется автоматически локализацией версии Windows.
![]()
Рис. 14.2. Поведение календаря TCalendarEdit в IOS
![]()
Двтв иврвмя 211
![]()
Отметим метаморфозу, на которую способен компонент тcalendarEdi t, когда он задействуется
в
приложениях
для IOS. Здесь, в момент
редактирования
даты вызы вается фирменный календарь мобильных устройств
Apple (рис. 14.2).
К дополнительным особенностям календаря тcalendarEdi t стоит отнести наличие
специфичных
событий,
связанных с
"выпадением"
календаря из
комбинированного
списка
propert;y OnPopup: TNotifyEvent;
и свертыванием календаря
propert;y OnClosePopup: ТNotifyEvent;
![]()
![]()
![]()
ГЛАВА 15
Любой из цветов радуги, который вы увидите на экране монитора, получается в результате сложения (в той или иной пропорции) всего-навсего трех базовых цве товых составляющих: красного, синего и зеленого цветов. Такую цветовую модель называют RGB, по первым буквам английских названий цветов (Red, Green и Вlue).
Почему выбраны именно эти цвета? Все объясняется физиологическими особенно стями строения человеческого глаза, он воспринимает цвета в диапазоне длин волн примерно 400 + 700 нанометров. Излучения с длинами волн от 380 + 470 нм имеют фиолетовый и синий цвета, от 480 + 500 нм - сине-зеленый, от 51О + 560 нм - зеленый, от 570 + 590 нм - желто-оранжевый, от 600 + 760 нм - красный. Вос приятие цвета глазом человека осуществляется за счет трех типов цветочувстви тельных фоторецепторов (колбочек). Существуют всего три типа этих фоторецеп торов - колбочки, реагирующие на красный, зеленый или синий цвет. В зависимо сти от интенсивности той или иной цветовой составляющей соответствующий фоторецептор отправляет сигнал в наш головной мозг, где и формируется резуль тирующая картинка.
Представление цвета ARGB
Представление цвета в FireMonkey изначально ориентировано на полноцветную 32-битную модель, сочетающую в себе три классических 8-битньrх канала цветно сти RGB и альфа-канал А, отвечающий за прозрачность закрашиваемого пиксела. Таким образом, информация о цвете хранится в формате ARGB и описывается типом данных:
TAlphaColor.= type Cardinal;
В младшем байте находится значение синей составляющей, во втором байте -
зеленой, затем следует красная составляющая цвета и
шествие завершается стар
шим байтом альфа-канала. Нулевое значение альфа-канала соответствует пол
ностью прозрачному режиму, значение
255 ($FF)- абсолютно непрозрачному ре жиму работы.
![]()
![]()
![]()
Дпя
назначения
цвета можно
сразу
воспользоваться
записью в
шестнадцатеричном
формате,
например,
значение $FF0000FF соответствует
·непрозрачному
чистому
си нему
цвету, $FFFF0000 - непрозрачному красному, значение $7F00FF00 - наполо
вину
прозрачному
зеленому.
Однако это не
всегда
удобно,
особенно
когда следует
получить тот
или иной
оттенок.
Поэтому, чтобы
сразу
усвоить
правила
формирова
ния цвета в
формате ARGB, стоит познакомиться с
небольшим примером. Дпя это го
нам понадобятся:
□ четыре компонента-ползунка
ттrасkваr, которые будут отвечать за регулировку
интенсивности
того или иного цвета и уровня
прозрачности.
Переименуйте
ком поненты
в tbRed,
tbGreen, tbВlue и tbAlpha. Присвойте
свойству Мах всей
четвер ки
значение 255;
□ фигура Circlel: TCircle позволит увидеть изменения в цвете;
□ текст тextl:ттехt отобразит числовое значение выбранного цвета.
·-·
---··--·-·
. Выберите событие OnChange
·() улюбого
из компонентов
TTrackBar и внесите
в него
строки кода,
предложенные
в листинге 15.1.
![]()
•••·•·•.. f' ..,,...............':'lo.-... ••···•·"'r.,.,,.,.••. •.,1..........----..-.,A,,,...••.•..... .....•....-.,.,- ,..............,.........,..t'.f........................ ••,.,,.,.,M.. .-t
function ARGB(const A,R,G,B:byte) :TAlphaColor; '
Ьegin
Result:=B + G SНL 8 + R SНL 16+ А
SНL 24;
end;
procedure TFoпnl.tbRedChange(Sender: TObject);
var R,G,B,A:byte;
AC:TAlphaColor;
Ьegin
R:=Round(tbRed.Value); G:=Round(tbGreen.Value); B:=Round(tbBlue.Value); A:=Round(tbAlpha.Value);
//значение R
//значение G
//значение в
//значение А
![]()
AC:=ARGB(A,R,G,B); //формирование значения цвета ARGB Circlel.Fill.Color:=AC; //nерекраска фигуры
Textl.Text:='h'+IntToHex(AC,8);
end;
Если
вам не понравилась придуманная нами
функция ARGB ( ) из
листинга 15.1, то
для
формирования
значения
цвета в
формате TAlphaColor можете
восп,ользоваться
библиотечной
функцией
function
MakeColor(R, G, В: Byte;
А: Byte = $FF): TAlphaColor;
Дпя завершения
задуманного осталось
сделать событие onchange
() общим для всех
компонентов-ползунков и
нажать клавишу <F9>. Результат нашей работы пред-
![]()
![]()
![]()
![]()
![]()
Рис. 15.1. Интерфейс приложения ARGB
ставлен
на рис. 15.1. К сожалению,
черно-белая
печать книги
не способна
передать полученную
комбинацию
цвета hFF84CA47 ...
Стандартные цветовые комбинации
Создавая собственные
цветовые оттенки, мы сможем раскрасить
приложение
во все
цвета радуги,
но далеко
не
всегда стоит
так делать. В бизнес-проектах более
полез ным окажется
не вычурный,
а строгий деловой стиль с
цветовой
гаммой,
подобран ной
специалистами.
Существенное подспорье в этом сможет оказать
объявленная
в модуле
System. UITypes запись TAlphaColorRec.
В составе
записи TAlphaColorRec определено
несколько
десятков
констант (лис
тинг 15.2),
обращаясь к
которым
программист
получит
значение заранее
предопре деленных цветов
по их
символьным
именам.
![]()
TAlphaColorRec = record const
Alpha = TAlphaColor($FF000000);
AliceЬlue = Alpha or TAlphaColor($FOF8FF); Antiquewhite = Alpha or TAlphaColor($FAEBD7); Aqua = Alpha or TAlphaColor($00FFFF); Aquamarine = Alpha or TAlphaColor($7FFFD4);
![]()
![]()
![]()
![]()
Компоненты цветовой модели ARGB
Библиотека
визуальных
компонентов
VCL и не могла
мечтать о
таком
разнообра
зии компонентов,
нацеленных
на
предоставление
пользователю
возможности
выбора
цветового значения,
как в библиотеке
FireMonkey. Чтобы
проверить это утверждение,
предлагаем
открыть
страницу
компонентов Colors, здесь
вы обнару
жите более
десятка
элементов,
способных
удовлетворить
самые притязательные
запросы
разработчиков. Среди
них 4 компонента
специализируются
на
представле
нии цвета в
формате ARGB (рис. 15.2):
1.
Цветовая
панель тcolorPanel наиболее
удобна с
точки зрения
быстрого форми
рования
цвета любого
оттенка и прозрачности.
Панель
представляет
собой
ком
бинацию двух
компонентов-ползунков
(управляющих
оттенком
цвета и
альфа
каналом).и
собственно
цветовой
панели.
2. Комбинированная
цветовая
панель тсоmЬоСоlоrвох.
В
свернутом
виде элемент
управления
отображает
выбранный
цвет, в
развернутом
выводит на экран па нель, аналогичную тcolorPanel.
3.
Комбинированный цветовой список тсоlоrСоmЬовох.
4.
Цветовой список TColorListBox.
![]()
![]()
![]()
![]()
![]()
Рис. 15.2. Компоненты цветовой модели ARGB
Перечисленные
компоненты
обладают схожим
перечнем свойств и методов, основ
ные из
которых
представлены
в табл. 15.1.
ЗАМЕЧАНИЕ
Компоненты-списки TColorListBox и TColorComЬoBox
можно считать нашими давни
ми
знакомыми, в главе 10 мы подробно изучили все их родовые черты.
![]()
Таблица
15.1. Основные свойства и
события TColorPanel, ТСотЬоСоlоrВох,
TColorListBox и ТСоlоrСотЬоВох
|
Свойства/события |
Описание |
|
property Color: TAlphaColor; |
Выбранный цвет |
|
property OnChange: TNotifyEvent; |
Событие, генерируемое в
момент
изменения цве.та |
|
property UseAlpha: Boolean; |
Использование канала прозрачности (только для TColorPanel и TComЬoColorBox) |
Компоненты цветовой модели
HSL
Модель
воспроизведения
цвета на
основе красного,
зеленого и синего
цветов весь
ма
распространена,
но далеко не
единственна.
Кроме RGB
существует еще
не сколько
часто
используемых
цветовых
моделей. Одна
из них - модель
HSL (Hue, Saturation, Lightness (lntensity)). Если
вы хорошо
знакомы с
английским,
то уже
до гадались,
что в цветовой
модели HSL
главными
действующими
лицами
выступают не
значения
цветов (как в RGB), а тон,
насыщенность
и
интенсивность.
Компоненты TColorPicker и TColorQuad
В составе
FireMonkey имеется
ряд
элементов
управления,
специализирующихся
на работе с
цветом в
формате HSL. В
первую
очередь это TColorPicker и тcolorQuad.
Представленные компоненты-союзники обычно эксплуатируются совместно (рис. 15.3).
Компонент
TColorPicker представляет
собой шкалу,
состоящую из
всех цветов
ра дуги (от
красного до фиолетового
оттенка). Для
того чтобы
пользователь
подобрал
требуемый
цветовой тон,
в
распоряжении
элемента
управления
имеется
свойство
property Hue: Single; //диапазон от О до 1
способное изменять цветовой оттенок в
диапазоне от О до 1.
![]()
Н5<.

1-\;.;t 0.2 1i-73-:!7437 f
S№r1tiМJ,to9S2.s&.."087707S
O..S1141.?S5%31%
![]()
Рис. 15.3. Выбор цвета в
формате HSL с помощью TColorPicker и TColorQuad
![]()
![]()
При необходимости
текущее
значение цвета можно
считать (или записать) в более
привычном для программиста
Delphi формате
ARGB. Для этого предназначено
свойство
property Color: TAlphaColor;
Для установления связи между TColorPicker и компQНентом TColorQuad, отвечаю
щим за управление насыщенностью и интенсивностью, воспользуемся свойством
property ColorQuad:
TColorQuad;
В свою
очередь
компонент тcolorQuad, получив
от своего
коллеги значение
цвето вого тона,
помещает его в
свое
свойство
property Hue: Single;
и предоставляет
в распоряжение
пользователя
квадратное поле, в
границах
которо
го
изменяется
насыщенность
property Sat: Single; //диапазон значений от О до 1
и интенсивность
property Lum: Single;
//диапазон значений от О до 1
исходного цвета.
В
момент
изменения
параметров
цвета у компонента
тcolorQuad генерируется
со бытие
property OnChange: TNotifyEvent;
При желании
(если
требуется
дополнительная визуализация
результирующего цве та)
можно
воспользоваться
услугами
дополнительного
компонента тсоlоrвох.
Для
подключения
к элементу
управления тсоlоrвох
задействуется
свойство
property ColorBox: TColorBox;
На тсоlоrвох возложена самая простая задача -
пересчитать цвет из
формата HSL в формат
ARGB и
возвратить
полученное
значение в свойстве
property Color: TAlphaColor;
Впрочем,
для
конвертации
цвета из
формата HSL в
формат ARGB
необязательно
задействовать
элемент управления
тcolorBox, вместо этого можно
воспользоваться
библиотечными
функциями,
обеспечивающими
взаимное
преобразование
цвета из
модели
HSL в RGB, и
наоборот
function
HSLtoRGB(H, S, L: Single):
TAlphaColor;
prooedure RGBtoHSL(RGB: TAlphaColor; out Н, S, L: Single);
Цветовые
полосы THueTrackBar,
TAlphaTrackBar и TBWТrackBar
Объединив
три элемента
компонента в категорию
"цветовые
полосы",
мы несколь
ко
преувеличили. По
большому
счету из анонсированной тройки
только
один ком-
понент имеет прямое отношение к цвету- это полоса цветового тона тнueTrackBar. Оставшиеся два компонента отвечают за прозрачность (тAlphaTrackВar) и градации серого (твwтrackBar). Однако перечисленные компоненты функционируют по еди ному принципу (они являются наследниками цепочки классов "ттrackBar - твi tmapTrackBar"), посему логически правильно рассмотреть их совместно.
Строго говоря, отличие между THueTrackBar, TAlphaTrackBar и TBWTrackBar чисто косметическое. Фактически это один и тот же элемент управления, отличающийся от своих коллег лишь внешним видом.(р:ие. 15.4).
![]()
Рис. 15.4. Приложение с компонентами TAlphaTrackВar, THueTrackBar и TBWTrackВar
Все рассматриваемые компоненты обладают идентичным перечнем свойств и ме тодов. Наиболее важные из них
property Min: Single; //по умолчанию О property Мах: Single; //по умолчанию 1
определяют границы интервала и информируют
property
Value: Single;
о . текущем положении ползунка. В момент перемещения ползунка генерируется событие
property OnChange: TNotifyEvent;
Все остальное
в руках
программиста. Например,
листинг 15.3 демонстрирует поря
док
назначения
градации
серого цвета
с помощью
компонента твwтrасkваr.
.............................. ...............,.,.,. ...,.·•········..., .....,..,_ ·······•······•·•·•······•·•···.., . ,:. ,................. ................,,.,,................. .......,.,_,.,.,.,.,,........-,.,.........................
i в..... --- -
proaedure TForml.
BWTrackВarlChange (Sender: TObject); ' var GRAY:byte;
Ьegin
GRAY:=Round($FF*BWTrackBarl.Value);
Rectanglel.Fill.Color:=GRAY + GRAY SНL
8 + GRAY SНL 16+ $FF SНL 24;
end;
![]()
![]()
Градиентная заливка TGradientEdit
В составе
компонентов
FireMonkey
предусмотрен
элемент управления
TGradientEdi
t позволяющий
устанавливать
правила градиентной
заливки
областей. Ключевое свойство
компонента
property Gradient:
TGradient;
представляет ссылку на объект TGradient, отвечающий за описание правил по строения градиента (табл. 15.2).
![]()
Таблица 15.2. Класс TGradient
|
Свойства и
методы |
Описание |
|
property Color: TAlphaColor; |
Начальный цвет градиентной заливки |
|
property Colorl: TAlphaColor; |
Конечный цвет градиентной
заливки |
|
function InterpolateColor(Offset: Single) : TAlphaColor; |
Функция интерполирует цвет градиента. Пара-
метр Offset должен
находиться
в диапазоне
значений от
О до 1 |
|
property Points: TGradientPoints; |
Коллекция
точек,
которые
используются
для доступа к цвету и отступу области градиентной
заливки |
|
property Style: TGradientStyle; |
Стиль
заливки:
линейный gsLinear или ради-
альный gsRadial |
|
property StartPosition: TPosition; |
Отправная
точка для
линейной
градиентной
заливки
назначается
в виде
прямоугольной
области с размерами, нормированными к
еди- нице
(координаты
левого
верхнего
угла - (О, О), координаты правого нижнего угла - (1,
1)) |
|
property StopPosition: TPosition; |
Конечная точка линейной градиентной заливки
(от (О, О) до (1, 1)) |
|
property RadialTransform: TTransform; |
Правила
преобразований,
применяемых
для радиальной
заливки. В данном
случае
среди
свойств класса TTransform самое важное ме- сто
занимает RotationCenter. Именно
оно
определяет
центр
заливки |
![]()
ВНИМАНИЕ!
Для настройки направления линейной градиентной
заливки и шага интерполяции цве та
(плавности
заливки)
следует
воспользоваться
свойствами StartPosition и
StopPosition (см. табл. 15.2).
По
умолчанию
эти свойства
установлены в состояние
(О, О) и (О, 1) соответственно и указывают на то, что градиент направлен
сверху вниз.
Компонент TGradientEdi t представляет собой полосу с двумя точками-ползунками
(рис. 15.5), при выборе любого из ползунков генерируется событие
property OnSelectPoint: TNotifyEvent;
Для
того чтобы
определигь,
какой именно
из ползунков
бьm выбран,
стоит проин
спектировать
свойство
property CurrentPoint: Integer;
Если
выбран левый
ползунок, то
свойство возвратит
нулевое
значение. О
выборе
правого
ползунка
будет
свидетельствовать
единица.
В момент
изменения
любых параметров градиентной
заливки компонент
Fенериру ет
событие
property OnChange: TNotifyEvent;
Для
того чтобы
оценигь
возможности
компонента TGradientEdi t, предлагаем
по вторигь
небольшой
пример. Для
этою · разместиге
на форме
компонент GradientEditl:TGradientEdit и два
комбинированных
списка
выбора цвета TComЬoColorBox (рис. 15.5).-
TComЬoColorBox
TGradientEdit ..--
Рис.
15.5. Демонстрационное приложение управления градиентной заливкой с
помощью TGradientEdit
Комбинированные
списки тсоmЬоСоlоrвох
выбора
цвета
предназначены
для управ
ления цветом
заливки. Для назначения
цвета мы
воспользуемся
событиями onchange () этих
компонентов
(листинг 15.4).
![]()
procedure TFoпnl.ComЬoColorBoxlChange(Sender: TObject);
Ьegin
GradientEditl.Gradient.Color:=ComЬoColorBoxl.Color;
end;
procedure TFoпnl.ComЬoColorBox2Change(Sender: TObject);
Ьegin
GradientEditl.Gradient.Colorl:=ComЬoColorBox2.Color;
end;
![]()
![]()
![]()
![]()
![]()
![]()
· Процесс заливки лучше всего
осуществить в
рамках события
OnPaint (), генерируе
мого
в момент
перерисовки единственной формы
проекта (листинг
15.5).
![]()
prooedure TForml.FormPaint(Sender: TObject;
Canvas: TCanvas;
const ARect: TRectF);
Ьegin
with Canvas do
if BeginScene then
Ьegin
Fill.Kind:=TBrushКind.bkGradient;
Fill.Gradient:=GradientEditl.Gradient; FillRect(ARect,0,0, [],1);
EndScene;
end; end;
Для
того чтобы форма
немедленно откликалась на все манипуляции пользователя,
воспользуемся
событием OnChange ()
компонента
GradientEditl (листинг
15.6).
![]()
prooedure
TForml.GradientEditlChange(Sender: TObject);
Ьegin
Forml.OnPaint(Sender,Forml.Canvas,Forml.ClientRect);
Expanderl.Repaint;
end;
![]()
![]()
ГЛАВА 16
В классических приложениях VCL для Windows весь механизм графического вы вода построен на фундаменте GDI (Graphics Device Interface, интерфейс графиче ских устройств). Для того чтобы нарисовать линию или вывести строку текста, программисту достаточно получить доступ к контексту нужного графического уст ройства и воспользоваться услугами стандартных функций двухмерной графики Windows. Несмотря на относительную "древность", GDI превосходно справляется с большинством задач, которые решают деловые приложения. А если программист ставит перед собой более амбициозные цели, то в качестве инструментария Microsoft предлагает воспользоваться GDI+, Direct2D и DirectЗD.
Скажем несколько слов о графическом механизме OS Х. Не стоит доказывать, что эта ОС ничего не знает ни о GDI, ни о DirectX. В OS Х работа с двухмерной графи кой осуществляется силами QuickDraw и Quartz 2D, а трехмерные сцены создаются на основе графической библиотеки OpenGL.
В проектах VCL объектно-ориентированным воплощением контекста графического устройства GDI выступал, выступает и наверняка еще очень долго будет выступать хорошо нам знакомый класс тcanvas. В двухмерных проектах FireMonkey на по прище графики трудится одноименный тcanvas (модуль FМX.Types), но для того чтобы одновременно стать полезным OS Х, Windows и Android, он организован по другому.
Для достижения универсальности принципиально изменена иерархия предков. Те перь в родительском списке мы обнаружим интерфейс
type TCanvas = class aЬstract (TinterfacedPersistent)
Интерфейс TinterfacedPersistent является прародителем всех объектов, способных сохранять в памяти и загружать из памяти значения своих полей.
Изменились и приемы программирования с холстом. Так листинг 16.1 демонстри рует порядок вывода обычной линии на поверхности формы.
![]()
pro08dur8 TForml,FormPaint(Sender: TObject;
Canvas: TCanvas;
oon■t ARect: TRectF);
var APtl, APt2: TPointF; Ь8qin
APtl:=Forml,ClientRect.TopLeft; APt2:=Forml.ClientRect.BottomRight;
if Canvas.BeginScene thlln //создание сцены
try
Canvas.DrawLine(APtl, APt2,
1);
//рисование линии
finally
Canvas.EndScene; //завершение сцены и вывод изображения на экран
end;
end;
ВНИМАНИЕ/
Графический вывод
FireMonkey осуществляется
в рамках
отдельных
сцен.
Прорисов ка
на поверхности физического устройства начинается только после
полного
форми
рования сцены. Такое решение
позволяет
улучшить
качество отображаемой
картинки, - т. к.
рисование
осуществляется
в памяти, и
только затем
уже
сформированный
образ переносится
на экран.
Первая, бросающаяся в глаза особенность листинга 16.1 - необходимость уведом лять холст о начале и завершении прорисовки. Графический вывод предваряется обращением к методу
function BeginScene (AClipRects: PClipRects
= nil;
AContextHandle: THandle =О): Boolean;
В момент открытия сцены устанавливается в исходное состояние свойства холста (в первую очередь кисти заливки областей и рисования линий) и при необходи мости назначается регион отсечения. После этого холст переходит в готовность воспринимать графические команды.
Одновременно на холсте может сосуществовать несколько открытых графических сцен, об их количестве проинформирует свойство
property BeginSceneCount: integer;
Команда
на закрытие
сцены procedur8
EndScene;
уведомляет, что все необходимые команды отправлены в систему, и она может приступать к выводу графики.
![]()
Предусмотрены два метода, позволяющих быстро очистить холст
(точнее,
буфер, в котором
хранится изображение).
Полную
очистку всей
площади холста осущест
вит процедура
prooedure
Clear(const Color:
TAlphaColor);
Единственный
параметр
метода Color содержит
новый цвет
заливки
холста. Для очистки
только заданной
прямоугольной области воспользуйтесь методом
procedure ClearRect(const ARect: TRectF; const AColor: TAlphaColor = О);
границы области, подлежащей перерисовке, ARect.
Ширину
и высоту холста возвращают
свойства
property Width: Integer; //только для чтения
property Height: Integer;
//только для чтения
Для
улучшения
качества
графического
вывода FireMonkey
перед выводом
изобра жения
на холст формирует
картинку в памяти
и только
после этого
переносит ее на экран. Признаком
того, что
холст
работает в
режиме
буферизации,
является
значе ние true.в,озвращаемое свойством
property Buffered: Boolean; //только для чтения
Доступ
к указателю
на
область
памяти
буфера и к
дескриптору
буфера
соответст
венно
предоставляют
свойства
property BufferBits:
Pointer; property
BufferHandle: THandle;
Для управления особенностями прорисовки графических примитивов пр работе с
холстом VCL мы
пользовались
услугами
кисти, пера и
шрифта. У
холста FMX
помощники те же самые, однако работать с
ними существенно интереснее (табл. 16.1).
Таблица 16.1. Атрибуты холста FМХ. Types. TCanvas
|
Свойство |
Описание |
|
property Fill: TBrush; |
Кисть, используемая
для заливки замкнутых
областей |
|
property Stroke: TStrokeBrush; |
Кисть, используемая для рисования линий |
|
property Font: TFont; |
Шрифт |
Первой и
едва
ли не
основной помощницей кроссплатформ.енной
версии
холста можно считать кисть твrush. Вас не должно вводить в
заблуждение знакомое по VCL
название
класса кисти,
кисть rМX.Types.TBrush
на
голову
превосходит
своего
коллегу 1:JЗ
VCL. Это.подтверждают
свойства
новой кисти.
![]()
Класс твrush позволяет создать несколько инструментов для закраски поверхности:
property Kind: TBrushКind;
type TBrushKind = (bkNone, //пустая кисть
bkSolid, bkGradient, bkBitmap, bkResource) ;
//сплошная кисть
//кисть с
градиентной заливкой
//кисть на основе растрового образа
//кисть из ресурса
![]()
В зависимости от вида кисти задействуется то или другое свойство класса твrush.
При определении цвета сплошной кисти обращаемся к свойству
property Color: TAlphaColor;
Кисть bkGradient умеет хранить описание градиентной заливки
property Gradient: TGradient;
Кисть bkBitmap способна интегрировать в себя растровый образ
property Bitmap: TBrushВitmap;
или произвольный ресурс, как кисть bkResource
property
Resource: TBrushResource;
,....
Порядок заливки эллиптической области сплошной кистью демонстрирует лис
тинг 16.2.
![]()
...................... ! .,,_
prooedure
TFonnl.FonnPaint(Sender: TObject; Canvas: TCanvas;
const ARect: TRectF);
Ьegin
if Canvas.BeginScene() then //создание графической сцены
try
Canvas.Fill.Kind:=TBrushКind.bkSolid;
Canvas.Fill.Color:=TAlphaColors.AliceBlue; Canvas.FillEllipse(ARect,1);
finally
Canvas.EndScene(); //завершение сцены и вывод изображения на экран
end; end;
Немаловажной особенностью кисти является способность реагировать на события, в частности
на
изменения в
параметрах
кисти
property OnChanged: TNotifyEvent;
А если кисть предназначена для работы градиентом, то
и на
изменения в настрой
ках
градиента
property OnGradientChanged: TNotifyEvent;
Читатель,
изучивший
табл. 16.1,
наверняка
заметил, что в графике FireMonkey роль
пера также
отводится
кисти. Однако
на этот раз
экземпляр
кисти-пера
построен не
на твrush,
а на
классе-наследнике
TStrokeBrush = class(TBrush)
и
передается в
свойство Stroke (см. листинг
16.3). Если вы уже
попробовали
пора ботать с
кистью FМХ. Types. StrokeBrush, то поняли,
что
возможности
FireMonkey по выводу
линий
поистине
безграничны.
Чего только
tтоит
градиентная
линия... Кро ме того, вне_шний
вид линий зависит
от состояния
квартета
свойств.
Толщина линии определяется свойством
property Thickness:
Single;
Особенности начертания определяет свойство
property Dash: TStrokeDash;
type TStrokeDash
= (sdSolid, sdDash,
sdDot,
sdDashDot, sdDashDotDot, sdCustom);
При желании
можно со дать
собственную
модель
штриховки
линии, для
этого при
годится
свойство
procedure
SetCustomDash(const Dash: array of Single; Offset: Single);
Наконечники линий могут быть плоскими и
скругленными
property Сар: TStrokeCap;
type TStrokeCap = (scFlat, scRound);
Наконец, места соединения нескольких линий также могут настраиваться свойст
вом
property Join: TStrokeJoin;
type TStrokeJoin
= (sjMiter, sjRound,
sjBevel);
Соединения
могут быть ограничены, скруглены или отсечены.
Порядок
работы с
пером
отражен в листинге
16.3.
![]()
procedure
TForml.FormPaint(Sender: TObject;
Canvas: TCanvas;
const ARect: TRectF);
VAr R:TRectF;
Ьegin
R:=RectF(l0,10,200,200);
if Canvas.BeginScene then
try
��nv��.Stroke,Color;=TAlphaColorRec.Crirn.son;
Canvas.Stroke.Thickness:=0.5;
![]()
Двухмернвя грвфика 227
![]()
Canvas.DrawRect(R,10,10,
[TCorner.crTopLeft, TCorner.crBottomRight], 1);
Canvas.Stroke.Color:=TAlphaColorRec.Blueviolet;
Canvas.Stroke.Dash:=TStrokeDash.sdDashDot; Canvas.Stroke.Thickness:=l;
Canvas.DrawEllipse(aRect,1);
f'inally
Canvas.EndScene;
end;
end;
Шрифт TFont
С точки зрения конечного
программиста,
нововведения
FMX не сильно затронули
устоявшийся
со
времен VCL
подход к
представлению
шрифта. В
проектах FireMonkey
класс шрифта
сохранил
традиционное
имя TFont, но на этот раз объяв ление
класс перенесено
в модуль
FМХ. Types.
Три
базовых
свойства
шрифта
обеспечивают
управление
гарнитурой,
размером и стилем:
property Family: TFontName; //гарнитура шрифта property Size: Single; //размер шрифта
property Style: TFontStyles; //стиль шрифта
type TFontStyle
= (fsBold, fsitalic,
fsUnderline, fsStrikeOut);
В
листинге 16.4
предложен
код,
позволяющий
вывести
строку текста в
заданной
прямоугольной
области.
Обратите
внимание, что
цвет надписи
определяется
цве том кисти.
![]()
procedure TForrnl.FormPaint(Sender: TObject; Canvas: TCanvas;
const ARect: TRectF);
var R:TRectF;
Ьegin
R:=RectF(l0,10,200,40);
if' Canvas.BeginScene then
try
Canvas.Fill.Color:=TAlphaColors.Red;
Canvas.Font.Style:=[TFontStyle.fsitalic]; Canvas.Font.Family:='Arial'; Canvas.Font.Size:=20;
Canvas.FillText(R, 'Привет, Мир! ',True,1, [],TTextAlign.taCenter);
finally
Canvas.EndScene;
Заливка замкнутых-областей
Все
методы
графического
вывода
содержат
параметр AOpacity:
Single, опреде
ляющий степень
прозрачности
рисуемого
примитива.
Параметр
должен
принимать
значения в пределах от О
до 1, где О соответствует
абсолютно
прозрачному
изобра
жению, а 1 - непрозрачному.
На
любых
графических
платформах
наиболее
быстрой
графической
операцией
счи тается заливка замкнутой области текущей кистью (кистью, выбранной в
свойство Fill холста FMX). Методы заливки предложены в
табл. 16.2.
Таблица 16.2. Методы заливки областей
|
Метод |
Описание |
|
procedure FillRect(const
ARect: TRectF; const XRadius, YRadius: Single; const ACorners: TCorners; const AOpacity: Single; const ACornerType: TCornerType = TCornerType.ctRound); |
Заливка прямоугольной
области ARect. Допус-
кается вывод
скругленных
углов, в этом
слу- чае
следует
определить
радиус -
XRadius и YRadius. Параметр
ACorners уточняет,
какой
именно угол
(или углы)
следует
нарисовать
скругленным.
Особенности
вывода
назнача-
ются
параметром ACornerType |
|
procedure FillEllipse(const ARect: TRectF; const AOpacity: Single); |
Заливка эллиптической области в границах
прямоугольника ARect |
|
procedure FillArc(const Center,
Radius: TPointF; StartAngle, SweepAngle: Single; const
AOpacity: Single); |
Заливка
сектора
окружности
с центром
Center и
радиусом Radius. Раскрытие
сектора
опре-
деляется
двумя лучами,
проведенными
из центра в
точке StartAngle и SweepAngle |
|
procedure FillPath(const
APath: TPathData; const AOpacity: Single); |
Заливка траектории, описанной в
структуре APath |
|
procedure FillPolygon(const Points: TPolygon; const AOpacity: Single); |
Заливка произвольного многоугольника с вершинами, заданными в массиве Points |
Листинг
16.5 предлагает
простой
пример заливки
прямоугольной
области
гради ентной кистью.
Обратите внимание,
что точки startPosition
и StopPosition опре
делят
координаты
вектора
заливки: Значения
координат
вектора
нормируются к
1. Например,
если бы мы захотели направить
градиент из левого
верхнего угла формы
в правый нижний,
то следовало
передать
координаты
(О, О) и (1, 1).
![]()
proced.ure TForml.FoпnPaint(Sender: TObject;
Canvas: TCanvas;
const ARect; TRectF);
Ьegin
![]()
![]()
if Canvas.ВeginScene then
try
Canvas.Fill.Kind:=TBrushKind.bkGradient;
With Canvas.Fill.Gradient do
hegin
StartPosition.X:=0.5; StartPosition.Y:=O; StopPosition.X:=0.5; StopPosition.Y:=l;
Style:=TGradientStyle.gsLinear; Color:=TAlphaColors.White;
Colorl:=TAlphaColors.Red;
end;
Canvas.FillRect(Foпnl.ClientRect,0,0, [TCorner.crTopLeft, TCorner.crTopRight, TCorner.crBottomLeft, TCorner.crBottornRight],1);
finally
Canvas.EndScene;
end;
Вывод простейших фигур
Вывод
графических
примитивов
осуществляется
кистью, выбранной
в свойстве stroke. Методы,
отвечающие
за вывод
простейших
геометрических
фигур, пред
ставлены в
табл. 16.3.
Таблица 16.З. Методы черчения простейших фигур
|
Метод |
Описание |
|
prooedure DrawLine(const APtl, APt2:
TPointF; const AOpacity: Single); |
Чертит отрезок из точки APtl в точку APt2 |
|
prooedure DrawRect(const ARect: TRectF;
const XRadius, YRadius: Single;
const ACorners:
TCorners; const AOpacity: Single; const ACornerType: TCornerType = 1cornerType.ctRound); |
Выводит прямоуг льник. Параметры
метода
идентичны параметрам
FillEllipse (см. табл.
16.2) |
|
procedure DrawArc(const Center, Radius:
TPointF;
StartAngle, SweepAngle: Single; const AOpacity: Single); |
Выводит
сектор.
Параметры
метода идентичны
параметрам FillArc (см. табл. 16.2) ' |
|
procedure DrawRectSides(const ARect:
TRectF; const XRadius, YRadius: Single;
const
ACorners: TCorners; const AOpacity: Single; const ASides:
TSides; const ACornerТype: TCornerType = TCornerType.ctRound); |
Чертит стороны прямоугольника ASides |
|
prooedure DrawEllipse(const ARect: TRectF; const AOpacity: Single); |
Чертит
эллипс в границах
прямоуголь- ной
области |
|
procedure DrawPath(const APath:
TPathData; const AOpacity: Single); |
Чертит траекторию APath с
прозрачностью AOpacity |
Таблица 16.3 (окончание)
|
Метод |
Описание |
|
procedure DrawPolygon(const Points: TPolygon; const AOpacity: Single); |
Выводит
многоугольник с
вершинами Points |
Из
предложенных
в табл. 16.3
методов
вывода
графических
примитивов
наиболь ший интерес
представляет
метод DrawPath (), позволяющий
осуществлять
графиче ский
вывод весьма
неординарных
объектов-траекторий,
реализуемых
классом FМX.Types.TPathData.
Траекторией
можно
описать
практически
любую
геометрическую
фигуру. Важно
понимать, что сведения
о произвольной
фигуре в объекте-траектории
TPathData бу дут храниться
не
попиксельно
(такой подход
противоречит
концепции
векторной
графики), а в
виде набора
кривых и линий. Во время
отображения
фигуры на
экран траектория
осуществит последовательный
вызов
сохраненных
кривых и
линий.
Если траектория уже содержит информацию, то свойство
function
IsEmpty: Boolean;
окажется в
состоянии false.
Доступ
к опорным
точкам
описанной в
траектории
геометрической фигуры
можно
получить
благодаря
свойству
property Points[Aindex: Integer]:
TPathPoint; //только мя чтения
Общее число точек в
траектории известно свойству
property Count: Integer;
//только мя чтения
Для очистки траектории проще всего воспользоваться свойством
procedure Clear;
Для определения
начальной точки траектории следует вызвать метод
procedure MoveTo(const Р: TPointF);
Кроме того,
метод моvето ( )·
потребуется
в том случае,
когда
необходимо
оторвать
виртуальное перо
от холста и
переместить
его в другую
точку.
Для
того чтобы
провести
линию из
последней
точки
траектории в
новую, проще
всего вызвать
метод
procedure LineTo(const Р: TPointF);
П<" nуч нных наний
уже вполне
достаточно,
чтобы
предоставить
пользователю
возможность рисовать
с помощью
мыши на
поверхности
формы.
Листинг 16.6 со
держит код
создания
экземпляра
траектории,
вызываемый в момент
создания
глав ной
формы
проекта.
![]()
var Path:TPathData; //глобальная переменная
prooedure
TForml.FormCreate(Sender: TObject);
Ьegin
Path:=TPathData.Create; //создаем траекторию
end;
Очередной
листинг 16.7
описывает
три обработчика
мыши (нажатие
кнопки, пере
мещение курсора
и отпускание
кнопки мыши),
с помощью которых
пользователь
начертит
свою фигуру.
![]()
prooedure TForml.FoпnМouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; Х,
У: Single);
Ьegin
Path.Clear; //очистка предыдущей траектории
Path.MoveTo(PointF(X,Y)); //начальная точка траектории
end;
prooedure
TForml.FoпnМouseMove(Sender: TObject;
Shift: TShiftState;
Х, У: Single);
Ьegin
if ssLeft in Shift then Ьegin
//строим траекторию
Path.LineTo(PointF(X,Y));
//! ! ! событие перерисовки FormPaint() пока отсутствует
FormPaint(Sender,Forml.Canvas,Forml.ClientRect);
//! ! ! см. листинг 16.8
end;
end;
prooedure TForml.FoпnМouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; Х,
У: Single);
Ьegin
Path.ClosePath; //пользователь отпустил кнопку МЬШJИ - закрыли траекторию
end;
Для визуализации жеста пользователя нам осталось воспользоваться событием
перерисовки
главной
формы проекта
(листинг 16.8).
![]()
prooedure TForml.FormPaint(Sender: TObject; Canvas: TCanvas;
const ARect: TRectF);
Ьegin
if (Path.Count>0) and (Canvas.BeginScene) then
try
Canvas.Clear(TAlphaColorRec.White);
Canvas.Stroke.Color:=TAlphaColorRec.Black; Canvas.StrokeThickness:=0.5;
Canvas.DrawPath(Path,1);
finally
Canvas.EndScene;
end;
end;
В рассмотренном примере для построения траектории нам оказалось достаточно услуг единственного метода Lineтo (). Изучив класс TPathData, вы обнаружите бо лее десятка методов, позволяющих составлять траекторию не только из линий, н_о и из кривых, дуг, эллипсов и прямоугольников.
Одна из сложнейших графических задач вывода текстовых данных в FireMonkey превращается в пару пустяков. Гарнитура, стиль и размер шрифта назначаются в свойстве Font холста, цвет шрифта определяется цветом текущей кисти. Методы холста тcanvas, отвечающие за работу с текстом, представлены в табл. 16.4.
Таблица 16.4. Вывод текстовых данных
|
Метод |
Описание |
|
function LoadFontFromStream(AStream: TStream): Boolean; |
Загрузка шрифта из потока AStream |
|
function TextWidth(const
AText: string): Single; |
Расчет ширины текстовой строки |
|
function TextHeight(const AText: string): Single; |
Расчет высоты текста |
|
procedure FillText(const
ARect: TRectF; const AText: string;
const WordWrap: B"Oolean; const AOpacity: Single; const Flags:
TFillTextFlags; const ATextAlign: TTextAlign; oonst AVTextAlign: TTextAlign = TTextAlign.taCenter); |
Вывод текста
AText в области ARect. Перенос
текста на
новую
строку опре-
деляется параметром
WordWrap. На-
правление
вывода
(по умолчанию
еле- ва
направо)
устанавливается в Flags. Горизонтальное
выравнивание
опре- деляется
параметром ATextAlign, вертикальное
выравнивание
- пара- метром
AVТextAlign |
|
function TextToPath(Path; TPathData; const ARect: TRectF; const AText: string; const WordWrap: Boolean; const
ATextAlign: TTextAlign; const AVТextAlign: TTextAlign = TTextAlign.taCenter): Boolean; |
Вывод текста по траектории Path |
Для прорисовки на поверхности холста графического образа следует применять метод
prooedure DrawBitmap(const AВitmap: TBitmap;
const SrcRect, DstRect:
TRectF; const AOpacity: Single;
const HighSpeed: Boolean
= False);
Процедура выводит изображение AВitmap полностью или его часть. Подлежащая выводу область изображения определяется параметром SrcRect. Место вывода и размеры результирующего изображения назначаются в параметре DstRect. Пара метр HighSpeed позволяет программисту отдать предпочтение скорости (true) или качеству (false) вывода.
Листинг 16.9 демонстрирует порядок работы с методом DrawBitmap () . В представ ленном примере мы загружаем графический образ из файла и выводим его на по верхности формы. Если нам потребуется отобразить только фрагмент исходного
• рисунка, то следует определить границы интересующего нас фрагмента в парамет ре srcRect. Управляя размерами области вывода DstRect, мы сможем увеличить или уменьшить рисунок.
![]()
var AВitmap:TBitrnap; aWidth,aHeight:integer; SrcRect,
DstReGt: TRectF;
Ьegin
if OpenDialogl.Execute then
with Forml.Canvas do Ьegin
AВitmap:=TBitmap.CreateFromFile(OpenDialogl.FileName);
aWidth:=AВitrnap.Width;
aHeight:=AВitmap.Height;
SrcRect:=RectF(0,0,aWidth,aHeight); DstRect:=SrcRect;
if BeginScene then Ьegin
DrawBitmap(AВitmap,SrcRect,DstRect,1,true); EndScene;
end;
end;
end;
Для организации быстрого просмотра изображения АВitmap стоит воспользоваться методом
prooedure DrawТhumЬnail(const AВitmap: TBitmap;
const Width, Height: Single);
создающим графическую миниатюру заданного размера.
Как
в Windows, так и
в OS Х в
качестве
основного
инструмента,
позволяющего
на строить
границы
области, в
которых
будет
осуществляться
вывод,
выступает
реги он
отсечения.
Библиотека
FireMonkey порадовала
возможностью
простого
управ ления
регионами
отсечения. В
ХЕ4/ХЕ5 в
распоряжении
программиста
имеются два
метода.
Изменить
текущий
регион
отсечения,
создав новый
на основе
пересечения
текуще го
региона и прямоугольной
области ARect, позволит
процедура
procedure
IntersectClipRect(const ARect:
TRectF);
Метод
prooedure
ExcludeClipRect(const ARect: TRectF);
наоборот исключает из текущего региона отсечения
прямоугольник ARect.
.состояния холста
При необходимости мы можем сохранить текущее состояние холста (сведения
о кистях,
шрифте,
матрице
преобразований
и т. п.).
Для этой цели
предназначена
функция
function SaveState: TCanvasSaveState;
Сведения о
состоянии холста представляются в
формате объекта тcanvasSaveState.
Для восстановления состояния холста вызываем метод
prooedure RestoreState(State: TCanvasSaveState);
![]()
Методы
сохранения/восстановления
должны
работать
вместе так,
как
продемонст
рировано в листинге
16.10.
var Canvas: TCanvas;
State: TCanvasSaveState;
Ьeiin
State := Canvas.SaveState;
//ROQДQЙC�BИQ на свойства холста
//другие операции
Canvas.RestoreState(State);
encl;
Работа с
растровой графикой, класс TBitmap
Если вы имеете опыт работы с растровой графикой в VCL, то, вне всякого сомне ния, вам приходилось сталкиваться с классом твi tmap, выступающим интеллекту альным контейнером для битовых образов для широко распространенных в Windows файлов с расширением имени bmp.
Разработчики библиотеки FMX решили не ломать сложившихся традиций и для обслуживания растровой графики в FireMonkey создали одноименный класс TBitmap (модуль FМX.Graphics). Несмотря на то, что классы VCL и FMX являются тёзками, возможности обновленной версии TBitmap в корне отличаются от его кол леги из VCL, превосходя его практически по всем параметрам. Так, обновленная версия FМХ. Т:уреs. твi tmap способна:
□
обслуживать графические файлы не только в
простейшем формате ВМР, но и
в форматах JPG, JPEG, PNG, GIF, ТIF, ICO (в приложениях Windows) и JP2, PSD, TGA, ICNS (в приложениях OS Х и IOS);
□
изменять геометрию изображения (размеры и
вращение);
□ осуществлять графический вывод
на своей рабочей поверхности благодаря ин-
капсулированному
холсту тcanvas.
Созданием экземпляра класса обслуживания растровых изображений ведает квар тет конструкторов. Вы можете определить пустой рисунок, ограничившись лишь указанием его размеров
constructor Create(const AWidth, AНeight: Integer);
создать объект, загрузив его данные из потока или файла
constructor CreateFromStream(const AStream: TStream); constructor CreateFromFile(const AFileName: string);
и, наконец, сформировать новый растровый объект на основе другого рисунка и его монохромной маски
constructor CreateFromВitmapAndМask(const Bitmap, Mask: TBitmap);
Загрузка и сохранение изображения
Традиционно растровый объект обладает возможностями загрузки данных из файла и потока
procedure LoadFromFile(const ДFileName: string;
const Rotate:
Single О);
procedure LoadFromStream(Stream: TStream);
и сохранения картинки в файл и поток.
procedure SaveToFile(const AFileName: string;
const SaveParams: PBitmapCodecSaveParams = nil);
procedure SaveToStream(Stream: TStream);
![]()
![]()
Если загрузка изображения осуществилась успешно, то метод
function
IsEmpty: Boolean;
возвратит значение false.
ЗАМЕЧАНИЕ
В
момент
сохранения
изображения
допускается
его
преобразование
в другой
графи ческий формат. Помощь в
этом окажет определенный в
модуле FМХ. Types класс TBi tmapCodecManager.
Кодирование и
декодирование графических форматов
Класс
твi tmap далеко
не всемогущ,
и при
обработке
поддерживаемых
им графиче
ских
форматов
зачастую
нуждается в
услугах
сторонних
"лиц", в
частности
класса твi tmapCodecManager, обеспечивающего
кодирование
и декодирование
хранящихся в
графическом контейнере
данных.
Перечень расширений имен файлов,
поддерживаемых классом TBitmapCodecManager,
возвратит метод
class function GetFileTypes: string;
Если
вы работаете
под
управлением
Windows, то получите
текстовую
строку при
мерно
следующего
содержания:
*.bmp;*.jpg;*.jpeg;*.png;*.gif;*.tif;*.ico;*.wmp
Если приложение выполняется под управлением OS Х, то менеджер возвратит
*.bmp;*.icns;*.jpg;*.jp2;*.jpeg;*.png;*.gif;*.tif;*.tga
Заметьте,
что в возвращаемой
строке
расширения
имен файлов
разделяются
точкой с
запятой.
Для
упрощения
работы
программиста,
использующего
в своем
проекте
диалоги
открытия и сохранения
файлов, в твi
tmapCodecManager предусмотрен
метод
class function GetFilterString: string;
передающий
в программу
перечень
поддерживаемых
форматов в
виде,
приемлемом для
свойства Filter компонентов
(т0penDialog и
тsaveDialog).
Если
текс овой
строки с
перечнем
расширений
поддерживаемых
файлов вам
не
достаточно,
то для того
чтобы
убедиться в
наличии
необходимого
кодека, доста
точно просто
направить
имя файла в функцию
класса
class function IsCodedExists(const AFileNarne: string): Boolean;
Получение миниатюры изображения
Если в разрабатываемом вами приложении предусматривается предварительный просмотр рисунков, то вместо загрузки больших файлов с изображениями более рациональным решением может стать получение их уменьшенных копий - миниа тюр.
![]()
С такой задачей справится процедура
proaedure
LoadThurnЬnailFromFile(const AFileName: string;
const AFitWidth, AFitHeight: Single; const UseEmЬedded: Boolean = True);
Кроме
обязательного
имени файла с
растровой
картинкой,
метод
потребует от
вас явным
образом
указать
геометрические
размеры
(параметры AFitwictth и AFitHeight)
миниатюры ..
Еще одну операцию, связанную с
созданием миниатюры, осуществляет функция
function CreateThurnЬnail(const Width,
Height: Integer): TBitmap;
Она
также
формирует
миниатюру
заданного размера,
но на этот
раз не из
файла, а из
уже загруженного
в контейнер рисунка.
Если
в
графический
контейнер твitmap загружена
картинка
(проверку
этого можно произвести
с помощью
метода IsErnpty), то мы
получаем
возможность
оценить ее
ключевые
параметры с
помощью
свойств,
представленных в табл.
16.5.
Таблица 16.5.
Основные свойства FМХ. Types. TBi tmap
|
Свойство |
Описание |
|
property Handle: TBitrnapHandle; //только чтение |
Дескриптор изображения |
|
property Width: Integer; |
Ширина картинки |
|
property Height: Integer; |
Высота картинки |
|
property PixelForrnat: TPixelForrnat; |
Глубина
цвета |
|
property BitrnapScale: Single; //только чтение |
Масштаб |
|
property CanvasClass: TCanvasClass; //только чтение |
Класс холста |
Простые манипуляции графическим образом
В классе твi tmap максимально упрощены все наиболее востребованные
операции с
графическим
образом. Дпя
изменения
геометрического
размера
достаточно
обра титься
к методу
proaedure Resize(const AWidth, AНeight: Integer);
Дпя поворота рисунка на заданный градус вызываем процедуру
proaedure
Rotate(const Angle: Single);
Дпя вращения вокруг горизонтальной и вертикальной оси заде_йствуются методы
proaedure FlipHorizontal; proaedure FlipVertical;
![]()
![]()
![]()
Инвертирование альфа-канала осуществит метод
prooedure InvertAlpha;
Замена прозрачного цвета выполняется процедурой
prooedure
ReplaceOpaqueColor(const Color:
TAlphaColor);
Редактирование битового образа
Одно
из
неоспоримых
преимуществ
класса FМX.Types.TBitmap
над его
тёзкой из VCL з'аключается
в том, что он
позволяет
программисту
рисовать на
поверхности
изображения,
используя
удобный
интерфейс
холста
property Canvas: TCanvas;
Внесенные
изменения
могут быть
сохранены в
файл или в
поток с
помощью уже
анонсированных ранее
методов saveToFile () и
saveтostream () .
При
необходимости
весь рисунок
или его отдельная
прямоугольная
область
могут быть
очищены,
точнее
говоря,
залиты
определенным
цветом. Эту сервисную
воз можность
обеспечивают
методы
prooedure Clear(oonst AColor: TAlphaColor);
prooedure ClearRect(const ARect: TRectF; co st AColor: TAlphaColor = О);
К процессу изменения картинки имеет отношение еще
одна пара
перегружаемых
методов:
prooedure CopyFromВitmap(const Source: TBitmap);
overload;
prooedure CopyFromВ tmap(const Source:
TBitmap;
SrcRect: TRect; DstX,
DstY: Integer); overload;
Перечисленные
процедуры
копируют
рисунок из
образа source.
Единственное
ус ловие
корректной
работы
методов
заключается
в равенстве
геометрических
разме ров
образа-источника
и получателя.
Для любителей экстремальных способов редактирования рисунка упомянем
о функции
function Map(const Access: ТМарАссеss; var Data: TBitmapData): Boolean;
предоставляющей
низкоуровневый
доступ к
битовой
карте
изображения через параметр
Data.
Особенности
доступа (maRead - только
чтение; maWrite - только
запись, maReadWrite-чтeниe и
запись) определяются параметром Access.
Завершив
работу с
битовой
картой,
следует закрыть
сессию,
воспользовавшись
процедурой
prooedure Unmap(var Data:
TBitmapData);
![]()
графической производительностью
При работе с графикой приложения FireMonkey в первую очередь стараются задей ствовать всю вычислительную мощь процессора видеокарты, и только если это не возможно; нагрузка перекладывается на центральный процессор. Вместе с тем, нам как разработчикам программного обеспечения следует понимать, что не каждый пользователь будет готов немедленно отправиться в магазин за очередным видео адаптером, лишь бы насладиться изысканным интерфейсом наших с вами про грамм.
Для того чтобы пользователь смог оптимизировать распределение задач между графическим и центральным процессорами, программисту стоит позволить ему управлять состоянием ряда глобальных переменных, объявленных в модуле FМX.Types (табл. 16.6).
Таблица 16.б. Настройка графической производительности Windows
|
Переменная |
Умолчание |
Описание |
|
GlobalDisaЫeFocusEffect: Boolean; |
false |
Эффект
фокуса
ввода.
Для устройств
с низкой
производительностью
уста- навить
в true |
|
GlobalUseDirect2D: Boolean; |
true |
Использовать аппаратный Direct2D |
|
GlobalUseDirect2DSoftware: Boolean; |
false |
Установить программную эмуляцию
Direct2D |
|
GlobalUseНWEffects: Boolean; |
true |
Использовать аппаратное ускорение
там, где это
возможно |
|
GlobalUseGDIPlusClearType: Boolean; |
true |
Использовать технологию СlеагТуре
для
улучшенного
отображения
шрифтов в Windows |
![]()
![]()
ГЛАВА 17
Платформа
FireMonkey - это в
первую
очередь
мощная
графическая
библиотека,
предназначенная
для создания современного
привлекательного
пользовательского
интерфейса.
Тому
подтверждением
выступает огромная
плеяда
специализирован
ных компонентов
со страницы
Effects палитры
компонентов,
реализующих
тот или иной
графический
алгоритм (от
простого управления
яркостью и
контрастностью
рисунка до
сложнейших
алгоритмов затуманивания, свертки,
зыби на воде
и т. п.).
Более шести десятков невизуальных
компонентов
со страницы
Effects построены
на
фундаменте
цепочки
классов 11TEffect - TFilterEffect- TimageFXEffect".
Вне зависимости
от
"классовой"
принадлежности,
все
графические
эффекты
можно раз делить
на пять категорий:
1.
Простейшие
корректирующие
эффекты, в
которых
результирующая
окраска
пиксела зависит
только от его
исходного
цвета и некой
функции
преобразова
ния. В качестве
примеров
подобных
эффектов
можно привести
обычное
инвер
тирование
изображений,
когда цвет
пикселов
меняется на
противоположный.
2. Эффекты,
в которых для расчета
итогового
цвета
функция
преобразования
ана лизирует
не только
целевой
пиксел, но и
пикселы,
входящие в его ближайшее,
а иногда и
дальнее
окружение. К таким
эффектам
относят эффекты
затуманива
ния (Ьlur) и эффекты искажения (distortions) изображения (например, свертка
в спираль).
3. Аддитивные
эффекты (additive effects),
привносящие
в изображение
новые эле
менты
(например,
зеркальное
отражение).
4.
Геометрические эффекты, изменяющие форму и
пропорции
изображения.
5. Разнообразные
эффекты
трансляции (transition
effects),
позволяющие
получать
итоговый
рисунок
путем
перехода от
одной
текстуре к
другой.
В
период
визуального
проектирования
подключение
компонентов-эффектов
к сво им целевым
объектам (в
первую
очередь
компонентам, работающим с
текстурами в формате
твitmap) осуществляется традиционным
для библиотеки
FireMonkey спо собом - с помощью окна управления структурой проекта (рис. 17.1). Програм-
![]()
![]()

Рис. 17.1. Подключение компонента-эффекта InvertEffectl к целевому объекту Imagel
мисту
достаточно
просто
перетащить с
помощью мыши
требуемый графический
фильтр
в подчинение заинтересованному
в
визуальных "фокусах"
объекту.
Если
разрабатываемое
вами
приложение
предполагает
динамическое
включе
ние/отключение
различных визуальных эффектов во время выполнения программы,
то стоит
познакомиться
со строками
кода из
листинга 17.1.
![]()
var i:integer;
Ьeqin
//находим и
отключаем старый эффект
for i :=О to Imagel.ChildrenCount-1 do
if Imagel.Children(i] is TimageFXEffect then
Ьeqin
Imagel.RemoveObject(Imagel.Children[i]);
break;
end;
//подключаем новый эффект
Imagel.AddObject(BlurEffectl);
end;
Предложенный
фрагмент
кода решает
две задачи.
Во-первых, благодаря
циклу for среди
дочерних компонентов
объекта Imagel мы
находим
ранее
подключенный
компонент-эффект
(потомок
класса TEffect) и
отсоединяем
его от
объекта. Во
вторых, с помощью
метода AddObject
() к
изображению Imagel мы присоединяем
новый эффект,
в
нашем
примере это BandsEffectl.
![]()
![]()
![]()
Применение эффекта к файлам изображений
Даже беглое знакомство с набором компонентов FireMonkey, реализующих алго ритмы разнообразных визуальных фильтров, впечатлит любого программиста, ко торый сталкивался с разработкой графических редакторов. Сразу возникает непре одолимое желание засучить рукава и написать приложение, способное составить конкуренцию таким грандам 1Т-индустрии, как Adobe Photoshop и Corel Photo Paint. Нет ничего невозможного, однако на первых порах придется решить одну непростую задачу - научиться применять эффекты не просто к элементам управ-· ления, а к хранящимся в файлах картинкам и сохранять полученные результаты в файл. '
Так в чем проблема? Если мы попробуем вызвать метод сохранения картинки (к
которой
применен тот или
иной
графический
фильтр) в файл,
например
Irnagel.Bitrnap.SaveToFile(<имя файла>);
то, просмотрев файл, к своему разочарованию получим исходное изображение без единого намека на фильтрацию. Все дело в том, что после подключения к любому элементу управления, в том числе и контейнеру изображений Tirnage, компоненты эффекты никак не врздействуют на физическую структуру рисунка, а лишь управ ляют процессом вывода картинки на экран.
ЗАМЕЧАНИЕ
Компоненты-эффекты
никак не
воздействуют
на
физическую
структуру
рисунка, а лишь
управляют
процессом
вывода
картинки на
экран.
Отключение
эффекта
(напри мер, с помощью
свойства EnaЫed) возвращает
результирующее
изображение в
ис ходное состояние.
Одним из решений задачи сохранения эффекта в файл может стать небольшой об ман. Суть идеи заключается в том, что после применения графического фильтра (например, коррекции контрастности ContrastEffectl: тcontrastEffect) к картинке (хранящейся в компоненте Irnagel: Tirnage) мы скопируем видимую область изо бражения в другой объект - растровый рисунок твi tmap. И только затем отправим результаты, полученные таким "мошенническим" путем, в файл (листинг 17.2).
![]()
var TempFoпn: TFoпn; Templmage:Tlrnage; TempBitrnap:TBitmap;
Ь&gin
if SaveDialogl.Execute then
Ьeqin
//создаем временную невидимую форму TempForm:=TForm.CreateNew(Application);
TempForm.ClientWidth:= Imagel.Bitrnap.Width; TempForm.ClientHeight:=Imagel.Bitmap.Height;
//на форме размещаем временный контейнер Timage Tempimage:=Timage.Create(TempFoпn);
Ternpimage.Parent:=TernpFoпn; Ternplrnage.Width:= Imagel.Bitmap.Width; Ternplmage.Height:=Imagel.Bitmap.Height;
//переносим в контейнер рисунок, хранящийся в
Imagel:Tlmage Ternplmage.Bitmap.Assign(Imagel.Bitmap);
//применяем к контейнеру эффект ContrastEffectl:TContrastEffectl Ternpimage.AddObject(ContrastEffectl);
//теперь в памяти есть невидимая форма с невидимым пользоватето
//рисунком, к которому применен графический фильтр
end;
//создаем пустой битовый образ TempBitmap:=TBitmap.Create(bWidth,bHeight);
//переносим в образ изображение из невидимой формы ' TernpBitmap. Canvas.
BeginScene () ;
TernpFoпn.PaintTo(TempBitmap.Canvas);//!! ! рисуем
именно так!!!
TernpBitmap.Canvas.EndScene;
//сохраняем результат в
файл TernpBitmap.SaveToFile(SaveDialogl.FileName);
//освобождаем ресурсы
TernpBitmap.Free; TernpFoпn.Release;
end;
![]()
Листинг достаточно подробно прокомментирован, но сделаю еще одно очень важ ное замечание. Картинка с эффектом должна быть нарисована на холсте целевого битового образа тempBitmap именно методом Paintтo () формы, а не каким-либо другим способом!
Применение
нескольких
эффектов к файлам
изображений
Предлагаю провести один простой, но очень поучительный эксперимент. Попро буйте одновременно подключить к компоненту Timage по крайней мере пару разно типных графических фильтров, например тwaveEffect и TPixelateEffect. Каков ре зультат? Идеалисты наверняка надеялись, что получат картинку со сложением эф фектов водной глади и пикселизации. В свою очередь скептики отошли подальше от компьютера, предполагая, что он расплавится при решении непомерно сложной вычислительной задачи. Как всегда истина находится посредине - компьютер ос тался цел, а к изображению применился всего один, первый по счету графический фильтр. Разочарованы?
ЗАМЕЧАНИЕ
Независимо
от числа
присоединенных
к визуальному
элементу
управления
компонен тов-эффектов,
к нему будет
применяться
только один
графический
фильтр.
![]()
В том, что визуальный элемент управления готов сотрудничать всего с одним гра фическим фильтром, а на остальные не обращает внимания, нет ничего удивитель ного. Это разумное ограничение, не позволяющее превратить проект FireMonkey в неработающую новогоднюю елку, ведь наверняка найдется "программист", кото рый подключит ко всем визуальным компонентам все компоненты-эффекты. В ито ге процессоры компьютера посвятят все свое время перерасчету фильтров.
Однако нет ничего невозможного. При остром желании можно решить задачу при менения нескольких графических фильтров к одному изображению, но это надо делать последовательно. В качестве основы можно воспользоваться идеей из лис тинга 17.2, но ее придется немного доработать·.
Предположим, что наш графический редактор активно пользуется услугами не. скольких фильтров, среди них:
□ фильтр контрастности/яркости ContrastEffectl: TContrastEffect;
□
фильтр цветового тона HueAdjustEffectl: THueAdjustEffect;
□
ряд других компонентов, потомков класса TimageFXEffect.
Пользователь рассчитывает изменять характеристики этих фильтров при редакти ровании одного и того же изображения, при этом результат его манипуляций дол жен немедленно отображаться на экране.
В первую очередь объявим глобальную переменную-список, способную хранить произвольный перечень компонентов-эффектов, и инициализируем ее в момент создания главной формы проекта (листинг 17.3).
;••••••••••••«,,ч,••••••••.. •••••••••,•••••••-•·•••··.,,.,, •••••<: •.i•••i••••••-r•••·•'•.\/'"' "••..•,..••••м• ч.. v,••••.. -•-•._•.•..••.••••....••q,.,..11'••••....••-.,••••••••••••••••-•..,"'"'"'""' ,..,,. .,,.,, • •---,.. ,.,,,._, • 11;
ЯМ-тинr 17.З. Cnиs•
alwкro8 ,--- ·:
uses ... , System.Generics.Collections;
/ / ...
var Forml: TForml;
EffectsList: TObjectList<TimageFXEffect>; //список TimageFXEffect i.nplementation
{$R *.fmx}
proaedure
TForml.FormCreate(Sender: TObject);
Ьegin
EffectsList:=TObjectList<TimageFXEffect>.Create(false);
EffectsList.Add(ContrastEffectl);
EffectsList.Add(HueAdjustEffectl);
EffectsList.Add(<дpyгиe графические фиnьтры>);
end;
Обратите внимание на то, что после создания списка мы сохраняем в нем ссылки на все имеющиеся в приложении фильтры, но при этом не делаем список их вла дельцем.
![]()
Графические эффекты 245
![]()
А теперь разработаем функцию ApplayEffects (), на вход которой будет направлено
tmap и перечень эффектов
ции должно оказаться изме
................. .,.....,............ ', .................· .....' ....,....,.,...·•···..:.
...............,............... ..... ··········"" .. •.........;i •.,,,..........r.o.. - ... .
Function ApplayEffects(const Source:TBitmap;
const List: TObjectList<TimageFXEffect>) :TBitmap;
var TernpFoпn:TFoпn; Tempimage:Timage; TempBitmap:TBitmap; i:integer;
Ьegin
//------ невидимая форма с
временным изображением
TempFoпn:=TFoпn.CreateNew(Application);
TempFoпn:c1ientWidth:=Source.Width;
TempFoпn.ClientHeight:=Source.Height;
Ternpimage:=Timage.Create(TempFoпn); Tempimage.Parent:=TempFoпn;
Tempimage.Width:=Source.Width; Tempimage.Height:=Source.Height;
Ternpimage.Bitmap.Assign(Source);
/1--------------------------------------------------------
TempBitmap:=TBitmap.Create(Source.Width,Source.Height);
//---------последовательно применяем все фильтры--------------
for i:=0 to List.Count-1 do
Ьegin
Tempimage.AddObject(List.Items[i]);//пpимeняeм фильтр TempBitmap.Canvas.BeginScene();
TernpFoпn.PaintTo(TempBitmap.Canvas); //перерисовка
TempBitmap.CaRvas.EndScene;
Tempimage.RemoveObject(List.Items[i]); //снимаем фильтр
Tempimage.Bitmap.Assign(TempBitmap);
end;
// Result:=Ternpimage.Bitmap;
TempBitmap.Free; TempFoпn.Release;
end;
Нам осталось лишь регулярно
вызывать
разработанную
функцию при любом дей
ствии пользователя, и
все хранящиеся в
списке List фильтры по порядку применят
ся к
редактируемому
изображению.
![]()
![]()
![]()
Простейшие корректирующие эффекты
Задействованные
в
корректирующих
эффектах
математические
модели
анализиру ют
только
исходное
состояние
изменяемого
пиксела и не
обращают
внимания на
остальные
пикселы
текстуры,
поэтому
корректирующие
алгоритмы
относительно
просты и
нересурсоемки.
Ряд
компонентов, реализующих простейшие
эффекты, практически
не нуждаются
в
дополнительной
настройке
свойств. Это
утверждение относится
к:
□ компоненту TinvertEffect, инвертирующему цвет рисунка;
□ компоненту тмaskToAlphaEffect, превращающему в
белые (путем добавления
альфа-канала)
все серые
пикселы
изображения;
□ компоненту тмonochromeEffect, конвертирующему цветной рисунок в
моно хромный.
Подключив
перечисленные
компоненты к целевому
объекту с текстурой,
мы сразу,
без
каких-либо
дополнительных
настроек,
получим
требуемый
результат.
Заливка цветом TFil/Effect
и TFil/RGBEffec t
Простейшую задачу по заливке пикселов изображения определенным цветом ре
шают компоненты
TFillEffect и TFillRGBEffect. Разница
между
классами
заключа ется в том, что первый из них закрасит заданным цветом всю поверхность изуаль
ного объекта. Второй компонент немного хитрее и
применит заданный цвет только
к непрозрачным
пикселам. Не трудно догадаться, что наиболее важное свойство
обоих компонентов
property
Color: TAlphaColor;
Яркость и контрастность TContrastEffect
Наиболее востребованная корректировка изображения, связанная с
управлением
яркостью
и контрастностью, производится
с помощью
компонента тcontrastEffect.
За установку яркости отвечает свойство
proper� Brightness : Single; //диапазон значений от -1 до 1
Для повышения
яркости следует
выбрать значения
выше нуля, отрицательная
часть
диапазона
соответствует
пониженной
яркости.
Управление контрастностью осуществляет свойство
property Contrast: Single; //диапазон значений от О до 2
Значение, превышающее 1,5, увеличивает контрастность,
значение меньше 1,5 -
уменьшает.
Графи•,ооиио оффо1<mы '147
Регулировка опенка цвета THueAdjustEffect
Компонент тнueAdjustEffect позволяет управлять оттенком цвета изображения, для этих целей класс снабжен свойством
property Hue: Single; //диапазон от -1 до 1
и пасмурная TG/oomEffect погода
Два родственных компонента TВloomEffect и TGloomEffect нацелены на придание изображению двух противоположных качеств - имитации яркого солнечного или хмурого пасмурного дня.
Для изменения параметров рисунка каждый из компонентов обладает четверкой свойств. Так, компонент твloomEffect для настройки интенсивности свечения за действует сво ства
property Bloomintensity: Single; //диапазон значений от О до 1
property Baseintensity: Single; //диапазон значений от О до 1
Цветовое насыщение TBloomEffect зависит от состояния свойств
property BloomSaturation: Single; //диапазон значений от О до 1
property BaseSaturation: Single; //диапазон значений от О до 1
Свойства TGloomEffect практически повторяют свойства его коллеги, интенсив ность свечения определяется состоянием свойств
property Gloomintensity: Single;
//диапазон значений от О до 1
property
Baseintensity: Single; //диапазон значений от О до 1
Насыщенность цветом определяется свойствами
property GloomSaturation: Single;
//диапазон значений от О до 1
property BaseSaturation: Single; //диапазон значений от О до. 1
Прозрачность TColorKeyA/phaEffect
Для того чтобы сделать определенный цвет текстуры прозрачным, стоит восполь зоваться услугами компонента тcolorKeyAlphaEffect. Значение ключа, определяю щего, какой именно цвет должен стать прозрачным, задается в свойстве
property ColorKey: Single; //диапазон значений от -1 до 1
Допустимая степень отклонения цвета от значения заданного в
свойстве ColorKey
определяет свойство
property Tolerance : Single; f/диапазон значений от О до 1
Если свойство принимает нулевое значение, то эффект прозрачности отключается, с возрастанием значения число цветов, попадающих под критерий прозрачности, возрастает.
![]()
![]()
По
сравнению с
алгоритмами,
специализирующимися
на создании
простых
коррек
тирующих
эффектов,
алгоритмы
размытия и
искажения
обрабатывают
куда боль
ший объем
исходных
данных. Дело в
том, что в их
основу
положен
более
глубокий
математический анализ
растрового
изображения.
В алгоритмах
размытия и искаже
ния решение о
закраске
результирующего
пиксела
основывается
на обработке группы
пикселов,
входящих в
его
ближайшее окружение.
Эффекты
размытия
(затуманивания)
достигаются
при активном участии
компо
нентов: TBlurEffect, TDirectionalBlurEffect,
TBoxBlurEffect, TGaussianBlurEffect и TRadialВlurEffect. Суть
эффекта
размытия
заключается
в том, что все
изображение
становится
расфокусированным
без выделения
какой-либо
области.
Каждый из
компонентов
решает
задачу
по-своему.
Так, самый непритязательный из них
(ком понент TВlurEffect) просто
затуманивает
изображение
за счет смещения
основных
контуров
рисунка. Степень
размытия
определяет
свойство
property Softness : Single; //по умолчанию 0,3
способное принимать значение из диапазона от О до 9.
Более
сложный
класс TGaussianBlurEffect
создает
эффект дымки
путем
расфокуси
ровки
изображения
в
соответствии
с законом
распределения
Гаусса. В
результате
работы
алгоритма,
заложенного
в компонент TGaussianBlurEffect, сведения
о пик селах
распределяются
от центра
наружу по
колоколообразным
кривым.
У
всех
рассматриваемых
компонентов
(за исключением TВlurEffect с его свойст
вом Softness) базовым
свойством,
управляющим
степенью размытия
изображения,
является
property BlurAmount : Single;
Кроме
того, у
компонентов
предусмотрен
ряд
вспомогательных
свойств,
оказы вающих
дополнительное
воздействие
на текстуру объекта.
В частности у
компо нента TDirectionalBlurEffect,
позволяющего конкретизировать,
в каком
направле нии
будет смещен
рисунок для
получения
эффекта затуманивания,
предусмотрено
свойство
property
Angle ; Single;
определяющее угол смещения.
В
свою очередь
решающий схожую
задачу
компонент TRadialВlurEffect
предлага ет
программисту
свойство
property Center: TPointF;
позволяющее определиться с
координатами центра эффекта.
![]()
![]()
Если вам необходимо изменить текстуру объекта до неузнаваемости, то стоит по ближе познакомиться с компонентами, отвечающими за искажения. Среди них вы найдете специалистов по увеличению выбранной области рисунка, скручиванию рисунка, имитации эффекта водной глади и по многому другому.
Вертикальные полосы-TBandsEffect
Компонент твandsEffect позволяет покрыть изображение вертикальными полоса ми, в результате получится эффект, в какой-то степени напоминающий жалюзи (рис. 17.2). Управление эффектом осуществляется с помощью свойства
property
BandDensity: Single;
//диапазон от О до 150
определяющего число
вертикальных
полос, и
свойства property
Bandintensity: Single; //диапазон от О до 1
управляющего
интенсивностью
засветки границ
полос.
![]()
Рис. 17.2. Пример эффекта вертикальных полqс, компонент TBandsEffect
Водоворот TSwir/Effect и TBandedSwir/Effect
Если вы поклонник неординарных решений, то эфф кт водоворота, создаваемый компонентами TSwirlE"ffect и твandedSwirlEffect, подойдет вам как нельзя лучше (рис. 17.3).
Центр водоворота определяется координатами точки
f property Center : TPointF;
У тswirlEffect интенсивность скручивания определяется свойст ом
property
SpiralStrength: Single;
У твandedSwirlEffect название свойства, решающего аналогичную задачу, немного другое:
property Strength: Single;
От знака значения, хранимого в свойстве, зависит направление вращения водово рота.
Если вы работаете с более "продвинутым" компонентом твandedSwirlEffect, то по лучаете возможность управлять дополнительным параметром - числом полос (кругов от водоворота) вокруг центральной точки эффекта
property Bands : Singlе;//диапазон значений от О до 20
![]()
Рис. 17.3. Пример эффекта водоворота, компонент TBandedSwirlEffect
![]()
Еще одна особенность водоворота зависит от состояния свойства
property AspectRatio: Single; //диапазон значений от 0.5 до 2
которое определяет наклон результирующего изображения.
Увеличительное стекло TMagnifyEffect и TSmoothMagnifyEffect
![]()
Компоненты TMagnifyEffect И TSmoothМagnifyEffect ПОЗВОЛЯЮТ Программисту "под нести" к
рисунку
увеличительное
стекло. Разница
между
компонентами
заклю чается
в том, что TMagnifyEffect просто
увеличивает
заданную
область, а тsmoothМagnifyEffect, кроме трго, вносит
искажения,
имитируя
реальную стеклян
ную линзу (рис. 17.4).
![]()
Рис.
17.4. Увеличение
области без искажения
(TMagnifyEffect) и с искажением
(TSmoothМagnifyEffect)
Коэффициент увеличения задается свойством
property Мagnification: Single; //диапазон от 1 до 5
Увеличиваемая область представляет собой окружность с центром, определяемым свойством
property Center·: TPointF;
Если вы работаете с тмagnifyEffect, то радиус увеличиваемой области зависит от состояния единственного свойства
property Radius : Single; //диапазон от О до 1
Второй
компонент, тsmoothMagnifyEffect - это размер
увеличиваемой
области, он
назначается
исходя из
состояния
пары свойств:
property InnerRadius Single; //диапазон значений от О до 1
property OuterRadius : Single; //диапазон значений от О до 1
соответственно определяющих радиусы внутренней и
внешней
границ эффекта.
Если
при выводе на
экран
пропорции
рисунка
нарушаются, то увеличиваемая
об ласть
из круга
превращается
в эллипс.
Чтобы
устранить
искажения,
следует обра титься к
свойству
property AspectRatio: Single; //диапазон от 0.5 до 2.00
Стягивание области TPinchEffect
Компонент
TPinchEffect реализует
еще один
эффект,
искажающий
не всю
рабочую
поверхность текстуры, а
лишь область (рис. 17.5) в форме окружности с
центром в
точке с
координатами
property Center: TPointF;
Радиус искажаемой области
property Radius : Single; //диапазон от О до 1
Степень искажения области зависит от состояния свойства
property Strength: Single;
//диапазон от О до 20
![]()
Рис. 17.5. Пример эффекта сдавливания области, компонент TPinchEffect
Для восстановления пропорций окружности следует задействовать
свойство
property AspectRatio : Single; //диапазон от 0.5 до 2.00
Для создания эффекта ряби, которая появляется на воде после падения в нее ка кого-то предмета (рис. 17.6), стоит воспользоваться услугами компонента TRippleEffect. Основные свойства класса позволяют управлять координатами цен тра эффекта
property Center TPointF;
амплитудой зыби
property
Amplitude
частотой волн
property Frequency
и фазой
Single; //диапазон значений от О до 1
Single; //диапазон значений от О до 100
![]()
property Phase : Single;
Кроме того, в распоряжении программиста имеется свойство
property AspectRatio: Single;
позволяющее управлять пропорциями эффекта.
![]()
Рис. 17.6. Пример эффекта ряби
на воде, компонент TRippleEffect
![]()
Еще одним специалистом по "водным" эффектам можно считать класс тwaveEffect,
его задача заключается в создании иллюзии водной глади, покрытой волнами. Расстояние между волнами на воде определяется свойством.
property WaveSize : Single; //диапазон значений от 32 до 256
Управляя параметром
property Tirne : Single; //диапазон от О до 2048
мы сможем моделировать перемещение волн по поверхности.
Горизонтальная деформация краев текстуры ТWrapEffect
Компонент тwrapEffect позволяет применить к левому и правому краям изображе ния эффект горизонтальной деформации. Управление эффектом осуществляется отдельно для каждого из края. Так, состоянием левой грани ведают свойства:
property LeftStart : Single; //верхняя опорная точка левой грани property LeftControll : Single; //первая отклоняющая точка левой грани property LeftControl2 : Single; //вторая отклоняющая точка левой грани property
LeftEnd: Single; //нижняя опорная точка левой грани
Свойства определяют координаты точек невидимой кривой Безье, проходящей по левой границе рисунка. Координаты кривой представлены не в виде физических значений (.Х, У), а в виде вещественного числа из диапазона значений от О до 1. Если вы заполните все свойства Left ... нулями, то эффект горизонтальной дефор мации для левой грани отключается и она выпрямляется. Увеличение значений приводит к смещению левой границы изображения вправо.
Степенью деформации правой грани ведает квартет аналогичных свойств:
property
RightStart : Single; //верхняя опорная точка правой грани property RightControll : Single; //первая отклонтощая точка правой грани property RightControl2 : Single; //вторая отклонтощая точка правой грани property
RightEnd: Single; //нижняя опорная точка правой грани
Правила управления правой гранью несколько отличаются. На этот раз единичные значения соответствуют недеформированной грани, а уменьшение значений приво дит к смещению правой грани рисунка влево по горизонтали.
Аддитивные
эффекты
основаны на
принципе добавления
к исходному
изображе нию
каких-либо
дополнительных
графических
элементов.
Эффект отражения выводимого в нижней части изображения достигается с по мощью компонента TReflectionEffect. Размер отражения определяется свойством
propsrty Length: Single;
![]()
Свойство воспринимает значения из диапазона от О до 1 (максимальный размер от
ражения).
Эффекты свечения TGlowEffect и TlnnerG/owEffect
Два схожих
по задачам
компонента,
создающие
эффект свечения
с той лишь
разни цей,
что TGlowEffect предназначен
для создания
свечения
вокруг рамки
с изобра жением, а TinnerGlowEffect подсветит
рамку
изнутри.
Цвет свечения определяется
свойством
property GlowColor: TAlphaColor;
Плавность полутоновых
переходов и размер области свечения
зависят от состояния
свойства
property Softness: Single; //диапазон значений от О до 9
Компонент
TShadowEffect позволяет
программисту
научить
графический
объект
отбрасывать
тень. Управление
тенью
осуществляется при
посредничестве четверки
свойств. В
первую
очередь это
свойство
property
ShadowColor: TAlphaColor;
определяющее цвет тени.
Протяженность и
направление тени зависит от состояния свойств
property Distance:
Single;
property Direction: Single; //от О до 359 градусов
По умолчанию
расстояние,
на которое
отбрасывается
тень, равна
трем
единицам, а
угол падения
тени
соответствует
45 градусам.
Плавность цветовых переходов настраивается с
помощью свойства
property Softness:
Single;
Эффект
тиснения,
благодаря
которому
рисунок
приобретает
выпуклые
формы, по
зволяет обеспечить
компонент TEmЬossEffect. Заложенный
в компонент
алгоритм
находит в
изображении
ключевые
контурные
линии, создает
их дубликаты
и добавляет к
ним тень, в
результате
наблюдателю
начинает
казаться, что
контуры
приподняты
(или
вдавлены).
Глубина тиснения определяется состоянием свойства
property Amount: Single;//пo умолчанию 0,5
Свойство воспринимает значения из диапазона от О до 1.
Также
можно
управлять
расстоянием
между
линиями
контура и
дубликатами
этих линий,
для этого предназначено свойство
property Width: Single; //диапазон от О до 10
Набросок на бумаге TPaperSketchEffect
Интересный художественный эффект предлагает компонент TPaperSketchEffect. В
результате
его
применения к
изображению
оно
превращается
в черно-белый
на бросок,
который
художник мог
сделать
углем на бумаге.
Единственный параметр, который управляет результирующим рисунком
property BrushSize: Single; //по умоfчанию 3
определяет
размер кисти.
Результаты
работы
алгоритма,
заложенного
в компонент TPaperSketchEffect, отражает экранный снимок, предложенный на рис. 17.7 - по
лученное изображение можно сравнить с
оттиском гравюры на
плотной бумаге.
![]()
Рис. 17.7. Пример эффекта наброска на бумаге, компонент TPaperSketchEffect
Карандашный набросок TPen i/StrokeEffect
Компонент TPencilStrokeEffect придает изображению
вид нарисованного
вручную. Алгоритм
осуществляет
обработку
изображения
так, что в
результате
вместо цвет
ной картинки
получается
эффект черно-белого
карандашного
наброска
(рис. 17.8).
![]()
Базовое свойство компонента
property BrushSize: Single;//пo умолчанию 5
отвечает за грубость рисунка. Свойство способно принимать значения
из диапазона
ОТ 1 ДО 19.
![]()
Рис. 17.8. Пример эффекта карандашного наброска на бумаге, компонент TPencilStrokeEffect
Компонент
TPixelateEffect отвечает
за создание
эффекта
пикселизации.
Пиксели
зация - это
способ
искажения
изображения,
при котором
отдельные
пикселы ста
новятся
различимы
для н:евооруженного глаза
или группы
пикселов отображаются
в виде блоков
цветов.
Управление эффектом осуществляется с
помощью свойства
property
BlockCount:
Single; //по умолчанию 25
По мере увеличения значения свойства степень пикселизации
снижается.
Старая фотография TSepiaEffect
Компонент
тsepiaEffect придает
изображению
эффект
пожелтевшей от
старости фотографии.
Степень
старения
зависит от состояния
свойства
property Amount: Single; //по умолчанию 0,5
Свойство
воспринимает
значения из
диапазона от
О до 1, где 1
соответствует
ими тации наибольшего
возраста
фото.
Управление резкостью TSharpenEffect
Компонент
TSharpenEffect предназначен
для
управления
резкостью целевого
изо
бражения. Для этого оказывается
воздействие
на интенсивность
свечения
пикселов
картинки с
целью
достичь
максимальной
(или минимальной)
разницы
между со
седними
пикселами.
Управление резкостью осуществляется с
помощью свойства
property Amount: Single;//пo
УМQлчанию 1
Диапазон допустимых значений заключен в пределы от О до 1.
Компонент
ттoonEffect управляет
глубиной
цвета,
используемой
при выводе
изо
бражения. В результате
существенно
упрощается
цветопередача, чем и достигается
"мультяшный"
эффект. Для удобства
управления
эффектом
глубина цвета
разделе на на
несколько
цветовых
уровней,
которые управляются
свойством
компонента
property Levels: Single;//пo умолчанию 5
Свойство
принимает значения из диапазона от 3 (минимум)
до 15
(максимум).
Геометрические эффекты
В соответствии
со своим
названием
геометрические эффекты
в первую
очередь на
целены на изменение
параметров
изображения,
определяющих
его геометрическую
форму.
Аффинные преобразования TAffineTransformEffect
Компонент TAffineTransformEffect
за
счет
аффинных
преобразований
позволяет
повернуть
изображение
на заданный
угол и
управлять
уровнем
пикселизации
тек стуры
объекта. Для
управления
вращением
следует
воспользоваться
свойствами
property Center: TPointF; //координаты центра вращения
property Rotation: Single; //угол поворота
За уропе :п, Шlкселu3ации текстуры отвечает свойство
property
Scale: Single; //диапазон от 0,05 до 4
Компонент TCropEffect позволяет осуществить обрезку части изображения. Для этого
надо указать
координаты левой
верхней
preperty LeftTop; TPointF;
Графические эффекты
и правой нижней точек отсечения
property RightBottom: TPointF;
Все, что останется за пределами точек отсечения, выводу не подлежит. Позиции отсечения определяются в физических единицах измерения - пикселах.
BHHMAHHEI
Для
корректной
реализации
эффекта
обрезки
изображения
последнее
должно выво
диться
без искажения
пропорций.
Например,
если в качестве
целевого
компонента
ис пользуется Timage, то его свойство WrapMode желательно установить в
состояние
TIMageWrapМode. iwOriginal.
Перспектива TPerspective
TransfonrrirEffect
Компонент TPerspectiveTransformEffect позволяет вносить геометрические иска жения в форму изображения, придавая последнему вид прямоугольника, ромба, трапеции или любого другого четырехугольника. В частности, благодаря TPerspectiveTransformEffect можно получить преобразование перспективы (рис. 17.9):
![]()
Рис. 17.9. Эффект перспективы, компонент TPerspectiveTransformEffect
Форма результирующего четырехугольника формируется за счет квартета точек:
property
TopLe'ft: TPointF; property TopRight:.
TPointF;
//левый верхний угол
//правый верхний угол
property BottomRight: TPointF;
//правый нижний угол property BottomLeft: TPointF; //левый нижний угол
Заметим,
что на
результат
оказывают
влияние не только
значения
вершин
четырех
угольника, но
и
параметры
объекта,
содержащего
изображение.
Например,
приме няя
эффект к
компоненту тrmage, стоит
поэкспериментировать
с его
свойствами Align И WordWrap.
Компонент
ттilerEffect реализует
эффект
плитки,
размножая
исходное
изображе
ние.
Количество
клонов
рисунка определяется
свойствами
property HorizontalTileCount: Single;
property
VerticalTileCount: Single;
которые соответственно определяют число рядов
и колонок.
При желании
программист
может
назначить
отступы
между рядами
и колонками,
для этого
следует воспользоваться
свойствами
property HorizontalOffset: Single; //горизонтальный отступ
property VerticalOffset: Single; //вертикальный отступ
Наложение изображений TNorma/BlendEffect
Сознаюсь,
что отнеся
класс TNormalBlendEffect
к
компонентам,
осуществляющим
геометрические преобразования, я
пошел на поводу
способа
классификации эффек-
![]()
Рис. 17.1О. Пример эффекта наложения, создаваемого компонентом TNormalBlendEffect
Графические эффекты 261
![]()
тов, предложенных в стенах Embarcadero. Программисту всегда стоит считаться с мнением компании разработчика, хотя, на мой взгляд, TNormalBlendNfect ближе к компонентам, предназначенным для проведения операций трансляции, в которых участвует пара изображений. Впрочем, судите сами. Опорное свойство компонента
property Target: TBitmap;
предназначено для хранения второго изображения, которое будет наложено на ис ходное, т. е. изображение, к которому подключен компонент TNormalВlendEffect. Накладываемое изображение желательно создавать в формате PNG (напомним, что формат PNG поддерживает альфа-канал). Все остальное - дело техники. На рис. 17.1О производится сложение фотографии горного озера и надписи "FireMonkey", сохраненной в формате PNG.
Эффекты трансляции предназначены для создания управляемого плавного перехо да от исходного изображения ко второй - целевой картинке. На роль специалистов по трансляции претендуют два десятка компонентов. Мы с вами остановимся на изучении лишь наиболее показательных из них:
□
твandedSwirlTransitionEffect - эффект, создающий завихрение;
□ TFadeTransitionEffect- эффект
постепенного
исчезновения исходного
изобра жения с
последующим выводом целевого;
□ тcircleTransitionEffect- эффект проявления целевого изображения внутри
эллиптической
области;
□ тмagnifyTransitionEffect - плавное проявление увеличенного целевого изо бражения
внутри эллиптической
области (рис. 17.11);
□ тwaterTransitionEffect осуществляет переход между двумя изображениями с
использованием
эффекта
волнистой
водной
поверхности.
Все наши новые знакомые снабжены парой важных свойств. В первую очередь это свойство
property Target: TBitmap;
в котором хранится целевое изображение.
Второе свойство
property Progress: Single; //диапазон от О до 100%
управляет процессом трансляции. Выбирая значение от О до 100, программист оп ределит процент выполнения эффекта.
Компоненты TCircleTransitionEffect, TMagnifyTransitionEffect И TBandedSwirl TransitionEffect помимо управления степенью выполнения эффекта разрешают программисту провести дополнительные настройки. В частности, тройка перечис ленных компонентов спо_собна управлять позицией центра эффекта.
property Center:
TPointF;
![]()
![]()
![]()
![]()
Рис. 17.11. Пример эффекта, создаваемого компонентом TMagnifyTransitionEffect
Для
TCircleTransitionEffect и TMagnifyTransitionEffect это
центр круга,
из кото рого
проявляется
второе изображение
(рис.
17.11 ), а
для твandedSwir1тran
sitionEf fect это центр
пульсации.
Кроме того,
компонент
TCircleTransitionEffect умеет
определять
коэффициент расплывчивости
property FuzzyAmount: Single; //от О до 1, по умолчанию 0.1
и размер круга
property Size: Single; //по умолчанию 1
Компонент
твandedSwirlTransitionEffect
обладает
дополнительной
парой
свойств, управляющих
силой
скручивания
property Strength: Single;
и частотой скручивания в
спираль
![]()
property Frequency: Single;
ГЛАВА 18
Библиотека FireMonkey позиционируется не только как средство разработки кросс платформенных приложений, но и как инструмент построения изысканного поль зовательского интерфейса. Особо отрадно, что для создания привлекательного ин терфейса от вас, как от программиста, потребуется минимум затрат. В какой-то степени будет корректнее говорить не столько о программировании, сколько о ди зайне. Еще один плюс FireMonkey в том, что если вы обладаете достаточно высо ким художественным вкусом, то внешний вид и привлекательность ваших проектов окажется вне всякой конкуренции. А это очень важно для победы над конкурентом на рынке программного обеспечения, ведь, как гласит поговорка, встречают по одежке...
Как сделать
интерфейс
наглядным и интуитивно
понятным?
Существует
множест во рекомендаций
по размещению
и группировке
элементов
управления,
по эаданию
очередности
передачи
фокуса ввода,
по
использованию
пояснительных
надписей, по
подбору
цветовых
схем и по
многому
другому.
Давайте
рассмотрим
еще одно направление
в разработке
пользовательского
интерфейса - анимироJ1анные
эле менты
управления.
ЗАМЕЧАНИЕ
В FireMonkey под
анимацией
понимается
управляемое воздействие
на элементы
пользовательского
интерфейса,
приводящее к
постепенному
изменению их
местопо ложения,
размеров и
визуальных
характеристик во
времени.
Механизм анимации настолько прост, что в минимальной нотации не потребует от нас ни одной строки к_ода. Создайте новый проект FireMonkey и разместите на форме круг Circlel:TCircle (страница Shapes палитры компонентов). Теперь обра титесь к странице Animations палитры компонентов и перенесите на форму компо ненты:
□ FloatAnimationl:TFloatAnimation, этот компонент позволяет воздействовать на описываемые
вещественными
числами свойства
элемента
управления;
□ ColorAnimationl:TColorAnimation, этот компонент предназначен для управления цве'Fом компонента.
Подключите компоненты-аниматоры к кругу Circlel. Для этого следует восполь зоваться услугами окна Structure, отображающего структуру нашего проекта (рис. 18.1). Убедившись, что отвечающие за анимацию компоненты попали в нуж ные "руки", перейдит,ек Инспектору объектов.
![]()
Рис. 18.1. Подключение объектов анимации к
фигуре TCircle
Выберите в Инспекторе объектов компонент FloatAnirnationl. Научим его управ лять горизонтальным размером круга Circlel. Для этих целей, вооружившись мышью, осуществим 4 операции:
1. В свойстве PropertyName находим анимируемое свойство scale.x, отвечающее за горизонтальный масштаб компонента.
2. Настраиваем свойства, задающие диапазон изменяемых значений масштаба
StartValue=l И StopValue=2.
3. Выбираем событие-триггер, выступающее инициатором анимации. Установив в свойство Trigger в состояние IsMouseOver=true, мы укажем, что анимация на чинается в момент появления над компонентом указателя мыши.
4. Выбираем в свойстве Triggerinverse событие, завершающее анимацию:
Is ouseover=false (указатель мыши покинул анимируемый объект).
На этом настройка анимации для компонента FloatAnimationl завершена. Теперь
выберите компонент ColorAnimationl и в Инспекторе объектов настройте его пове-
![]()
............�-------..,..=---�
ОЬjю lmpector II
FloatAnlm./Jtion1 Tf!oatAn naoon
-JP1rqю,erntieтsw] Evеen--ts:
Object lnipector
·Colorд1llmatic111t "tCtk<A.'lil>,;,!ioo
jProper 1 Events1
!Auto11.everse
l()ekiv
Dural.iOn
fnilbled
---Тat1n
iuFl!lse
1 о
i□
·t 0,2
False
Animal.iOnType AuroReverse Delay
Duralion
Enabled
;atin
; :,2
E]False
[}False
!nterpo/г\ion
lnverse w,liveBirlФ'105 DeSigner
!1.оор
»!Nalne
Propert,/f'lame
StartfromCurrent
Suirtva!ue
.1\1.ne/Jr
·=-
!rlfa!se
: :eВinDdщi-/nieor s
H:'jF!lse
]Scale.X
itJFalse
1
!nierpolalion ,iliim,ar
1
,Inverse ! False
ooju,.,eВoongs Designer !uveflindiDnegSsigner
»r:;i iмва1
iloop 1[Е'F1alse
opertyt;ame I fill.Color
St:arlfromCurrent ;[';]Fl!lse
llstartValue I О Yellow
StopV.we 2
St,/leName
StopValue StyleNarne
1 ■Firebrick
1
.Тlt!I
ITrigger
ITtigger!nverse
Visualy..,
Alshown
\о
·IsНouseOver=
\tsMOUROver=falst!
,T;sg 10
1тngger 'IsНou'JeOver=Ьve
lтrig!jerlnverse IIsHouseOver=false
. l!linil y...--
---
![]()
Alstюwn
![]()
Рис. 18.2. Настройка свойств компонентов FloatAnimationl и ColorAnimationl
дение
в
соответствии
с рис. 18.2.
Анимированный
проект готов
к старту.
Согласи
тесь, что программисты
Embarcadero
потрудились
на славу,
оставив на
нашу долю
лишь
визуальную
сторону проектирования.
Общие черты компонентов-аниматоров,
класс TAnimation
Все
компоненты-аниматоры
(которые вы
обнаружите
на странице Animations па
литры
компонентов
Delphi) берут
начало от класса
EМX.Types.TAnimation.
Соответ
ственно большинство
свойств и методов
наследники "впитывают" от своего предка.
Рассмотрим
наиболее
важные из
них.
Длительность анимации определяется
значением свойства
property Duration: Single;
//единица измерения - секунды
При необходимости перед началом анимации можно выждать небольшую паузу.
Время задержки
устанавливается в
свойстве
property Delay: Single; //по умолчанию О секунд
Закон,
в соответствии
с которым
анимируемый
объект станет переходить
из одного
состояния в
другое,
определяется
свойством
property Interpolation: TinterpolationType;
type TinterpolationType = (itLinear, itQuadratic, itCubic,
itQuartic, itQuintic, itSinusoidal, itExponential, itCircular,
itElastic, itBack, itBounce);
По умолчанию установлен обычный линейный переход itLinear, приводящий к последовательной смене состояний (например,
цвета или размера). Вместе
с тем тип
данных тr�terpolationType предоставляет
такой
большой
выбор допустимых
вариантов
поведения,
что законом
интерполяции
наверняка
стоит
поэксперименти
ровать. На математический аспект интерполяции оказывает влияние вид анимации:
property AnimationType: TAnirnationType;//пo умолчанию atin
type TAnimationType = (atin, atOut, atinOut);
Сторонникам
бесконечного
цикла смены
размеров
(местоположения,
цвета и т. п.) стоит
установить в true свойство
property Loop: Boolean;
//по умолчанию false
Управление анимацией обычно осуществляется за счет
свойств-триггеров
property
Trigger:
TTrigger; //старт анимации
property Triggerinverse: TTrigger; //завершение анимации
указывающих,
какое именно
событие и при
каких
условиях
послужит
поводом для
начала и завершения
анимации. В качестве
примера таких событий
можно привести
получение и
утрату
элементом фокуса
ввода (IsFocused=true
и IsFocused=false) или
появление над объектом указателя мыши (rsмouseover=true) и уход указателя
( IsMouseOver=false).
Если процесс анимации в
данный
момент активен, то свойство
property Running: Boolean; //только для чтения
возвратит значение true.
При необходимости старт и
остановка анимации осуществляются из кода:
prooedure
Start; //старт анимации
prooedure
Stop; //остановка анимации
prooedure StopAtCurrent; //остановка анимации в
текущем состоянии
Временно приостановить процесс позволит свойство
property Pause: Boolean;
•
Как и
положено полноценным объектам, все компоненты-аниматоры способны
генерировать
события.
Старт анимации
сопровождается
вызовом
события
property OnProcess: ТNotifyEvent;
О завершении анимации уведомляет событие
property OnFinish:
TNotifyEvent;
Полученных знаний вполне достаточно для того, чтобы попробовать анимировать
объект не в период визуальной разработки, а во время выполнения программы. Во
![]()
фрагменте
кода,
предложенном в
листинге 18.1,
динамически создается
и подклю
чается
к панели Panell экземпляр
класса TFloatAnimation.
,......, . ......... -·-··1
•••••,;.1}................,....................................... ,
with TFloatAnimation.Create(Panell) do
Ьegin
Parent:=Panell;
PropertyName: = ',RotationAngle';
StartValue:=0;
StopValue:=180;
Trigger:='IsMouseOver=true'; Triggerinverse:='IsMouseOver=false';
end;
Обратите внимание
на то, что для
подключения к
анимируемому объекту
нам по
требовалось
передать
ссылку на
этот объект в
свойство Parent.
Свойство
property Inverse: Boolean;
//по умолчанию false
позволяет инвертировать анимацию (например, заставить сменяться кадры
TBitmapAnimation в обратном порядке).
Индивидуальные
особенности компонентов-аниматоров
На
странице Animations палитры
компонентов
Delphi
расположилось
9 компонен
тов, предоставляющих программисту
разноплановые
услуги по анимации
объектов FMX.
Каждый из
компонентов
обладает
индивидуальными
чертами,
определяю
щими область
применения
того или иного
потомка
класса TAnimation.
После подключения к анимируемому объекту (во время выполнения программы для этого достаточно воспользоваться свойством Parent, а во время визуального проектирования просто перетащить мышью узел компонента-аниматора в подчи нение обслуживаемого объекта (см. рис. 18.1)) следует выбрать управляемое свой ство. Для этого предназначено свойство
property PropertyName: AnsiString;
Надо понимать, что далеко не все свойства могут быть анимированы. Так, если вы
пытаетесь управлять
заливкой объекта с
помощью
компонента тcolorAnimation, то в
PropertyName могут быть
переданы только названия свойств, работающих с
цве том КИСТИ (Fill И Stroke).
ЗАМЕЧАНИЕ
Во время
визуального проектирования
Инспектор объектов подскажет
программисту,
какие именно
свойства
могут быть
задействованы
в анимации.
Для управления
правилами
анимации у
большинства
компонентов-аниматоров
имеется пара
свойств: startValue и StopValue,
однако тип
обслуживаемых значений
у каждого из аниматоров
индивидуальный, зависящий
от стоящих
перед
компонен том
задач.
Цветовая анимация, компонент TColorAnimation
Компонент тcolorAnimation позволяет управлять цветовыми характеристиками кис
ти заливки (свойство
Fill) или кисти
вывода
графических
примитивов
(свойство Stroke). Анимируются два цвета, значения которых указываются в свойствах:
property StartValue:
TAlphaColor; property StopValue: TAlphaColor;
Градиентная анимация, компонент TGradientAnimation
Благодаря
градиентной
анимации,
реализуемой
с помощью
компонента TGradientAnirnation, можно значительно улучшить внешний вид объектов, способ
ных управлять своими цветовыми характеристиками с помощью свойств Fill и
Stroke.
Параметры анимации назначаются при посредничестве свойств:
property StartValue:
TGradient; property StopValue: TGradient;
Но теперь вместо
данных о
цвете (как
это бьmо в тcolorAnimation) мы должны
ука зать
градиентные
настройки TGradient.
Анимированная картинка,
компонент TBitmapAnimation
Компонент
TBitrnapAnimation может
работать
совместно с
объектами,
способными
отображать
растровые
картинки
(обладающие
свойством Bitrnap). Эффект
анима ции достигается
путем
смены пары
картинок,
хранимых в свойствах
property StartValue: TBitmap; property StopValue:
TBitrnap;
Анимированный ряд, компонент TBitmapListAnimation
Возможности
компонента TBitmapAnimation меркнут
по сравнению
с его
"коллегой" твitmapListAnimation, ведь
последний
умеет
управлять не
парой, а целым
списком
картинок. Так
что при
остром желании
твi
tmapListAnimation способен
показать
пользователю
короткометражный
мультфильм. К
кадрам
анимации не
предъявля
ется особых
требований
за
исключением одного - все
они должны
быть одинако
вого размера
и склеены
друг за
другим в одну
ленту.
Лента с картинками передается в
свойство
property AnimationBitrnap: TBitrnap;
![]()
Для того чтобы
компоненг-аниматор смог корректно
воспроизводить наш
"шедевр"
мультипликации,
следует уведомить
его о
количестве
кадров в
ленге:
property AnimationCount: Integer;
Анимация числовых
свойств, компонент TF/oatAnimation
Компонент
TFloatAnimation предназначен
для
управления
свойствами компоненгов,
обслуживающих
числовые значения
вещественного
типа. Среди
потенциальных
клиенгов
компоненга-аниматора
свойства,
отвечающие
за
определение
местополо
жения
элеменга
управления,
размеры элемента
управления,
угол
поворота,
мас штаб, пррзрачность и
т. п. Стартовое и
конечное значения анимации заносятся
в
традиционные свойства
property StartValue: Single; property StopValue: Single;
Анимация
прямоугольной области, компонент TRectAnimation
Задача
компонента TRectAnimation заключается
в
воздействии
на значения
свойств мargins или Раdding ·элеменга
управления
(напомню, что
названные
свойства
опре деляют
величины
отступов
краев
дочернего
объекта от
границ
клиентской
области
родительского
контейнера).
В результате
мы получаем
эффект
изменения
размеров
анимируемого
объекта
относительно
его контейнера-владельца.
Вновь нашими основными помощниками станут свойства
property StartValue: TBounds; property StopValue: TBounds;
но на этот раз они типизируются структурой твounds.
Анимация траектории, компонент TPathAnimation
Проявив
немного
терпения
можно заставить объект двигаться
по заданной
траекто рии. Для анимации
этого весьма
занятного
действа стоит
воспользоваться услугами
компоненга TPathAnimation. Траектория
движения
задается с
помощью свойства
property Path: TPathData;
Свойство
предоставляет
доступ к
классу TPathData, предоставляющему
услуги по
определению
контрольных
точек траектории.
Стоит отметить существование еще одного свойства:
property Rotate:
Boolean; //по умолчанию false
которое в
состоянии true заставит вращаться анимируемый объект вокруг своей
оси.
Проиллюстрируем работу с
анимированной траекторией на небольшом примере.
Для этого нам
понадобится
помощь фигуры
Circlel:TCircle, к
которой
следует
подключить
компоненг PathAnimationl:TPathAnimation.
Настройте компонент PathAnimationl следующим образом:
□ триггер Trigger активации анимации переведите в состояние IsVisiЫe=true
(анимация включена, ·если объект виден);
□ продолжительность анимации Duration=б;
□ активируем автореверс AutoReverse=true;
□ выберем синусоидальную интерполяцию Interppolation=itSinusoidal.
Точки траектории заполним в коде программы, для этого подойдет событие созда
ния формы (листинг 18.2). В качестве контрольных точек мы выберем четыре угла родительского контейнера, которому принадлежит окружность Circlel:тc�rcle.
![]()
with PathAnimationl do
Ьegin
Stop; Circlel.Position.Point:=PointF(0,0); Path.Clear; Path.MoveTo(PointF(0,0));
Path.MoveTo(PointF((TControl(Circlel.Parent) .Width -
Circlel.Width)/2,0));
?ath.MoveTo(PointF(TControl(Circlel.Parent) .Width -
Circlel.Width,O)); Path.MoveTo(PointF(TControl(Circlel.Parent) .Width-Circlel.Width,
TControl(Circlel.Parent) .Heig:it-Circlel.Height));
Path.MoveTo(PointF((TControl(Circlel.Parent)
.Width -Circlel.Width)/2, TControl ('Circlel. Parent) .Height-Circlel.Height) ) ;.
Path.MoveTo(PointF(O,TControl(Circlel.Parent) .Height-Circlel.Height)); Path.MoveTo(PointF(0,0));
Path.ClosePath;
Start;
end;
После старта
приложения окружность
начнет
свое
движение
вдоль границ контей
нера.
![]()
![]()
Пользователи
компьютера
далеко не
всегда
набирают
деловые
письма или
готовят сложные
бухгалтерские
отчетности.
Вместо этого довольно
много
времени
человек
проводит за
компьютером,
не нажимая
клавиши, а
просто
откинувшись
на спинку
кресла и
включив любимую
мелодию или
фильм. В этом
ему помогают
многочис
ленные
мультимедийные
проигрыватели,
написанные
специалистами
своего дела. Язык Delphi предлагает вам присоединиться к
гонке по разработке приложений
с функцией
поддержки
мультимедиа, в
этом вам
помогут очень
удобные и простые
в обращении компоненты
и классы.
Реализуемую в рамках FМХ. Media работу с мультимедиа можно разделить на два направления. Первое из них нацелено на воспроизведение аудио- и видеофайлов, второе посвящено захвату аудио- и видеопотоков, поступающих от аппаратных устройств компьютера или их программных эмуляторов.
С целью воспроизведения мультимедийных данных создателями FireMonkey разра ботана пара визуальных элементов управления - TMediaPlayer и TMediaPlayerControl. Их вы обнаружите на вкладке Additional палитры компонентов Delphi. Компо нент тмectiaPlayer обеспечивает доступ к файлам с данными мультимедиа, а TMediaPlayercontrol отвечает за вывод видеопотока на экран. Кроме того, в составе FMX имеется класс TMediaCodecManager, осуществляющий общее руководство над декодерами мультимедиа.
Менеджер кодеков TMediaCodecManager
Менеджер кодеков предназначен для управления имеющимися в системе кодеками мультимедиа. По умолчанию при работе под управлением операционной системы Windows менеджер кодеков поддерживает аудиоформаты WМА, МРЗ, WАV и ви деоформаты AVI и WMV. Если приложение предназначено для OS Х, то вы полу чите доступ к звуковому кодеку МРЗ и видеокодекам и MOV, M4V, и МР4.
![]()
![]()
Для того чтобы избежать возникновения исключительной ситуации при попытке воспроизведения неподцерживаемого формата файла, следует проверить наличие соответствующего кодека. Данная задача решается при посредничестве метода
class function IsCodedExists(const AFileName: string): Boolean;
В единственный
параметр метода передается
имя файла
или только расширение
имени (например,
".mрЗ"),
если файл
может быть
воспроизведен- метод
возвра тит true.
ВНИМАНИЕ!
Для
своей работы
компоненты TMediaPlayer и ТМediaPlayerControl используют
имеющиеся в вашей
операционной
системе
кодеки. Поэтому
качество
воспроизведе
ния
мультимедиа
в первую
очередь
определяется
предустановленным
программным обеспечением.
Теоретически
никто не
запрещает
нам
попытаться
воспроизвести
и неподцержи
ваемые по умолчанию
форматы, но в
подобном
случае
следует
попробовать
зареги
стрировать
новый кодек.
Такая задача
по плечу
методу
class
procedure RegisterMediaCodecClass(const Extension, Description: string; MediaType: TMediaType; MediaCodecClass: TCustomМediaCodecClass);
Однако перед обращением к методу вы должны быть уверены, что соответствую щее программное обеспечение развернуто на компьютере пользователя.
Класс TMediaCodecМanager окажется весьма полезным 8 ТОЙ ситуации, когда ВЫ предполагаете предоставлять доступ к зарегистрированным файлам мультимедиа с помощью диалога открытия файлов. В таком случае метод класса
class function GetFilterString: string;
возвратит строку, отформатированную в соответствии с требованиями свойства Filter диалога TOpenDialog. Нам останется только инициализировать названное свойство компонента во время запуска приложения.
Если вам достаточно лишь узнать типы поддерживаемых файлов, то стоит восполь зоваться более простым методом
class function
GetFileTypes: string;
Метод возвратит текстовую строку с допустимыми расширениями имен файлов, разделенными точкой с запятой.
При желании сведения о приемлемых расширениях имен файлов можно запраши вать отдельно для каждого из типа кодеков
ТМеdiаТуре = (Audio, Video);
для этого предназначены методы
class function GetFilterStringByТype(MediaType: TMediaType): string;
class function GetFileTypesByType(MediaType: TMediaType): string;
![]()
![]()
и компонент TMediaP/ayerControl
Для построения приложения, способного воспроизводить аудио- и видеофайлы, следует разместить на форме проигрыватель тмectiaPlayer и его помощник - ком понент тмectiaPlayerControl, специализирующегося исключительно на отображении видеоряда (рис. 19.1).
![]()
![]()
Рис. 19.1. Проигрыватель на основе компонентов TMediaPlayer и TMediaPlayerControl
Работа мультимедийного приложения начинается с отправки имени подлежащего воспроизведению файла в свойство
property FileNarne: string;
проигрывателя тмectiaP1ayer. Если в системе предустановлен кодек, способный ра ботать с содержащимися в файле данными, то в этом случае в недрах компонента создается экземпляр класса тмеdiа, представляющий собой объектно-ориенти рованное воплощение мультимедийных данных, загруженных из файла. Доступ к экземпляру тмесtiа обеспечивает свойство
property Media: TMedia; //только для чтения
Получив экземпляр класса тмесtiа, мы приобретаем возможность управлять воспро изведением мультимедийных данных. В простейшем случае программисту доста точно знать о существовании методов
![]()
prooedure Play;
procedure Stop;
соответственно начинающих и останавливающих воспроизведение. Вызов пере численных методов сразу отражается в свойстве
property
State: ТМediaState; //только для
чтения TMediaState = (UnavailaЫe, Playing,
Stopped);
отражающем текущее состояние проигрывателя TMediaPlayer.
Кроме старта и остановки воспроизведения TMediaPlayer предоставляет возмож ность изменять громкость звуковой дорожки
property Volume: Single; //диапазон от О до 1
Кроме того, совсем несложно отследить (а при необходимости и изменить) пози цию воспроизведения, для этого предназначено свойство
property CurrentTime: TMediaTime;
Для того чтобы убедиться, что не достигнуто ли окончание мультимедийного фай ла, следует проверять состояние свойства
property Duration: ТМediaTime;
хранящего общую продолжительность файла.
ЗАМЕЧАНИЕ
Свойства CurrentTime и ouration ориентированы на тип данных TTimespan, пред
ставляющий время
как интервал, в котором один такт соответствует 100 наносекундам.
Если воспроизводится видеоряд, то обратившись к свойству
property VideoSize: TPointF; //только для чтения
мы сможем уточнить размер отображаемой картинки.
Когда работа с мультимедийным объектом завершается, то следует обратиться к методу
procedure Clear;
освобождающему задействованные ресурсы и
удаляющему экземпляр TMedia.
Второй ингредиент приложения мультимедиа - компонент тмediaPlayercontrol просто отвечает за вывод видеоизображения на экран, для этого он подключается к проигрывателю при посредничестве свойства
property MediaPlayer: ТМediaPlayer;
на этом настройки
компонента
заканчиваются - он
готов
отображать
загруженное в медиаплеер
видео.
![]()
![]()
Приложения FireMonkey способны не только воспроизводить мультимедийные файлы, но и перехватывать аудио- и видеопотоки, формируемые с помощью имеющихся в распоряжении компьютера устройств мультимедиа (видеокамер и микрофонов). Для обслуживания подобных аппаратных устройств в FireMonkey был создан ряд классов, наиболее важные из них:
□ тcaptureDeviceManager отвечает за доступ к аппаратным устройствам;
□
TAudioCaptureDevice предоставляет в
распоряжение базовый функционал теку- щего
аудиоустройства;
□ тvideoCaptureDevice позволяет управлять текущим видеоустройством.
Кроме
того, в
составе
библиотеки
предусмотрен
очень
простой в
использовании
компонент-камера
тcameracomponent, нацеленный на
мобильные приложения.
Менеджер устройств TCaptureDeviceManager
Класс тcaptureDeviceManager
нацелен
на сбор
сведений об
установленных
в системе
устройствах
мультимедиа
(поток данных
которых
может быть
захвачен и
задейст
вован в
интересах
приложения) и
предоставлении
доступа к
этим
устройствам
из программ
FireMonkey.
Для того чтобы воспользоваться услугами менеджера, не стоит вызывать его кон структор, вместо этого гораздо проще обратиться к свойству класса
property Current: TCaptureDeviceManager;
и получить доступ к менеджеру текущих аудио- и видеоустройств.
Число доступных мультимедийных устройств уточняется благодаря свойству
property Count integer;
Зная количество устройств, мы сможем обратиться к любому из них по индексу
property Devices[Index: Integer]: TCaptureDevice;
и в качестве
ответа
получить
доступ к устройству
и его
описание в формате
класса тcaptureDevice. Такая
идея
реализована
в листинге 19.1, в коде которого
мы собра
ли
полный набор
текущих
мультимедийных
устройств,
установленных
в компью тере, и
передали его
в список Listвoxl: TListBoxl.
' , t.1• ,...,,.," if;/ 1./1 1','.J/!$,;/'l!r .t ,"''!,••1 , elif:,1' '.,'ilf.J/ , ; 1 ' , ,,."'f I 17'-'!/l/t.h /; :,,r:., '?W-i','lrtт,,lii, ;;1,': ," л; ,.;,"
, ,,,.,, !v, , t/..,ti, : ,j,,' 1tt• , • 1 ,,' i1!1 '! , 1, r ,'( l1t•,C'l'•l+f1!w/' v. )i,;'l!i!Jiif!'"J,ii/1.) \./ir•. )1J.'f'i //!!;,": 1;/,1,,,"1 .
var CDM TCaptureDeviceManager;- CD TCaptureDevice;
LBI TListBoxitem; i integer;
Ьegin
CDM:=TCaptureDeviceManager.Current;
![]()
![]()
for i := О
to CDM.Count-1 do Ьegin
CD:=CDM.Devices[i];
LBI:=ТListBoxitem.Create(ListBoxl);
ListBoxl.AddObject(LBI);
end; end;
Еще один способ захвата текущего аудио- и
видеоустройства обеспечивают свойства
property DefaultAudioCaptureDevice: TAudioCaptureDevice; //ayдJIIO
property DefaultVideoCaptureDevice: ТVideoCaptureDevice; //видео
Если устройства отсутствуют, то свойства возвратят неопределенные
указатели NULL, иначе в
ваше
распоряжение
соответственно
поступят
экземпляры
классов TAudioCaptureDevice и TVideoCaptureDevice, способные
передавать в
приложение мультимедийные
данные
(листинг 19.2).
!,:"........................,.. .......................················••'f------·"""'••..····..,--..............-...................,_.........,...............-...,....,......_............_.. .,, _,.,....,,.... ·-··-........ .._,
' л...,.... 18.Z. lull8'f --- • ![]()
var СОМ TCaptureDeviceManager; АС TAudioCaptureDevice; VC TVideoCaptureDevice;
Ьegin
СОМ:=
TCaptureDeviceManager.Current; АС ·= CDM.DefaultAudioCaptureDevice;
VC ·= CDM.DefaultVideoCaptureDevice;
if Assigned(AC) and Assigned(VC) then Ьegin
//работаем с устройствами
end; end;
Для
программистов,
желающих
получить
доступ к
строго
определенному
устрой ству,
подходит
метод
function GetDevicesByName(Name: string): TCaptureDevice;
В единственный параметр метода следует передать название аудио- или видеоуст ройства.
В FireMonkey
специалистами
по захвату
аудио- и видеопотоков
считаются
классы TAudioCaptureDevice и TVideoCaptureDevice. Оба
класса
построены на
фундаменте
абстрактного
класса тcaptureDevice и
поэтому
вооружены
идентичным
базовым набором
свойств и
методов
(табл. 19.1).
![]()
Таблица 19.1. Свойства и
методы, унаследованные от класса тcaptureDevice
![]()
![]()
Аудиозахват ТAudioCaptureDevice
Нацболее важные профессиональные черты
классов
аудио- и видеозахвата
реализо ваны
самостоятельно.
Так, у устройства
захвата
аудиопотока
(кроме
унаследован
ных от
родительского
класса тcaptureDevice свойств и
методов)
имеются два
клю чевых свойства.
Первое из них
property
FilterString: String;
содержит перечень поддерживаемых расширений имен файлов (*.mрЗ; *.wav;
*.wma).
Второе свойство
property FileName : String;
определяет
путь и имя к файлу,
в который
будет отправлен
аудиопоток.
Установив имя
файла,
нам остается
лишь вызвать
метод startcapture
(), и файл
начнет запол
няться
данными.
Видеозахват TVideoCaptureDevice
В качестве
основного
устройства
видеозахвата ·в
большинстве
случаев
рассматри
вается
видеокамера.
Если мы работаем
со смартфоном
или планшетным
компьюте
ром,
оснащенным
дополнительной
фронтальной
камерой, то
перед тем как
сделать
первый
снимок
проверьте, что
выбрано
нужное
устройство.
Для этого предназна
чено
свойство
property Position:
TDevicePosition;
TDevicePosition =
(dpUnspecified, dpFront,
dpBack);
Если камера оснащена вспышкой, то свойство
property
HasFlash: Boolean;
![]()
возвратит значение true. Убедившись в наличии вспышки, можно установить предпочтительный режим ее функционирования
property FlashМode: TFlashМode;
Предусмотрены
три варианта
настроек: автоматический
(fmAutoFlash), вспышка
отключена (fmFlashOff) или
вспышка
постоянно
включена (fmFlashOn).
Наличие дополнительного освещения (предназначенного для устранения эффекта красных глаз при портретной съемке) проверяется свойством
property HasTorch: Boolean;
При положительном результате программист может установить режим этого осве щения
property TorchМode: TTorchМode;
Допустимых вариантов три: отключено (tmModeOff), включено (tmМodeon), автома
тическое
включение (tmМodeAuto).
Способ фокусировки камеры определит свойство
property FocusMode: TFocusMode;
TFocusMode = (fmAutoFocus, fmContinuousAutoFocus, fmLocked);
Качество фото- и видеоматериала, возвращаемого камерой, также подлежит на стройке, для этого предназначено свойство
property Quality: TVideoCaptureQuality; TVideoCaptureQuality
= (vcPhotoQuality, vcHighQuality,
� vcMediumQuality, vcLowQuality);
(
В отличие от устройства аудиозахвата, выдающего непрерывный поток данных,
видеозахват осуществляется с некоторой периодичностью - покадрово. В тот мо мент времени как устройство видеозахвата (например, веб-камера) сформирует очередной фотоснимок, у нашего программного об'Ьекта ТVideoCaptureDevice гене рируется событие
property OnSarnpleBufferReady: TSampleBufferReadyEvent;
типизированное следующим образом
TSampleBufferReadyEvent = prooedure(Sender: TObject;
const ATime: TMediaTime) of oЬject;
Первый параметр события sender содержит ссьmку на камеру, а
второй ATime -
номер такта времени.
Для того чтобы мы смогли перехватить полученный камерой снимок, в коде обра ботки события onsampleBufferReady () следует вызвать еще один метод устройства видеозахвата
prooedura SarnpleBufferToBitmap(const AВitmap: TBitmap;
const ASetSize:
Boolean);
Снимок
возвращается
методом
через параметр АВitmap, во второй
параметр следует
заносить
значение true (это
заставит
метод
самостоятельно
настроить
размеры снимка).
![]()
Практическая сторона решения задачи получения фотоснимка с помощью устрой ства видеозахвата выглядит следующим образом. Сначала в заголовочном файле с описанием главной формы проекта следует объявить прототип метода, совмести мого с TSampleBufferReadyEvent. В нашем примере этот метод станет называться VideoBufferReady () (листинг 19.3).
![]()
prooedure TForml.VideoBufferReady(Sender: TObject; const ATime: int64);
var AВitmap :TBitmap;
VCO: TVideoCaptureOevice;
Ьegin
AВitmap:=TBitmap.Create(0,0);
VCO:=Sender as TVideoCaptureOevice; VCO.SampleBufferToBitmap(AВitmap,true);
{дальнейшая обработка полученных растровых картинок
при желании можно сохранить файлы с фотографиями на диск}
AВitmap.SaveToFile('c:\\photo'+IntToStr(ATime)+'bmp',nil);
end;
В рамках представленного кода мы создаем объект растровой графики твi tmap* АВitmap и передаем в него снимок с видеокамеры. Дальнейшие действия зависят от стоящих перед приложением задач, в простейшем случае снимки можно просто сохранять на диске компьютера.
Для того чтобы устройство видеозахвата смогло самостоятельно вызывать разрабо танный нами метод, следует подключить его в качестве обработчика события onsampleBufferReady () . Для этого достаточно добавить всего OДIJY строку кода в рассмотренный ранее листинг 19.4.
![]()
var СОМ TCaptureOeviceManager; VC TVideoCaptureOevice;
Ьegin
СОМ:=
TCaptureOeviceManager.Current; VC := COM.OefaultVideoCaptureOevice; if (VC<>nil) then
VC.OnSampleBufferReady:=VideoBufferReady;
end;
Теперь, для получения видеопотока от камеры нам остается вызвать метод
Startcapture () устройства.
![]()
Компонент-камера тcameraCornponent описан в модуле FМX.Media и предназначен для
использования
в мобильных приложениях.
Компонент
очень прост в обращении,
и зачастую
используется
совместно
с
компонентом TActionList (см.
главу 7), в кото
ром вы найдете
несколько
заранее заготовленных
команд для
управления
камерой.
Стоит
отметить,
что
компонент
позволяет
выбрать
фронтальную
или основную камеру
мобильного
устройства
property Kind: TCameraKind;
TCameraKind
= (ckDefault, ckFrontCamera, ckBackCamera);
Включение камеры осуществляется свойством
property Active: Boolean;
После осуществления снимка генерируется событие
property OnSampleBufferReady: TSampleBufferReadyEvent; TSampleBufferReadyEvent = procedure(Sender: TObject;
const ATime: TMediaTime) of object;
В этот момент
и следует
извлечь снимок из буфера
камеры, для
этого
предназначен
метод
procedure SampleBufferToBitmap(const AВitmap: TBitmap;
const ASetSize: Вoolean);
ЗАМЕЧАНИЕ
У камеры
TCameraComponent вы
обнаружите
уже знакомые
по классу
видеозахвата
TVideoCaptureDevice свойства Quality, HasFlash,
FlashМode, FocusMode и TorchМode.
![]()
![]()
ГЛАВА 20
Если еще три десятилетия назад персональная ЭВМ для большинства из нас была невиданной диковинкой, к которой боялись даже прикоснугься, то сегодня перед компьютером не спасует даже ребенок. Почему так произошло? Неужели нынеш ние дети умнее детей, появившихся на свет четверть века назад? Надеюсь, что да. Но скорее всего отсутствие боязни перед электронной техникой объясняется про ще - современные компьютеры очень дружелюбны.
В первую очередь дружелюбие компьютера проявляется через развитый пользова тельский интерфейс. И с каждым десятилетием этот интерфейс приобретает новое качество. Судите сами. В середине ХХ века основным способом ввода данных бы ли перфокарты и перфоленты, полагаю, что об удобстве здесь можно даже не гово рить. Позднее появились клавиатуры, еще позднее - манипуляторы-мыши. Пер вые монохромные устройства отображения на электронно-лучевых трубках посте пенно сменились цветными мониторами, а сегодня они уже почти полностью вытеснены жидкокристаллическими дисплеями. Но прогресс не стоит на месте. Пользователи проявляют все возрастающий интерес к интеллектуальным устройст вам, обладающим сенсорными функциями ввода данных. Это в первую очередь многочисленные платежные терминалы, планшетные компьютеры, электронные доски и смартфоны. Как правило, перечисленные устройства не снабжены отдель ной клавиатурой или же возможности их клавиатуры сильно ограничены. Но это не беда, программисты научили эти устройства воспринимать естественный ввод или, говоря проще, жесты человека.
ЗАМЕЧАНИЕ
Под жестом
(gesture) мы
станем
понимать
произвольную
геометрическую фигуру,
на
рисованную
пользователем
на устройстве
сенсорного
ввода с помощью
электронного
пера или
просто
пальцем.
Описание жеста
Большинство построенных на базе класса тcontrol элементов управления Delphi способно
реагировать
на некоторый
набор
заранее
предопределенных жестов.
Жест
![]()
282 • Глава 20
+-- sgiLeft=1
--+ sgiRight=2
1
sgiUpDown=13
sgiDownUp=14
У sgiCurlicue=25
···································
: sgiDouЫeCurlicue=26
|
|
sgiUp=З
- sgiRightLeft=16
!
+ -►• sgitLeftRigh=15
о) sgiCircle=27
sgiDown=4 sgiDouЫeCircle=28
1 ; ,Lel\., ..... ;u c . 17 ; m; , L 29
1.. .. ; ,.. .......F ;" . ,.... ... .. ;s ; ;,o .;,hF,.
. . . . . . . . . . . . . . . . . ' . . . . . . . . \ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
... .. i o Le 7..... - ..sg - n-Le L-on =-1.9 .. .. -hev n -31...... <
...L.. V
L
i - ght= .-. . - .. - -Ri.ghtLong=20
,g;eenup=9 ---: ,g;saatohoШ•21 !
sg:iCсhev'r•on"D=own=з32
г ·6······.·······························································
□
sgiLeftDown=10
s iТriangle=22 :> sgiChevronRight=34
J sgiRightUp=11
-i sgiRightDown=12
··································!···································
sgiSquare=23
··································,···································
sgiCheck=24
Рис. 20.1. Стандартные жесты и их константы
представляет собой некую геометрическую фигуру, хранимую в памяти в формате
структуры тstandardGestureData (листинг
20.1),
определение которой
вы обнаружи
те в модуле FМX.Gestures.
![]()
type TStandardGestureData = record
Points: TGesturePointArray; //массив точек TPoint,
описывающих жест GestureID: TGestureID; //идентификатор жеста
Options: TGestureOptions; //опции
Deviation: Integer; //допустимое отклонение жеста от стандартного
ErrorMargin: Integer; //максимальное число ошибок
end;
Ключевое
поле
структуры - Points.
Это
динамический
массив,
хранящий
коорди наты
точек
геометрической
фигуры жеста.
Второе поле
содержит
идентификатор
жеста. В Delphi
имеется
более трех десятков
предустановленных жестов
(рис. 20.1).
При анализе
жеста учитывается не только его соответствие
геометрической фигуре,
но и ряд
других
характеристик. В
частности,
поле опций Options контролирует-на
правление goDirectional, наклон goSkew и факт совпадения
начальной и
конечной
точек goEndpoint.
В
библиотеке
FireMonkey реакция на
сенсорный
ввод обеспечивается
на уровне
класса TFrnxObject. Для того
чтобы
элемент
управления
приобрел
способность
реа гировать
на
предопределенные
жесты, ему
понадобится помощник
- менеджер
жестов,
компонент TGestureмanager. Для
подключения
менеджера
жестов к элемен ту
управления необходимо сделать несколько шагов. Сначала следует обратиться
к свойству
property Touch: TTouchМanager;
Свойство
предоставляет
доступ к
инкапсулированному
в элемент
управления
объ екту - менеджеру
прикосновений
(экземпляру
класса ттouchМanager). В свою
оче редь у
менеджера
прикосновений
имеется
свойство
property GestureManager: TGestureManager;
позволяющее
ассоциировать
с элементо управления
интересующий
нас менеджер
жестов TGestureManager.
Процесс подключения компонента TGestureManager к форме проекта отражает эк
ранный
снимок
Инспектора
объектов
(рис. 20.2).
Обратите
внимание на
то, что
воспользовавшись разделом Touch I Gestures I Standard, программист определяет,
на какие
именно жесты
должен реагировать
элемент
управления. Для
этого доста
точно поставить
"галочку" рядом с
соответствующим изображением.
Для
описания
реакции на
жест
программисту
следует
воспользоваться
событием
потомков
класса FМХ. Types. тcontrol:
property
OnGesture: TGestureEvent;
type TGestureEvent = procedure(Sender: TObject;
const Eventlnfo: TGestureEventlnfo; var Handled:
Boolean) of object;
Событие
генерируется
в тот момент,
когда пользователь
(воспользовавшись
сен сорным
экраном,
электронным
пером или
просто мышью)
нарисует над
поверх
ностью
элемента управления
какую-то
геометрическую
фигуру.
Основная
задача
обработчика
события заключается
в двух вещах:
![]()
284 Глава 20
□ провести анализ введенного жеста и найти ему наиболее точное соответствие среди жестов, имеющихся в распоряжении элемента управления (свойство
Touch);
□ выполнить соответствующую жесту операцию.
![]()
Рис. 20.2.
Присоединение менеджера TGestureManager к
форме
Ключевой параметр собьпия - Eventinfo, именно он уведомляет нас о том, какая фигура бьша нарисована пользователем_. Параметр представляет собой запись TGestureEventinfo, объявление которой вы найдете в листинге 20.2. Установив по следний параметр события нandled в состояние true, мы уведомим систему, что жест в обработке более не нуждается.
![]()
type TGestureEventinfo = record
GestureID: TGestureID;
Location: TPointF;
Flags: TinteractiveGestureFlags; Angle: DouЫe;
InertiaVector: TPointF;
Distance: Integer; TapLocation: TPointF;
end;
Назначение полей структуры TGestureEventinfo предложено в табл. 20.1.
![]()
Таблица 20.1. Описание полей записи TGestureEventinfo
|
Поле записи |
Описание |
|
GestureID |
Идентификатор жеста |
|
Location |
Координаты текущей точки на поверхности устройства ввода |
|
Flags |
Набор флагов (gfBegin, gfinertia, gfEnd}, доступных только в
момент ввода жеста |
|
Angle |
Угол движения электронного пера (курсора мыши, пальца пользователя)
относительно
координатных
осей
ус;тройства
ввода |
|
InertiaVector |
Пара
значений х и У, благодаря
которым
можно
идентифицировать
на-
правление движения
электронного
пера. Положительное
значение х сви- детельствует
о движении
пера к
правой
границе,
отрицательное
- к левой. Положительное значение
У говорит о том, что перо опускается
вниз экрана,
отрицательное
- поднимается
вверх |
|
Distance |
Расстояние в
пикселах между текущей (Location) и предыдущей точками |
|
TapLocation |
Местоположение начальной точки фигуры жеста |
ЗАМЕЧАНИЕ
Стоит
заметить, что
значения
идентификаторов
стандартных
жестов имеют положи тельные
значения (от 1 и
далее),
значения
идентификаторов пользовательских
жестов
всегда
отрицательные
(от -1 и
далее).
Кроме стандартных жестов, предложенных разработчиками FireMonkey, приложе ние способно реагировать и на многоточечные касания (multi-touch). Для этой цели стоит в Инспекторе объектов развернуть свойство Touch и изучить вкладку InteractiveGestures. Среда проектирования позволит нам подключить 5 дополни тельных жестов (табл. 20.2).
+
Таблица 20.2. Интерактивные жесты FМХ. Types. TinteractiveGesture
|
Значение |
Описание |
Точек
касания |
Win |
osx |
iOS и Android |
|
igZoom |
Увеличение |
2 |
+ |
+ |
+ |
|
igPan |
Прокрутка |
1 |
+ |
+ |
+ |
|
igRotate |
Вращение элементов польза-
вательского
интерфейса |
2 |
+ |
+ |
.. |
|
igTwoFingerTap |
• Касание двумя пальцами |
2 |
+ |
|
+ |
|
igPressAndTap |
Касание и нажатие |
2 |
+ |
|
+ |
|
idDouЫeTap |
Двойное касание |
2 |
|
|
+ |
|
idLongTap |
Долгое нажатие |
|
|
|
+ |
![]()
286 Глвт, 20
Пример обработки стандартных жестов
Специалисты Embarcadero приложили все усилия для того, чтобы программист чувствовал себя максимально комфортно при использовании в проекте механизма сенсорного ввода. Подтверждение тому - простота создания приложения, способ ного общаться с пользователем с помощью жестов.
Для нашего примера понадобятся новый проект и компонент TGestureManager, ко торый следует подключить к форме Forml с помощью свойств тouch - GestureМanager. Отметьте те жесты, на которые должна реагировать форма (см. рис. 20.2). Разместите на форме метку Labell, этот компонент проинформирует нас о жесте пользователя.
Собственно обработка жеста будет осуществлена в
рамках события OnGesture ()
единственной формы проекта (листинг 20.3).
![]()
prooedure
TForml.FormGesture(Sender: TObject;
conet Eventinfo:
TGestureEventinfo; var Handled:
Boolean);
Ьegin
if Eventinfo.GestureID>-1 th8n
LaЬell.Text:=IntToStr(Eventinfo.GestureID) elae Labell.Text:= '?'
end;
Теперь, нарисовав одну из стандартных геометрических фигур в клиентской облас ти формы, вы моментально узнаете ее идентификационный номер.
Результаты
работы
приложения,
способного
распознавать
стандартные
жесты, представлены
на рис. 20.3.
В данном
случае форма
распознала
состоящий из
двух петель
стандартный
жест с номером
26.
![]()
Рис. 20.3. Экранный снимок формы распознавания жестов
![]()
ГЛАВА 21
Давным-давно, в середине 90-х годов прошлого века при появлении на свет самой первой версии Delphi (в те времена еще компании Borland) о новой среде проекти рования в первую очередь говорили как о превосходной платформе для разработки настольных и клиент-серверных приложений для баз данных (БД). Спустя почти 20 лет Delphi не только не угратила своих позиций в этом сегменте компьютерного рынка, но и существенно нарастила свои возможности. Судите сами, Delphi ХЕ5 позиционируется как среда проектирования, поддерживающая самые распростра ненные и успешные системы управления базами данных (СУБД), такие как Oracle, Informix, Microsoft SQL Server, DB2, Sybase, MySQL, Fireblrd, PostgreSQL и конеЧt но же собственное дeтищe-InterBase Server. r
Разработка
приложений
БД в Delphi - весьма
интересная и
многогранная
тема; достойная отдельной книги. К
сожалению, сейчас автор вынужден ограничить
ся всего одной главой, знакомящей читателя с
настольной базой данных
InterBase ToGo. Излагая
материал,
будем
исходить из
предположения, что
читатель
хорошо
знаком с
процессом разработки
Бд, работал
с сервером InterBase
и имеет хотя
бы начальное
представление
о
проектировании
приложений с
помощью ком
понентов dbExpress.
ВНИМАНИЕ/
Для
работы с
базами
данных lnterBase в Delphi
предусмотрены
специализированные
компоненты lnterBase Express. Если в вашей поставке Delphi ХЕ4 они отсутствуют,
то обратитесь
к ссылке http;//cc.embarcadero.com/item/29408, перейдя
по которой вы
сможете
скачать архив с этими компонентами.
Механизм управления данными InterBase ToGo предназначен для создания про стейших однопользовательских БД, для функционирования которых совсем не тре буется программное обеспечение сервера InterBase, в простейшем случае окажется достаточным воспользоваться единственной библиотекой. Это кроссплатформен ная система, способная работать под управлением Windows, OS Х, Linux и Solaris и не требующая никакого администрирования!
![]()
288 Глава 21
ЗАМЕЧАНИЕ
В Delphi ХЕ4/ХЕ5
механизм lnterBase ToGo
представлен
двумя платформами:
полно
функциональной
ToGo и упрощенной
IBLite
(предназначенной
для
встраивания
в мо бильные
приложения IOS
и Android).
Упрощения
сводятся к
ограничению
у IBLite раз мера
БД (до 100 Мбайт),
отсутствию
механизма
шифрования,·невозможностью
под держки
сервисов API. Во
всем
остальном и первая,
и вторая
платформы
основаны на
идентичной модели
разработки,
поэтому с точки
зрения
программиста
отличия
между ними
незначительны.
Соединение с
БД TSQLConnection
Наличие в проекте БД компонента TSQLConnection - это обязательное условие для организации взаимодействия между клиентским приложением и базой данных. Компонент отвечает за установку соединения между приложением и БД, регистра цию пользователя на БД и управление транзакциями.
ЗАМЕЧАНИЕ
Для
создания
схемы и
объектов
базы данных
lnterBase ToGo проще
всего воспользо
ваться консолью управления СУБД IBConsole, которую вы найдете в
папке c:\Program Files (x86)\Embarcadero\RAD Studio\n.n\lnterBaseXEЗ\Ьin\. Благодаря интуи
тивно
понятному
интерфейсу
утилита
окажется
полезной
даже для
начинающего про
граммиста.
Работа с компонентом начинается с выбора драйвера, для этого предназначено свойство
property DriverName:·string;
Драйвер определит, какие именно библиотеки должны использоваться для обслу живания специфи<rной СУБД и для клиентской стороны, в нашем случае (рис. 21.1) мы воспользуемся драйвером IВLite/ToGo.
Подбором драйверов и библиотек этап предварительной настройки соединения за вершается, и мы переходим к этапу описания нюансов обслуживания конкретной базы данных. Для этого нам следует обратиться к свойству
property Params:
TStrings;
В результате
среда программирования
вызовет
редактор
соединения,
позволяющий назначить его параметры и
протестировать их корректность. Список параметров
зависит от конкретной
реализации сервера, но в самом общем случае нам прихо дится определиться
с именем хоста HostName, на котором развернут сервер, именем
базы
данных Database, именем UserName и паролем
пользователя Password.
Разобравшись с базовыми свойствами компонента, обсудим его главное назначе ние - процесс установки соединения. Компонент TSQLConnection обладает парой методов, осуществляющих подключение к БД и отключение от БД
procedure
Open; . · procedure
Close;
lnterВase ToGo 289
W Dat.iМodu Unitp11,
![]()
: .: .: .: ..S..Q.
l. .1..:.: ': .: ..:; .: ..
Object !nspector
'5QLConnectioo1 TSQLConn ction
jP�rop�er-lie-s'I=Ev=enl=:s1----------------- ==========-!
_=b
· i Comected ji:JFalse ----· Comecliarl'
8 Qrtve t181..ih!/ToGo
=:::•... ·
sysdЬa
J
Dl!Jegateti
Delegatee
Кet!peonne<1[:q•тrue
rtJ UVl!Bindln.o Uvi!l!indings Designer
PROCJ'ERFORМERJ,IOAТE: 1 Loacl>ar '{CjFalse
o:;i:::::::::::
h.oow,r tr] filfse
n1
1- !SQtcamюo
!Pari!lmS !(ТStnnos)
Tl!blesщ,e ([l:sTable,l:sVl
PR.OC_Pl:RFaМ'RJ)EU:ТE :
Iтао ;о ew]
••• 61: 18 lnlert
· par-tzrs Reloed mm«llan pвrametzrs
1mgp.-.,,
![]()
Alsnown
Рис. 21.1. Подключение драйвера к компоненту TSQLConnection
Тот же результат достигается за счет
обращения к свойству
property_Connected: Boolean;
Кроме того, по состоянию connected
можно
судить о факте соединения с
сервером.
.
Простейший
пример подключения
к файлу БД МUSIC.GDB,
расположенному в
том же
каталоге, что
и исполняемый
ехе-файл,
представлен
в листинге 21.1. В
ключах
параметров Params указываются
все тонкие
настройки
сервера и
регистрационные
данные
пользователя.
![]()
var FileName:string;
Ьegin
FileName:= GetCurrentDir+TPath.DirectorySeparatorChar+'MUSIC.GDB';
if TFile.Exists(FileName} then Ьegin
SQLConnectionl.LoginPrompt:=false;
SQLConnectionl.Params.Values['HostName'] :='localhost'; SQLConnectionl.Params.Values['DataBase'] :=FileName;
![]()
![]()
290 Глввв 21
SQLConnectionl.Connected:=true;
end else raise
Exception.Create('Фaйл '+TPath.GetFileName(FileName)+' не найден!');
end;
При
желании
настроечные
данные можно
редактировать
и во время
проектирова
ния. Для
этого
предназначен
редактор соединения,
вызываемый
щелчком по свой ству Params компонента
в Инспекторе
объектов.
С
процессом установки
и
разрыва
соединения
связан
классический
(для всех ком
понентов Delphi,
отвечающих
за подключение
к Бд) перечень
событий
(табл. 21.1).
Таблица 21.1. События, связанные с
соединением и
разрывом соединения
|
Событие |
Описание |
|
property BeforeConnect: TNotifyEvent; |
Генерируется перед установкой соединения |
|
property AfterConnect: TNotifyEvent; |
Генерируется после установки соединения |
|
property BeforeDisconnect: TNotifyEvent; |
Генерируется перед разрывом
соединения |
|
property AfterDisconnect: TNotifyEvent; |
Генерируется после разрыва соединения |
Если программная логика приложения требует постоянного удержания контакта с
БД, даже в
случае, если
нет ни одного
активного
открытого
набора
данных, то проследите, чтобы свойство KeepConnection оставалось в состоянии true. Иначе
' с закрытием последнего набора данных соединение будет разорвано.
Если для
работы
пользователя
с БД необходимо
осуществить
ввод имени
и пароля,
то свойство
property LoginPrornpt: Boolean; //по умолчанию true
должно
оставаться в
состоянии true (настройка
по умолчанию), в этом
случае бу
дет автоматически
отображен
диалог
регистрации-пользователя.
Если
необходимо
сти регистрации нет - переведите
свойство в false.
ЗАМЕЧАНИЕ
По
умолчанию административный
доступ к
lnterBase ToGo
осуществляется
под учет ной
записью "sysdba" с
паролем "masterkey".
Кроме
того,
для передачи
в адрес СУБД
имени и
пароля пользователя
(а также
основных параметров
соединения)
можно
задействовать
событие
property OnLogin:
TSQLConnectionLoginEvent;
type TSQLConnectionLoginEvent = procedure(Database: TSQLConnection;
LoginParams: TStrings) of oЬject;
lnterBвse ToGo 291
ЗАМЕЧАНИЕ
В череде
событий
компонента TSQLConnection событие
OnLogin () генерируется
сра зу после Beforeconnect (). Событие
будет
вь1звано
только при
условии, что
свойство LoginPrornpt установлено
в состояние true.
О текущем состоянии соединения можно судить по свойству
property ConnectionState: TConnectionState;
Возможные варианты состояний вы найдете в табл. 21.2.
Таблица 21.2. Возможные значения TConnectionSta te
|
Состояние |
Описание |
|
csStateClosed |
Соединение отсутствует |
|
csStateOpen |
Компонент соединен с
БД |
|
csStateConnecting |
Процесс соединения инициирован, но еще не завершен |
|
csStateExecuting |
Компонент отправил
в адрес СУБД инструкцию SQL, и она выпол- няется |
|
csStateFetching |
Компонент получает информацию от сервера |
|
csStateDisconnecting |
Момент разрыва соединения, но оно еще не завершено . |
Управление подчиненными наборами данных
Компонент
TSQLConnection способен
оказывать
влияние на
подключенные
к нему
компоненты-наборы данных. Весь массив этих элементов управления хранится в
свойстве.
property DataSets[Index: Integer]:
TCustornSQLDataSet;
Количество
использующих
соединение
компонентов
мы выясним
благодаря
свой ству
property DataSetCount: Integer;
Для закрытия всех открытых наборов данных достаточно вызвать процедуру.
Управление транзакциями
Прежде чем попытаться управлять транзакциями, стоит убедиться, что целевая
СУБД их
поддерживает.
Для этого предназначено
свойство
property TransactionsSupported: LongBool;
Значение
true свидетельствует
о том, что в
один и
тот же момент
времени
сервер
способен
выполнять по крайней
мере одну
транзакцию. Если же одновременно
мо
гут быть
запущены несколько
транзакций,
то об этом
просигнализирует
свойство
property MultipleTransactionsSupported: LongBool;
![]()
Для
старта новой
транзакции обратитесь к
процедуре
prooedure
StartTransaction(TransDesc: TTransactionDesc);
Завершение транзакции с сохранением результатов работы обеспечит процедура
procedure Commit(TransDesc: TTransactionDesc);
Откат транзакции
procedure Rollback(TransDesc: TTransactionDesc);
В качестве параметра во все три метода передается одна и та же запись ттransactionDesc. Эта структура позволяет идентифицировать транзакцию и опре делять ее основные характеристики.
type TTransisolationLevel = (xilDIRTYREAD, xilREADCOММITTED,
xilREPEATAВLEREAD, xilCUSTOM);
TTransactionDesc = packed record
end;
TransactionID GlobalID IsolationLevel Customisolation
LongWord; //уникальный идентификатор транзакции
LongWord; //глобальный идентификатор для ORACLE TTransisolationLevel;//ypoвeнь изоляции LongWord;
//пользовательский уровень изоляции
Поля
с идентификаторами
транзакций
заполняются
системой
сразу после
старта
транзакции, поэтому
на долю программиста
остается
только
настройка уровня
изо ляции.
Во время выполнения транзакции свойство
property InTransaction: Boolean;
переходит в
состояние true.
При острой необходимости компонент TSQLConnection вполне способен самостоя тельно отправить в адрес сервера команду SQL. В простейшем случае (когда команда не содержит параметров) воспользуемся методом
function ExecuteDirect(const SQL: string): Integer;
При корректном выполнении метода он возвратит нулевое значение. В противном случае функция передаст нам код ошибки. Если же SQL-команда включает пара метры и, более того, возвращает какое-то- результирующее множество, то выбираем одну из перегружаемых версий метода:
function
Execute(const SQL: string, Params: TParams):
Integer; overload; function Execute(const SQL: string, Para'ms: TParams,
var ResultSet:
Object): Integer;
overload;
![]()
Ряд методов компонента специализируется на получении информации об имею щихся в распоряжении базы данных объектах (таблицах, процедурах, индексах и т. п.). Перечень этих процедур представлен в табл. 21.3.
Таблица 21.3.
Информационные методы компонента TSQLConnection
![]()
![]()
Совместно с методом GetTaЬleNames () трудится свойство
property ТаЫеSсоре: TTaЬleScopes; //по умолчанию tsTaЬle и tsView
type TaЬleScope = (tsSynonym,
tsSysTaЬle, {системные таблицы}
tsTaЫe, {обычные таблицы}
tsView); {представления}
оно определяет тип таблиц, который может увидеть компонент.
Компонент TSQLDataset фактически объединяет в себе функциональные возможно сти триумвирата компонентов - таблицы, запроса, хранимой процедуры. Благода ря такой самодостаточности компонента TSQLDataSet необходимость в использо вании в наших проектах TSQLTaЫe, TSQLQuery и TSQLStoredProc при построении любого проекта dbExpress можно поставить под сомнение.
![]()
ВНИМАНИЕ!
Все наборы
данных dbExpress - это
однонаправленные наборы
данных. Это
означа ет,
что компоненты
не кэшируют
записи в
локальном
буфере.
Поэтому
перебор запи
сей в
результирующем
наборе может
осуществляться
только вперед,
от первой к
по следней.
Работа с компонентом начинается с подключения его к БД (точнее говоря, к ком поненту TSQLConnection) с помощью свойства
property
SQLConnection: TSQLConnection;
Наличие свойства
property CornmandType: TSQLComrnandType;
TPSComrnandType = (ctUnknown, ctQuery,
сtТаЫе,
ctStoredProc,
ctServerMethod,
ctSelect, ctinsert, ctUpdate, c�Delete, ctDDL);
позволяет
нам
применять TSQLDataSet в роли
таблицы,
запроса или
хранимой про цедуры.
Например, установив свойство CornmandType в состояние ctQuery, мы превращаем компонент в запрос, работающий на основе SQL. В этом случае щелчок по свой ству
property CornmandText: string;
в Инспекторе объектов вызывает редактор запросов (свойство ComrnandType=ctQuery), который окажет помощь при проектировании простейших инструкций. Возможно сти редактора далеко не безграничны, но, по крайней мере, он позволит начинаю щему разработчику не перепутать названия таблиц и полей (рис. 21.2).
![]()
Рис. 21.2. Встроенный редактор SQL
![]()
Немного разовьем идею использования TSQLDataset в качестве запроса, возвра щающего строки таблицы. В качестве основы возьмем таблицу perfonner из БД, предназначенной для хранения данных "Исполнитель- альбом" (рис. 21.3).
ЗАМЕЧАНИЕ
При
изучении
процесса
создания
приложения
БД вместо
этого
примера
можно вос
пользоваться
демонстрационной
базой данных
dbdemos.gdb, которую вы
найдете в
папке C:\Users\PuЫic\Documents\RAD
Studio\11.0\Samples\Data\.
![]()
![]()
Рис. 21.3. Концептуальная модель БД
"Исполнитель - альбом"
-
Присоедините набор
данных Perfoпner_select: TSQLDataSet к базе
данных (для
это го понадобится
свойство SQLConnection). Воспользовавшись свойством
commandText,
передайте в
компонент
текст
запроса: Select * from
perfoпner 6rder Ьу
perfoпner. Убедитесь,
что свойство CommandType
установлено в
состояние ctQuery. Разместите
на форме
компонент lbxPerfoпners :TListBox, именно
в него мы
направим
результа ты
запроса. Все
остальное вы
найдете в
листинге 21.2.
![]()
![]()
. ,... ..,,.,. - -·-
![]()
var LBI:TListBoxltem;
Ьegin
lbxPerfonners.Clear; //очистка списка
with Perfoпner select
do
Ьegin
Open; //открываем запрос
while not eof do //пока не прочитаны все строки
Ьegin
//создаем элемент списка с
данными LBI:=TListBoxltem.Create(lbxPerfoпners);
LBI.Text:=
FieldВyName('perfoпner') .AsString; //исполнитель
LBI,Tag:= FieldВyName('perfoпner_id') .Aslnteger; //кточ
//----- Пока не снимайте комментарий с этих строк!
// LBI.OnClick:=PefoпnersitemClick;
//----- см. листинг
,21.5
lbxPerfoпners.AddObject(LBI);//дoбaвим
элемент в список
Next; //переход к
очередной строке
end;
Close; //закрываем запрос
end;
end;
![]()
![]()
Обратите
внимание на
то, что кроме
названия
исполнителя
в
динамически
созда ваемый
элемент списка
заносится
значение
первичного
ключа записи, для
этого мы
приспособили
свойство таg. Значение
ключа
понадобится
для идентификации
запи си при
описании
процедур редактирования
и удаления,
а также
для сбора
сведений об
альбомах
исполнителя
(см. листинг 21.5).
Если
команда SQL
основана на
инструкции SELECT, то для
выполнения
запроса
используют
унаследованный
от далекого
предка TDataSet метод
Open (). Во
всех
остальных
случаях
задействуем
метод '
function ExecSQL(ExecDirect: Boolean False): Integer;
Передав в
параметр ExecDirect значение true,
мы просигнализируем
СУБД, что
наша команда
не нуждается
в предварительной подготовке.
Большинство
событий
компонента TSQLDataset получено
в наследство
(правда, в весьма ограниченном виде) от класса TDataset. Компонент способен реагировать
на
следующие
события:
подключение к
таблице BeforeOpen () и AfterOpen
(); отключение от таблицы BeforeClose () и AfterClose
( J; перемещение по запи сям BeforeScroll () и
AfterScroll (); обновление данных BeforeRefresh () и AfterRefresh () ; работа
с
вычисляемым полем OnCalcFields () .
Кроме
того, у
компонента TSQLDataSet имеются четыре собственных
обработчика
событий. Все
они предназначены
для
осуществления
анализа
отправляемых
на вы
полнение инструкций
SQL. Выполнение
команды INSERT
возможно
отследить с
по мощью
события:
property ParseinsertSql: TParseinsertSqlEvent; TParseinsertSqlEvent = procedure(var Field.Names: TWideStrings;
SQL: UnicodeString;
var BindAllFields: Boolean;
var TaЬleName: UnicodeString) of object;
Здесь:
Field.Names - имена
полей,
задействованных
при выполнении
инструкции; SQL - текст
инструкции; BindAllFields - признак,
надо ли
задействовать
все поля; TaЬleName - имя таблицы.
Команды SELECT, UPDATE и DELETE обслуживаются событиями
property ParseSelectSql: TParseSqlEvent; property ParseUpdateSql: TParseSqlEvent; property ParseDeleteSql: TParseSqlEvent;
TParseSqlEvent = procedure(var Field.Names: ТWideStrings;
SQL: UnicodeString; var TaЬleName: UnicodeString) of object;
Хранимая процедура TSQLStoredProc
Хранимая
процедура TSQLStoredProc специализируется на вызове
заранее подготов
ленных на
стороне
сервера
процедур. При
подготовке к
работе
компонент
следует
соединить с
базой данных,
поэтому в процессе
создания
проекта
самым первым
шагом программиста
должно быть
подключение
к TSQLConnection с помощью
свой ства SQLConnection.
![]()
![]()
![]()
Имя хранимой процедуры передается в свойство:
property StoredProcName: string;
Если
свойство ParamCheck компонента
находится в
состоянии true, то
при измене
нии имени
процедуры
компонент
самостоятельно
обращается к
базе данных и
со бирает
сведения о
параметрах
процедуры и заносит
их в свойство
Params.
Для принудительной предварительной подготовки процедуры к выполнению на стороне сервера можно вызвать функцию:
function PrepareStatement(
var RecordsAffected: Integer): TCustomSQLDataSet;
Впрочем, вместо этого метода разработчики Delphi рекомендуют обращаться
к свойству Prepared. В
этом случае
гарантируется,
что сервер не
станет без
всякой на то
надобности
перекомпилировать
уже давно
готовую к
работе.процедуру.
Входные параметры процедуры передаются в свойство
property Params: TParams;
Это же свойство служит хранилищем для возвращаемых в результате выполнения процедуры параметров.
Если процедура не основана на инструкции SELECT (не возвращает результирующе го набора данных или возвращает результаты в виде выходных параметров), то для ее вызова обращаемся к методу
function ExecProc: Integer;
в противном
случае надо
использовать
метод Open () либо
установить в true свойст
во Active.
Вернемся к нашему примеру "Исполнитель - альбом". Для вставки новой записи с исполнителем в БД создадим хранимую процедуру proc_performer_insert с един ственным входным параметром aPerformer и одним возвращаемым значением new_id - первичным ключом только что вставленной записи (листинг 21.3).
![]()
create procedure proc_performer_insert(aPerformer varchar(S0)) returns (new_id
int)
as Ьegin
insert into
performer (performer)
values
( :aPerformer);
select max(performer_id) from performer into :new id;
end
Для того чтобы обратиться к этой процедуре из приложения FireMonkey, можно вновь воспользоваться услугами TSQLDataset или отдать свои предпочтения спе-
![]()
![]()
циализированному компоненту
TSQLStoredProc. Для
того чтобы
разнообразить
наш код, пойдем
по второму
пути.
Перенесите на
модуль
данных
компонент TSQLStoredProc, соедините его с
базой данных
с помощью
свойства SQLConnection и
передайте в
свойство StoredProcName название хранимой
процедуры (в
нашем случае PROC_PERFORМER_INSERT). Убедитесь
в
корректности
подключения,
если все
сделано
верно, то
после щелчка
по свойству Params перед вами
появится окно с параметрами хранимой процедуры.
'::::::::
=
: :' : SQlCannec1icn1 : ; ; : '
_D«SOtT
1Тао ,.....,._"-'= О
Edtor... Exeaite 8lnd Vl!iully••• Add SlndSou-ce
Рис. 21.4. Подготовка компонента TSQLStoredProc к работе
Порядок
использования
компонента TSQLStoredProc
в проектах FireMonkey
раскры вает
листинг 21.4, в
нем мы вносим
нового
исполнителя
в БД и
создаем
соответст вующий ему элемент в
списке lbxPerfoпners:TListBox.
![]()
var id:integer;
LBI:TListBoxitem; s:string;
Ьegin
if InputQuery('Hoвый исполнитель', 'Исполнитель',s) then
Ьegin
PROC_PERFORМER_INSERT.ParamВyName('aPerfoпner')
.Value:=s.Trim; PROC_PERFORМER_INSERT.ExecProc;
![]()
id:=PROC PERFORМER INSERT.PararnВyName('new_id').Value; LBI:=TListBoxitem.Create(lbxPerfoпners); LBI.Text:=s.Trim;
LBI.Tag:=id;
r lbxPerfoпners.AddObj ect ( LBI) ;
end; end;
В
соответствии
с
классическими
традициями 'наборов
данных Delphi
доступ к пе
речню
параметров
обеспечивает
свойство
property Params: TParams;
Для
того чтобы
компонент
самостоятельно регенерировал
список
параметров
(име на, типы
данных и т. д.) при
вводе или
любом
изменении SQL
команды в
поле CormnandText,yбeдитecь, чтобы свойство
property
ParamCheck: Boolean; было установлено в
состояние true.
ВНИМАНИЕ!
При работе
с хранимыми
процедурами
старайтесь
объявлять
параметры в
той же по
следовательности, в
какой они
описаны на
серверной
стороне.
Компонент-запрос
специализируется
на отправке
серверу
инструкции
на языке SQL.
Организация
работы с
компонентом
не сильно
отличается
от работы с
одно типными
компонентами
из других
технологий
доступа к
данным. После
подключе ния к
БД с помощью
свойства SQLConnection следует
определить
текст запроса:
property
SQL: TStrings;
Запрос может редактироваться как во время проектирования Бд, так и
во время вы
полнения
программы.
Если запрос содержит
параметрьi,
то доступ к
ним осуществ
ляется с
помощью
сtюйства
property Params:TParams;
Для
того чтобы
параметры
компонента
самостоятельно подстраивались
под все из
менения в
тексте
запроса,
убедитесь,
что свойство
property ParamCheck; установлено
в true.
Если запрос работает с инструкциями INSERT, UPDAТE и DELETE, то для отправки SQL
зaпpoca к
серверу
применяют
метод ExecSQL ( ) ,
запрос,
нацеленный
на выборку
данных (инструкция
SELECT),
активируется
методом Open ().
Вновь
возвратимся
к нашему
примеру
"Исполнитель
- альбом"
и научим наше
приложение отображать список альбомов после выбора пользователем элемента
с названием
исполнигеля
(щелчок по
элементу
списка lbxPerformers). Для
этого
разместиге на модуле
данных
компонент TSQLQuery
'(не
забыв
подключигь
его к БД),
перенесите,на
главную
форму
проекта еще
один список TListBox и переиме нуйте его в
lbxConcerts. Так как элементы списка lbxPerformers
создаются динами
чески
в момент
сбора
сведений об
исполнигелях,
нам придется
подключать
обра ботчик события onclick () вручную. Для этого в
секции частных объявлений
с
главной
формой
проекта
объявим
процедуру
procedure PeformersitemClick(Sender: TObject);
Код процедуры вы найдете в
листинге 21.5.
Лмстмнr 21.S. Со6ытие..щеnчок no амменту сnмс• 1.Ьвhl:f'_..
![]()
,.,...:. ······:iil .. ........ .. ----······"' - ....... ,.t-,_,., ·•······ '""'
procedure TfпnМain.PeformersitemClick(Sender: TObject);
var Performer_id :integer; ConcertLBI:TListBoxitem; lb:TLabel;
Ьegin
Performer_id:=TListBoxitem(Sender)
.Tag; //узнаем первичный ключ
if Performer id>0
then with SQLQueryl
do Ьegin
SQL.Clear; //готовим новый текст запроса
SQL.Add('SELECT * FROM CONCERT WHERE');
SQL. Add ( ' PERFORМER_ ID=' +
Performer_id.ToString()+' ORDER ВУ CONCERTDATE');
Open; //вьmолняем запрос
lbxConcerts.Clear; //очищаем список
while not eof do //перебор записей, возвращенных запросом
Ьegin
ConcertLBI:=TListBoxitem.Create(lbxConcerts);
lbxConcerts.AddObject(ConcertLBI); ConcertLBI.Text:= {дата}
FormatDateTime ( 'уууу',
FieldВyName ( 'concertdate')
.AsDateTime); ConcertLBI.Tag:=FieldByName('concert_id') .Asinteger; //ключ
lb:=TLabel.Create(ConcertLBI); //метка для
названия альбома lb.Parent:=ConcertLBI;
lb.Position.X:=40; · lb.Text:=FieldВyName('concert')
.AsString;
Next; //к следующей записи
end;
end;
Close; //закрываем набор данных
end;
Для того чтобы код стал работоспособным, следует подключить процедуру к обра ботчику события OnClick () элементов списка lbxPerformers. Это нам придется делать в динамическом режиме, поэтому возвратитесь к листингу 25.2 и снимите комментарии со строки
//LBI.OnClick:=PeformersitemClick;
Если вы имеете хотя бы небольшой опыт разработки клиентских приложений баз данных, работающих под управлением InterBase, то наверняка знаете, что при пе реносе исполняемого файла на компьютер заказчика в дистрибутив надо обяза тельно включить динамическую библиотеку gds32.dll. В названной библиотеке со средоточен весь необходимый функционал для доступа и обработки данных, об служиваемых сервером InterBase. В InterBase ToGo ситуация очень похожая, но на этот раз в папке с исполняемым файлом должна оказаться библиотекц ibtogo.dll (или ibtogo64.dll), если речь идет о Windows, библиотека liЬibtogo.dylib для OS Х и мобильной платформы iOS.
ЗАМЕЧАНИЕ
Разработчики
клиентской
библиотеки
для проектов
lnterBase ToGo
утверждают,
что
интерфейсная
часть iblogo.dll (iblogo64.dll, liblblogo.dylib)
на 98%
совпадает с
интер фейсом
gds32.dll, что
существенно
упрощает
переносимость
настольных и
клиент
серверных приложений.
Для включения необходимых файлов в дистрибутив следует воспользоваться менеджером развертывания (Deployment Manager) приложения, для этого надо обратиться к элементу меню Project I Deployment среды проектирования. В комби нированном списке менеджера необходимо выбрать целевую платформу или вы
брать универсальный элемент АН configurations - АН Platforms. Затем щелкаем по кнопке Add Featured Files и в появившемся на экране окне с наиболее часто ис пользуемыми файлами отметить флажки (рис. 21.5) интересующих нас строк (в нашем случае InterBase ToGo и DBExpress InterBase Driver).
Для присоединения файла с базой данных в окне Deployment нажимаем кнопку Add Files, с помощью диалога выбора файлов находим необходимый файл БД и подключаем его к проекту.
ЗАМЕЧАНИЕ
Если
логика
работы
программы
требует размещения
ваших файлов
в строго
опреде ленных
папках, то в окне
Deployment следует
отредактировать
ячейку Remote Path. Путь можно указать раздельно для
каждой из
поддерживаемых
платформ, например
в
IOS файл БД
должен
оказаться в
папке Star1Up\Documents\.
LoalPalh
r:J S(ll!R!D
е
r.'.:j С:\
[)epo'jml!nt-ТoGopl,цlc!
..
.... -. - L4J_ - - ;11ons.м!-t1,inм
w· f.11 1ntere.se e1en1
1,;1-!!l] l ToGo
OSXЭ2'()@Ьug\
[.! OSX32'()м,ug\
l':'J S(JIIRВ)ISЮIR)._Э,
l':'J $(JIIRВ)l!ifDIR)"'8x3
' t(IOS) \
, l':'J S(JIIRВ)lstDIR).............. ,S.
1 rJ t{l8RS)ISro]R)-,э.
' IR
i•"-!!■.!l!!!-.,.11!'!"-t
(t)- tiJ Wln32 (!} !!l] т,64
itl· OSX32
1 1В !tl /OSSi!uator
: 1& ffj IOSOewJ!
1!), Anctcid
m fJ'l! Deexpress 1n Orlver
\Ь- [:J Deflфresc DIIZ Driv« iiJ. [!:! DIJE,q,rm Frdii'd Driver m - l!'.'!J оее-МySQL 0r1v«
!•,..... ------,r-41m оеехрrен Orllde Oiiver
liJ {f! DВЕ"Р'- Informi• Drlver
°"""'
===:a::z!ПE;:t],ф 1/f:J ов&р,ен МSSQ.. Drivtt
' i) · 11:Э Dlll:"l)l"ess MSSQL9
Рис.
21.5. Подключение к
проекту модулей, необходимых для работы
lnterBase ToGo
ЗАМЕЧАНИЕ
Тематика
разработки баз
данных будет
продолжена в главе 22, но
на этот раз в
кон тексте
технологии
живого
связывания
LiveBindings.
![]()
ГЛАВА 22
Объем принципиально новых возможностей, появившихся в Embarcadero RAD Studio с выходом FireMonkey, без преувеличения можно сравнить с прорывом пер вых версий Delphi в середине 1990-х годов. В этой главе нам предстоит обсудить очередную новацию Embarcadero - технологию "живого связывания" LiveBindings, позволяющую программисту организовать передачу данных в рамках приложения между произвольным источником и объектом-получателем данных.
Несмотря на то, что собственно идея живого связывания нашла свое полноценное программное воплощение в FireMonkey, фундамент LiveBindings был заложен до выхода новой платформы - еще 2008 году. Именно тогда вышло в свет принци пиально усовершенствованное ядро RTТI (Run-time type information). Не станем перечислять положительные качества нового RTTI, отметим лишь, что с этого мо мента появилась возможность создать универсальную технологию, способную обеспечить взаимодействие между объектами приложения, строго говоря, без опо ры на конкретную операционную систему. Поэтому технология LiveBindings оди наково хорошо функционирует как в классической библиотеке VCL, так и в новой кроссплатформенной FireMonkey.
Что
такое LiveBindings? Если
кратко, то
это технология
взаимодействия
двух и бо
лее объектов.
Связь может
быть как односторонней, так и двусторонней.
В качестве
участника
взаимодействия
выступают
два типа
объектов: управляемые
объекты (control objects) и источники
данных (source objects). Обмен
данными осуществля
ется между свойствами
объектов, а правила
обмена
определяются
с помощью
регу лярных выражений
(expression).
Что
немаловажно,
в выражения
разрешается
включать
ссьmки на
другие
внешние
объекты.
ВНИМАНИЕ/
Строго
говоря, за
связь между
двумя объектами
LiveBindings отвечает
третий
объект, например,
экземпляр
класса TBindExpression.
В
LiveBindings
предусмотрено
более двух десятJ<ов
классов,
специализирующихся
на обслуживании
определенных
типов связей,
их можно
разделить на
4 современ
ных и одну
устаревшую
на сегодня
ветвь:
![]()
![]()
1. Ветвь Quick Bindings содержит
группу
специализированных
классов, позво
ляющих создавать
живые связи
практически
без
программирования.
Например,
класс TLinkControlToProperty
позволяет
связать
элемент управления
со
свойст вом
управляемого
объекта так,
что любые изменения
в источнике
данных не
медленно отразятся
в выбранном
свойстве
управляемого
объекта.
Ключевое достоинство
классов
Quick Bindings в
том, что они
поддерживаются
визуаль ным
дизайнером
живых связей (LiveBindings Designer).
2. Ветвь Binding Expressions включает
классы TBindExpression и TBindExprrterns, контролирующие
процесс
взаимодействия
между
управляемым
объектом и
ис точником данных в соответствии
с правилами,
описанными в форме
регулярных
выражений. В
простейшем случае
связь
организуется
между двумя
свойствами
разных
объектов.
3.
Тип связей Lists объединяет
классы TBindList и TBindGridList. Задача указанных
классов - обеспечить
связь не
просто между
парой
свойств,
содержащих
по одному
атомарному
значению, а
между
списками,
хранящими
большой
объем данных.
4. Тип
связи Links
включает
классы TBindLink, твindListLink, TBindGridLink и твindPosition. Перечисленные
классы
позволят нам
создавать
ссылки между
списками,
сетками и другими
участниками
живой связи
(обычно полями
таб лиц).
В отличие от
классов,
входящих в
состав DB
Links, классы Links
требуют от
программиста
явным образом
задать
выражения, в которых
должны быть оп
ределены
правила
взаимодействия
между полем и
визуальным элементом
управления.
ЗАМЕЧАНИЕ
В LiveBindings
существует и устаревшее
направление DB Links. Оно было
предназна
чено для построения клиентских приложений БД в первой версии FireMonkey (Delphi ХЕ2). В
состав
данного типа
связей LiveBindings
входят: твindDBEdi tLink, TBindDBTextLink, TBindDBListLink,
TBindDBimageLink, TBindDBMemoLink, TBindDBCheckLink и
твindDBGridLink. Каждый
из
перечисленных классов
позволяет формировать
связь между
полем
таблицы соответствующего
типа и
подходящим
для обслуживания
этого типа данных
элементом
управления,
например твindDBEdi tLink с легкостью
соединит
текстовое
поле и строку
ввода TEdi t.
Палитра компонентов FireMonkey насчитывает несколько компонентов, обеспечи
вающих
функционирование
технологии
LiveBindings, среди них:
□ список
связей TBindingsList представляет
собой
контейнер
для анонсированных
выше экземпляров
классов,
осуществляющих связь
LiveBindings;
□ наблюдатель
TBindScope, упрощающий
организацию
взаимодействия
между
визуальными элементами
управления;
□ компонент
TBindSourceoв используется
в проектах
баз данных,
он
обеспечит
взаимодействие
между набором
данных (любым
потомком
класса тoataset)_и
другими компонентами;
□ компонент TBindSourceoвx также используется
в проектах баз данных, но на этот раз имеет жесткую
специализацию
- он нацелен
исключительно
на технологию
доступа к
базам данных
dbExpress (DBX);
![]()
![]()
□ для доступа к
неструктурированным в
виде БД данным
задействуется компонент
TAdapterBindSource, например
указанный
компонент
способен работать
с кол
лекциями
различных
объектов;
□ ,генератор TDataGeneratorAdapter предназначен для совместной работы с
TAdapterBindSource;
□ источник произвольных данных TPrototypeBindSource позволит программисту
создать
некоторый
временный
случайный набор
данных,
используемый
для от
ладки
приложения,
позднее
вместо него можно
использовать
реальные
данные;
□ навигатор для проектов баз данных TBindNavigator - единственный
визуальный элемент
управления
LiveBindings,
обеспечивающий
интерфейс
пользователя
по
управлению
табличными
данными
(перемещение
по набору
данных,
добавление записей,
удаление
записей и т.
п.).
Продемонстрируем возможности визуального дизайнера LiveBindings на примере приложения FireMonkey с интерфейсом Metropolis. С этой целью воспользуемся элементом меню New I FireMonkey Metropolis UI Application и в окне выбора ти па приложения Delphi New FireMonkey Metropolis UI Application отметим шаб лон Grid Metropolis UI Application. В результате этих действий среда проектиро вания создаст приложение для Windows 8 с пользовательским интерфейсом Metropolis. Указанное приложение будет состоять из двух модулей: главной формы GridViewl.pas и дочерней формы DataViewl.pas. Главная форма обладает списком Listвoxl с некоторым перечнем элементов, щелчок по тому или иному элементу списка послужит командой для вызова формы детализации с более подробной информацией.
Разместите на главной форме проекта тестовый источник данных PrototypeBindSourcel:TPrototypeBindSource, который позволит смоделировать не который произвольный набор данных. Для того чтобы определить структуру гене рируемых данных, следует воспользоваться контекстным меню компонента и обра титься к пункту Add Field (Добавить поле). В ответ на это действие на экране ком пьютера окажется одноименный диалог добавления поля к набору данных Add Field, выберите в списке несколько произвольных полей (рис. 22.1) и нажмите кнопку ОК. Вновь созданные поля заполнят коллекцию
property FieldDefs: TGeneratorFieldDefs;
и станут имитировать поведение полей из обычного набора данных (например, таб лицы базы данных).
Сразу ограничим число записей, создаваемых в
компоненте PrototypeBindSourcel, ,
для этого следует передать значение (допустим, число 5) в свойство
property RecordCount: integer; //по умолчанию -1
Отметим, что по умолчанию свойство принимает значение равное -1, и это указы вает на отсутствие ограничений.
![]()
306 Глввв 22
Рис. 22.1. Имитация набора данных с помощью TPrototypeBindSource
Теперь нам следует вызвать на сцену визуальный дизайнер связей LiveBindings. Для этого (предварительно убедившись, что главная форма проекта активна) выбе рите пункт меню View I LiveBindings Designer. В результате на вашем дисплее
отобразится окно редактора со схематичным представлением компонентов главной формы, способных поддерживать друг с другом живую связь. Как вы уже догада лись, в качестве источника данных выступит компонент PrototypeBindSourcel, остальные элементы управления станут претендовать на роль получателей данных.
Продемонстрируем
процесс
создания
живой связи.
Для этого
найдите на
схеме
прототип PrototypeBindSourcel и левой
кнопкой мыши
выберите в
нем поле с име
нем BitmapNamel. Удерживая
кнопку
мыши в нажатом
состоянии,
перетащите
поле к списку ListBoxl и отпустите кнопку в
тот момент, когда указатель мыши окажется
над свойством
Item.Text. В результате на схеме появится
"стрелка", соединяющая
выбранное поле и свойство
(рис. 22.2). Данная
"стрелка"
визуализирует
реальный объект-связь
LinkFillControlToFieldl, в данном
случае
созданный на основе
класса TLinkFillControlToField. В этом вы сможете
убедиться,
взглянув на
Инспектор объектов. Обращение к Инспектору объектов
является не только праздным
любо пытством,
нам еще
следует изменить
состояние
свойства ListitemStyle, переведя
его в режим MetropolisUI.
Обрати_те внимание на то, что после установки типа связи в
состояние MetropolisUI
у представления Listвoxl на схеме LiveBindings Designer заметно расширился
![]()
OЬjecl ln,pctor
, l.lmlfllConl:nll'fofleldl 11.Wfilleootr!JIТQF\eld
Tru11
l'alse
"Trve
·1
Quklcllindln\Js
Ustlloxl
ec:tlan f tnld!idlr«tiOnal
J
1
1lcGr (Т,-.,akGroups) PrototypdlndSouкeJ
, IRtmllpNillmel
(Тformatfxpresllloм)
,ormalfxpn!нioм)
Рис. 22.2. Создание связи между полем данных и
свойством компонента
перечень свойств. Воспользуемся этим и добавим к схеме еще пару связей:
Bi tmapl - Itern. Icon И
ColorsNamel - .Itern. SubTi tle.
Переходим к дочерней форме проекта. В первую очередь (воспользовавшись пунк том меню File I Uses) подключим к дочерней форме модуль GridViewl .pas, что по зволит дизайнеру связей LiveBindings увидеть принадлежащий главной форме про
екта
прототип
данных PrototypeBindSourcel. После
этого
(щелкнув
правой
кнопкой мыши по
клиентской
области LiveBindings Designer) во
всплывающем
меню выбе
рем пункт Refresh Designer (Обновить
Дизайнер). В
результате к схеме
(до сцх пор отражавшей
лишь
принадлежащие
дочерней форме
компоненты)
добавится PrototypeBindSourcel. Для того чтобы
подчеркнуть
тот факт, что
прототип принад
лежит другому модулю, LiveBindings Designer окрасит его в оранжевый цвет. Нам
осталось
связать поля
прототипа со свойствами
соответствующих компонентов
формы детализации так, как предложено на рис. 22.3.
В завершение нашего примера нам предстоит немного потренироваться в програм мировании. Возвращаемся к модулю главной формы и находим на ней компонент ListBoxl: TListвox, он нам поможет вызывать форму детализации. С этой целью
![]()
опишем событие
onchange ( ) , генерируемое компонентом в
момент выбора
того или иного
элемента
(листинг 22.1).
![]()
prooedure
TGridViewForm.ListBoxlChange(Sender: TObject);
Ьegin
if (ListBoxl.Itemindex<>-1) then
Ьegin
PrototypeBindSourcel.Itemindex:=ListBoxl.Itemindex;
Application.GetDeviceForm('DetailView').Show();
end; end;
![]()
Рис. 22.3. Создание связи между прототипом и компонентами дочерней формы
Проект
закончен.
Теперь
щелчок по элементу списка Listвoxl вызывает
форму детализации с соответствующими данными.
LiveBindings в
проектах баз данных
Еще
с первых
версий среда
проектирования
и язык
Delphi
предоставляли
разработ
чику ни с чем несравнимые
(как по
удобству, так
и по скорости
проектирования)
возможности
по построению
проектов баз данных.
Долгое время
казалось, что уже
достигнут предел совершенства
и ничего более успешного
в технологии
разработки
клиентских
приложений
БД (с точки
зрения RAD
сред)
создать
невозможно. Одна ко в FireMonkey компания
Embarcadero превзошла
саму себя,
поставив LiveBindings на
службу
приложений
БД. Чтобы
подтвердить
это
утверждение,
предлагаем написать
демонстрационный проект
БД в FireMonkey.
Чтобы
не заставлять
вас разворачивать
на компьютере
сложные СУБД,
для пробно
го проекта LiveBindings
для БД
предлагаю
временно
отказаться от Мае
и написать
![]()
![]()
приложение
FireMonkey
исключительно
для Windows. Более того, советую
остано
виться на
распространенной
настольной
БД Microsoft Access и
создать в ней
одно табличную
базу данных
контактов
(рис. 22.4).
Предлагаемый
пример - не
догма, при желании
вы можете
выбрать
любые другие
альтернативные
источники
данных,
например кроссплатформенную
базу данных
InterBase ToGo (см. главу 21), но сей час
исключительно
ради
быстроты освоения
идеи работы
LiveBindings с БД реко
мендуем ненадолго
отклониться
от
генеральной
линии.
![]()
Рис. 22.4. Определение полей таблицы
контактов contacts
Создайте приложение FireMonkey НD Application и
разместите на
главной форме
проекта
следующие
компоненты
(рис. 22.5):
□
таблицу ADOTaЫel: ТАDОТаЫе; воспользовавшись свойством ConnectionString,
сформируйте строку соединения с
файлом БД, а в
свойстве таЫеNаmе укажите
имя обслуживаемой таблицы;
□ источник данных BindSourceDBl:TBindSourceDв; обратившись к
свойству DataSet,
присоедините компонент к
таблице ADOTaЫel;
□
навигатор
данных BindNavigatorl:твindNavigator; навигатор следует присоеди
нить к
наблюдателю BindSourceDBl, для этого предназначено
свойство DataScope;
□ контейнер связей BindingsListl:TBindingsList;
![]()
Рис. 22.5.
Интерфейс приложения БД с компонентом TBindSourceDB
![]()
![]()
□ строки ввода TEdit (по числу текстовых полей в таблице) и изображение
ImageControll:TirnageControl.
ЗАМЕЧАНИЕ
В
современных
версиях Delphi в
распоряжении
программиста
появился компонент
TBindSourceDB.
По своей сути TBindSourceDB - это два компонента в
одном флако
не: с одной стороны, TBindSourceDB способен работать с
технологией LiveBindings, а с
другой стороны, это замена источника данных TDataSource, существовавшего
с первых
версий Delphi.
Установив свойство Active в состояние true, активируйте таблицу ADOTaЫel. Вызо вите контекстное меню компонента BindSourceDBl и найдите в нем пункт Bind Visually. В появившемся на экране визуальном дизайнере связей создайте связи между полями данных и соответствующими им строками ввода (рис. 22.6).
![]()
Рис. 22.6. Создание связей между полями данных и
элементами управления
"Программирование"
завершено - смело
нажимаем
клавишу <F9> и
отправ:
яем проект
на
компиляцию.
связь с
помощью выражений
Несмотря на то, что визуальный дизайнер LiveBindings дает возможность даже "по трогать руками" саму идею применения живых связей в наших проектах, автор является сторонником того, чтобы (простите за тавтологию) программист програм мировал, а не прятался за спиной различных визуальных программ-помощников. Тем более что полноценного результата можно добиться только путем написания собственного кода.
Начнем
с
малого.
Предлагаем
создать
небольшое приложение,
позволяющее
осу ществить
прямой обмен
данными
между двумя
визуальными
компонентами.
Запускаем
новый проект НD
FireMonkey Application и размещаем
на главной форме
строку ввода TEdit, метку TLabel и список
связей TBindingsList. В нашем
примере
метке
отведена
роль управляемого
объекта - в
ее свойство техt станут
поступать
данные из
свойства техt управляющего
объекта TEdit.
Все
подготовител!>ные
операции
завершены,
теперь нам
следует
осуществить
свя зывание объектов. Распишем эту
операцию по
пунктам.
1. Дважды
щелкните по
компоненту BindingsListl, в
результате
чего на экране компьютера
будет
отображен
редl\КТОР
связей.
2.
Дпя создания новой "живой связи" нажмите клавишу <lns> (рис. 22.7).
![]()
Рис. 22.7. Создание новой "живой связи" для компонента Labell
3. ![]()
В
диалоге New LiveBinding выберите
узел TBindExpression
(это
укажет среде проектирования на то, что мы намерены создать связь на основе выражения)
и нажмите
кнопку ОК (см. рис. 22.7).
4. Найдите
в Инспекторе
объектов
связь BindExpressionl: TBindExpression и пе
рейдите на
страницу
свойств Properties. Теперь
нам
предстоит
настроить
ряд свойств
связи
(рис. 22.8):
ControlComponent:=Labell; ControlExpression:=Text;
//управляемый объект
//получатель - заголовок
Direction:=dirSourceToControl; //направление связи "источник->элемент"
Managed:=true; SourceComponent:=Editl;
SourceExpression:=Text;
//выражение активно
//источник данных
//свойство источника данных
![]()
Alshown
![]()
Рис. 22.8. Настройка свойств связи TBindExpression
Для
завершения
задуманного
нам осталось немного
поработать
за
клавиатурой.
Листинг 22.2 содержит
обработчик
события onchangeTracking () строки
ввода. Это
событие
генерируется
с каждым
изменением
содержимого
компонента Editl.
... '!1"·····.. · ·--,,.,• ············--··-· ,...Nl"". ··1
-.··
.,..,_..,
;,.. j
implementation
uses System.Bindings.Helper;
{$R*.frnx}
procedure TForml.EditlChangeTracking(Sender: TObject);
Ьegin
TBindings.Notify(Editl, 'Text');
end;
![]()
На первый взгляд может показаться, что заданного результата (передачи текста из строки ввода в метку) мы достигли слишком высокой ценой. Но пример пресле довал другую цель - обратить ваше внимание на то, что текст из строки ввода в заголовок метки отправился неявно, ведь в коде даже нет и намека на
Labell.Text:=Editl.Text!
ЗАМЕЧАНИЕ
В прилагаемом
к книге
архиве (см.
приложение 5) вы найдете
пример, в котором
связь
организуется между
компонентами,
расположенными на разных
формах.
Особенность
упоминаемого
примера в
том, что
благодаря LiveBindings
объект-источник
передает
данные
управляемому объекту,
даже не
подозревая о
его существовании!
Повторяя предыдущий пример, вы наверняка заметили, что основным действую щим лицом первого приложения стал экземпляр класса твindExpression (модуль Data.Bind.Components). Именно TBindExpression взял на себя ответственность за ор ганизацию взаимодействия между двумя компонентами: объектом-источником и управляемым объектом. Дпя этого понадобилось лишь внести ряд настроек в свой ства объекта.
Ссылка на объект-источник заносится в свойство
property SourceComponent: TComponent;
Сведения об управлении окажутся неполными до тех пор, пока мы не опишем входное управляющее выражение в свойстве
property SourceExpression: string;
В простейшем случае управляющее выражение будет хранить имя свойства объек та-источника с данными, которые следует передать управляемому объекту.
Ссылка на второго участника взаимодействия LiveBindings заносится в свойство
property ControlComponent: TComponent;
Кроме имени управляемого объекта следует описать выходное управляющее выра жение, для этого предназначено свойство
property ControlExpression: string;
Вновь заметим, что в простейшей ситуации здесь достаточно упомянуть название свойства, в которое поступят "живые" данные.
Направление потока данных между объектами определяется свойством
property Direction:
TExpressionDirection;//dirSourceToControl
По умолчанию поток данных направлен от источника к управляемому объекту, од нако существуют еще два варианта направлений:
type TExpressionDirection = (
dirSourceToControl,
dirControlToSource, dirBidirectional);
//источник-> управляемый объект
//управляемый объект-> источник
//двустороннее взаимодействие
Для
построения
примера двустороннего
взаимодействия от нас не потребуется
осо бых
трудозатрат.
Разместите
на форме два
компонента ттrackBar. Выбрав
любой из
компонентов,
создайте связь
LiveBindings. Данные
должны
передаваться
между свойствами
Value (отвечающими
за
местоположение
ползунка), а
направление
свя зи Direction
установите в состояние dirBidirectional (рис.
22.9).
![]()
Рис. 22.9. Организация двусторонней связи LiveBindings
Для завершения примера выберите обработчик события onchange () одного из ком
понентов и
внесите в него.всего одну строку кода (листинг 22.3).
п 22.3. Qnlp... у■ ._ о !
:!• nм• с•.•.•.................. ,., ,
• "
• • , , • .. .. •t ,. •.,. t
,, f <
![]()
f t 1 > 1 i - · · .
.......········'...............-···.......······-·······........············"...............................- .....................................................- ·- •••·- ...............·-·········••"" •••••••••............'. ,.,., ,..,.,д...................- ---·....
implementation
uses System.Bindings.Helper;
{$R *. frnx}
procedure
TForml.TrackBarlChange(Sender: TObject);
Ьegin
TBindings.Notify(Sender, ''); end;
Заключительный штрих таков - сделайте событие общим для обоих компонентов.
Продолжим рассмотрение характеристик класса твindExpression. Для деактивации
объекта
TBindExpression устанавливаем в false свойство
property Active: Boolean;
LivвBindings 315
ВНИМАНИЕ!
Во
время
визуального
проектирования
свойство Active следует
переводить в
состоя ние false, если в
связи
задействованы
элементы
управления с
разных форм и
дина мически
создаваемые
объекты. В
этом случае
активация
связи
осуществляется
про граммным
способом, после
создания
всех форм и
объектов.
Еще один способ приостановки взаимодействия обеспечивает
свойство
property Managed:
Boolean; //по умолчанию true
Отказавшись от значения true, мы изымем связь из-под контроля менеджера связей.
Как
и положено
полноценному
объекту, выражение
обладает
способностью
реаги ровать
на события
(табл. 22.1).
Таблица 22.1. События TBindingExpression
|
Событие |
Описание |
|
property OnActivating: TNotifyEvent; |
Связь
активируется |
|
property OnActivated: TNotifyEvent; ,. |
Связь активирована |
|
property OnAssigningValueEvent:
TBindingAssigningValueEvent; type TBindingAssigningValueEvent = procedure(AssignValueRec: TBindingAssignValueRec;
var
Value: TValue; var Handled: Boolean) of oЬject; |
В
объект
поступает
новое значе-
ние Value. Установив
перемен- ную Handled в состояние true, указываем объекту, что обработка
события
завершена |
|
property OnEvalErrorEvent: TBindingEvalErrorEvent; type TBindingEvalErrorEvent
= procedure(AException: Exception) of oЬject; |
Генерируется в момент возникно-
вения исключительной
ситуации AException |
|
property OnAssignedValueEvent:
TBindingAssignedValueEvent; type TBindingAssignedValueEvent = prooedure(AssignValueRec: TBindingAssignValueRec;
const Value: TValue)
of oЬject; |
В объект поступило новое значе-
ние Value |
Взаимодействие
между
объектами LiveBindings
осуществляется
в форме
регуляр ных выражений.
Повторив вводные примеры, мы практически не задумывались
над порядком
создания
выражения - за нас
все сделала
Delphi. Однако
пытливого
про
граммиста
такое положение
вещей вряд ли
устроит.
ЗАМЕЧАНИЕ
По
своей сути выражение
LiveBindings
представляет
собой
обычную текстовую
строку,
отформатированную
в соответствии
с заданными
правилами.
Поучительной
иллюстрацией
работы метода
создания
простейшего
выражения
может стать пример программы, способной осуществлять расчет
арифметических
выражений,
записанных в
форме
обычной текстовой
строки. Для
этого нам
потре буется
помощь трех
элементов
управления:
□ строки ввода Edi tl: TEdi t, которая предназначена для ввода
выражения;
□ кнопки Buttonl:TButton, она даст команду на расчет выражения;
□ метки Labell: TLabel, которая ?тобразит результат расчета.
Полагаете,
что далее
последует
сложный пример
с элементами
синтаксического
разбора
сложных
предложений?
Нет!
Технология
LiveBindings сделает
это без
посторонней помощи! На нашу
долю выпадет только написать две строки кода в
событии
щелчка по
кнопке вuttonl (листинг
22.4).
![]()
uses System.Bindings.Helper;
{$R *.dfm}
procedure TForml.ButtonlClick(Sender: TObject);
Ьegin
with TBindings.CreateExpression([], Editl.Text) do Labell.Text:=Evaluate.GetValue.ToString;
end;
Единственная
особенность
нашего
примера в
том, что при
вводе
арифметического
выражения
пользователю
требуется
соблюдать
знакомые ему
по начальной
школе
правила,
например: (10 + 2): 3 + 1.
Кроме
знаков
основных
математических
операций в
строке
выражения
могут ис
пользоваться
встроенные
методы. На
момент написания
этой главы,
технология
живого связывания поддерживала методы: Format (), FormatDateTime (), LowerCase (),
Math_Мах
(), Math_M
n (),
Round (), StrToDateTime (), ToNotifyEvent
(), ToStr
(), тovariant () и Uppercase (). Кроме
того, более
двух
десятков
методов отвечают
за
преобразование типов
данных.
ЗАМЕЧАНИЕ
Для
получения
списка
зарегистрированных
методов LiveBindings во
время
визуально го проектирования приложения
следует в Инспекторе
объектов
обратиться к свойст
вам Methods и OutputConverters компонента TBindingsList.
Названия методов говорят
сами за себя
и вряд ли
требуют отдельных
комментариев
для программиста, имеющего опыт работы в
Delphi. Например, метод Format () по
зволяет
форматировать текстовую строку, и его
синтаксис очень напоминает поря док обращения
к
одноименной
фун ции
из модуля system. sysUtils. Допустим,
что в нашем
распоряжении
есть связь твindExpression между
компонентом
источником ArcDiall и компонентом-получателем Labell. Наша задача - сформу
лировать выражение, которое позволит отобразить в метке Labell значение угла поворота ArcDiall. Value. Решение задачи достаточно простое: SourceExpression:=
Format ( 'Угол: "%f"', Value).
![]()
![]()
![]()
ЗАМЕЧАНИЕ
К
сожалению, методы
LowerCase () и UpperCase () пока
не работают с
национальны
ми алфавитами.
Не исключено, что в дальнейшем перечень методов возрастет, поэтому предлагаем пример, с помощью которого вы сможете самостоятельно опросить главного спе циалиста по методам в тех,нологии LiveBindings - класс TBindingMethodsFactory (листинг 22.5).
![]()
![]()
uses System.Bindings.Methods;
{$R *. fmx}
proaedure TForml.FormCreate(Sender: TObject);
var MD: TMethodDescription;
Ьegin
for MD in TBindingMethodsFactory.GetRegisteredМethods do
if MD.DefaultEnaЫed then
ListBoxl.Items.Add(MD.Name) else ListBox1.Iterns.Add(MD.Name + ' (отключено)');
end;
Для
повторения
кода вам
понадобится
помощь
списка TListвox и
ссылка на
про граммный модуль
методов System.Bindings.Methods.
Если
логика
приложения
предполагает
создание
связи LiveBindings не
поверхност ным визуальным, а
программным образом, то разработчику стоит познакомиться
с классом TBindings. Сразу
отметим тот
факт, что твindings попросту
является владельцем
некоторого
набора
методов,
специализирующихся
на создании
живых связей.
Все методы
объявлены
как методы класса
(class function) и
поэтому не
нуждаются в
физическом существовании экземпляра TBindings:
ЗАМЕЧАНИЕ
Основной код программной реализации технологии живого связывания сосредоточен
в двух модулях: System. Bindings. Expression и System. Bindings.Helper.
Для создания простейшего выражения LiveBindings следует вызвать метод
class function
TBindings.CreateExpr BindGridLinklession(
const InputScopes: array of IScope;
//массив источников данных
const BindExprStr: string //строка выражения
) : TBindingExpression; overload;
Параметр Inputscopes
содержит массив ассоциаций, представляющий собой пары
"объект
источник
данных-
псевдоним объекта
в выражении".
Текст
выражения,
описывающего поведение источников данных, передается в
параметр BindExprStr.
ЗАМЕЧАНИЕ
Еще
одна
особенность твindings в том,
что в
результате
действий
класса на свет
по является
нерассмотренный
несколькими
страницами
ранее TBindExpression.
Вместо
этого TBindings оперирует экземпляром
класса - TBindingExpression.
Для создания связи программным образом обычно.применяют метод класса
class function TBindings.CreateManagedBinding(
const InputScopes: array of IScope; //массив источников данных
const BindExprStr:-string; //строка выражения
const OutputScopes: array of IScope; //массив выходных значений
const OutputExpr: string; //выходное выражение
const OutputConverter: IValueRefConverter;//выx:oднoe преобразование
BindingEventRec: TBindingEventRec; //событие
Manager: TBindingManager = nil; //ссылка на менеджер
Options:
TCreateOptions = [coNotifyOutput]//oпции
) : TBindingExpression; OV8rload;
Параметры Inputscopes и BindExprStr нам уже знакомы. Параметр outputScopes со держит массив пар "управляемый объект - псевдоним объекта в выходном выра жении". Выходное выражение заносится в параметр outputExpr. Правила преобра зования данных определяются параметром outputconverter. Шестой по счету пара метр BindingEventRec предоставляет возможность подключения к созданному объекту связи обработчика события. Седьмой по счету параметр мanager требует более подробного рассмотрения.
В результате выполнения функции появившийся на свет экземпляр связи сразу пе-
. редается под опеку менеджера взаимодействия (твindingManager). Менеджер управ ляет всеми связями в приложении и уведомляет их обо всех изменениях с ассоции рованными с этими связями объектами. В распоряжении менеджера могут состоять подчиненные менеджеры (субменеджеры), которым также рассылаются уведомле ния об изменениях в объектах. Если вы явным образом не укажете на экземпляр менеджера и передадите в параметр мanager неопределенный указатель nil, то связь автоматически передается под опеку глобальному менеджеру связей прило жения.
ЗАМЕЧАНИЕ
Технически
связь
может быть
создана без
зависимости
от менеджера
взаимодейст
вия. Для
этого
применяется
метод класса CreateUrunanagedBindin'g().
Для создания ассоциации между реальным объектом Delphi и его псевдонимом в строке выражения задействуется метод
class function
TBindings.CreateAssociationScope(
Assocs: array of TBindingAssocia·tion) : IScope;
В самом простейшем случае в единственный параметр метода - массив ассоциа ций Assocs - передаются пары "объект - текстовый псевдоним". Эти пары легко формируются сервисной функцией
function Associate(RealObject: TObject;
const ScriptObject: String): TBindingAssociation;
![]()
После успещноrо формирования связи между объектами обсудим самый главный (уже знакомый по предыдущим примерам) метод-уведомление: \
class procedure TBindings.Notify(Sender: TObject; PropNarne: string
Manager: TBindingManager = nil);
''.,
Метод
уведомляет
об изменении
свойства или
объекта, чем
стимулирует
один из
взаимодействующих
объектов на
выполнение
выражения.
Здесь: sender- объект,
инициирующий
взаимодействие объектов;
PropNarne - участвующее
во взаимодей
ствии
свойство; мanager- необязательная
ссылка на
менеджер
взаимодействия.
Технология LiveBindings позволяет не только связывать отдельные свойства пары объектов, но и способна еще на более существенные достижения. Одно из них организация взаимодействия между списками. Тот факт, что списочное представ ление данных предполагает наличие не одного, а целого ряда взаимоувязанных значений, существенно повышает сложность задачи, решаемой LiveBindings.
Вновь возьмемся за программирование, на этот раз напишем пример, демонстри рующий возможности технологии живой связи при работе со списками. Правда, сначала нам придется немножко подготовиться и создать список, специализирую щийся на обслуживании произвольных данных. Этот список позднее станет источ ником данных для LiveBindings.
Листинг 22.6 представляет исходный код класса тvalues, предназначенного для хранения тройки разнотипных значений. Повторяя пример, вы можете добавить или удалить поля класса, выбрать свои типы данных - это лишь подчеркнет все ядность LiveBindings.
![]()
type TValues=class
private
fRValue:Real; //поле вещественного типа fIValue:Integer;
//целочисленное поле
puЫic
constructor Create(aRValue:Real;aIValue:Integer);
property RValue:real read fRValue;
property IValue:Integer read fIValue;
end;
//конструктор класса TValues
constructor
TValues.Create(aRValue: Real; aIValue:
Integer);
Ьegin
fRValue:=aRValue; fIValue:=aIValue;
end;
![]()
После описания класса тvalues, который возьмет на себя обязанности элемента списка, перейдем к описанию собственно списка, который станет источником дан ных для LiveBindings (листинг 22.7).
![]()
uses ... , System.Generics.Collections;
var Forrnl: TForrnl;
ValuesList:TList<TValues>; //объявление списка источника данных
implementation
{$R *. fmx}
prooedure TForrnl.ForrnCreate(Sender: TObject);
var Values:TValues;
i:integer;
Ьegin
Randomize ();
ValuesList:=TList<TValues>.Create; //создаем исходный список
//заполняем 10 элементов списка произвольными данными
for i :=О
to 9 do
Ьegin
Values:=TValues.Create(Random(),Random(l00)); ValuesList.Add(Values);
end;
end;
Основная
изюминка
предложенного
в листинге
22.7 кода заключается
в том, что на роль списка
мы выбрали
объявленный
в модуле System.Generics.Collections
класс
TList. Эго класс-шаблон (см. главу 3), создающий список, способный обслуживать
практически
любой тип
данных. Мы
воспользовались
универсальностью
TList и заставили его работать с
элементом типа ТValues. После создания списка случай
ным
образом заполним его
значениями и на этом
ставим точку в подготовительном
этапе.
Наконец мы подошли к этапу организации связи между списком valuesList и ком понентом Listвoxl. Для налаживания отношений между списками мы воспользуем ся элементом управления списком и парой компонентов со страницы LiveBindings, нам понадобятся:
□ ListBoxl :TListBox;
□ BindingsListl: TBindingsList;
□ BindScopel: TBindScope;
Дважды
щелкнув
левой
кнопкой мыши
по·компоненту
BindingsListl, войдем
во
встроенный
редактор и
создадим новую
связь - элемент
TBindList (рис. 22.1О).
Новому
объекту по
умолчанию
будет присвоено
имя BindListl.
![]()
![]()
LiveBindings 321
![]()
Рис. 22.10. Создание связи между списками - TBindList
Найдите
связь BindListl в
Инспекторе
объектов и
приведите ее
свойства в
следую щее
состояние:
□
автоматическая активация AutoActiyate=True;
□
автоматическое заполнение целевого списка AutoFill=False;
□
управляемый компонент ControlComponent=ListBoxl;
□
компонент-источник SourceComponent=BindScopel;
□ щелкнув
по кнопке
с
многоточием
напротив
свойства FormatExpressions, вы по
лучите доступ
к редактору выражений (рис. 22.11). Заполните два
поля единст
венного выражения следующим образом:
•
ControlExpression=Text;
•
SourceExpression=ToStr(Current.IValue) +" "+ ToStr(Current.RValue).
По
большому
счету связь
уже
полностью
организована.
Чтобы вы не
слишком рас
слаблялись,
разбавим
процесс
визуального
проектирования
LiveBindings кодирова-
нием, нам
предстоит написать
целых три
строки кода
(листинг 22.8). ·
...... ·······•·..................................................................................................... •
1
...-.·..-.........................2....1.. .8о··.'"·····."'·.··т., ....
procedure TForml.ButtonlClick(Sender: TObject);
Ьegin
·•·•··"""'•,...•··.·········· •··"•·'"••• •••·•·•··"··'·•·•
BindScopel.DataObject .- ValuesList; //подключаем исходный список
BindListl.FillList; BindScopel.DataObject ·= nil;
end;
//передаем данные в
целевой список
//удаляем ссылку на объект
![]()
![]()
![]()
Рис. 22.11. Редактор выражения элемента TBindList
Щелчок по кнопке заставит отвечающие за связь компоненты BindScopel и
BindListl заполнить список ListBoxl данными из целевого списка ValuesList.
Класс TBindList обеспечивает
связь между
двумя
списками данных.
В качестве
ис точника
может выступать практически
любой набор значений,
оформленный
в виде
списка
(именно так
мы и
поступили в
листинге 22.6),
ссьmка на
список-источник
данных передается в
свойство
property SourceComponent: TComponent;
Второй участник связи (управляемый объект) должен быть упомянут в свойстве
property ControlComponent: TComponent;
Правила взаимодействия между списками описываются в свойстве
property FormatExpressions: TExpressions;
Экземпляр класса TExpressions представляет собой коллекцию элементов TExpressionitem, специализирующуюся на обслуживании пар управляющих выра жений ControlExpression и SourceExpression. Правила формирования выражений зависят от вида обслуживаемых данных.
Для отправки данных от списка-источника к получателю задействуется метод
prooedure FillList;
Впрочем, если свойство
property AutoFill: Boolean; //по умолчанию true
установлено в состояние по умолчанию, то управляемый объект станет получать данные от источника автоматически.

![]()
![]()
Сегодня
однопоточное
приложение - большая
редкость,
ведь в таком случае
поток
приложения
выступает
единственным
связующим
звеном между
всеми сторонами,
заинтересованными
в его работе.
Поток берет
на себя
обязательства
за взаимодей
ствие
приложения с
ОС, за
рендеринг
окон, за
реакцию
нажатий
пользователем
клавиш, за осуществление
расчетов,
одним словом - за
все! Не трудно
догадаться,
что
произойдет с
подобным
приложением,
если его
очень
трудолюбивый,
но, увы, не
имеющий
помощников
поток столкнется
с
ресурсоемкой
вычислительной
зада чей. На
время
выполнения
этого задания
программа не
сможет
заниматься
всеми остальными
делами, как
следствие - приложение
перестанет
откликаться
на дей ствия пользователя, прекратит взаимодействовать с
ОС, одним словом, окажется
в состоянии,
близком к
"зависанию".
К счастью как в Windows, так и в OS Х, кроме обязательного потока управления процесс имеет возможность обладать набором вспомогательных потоков. Напри мер, текстовый процессор Microsoft Word (в котором набираются эти строки) одно временно с вводом текста разбивает документ на страницы, контролирует знания языка (безжалостно подчеркивая мои ошибки), отправляет задание на печать и ре шает ряд других, менее очевидных для обычного пользователя задач. Все это про исходит одновременно благодаря тому, что в Word запущено несколько потоков. Каждый из потоков выполняет поставленную перед ним подзадачу и при желании может быть приостан<?влен или запущен вновь.
Как видите, возможность создания в приложении нескольких потоков предоставля ет нам определенные преимущества, в первую очередь связанные с повышением производительности. Дело осталось за малым - научить приложения FireMonkey работать с потоками.
Для программной реализации потока в модуле systern. Classes объявлен абстракт ный класс ттhreact, на основе которого программист создаст свой полнофункцио нальный поток.
![]()
![]()
![]()
ВНИМАНИЕ!
При проектировании многопоточных приложений
для OS Х надо учитывать
тот факт,
что данная ОС поддерживает
две технологии потоков: потоки Сосоа и потоки POSIX. Представленный в
RAD Studio ХЕ5
класс System. Classes. TThread нацелен
на работу с технологией
потоков POSIX.
На
палитре
компонентов
элемент
управления TThread вы не
найдете. Для
того что бы самое обычное приложение сделать
многопоточным,
требуется к
уже сущест
вующему
приложению
добавить
специальный
программный
модуль. Для
этого по сле создания нового
приложения выберите
пункт меню File I New I Other и в
разде
ле Delphi Files диалогового
окна New Items найдите
значок Thread
Object. После
нажатия кнопки ОК в новом
диалоговом окне
требуется ввести
имя нового
клас са
- потока.
Например, TDemoThread, как
показано на
рис. 23. I.
е Newltems
![]()
Q ■
Ccщ,onent Dat.МodAe Firl!Мori<.ey FireМ<pey
Form Мetropols...
[1]
мsa,id Teedwt mn.d · Lnt
г-- :
rмoets File WiDrd
New Тhread OЬject
r;;-
ТDеmоl!тем
ок. 11 c.icel
Рис. 23.1. Добавление потока к
приложению
В
результате
выполненных
действий Delphi
создает
представленный
в листин ге 23.1 шаблон,
предназначенный для описания
потока.
Обратите
внимание, что
в секции Protected среда
проектирования уже подготовила
процедуру Execute () . В
теле этой
процедуры
программист
располагает
программный
код, который
станет исполняться
в отдельном
потоке. Если
подзадача
вызывается
многократно,
то строки
кода
помещаются внутрь
тела цикла,
выход из
которого завершает работу
потока.
![]()
unit Unit2;
interface
uses System.Classes;
![]()
end; end.
При
старте
приложения
автоматическое
создание
потока не
произойдет,
для этого
следует явно
обратиться к конструктору
потока
constructor
Create(CreateSuspended: Boolean);
Единственный
параметр
конструктора
определяет,
каким
образом
производится
запуск
потока. Если
параметр CreateSuspended установлен
в false, то
поток старту
ет немедленно, сразу после создания экземпляра класса. Иначе поток создается
в спящем
режиме. В
последнем
случае для
запуска
потока
вызывайте
метод
procedure Start;
Говоря образно, эта процедура заставит биться сердце потока.
Для временной приостановки выполнения потока используйте процедуру
procedure Suspend;
Для возобновления работы приостановленного потока
procedure Start;
Возобновление
выполнения
потока
произойдет
именно с того
места (с той
строки кода), где
он был
приостановлен. Два
названных
метода
дублируются
свойством
property Suspended: Boolean;
Если вы установите
свойство в true, то поток приостановится,
false - вновь акти визируется.
ЗАМЕЧАНИЕ
В более ранних версиях Delphi
вместо метода Start (} применялся метод Resume ().
В FireMonkey применение этого метода
не рекомендуется.
Для
временной
приостановки
потока программисты
Delphi часто
пользуются
про цедурой
класса
class procedure Sleep(Timeout: Integer); static;
![]()
В
качестве параметра передается число миллисекунд, на которые необходимо за
морозить
подзадачу.
ЗАМЕЧАНИЕ
Основное
достоинство
метода Sleep () в
том, что
поток,
переведенный
в состояние сна,
не снижает
производительность
системы, т. к.
не требует
выделения
ему квантов времени.
Признаком
того, что поток завершил выполнение своей задачи, выступает значение
true, возвращаемое свойством
property Finished: Boolean;
Для полной остановки потока вызывайте метод
prooedure Terminate;
Этот способ
остановки
называется
мягким. Процедура
просто-напросто присваива
ет значение true свойству
property
Terminated: Boolean;
В этом
случае Delphi не
настаивает
на немедленном прекращении работы
потока и
разрешает
ему достичь
логического
конца.
Очень
часто
проверка
состояния свойства
служит
сйгналом к
выходу из
цикла об
работки
задачи
потока
(листинг 23.2).
![]()
prooedure TDemoThread.Execute;
Ьegin
repeat
{исполняемый код потока}
until Terminated;
end;
Останов потока сопровождается вызовом обработчика события
property OnTerminate: TNotifyEvent;
Если установить в
true свойство
property
FreeOnTerminate: Boolean;
то сразу
после
останова
уничтожится экземпляр
потока. Иначе
за вызов
деструкто ра
потока
отвечает
программист.
Если выполнение метода
Execute () прервалось в
результате ошибки, и
ошибка не
была
обработана в
рамках этого
метода, то в
свойство
только для
чтения
property FatalException: TObject; //только для чтения
будет записан объект ошибки.
![]()
Еще
одним
способом,
косвенно
проверяющим корректность
завершения
работы
потока, является
использование
свойства
property ReturnValue: Integer;
Это служебная переменная, в которую можно записывать данные, например, о ко личестве запусков потока. Свойство Returnvalue часто применяется совместно с методом ожидания wai tFor (} . Функция предназначена для организации ожидания, пока какой-нибудь другой поток не выполнит поставленную перед ним задачу.
Существует альтернативный доступ к значению ReturnValue, его обеспечивает метод класса
class prooedure
SetReturnValue(Value: Integer}; static;
Благодаря методу мы сможем изменить возвращаемое потоком значение, даже не имея прямого доступа к экземпляру потока.
Современные версии Windows и OS Х поддерживают работу с несколькими про цессорами, распределяя задачи между ними. Класс TThread способен проинформи ровать программиста о процессоре компьютера. Свойство
class property IsSingleProcessor: Boolean;
в состоянии true уведомляет разработчика, что поток будет запущен на однопро цессорной станции, если же свойство возвратит false, то стоит обратиться к свой ству
class property
ProcessorCount: Integer;
возвращающему число процессоров или ядер процессора.
ВНИМАНИЕ!
Разработчику
рекомендуется
не запускать
в приложении
более 16
потоков на
один процессор,
т. к. при превышении
этого
значения
будет
существенно
снижена
произво
дительность
системы.
Еще одна полезная функция класса
class function GetCPUUsage(var PrevSystemTirnes: TSysternTirnes}: Integer;
позволит программисту выяснить степень загрузки процессора. Единственная осо бенность метода в том, что поступающая в его параметр структура PrevSysternTirnes: тsysternTirnes должна быть объявлена как глобальная переменная. В самом про стейшем случае, разместив на форме шкалу TProgressBar, метку TLabel и таймер ттirner, вы сможете получать регулярные сведения о занятости центрального про цессора (листинг 23.3).
![]()
![]()
![]()
var Forrnl : TForrnl;
ST: TThread.TSysternTirnes;
![]()
![]()
prooedure TForml.TirnerlTirner(Sender: TObject);
var Х: Integer;
Ьegin
Х := TThread.GetCPUUsage(ST);
ProgressBarl.Value := Х; Labell.Text ·= Format('%d%%', [Х]);
end;
Если в
приложении
допускается
исполнение
нескольких потоков,
то мы имеем пра во попросить текущий
процессор
перейти на обслуживание
очередного
потока.
Для этого
предназначен
метод класса
class procedure Yield; static;
Заметим, что окончательное
решение на постановку
потока на обслуживание
всегда
останется за
операционной
системой.
При
проектировании
многопоточного
приложения, в
котором
несколько
потоков
обслуживают
один и тот
же ресурс
(файл,
коммуникационный
порт и т. п.) или вы
полняют общий участок кода, надо исключить конфликт совместного доступа
к ресурсу.
Большую
помощь в этом оказывает
метод
ожидания,
позволяющий
одному
потоку дождаться
завершения
другого.
function
WaitFor: LongWord;
Функция
возвращает
значение,
содержащееся
в свойстве Returnvalue ожидаемого
потока (табл. 23.1).
Таблица 23.1. Значения, возвращаемые методом WaitFor()
|
Значение |
Описание |
|
WAIT- OBJECT- о |
Контролируемый объект
перешел в сигнальное
состояние.
Это признак
того, что выполняющийся ранее поток корректно завершил
свою задачу |
|
WAIT-
TIMEOUT |
Хотя время тайм-аута истекло, но состояние
опрашиваемого
объекта син-
хронизации
несигнальное.
Это означает, что ресурс пока захвачен другим
потоком |
|
WAIT- AВANOONED |
Выполнение обладавшего объектом синхронизации потока завершилось,
но объект
по-прежнему
захвачен
этим
потоком.
Ввиду
некорректности
такого
положения
вещей объект
синхронизации
передается
во владение
вызвавшего
его потока и переводится
в
несигнальное
состояние |
|
WAIT- FAILED |
Ошибка выполнения метода |
ЗАМЕЧАНИЕ
Метод WaitFor ( J применяется для совместной работы не только с
потоками, но и с такими
объектами
синхронизации
Windows, как события,
мьютексы и
семафоры.
Ключевая
особенность
метода ожидания
заключается
в том, что он не возвращает
ре-
зультат
немедленно, а
ждет, пока не
будет удовлетворен
некоторый
перечень
задан ных программистом
критериев.
Таким образом
поток,
обратившийся
к объекту
синхро
низации при
помощи
метода WaitFor () , превращается
в пленника,
ожидая от
метода ответа.
Соответственно
выполнение
потока
приостанавливается
и таким
образом ре
шается
задача
синхронизации.
Обычный
поток
приложения
(точно так,
как и поток
управления
приложения)
об ладает
определенным
приоритетом,
от которого
зависит,
сколько
квантов
времени
будет
выделено
процессором
компьютера
для
обработки подзадачи.
Приоритет
потока
определяется
значениями
двух
составляющих:
приоритетом
процесса
владельца
потока и
собственно
приоритетом
этого потока.
Поэтому
приоритет
обычного
потока часто
называют относительным
приоритетом. Такое
название
объясняется
тем, что два
потока,
скажем, с
приоритетом tpNorrnal, но
порожденные
процессами с
различными приоритетами,
не смогут
претендовать
на одинаковое
процессорное
время.
Реальный приоритет
потока
определяется
приоритетом
поро
ждающего его
процесса и
своим приоритетом.
При определении приоритета потока следует учитывать некоторые различия Windows и OS Х. Так в приложениях Windows приоритет потока определяется именованной константой
type TThreadPriority = (tpldle,
tpLowest, tpLower, tpNorrnal, tpHigher, tpHighest,
tpTimeCritical);
//фоновый приоритет
//низкий приоритет
//пониженный приоритет
//нормальный приоритет
//повЬШiенный приоритет
//высочайший приоритет
//приоритет реального времени
в
свойстве
property Priority: TThreadPriority; //по умолчанию tpNorrnal
В проектах для OS Х при определении приоритета потока POSIX мы воспользуемся целым числом
property
Priority: integer;
ВНИМАНИЕ!
Без
особой на то
необходимости старайтесь
не создавать
потоков с приоритетом
вы ше нормального.
Такой поступок
может
привести к тому, что системные ресурсы
будут
нацелены на
обслуживание
только этой
задачи.
ЗАМЕЧАНИЕ ,
В состав
Delphi входит специальная
утилита,
предназначенная для просмотра
потоков
приложения. Для обращения к утилите выберите пункт меню View I Debug Windows 1
Threads.
![]()
Синхронный и асинхронный вызовы
внешнего
метода
Если из тела основного метода потока Execute () необходимо осуществить вызов внешнего метода или обратиться к свойству компонента библиотеки VCL, то не следует это делать напрямую, вместо этого надо использовать специализированные методы Synchronize () или Queue (). Задача методов - исключение взаимных бло кировок потоков.
ЗАМЕЧАНИЕ
Блокировка
потоков
возникает в
том случае,
когда поток
или
несколько
потоков пыта
ются
одновременно
получить
доступ к
одному и тому
же ресурсу.
Если этот
ресурс
захвачен
третьей
стороной,
например
главным потоком
приложения, и корректный
за хват
ресурса невозможен,
то потоки
попадают в
тупик. Такая
ситуация
называется взаимной
блокировкой.
Существует
несколько
перегружаемых
версий
методов, мы
остановимся
на наибо лее
простых:
prooedure
Synchronize(Method: TThreadМethod); overload; procedure Queue(AМethod: TThreadМethod); overload;
Методы гарантируют, что к объекту (методу объекта) одновременно получит дос туп только один поток. В качестве аргумента процедуры передается имя вызывае мого метода. Разница между Synchronize () или Queue () заключается в порядке ожидания ответа от вызванного внешнего метода. Процедура Synchronize () работа ет в синхронном режиме. Это означает, что поток, обратившись к внешнему мето ду, приостановит свою работу до тех пор, пока не получит ответа о том, что внеш ний метод выполнен. Процедура Queue () отдает предпочтение асинхронному режиму, поток просто ставит задачу в очередь и, не дожидаясь ответа о ее выпол нении, продолжает свою работу.
Пример многопоточного приложения
Для демонстрации возможности многопоточной обработки данных разработаем приложение, позволяющее собирать сведения о находящихся в определенной папке файлах с растровыми изображениями JPG и создающее их миниатюры. Для реали зации задуманного нам понад?бится новое приложение HD, на единственной фор ме которого следует расположить:
□
строку ввода Editl, в ней пользователь введет путь к папке с файлами;
□ область с
полосами прокрутками ScrollBoxl, внутри этой области мы станем
динамически
создавать
миниатюры изображений;
□ кнопку Buttonl, отвечающую за запуск процесс сбора.
В листинге 23.4 представлен предельно упрощенный исходный код потока
TThurnЬThread, отвечающего за создание миниатюры.
![]()
![]()
![]()
FileName:TFileName; //имя. файла с
изображением
Irnage: Tirnage; //ссылка на компонент Tirnage
protected
procedure Execute; override; end;
procedure TThumЬThread.Execute;
Ьegin
Irnage.Bitrnap.LoadThumЬnailFrornFile(FileName,Irnage.Width,Irnage.Height);
Terrninate;
end;
В
секции
частных объявлений потока
TThumЬThread мы ввели
два
дополнительных
поля. В поле FileName заносится имя
файла, для которого
следует
создать
миниатю ру, а в поле Irnage окажется ссылка
на компонент тrrnage,
отображающий картинку.
Процесс сбора миниатюр начинается в момент щелчка пользователя по кнопке (листинг 23.5).
![]()
![]()
procedure
TForrnl.ButtonlClick(Sender: TObject); var SDA:TStringDynArray;
i: integer;
ThumЬirnage:Tirnage;
Ьegin
SDA:=TDirectory.GetFiles(Editl.Text,
'*.jpg', TSearchOption.soTopDirectoryOnly);
for i:=0 to High(SDA)
do
Ьegin
ThumЬirnage:=Tirnage.Create(ScrollBoxl);
ThumЬirnage.Parent:=ScrollBoxl; ThumЬirnage.Width:=100; ThumЬirnage.Height:=100;
with
TThumЬThread.Create(true)
do
Ьegin
FileName:=SDA[i]; Irnage :=ThumЬirnage; FreeOnTerrninate:=true;
{$IFDEF MSWINDOWS} //код для.
Windows Priority:=TThreadPriority.tpLower;
{$ELSE}
![]()
end; end;
{$IFDEF POSIX}
Priority:=10;
{$ENDIF}
{$ENDIF}
Start;
end;
//код дпя потоков POSIX
Воспользовавшись
услугами
метода GetFiles () класса
TDirectory, мы
собираем все
файлы с
расширением
имени jpg в
динамический
массив SDA. Затем,
перебирая
эле менты
массива, создаем экземпляры
класса тrmage для каждого файла с
картинкой и
размещаем их на
поверхности
компонента scrollBoxl. После
этого
наступает
черед
формирования отдельного
потока - он
создается на
основе
описанного в
листин ге 23.4
класса TThurnЬThread. Потоку
передаются сведения об
имени файла и
ссылка на
объект Timage.
Проект
почти готов,
вам осталось
самостоятельно
решить
задачу
размещения
кар тинок на
поверхности scrollBoxl или
обратиться к
архиву с
примерами к
этой гла ве. В
результате
мы получим
приложение,
представленное
на рис. 23.2.
![]()
Рис. 23.2. Экранный снимок работающего приложения
Синхронизация потоков в
Windows
Одной
из серьезных
проблем, с
которой сталкиваются
разработчики
многопоточ
ных приложений,
является
организация
совместного
доступа двух
(и более) пото
ков к общему ресурсу. Конкурируя за обладание общим ресурсом, потоки из по-
![]()
![]()
мощников
превращаются
в хулиганов,
инициируя
неприятные
ошибки.
Поэтому при
реализации
взаимодействующих
потоков
программист
обязан
решить
задачу
корректного
доступа потоков к
разделяемому
ресурсу. Для исключения
ошибок при
подключении
к общему
ресурсу
потокам необходимо
синхронизировать
свои дей ствия
так, чтобы
ресурс
принадлежал
не более чем
одному
потоку
одновременно.
При попытке
обращения к
захваченному
ресурсу остальных
потоков они
должны быть
приостановлены
до моме,нта его
освобождения.
Операционная система Windows - большая мастерица по синхронизации процес сов и потоков. Для этого реализовано несколько механизмов управления процесса ми и потоками, при которых диспетчер задач не выделяет ожидающим освобожде ния ресурса ни кванта лишнего времени. Синхронизация производится с помощью объектов синхронизации: событий, критических секций, мьютексов, семафоров ит.д.
Объект синхронизации обладает двумя устойчивыми состояниями: сигнальным
·(signaled) и несигнальным (nonsignaled). Сигнальное состояние разрешает приоста новленному потоку приступить к работе, несигнальное состояние объекта синхро низации, напротив, предлагает ожидающему своей очереди потоку спать дальше.
ЗАМЕЧАНИЕ
Все рассматриваемые в этой главе объекты синхронизации описаны в модуле SyncObj s.
К числу
наиболее
простых
средств
синхронизации
потоков
относится
синхрониза ция с помощью описанного в
модуле syncoьjs события TEvent. Как и
все объекты
синхронизации
Windows, событие
может
находиться в
одном из двух
состояний:
сигнальном и
несигнальном.
Синхронизируемые
потоки способны
выяснить, в
ка ком
расположении духа
находится экземпляр класса
TEvent, и
приостанавливать
или,
наоборот,
возобновлять
свою работу.
Как всегда, создание объекта начинается ,с вызова его конструктора:
constructor Create(EventAttributes: PSecurityAttributes;
ManualReset, InitialState: Boolean; const Narne: string) ;
Первый параметр
конструктора
EventAttributes - это
дескриптор
безопасности,
применяемый
в Windows. Параметр
ManualReset разрешает (значение
true) перево
дить событие
в
несигнальное
состояние
вручную (это
умеет делать
метод ResetEvent
() ), иначе
сброс
произойдет
только после
старта
потока,
ожидающего
этот объект
синхронизации. Параметр
InitialState определяет,
в какое
из состоя
ний
переходит
событие
сразу после
создания: в
сигнальное (true) или
несигнальное
(false). Последний параметр назначает 1:fМЯ создаваемому объекту синхронизации.
Будьте внимательны, имя должно быть уникальным. Впрочем, имя понадобится,
только если
этим
событием надо
воспользоваться
извне
процесса,
создавшего
экземпляр TEvent.
![]()
При обсуждении конструкт ра события TEvent упоминался метод
procedure ResetEvent;
предназначенный для перевода объекта-события в
несигнальный режим "вручную".
Обратная операция, включающая событие, обеспечивается процедурой
procedure SetEvent;
В составе класса TEvent предусмотрен метод
ожидания:
function WaitFor(Timeout: DWORD): TWaitResult;
type TWaitResult = (wrSignaled, wrTimeout, wrAЬandoned, wrError);
Время
ожидания
перехода
объекта
синхронизации
в сигнальное
состояние
опреде
ляется
единственным
аргументом
метода Timeout и
измеряется в
миллисекундах.
Если по истечению
заданного
тайм-аута
ничего не
произошло, то
метод
возвратит wrTimeout;
если же за это время объек-- :_-событие перейдет в
сигнальное состояние,
то метод, не
дожидаясь
истечения
тайм-аута,
вернет wrsignaled.
Дескриптор объекта синхронизации Event доступен из свойства
property Handle: THandle;
Код последней ошибки, связанной с
объектом,
доступен из свойства
property LastError:
Integer;
Признаком того, что все в
порядке, будет нулевое значение.
Как
уже
отмечалось в
начале
подраздела,
синхронизация
событием
очень проста.
Создайте
новый проект
и разместите
на его
главной
форме три
компонента:
шкалу ProgressBarl: TProgressBar, метку Labell: TLabel и
кнопку Buttonl: TButton. Добавь те к
проекту
новый поток,
присвоив ему
имя TProgressThread (листинг 23.6).
![]()
uses Classes,
SyncObjs, SysUtils,
type TProgressThread = class(TThread) private
procedure ChangeProgress; //процедура управления шкалой
protected
procedure Execute; override;
end;
var
Event:TEvent; //событие синхронизации ,
i.mplementation
procedure TProgressThread.Execute;
Ьeqin
repeat
![]()
until Terminated; Event.Free;
end;
prooedure
TProgressThread.ChangeProgress;
Ьegin
if Forml.ProgressBarl.Value+l<Forml.ProgressBarl.Max then
Ьegin
Forml.ProgressBarl.Value:=Forml.ProgressBarl.Value+l;
Forml.Labe'll. Text:=
Format('%.2f%%', [Formi.ProgressBarl.Value]);
end else Forml.ProgressBarl.Value:=0;
end; end.
.
Изучив
листинг 23.6, вы
увидите, что
в рамках программного
модуля
объявляется
событие синхронизации
Event: TEvent.
Визуальной
стороной
работы
потока стано
вится
изменение
положения
шкалы Progressвarl, эта
операция
производится
в ме- тоде ChangePrqgress () .
Теперь переключим наше внимание на главную форму проекта. Нам понадобится описать два события. В событии OnShow (), вызываемом в момент вывода формы на экран, создается низкоприоритетный поток. Щелчок по кнопке формы вызывает диалог редактирования позиции шкалы Progressвarl. Для того чтобы в момент вво да нового значения поток приостановил свою работу, мы переводим событие Event в сигнальное состояние. Завершив редактирование, сигнальный режим отключается (листинг 23.7).
![]()
prooedure TForml.FormShow(Sender: TObject);
Ьegin
Event:=TEvent.Create(nil,true,false, 'Eventl');//cQздaниe события
with TProgressThread.Create(True) do //создание потока
Ьegin
Priority:=tpLowest; FreeOnTerminate:=True;
Start;
end;
end;
![]()
![]()
procedure TFonnl.ButtonlClick(Sender: TObject); var Value: string;
Ьegin
Event.SetEvent; //перевод события Event в сигнальный режим Value:=FloatToStr(ProgressBarl.Value);
if InputQuery('Шкaлa', 'Новое знaчeниe',Value)=true
.then ProgressBarl.Value:=StrToFloatDef(Value,ProgressBarl.Value);
Event.ResetEvent; //отключение сигнального режима
Критическая секция TCritica/Section
Критическая, секция обычно применяется для синхронизации параллельно выпол няющихся потоков-близнецов (т. е. потоков, порожденных от одного класса и имеющих один и тот же код) или для защиты разных потоков, обращающихся к одному и тому же коду. Критическая секция - это специальным образом поме ченный участок кода, из которого и производится обращение к разделяемому ре сурсу. Если код в этой секции выполняется хотя бы одним потоком, то вход в кри тическую секцию для остальных потоков запрещается.
Создание критической секции начинается с обращения к методу create (). Появив шаяся на свет в результате вызова конструктора секция приступит к выполнению своих прямых функциональных обязанностей после обращения к методу
procedure Enter;
С этого момента объект синхронизации встает на защиту отмеченного участка кода и не допустит к нему ни один из потоков до тех пор, пока не будет вызван метод
procedure
Leave;
извещающий все остальные потоки, что критический участок кода больше не ис пользуется.
Критическая секция может ждать своего выполнения бесконечно долго, поэтому при разработке многопоточного приложения с критической секцией программист отвечает за то, чтобы:
□ при аварийном завершении потока критическая секция немедленно освобожда лась;
□ время, затрачиваемое конкурирующими процессами на вход в критическую сек- цию, было минимальным.
Обычно код входа в критическую секцию оформляется в защищенной от ошибок секции try .. finally, что гарантирует освобождение заблокированного кода (лис тинг 23.8).
![]()
![]()
![]()
![]()
var CriticalSection:TCriticalSection;
procedure TDemoThread.Execute;
Ьegin
repeat
//код потока
try
CriticalSection.Enter; Synchronize(<вызoв внешнего метода>);
finally
CriticalSection.Leave;
end;
until Teпninated;
Мьютекс TMutex
Как
и критическая
секция,
объект синхронизации
мьютекс
(mutex)
может принад
лежать
только
одному
потоку.
Если мьютекс
свободен, то
он устанавливается
в
сигнальное
состояние,
если же он
кем-то захвачен - переходит
в
несигнальный
режим.
При попытке
получить
доступ
к "приватизированному"
другим
владельцем мьютексу
поток
получает
отказ и
переводится в состояние
ожидания его
освобож дения.
Таким
образом, мьютекс
обеспечивает
взаимное исключение
(mutual exclusion)
доступа
потоков к
разделяемому
ресурсу.
ЗАМЕЧАНИЕ
Разница
между
мьютексом и критической
секцией заключается
в том, что мьютекс
мо жет быть
доступен не
только внутренним
потокам
приложения,
но и другим
процес сам, протекающим
в операционной
системе. Единственным
условием для этого явля ется
наличие у
мьютекса
уникального
имени. Таким
образом,
мьютекс
представляет
собой
средство
синхронизации
не только
потоков
внутри
приложения,
но и всех
про цессов
операционной
системы.
Мьютекс описывается классом тмutех. Существуют три варианта создания мьютек са. Простейший из них обеспечивает конструктор
constructor Create(UseCOМWait: Boolean
= False); overload;
В итоге
на свет
появляется
неименованный
объект синхронизации,
доступный
только внутренним
потокам
одного
приложения. Единственный
параметр
конст руктора определяет способность
мьютекса
взаимодействовать с компонентами
STА (single-threaded
apartment) СОМ.
Наиболее востребованной версией конструктора считается
constructor Create(MutexAttributes: PSecurityAttributes;
InitialOwner: Boolean; const
Name: string; UseCOМWait: Boolean = False); overload;
..
![]()
338 Глава 23
Первый
параметр
метода
представляет
собой
указатель на
запись
атрибутов
защи ты
pSecurityAttributes = лтsecurityAttributes. Параметр
определяет
атрибуты безопасности,
в
простейшем
случае в
качестве
этого аргумента
допускается
пере дача неопределенного
указателя
nil. Параметр
InitialOWner указывает,
станет ли
поток,
создающий
мьютекс, его владельцем.
Для этого
параметру
передавайте
зна чение true. Последний
аргумент
содержит имя
мьютекса.
Если мьютекс
не плани
руется
разделять с
внешними
процессами,
то метод
удовлетвориться
пустым аргу ментом.
Третий
конструктор
открывает
уже существующий
мьютекс,
обращаясь к нему
по имениNаmе.
constructor Create(DesiredAccess: LongWord; InheritHandle: Boolean;
const Name: string; UseCOМWait: Boolean = False); overload;
Порядок доступа зависит от первого параметра, в который направляется комбина
ция флагов доступа: МUТЕ-Х
AL-L ACCESS, МUТЕ-Х
MODIF-Y
STATE, SYNCHRONIZE, _DELETE,
READ_CONTROL, WRITE_DAC, WRITE_OWNER.
Для управления мьютексом достаточно пары методов:
prooedure
Acquire; override;
prooedure Release; override;
Первая из процедур захватывает мьютекс, вторая- освобождает.
ВНИМАНИЕ!
Не забывайте освобождать мьютекс, иначе первый поток (захватив мьютекс) станет
единственным владельцем критического
участка
кода и остановит
работу
оставшихся потоков по крайней
мере до
момента
своего
физического
уничтожения
методом Terminate (). В этом случае мьютекс перейдет в
разряд покинутых,
и операционная
система передаст права на него другому потоку.
Семафор (semaphore)- логическое развитие мьютекса. Как вы помните, объект
взаимного исключения мьютекс позволял владеть собой только одному потоку.
В отличие от него
семафор может находиться в
совместном использовании сразу
нескольких
потоков, но
при этом он
умеет
управлять
количеством этих
потоков -
выступает в роли
ограничителя их
"популяции". Для
этого он
снабжен счетчиком,
определяющим максимальное число
потоков,
которым разрешено
выполняться
со вместно. С запуском каждого нового потока значение счетчика уменьшается на
единицу, и когда оно достигнет
нуля, семафор
перейдет в
несиrнальное состояние.
Это означает, что последующие потоки приостанавливаются и
будут дожидаться
момента, пока какой-нибудь из
потоков не
освободит семафор. Характерным при
мером области применения
семафоров
может послужить
программное
обеспечение
какого-нибудь аппаратного устройства, с
которым может работать только
ограни
ченное количество потоков, или
реализация веб-сервера (сервера баз
данных),
до пускающего
одновременный
доступ
ограниченного
числа
посетителей.
![]()
Семафор обладает сразу тремя перегружаемыми версиями
конструктора:
constructor Create(UseCOМWait: Boolean
= False); overload; constructor Create(SernaphoreAttributes: PSecurityAttributes;
AinitialCount, AМaximumCount: Integer;
const Name: string; UseCOМWait: Boolean = False); overload;
constructor
Create(DesiredAccess: LongWord;
InheritHandle: Boolean; const Name: string; UseCOМWait: Boolean = False); overload;
![]()
Первый
конструктор
наиболее
прост, он создает
незатейливый
семафор, по
своим характеристикам
почти не
отличающийся
от мьютекса.
Наиболее
полезна
вторая
версия конструктора,
именно она
формирует
полноценный
объект
синхронизации.
Третий конструктор предназначен не для создания, а
для получения доступа
к семафору
(обычно
создаваемому
в другом
приложении).
Параметры
конструкто ров
представлены в табл.
23.2.
Таблица 23.2. Параметры конструктора семафора
|
Параметр |
Описание |
|
SemaphoreAttributes |
Ссылка
на
структуру тsecurityAttributes с атрибутами
безопас-
ности,
допускается
nil |
|
AinitialCount |
Начальное
значение
числа
доступных
потоков и
максимальное
зна- чение
сче ика семафора. Когда число
свободных потоков подходит
к нулю,
семафор
приостанавливает
доступ к
ресурсу |
|
AМaximumCount |
|
|
Name |
Чувствительное к
регистру символов уникальное имя семафора |
|
UseCOМWait |
Параметр
устанавливается в true, если
блокируемый
ресурс дол-
жен
поддерживать
технологию STA
СОМ |
|
DesiredAccess |
Уточняет особенности доступа к
семафору: SEМAPHORE_ALL_ACCESS,
SEМAPHORE_MODIFY_STATE,
SYNCHRONIZE,_DELETE, READ_CONТROL, WRITE_DAC, WRITE_OWNER |
|
InheritHandle |
Разрешает дочерним процессам наследовать дескриптор |
Захват семафора осуществляется после обращения потока к методу
prooedure Acquire;
Внутренний
счетчик
семафора
получает приращение,
и если он
превысил
ограни чение
на
максимальное
число
потоков,
доступ к ресурсу
временно
блокируется.
Блокировка снимается только по ле того, как другой поток освободит семафор
с помощью
метода
pro08dure Release; overload; overricle;
function Release(AReleaseCount: Integer):
Integer; reintroduoe; overload;
Несмотря на то, что семафор - достаточно сложный объект синхрониз;щии, работа с ним не вызовет особых затруднений. Листинг 23.9 отражает процесс создания экземпляра семафора.
............. '··"'"······, _., .,...,.,.,...,.........,... ...,,.,...............,.."f\'•···············..-···············..................,....................,......,...........................................................,.•••.•.•..•.•. •••'lt
Лист11мr 23.1. а......•• 1 ре .
• ![]()
![]()
![]()
![]()
,;.,, -,'"(f'i........••••••'·•"'-""'•"'••••.. •• ,-1, ,,,, ._.., -<........'!';№, ·f"h.••.. •••••'d ....., '""•"'••"'•,<:· -.. • .•.. ••••••••..
•• - -'::"-•и••••••••••'·, ·ъ·••••••••.......••••.. •••••••••••........• ••••••••••............ "'""•••••••................. ••»•••••••••••••••"••-••••••
uses SyncObjs, ...
var Semaphore:TSemaphore; //глобалQная переменная
procedure TfnnМain.FoпnCreate(Sender: TObject);
var AМaximurnCount:Integer;
Ьegin
AМaximurnCount:=TThread.ProcessorCount*l0;
Semaphore:=TSemaphore.Create(nil, AMaximumCount-1, AМaximurnCount,
'DemoSemaphor');
Обратите
внимание на
то, как в коде
определяется
максимальное
число
потоков,
ко,нтролируемое
семафором. Мы
узнаем число
процессоров в
компьютере и,
исходя из
этого,
ограничиваем
количество
потоков.
Напомню, чт"о
не
рекомендуется
созда вать
более 16 потоков
на одно ядро
процессора.
Листинг 23.1О демонстрирует работу с
семафором потока.
![]()
procedure TThumЬThread.Execute;
.
Ьegin
Semaphore.Acquire; //ограничиваем число работающих потоков
repeat
if Semaphore.WaitFor(200)<>wrSignaled then //ожидание 200 мс
Image.Bitmap.LoadThumЬnailFrornFile(FileName,
Image.Width, Irnage.Height);
if NOT Image.Bitmap.IsEmpty then Terminate;
until Teпninated;
Semaphore.Release;
end;
Если
вы не забьши
пример из
начала главы
(см. листинг 23.4),
то сразу
узнаете усо
вершенствованный
вариант
потока сбора
миниатюр. Теперь
семафор
контролирует
число
одновременно выполняющихся потоков и не
дает им
перегружать процессор.
ВНИМАНИЕ!
Будьте
аккуратны
при
использовании
семафоров в
зацикленных
потоках
(потоках, в·методе Execute () которых
используются
операторы
цикла). В таких
случаях
высока
вероятность
блокирования
потока.
![]()
Кто из
программистов
не тешет себя
надеждой
написать
программу,
которая ока
жется востребованной
не только
в стенах
компании, во
славу
которой он трудится,
но и внешними,
а возможно,
и зарубежными
пользователями? Сегодня,
в эпоху Ин
тернета, не
существует
никаких
проблем проявить
себя и
представить
свой про
граммный
шедевр на
всеобщее
обозрение.
Единственным
ограничением
может стать
сложность с
локализацией
вашего
программного
продукта.
Впрочем, в
этом
предложении
я допустил
ошибку - следовало
написать
слово
"может" в
прошед шем
времени. Ведь
в FireMonkey эта
проблема
снята.
Все
гениальное
просто,
особенно
если об этом
позаботились
в корпорации
Embarcadero. Подтвердить
эти слова
сможет
компонент TLang, позволяющий
созда вать
проекты,
одновременно
поддерживающие
несколько
национальных
языков.
Компонент языковой поддержки TLang
По
своей сути
компонент TLang представляет
собой
систему
управления текстовы
ми
ресурсами
приложения. В
этой системе
реализован
механизм,
позволяющий
программисту
сопоставлять
с элементом
управления
различные
варианты
перевода
текстовой
надписи.
Для
того чтобы
сразу понять
порядок
применения
компонента,
создадим
новое
приложение FireMonkey НD Application и
на главной
форме
проекта
разместим
произвольный
перечень
элементов
управления. В
их числе (рис.
24.1):
□ кнопка выбора RadioButtonl: TRadioButton, с надписью "English language";
□ кнопка выбора Radioвutton2: TRadioButton, с надписью "Russian language";
□ кнопка Buttonl: твutton с надписью "Apply" (Применить);
□ компонент языковой поддержки Langl: TLang.
ВНИМАНИЕ!
При проектировании многоязычного проекта за основной целесообразно принимать
английский
язык.
![]()
![]()
Рис. 24.1. Состав элементов управления для демонстрационного приложения
Исходный язык приложения - английский, наша задача - научить проект "изъяс няться" на русском языке. Технология обучения весьма незатейлива. Находим на форме компонент Langl:TLang и дважды щелкаем по нему левой кнопкой мыши. В ответ на это действие на экране компьютера отобразится редактор Language Designer, позволяющий локализовать наш программный продукт.
Для создания русскоязычного (или любого другого) перевода следует воспользо ваться кнопкой Add Language... (Добавить язык) и ввести текстовый идентифика тор нового языка. В нашем случае, для русского языка восiюльзуемся аббревиату рой "ru". Затем, нажав кнопку Scan for strings (Сканировать строки), обновим спи сок подлежащих переводу текстовых ресурсов проекта. Последним штрихом станет перевод англоязычных терминов на русский язык (рис. 24.2).
ЗАМЕЧАНИЕ
При
желании
можете
создать
текстовый
файл-шаблон,
содержащий
подлежащие
пе реводу на
другой язык
текстовые
данные.
Строки файла
представляют
собой пары,
например: Button1=Кнопка1.
Заполнив
файл, его
можно
подключить к
компоненту TLang.
Закончив перевод англоязычных· названий на русский язык, немного ·попрактику емся в программировании - опишем щелчок по кнопке Buttonl (листинг 24.1).
![]()
prooedura TFoпnl.ButtonlClick(Sender: TObject);
Ьegin
if RadioButtonl.IsChecked then Langl.Lang
:= 'en'
еlве Langl.Lang := 'ru';
end;
![]()
![]()
Рис. 24.2. Окно Language Designer с русскоязычным переводом
В
рамках
события, в
зависимости
от состояния
кнопки
выбора RadioButtonl выби
рается
англоязычный
или
русскоязычный
режим работы
приложения. Дтiя этого
текстовая аббревиатура
языка
передается в
свойство
property Lang: string;
компонента TLang. Хотите- верьте, хотите- нет, но все остальное сделает ком понент-переводчик TLang (рис. 24.3).
![]()
Рис. 24.3. Англо-
и русскоязычный интерфейсы приложения
ЗАМЕЧАНИЕ
Не все элементы управления способны
взаимодействовать
с компонентом языковой
поддержки TLang. Например, такой возможностью пока не обладают пункты меню
(TMainМenu, TPopupMenu, TMenuBar). Характерным признаком того, что компонент спо
собен
взаимодействовать
с переводчиком
TLang, является
наличие у него
свойства AutoTranslate.
По
умолчанию
все сведения
о переводе
хранятся
непосредственно
в коде
програм мы
(если быть
точнее - в коде
файла формы fmx,
которой
принадлежит
компо нент-переводчик).
property StoreinForm: Boolean; //по умолчанию true
Такой
подход
вполне
жизнеспособен
для относительно
простых
приложений,
но если речь
идет о
серьезных
проектах со сложным
пользовательским
интерфейсом,
то имеет
смысл
располагать
ресурс с
переводом во
внешних
файлах. В
таком слу
чае нам на помощь
придут
методы
procedure LoadFromFile(const AFileName: string); procedure SaveToFile(const AFileName: string);
позволяющие
соответственно
загрузить ресурс
с переводом
из файла и
сохранить
ресурс в фащ:.
Кроме того, ресурс можно
загрузить с
помощью свойства
property FileName: string;
В этом случае целесообразно файл с
переводом размещать
в папке приложения.
ЗАМЕЧАНИЕ
Для файлов с
переводами принято
использовать расширение имени lng.
Файл lng -
это обычный текстовый файл в
кодировке Unicode,
содержащий пары значений (ис
ходный
текст=перевод).
Компонент
TLang допускает
управление
языками
перевода не только в
период визу
ального
проектирования,
но и из_кода
программы.
Метод
procedure
AddLang(const AName: string);
позволит
добавить
новый язык
перевода в компонент.
Для этого в
параметр AName следует
добавить
аббревиатуру
нового языка. Все доступные
аббревиатуры
языков
перевода будут
доступны в
свойстве-списке
property Resources: TStrings; //только дпя чтения
Соответствующий текущему языку набор терминов находится в
свойстве
property Original:
TStrings; //только дпя чтения
Существует
способ
доступа ко
всем возможным
вариантам
перевода. Для
этого
следует в
свойство
property
LangStr[const Index: string]:
TStrings;
передать
текстовую
аббревиатуру
интересующего
нас языка. В
ответ мы
получим
доступный
только для
чтения набор
строк,
содержащий
текстовые
пары "исходное
название=перевод".
![]()
Режим автоматического перевода
Для того чтобы языковой компонент TLang приобрел способность автоматически выбирать необходимый язык для локализации приложения в соответствии с теку щими системными настройками, проверьте, чтобы свойство
property AutoSelect: Boolean; //по умолчанию true
было установлено в состояние true. Это необходимое, но недостаточное условие для того, чтобы все поддерживающие перевод компоненты немедленно откликну лись на требование языкового компонента и перевели свои названия. Для того что бы приложение "заговорило"·на заданном языке, надо вспомнить, что на уровне класса FМХ. Types. тstyledControl объявлено свойство
property
AutoTranslate: Boolean; //по умолчанию true
унаследованное внушительной rmеядой дочерних компонентов. Указанное свойст во отвечает за то, чтобы компонент автоматически отображал текст, соответст вующий системным настройкам операционной системы.
К сожалению, компонент TLang наотрез отказывался переводить текст, принадле жащий элементам меню. Почему элементы меню тмenuitem постигла такая участь, мы гадать не станем. Предположим лишь, что с развитием FireMonkey программи сты Embarcadero исправят этот недостаток, а пока предлагаем вам свой вариант перевода меню приложения. Решение не столь простое, однако вполне жизнеспо собное.
Разместите на форме меню (например, тмainМenu) и, дважды щелкнув по компонен ту, заполните его произвольными элементами. Для перевода меню приложения на альтернативный язык предлагаем вновь воспользоваться услугами компонента переводчика. Пусть этот компонент пока игнорирует элементы меню тмenurtem, но ведь с переводом у него все в порядке. Дважды щелкнем по компоненту переводчику. В раскрывшемся на вашем экране редакторе Language Designer до бавьте (используя кнопку+ в правой части редактора) несколько новых строк, впи сав в них названия пунктов меню (мenuiteml, Menuitem2 и т. д.). В строке ввода Select Language выберите язык перевода и введите в редактор соответствующий перевод для пунктов меню. Подготовительные операции завершены, теперь нам предстоит немного потрудиться за клавиатурой (листинг 24.2).
![]()
proc:edure
TForml.MenuTranslate(lang: string);
var i, j, р:
integer;
MI: TMenuitem; Str:, TStrings; Sl, S2: string;
--··-·-·..
-"-•·······
...t.'
![]()
Ьegin
Str:�Langl.LangStr[lang]; //получаем все строки с
переводом
if Str<>nil then //если есть перевод для языка lang
for i :=О to Fonnl.ComponentCount-1 do //перебор компонентов формы
Ьegin
if not (Fonnl.Components[i] is TMenuitem) then continue; MI:=TMenuitem(Fonnl.Components[i]);
end;
for j :=О to Str.Count-1 do //поиск перевода
Ьegin
p:=pos('=',Str.Strings[j]);//пoзиция символа-разделителя
Sl:=COPY(Str.Strings[j],l,p-1);
if Sl=MI.Text
then //совпала строка и имя компонента
Ьegin
MI.Text:=COPY(Str.Strings[j],P+l,Length(Str.Strings[j])-P);
break;
end; end;
end;
![]()
На'вход
функции мenuтranslate () поступает
языковая
аббревиатура
lang, иденти
фицирующая
язык перевода.
С помощью
метода LangStr () мы осуществляем
пере вод всех
строк (в том числе и
строк для элементов меню) и
помещаем перевод
в список строк Str:TStrings. Далее в дело вступает цикл for i:=0 to Fonnl. ComponentCount-1
do, в рамках
которого мы
перебираем
все
компоненты
формы с целью
поиска
элементов
меню тмenurtem.
Найдя пункт
меню, мы
запуска ем вложенный цикл, в
котором
находим
соответствие между
названием ( свойством
Name) пункта меню
и строкой перевода (напомним, что
они
разделены
символом=).
Вот собственно и
все... Вам осталось лишь вызвать функцию, и
перевод меню в
ваших руках.
ГЛАВА 25
В
самом
начале книги,
в ее
первой главе,
мы уже
посвятили
несколько
страниц во
просу
выпуска
приложения
для платформы
IOS и Android. Напомним,
что кроме
языка Delphi в процессе
создания
приложения
для iPhone и iPad
задействуются
еще
несколько
ингредиентов:
□ рабочая станция Мае с
операционной системой OS Х 10.7 Lion или 10.8 Mountain Lion;
□ мобильная ОС IOS 5.1 (и
выше);
□ поставляемый совместно
с Delphi ассистент
Platfonn Assistant (PAServer), кото
рый следует
установить
на компьютере
Мае;
□ программное обеспечение Xcode вместе с
Xcode Command Line Tools;
□ мобильное устройство iPhone или
iPad,
подключенное к
станции Мае по
USВ- кабелю.
Для отладки
приложений
для смартфонов
и планшетных
компьютеров
среда проек
тирования
активно
сотрудничает
с симулятором
iOS, отвечающим
за создание
вир
туального
устройства iPhone
и iPad. Благодаря
симулятору
(рис. 25.1)
мы с вами
сможем не только осуществлять отладку своего проекта, но и имитировать весь
![]()
Рис. 25.1. Приложения для iPhone с активным меню симулятора IOS
![]()
имеющийся в мобильной платформе функционал, например, вызвать клавиатуру, повернуть или встряхнуть iPhone, имитировать перемещение устройства по мест ности.
В
случае
подготовки
проекта Android
следует задействовать
симулятор Google,
развернутый непосредственно
на том же самом компьютере, что и Delphi ХЕ5 (рис. 25.2).
Напомним, что
для его
установки
следует запустить
приложение Android SDK Manager (Start I АН
Programs I Embarcadero RAD Studio I Android Tools) и
скачать с
сайта Google
бесплатные
драйверы и библиотеки
(см.
главу 1).
![]()
Рис. 25.2. Приложение Project1
в окне симулятора устройства Android
Изучение
компонентов,
разработанных
специально
для
мобильной
платформы, мы
начнем с
интернет-браузера
FМХ. WebBrowser. ТWebBrowser.
Это
во всех
отношениях
уникальный
элемент
управления,
способный в два счета предоставлять
пользовате
лю доступ к
интересующей его
странице в
Интернете.
Мобильная платформа =(�1!1
Для этой цели URL-aдpec страницы заносится в
одноименное свойство
property URL: string;
и вызывается ключевой метод компонента
prooedure
Navigate; overload;
Вторая перегружаемая версия метода
prooedure Navigate(const URL: string); overload;
на свойство
URL внимания не обращает,
т: к. эти сведения
передаются через одно
именный
параметр.
ЗАМЕЧАНИЕ
Кроме НТМL-страницы метод Navigate () способен загрузить и
локальный файл, в данном случае в свойстве (или параметре)
URL имени файла
должна предшество
вать
строка file: //.
С процессом
загрузки
страницы (или локального
файла) связан
ряд событий.
В пер
вую
очередь это
событие,
предваряющее
процесс
загрузки
property OnDidStartLoad: TWebBrowserDidStartLoad;
и событие
property
OnDidFinishLoad: TWebBrowserDidFinishLoad;
сигнализирующее об успешном завершении загрузки.
Допустим,
что в
качестве
строки ввода
URL-aдpeca в нашем
мобильном приложе
нии задействован редактируемый комбинированный список comЬoEdi tl: тcomЬoEdi t. В таком
случае
событие OnDidFinishLoad () можно
задействовать
для
построения списка
посещенных
сайтов
(листинг 25.1).
1111••-•t.......... ......... !
t•••• "" ,.., • • ., • ,.,,.. • • --• '""•••• •-• •-u-,, 11',11'• •-561 ••-.,--•• •••....,_.,.... , ,,..,._. "'"'"""'"""'"'...,.,,..,м,.••••..,,..,.,.,,,,,.
,.,.,.................................. ,.,.,
![]()
---,1\;,NA.••••.. •.. •hW"' ·••••••·•·><•·i -•···••••• i,i\;,>c<i",-,O,,,,;-;·•...,·,,.,,,••,,,,, ,.,,.,,,-;,•••n,,,-••••••••·••••••••·•·••••••••••••••••••'r .................... ••••t,,•••••••-''"' .....t.· •••••••••.. •·•·•·••- ...........:.
procedure TForml.WebBrowserlDidFinishLoad(ASender: TObject); var aURL, s:String;
i:integer; b:boolean;
Ьegin
b:=false;
aURL:=LowerCase(ComЬoEditl.Text,TLocaleOptions.loUserLocale);
for i:=0 to ComЬoEditl.Items.Count-1
do
Ьegin
s:=LowerCase(ComЬoEditl.Items.Strings[i],
TLocaleOptions.loUserLocale); if s=aURL then
Ьegin
b:=true;
break;
end;
end;
if Ь=false then ComЬoEditl.Items.Add(aURL);
end;
![]()
Если в моменг загрузки произошла ошибка, то в ответ компонент сгенерирует со бытие
property
OnDidFailLoadWithError: ТWebBrowserDidFailLoadWithError;
Ошибка может быть связана не столько с неверным URL, сколько со сбоями в вы полнении сценариев на сайте, отсутствием файлов по ссьmкам и т. п.
Для быстрого перемещения по недавно посещенным страницам Всемирной паути ны следует обратиться к методам
prooedure
GoBack; //вернуться назад
prooedure GoForward; //перейти вперед
Прежде чем обратиться к перечисленным методам, целесообразно проверить воз можность перемещения с помощью свойств
property CanGoBack: Boolean;
property CanGoForward: Boolean;
Многие современные мобильные приложения нуждаются в точном определении географических координат устройства, на котором они выполняются. Благодаря этому пользователь смартфона или планшетного компьютера получает дополни тельный уровень услуг, в первую очередь связанный с привязкой приложения к ме стности, например, смартфон может рассказать о ближайших кинотеатрах, музеях или остановках общественного транспорта.
У вашего мобильного .устро' йства имеется несколько источников получения сведе-
ний о своем местоположении. В идеальном случае это спутниковая система гло бального позиционирования (Global Positioning System, GPS), дающая весь·ма точ ный результат - при максимальном числе доступных спутников погрешность оп ределения координат сегодня составляет немногим более 2-3 метров! Однако спутниковые системы не всесильны, например они окажутся бесполезны, когда мо бильное устройство находится в железобетонных сооружениях, в метрополитене, в условиях очень плохой погоды и также в ситуации зашумления. При отсутствии GPS мобильное устройство способно извлечь сведения о своих координатах из со товой сети или сетей Wi-Fi, однако в последнем случае о высокой точности геоло кации говорить не приходится, погрешность может составлять сотни метров.
Датчик местоположения TLocationSensor
В модуле FМХ. sensors, хранящем исходный код классов-сенсоров, описан датчик местоположения - класс TLocationSensor (страница Sensors палитры компонен тов), способный возвратить в написанную на Delphi программу все необходимые сведения о расположении мобильного устройства.
Для активации датчика устанавливаем свойство
property Active: Boolean;
в состояние true.
![]()
Ключевое
событие компонента
property OnLocationChanged: TLocationChangedEvent;
TLocationChangedEvent = prooedure (�ender: TObject;
const OldLocation, NewLocation: TLocationCoord2D) of object;
генерируется
в момент определения
координат
устройства.
Параметры Oldtocat:Lon и NewLocation соответственно возвращают старую и
новую широту и
долготу в
формате
записи
TLocationCoord2D = record
Latitude: TLocationDegrees; //широта
Longitude: TLocationDegrees; //долгота
// ...
end;
Мудреное
название
типа данных TLocationDegrees на самом
деле
соответствует
обычному вещественному
типу данных с
двойной
точностью DouЫe. Целая
часть
значения
соответствует
градусам, а дробная - долям
градуса.
Сторонникам
минимализма
уже пора засучить рукава и
написать
приложение,
по зволяющее
пользователю
увидеть на
карте свое
местоположение. Для
возложения задуманного нам
понадобится скромный набор
компонентов:
□ сенсор TLocationSensor;
□ метка
TLabel;
□ интернет-браузер TWebBrowser.
Создайте новое мобильное приложение, а
все остальное вы найдете в листинге 25.2.
![]()
prooedure TForml.FoпnShow(Sender: TObject);
Ьeqin
LocationSensorl.Active:=True;
end;
prooedur•
TForml.LocationSensorlLocationChanged(Sender: TObject;
const OldLocation, NewLocation: TLocationCoord2D);
const ТХТ: String
='Широта
%s Долгота %s';
URL: String
= 'https://rnaps.google.com/rnaps?q=%s,%s&output=emЬed';
Ьegin
Labell.Text := Format(TXT,
[NewLocation.Latitude.ToString(),
NewLocation.Longitude.ToString()]);
WebBrowserl.Navigate(Format(URL, [NewLocation.Latitude.ToString,
NewLocation.Longitude.ToString]));
end;
Сенсор активизируется при выводе формы приложения на экран мобильного
устройства. Определение координат осуществляется в
момент генерации события
![]()
OnLocationChanged (). Для того чтобы пользователь увидел свое местонахождение на карте, мы воспользовались популярным сервисом Google Карты. Для этого доста точно правильно отформатировать управляющую строку
https://maps.google.com/maps?q=<широта>,<дoлгoma>&output=embed
передав в параметры <широта> и <долгота> полученные от сенсора географиче ские координаты. Действо завершает вызов метода интернет-браузера Navigate (), отправляющего в адрес Google· наши координаты. Результат представлен на рис. 25.3 - сервис Google возвратил в браузер карту местности.
![]()
Рис. 25.3. Отображение карты местности по
заданным географическим координатам
Прямое и
обратное преобразования
координат TGeocoder
![]()
Подавляющее
большинство пользователей, увидев
на экране
мобильного
устройст ва
свои координаты,
в лучшем
случае
примет их к сведению,
в худшем - переста
нет
пользоваться
нашими
программами.
Спросите
почему?
Полагаем, не
сразу от
ветите, какой объект скрывается за географическими координатами 59°46'18.52" с. ш. 30°19'33.77" в. д.
Впрочем,
сильно не
расстраивайтесь,
тем более,
далеко не
каждый
географ
помнит, что здесь
расположена
главная
(Пулковская)
астрономи
ческая
обсерватория
Российской академии
наук. Сухие
цифры
координат
важны для
специалиста,
но
малоинтересны
для обывателя!
Последнему
гораздо
удобнее
получить от смартфона
сообщение,
что он стоит
на углу
Невского
проспекта и ули цы
Марата в двух
шагах от
станции
метро
Маяковская,
чем
географические
коор динаты,
пусть даже с
точностью до
секунды.
Платформа
FireMonkey дает нам в
руки
множество
конкурентных
преимуществ,
ими только
надо уметь
воспользоваться.
Одно из них - возможность
перевода ад
реса в
географические
координаты и
решение
обратной
задачи - декодирования
географических координат в
более доброжелательный формат почтового адреса.
![]()
![]()
Специалистом в этой области высrупает класс TGeocoder, который вы обнаружите
в модуле System. Sensors.
ВНИМАНИЕ!
Класс
TGeocoder выступает
объектно-ориентированной
оболочкой
для сетевого
сер виса кодирования/декодирования
географических координат
и адресных
данных (схо жих с
почтовым
адресом).
Поэтому для
корректной
работы
класса
необходим
доступ приложения
во Всемирную
паутину.
Конкретно
для мобильных
устройств,
работающих
под управлением
IOS и Android, обычно
задействуется
сервис геокодирования Google.
Класс
TGeocoder оперирует
двумя
разновидностями
данных.
С одной
стороны, его интересуют географические координаты,
представленные в уже встречавшемся
нам формате
TLocationCoord2D и
используемом
для хранения
широты и долготы.
С дру гой сторонь1, для обслуживания почтового адреса класс задействует более "продви
нутый" форм.ат данных, описанный в
классе TCivicAddress (табл. 25.1).
Таблица 25.1. Описание почтовых координат и
адреса в классе тcivicAddress
|
Свойство |
Описание |
|
property Coord: TLocationCoord2D; |
Географические координаты |
|
property Address: String; |
Адрес |
|
property CountryCode: String; |
- Код страны |
|
property CountryName: String; |
Название страны |
|
property FeatureName: String; |
Подробное название |
|
property AdminArea: String; |
Административно-территориальная единица,
для США - название
штата |
|
property SuЬAdminArea: String; |
Уточнение административно-территориальной
единицы, для
США - округ |
|
property Locale: String; |
Город (административный центр) |
|
property Locality: String; |
Район города |
|
property SuЬLocality: String; |
Дополнительное описание месторасположения,
например
квартал |
|
property Phone: String; |
Телефон |
|
property PostalCode: String; |
Почтовый код |
|
property Premises: String; |
Помещение . |
|
property Thoroughfare: String; |
Главная улица |
|
property SuЬThoroughfare: String; |
Дополнительное описание улицы |
|
property URL: String; |
Адрес интернет-ресурса в
формате URL |
Запрос на преобразование
координат может
быть осуществлен
при посредничестве
методов
class prooedure
Geocode(const Address:
TCivicAddress);
class prooedure
GeocodeReverse(const Coords:
TLocationCoord2D);
![]()
Метод Geocode ( ) осуществляет прямое преобразование, отправляя сетевому сервису
интересующий нас адрес Address, а обратный метод GeocodeReverse () передает
в сеть
географические
координаты Coords.
Получив запрос на преобразование координат, сетевой сервис выполняет задание и возвращает результаты в приложение. Если наш класс задействуется для прямого преобразования адреса в географические координаты, то в момент получения от сетевого сервиса запрашиваемых координат генерируется событие
class property OnGeocode: TGeocodeEvent;
Указанное событие возвратит массив координат с широтой и долготой
TGeocodeEvent = procedw:e(const Coords: TArray<TLocationCoord2D>) of oЬject;
При решении обратной задачи трансляции широты и долготы в адрес генерируется другое событие
class property OnGeocodeReverse: TGeocodeReverseEvent;
Событие обладает единственным параметром
TGeocodeReverseEvent = procedure (const Address: TCivicAddress) of oЬject;
уже хорошо знакомым нам по табл. 25.1.
ЗАМЕЧАНИЕ
Надо
понимать, что
прямой и
обратный
процессы
преобразования
координат доста
точно
ресурсоемки,
поэтому
компания Google ввела
ограничение
на число
запросов
геопозиционирования.
На момент
написания
этих строк
обычный
пользователь
имел лимит в
2500 запросов в
день, организация - 100 ООО
запросов в день. Отметим одну
важную
деталь - перевод
адреса в координаты
или координат
в адрес
не всегда
воз можны,
ведь в базе
данных Google
могут просто
отсутствовать
сведения об
интере сующем
вас районе
города или
поселка.
К сожалению, создав класс TGeocoder, разработчики FireMonkey не позаботились о реализации на его основе компонента, который мы смогли бы просто перенести с палитры компонентов на форму своего проекта для iPhone или iPad. Посему, для того чтобы задействовать умения класса-геокодировщика, нам придется написать несколько строк кода.
Допустим, что в нашем приложении мы планируем осуществить преобразование географических координат в адрес. В таком случае програ·ммисты Embarcadero ре комендуют в секции частных определений формы объявить объектную перемен ную TGeocoder и событие обратного преобразования OnGeocodeReverse (лис тинг 25.3).
![]()
type
TForml class (TForm)
/ / ...
private
![]()
procedure TForml,OnGeocodeReverseEvent(const-Address: TCivicAddress);
Ьegin
Labell.Text Label2.Text
// ...
Labell0.
Text
end;
:= Address.AdminArea;
:= Address.CountryCode;
:= Address.SuЬThoroughfare;
![]()
Теперь
нам
предстоит
создать
объект преобразования
координат. На
этом этапе
программирования в
первую очередь следует уточнить сам факт наличия доступа
к сервису
геокодирования,
без которого
все наши
попытки по
преобразованию
координат
окажутся
тщетными. Для
этого
предназначено
свойство
class property
Current: TGeocoderClass;
Убедившись
(например, с
помощью
функции Assigned) в наличии
сервиса, смело
вызываем
конструктор
объекта.
Разработчики
класса
предлагают
для создания
эк земпляра TGeocoder задействовать
событие OnLocationChanged () компонента
сенсора TLocationSensor, как вы
помните,
генерируемого
в момент получения
гео
графических
координат мобильного
устройства
(листинг 25.5).
'
procedure TForml.LocationSensorlLocationChanged(Sender: TObject;
const OldLocation, NewLocation: TLocationCoord2D);
Ьegin
// ...
// код листинга 25.2
// ...
if Assigned(fGeocoder)=false then Ьegin
if Asfiigned(TGeocoder.Current) then
fGeocoder
:= TGeocoder.Current.Create;
if Assigned(fGeocoder) then
fGeocoder.OnGeocodeReverse := OnGeocodeReverseEvent;
end;
![]()
![]()
if Assigned(fGeocoder) and fGeocoder.Geocoding=false then
fGeocoder.GeocodeReverse(NewLocation);
end;
В рамках примера из
листинга 25.5 мы создаем объект преобразования коорди
нат и связываем
его с
описанным
ранее (см. листинг 25.4)
событием OnGeocodeReverseEvent () . Обратите
внимание на
то, что мы
стараемся не
перегру жать
сетевой
сервис
излишними запросами,
в частности
для этого
задействуется
функция
class function Geocoding:
Boolean;
возвращающая значение true, если приложением уже бьm сформирован и отправ лен запрос на декодирование координат, и оно в данный момент находится в со стоянии ожидания ответа.
Датчик ориентирования TOrientationSensor
Современные
мобильные
устройства
буквально
напичканы
разнообразными датчи
ками. В
частности,
многие
смартфоны и
планшетные
компьютеры
способны пре
доставить
пользователю
сведения
о наклоне
устройства
относительно
земли, прой денном расстоянии
и даже
работать как
компас. Для доступа
к перечисленным
сер висам в
состав FireMonkey
включен
компонент TOrientationSensor, его
вы обнаружите
на странице Sensors палитры
компонентов.
Исходный код
класса рас
средоточен по двум
программным модулям:
Systern. Sensors и FМХ. Sensors.
Для активации компонента задействуется метод
prooedure Start;
Если сенсор бьm успешно запущен, то об этом просигнализирует свойство
property
Started: Boolean;
//только дпя чтения
Выключение прибора обеспечит метод prooedure
Stop;
Существует несколько типов сенсоров: компас (compass), сенсор наклона (inclinometer) и сенсор расстояния (distance). Об имеющихся в наличии сенсорах (все определяется классом смартфона или планшетного компьютера) можно судить по свойству
property
SensorType: TOrientationSensorType; //только дпя чтения
TOrientationSensorType = (CornpasslD, Cornpass2D, CornpassЗD, InclinorneterlD, Inclinorneter2D, InclinorneterЗD, DistancelD, Distance2D, DistanceЗD);
В зависимости от типа сенсора мы сможем получать ту или иную информацию об ориентации устройства (табл. 25.2).
![]()
|
Сенсор |
Свойство |
Описание |
|
Сенсор
наклона |
property TiltX:
DouЫe; property TiltY: DouЫe; property TiltZ: DouЬle; |
Наклон мобильного устройства в градусах относительно
осей |
|
Сенсор
расстояния |
property DistanceX: DouЫe; property DistanceY: DouЬle; 1 property DistanceZ: DouЬle; |
Пройденное
относительно
точки
предыдущего измерения расстоя-
ние в метрах |
|
property Updateinterval: DouЬle; |
Как
часто
обновляются
данные о движении |
|
|
Компас |
property HeadingX: DouЬle; property HeadingY: DouЬle;
property HeadingZ: DouЬle; |
Направление на север в
градусах |
Если
требования к
точности
определения
направления
на север
высоки, то
из-за изменяющегося
магнитного
склонения
(напомним, что это угол между
географиче
ским и
магнитным
меридианами
в точке
земной
поверхности)
приходится
учиты вать
ряд поправок.
Компенсированный угол
на магнитный
и
географический север
возвратят
соответственно
свойства
property CompMagHeading: DouЬle; property CompTrueHeading: DouЫe;
Некомпенсированные значения окажутся в свойствах
property MagHeading: DouЬle;
property TrueHeading: DouЬle;
При
проектировании
приложения с
использованием
компонента тorientationSensor надо
учитывать
тот факт, что
нам заранее
неизвестен
состав
датчиков на
мобиль ном
устройстве.
По этой
причине для
доступа к свойствам
соответствующего
сен сора
может осуществляться
при
посредничестве
свойства
property AvailaЫeProperties: TProperties; //только для чтения
Проверка факта существования того или иного сенсора выполняется в рамках со бьпия
property OnSensorChoosing: TOnSensorChoosing; TOnSensorChoosing = prooedure
(Sepder: TObject;
const Sensors:
TSensorArray; va ChoseSensorindex: Integer) of object;
Параметр
Sensors возвращает
массив
доступных
сенсоров, а с помощью
ChoseSensorindex мы можем выбирать индекс интересующего нас устройства. По рядок
работы с
событием
раскрывает
листинг 25.6.
Здесь
продемонстрирован
спо соб поиска
в мобильном
устройстве
встроенного
компаса. С
этой целью мы
прове ряем
факт
вхождения
свойства HeadingX в
перечень
свойств AvailaЫeProperties к_аждого
из датчиков.
![]()
![]()
![]()
procedure TForml.OrientationSensorlSensorChoosing(Sender: TObject;
const Sensors: TSensorArray; var ChoseSensorindex: Integer);
var i: Integer;
Ьegin
ChoseSensorindex:= -1;
for i := О to High(Sensors) do
if (TCustomOrientationSensor.TProperty.HeadingX in
TCustomOrientationSensor(Sensor.s[I])
.AvailaЫeProperties) then Ьegin
ChoseSensorindex:= i;
Break; end;
if ChoseSensorindex < О then ShowMessage('Koмпac отсутствует!');
end;
ЗАМЕЧАНИЕ
К сожалению, в современных симуляторах
IOS эмуляция
сенсора
ориентирования по
ка отсутствует.
Поэтому проверка
работоспособности нашего проекта
может осущест
вляться только на реальном устройстве.
В момент получения сведений от датчиков у
сенсора генерируется событие
property OnDataChanged: TNotifyEvent;
Порядок работы с
событием рассмотрен в
листинге 25.7.
![]()
procedure
TForml.OrientationSensorlDataChanged(Sender: TObject);
Ьegin
Labell.Text := Format
( 'Компас (Х, У, Z): (%.2f,
%.2f, %.2f) ', [OrientationSensorl.Sensor.HeadingX, OrientationSensorl.Sensor.HeadingY, OrientationSensorl.Sensor.HeadingZ]);
Менеджер датчиков TSensorManager
Рассмотренные
ранее
датчики
местоположения TLocationSensor и ориентирования
тorientationSensor представлены
на палитре
компонентов
FireMonkey и включают
ся в приложение
э-лементарным
переносом компонента
на форму мобильного про
екта. Однако
работоспособность современных
мобильных
устройств
обеспечивает
более существенный
перечень
датчиков,
сканеров и
сенсоров. Это подтверждает
специальный тип данных тsensorcategory, который
классифицирует существующие
датчики
TSensorCategory = (Location,
Environmental, Motion, Orientation,
Mechanical, Electrical, Biometric, Light, Scanner);
//датчик расположения
//датчик данных окружающей среды
//датчик движения
//датчик ориентации в
пространстве
//механический датчик
//электрический датчик
//биометрический датчик
//датчик освещения
//сканер
![]()
Как
быть с ними?
Ответ на
заданный
вопрос можно
получить,
изучив
определен
ный в модуле System. sensors
менеджер
датчиков тsensorManager. Этот
абстрактный
класс
позволяет
идентифицировать
имеющиеся в
мобильном
устройстве
датчики и
передать
получаемые
от них сведения
в
распоряжение
программиста.
ЗАМЕЧАНИЕ
Для работы
с датчиками·в
состав
модуля System.Sensors включен
ряд
специализи
рованных
классов, представляющих собой
программную
оболочку для разнообразных
устройств.
Описание
большинства
из них
представлено в приложении 4.
Для подключения к
менеджеру датчиков следует воспользоваться свойством
class property Current: TSensorManager; //только для чтения
В
результате
мы получим
доступ к
функционирующему
в мобильном
устройстве
программному
объекту,
способному
решить все
базовые
задачи по
управлению сенсорами.
Прежде
чем
воспользоваться услугами
сенсора,
обязательно
убедитесь в
его актив
ности с
помощью
свойства
property Active: Boolean;
Если менеджер отключен - проверяем возможность включения
property
CanActivate: Boolean; //�олько для чтения
и передаем в свойство Active значение true или вызываем
метод
prooedure
Activate;
Получив
доступ к
менеджеру, мы
приобретаем
возможность
взаимодействовать
со всеми
доступными
датчиками
смартфона
или планшетного
компьютера.
Для этого
достаточно
направить
индекс
интересующего
вас устройства
в свойство
property Sensors[Index: Integer]: TCustomSensor; �/только для чтения
Общее количество
датчиков хранится в
свойстве
property Count: Integer;
Если
нас
интересует
сенсор
определенной
категории, то
целесообразно
воспользо
ваться
функциями,
сужающими область
поиска. В
первую
очередь это
мет д
function GetSensorsByCategory(Category: TSensorCategory): TSensorArray;
возвращающий массив сенсоров заранее определенной
категории, и
метод
function GetSensorsByFilter(Filter: TSensorFilter): TSensorArray;
![]()
![]()
позволяющий определить более сложные критерии отбора. Правда, в последнем случае придется потрудиться побольше и самостоятельно написать функцию фильтр
TSensorFilter = referenae
to function (Sensor: TCustomSensor): Boolean;
отбирающую требуемые датчики.
Работа с менеджером сенсоров продемонстрирована в листинге 25.8, отражающем порядок обращения к сенсору мобильного устройства заданной категории.
![]()
var SA: TSensorArray; SM: TSensorManager;
/ / ...
proaedure TForml.FormCreate(Sender: �Object);
Ьegin
SM := TSensorManager.Current;
if SM.CanActivate then SM.Activate
Ьegin
SM.Activate;
SA := TSensorManager.Current.GetSensorsByCategory(
TSensorCategory.Electrical); if (Length(SA)>0) and (SA[0] is TCustomElectricalSensor) then Ьegin
if not TCustomElectricalSensor(SA[0J) .Started then Ьegin
TCustomElectricalSensor(SA[0]) .Start;
//дальнейшая работа с
датчиком
end;
end;
end;
end;
Обратите внимание, что переменная SA ( определяющая динамический массив сен соров тsensorArray) и объектная переменная sм: тsensorManager объявлены гло бально. Такой подход позволит обращаться к менеджеру датчиков и конкретному датчику (или датчикам) из любого места приложения.
Увеличительное стекло TMagnifierGlass
Из-за физического
ограничения
размера
экрана мобильных
устройств
пользователь
часто сталкивается
с проблемой
чтения
текста,
набранного
шрифтом с маленьким
кеглем, и просмотра
небольших
изображений.
Сложностей
добавляет
тот факт,
что
владельцем
мобильного
устройства
может стать
человек с
неважным
зрением. Одним из способов решения этой проблемы может стать использование компо-
![]()
нента тмagnifierGlass, предназначенного исполнять роль увеличительного
стекла (рис. 25.4).
Степень увеличения настраивается с
помощью свойства
property LoupeScale: Single;
Центр увеличения устанавливается свойством
property ZoomRegionCenter: TPosition;
По умолчанию он расположен в
точке (О, О).
Для управления
фоновым цветом следует воспользоваться свойством
property BackgroundColor: TAlphaColor;//пo умолчанию белый
Стекло может принимать традиционную круглую или прямоугольную
форму
property LoupeMode: TLoupeMode; default TLoupeMode.lmCircle;
TLoupeMode = (lmCircle, lmRectangle);
![]()
![]()
Рис. 25.4. Компонент TMagnifierGlass
ЗАМЕЧАНИЕ
В
простейшем
случае можно
не
заботиться о написании
кода,
отвечающего
за пере
мещение
увеличительного стекла,
т. к. он
заложен.в
компонент.
Пользователю
доста точно
просто
двигать
стекло по
экрану,
удерживая край
компонента.
Для
организации
взаимодействия
между пользователем
и
интеллектуальным
уст ройством
любая
операционная
система
должна не
только с
покорностью
воспри
нимать указания
человека, но и
обладать
возможностью
по организации
канала об
ратной связи,
по которому
ОС сможет
информировать
своего
владельца о
событи ях, возникающих в
системе. В
устройствах на основе _iOS обратная связь между
![]()
работающим в фоновом режиме приложением и пользователем строится на основе центра уведомлений (notification center). Центр уведомлений способен "достучать ся" ДО владельца мобильного устройства по крайней мере тремя способами:
□ выводом на экран сообщения (рис. 25.5);
□ отображением информационного баннера (для планшета);
□ размещением на значке приложения дополнительной картинки с цифрой (на пример, информирующей о количестве пропущенных входящих телефонных звонков).
ЗАМЕЧАНИЕ
Планшетный
компьютер позволяет
своему
владельцу
выбирать предпочтительный
способ
вывода
уведомления.
Немного
покопавшись
в настройках
iPad, пользователь
сможет
установить
режим отображения
в форме
сообщения
либо баннера.
Для на стройки
параметров центра
уведомлений вашего
iPad следует
воспользоваться
меню
Settings I Notifications.
![]()
Вывод на экран сообщения Число на значке приложения
Рис. 25.5. Примеры уведомлений в IOS
Как и положено, для объектно-ориентированного языка программирования уве домление представлено в виде самостоятельного объекта, построенного на основе класса TNotification. Собственно класс TNotification является едва ли не самым элементарным из всех встречавшихся нам на этих страницах книги (листинг 25.9).
-----�-=,--к---
r-····· ....-,,..,,.,., . •·...
\ Allenlиr 25.9. ОбъЯВJ11еиме кnнса Dlot!r
i ••-•
TNotification = class puЬlic
Name: string; AlertBody: string;
//уникальный идентификатор уведомления
//текстовое содержание уведомления
AlertAction: string; //текст альтернативного действия_
ApplicationiconBadgeNurnЬer: Integer; //число на значке приложения
![]()
FireDate: TDateTime; EnaЬleSound: Boolean;
HasAction:
Boolean;
end;
//дата-время вывода уведомления
//наличие звукового сопровождения
//наличие кнопки альтернативного действия
![]()
Отдельных
комментариев
достойно
текстовое
поле Name.
Оно
должно
содержать
уникальное
текстовое
значение,
однозначно
идентифицирующее
уведомление.
Позднее
данный
идентификатор
потребуется
приложению
для отказа
(сброса) от уведомления.
Для отправки
уведомления
нам понадобится
доступ к функционалу
центра
уведом
лений, это
вотчина
интерфейса IFМXNotificationCenter, объявление
которого вы
обнаружите в
модуле FМX.Notification. Интерфейс весьма аскетичен, в его
арсена ле
меньше
десятка
методов. Наиболее
востребованы
методы
procedure PresentNotification(const ANotification: TNotification);
и
procedure
ScheduleNotification(const ANotification: TNotification);
Это два·специалиста
по отправке
уведомлений.
Разница
между ними
только в том,
что PresentNotification() выводит уведомление немедленно (невзирая на состоя
ние поля FireDate), а метод ScheduleNotification(), наоборот,
является
большим
педантом и отобразит
сообщение
(или баннер) в
точно назначенный
срок.
Пара методов нацелена на сброс уведомлений. Наиболее кардинален метод
pro�ure.CancelAllNotifications;
он отменит
все
запланированные в
системе
уведомления.
Для
избирательного отка
за от
уведомления
лучше
задействовать
перегружаемый метод
procedure
CancelNotification(const AName: string); overload;
procedure
CancelNotification(const ANotification: TNotification); overload;
Здесь
нам
потребуется
либо
текстовый
идентификатор
уведомления,
либо ссылка
на объект
сбрасываемого
уведомления.
Для
работы с
дополнительными
картинками с
числовыми
значе·ниями,
размещае
мыми над
значком
приложения,
вызывайте
процедуры:
procedure SeticonBadgeNurnЬer(const ACount: Integer);
procedure ReseticonBadgeNurnЬer;
Первая процедура устанавливает цифровое значение, вторая - сбрасывает.
Мы уже
почти готовы
работать с
центром
уведомлений,
осталось
лишь уяснить
два нюанса.
Во-первых,
центр
уведомлений
может быть
отключен
пользователем,
по этому
наше
приложение
стоит научить
проверять
его
состояние.
Во-вторых, надо
разобраться с
вопросом получения заветного интерфейса IFМXNoti_ficationcenter. И в
первом, и во втором
случае нашим
помощником
станет класс TPlatformServices, описанный
в модуле FМX.Platform.
![]()
![]()
Как
вы уже
поняли, класс
TPlatfoпnServices является
специалистом
по взаимодей
ствию с
различного
рода
функционирующими
в операционной
системе
сервисами, в
том числе и
со службой
уведомлений.
Несмотря
на наличие у TPlatfoпnServices самостоятельного
конструктора,
не стоит создавать
экземпляр
класса с
помощью
традиционного
вызова create ().
Вместо
этого
следует
обратиться к
свойству
class property
Current: TPlatfoпnServices;
выступающему интеллектуальной надстройкой над конструктором.
Получив
в свои руки
объект TPlatfoпnServices, мы
приобретаем
возможность
по просить
его уточнить
наличие в
системе
интересующего
нас
интерфейса.
Для этих
целей надо
лишь отправить
уникальный
глобальный
идентификатор
GUID интер фейса
в метод
function Supp?rtsPlatformService(const AServiceGUID: TGUID): Boolean;
Получив
в ответ
значение true, свидетельствующее
об
активности
службы, запра
шиваем ее
интерфейс, на этот
раз при
посредничестве
метода
function GetPlatfoпnService(const AServiceGUID: TGUID): IInterface;
Как
правило,
методы SupportsPlatfoпnService () и GetPlatfoпnService () должны вызываться совместно, порядок обращения
к интерфейсу
центра уведомлений от
ражен в
листинге 25.10.
![]()
uses FМX.Platform, FМX.Notification;
function GetNotificationCenter:IFМXNotificationCenter;
Ьegin
if TPlatfoпnServices.Current.SupportsPlatfoпnService
(IFМXNotificationCenter)
then
Result:= TPlatfoпnServices.Current.GetPlatformService
(IFМXNotificationCenter) as IFМXNotificationCenter
else Result:=nil;
end;
Пока не
забывайте о
функции GetNotificationCenter (), она
еще не раз·пригодится
нам в
этой главе. 1
Пример вывода текстового уведомления
в
назначенное
время
Исполняющееся
в фоновом
режиме
приложение
имеет полное
право
отправить
пользователю
текстовое
уведомление.
В простейшем случае
нам
достаточно
ука зать
идентификатор,
текст и время
вывода
уведомления
(листинг 25.11).
procedure ScheduleNotification(const NotificationID,
NotificationTxt: string;
ADateTime:
TDateTime);
var Notification: TNotification; NC: IFМXNotificationCenter;
Ьegin
NC := GetNotifгeationCenter;
if Assigned(NC) then Ьegin
Notification:=TNotification.Create;
Notification.Name := NotificationID; Notification.AlertBody := NotificationTxt; Notification.FireDate := ADateTime; NC.ScheduleNotification(Notification); Notification.Free;
end;
..... !..
![]()
-- --,, {
end;
Если
по
какой-то
причине от
вывода
уведомления на
экран можно отказаться,
то предлагаем воспользоваться
исходным кодом, представленным
в листинге 25.12.
![]()
![]()
Prooedure ResetNotification(const NotificationID:string);
var NC: IFМXNotificationCenter;
Ьegin
NC := GetNotificationCenter;
if Assigned(NC) then NC.CancelNotification(NotificationIP);
end;
На вход процедуры ResetNotification() следует передать единственный аргумент с идентификатором
уведомления.
Вызов приложения из окна уведомления
Не исключено,
что
получив
уведомление,
пользователь
не просто
примет его к све
дению, а захочет
перейти к
приложению,
послужившему
источником этого
уведомления (листинг 25.13).
В таком случае нам понадобится помощь полей AlertAction
и HasAction класса TNotification.
![]()
NC := GetNotificationCenter;
if Assigned(NC) then Ьegin
Notification:=TNotification.Create;
![]()
Notification.Name := 'Notification_id'; Notification.AlertBody := 'Выбор действия';
Notification.AlertAction:='B
приложение'; Notification.HasAction:=true;
NC. PresentNotification.(Notification); Notification.Free;
end;
Воспользовавшись кодом из листинга 25.13, вы
получите
сообщение с
двумя кноп
ками (рис. 25.6).
Щелчок
по кнопке В
приложение развернет
на экране
приложение,
до сих пор ра
ботавшее в
фоновом
режиме.
![]()
Рис. 25.6. Уведомление, позволяющее перейти к приложению
Пример размещения числа на значке приложения
Как уведомить пользователя, что он
пропустил 1 О входящих звонков или получил
20 текстовых
сообщений? Очень
просто - разместить
над значком
приложения
числовое значение. В
этом нам помогут листинги 25 .14
и 25 .15.
!Jhмmuн' •. , •.11с:
-\'1 •-v•
procedure SetBadgeNurnЬer(Value: integer);
var NC: IEМXNotificationCenter;
Ьegin
NC := GetNotificationCenter;
if Assign�d(NC) then NC.SeticonBadgeNurnЬer(Value);
end;
![]()
1,.... •·•·····..·······•·.. ···•··..·••······..•·•·•··.,...,... ............,... .•........•,.•• ........,,..,1,•·······
8.15. С1рО8 'IМCtlOIIDrO ----
...... •................... ,.
- ........,1 - ........ \, w....1.. ,..
procedure ResetBadgeNurnЬer;
var NC: IEМXNotificationCenter;
Ьegin
NC := GetNotificationCenter;
if Assigned(NC) then NC.ReseticonBadgeNurnЬer;
end;
![]()
мооильная платформа
Процедура setBadgeNшnЬer () позволяет отправить в центр уведомления число, которое мы планируем разместить на значке приложения, а процедура ResetBadgeNшnЬer () осуществляет сброс предыдущего значения.
Завершая
главу,
посвященную
исключительно
мобильной платформе,
не ri,e вспомнить
об основном
предназначении
телефона - осуществление
телефонных
звонков. И в этом
случае FireMonkey
вновь
оказывается
на высоте,
предоставляя
разработчику
искmочительно
простой и
удобный API,
позволяющий
достичь две цели:
□
получить все необходимые сведения об операторе связи;
□
совершить телефонный звонок.
Обе задачи
решаются с помощью
описанного в модуле
FМХ. PhoneDialer интерфейса
IFМXPhoneDialerService. Доступ
к интерфейсу
осуществляется
при посредничестве
уже
встречавшегося в этой
главе класса TPlatformServices (листинг 25.16).
![]()
var PDService: IFМXPhoneDialerService;
Ьegin
if TPlatformServices.Current.SupportsPlatformService( IFМXPhoneDialerService, IInterface(PDService)) then
Ьegin
//работа с
интерфейсом IFМXPhoneDialerService
end else raise Exception.Create('Heт доступа к
IFМXPhoneDialerService! ');
end;
Для получения подробных сведений об операторе связи, к услугам которого в на стоящее время подключен смартфон, следует обратиться к методу
function GetCarrier: TCarrier;
интерфейса IFМXPhoneDialerService. В результате мы получим доступ к абстракт ному классу тcarrier (табл. 25.3).
Таблица
25.3. Возможности класса тcarrier
|
Свойство |
Описание |
|
function GetisoCountryCode: string; ' |
Кодировка
страны
в соответствии
со стандартом
ISO 3166-1. Бывает
трех видов:
2-символьная, 3-символьная
и числовая.
Например,
для России:
"RU", "RUS" и
643 |
|
function GetMobileCountryCode: strinq; |
Код страны мес (moЬile country code). Например,
для России
код МСС=250 |
![]()
Таблица 25.3 (окончание)
|
Свойство |
Описание |
|
function GetMobileNetwork: string; . |
Код
оператора MNC (moblle
network code). Например,
для операторов
России: МТС MNC=01,
Мегафон MNC=02,
Билайн MNC=28 и 99 |
|
function GetCarrierName: string; |
Несущая (частота), используемая для связи |
|
property OnCarrierChanged: TOnCarrierChanged; |
Определяет,
что событие генерируется
в момент
смены
оператора |
Для того чтобы пользователь смог сделать телефонный звонок, достаточно пере дать номер телефона в метод
function Call(const APhoneNurnЬer: string): Boolean;
Для получения сведений о входящих звонках следует обратиться к методу
function GetCurrentCalls: TCalls;
Метод возвращает массив, элементами которого выступают отдельные звонки, описанные в рамках абстрактного класса тcall (листинг 25.17).
![]()
TCall
puЫic
class aЬstract
function
GetCallState: TCallState; virtual; aЬstract;
function GetCallID:
string; virtual;
aЬstract; end;
Благ щаря тcall мы можем выяснить состояние звонка и номер телефона абонента, звонившего нам.
![]()
![]()
![]()
ГЛАВА 26
Первые страницы темы о 3D-проектах посвятим не программированию, а геомет рии. Конечно же, мы не станем доказывать теоремы, но и без их упоминания не обойдемся, так что если вы в школе не очень дружили с точными науками - сове туем проявить совсем немного терпения и познакомиться с основными законами трехмерного мира FireMonkey.
Человек всегда бьm склонен к систематизации, поэтому уже очень давно были раз работаны трехмерные системы координат. Таких систем несколько, и самая рас пространенная из них представлена на рис. 26.1. Это так называемая прямоугольная декартова система координат. Как и полагается для 3D-системы, классическая пря моугольная система координат содержит три ортогональных (перпендикулярных) координатных оси:
□
ось абсцисс х идет слева направо;
□
ось ординат у уходит вверх;
□ если
ось аппликат z направлена
из точки
начала
координат (О,
О, О)
навстречу
наблюдателю
(рис. 26.1, а), то
это правая
система
координат,
иначе речь
идет о левой
(рис. 26.1, б) системе.
![]()
![]()
х
Рис. 26.1. Прямоугольная декартова система координат: а - правая, б - левая
Предложенная на рис. 26.1 система неоднократно встречалась нам в школьных и университетских учебниках, и поэтому сразу приходит на ум, когда мы вспоминаем о трех измерениях. Однако в Delphi так полюбившаяся нам по школе система при меняется в слегка модифицированном виде. И это не потому, что инженеры программисты из Embarcadero хотят отучить нас от шаблонного мышления (хотя это было бы неплохим объяснением). Все дело заключается в традициях двухмер ных графических систем.
Еще со времен становления первых устройств видеовывода (еще в те времена, ко гда не существовало ни Microsoft Windows, ни OS Х) инженеров-разработчиков электроники и программистов устраивал следующий подход:
□ начала двухмерных
координат находятся в
левом верхнем углу экрана;
□ ось х традиционно направлена слева направо;
□ ось у устремлена вниз экрана.
Почему бьmо принято столь странное на первый взгляд решение? Потому что эта система целиком и полностью соответствовала европейскому стилю чтения книг слева направо сверху вниз. Более того, именно так (слева направо сверху вниз) пробегал по экрану кинескопа "древних'' мониторов электронный луч, рисуя на нем символы первых программ. Одним словом, так 'получилось... Позднее текстовый режим видеовывода вошел в норму, и любой работающий в современных средах проектирования программист четко знает, как устроена двухмерная оконная систе ма координат (рис. 26.2).
![]()
Рис. 26.2. Оконная система координат 2D-приложения
Когда перед разработчиками программного обеспечения из компании Embarcadero RCTШI RОП С (1 том, как научить FireMonkey работать в трех измерениях, они при няли самое рациональное для сложившейся ситуации решение. Программисты соз дали систему координат приложения ЗD, в которой:
□ центр координат (О, О,
О) расположен в
центре клиентской области формы;
□ оси
абсцисс х и
ординат у сохранили традиционные для текстового режима на правления;
□
ось аппликат z устремилась вглубь формы.
![]()
.:1с11шны тр1:мм11рниги мира 371
Если вы
вернетесь к
рисунку 26.1, а,
на
котором
представлена
правая
система координат, и,
не трогая ось х, мысленно
развернете
плоскость, создаваемую
осями у и z, на 180
градусов, то
получите
систему
координат,
применяемую
в приложении
FireMonkey (рис. 26.3).
![]()
Рис. 26.3. Система координат ЗD-приложения FireMonkey
ЗАМЕЧАНИЕ
Строго
говоря,
система
координат FireMonkey
является все
той же
традиционной
пра вой
прямоугольной
системой.
Единственная
тонкость
только в том, что она развернута
в удобном для
программистов направлении.
Любой программист, имеющий опыт разработки графических приложений, знает, что картинка любой степени сложности, которую вы видите на своем дисплее, со стоит из пикселов. Пиксел - это основа построения растровых изображений, он представляет собой самую минимальную деталь изображения, как кусочек слюды представляет собой строительный элемент большого мозаичного панно.
Благодаря такой значимой роли пиксел выступает базовой физической единицей измерения размеров в обычных двухмерных деловых приложениях. Например, мы создаем окно размерами 640х480 пикселов, размещаем на окне кнопку размерами 80х30 пикселов и т. д.
Однако
физическая
единица
измерения в
виде пиксела
не подходит
для описания
объектов из
реального
мира.
Попробуйте
измерить в пикселах
вашу комнату,
др м
лющую на окне
кошку и (для
уяснения
масштабов)
Солнечную
систему. Не
скажу, что
это сделать
невозможно,
но то, что
совсем
неудобно - это
точно. Кроме
того, пиксел - это
дискретная
единица, не
существует
понятия 1/2 или 1/3
пиксела.
Именно
поэтому
основной
единицей измерения
в 3D-проектах
выступает не
пик сел, а некая
условная
логическая
единица.
ВНИМАНИЕ!
В ЗD-проектах FireMonkey необязательно присутствие строгой единицы измерения,
вместо нее
используются
условные
логические
единицы.
Еще
одна очень
важная черта
FireMonkey
заключается
в том, что
здесь (в
отличие от
классических
проектов VCL)
расстояния,
размеры, углы
поворотов,
координаты
объектов описываются
вещественными
значениями. Благодаря
тому, что координа
ты способны
работать с
плавающей
точкой, работа
с численно
малыми
единицами не
приведет к потере
точности.
Согласитесь,
что это гораздо
лучше, чем
манипуля ции
целыми числами,
которые при
делении (и
умножении) на
дробные
коэффици енты
утрачивают
точность.
Зная правила построения трехмерной системы
координат,
нам не составит
никакого
труда задать
координаты
точки в этом
пространстве.
Тем более что
в FireMonkey для этого
есть много
помощников,
один из них - представленная
в листинге 26.1
запись TPointЗD.
![]()
![]()
type TPointЗD = record
Х: Single;
//координата Х
У: Single; //координата У Z: Single; //координата Z
private
end;
Запись
состоит из
трех полей
вещественного
типа, каждое
из которых
отвечает за
хранение
координаты
точки по
соответствующей
оси.
Разработчики
ЗD-моделей в своей работе
охотно
применяют
векторы.
Даже, вернее
сказать, что без
векторов
невозможно
построение
трехмерных
алгоритмов. С точки
зрения
математики
вектор
представляет
собой
направленный
отрезок от одной точ ки
к другой.
Вектор не
только
хранит сведения
о
координатах
точки в пространст
ве, но еще и
указывает
направление
на эту точку
(рис. 26.4).
Для
описания
вектора
необходимы
две точки. В FireMonkey
начальной
точкой век тора всегда
выступает
точка начала
координат (О,
О, О), поэтому
при
программиро
вании мы станем определять только координаты его конечной точки.
В Delphi трехмерный вектор
описывается структурой тvectorзo. В прwюжении 1 мы
достаточно
подробно
обсудим эту
запись и
возможности
векторов:
операции
сложения и
вычитания,
масштабирование
вектора, повороты
и многое
другое.
![]()
. :: ,,-пи треvмерио20 мира 373
![]()
х
у
Рис. 26.4. Определение точки А с помощью вектора
ЗАМЕЧАНИЕ
![]()
Для того
чтобы
однозначно
задать
положение и
ориентацию
трехмерного
объекта FMX в
пространстве,
необходимы
несколько
векторов.
Первый
вектор
определяет
по ложение
объекта.
Второй
вектор
хранит угол
поворота
объекта.
Третий
вектор опре
деляет центр вращения, относительно которого повернут объект. Именно поэтому
в базовом для трехмерных
объектов классе тcontrolЗD мы найдем три свойства (Position, RotationAngle и
RotationCenter), доступ
к которым
может быть
предос тавлен в векторной форме.
Какова
должна быть
последовательность
действий
программиста,
чтобы
описать
объект в
трехмерном
пространстве?
Для этого
следует
сделать 4
шага.
В
первую
очередь надо
позаботиться
об определении
координат
точек,
которые
позднее
превратятся в
вершины фигуры.
Например, для
того чтобы мы
смогли по
строить простейший 3D-объект с
мудреным
названием "тетраэдр",
нам понадобятся
4 точки.
Однако для
отображения на
экране
простейшего
многогранника сведений
о координатах
его вершин
далеко не достаточно.
Взгляните на
рис. 26.5, а,
на
кото ром обозначены точки А, В, С
и D. Полагаем,
что не каждый
наблюдатель с
первой
попытки
угадает в них
заготовку тетраэдра.
На втором
этапе
описания на основе
сведений о
вершинах
3D-объекта
необходимо
определить
его грани. Трехмерные
объекты
состоят из
нескольких
граней, в случае
тетраэдра их всего
четыре.
Вне
зависимости
от сложности
объекта для
компьютера
эти грани
являются
абсолютно
плоскими. Для
того чтобы
механизм
графического
вывода без проблем
превратил
вершины в грани, ПР!f
объявлении
вершин в исход ном
коде
программы
(или в файле с
данными
фигуры) их
следует
перечислять
в определенном
порядке. В
противном
случае мы рискуем
увидеть на
экране
совсем не то,
что рассчитывали.
ЗАМЕЧАНИЕ
Независимо
от заданной
программистом
или дизайнером
формы граней
механизм ви
зуализации во время прорисовки обычно разбивает многоугольные
грани на треуголь
ники. Поэтому, чтобы ускорить вывод сложных объектов, старайтесь
изначально
фор мировать
их из треугольников.
![]()
![]()
а) б) в) г)
А
z
D
х
у у у
Рис. 26.5. Определение тетраэдра в трехмерном пространстве
Наш
подопытный
тетраэдр
обладает
четырьмя
гранями, на
рис. 26.5, б
мы ограни
чились
отображением
всего одной
из них - грани,
определенной
точками А,
D, С. Заметьте,
что
определяющие
грань
вершины перечислены
по ходу
часовой
стрел ки -
это
требование
FireMonkey.
ВНИМАНИЕ{
При
обходе
вершин грани
графический
механизм FireMonkey
рассчитывает
на то, что
они
объявлены по
часовой
стрелке.
За
описанием
вершин
граней
следует
третий этап - этап
определения
ориентации
каждой из
граней
ЗD-объекта в
пространстве.
Во-первых,
это
необходимо
для вы
яснения
внешней и изнаночной
сторон грани.
Во-вторых,
ориентация
грани позво
лит
графическому
механизму
определиться
с порядком
освещения и текстурной
заливки поверхности
объекта. Ориентация
грани задается с
помощью
специального
вектора,
называемого нормалью.
Нормаль - это
вектор,
перпендикулярный
по верхности
грани (рис. 26.5, в).
ЗАМЕЧАНИЕ
На самом деле ориентация
граней трехмерных
объектов FireMonkey
описывается
еще сложнее - для
этого для
каждой из
вершин грани
определяется
отдельный
вектор нормали.
Заключительным,
четвертым по
счету действием
создателя
3D-объекта
становится
раскраска
объекта в подходящий
цвет. В FireMonkey
предусмотрено
несколько ме
ханизмов окраски
поверхности,
в том
числе
градиентные
и текстурные
заливки.
На первый взгляд может
показаться,
что набор
стандартных
ЗD-фигур со страницы
3D Shapes палитры
компонентов
FireMonkey весьма
скромен.
Однако постараемся
убедить вас,
что за счет
них просто
создавать
более
сложные
структуры.
Не станем
утверждать,
что изображенный
на рис. 26.6
грузовичок
представляет
со бой
шедевр дизайнерского искусства, но он
является
достаточно наглядной иллю-
![]()
![]()
законы трехмерного мира 375
страцией того, как, используя простые подручные ЗD-фигуры, можно создать более
"рп одвинутые" модели. Как видите, "шедевр" автомобилестроения собран из не-
скольких
прямоугольных
параллелепипедов
(рама и кабина),
четырех
цилиндров (по
числу колес) и
двух сфер,
имитирующих
фары.
![]()
![]()
Рис. 26.6. Фрейм "грузовик"
Заострим внимание читателя еще на одной важной детали рис. 26.6. Изучив окно структуры (Structure), вы заметите, что все задействованные в построении грузо вика ЗD-фигуры принадлежат компоненту RectangleЗDl (это рама автомобиля). Та кое объединение нескольких объектов обычно называют фреймом. Важнейшая особенность фрейма в том, что все вошедшие в его состав многоугольники полу чают описание не в координатах сцены, а в системе координат головного объекта. Как результат, существенно упрощается процесс визуального проектирования (по ворот рамы автомобиля приведет к соответствующему перемещению и всех оста:1ьных объектов фрейма) и программное управление объектами (во время дви жения грузовика по сцене колеса могут вращаться относительно рамы автомобиля).
ЗАМЕЧАНИЕ
В
качестве основы
фрейма можно использовать специальный компонент TDummy, ко торый вы
обнаружите
на странице 3D Scene палитры
компонентов.
При желании трехмерная сцена FireMonkey может оказаться бесконечной или по крайней мере весьма большой (это преимущество достигается благодаря использо-
![]()
ванию
вещественных
чисел при
определении
логической
единицы
измерения).
По этому никто
не запрещает
нам
"втиснуть" в
ЗD-пространство
приложения Delphi все космические
тела нашей галактики
и по вечерам
наслаждаться
мерцанием
звезд не на
небе, а на
экране
своего компьютера.
Человек - существо
любознательное,
еще в
стародавние
времена он
мечтал быть
ближе к
звездам и для
этого
изобрел
телескоп.
Телескоп не способен
перенести
астронома на другую
планету, но он по
зволил приблизить
картинку
выбранного
участка неба.
Зачем
мы вспомнили
о телескопе?
А затем, что
принцип
работы
телескопа
позво лит нам разобраться
с идеей построения
ЗD проекционной
системы. Для того чтобы
мы с вами
увидели
интересующий
нас объект,
например
Марс, нам
надо осущест
вить две
важных
подготовительных
процедуры:
l . Установить
нужное направление телескопа.
2.
Подобрать подходящее фокусное расстояние линз.
Если
все сделано
верно, то
перед
наблюдателем
появится
планета,
названная в честь
бога войны.
В
приложениях
ЗD для
наблюдения
за сценой вместо
телескопов
используют
спе циальные
объекты,
называемые камерами.
Камера
помогает
создавать
программ ную
проекцию
области,
воспроизводимой
на экране.
Благодаря
тому, что
камеры
смотрят не на
реальные, а
на
виртуальные
ЗD-про
странства, у построенных с их помощью
проекций есть существенное
преимущест
во над
картинками,
сделанными
камерами, телескопами,
фотоаппаратами и
прочей
оптической
техникой из
реального
мира.
Преимущество
заключается
в наличии
двух
плоскостей
отсечения
ЗD-проекции.
Все объекты,
находящиеся
перед перед
ней плоскостью
и дальше
задней
плоскости
отсечения, не
отобразятся
на экране
(рис. 26.7).
Таким
образом,
проекционная
система позволяет
в буквальном
смысле
смотреть
сквозь стены.
Невидимая
область
Видимая
область
отсечения Задняя плоскость
отсечения
Рис. 26.7. Принцип построения ЗD
проекционной системы
Выбранная
область
просмотра
может находиться
в любом
месте
трехмерного
про
странства, для того
чтобы она
превратилась
в полноценную
картинку,
осуществля
ется
операция
переноса в переднюю
область
отсечения
(рис. 26.8).
![]()
.законы трехмерного мира 377
Видимая область Перенос
х
у у
Рис. 26.8. Перенос системы координат
в переднюю область отсечения
Для того
чтобы
трехмерный
объект смог
корректно
отображаться
на плоском двухмерном
экране, к его
вершинам
применяется
преобразование,
которое
отобра жает трехмерные координаты
на двухмерные координаты окна. Преобразование
ко()рдинат
осуществляется с
помощью
квадратной
матрицы тмatrixЗD (см.
прwю же11ие 1).
![]()
ГЛАВА 27
Актеру для полного перевоплощения в образ необходима сцена - тяжело почувст вовать себя Отелло (или'Дездемоной) в голом поле... Поэтому еще в старозаветные времена даже бродячие труппы комедиантов перед спектаклем сооружали себе хотя бы примитивную сцену из повозок. А что уж говорить о требованиях профессио нальных актеров! Во всех эпохах для них воздвигались куда более серьезные сце ны. Вспомните театры Древней Греции, амфитеатры Римской империи и знамени тый лондонский театр "Глобус", на подмостках которого давались гениальные пье сы Шекспира. Одним словом, для исполнителей всегда требовалась сцена, ведь без нее не может существовать полноценный театр.
Заведя речь о сцене, мы ни в
коем случае не
отвлекаемся от
тематики книги. Дело в том, что современные
трехмерные
проекты FireMonkey
также не
могут дать
хорошее "театральное" представление без грамотно спроектированной сцены.
К счастью,
для этого в
распоряжение
программиста
предоставлено
все необхо димое:
□ источники света TLight, способные освещать
сцену;
□ компоненты-камеры тcamera, позволяющие установить точки наблюдения за сценой;
□ прокси-объекты TProxyObj ect, умеющие взять на
себя обязанности по замеще
нию любых ЗD-объектов;
□ макеты тcturnmy, позволяющие объединять разрозненные трехмерные объекты
в единые
фреймы.
Разработчику,_ не имеющему опыта проектирования ЗD-приложений, на первых по рах придется нелегко. Прежде чем третья координата приложений FireMonkey из хулигана превратится в вашего доброго помощника, пройдет небольшой адаптаци онный период. Чтобы минимизировать продолжительность "борьбы" с третьим измерением. предлагаем сразу спроектировать трехмерную сцену.
![]()
ПJIOP.l(m11pyeм ЗD-сцеJ.Jы 37§
ЗАМЕЧАНИЕ
При обсуждении особенностей формы TFonnЗD (см. главу 26) уже упоминались осо
бенности координатной системы трехмерной формы. Начала координат находятся
в центральной части формы, именно в
эту точку и попадают все размещаемые на
форме компоненты. Ось абсцисс х направлена
из точки (О, О, О) вправо, ось ординат
у вниз,
виртуальная
ось аппликат z уходит в глубину
формы.
Самая безукоризненная методика (в
свое время следуя которой большинство и нас научилось
плавать)
заключается
в забрасывании
обучаемого
на глубину.
Не почув
ствовав под ногами опоры, ученик осуществляет выбор из двух альтернатив - пойти ко дну или освоить принципы
перемещения
на поверхности
воды. Мы с вами
поступим
гуманнее и первое
погружение в основы
проектирования ЗD-сцен
осуще ствим
со всей
осторожностью на
мелководье.
Предлагаем
наш первый опыт
проектирования объемных
сцен
приобрести
при соз
дании фрагмента
ЗD-комнаты.
Создайте
новый проект
FireMonkey ЗD Application. На странице
3D Scene палитры
компонентов
найдите
источник освещения
TLight и
положите его на форму. По умолчанию он окажется в
точке с координатами
(О, О, О), соответствующей центру
клиентск?й
области формы.
Теперь
наступила очередь стен и
пола. Для их создания
нам понадобится
несколько
компонентов TPlane со
страницы 3D Shapes. Для того
чтобы один из
экземпляров
класса TPlane превратился
в пол сцены,
вы можете
немного повозиться
с ним, пе
ремещая
объект с
помощью мыши.
Левая панель: Position (-9, О, О) RotationAngle (О, 270,180)
Свет:
U
Position (О, О,
О)
Правая панель:
Position (9, О, О) RotationAngle (О, 90, 180)
Нижняя панель: ---i-+--,;-
Position (О, 7, О)
RotationAngle (90, О, О)
Рис. 27.1. Модель ЗD-комнаты
Задняя
панель: Position
(0, О, 8) RotationAngle (0,
О, О)
![]()
380 Главв 27
Когда вам это наскучи_тс, делайте следующее:
□
в свойство Position() компонента-плана передайте значения (х=О, У=7, Z=O);
□
в свойство RotationAngle () направьте
углы поворота (х=90, У=О, Z=O).
Не
успеете
оглянуться, а
претендующий стать
полом
компонент
уже
опустится
вниз по оси у на 7
условных
единиц и
развернется
относительно
оси х
на 90
граду сов.
Вам осталось
задать
подходящие
значения
ширины (wictth) и
высоты (нeight) компонента и,
при желании, подобрать текстуру для заливки пола (подключив к
свойству мaterialSource один
из компонентов
со страницы Materials). Что
надо сделать
с остальными
панелями, вам
подскажет
рис. 27.1.
Как
видите, для
создания
простейшей
трехмерной
сцены в Delphi от
нас не потре
бовалось никаких
усилий.
Единственное
и достаточное
условие для
успеха - зна ния
о
координатной
системе
3D-проекта.
При построении сцены не обойтись хотя бы без одного источника света. Поэтому
нам стоит поговорить о
классе TLight, благодаря
которому мы сможем создать са мые
разнообразные
световые шоу.
Как и все другие
3D-элементы
управления,
класс TLight построен на фундаменте
тcontrolЗD и поэтому обладает всеми стандартными
способами
позиционирования, основанными
на свойствах
Position и RotationAngle.
ВНИМАНИЕ!
Источник света TLight - важный, но не единственный участник сцены FireMonkey. Результат
светового
оформления
во многом
зависит от
материала, из
которого сде
лан
освещаемый
объект. Для
подключения
материала (см.
главу 29) к
трехмерному элементу
объекта используют свойство MaterialSource.
Особенности
формирования светового
потока в
первую
очередь
зависят от состоя
ния свойства
property
LightType:
TLightType;
TLightType =
(ltDirectional, ltPoint, ltSpot);
Самый
простой из
перечисленных
типов
источников
освещения - направленный
(ltDirectional) световой
поток. В этом
случае от
источника идет
своего рода
стена света,
не оставляя шанса
на появление
каких-либо
полутонов и теней
(рис. 27.2).
Точечный
источник ltPoint испускает
свет
равномерно
во всех
направлениях,
чем дальше от
точечного
источника вы
разместите
объект, тем
меньше он
будет осве
щен. Прожекторный
тип ltSpot испускает
один
направленный'луч
света в
заданном
направлении.
Свет идет от
центральной
точки,
перемещается
в
определенном
на правлении
и создает
плавные
переходы от
яркого к темному
по краям.
ЗАМЕЧАНИЕ
Пусть
термин
"точечный"
источник света
не вводит вас
в заблуждение.
Надо пони
мать, что
речь идет не
об освещении
какой-то
точки на
сцене, а
о точке
в пространст
ве, из кот рой
свет исходит
во всех
направлениях.
Таким
образом
"светильник", уста
новленныи в
состояние 1 tPoint, освещает
всю сцену
одновременно.
В качестве
при мера
точечного
источника света
можно
привести
обычную
лампочку накаливания.
![]()
Проектируем 30-сцены J§1
Рис. 27.2. Виды освещения
Любой источник света характеризуется цветом испускаемого светового потока, для его настройки предназначено свойство
property Color: TAlphaColor;
Если источник освещения работает в режиме прожектора (1tspot) или точечного источника (ltPoint), то стоит обратить внимание на пару свойств- SpotCutOff и SpotExponent. Свойство
property SpotCutOff:
Single;
определяет порядок отсечения внешней границы луча от прожектора. С увеличени ем знач ния свойства переход между светом и тенью становится все более плавным и размытым. Так, у точечного источника это значение по умолчанию равно 180 единицам, а у прожекторного- всего 60.
Or состояния свойства
property SpotExponent: Single;
зависит
степень
фокусировки
луча (чем больше
значение, тем
выше
фокусировка).
У точечного
источника
речь о фокусировке
не идет,
поэтому
свойство
устанавлива
ется в О,
а у прожекторного значение
по умолчанию
составляет 4.
ЗАМЕЧАНИЕ
Библиотека
FiteMonkey
ограничивает
число
источников
света на
сцене 100
единица ми.
Информация об
этом
хранится в
константе MaxLightCount, которую вы
обнаружи те
в модуле FМХ. TypesЗD.
Камера, класс TCamera
Вы бывали в саду камней? Самый знаменитый из японских садов - сад Реан-дзи, состоящий всего из 15 камней, вот уже полтысячелетия предлагает посетителям решить интересную задачу - найти такую позициk), из которой будут видны все камни. Однако с тех пор как существует сад, этого никому не удавалось! Сад спла нирован так, что наблюдатель (если он конечно не способен воспарить над пло щадкой) будет всегда видеть лишь 14 камней.
![]()
382 Глава 27
Обсуждая применяемую в трехмерных проектах форму TFormЗD, мы уже упоминали тот факт, что 3D-форма имеет встроенную камеру, определяющую позицию на блюдателя. Инкапсулированная камера неподвижна, она находится прямо перед сценой и нацелена на точку с координатами (О, О, О). Статичная камера не позволя ет изменить ни точку, ни утол просмотра, поэтому для создания настоящих дина мичных сцен нам стоит познакомиться еще с одним элементом управления - клас сом тcamera. После знакомства вы без труда сможете разрабатывать проекты своих виртуальных садов. Посетители ваших садов смогут легко плавно изменять точку наблюдения или переключаться между несколькими заранее подготовленными точками.
Применение камеры несложно. Расположите компонент тcamera на форме и на правьте "объектив" на нужный участок сцены (рис. 27.3). Чтобы камера стала активной, ее следует подключить к свойству camera формы и отключить у формы встроенный экземпляр камеры (свойство UsingDesignCamera). На практике это зай мет две строки кода (листинг 27.1).
![]()
prooedure TForml.RadioButtonlClick(Sender: TObject);
Ьegin Forml.UsingDesignCamera:=false; Forml.Camera:=Cameral;
end;
![]()
Рис. 27.3. Пример сцены с двумя камерами
![]()
![]()
![]()
![]()
Проонmируам ЗD-сц0ны 383
ЗАМЕЧАНИЕ
Вы увидите картинку из дополнительных камер только во время выполнения приложе
ния. Во время проектирования сцены
вашей
основной и
единственной
камерой
будет
оставаться
камера,
встроенная в форму.
ВНИМАНИЕ!
Для
того чтобы
воспользоваться
альтернативной
камерой,
недостаточно
разместить компонент
тcamera на
форме. Для
того чтобы
именно эта
камера стала активной,
ее следует
подключить к свойству
Camera формы и
установить свойство
формы UsingDesignCamera в
состояние false.
Из
опубликованных в
классе тcamera свойств
интерес
для
разработчика
представля
ет лишь
property
Target: TControlЗD;
упрощающее выбор целевого объекта для объектива камеры.
Объект-заместитель, класс TProxyObject
Объект-заместитель
способен
взять на себя
обязанности
по
графическому
выводу
любого из
трехмерных
объектов
(потомков
класса тcontrolЗD). Дг я
этого
достаточ но
подключить
целевой
объект к
свойству
property SourceObject: TControlЗD;
![]()
Рис. 27.4. Представление целевых объектов с помощью объекта-заместителя
![]()
384 Глввн 27
![]()
Прокси-объект не просто воспроизводит образ замещаемого объекта на экране, он подходит к этому творчески - изменяет позицию и углы поворота целевого объек та в соответствии со своими настройками (рис. 27.4).
Из всех компонентов, которые вы обнаружите на странице 3D Scene, самым не приметным (как в прямом, так и в переносном смысле) можно считать тоuппnу. Единственная задача компонента - стать основой для фрейма (объектов, объеди ненных в одну логическую группу).
Вошедшие
во фрейм
ЗD-фиrуры
будут
описываться
не в системе
координат
сцены, а
в локальной
системе
координат
головного объекта
(макета).
Объединение
объек тов во
фреймы
сушественно
упростит как
процесс проектирования
сцены, так и
программное
управление
объектами.
![]()
![]()
· ГЛАВА 28
На
странице 3D Shapes палитры
компонентов
вы обнаружите
несколько
компонен
тов, превращающихся
в 3D-объекты в
приложении
FireMonkey. Среди них
сфера (тsphere),
куб
(тсuье),
цилиндр
(тcylinder), конус
(тсоnе)
и
ряд других
объемных геометрических
фигур.
Спроектированные
программистами
Embarcadero классы
позволят нам
создавать
собственные
композиции
небольшой и
средней
степени сложности. Дr
я этого надо лишь научиться комбинировать имеющиеся фигуры.
А что делать,
если
разработчику
понадобится
свой
эксклюзивный
объект? В
таком случае
следует
воспользоваться
услугами
компонента тмеsh.
Произвольный
объект,
классы
TMesh и TMeshData
Класс тмеsh (впрочем,
как и все
остальные
трехмерные
фигуры)
является
наследни ком
нескольких
весьма
примечательных
классов (рис. 28.1), среди
них вы
обнару жите
опорный для
всех трехмерных
элементов
управления
класс тcontrolЗD. Но
сейчас нас в
большей
степени
интересуют
не предки тмеsh,
а
неприметный
класс тмeshData,
специализирующийся
на хранении
сведений о
геометрии
объекта.
Экземпляр
данного класса
инкапсулируется
во все
объекты, построенные
на фун
даменте TCustomМesh.
Как только для создания сцены нам понадобится объект (цилиндр, сфера, куб и т. п.), мы обращаем свой взор к странице компонентов ЗD Sbapes. Данные о гео метрических параметрах, перечисленных выше, и ряда других стандартных 3D-фигур заполнены в стенах Embarcadero, а на нашу долю остается лишь найти интересующую нас фигуру и перенести на форму проекта (рис. 28.2).
Но из правил всегда есть исключение. Разместив на форме компонент тмеsh, един ственное, что вы увидите- пустой каркас (см. рис. 28.2). Дело в том, что компо нент тмеsh представляет собой заготовку произвольного объекта, и изначально его данные отсутствуют. В своем роде тмеsh - это мастер перевоплощения, который готов услужить программисту и превратиться в любую фигуру. Единственное
![]()
![]()
![]()
![]()
;
Рис. 28.1. Иерархия наследования трехмерных фигур
TMesh.
TCube
TCylinder,
TRoundCube
TSphere
TRectangleЗD
TEllipseЗD
TCone
Рис. 28.2. Внешний вид основных ЗD-фигур
![]()
![]()
Гcoмcmpuчt:GtнJt: опиr.;ание фигур и теsh-объекты 387
условие
для
успешного
превращения - наличие
четкого
описания
характеристик
целевого
объекта. А за
описание, как
вы уже
поняли,
отвечает
инкапсулированный
класс TCustomМesh.
Доступ к данным с
параметрами объекта предоставляет
свойство
property Data: TMeshData;
в указанное свойство программист (даже скорее не программист, а ДИ!Шйнеr, абt
екта) долж н
занести исчерпывающие сведения о:
□ координатах вершин объекта;
□
индексах (порядке следования) вершин;
□ направлениях нормалей к
вершинам;
□ координатах текстуры заливки грани объекта.
Для
этих целей у тмeshData объявлены
четыре
свойства, с
говорящими за
себя на званиями
property
Points: String; //координаты вершин
property Triangleindices: String;//индeкcы
вершин property Normals: String; / /нормали вершин
property TexCoordinates: String;
//координаты текстуры
Координаты
вершин могут быть заполнены
как в период
визуального
проектирова
ния, так и
во время
выполнения
программы.
Вносимые значения
разделяются
про белами
или запятыми,
мы это
продемонстрируем
в дальнейших
примерах.
ЗАМЕЧАНИЕ
Об управлении координатами текстуры объекта с помощью свойства TexCoordinates
мы поговорим в
главе ?9.
Несмотря
на то, что
четыре
перечисленных
свойства
типизированы
как string,
надо
понимать, что
сведения о
вершинах объекта
не хранятся в
текстовом
виде (такой
подход
нельзя было бы
назвать
рациональным).
Физически
хранение
данных
осуществляется
в
специализированной
структуре
данных,
называемой вершинным
буфером. Вершинный
буфер
описывается
в виде класса
тvertexBuffer и инкапсули
руется в тмeshData. Доступ к вершинному
буферу
обеспечивает
свойство
property
VertexBuffer: TVertexBuffer;//тoлькo для чтения
Здесь
(кроме уже
знакомых нам
данных с координатами,
нормалями
вершин и ко
ординатами
текстур)
находится
информация о
цветах и подсветке
вершин.
Кроме
вершинного
буфера в
составе
компонентов-фигур
FireMonkey
используется
индексный
буфер, построенный
на основе
класса TindexBuffer. Доступ к
индексно му
буферу
предоставит
свойство
property IndexBuffer: TindexBuffer; //только для чтения
Благодаря
индексному
буферу
системе передаются
сведения о
порядке
обхода
вершин при
перерисовке
объекта
(вспоминайте
правило
часовой
стрелки).
Кроме того,
индексный
буфер
позволяет
исключить
хранение в
вершинном
буфере сведе-
ний о повторяющихся вершинах, вместо этого достаточно просто вновь указать ин декс ранее сохраненной вершины.
Проверим себя на практике? Предлагаем опробовать свои силы на самом простей шем объекте- равностороннем треугольнике. На рис. 28.3 предложен прототип будущей фигуры с координатами ее вершин.
- -2
х
С=(-2,1.72,0). -2
, у
В= (2, 1.72, О)
Рис. 28.3. Чертеж равностороннего треугольника
Изучив
чертеж
фигуры,
приступим к
визуальному
проектированию.
Для этого
раз местите на ЗD-форме компонент мeshl: тмеsh. Выделив компонент, переходим к
Инспектору
объектов и
разворачиваем
комплексное
свойство Data.
В первую
оче редь, нам
следует
заполнить
координаты
вершин. Для
этого в поле Points последо
вательно (вершина А, вершина В и вершина С) заносим тройки значений
(Х, У, Z). В
качестве
разделителя
между
значениями допускается
задействовать
символ про
бела или запятую,
например: о. о, -1. 72, о. о, 2 .о, 1. 72, о. о, -2. о, 1. 72, о. о.
Заполнив координаты вершин, обращаемся к полю Triangleindices и указываем очередность вершин объекта: о, 1, 2. Это означает, что при формировании изо бражения объекта в качестве стартовой вершины графический механизм FireMonkey станет воспринимать первую тройку значений из свойства Data. Points (о, -1. 72,о),в качестве второй - вторую тройку (2, 1. 72,,0) и·т. д.
Мьгзавершили минимальный набор операций и поэтому смело нажимаем клавишу
<Enter>. Для такого
простого
объекта, как
плоский равносторонний
треугольник,
класс тмeshData способен
самостоятельно
рассчитать
значения
нормалей. Для
этой цели автоматически вызывается метод
procedure CalcNoпnals;
В результате свойство Noпnals компонента Meshl без посторонней помощи запол
ИИТСJI шюбх.одимtrми шачепиями.
![]()
Геометрическое описание фигур и
теsh-объекты 389
![]()
![]()
Рис. 28.4. Создаем равносторонний треугольник в
компоненте TMesh
Координаты
текстур
в данном
примере нам
пока неинтересны
-
мы
удовлетво римся любым цветом
заливки
треугольника
(рис. 28.4).
То, что мы сделали во время визуального проектирования, не сложно превратить
в код.
Например,
так, как
предложено в
листинге 28.1.
![]()
procedure TForntl.FormЗDCreate(Sender: TObject);
Ьegin
Meshl.Data.Clear;
Meshl.Data.Points:='0,-1.72,0,2,1.72,0,-2,1.72,0';
Meshl.Data.Triangleindices:='0,1,2';
end;
Если вы
разобрались
с порядком
проектирования
плоской
фигуры, то
самое время
проверить
свои силы на
более
сложном
объекте - тетраэдре.
Хотя в мире
все от
носительно, среди
всех
многогранников
тетраэдр
также
считается
простейшим геометрическим объектом - у него
всего-навсего
4 треугольных грани, 4 вершины
и 6 ребер (рис. 28.5).
Если вы уверены
в себе, Т? отложите
книгу в
сторону и попробуйте самостоятельно
"засунуть"
предложенный на рис. 28.5
тетраэдр в свойство
Data компонента тмеsh. И
если у вас
все
получится с
первого раза,
то примите
мои
поздравления
-
вы
прирожденный
ЗD-дизайнер.
Если же
что-то пошло
не так, то
ничего
страшного
продолжим
работать·вместе.
D = (О, -2, О)
В= (2, о, -1.72)
у
Рис. 28.5. Чертеж
тетраэдра
Сначала
стоит
ответить на
вопрос:
сколько точек
с
координатами
вершин
следует занести
в свойство Data. Points компонента тмеsh, чтобы
увидеть на
экране тетра
эдр? Если вы
полагаете,
что 4, то вы
сильно
ошибаетесь,
правильный ответ- 12!
Дело в том,
что нам
следует
описывать не
вершины, а грани
многогранника. Граней
4, и каждая из
них задается
3 точками,
произведение 4 на 3
дало 12.
Предлагаем
в качестве
первой грани
выбрать
основание
тетраэдра,
описываемое
треугольником
АСВ. Передайте
координаты
вершин в соответствующие
поля свой
ства Data
Meshl.Data.Points:='0 О 1.72 -2 О
-1.72 2
О -1.72';//координаты
А, С, В Meshl.Data.Triangleindices·:='0
1 2'; //обход вершин А,
с, В
и не удивляйтесь, если вы ничего не увидите на экране. На самом деле у нас все получилось, но графический механизм FireMonkey сознательно не отображает нижнюю грань тетраэдра, справедливо полагая, что она должна быть видима, толь ко если наблюдатель окажется снизу объекта. Это утверждение легко проверить, если повернуть тмеsh на -90° относительно оси х (рис. 28.6).
Обратите внимание, каким образом мы указали компоненту тмеsh
порядок вывода
нижней
грани
многоугольника.
Мысленно
взгляните на
тетраэдр
снизу и
перечис лите в ршины грани по часовой стрелке - у вас получится А, С, В. Поэтому
в
свойстве
Meshl. Data. Triangleindices и
оказались
значения О
1 2.
ВННМАННЕ/
При определении порядка обхода вершин надо помнить, что координаты вершин ВИ
ДИМОЙ тороны rрани должны перечисляться по часовой стрелке.
С гранью DCA история повторится - она по умолчанию также невидима.
□
граньАСВ = (О о 1.72 -2 о -1.72 2 о -1.72); □ грань DCA = (О -2 о -2 О -1.72 О О 1.72);
С
остальными сторонами
тетраэдра
никаких проблем не возникнет
(рис. 28.7).
Подытожим
нашу работу. В результате
тетраэдр
будет
содержать 4
грани:
![]()
rоо.,,юmричсс«оа описание фигур и
теsh-объекты 1У1
□
грань DBC = (О -2 о
□ грань DAB = (О -2 о
2 О -1.72 -2 О -1.72);
00 1.72 20-1.72).
Необходимо последовательно ввести координаты всех перечисленн:мх граней
в свойство Meshl. o·ata. Points и определить порядок следования вершин о 1 2 з 4
5
6 7 8 9 10 11 в свойстве Meshl. Data.
Triangleindices.
![]()
![]()
Meshl.RotationAngle=(-90,0,0)
![]()
Рис. 28.6. Поворот TMesh с нижней гранью тетраэдра на -90° относительно оси х
![]()
Рис. 28.7. Компонент ТМеsh с данными тетраэдра
Вновь
возвратимся
к плоским
фигурам и решим
еще одну
задачу - подготовим
данные для
обычного
квадрата. Если
вы не видите в этом никаких
проблем, то вновь предлагаем провести конкурс на
сообразительность.
Берите очередной ком-
понент тмеsh и "рисуйте" в нем квадрат. Заранее поздравляем вас с очередной по бедой, а для менее нетерпеливых натур рекомендуем прочитать еще пару страниц.
Если вы сразу отказались от самостоятельной попытки конструирования квадрата и продолжили чтение, то наверняка ожидаете подвоха. Отчасти вы правы. Как вы по лагаете, сколько вершин следует описать, чтобы увидеть на экране квадрат? Пра вильный ответ - как минимум 6! Почему? Попробуем объяснить.
|
А= |
(-2, -2,
О) |
В= (2, -2, О) |
|||||
|
|
Г' ' ' ' ' ' '' ' 1 ' ' |
- |
-1 |
1 |
|
|
:,,,. |
|
|
2 -1 о |
' ' |
1 ' ' |
|
2 |
r х |
|
|
|
|
1 ' '' 2 |
' |
' ' |
|||
|
D= (-2, 2, О) |
|
|
|
С= |
(2, 2, О) |
||
r у
Рис. 28.8. Чертеж квадрата
С точки зрения FireMonkey, плоский четырехугольник или любая другая плоская фигура рассматривается как грань объекта. Грани могут иметь любую форму - от простейших треугольников (как у тетраэдра) до сложных многоугольников. А те перь самое главное - независимо от заданной формы граней механизм визуализа ции полагает, что многоугольные грани состоят из элементарных треугольников. Поэтому для создания фигуры квадрата нам понадобится разбить его на треуголь ники, в простейшем случае это будут два треугольника, например АВС и CDA (рис. 28.8). Соответственно нам понадобится описать 6 вершин, вот такая "тре угольная" арифметика (листинг 28.2).
![]()
..... ..,. ......................... ········••.-•···· .,...........:.
..,,,;;,\,
- .......2 --·------- ·-----
With Meshl.Data d.o
Ьegin
Clear;
Point�;=•-2,-2,0, 2,-2,0, 2,2,0,
2,2,0, -2,2,0, -2,-2,0';//A-B-C-C-D-A Normals:='0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0';
Trianglelndices:='0,1,2,3,4,5';
end;
Гr::uм,::111µuчt1r.;кut1 описание фигур и теsh-объекты 393
До СИХ пор,
проектируя
многоугольники,
мы не обращали
внимания на
нормали их вершин, доверяя расчет нормалей системе или присваивая им нулевые значения
(О, О, О). Однако
когда вы
рассчитываете
получить более
изысканное
изображение
грани, то стоит поэкспериментировать
с нормалями вершин. В качестве подопыт
ного
предлагаем вновь выбрать квадрат, но на
этот раз
собрать его из 4 треугольни
ков (рис. 28.9).
![]()
Рис. 28.9. Чертеж квадрата, состоящего из 4 треугольников
Современные
методики,
отвечающие
за трехмерный
синтез
изображения
грани, не просто
заливают их текстурой
или краской
определенного
оттенка, а еще учитыва
ют
направления
нормалей ее
вершин и
расположение
источников света. Благодаря
этому
вместо
плоского
вида мы можем
добиться более
реалистичного
объемного
изображения.
В подтверждение
этих слов предлагаем повторить
листинг 28.3, в ко тором
создается
квадрат с
рис. 28.9.
with Mesh2.Data
do
Ьegin
Clear;
............... • ..........................................4.....,....
---
•.•..•••••••••••••••••1., .!...•
Points:='-2,-2,0, 2,-2,0, 0,0,0,' + //грань АВЕ
' 2,-2,0, 2, 2,0, о,о,о,'
+
' 2, 2,0, -2, 2,0, о,о,о,'
+
//грань ВСЕ
//грань CDE
'-2, 2,0, -2,-2,0, 0,0,0'; //грань DAE
//определение нормалей к
вершинам, для вершины Е(О,0,-1) Noпnals:='0,0,0, 0,0,0, 0,0,-1, 0,0,0, 0,0,0, 0,0,-1,
О, О, О, О, О,
О, О, 0,-1, О, q,О, О,
0,0,
О, О, -1';
Triangleindices:='0,1,2,3,4,5,6,7,8,9,10,11'; //обход вершин
end;
![]()
394 · Глава 28
Самое важное в предложенном коде сосредоточено в строке определения нормалей. Обратите внимание на то, что описывая нормаль к общей для всех 4 треугольников вершине Е, мы направили ее в сторону, противоположную наблюдателю (вдоль оси z). Нам осталось подобрать положение и направление источника освещения и нажать клавишу <F9> для компиляции проекта (рис. 28.10). Как говорится, ком ментарии здесь излишни...
![]()
![]()
![]()
Рис. 28.10. Внешний вид квадрата с обычными и модифицированными нормалями
Полагаем, что потратив немало сил на разработку весьма непритязательных гео метрических фигур, многие из вас чуточку устали. Кроме того, наверняка некото рых читателей начали терзать смутные сомнения, а хватит ли у них терпения для того, чтобы самостоятельно "нарисовать" в тмеsh 3D-объект более сложный, чем тетраэдр? Поэтому спешим к вам с хорошей новостью, точнее новостями. Во первых, совсем необязательно собирать геометрические фигуры программным спо собом, вместо этого существуют специальные 3D-редакторы. Во-вторых, во Все мирной паутине есть множество заготовок, как простых, так и весьма нетривиаль ных объектов, подготовленных профессиональными дизайнерами. И наконец, в-третьих (и в-главных), в FireMonkey существует весьма простой и одновременно
gффективный способ управления не отдельными 3D-объектами, а целыми объем
ными конструкциями. Об этом мы сейчас и поговорим.
Обратившись к странице 3D Sbapes палитры компонентов Delphi, вы найдете ком понент тмоdеlЗD - это сверхмощное оружие 3D-моделирования, стоящее на во-
Геометрическое описание фигур и
теsh-объекты 395
оружении
FireMonkey, и в этом нет
ни капли
преувеличения.
Дело в том,
что TModelЗD способен
работать не
только с
отдельной геометрической
фигурой (как тмеsh), но и с целой коллекцией ЗD-фигур. В подтверждение этих слов предлагаем вам
экранный
снимок формы,
на котором вы
обнаружите
ни много ни
мало, а зна
менитый
Тауэрский
мост через
реку Темзу в
Лондоне (рис. 28.11).
![]()
Рис. 28.11. Компонент TЗDModel с моделью Тауэрского моста
Модель
моста не монолитна,
она собрана
из сотен более
мелких деталей.
Каждая из
деталей
описана в уже
знакомом нам
формате
(координаты
вершин граней,
обход граней,
нормали и
координаты
текстур). В
свою очередь
компонент тмоdеlЗD соби
рает все
составные
блоки модели в коллекцию
property MeshCollection: TMeshCollection;
где
TMeshCollection = TMeshDynArray;
в свою очередь тмeshDynArray оказывается динамическим массивом
TMeshDynArray = array of TMesh;
Таким образом, тмоdеlЗD выступает интеллектуальным хранилищем простых фи гур, которые в совокупности превращаются в объект любой сложности. Самое от-
396 Глава 28
![]()
радное в этом то, что компонент воспринимает очень распространенный формат хранения трехмерных моделей COLLAborative Design Activity (COLLADA). Мо дель может быть загружена как во время выполнения программы, для этого предна значена функция
function LoadFromFile(const AFileName: string): Boolean; virtual;
так
и в период
визуального
проектирования,
для этого
следует
обратиться к
Ин спектору
объектов и
при
посредничестве
свойства MeshCollection вызвать
встроен ный редактор
Mesh
Collection Editor.
ВНИМАНИЕ!
Для
загрузки
файла dae с
моделью в
формате COLLADA в
компонент_TModelЗD во время
выполнения
программы
потребуется
помощь
классов TDAEModelimporter и TModelimportServices.
ЗАМЕЧАНИЕ
Компонент
тмосtеlЗd воспринимает
ЗD-модели в
формате COLLADA,
сохраненные
в файлах с
расширениями
имен ase, obj и dae.
Среди них
наибольшее
предпочтение
следует
отдавать
файлам dae.
Предусмотрена возможность обмена данными между компонентами тмосtеlЗD с по мощью потоков в памяти, для этих целей предназначены методы чтения и записи:
procedure
ReadМodel(Stream: TStream); //чтение модели из потока
procedure WriteModel(Stream: TStream); //запись модели в
поток
Ко всему прочему компонент тмосtеlЗD может оказаться полезным для программи ста, стремящегося разобрать относительно несложную 3D-модель на составные части, например для того, чтобы использовать ее отдельные элементы в других проектах. Для этого достаточно разложить свойство MeshCollection н:а массив эле ментов тмеsh и исследовать основные характеристики интересных для нас фигур (листинг 28.4).
![]()
var i:integer;
Ьegin
Memol.Lines.Clear;
for i:=O to High(ModelЗDl.MeshCollection) do
Ьegin
Memol.Lines.Add('Mesh '+ Intтostr(i));
Memol.Lines.Add('Points: ' + ModelЗDl.MeshCollection[i] .Data.Points); Mernol.Lineв.Add('Nonnalв: ' +
ModelЗDl.MeshCollection[i].Data.Normals); Memol.Lines.Add('Triangleindices: ' +
ModelЗDl.MeshCollection[i] .Data.Triangleindices); Memol.Lines.Add(' '.);
end; end;
![]()
![]()
t еометрическое описание фигур и
теsh-объекты 397
В
результате
выполнения
предложенного
кода в многострочный
текстовый
редак тор
Memol будут
помещены
сведения о
составе
вершин, нормалях
к
вершинам и очередность смены координат для каждого отдельного фрагмента компле:r;:сиоrо
Импорт модели во время выполнения программы
При
необходимости
модель COLLADA
может быть импортирована
в компонент тмоdеlЗD и во
время
выполнения
программы.
Эго многоходовая
операция,
в кото рой
задействуется
не только
экземпляр класса
тмоdеlЗD, но и
специалисты по кон вертации
модели из
файла dae в
формат,
приемлемый
FireMonkey, - класс TDAEМodelimporter и класс
TModelimportServices.
Основная роль по преобразованию модели COLLADA во внутренний формат FireMonkey отводится импортеру TDAEModelimporter. Экземпляр класса последова тельно, узел за узлом, разбирает целостную 3D-модель COLLADA на составные элементы, преобразуя их в данные, понятные FireMonkey. Еще один наш помощник TDAEModelimporter обладает функцией класса Registerimporter () , уведомляющей компонент тмоdеlЗD о том, что он будет загружать файл COLLADA.
В листинге 28.5 предложен простейший пример импорта модели COLLADA в при ложение FireMonkey.
![]()
![]()
uses FМX.DAE.Importer, FМX.Import;
{$R *.fmx}
procedure TForml.ButtonlClick(Sender: TObject); var DAEModelimporter: TDAEModelimporter;
Ьegin
if OpenDialogl.Execute then Ьegin
DAEModelimporter:=TDAEModelimporter.Create;
TModelimportServices.Registerimporter(DAEМodelimporter); ModelЗЫ.LoadFromFile(OpenDialogl.FileName);
end; end;
Щелчок по кнопке вuttonl вызывает диалоговое окно выбора файла
с расширением
имени
dae. Все
остальное
сделают наши
новые знакомые - TDAEModelimporter и ТМodelimportServices.
![]()
ГЛАВА 29
Для того чтобы JD-объект выглядел реалистично, недостаточно определить конту ры его граней. В комплексе с качественным геометрическим описанием вершин и нормалей к вершинам программисту следует проявить заботу о материале объекта. С некоторой степенью абстракции стоит говорить о том, что материал - это то, из чего сделана поверхность граней фигуры. Надо понимать, что информационные технологии еще не дошли до того уровня, когда мы сможем потрогать программ ный объект рукой и почувствовать холод мрамора, свежесть воды или шерохова тость свежеструганной доски. Поэтому говоря о материале объекта, мы имеем в виду способ "обмана" глаз пользователя - надо постараться сделать так, чтобы виртуальный объект как можно более походил на его прототип из реального мира.
Базовым классом, предназначенным для описания материала, из которого "сделана" поверхность трехмерного объекта, в FireMonkey выступает класс тмatreial. В пер вой версии FireMonkey экземпляр класса инкапсулировался в цепочку насле дования ЗD-фигур (начиная с класса тshарезо), и доступ к нему обеспечивало свой ство Material. Позднее компания Embarcadero несколько пересмотрела подход управления материалом и убрала из 3D-объектов вышеупомянутое свойство. Вместо этого (начиная с Delphi ХЕЗ) в распоряжение программистов были предос тавлены три новых компонента- TColorMaterialSource, TTextureМaterialSource и TlightMaterialSource, которые взяли на себя ответственность за заливку трехмер ных объектов. А трехмерные фигуры вместо свойства мaterial приобрели свойство
property MaterialSource: TMaterialSource;
к которому и подключаются перечисленные компоненты.
JiИ.'lbI I\ОМПОifйПЫ также построены на основе идеи, заложенной в классе тмaterial, но на этот раз они поделили между собой различные сферы управления заливкой трехмерного объекта. Так, TColorMaterialSource взял на себя наиболее простую задачу по управлению цветом заливки. Компонент ттextureMaterialSource позволяет осуществлять заливку текстурой; которая может быть создана из любого файла с растровой картинкой. Наконец, TlightMaterialSource освоил наиболее
сложный функционал - он позволяет материалу ЗD-фигуры реагировать на осве щение.
![]()
ll,faт110/JUйJI UU Ьt:11\f f/cl 399
компонент TColorMaterialSource·
Самым
простым
способом
заливки
поверхности
трехмерного
объекта
станет под
ключение к нему компонента TColorMaterialSource. Совсем не сложно догадаться,
что ключевым свойством компонента окажется Color, хранящее значение цвета
]а.
ливки.
На
первый
взгляд может
показаться,
что разработчики
FireMonkey несколько
пере мудрили,
создав
отдельный
компонент
для решения
столь
тривиальной
задачи.
Однако это не так. Во-первых,
класс тcolorMaterialSource вписывается
в принятую
концепцию
по
распределению
обязанностей
при заливке
поверхности
объектов. Во
вторых,
компонент
окажется
полезным в тех
случаях,
когда он
служит
источником цвета для нескольких 3D-объектов - в этом случае упрощается процесс централи
зованной смены
цвета
одновременно для
всех
получателей.
Чтобы достичь
полной иллюзии
подлинности
проектируемого нами 3D-мира,
стоит
задействовать
сильнейшее
оружие - текстуры.
В роли
текстур
вправе
выступать простой
растровый
рисунок,
который вы
нарисуете
сами или
найдете в Интернете,
либо обычная
фотография,
сделанная вами во
время
отпуска.
Благодаря
текстуре
неуклюжий 3D-пря
оугольник
легко
превратится
в кирпичную
или
деревянную
стену,
размещенный
в верхней
части сцены план без проблем станет небом, а
сфера
замаскируется
под
футбольный
мяч...
ЗАМЕЧАНИЕ
На роль
текстур
рекомендуется
назначать рисунки
с
геометрическими
размерами,
кратными
значению 2.
Однако это
просто
рекомендация,
несколько
ускоряющая
про цесс текстурирования. Если скорость
рендеринга несущественна,
то вы можете
выби рать и
произвольные
размеры.
В простейшем случае 3D-объект FireMonkey, получив в
свое распоряжение файл с
текстурой,
самостоятельно
нанесет изображение
на свою
поверхность.
Если же вы
не намерены
ни на секунду
отпускать
бразды
правления
текстурами
из своих рук, то стоит вспомнить о классе тмeshData и
его свойстве тexCoordinates (см. главу 28), позволяющем программисту
конкретизировать
координаты
вершин
текстуры.
Получив
координаты
текстуры,
механизм
графического
вывода рассчи
тывает
соответствие
между
минимальным
элементом
текстуры (текселом) и соот
ветствующим
логическим
элементом на
грани
объекта.
Так
как текстура
задается в
формате
растрового
рисунка, то
ее можно
рассматри
вать как
квадратную
матрицу,
состоящую из и строк
и v столбцов.
Таким
образом,
каждый
тексел текстуры
обладает
своим
адресом
(номером строки
и столбца).
В FireMonkey
размер
текстуры, как
по
горизонтали,
так и по
вертикали,
нормиро ван к
1, т. е.
значение (О,
О) адресует самый верхний левый элемент текстуры, а
зна-

Рис. 29.1. Система координат для текстурирования
чение
(1, 1)
хранит адрес
правого
нижнего
тексела (рис. 29.1).
Несложные
расчеты
подскажут,
что центр
текстуры адресуется
значением (0.5, 0.5).
Нормирование
протяженности
текстуры к единичному
значению не
является дог
мой, программист
имеет возможность
выбирать и другие значения. Например, если
мы вместо
единичных
значений (1, 1), характеризующих
координату
правого ниж него
угла
текстуры,
воспользуемся
значениями
больше 1
(например, (2, 2)), то тек
стура
размножится
как по
горизонтали, так
и по
вертикали, а
если
значения ока
жутся меньше
единицы
(допустим, (0.5, 0.5)),
то текстура
растянется.
ЗАМЕЧАНИЕ
Процесс размножения рисунка текстуры на поверхности объекта называется фраг
ментацией (tiling).
Источник текстуры TTextureMateria/Source
За подключение текстуры к ЗD-объекту отвечают компонент ттextureMaterialSource
и его свойство
property Texture: TBitmap;
Если в
свойство загружено растровое изображение, то
заранее подготовленные
ЗD-объекты
(сфера TSphere, куб
ТСuЬе,
цилиндр
TCylinder и т. д.)
немедленно
"на тянут" на
все свои
грани
выбранный
программистом
рисунок. Если
же вы рассчи
тываете
получить
больше
рычагов
управления
процессом текстурирования, то сле дует
отдать
предпочтение
универсальному
компоненту тмеsh.
Управление координатами текстуры в
TMesh
Очередной
эксперимент,
который
предлагаем
провести, как
раз посвящен
заливке текстурой
фигуры,
собранной в
компоненте тмеsh.
Если вы
повторяли
примеры из главы
28, то в
вашем
распоряжении
наверняка
остался
исходный код
создания
квадрата в компоненте
тмеsh (см. листинг
28.2), именно э;rот
квадрат
вновь окажет
ся нашим
подопытным.
1
Материал объект=а'-------______________________________________________________ 4 8 1
Занесите
в свойство Data компонента тмеsh сведения
о вершинах
квадрата и
под ключите
источник
текстуры. Из-за того, что наш квадрат
собран из 2 треугольников
(см. рис. 28.8),
нам
потребуется
описать 6
вершин
текстуры (по 3 вершины
на каж дый из треугольников). Для
начала мы
заполним
лишь первые
три координаты
сле дующим
образом:
Meshl.Data.TexCoordinates:='0 О 1 О 1 1 00 00 00';
В
ответ на наши
действия
заливке
будет подвергнут
только
первый из
составляю
щих квадрат
треугольников. Если
мы
воспользуемся
другим
выражением:
Meshl.Data.TexCoordinates:='0 О О О О О О 1 1 1 1 О';
текстурой
покроется
второй
треугольник. Как
вы уже
догадались, для получения
целостной
картины
стоит объединить
левую и
правую части
координат:
Meshl.Data.TexCoordinates:='0 О 1 О 1 1 01 11 10';
В результате заливке будет подвергнута вся площадь квадрата.
Несколько
опытов с
заливкой
фигуры
текстурой
отражает рис. 29.2.
Обратите вни-. мание на то,
что текстуру
допускается вращать,
растягивать
и размножать.
TextCoordinates=
010010000000
TextCoordinates=
101101000000
ТextCoordinates=
001011000000
TextCoordinates=
110100000000
,..- - ·' \-
т"аур, об-
TextCoordinates=
001011011110
TextCoordinates=
■-
003033033330
!![]()
r
TextCoordinates=
000000011110
11 f i
i
TextCoordinates=
000000100001
TextCoordinates=
000000000111
TextCoordinates=
000000111000
Рис. 29.2. Управление координатами текстуры
Отраженный свет и
компонент TLightMateria/Source
Как
следует
вести себя
материалу
при падении
на него лучей
света и какие
пара метры следует
учесть, чтобы
виртуальный
объект как
можно больше
походил на
натуральный?
Это очень
непростой
вопрос, над
решением
которого уже
давно ра
ботают
программисты,
математики и инженеры,
разрабатывающие программную
и аппаратную
части
компьютеров.
В современных
графических
моделях для
описания
реакции
объекта на
источники
освещения
принято
учитывать
четыре разновидности
световых
потоков.
□ Amblent light - окружающий (не исходящий из каких-либо отдельных источни
ков) свет, подсвечивающий объект одинаково во всех
его точках.
□
Dif.fuse light - диффузный
(рассеянный)
свет. Это
свет, который
попадает на
поверхность
объекта и отражается
одинаково во
всех
направлениях.
Диффузное
отражение
присутствует
в нормальных
условиях и
придает
объекту его естест
венный цвет.
Основная
отличительная
черта
диффузных
материалов - незави
симость их
яркости от
положения наблюдателя.
Классическим
примером
мате риала,
чей внешний
вид в первую
очередь
определяется
диффузным
отражени ем,
может стать
бархатная
ткань, сухой
асфальт,
рыхлый грунт.
□
Specular light-
отраженный (зеркальный) свет. В
отличие от рассеянного света
зеркальный
свет отразится только в
определенном
направлении
(угол отражения
равен углу
падения).
Подобное
отражение
возникает на блестящих
(например,
металлических
и стеклянных)
поверхностях
в виде
отдельного
блика.
□ Emissive light- эмиссия света. Учитывается в поверхностях, способных само-
стоятельно излучать свет (лампа, угли в
костре и т. п.).
Эксперимент,
который
обязательно
стоит
провести при
изучении
возможностей
компонента TLightMaterialSource, следует
посвятить
квартету
свойств, опреде
ляющих
отражаемый и
излучаемый
свет объекта
property AmЬient: TAlphaColor; //окружающий свет
property
Diffuse: TAlphaColor; //рассеянный свет от материала
property Specular: TAlphaColor; //отраженный свет
property Emissive: TAlphaColor; / /излучаемый свет
С этой
целью
предлагаем разместить
на форме
несколько
сфер и истоЧJ-IИК
освеще- .
ния в режиме
точечного
излучения LightType=ltPoint в центре. Все
остальное по
знается в результате включения и
отключения (состояние Null) перечисленных
выше свойств (рис. 29.3).
Любителям
более строгого
подхода к изучению
особенностей
поведения
материала
объекта
можно
предложить
математическую
формулу,
взятую из
документации
DirectX 9.0 с SDK. Здесь
говорится,
что для
расчета
влияния
окружающего
освеще ния и
рассеянного
света
используется
следующее
математическое
выражение:
Dv =laSa +Se +"f,A;(RdiSdLdi +SaLa;).
i
![]()
![]()
lt,fa,11aµue111 UUЫ:JKffli:I 403
![]()
Рис. 29.3. Особенности отражения света
материала от свойств Amblent,
Diffuse, Specular и Emissive
Результирующий
цвет Dv, который
окрасит
поверхность
объекта,
складывается
из следующих
компонентов:
□
la - окружающий свет для данной сцены;
□ Sa- окружающий свет материала поверхности объекта (свойство AmЬient);
□ Se- свет, излучаемый материалом (свойство Emissive);
□ А; - коэффициент ослабления для i-го светового потока, который зависит от
характеристик
источника
освещения;
□ RJ; - фактор
диффузии для i-го светового
потока, он в
свою очередь
зависит от
направления
нормали к освещаемой
грани и угла
падения
светового
потока;
□ Sd - значение рассеяния, выбранное в
свойстве Diffuse материала;
□
Ld; - рассеяние для i-го светового потока;
□ La;-
свет окружения, генерируемый i-м световым потоком.
Вот такая арифметика... Она была приведена
не для того, чтобы читатель
переписал
формулу в записную
книжку и заучивал
ее наизусть,
а для того, чтобы
стало понят
но, насколько
сложные математические
расчеты
положены в
основу на
первый
взгляд
элементарных
операций. Хотите - верьте,
хотите - нет, но
предложенная
формула - одна
из наиболее
простых.
Сложность
расчета
бликов
отраженного света
или цветовых
оттенков для
материалов,
способных
излучать
световой
поток, несравненно
выше.
ВНИМАНИЕ/
Для
расчетов
параметров и
отображения
сцены,
перенасыщенной
источниками
осве щения,
будет
затрачиваться
значительная
часть
времени как
центрального,
так и гра
фического
процессора.
· В реальном
мире на
отражающие
способности
объекта
оказывает
влияние его
по лировка.
Программные
объекты
также можно
отполировать
с помощью
свойства
property
Shininess:
Integer; //по умолчанию 30
Свойство может принимать значение из диапазона от О до 128.
Дополнительная
настройка
текстур и класс TTexture
Текстурирование-
весьма
сложный
многоуровневый
процесс, в
котором
задейст
вуются скрытые
от обычного
программиста
классы. Сейчас
мы поговорим
об одном
из
"невидимок" - классе ттexture.
Класс
ттexture задействуется
в тех
случаях,
когда в основу
текстуры
положен
битовый
образ. Образ
текстуры
загружается
в
компоненты-источники
текстур TTextureMaterialSource и TLightMaterialSource с помощью инкапсулированных
в них:
□ специалиста по растрам - класса твi tmap;
□ класса-помощника твi tmapHelper.
Доступ
к хранилищу
текстуры · обеспечивает
уже
упомянутое
ранее свойство
Texture, у
которого в
свою очередь
надо вызвать
свойство-"однофамильца"
тexture (листинг
29._1).
r......;,aa.,:-- · ........... .... · ·· · i
; ...... - =··-·...-·.,·"'
·=··-···· . . ......;.- ..........;.v...... ,···"'"'·.....,.. .._,....... _..,
prooedure
TForml.FormЗDCreate(Sender: TObject);
Ьegin
TextureMaterialSourcel.Texture.Texture.Style:= [TTextureStyle.tsMipMaps] + [TTextureStyle.tsDynamic];
··-·-""···:
end;
примере
из листинга
29.1 демонстрируется порядок настройки
заливки поверхно
сти объекта
текстурой с
помощью свойства
property
Style: TTextureStyles;
type TTextureStyle = (tsMipMaps, tsDynamic, tsRenderTarget);
Свойство
определяет,
будет ЛИ текстура
построена на
базе
нескольких
текстурных
карт (tsMipMaps), является
динамической
(tsDynamic) или
используется
для перери
совки целевого
объекта (tsRenderTarget). По
умолчанию
стиль
заливки
включает
элементы tsM�pMaps и tsDynamic.
Если текстура отсутствует, то свойство
function
IsEmpty: Boolean;
возвратит значение true.
![]()
![]()
Матеп11ап пбъвкта i/0§
Формат хранимых данных и глубина цвета текстуры доступны благодаря свойствам
property
PixelFormat: TPixelFormat;
и
property BytesPerPixel: Integer;
//только для чтения
Геометрические
размеры
растрового
образа можно
получить,
обратившись
к свой ствам
property
Width: Integer;
property Height: Integer;
![]()
При
текстурировании
поверхности
удаленного
объекта
графический
процессор
вынужден
пропорционально
уменьшать
текстуру, при
приближении
объекта к на
блюдателю приходится
выполнять
кардинально
противоположное
действие - уве
личивать ее
размер. В
таких случаях
качество
внешнего
вида объекта
определяется
состоянием
свойств:
property MinFilter: TTextureFilter; //фильтр вывода уменьшенной текстуры
property MagFilter: TTextureFilter; //фильтр вывода увеличенной текстуры
где
type TTextureFilter = (tfNearest,
tfLinear);
Для получения лучшего качества изображения установите свойство в
состояние tfLinear.
В этом случае подбор цвета элементов материала станет осуществляться
с
помощью
алгоритма
линейной
интерполяции.
Второй
вариант tfNearest не столь
изощрен, он
просто
размножает
текселы
текстуры. В
последнем
случае можно
и не мечтать
о
качественном
рендеринге,
однако есть и
утешительный
приз - мы
выиг рываем в
скорости заливки поверхности объекта. Не
трудно сделать предположе
ние, что
здравый
баланс между
производительностью и качеством
предполагает
упрощенный
вывод
удаленных
объектов и интерполяционную
прорисовку
мате риала
объектов, близких
к
наблюдателю.
![]()
ГЛАВА 30
ЗD-контекст TContextЗD
Вся ответственность за графический вывод трехмерных сцен в проектах FirеМоnkеу·возлагается на ЗD-контекст, описываемый классом тcontextЗD. Класс определен в модуле FМХ. TypesЗD и несет ответственность за:
□ освещение
сцены;
□ работу
с шейдерами;
□ текстурную заливку;
□ вывод простейших графических
примитивов;
□ управление графическим буфером.
Одним словом, в руках графического ЗD-контекста сосредоточен весь базовый ' функционал рендеринга. Обычно доступ к контексту обеспечивает свойство Context рабочей формы проекта, однако не исключена ситуация, когда вам потре буется самостоятельно создать контекст. В последнем случае следует помнить, что, несмотря на наличие у класса тcontextЗD собственного конструктора, экземп ляр класса должен создаваться при посредничестве менеджера контекста
тcontextManager, описанного в модуле FМХ. TypesЗD.
Несмотря на то, ч;го FireMonkey использует трехмерную систему координат и е щественные числа в качестве единиц измерения, едва ли не основная характеристи ка контекста - физическая высота и ширина рабочей области двухмерна и описы вается целыми числами
property Height: Integer; //только дпя чтения
property
Width: Integer; //только дпя чтения
Здесь нет никакого противоречия, нравится нам это или нет, современный растро вый дисплей пока двухмерен и его основной единицей измерения выступает пиксел.
Если речь заходит о трехмерной системе координат, возможно, нам потребуется убедиться в отсутствии сдвига начала координат от центра рабочей области кон·- текста (например, принадлежащего форме). Для этой цели введено свойство
property CenterOffset: TPosition; //только дпя чтения
Если смещение отсутствует, то свойство возвратит значение (О, О,
О).
![]()
':ln_vnumc.vcm TCc,nfextЗD
Для связи с низкоуровневыми функциями прикладного программного интерфейса может пригодиться дескриптор родительского окна
property
Parent: TFmxHandle;
которому принадлежит контекст.
Управление графической сессией
Любое действие контекста, связанное с видеовыводом, должно осуществляться в рамках сессии. Команду на старт сессии отдает метод
function BeginScene: Boolean;
Если
контекст
готов к
работе, метод
возвращает
значение true, и
внутренний
счет чик сцен получает приращение на единицу. Все следующие за методом BeginScene () графические
операции
станут
рассматриваться
как команды,
форми рующие
внешний вид
текущей
сцены; Важно
понимать, что
сцена
полностью
соз дается в памяти
компьютера, а на
экране она
появится
лишь после
завершения
сес сии.
Команду на
это даст
метод
procedure
EndScene;
После завершения сессии значение внутреннего счетчика сцен уменьшается.
Разработчики FireMonkey рекомендуют при управлении сессией задействовать конструкцию защиты от ошибок try .. finally, примерно так, как предложено в листинге 30.1.
![]()
procedure TFonnl.FonnЗDRender(Sender: TObject;
Context: TContextЗD);
Ьegin
if Context.BeginScene
then
try
Context.Clear(TAlphaColorRec.White); //заливка контекста белым
//другие команды
finally
Context.EndScene;
end; end;
Вызов метода EndScene () в секции finaliy гарантирует закрытие текущей сессии. В коде примера 30.1 нам встретился еще один новый метод:
procedure
Clear(const AColor: TAlphaColor); over�oad;
позволяющий быстро стереть предьщущую сцену и залить контекст назначенным цветом.
![]()
У метода есть более сложная перегружаемая версия
prooedure Clear(const
ATarget: TClearТargets; const AColor: TAlphaColor; const ADepth: single; const AStencil: Cardinal); overload;
позволяющая с помощью параметра ATarget конкретизировать очищаемые элементы.
Графические
примитивы класса TContextHelper
![]()
![]()
Трехмерный
контекст тcontextЗD обладает
набором
методов,
позволяющих
осуще
ствлять
вывод
простейших
графических
примитивов
(точек, линий
и треугольни
ков). Кроме
того, в
состав контекста
интегрирован
класс-помощник тcontextHelper,
расширяющий
графические
возможности
контекста и
позволяющий
рисовать ряд
несложных
примитивов.
Заметим, что
с точки
зрения
написания
исходного
кода
обращение к
методам
вывода
графических
примитивов
класса тcontextзo или тcontextHelper осуществляется
абсолютно
одинаково,
поэтому
программист
может даже и
не заметить участия
класса-помощника. Свидетельством того
станет
пример
вывода
отрезков,
формирующих
плоскость xz
(рис.
30.1).
![]()
Ри,, 1),1, 6til60Д плоскости xz средствами класса-помощника TContextHelper
Для реализации представленной на рис. 30.1 модели требуется лишь помощь
имеющегося в
распоряжении тcontextHelper
метода
вывода
отрезков
prooedure DrawLine(const StartPoint, EndPoint: ТVectorЗD;
const Opacity: Single; const Color: TAlphaColor);
и несколько строк кода (листинг
30.2).
:JD но.-== TCoпtextЗD 409
f .."- • .i· -;.... - .. -_-;;·;;.;;;;;·;;---... · · 1
![]()
••••••....•»l••-•_...,м,,_ -. .,,,.,ww.,. ..••••-••.-.-,...,.,,..,cr.,., -•,.,,..,..,,. .,,,_,,.......... ,
var Vl,V2:TVector3D;
A:single;
Ьegin
if Context.BeginScene then
try
,,,,,,,,;,,.,;,,,.,_,,,,,.,.,.,.,., •••••••••.. •A••••.. •••••••••.. •••''"'•
•••••••••..-,t,.,-h. •·•••••••М••••/4\,о' - •
А:=-10;
while A<=l0
do
Ьegin
Vl:=VectorЗD(A,0,-10); V2:=Vector3D(A,0,10);
Context.DrawLine(Vl,V2,1,TAlphaColorRec.LightЫue);
// ...
end;
Vl:=VectorЗD(-10,0,A); V2:=Vector3D(10,0,A);
Context.DrawLine(Vl,V2,1,TAlphaColorRec.LightЫue);
Как
видите,
для вывода простейшего отрезка
в первые два
параметра метода следу
ет передать координаты начал_ьной
и конечной точек, степень прозрачности линии
и значение
цвета.
Класс-помощник позволяет нарисовать прямоугольник
procedure DrawRect(const TopLeft,
BottomRight: ТVectorЗD;
const Opacity: Single; const Color: TAlphaColor);
Координаты левой верхней вершины и
нижней правой направляются в
параметры
TopLeft И BottomRight.
Весьма удобен метод, способный вывести на экране контур куба
procedure
DrawCuЬe(const Center, Size: TVectorЗD; const Opacity: Single;
const Color: TAlphaColor);
Для
того чтобы
увидеть
изображение
куба, совсем
нет
необходимости
вычислять
координаты всех
вершин, вместо
этого
достаточно указать
геометрический
центр куба center и размер
куба с
помощью
вектора Size. Замечу,
что управляя
полями
вектора size, можно
рисовать не
только куб (у
которого все
стороны представляют
собой квадраты), но и
параллелепипед
с прямоугольными
сторонами (рис. 30.2).
У
метода
DrawCube ()
имеется
коллега с
однотипными
параметрами,
позволяющий
осуществить
заливку сторон
куба
procedure FillCuЬe(const Center,
Size: TVectorЗD; const Opacity: Single;
const Color: TAlphaColor);
Последний из рассматриваемых методов класса тcontextHelper наиболее сложен,
его задача - осуществление заливки одной из граней ЗD-объекта. Другими слова
ми, за счет
нескольких
вызовов
метода
![]()
prooedure FillPolygon(const Center, Size: TVectorЗD; const Rect: TRectF;
oonst Points: TPolygon; cons� Material: TMaterial; oonst Opacity: Single; Front: Boolean = True;
Васk: Boolean
= True; Left: Boolean = True);
мы сможем последовательно создать несколько двухмерных планов с
изображени ем полноценного ЗD-объекта. Здесь: center определяет центральную точку закра шиваемого объекта;
Size назначает
размер объекта; Rect при необходимости
огра ничивает
прямоугольную
область
вывода, если
в этом нет
надобности,
допускается задавать пустой прямоугольник; Points - массив с
координатами точек, которые в момент выполнения метода будут нормированы
к 1; мaterial- материал заливки
(см. главу 29). Передавая
в параметры
Front, васk
и Left значения
true или false, вы указываете, следует ли закрашивать фронтальную, заднюю и
левую стороны объек
та.
Простейший
пример
использования
возможностей
метода вы
обнаружите в лис тинге 30.3.
![]()
Рис. 30.2. Рисуем куб с помощью метода Dra1:1CuЬe ()
![]()
var Vl,V2:TVector3D;
Plgn: TPolygon;
Material:TColorMaterial;
Ьegin
SetLength(Plgn, 5); Plgn[O] := PointF(0,0);
Plgn[l] := PointF(l, О);
ЗD-контекст TContextЗD 411
![]()
Plgn[2] ·= PointF(l, 1); Plgn[ЗJ .- PointF(0, 1);
Plgn[4] := PointF(0,0);
if Context.BeginScene then
try
Material:=TColorMaterial.Create; Material.Color:=TAlphaColorRec.Bisque;
Context.Clear(TAlphaColorRec.White); //заливка контекста белым Vl:=VectorЗD(0,0,0);
V2:=Vector3D(5,-5,0);
Context.FillPolygon(Vl, V2, RectF(0,0,0,0),
Plgn, Material, 1, true, false,
false);
finally Context.EndScene; Material.Free;
end;
Графические примитивы класса TContextЗD
Кроме
методов,
подаренных
классом-помощником тcontextHelper,
в
распоряжении
контекста тcontextЗD имеется
тройка
собственных
методов, предназначенных
для вывода
соответственно
точек, линий
и треугольников
_prooedure DrawLines(const Vertices:
ТVertexBuffer;
const Indices:
TindexBuffer;
const Material: ТМaterial; oonst Opacity: Single);
prooedure DrawТriangles(const Vertices: ТVertexBuffer;
const Indices: TindexBuffer; oonst Material:
TMaterial;
const Opacity: Single);
prooedure DrawPoints(const Vertices: TVertexBuffer;
const Indices: TindexBuffer; oonst Material:
TMaterial;
const Opacity: Single);
Основными
параметрами
всех методов
выступают
вершинные Ver�exBuffer и ин
дексные
буферы IndexBuffer. В
первом
параметре хранятся сведения о
вершинах,
нормалях,
цветах и т. д., во втором - индексные
сведения о порядке
обхода
вер шин в
массиве vertexBuffer. Кроме того, методы
позволяют
конкретизировать
материал Material, которым
будет "покрыт"
объект, и степень
прозрачности
объек та Opacity.
ЗАМЕЧАНИЕ
Точка,
линия и
треугольник являются
основными и
вполне
достаточными
графически
ми
примитивами,
из которых
можно
создать
сколь угодно
сложный
ЗD-объект.
Листинг 30.4 демо стрирует порядок работы с простейшей из функций DrawLines () .
![]()
ln-11811•.._. ..._..···.·•·:-·---o-········· ·,.,------, -·-····· ---- ---,--, ,.
!. : ·········"' • ' =-·-·····" .................. ···········' ><·•-····-······· .•,:.,; ..............,..,- ••·····••·.•.....-..................,...••...•..............«1..............
varMaterial:TColorMaterial; Vertices: TVertexBuffer; Indices: TindexBuffer;
Ьegin
if Context.BeginScene then
try
Vertices:=TVertexBuffer.Create([TVertexFoпnat.vfVertex],2);
Vertices.Vertices[O] :=PointЗD(-10,0,0); Vertices.Vertices[l] :=PointЗD(l0,0,0);
Indices:=TindexBuffer.Create(2);
Indices.Indices[O] :=О; Indices.Indices[l] :=1;
Material:=TColorMaterial.Create; 'Material.Color:=TAlphaColorRec.Red;
Context.Clear(TAlphaColorRec.White);
Context.DrawLines(Vertices,Indices,Material,1);
finally Context.EndScene; Vertices.Free;
Indices.Free; Material.Free;
end; end;
Если вы
внимательно
прочитали главу 28, посвященную
геометрическому
описа нию фигур, то наверняка запомнили пример с
тетраэдром
(см. рис. 28.5).
Предлага ем
попробовать
нарисовать
этот тетраэдр
с помощью
метода DrawTriangles (), весь
необходимый
код вы
обнаружите в
листинге 30.5.
. 'n,.,..,•.![]()
!,\-' ·•• .•,...•,• ...,.. •••••.,. ··-········· ..................,.. •.•• .• ·············,-,.,_ ·••············-•· с.·•·············, -....- •·t-·•·-
Oa,ot. -•••..•·•""'"·,.'t••••••..-1••• ,,,. ,У,"6,,,,.,,,,,-.,, ....,., .,,_,,.,
,.,."'1. <,,4-••'""'··•· "1,,· ,·-- -·,,.,.,_А.,....,..'
![]()
var Material:TColorMaterial;
Vertices: TVertexBuffer; Indices: TindexBuffer;
Ьegin
if Context.BeginScene then try
Material:=TColorMaterial.Create;
Material.Color:=TAlphaColorRec.Gray;
![]()
:}D щ>,,п,оноn? TContextЗD 4-1]
Context.Clear(TAlphaColorRec.White); //заливка контекста белым
Vertices:=TVertexBuffer.Create([TVertexFonnat.vfVertex],12);
/ / ========
КООРДИНАТЫ ВЕРШИН ТЕТРАЭДРА
Vertices.Vertices[0] :=PointЗD( О, О, 1.72); //грань АСВ
Vertices.Vertices[l] :=PointЗD(-2, О, -1.72); Vertices.Vertices[2] :=PointЗD( 2, О, -1. 72);
Vertices.Vertices[З] :=Point3D( о, -2, О); //грань ОСА Vertices.Vertices[4] :=PointЗD(-2, 0,-1.72);
Vertices.Vertices[5] :=PointЗD( О, О, 1. 72);
Vertices.Vertices(б] :=PointЗD( О, -2, О); //грань DBC Vertices.Vertices[7] :=PointЗD( 2, о, -1. 72); Vertices.Vertices[B] :=PointЗD(-2, О, -1.72);
Vertices.Vertices[9] :=PointЗD( О, -2, О); Vertices.Vertices[10]:=Point3D( О, О, 1.72); Vertices.Vertices[ll]:=PointЗD( 2, О, -1.72);
// ========= ИНДЕКСЫ
Indices:=TindexBuffer.Create(12);
//грань DАВ
![]()
Indices.Indices[0] :=О;
Indices.Indices[l] :=1; Indices.Indices[2] :=2;
Indices.Indices[ЗJ :=3; Indices.Indices[4] :=4; Indices.Indices[5] :=5;
Indices.Indices[б] :=6; Indices.Indices[7] :=7; Indices.Indices[B] :=8;
Indices.Indices[9] :=9; Indices.Indices[l0] :=10;
Indices.Indices[ll] :=11;
![]()
![]()
//
Context.DrawTriangles(Vertices,Indices,Material,1)
finally
Context.EndScene; Vertices.Free; Indices.Free; Material.Free;
end;
end;
Внешний
вид реальных
и
нарисованных
объектов во
многом
определяется
особен ностями
освещения
сцены,
поэтому
трехмерный
контекст как
никто больше
заин
тересован в
наличии рычагов по
управлению источниками света. И эта возмож
ность у него
имеется.
В первую очередь отметим существование свойства, способного сослужить хоро
шую службу в
ситуации, когда источники света создаются динамически
property Lights: TList<TLight>;
предоставляющего
доступ ко
всем "прожекторам"
и
"лампочкам"
в формате
удоб ного списка
TList.
Кроме того, в арсенале тcontextЗD имеется пара методов
prooedure
AddLight(const ALight:
TLight);
prooedure DeleteLight(const ALight: TLight);
позволяющих добавлять и удалять источник света к контексту.
Матрицы и матричные преобразования.
Поддержка
трехмерных
матричных
преобразований
выступает
едва ли не
самым сильным
козырем
ЗD-контекста,
ведь
благодаря им
можно с
легкостью
реализовы
вать
операции переноса,
масщтабирования,
вращения. В
результате
этих
действий
достигаются
весьма неординарные
графические
эффекты.
Для
осуществления
преобразований
достаточно
сконструировать
необходимую
матрицу тмatrixЗD
(см.
прwюжение 2),
а
затем
вызвать метод
prooedure SetMatrix(const М: TMatrixЗD);
применяющий матричное преобразование к контексту.
Как вы думаете, каким образом бьmо получено изображение фигуры с рис. 30.3?
![]()
Рис. 30.3. Пример матричного
преобразования
Все не так и сложно, как может показаться на первый взгляд. Мы просто подгото вили прямоугольник, а затем несколько раз повернули его вокруг оси у с помощью матрицы вращения. Исходный пример создания матрицы вращения и применения
к :Н)-к91rпжсту предложен в листинге 30.6.
![]()
![]()
var M:TMatrixЗD; Angle: Single;
Ьegin
if Context.BeginScene then
try
Context.Clear(TAlphaColorRec.White);
Angle:=0; //исходный угол
while Angle<pi do Ьegin
M:=CreateRotationМatrixЗD(VectorЗd(0,1,0),Angle); //вращение
Context.SetMatrix(M);
Context.DrawRect(VectorЗD(-5,-5,0), Vector3D(5,5,0), 1, TAlphaColorRec.Blue);
Angle:=Angle+0.1;
end;
//восстановим состояние контекста
Context.SetMatrix(IdentityMatrixЗD); //! !
! единичная матрица
// ... другие операции
finally
Context.EndScene;
end;
end;
Обязательно обратите внимание на то, что завершив операции вращения, мы при менили к контексту единичную матрицу. Напомним, что единичная матрица - это , матрица, все элементы которой (за исключением главной диагонали) заполнены нулями, а диагональные элементы содержат единицы. Единичная матрица восста новит изначальное состояние контекста, что позволит нам далее работать с ним в обычном режиме.
Другой вариант восстановления исходного состояния может быть основан на пред варительном чтении свойства
property CurrentMatrix: ТМаtriхЗD;//только для чтения
возвращающего текущее состояние контекста. Сохранив текущую матрицу, мы сможем смело провести необходимые преобразования, а затем вернуться к сохра ненному состоянию.
С трехмерными матрицами связано еще несколько доступных только для чтения свойств контекста (табл. 30.1).
Таблица 30.1. Информация о состоянии контекста, возвращаемая в формате тмatrixЗD
|
Свойство |
Описание |
|
property CurrentCarneraMatrix: TMatrixЗD; |
Матрица
вида.
Представляет
матрицу текущей
камеры на
основе
пересчета
вектора
абсолютной
позиции (AЬsolutePosition), т. е.
позиции
относительно
данного
контекста |
Таблица 30.1 (окончание)
|
Свойство |
Описание |
|
property CurrentCarnerainvMatrix: TMatrixЗD; |
Инвертирует
матрицу вида, возвращен-
ную свойством
CurrentCarneraМatrix |
|
property CurrentProjectionМatrix: TMatrixЗD; |
Матрица
проекции.
Отвечает за правиль-
ное проецирование
трехмерного
объекта в
двухмерной
плоскости
проекции |
Доступные
контексту
текстуры
хранятся в специальном
массиве,
который
очища ется в момент
старта новой
сессии
(вспоминаем
метод BeginScene ()) при условии,
что на момент
инициализации
сцены
счетчик сцен
равен нулю.
Одновременно
в контекст
может быть выбрано
несколько
текстур. Об их
количест ве
можно судить
по состоянию
свойства
property TextureUnitCount: Integer; //только дпя чтения
Обычно свойство возвращает
число 16.
Текущая (выводимая в данный
момент времени) текстура доступна
благодаря
свой ству
property Texture: ТТехturе;//только дпя чтения
, Для размещения в
контексте текстуры Texture следует воспользоваться методом
prooedure SetTexture(const AUnit: Integer;
const Texture: TTexture);
Н первый
параметр
метода AUnit следует
направить
индекс
элемента
массива, в ко торый будет
помещена
текстура.
Индекс не
должен превышать
размера
массива (значения,
возвращаемого
свойством TextureUnitCount).
Размер
воспринимаемой контекстом
текстуры
также ограничен,
эту "тайну"
хранит свойство
property MaxTextureSize: Integer; //только дпя чтения
Значение
максимального
размера
зависит от
выбранной
библиотеки (DirectX
или OpenGL) и
обычно
находится в
пределе от 8
до 16 Кбайт.
Являясь слугой
двух господ
(речь о Windows
и OS Х),
исповедующих принципи
ально разный графический механизм (с
одной стороны DirectX,
а с другой - OpenGL), трехмерный контекст нуждается в
некотором универсальном подходе
к решению
задач работы
с ЗD-графикой. Одним из
помощников на этом
нелегком
поприще выступает класс тcontextShader, объявление которого вы обнаружите
в
модуле FМX.TypesЗD.
![]()
ЗD-контекст TContextЗD 417
![]()
Прочитав название класса тcontextShader, вы уже наверняка догадались, что он от вечает за доступ к подпрограммам обращения к шейдерам. Так как FireMonkey является кроссплатформенной библиотекой, то класс сделан всеядным - он спосо бе обращаться к шейдерам, представленным в DirectX 9/1О и OpenGL. Особо от метим, что поддерживается не только классическая спецификация OpenGL АRВ консорциума Architecture Review Board, но и подмножество OpenGL ES (OpenGL for Embedded Systems) для встраиваемых систем, широко применяемое в мобиль ных телефонах и карманных компьютерах.
ЗАМЕЧАНИЕ
Основное
назначение
шейдеров
заключается
в создании
сложных
видеоэффектов,
которые
собираются
(если хотите,
как в
конструкторе)
из
элементарных
операций,
поддерживаемых
видеоакселератором компьютера.
В сфере
ответственности
шейде ров могут
оказаться
практически
все эффекты:
отражение и
рассеяние
света,
текстур- ное
наложение, преломление,
тени. ·
Библиотека FireMonkey различает две разновидности шейдеров:
□ вершинные шейдеры (vertex shader);
□ пиксельные шейдеры (pixel shader).
В
основе
вершинных
шейдеров
лежат сведения
о вершинах,
подлежащих
выводу
объектов, как
минимум это такие
векторные
параметры,
как трехмерные
координа ты
и нормали к
вершинам.
Благодаря
векторной "начинке"
вершинные
шейдеры
отвечают за
различные
геометрические
пространственные преобразования и транс
формации. В
противовес
вершинным шейдерам
их пиксельные
коллеги
оперируют
понятиями, присущими
растровой
графике, - цвет, глубина
цвета, текстура.
Таким
образом,
пиксельный
шейдер несет
ответственность
за внешний
вид рисуемого
объекта и поэтому
в
графическом:
конв йере,
отвечающем
за
формирование
изо бражения,
его
подпрограмма
вызывается
последней.
Доступ к текущим вершинному и пиксельному шейдерам предоставляют свойства
class property CurrentVertexShader: TContextShader;
class property CurrentPixelShader: TContextShader;
Для смены шейдера следует воспользоваться методом
procedure
SetShaders(const VertexShader, PixelShader: TContextShader);
ЗАМЕЧАНИЕ
![]()
В
графическом
конвейере
существует
еще одна
разновидность
шейдеров - гммет
рические (geometry shader).
К сожалению,
с геометрическими шейдерами
FireMonkey пока не
работает.
1 ■ , - ■ ‘ ’ >•■■■ - ; • '
■ ! " " /■ .. , ■ ...
. . . .
- "О , ; V.-. S. ". Л :■J‘r;»sэ {л'-;- .ч .
• г'т-- ■ " . ■ / . . - - .'• .■ ■ • : :f ■ 'flVf'
’ ' / i -,■ ‘ '.'sr/ -•! a - ' I - . ..." . -. . . ‘ ‘ ' ' ' ; >
; ■''■ * - ■' ' ■
\.y . " ■: ' ■/
■ • " ■' ' .4 . ...: . . .
■' ■ "’> ' -П :-rV ■kx~~?l-Q,t. ". •' '-с / •,. . •' ; •» ■, и» . ■ - .
-• ’ ' '
. . 5 ' . . . . . . . '. -
|
i |
|
' . |
' |
' |
-л *; |
j i |
С . |
м-.у |
Ц |
|
|
:Vj - .«/V» Д»г. |
|
|
|
■ |
|
|
. |
|
- ПЪ'Фi- г..-.." .О ry: -yyt .Л
Ш Q'
•
- • - - . .
- • • .■ . . . . .
. ■ ■ ' ■‘ - ; ' • . ' ■ • ■ • • •
^ ^ . ' * ‘w " ». ‘ , >
~
;. '■ - ' •- ‘
• . ' V ' ■ '
- •' . * *. ■ *.
>. . - ,
г ■ ”Г; -
Л .1;- ; : "
; - ц? 1 ^ v ^ ^
- f:). : :у .7
ПРИЛОЖЕНИЕ 1
Вектор ТVectorЗD
Весьма
удобной
абстракцией,
используемой
для описания
операций в
трехмерном
пространстве,
выступает
вектор. С
точки зрения геометрии
вектор - это
некий объект,
имеющий
длину и
направление.
С точки
зрения
представления
в памяти вектор - это
группа чисел,
которые
связаны
вместе в
одном поле
объекта или
пе ременной.
Таким
образом,
векторы могут
определять
расстояния по
осям х, у и z или
(если вам так
захочется)
выражать
красную,
зеленую и
синюю
составляющие
цвета - одним словом,
смогут
представлять
любую четверку
чисел,
которую
имеет смысл
хранить вместе.
ЗАМЕЧАНИЕ
На первый
взгляд может
показаться,
что между
понятием
вектора и
точки можно
по ставить
знак
равенства. На
самом деле
это не так! Точки
имеют
местоположение,
но не
обладают
размером и направлением. В свою
очередь
вектор
характеризуется
раз мером и
направлением, но не
имеет
местоположения.
В первой версии FireMonkey вектор представлял собой обычную запись тvectorЗD с
вариантным
полем и
просто
обеспечивал
хранение
координат
конечной
точки
вектора.
Позднее
разработчики
существенно
расширили структуру,
добавив в
рас
поряжение
трехмерного
вектора
набор
специализированных
методов. А на
роль обычного хранилища
координат стала претендовать
запись tagVECTORЗD (лис
тинг
Пl.1). В
современных версиях
FireMonkey запись тvectorЗD определена
в мо дуле System. Types. Кроме
того, надо
знать о
наличии в
этом же модуле
ряда вспо
могательных
функций, предназначенных для
работы с
векторами.
ВНИМАНИЕ!
В записях тvectorЗD и tagVECTORЗD хранятся только координаты конечной точки век
тора, а
в качестве начальной всегда принимается
точка с координатами
(О, О, О).
![]()
tagVECTORЗD = record
case Integer of
О: (V: TVectorЗDType;);
1: (Х: s.ingle;
У: Single;
Z: Single;
W: Single;);
end;
Как
видите,
вариантное
поле содержит
не только
сведения о
координатах
конечной
точки в
трехмерном
пространстве,
но и включает
поле w, предназначенное
для опи
сания дополнительного параметра вектора. Четвертый компонент показывает, вхо дит ли в состав объекта начало отсчета. Строго говоря, в 99% операциях с
вектора ми
компонент w никакого
участия не
принимает и
хранит значение,
равное 1. Ис
ключение составляют операции нормализации (в
этом случае в
w оказывается ноль) и
преобразование
цвета ARGB в
вектор (здесь в w помещается значение альфа
канала).
Четвертая
координата
вектора в
основном
задействуется
в матричных опе
рациях, но об
этом
поговорим немного
позже.
Раз мы
упомянули о связи между
цветом и вектором,
сразу
приведем
пару функций
из программного
модуля FМХ. Types ЗD.
functiod ColorToVectorЗD(AColor: TAlphaColor): ТVectorЗD;
Вполне естественно,
что предусмотрено и обратное
преобразование
function VectorЗDToColor(const AColor: TVectorЗD): TAlphaColor;
Для
инициализации
вектора
можно использовать
один из двух перегружаемых
кон
структоров
записи
constructor Cr�ate(const Х, У,
Z: Single; const W: Single=l.0); constructor Create(const APointЗD: TPointЗD; const W: Single=l.0);
или парой самостоятельных функций
function VectorЗD(const Х,
У, Z: Single;
const W: Single = 1.0): TVectorЗD;
overload;
function VectorЗD(const Р:
TPointЗD; const W: Single = 1.0): TVectorЗD;
overload;
Для
расчета
длины
вектора (в
математической литературе
для понятия
"длина век
тора" введен
термин "норма')
в
трехмерном
пространстве
стоит
воспользоваться
теоремой
Пифагора
llv 11v=.x2+ v.y2 + v.z2 (Пl .1) или обратиться к
подготовленному в
стенах Embarcadero методу
function Length: Single;
![]()
Операция
нормализации
вектора
выдает вектор
того же
направления,
но единичной
длины.
Например,
если
исходный
вектор задан
значениями
[О, О, 3], то его
норма
лизованный
коллега
примет
значения [О, О, 1].
С точки
зрения математики
операция
нормализации
выглядит
следующим
образом:
-Vл =V-·
11V-1.1
(Пl .2)
Если
описать формулу (Пl
.2) более
подробно, то мы
увидим, что
нормализуются
все три
координаты
вектора:
![]()
/ 2 2 2
vл=
V.X · yV.X +V.y +V.Z
v.z · ,Jv.x2 + v.y2 + v.z2
v.z•y/v.x2 +v.y2 +v.z2
(Пl.3)
ЗАМЕЧАНИЕ
Кому не
по душе язык
формул,
предложим
более
простое
объяснение
процесса нор
мализации: нормализованный вектор - это исходный вектор, деленный на свою
длину.
В FireMonkey нормализацию может выполнить функция
function Nonnalize: TVectorЗD;
говорящая, как станет выглядеть вектор после нормализации.
ЗАМЕЧАНИЕ
Операция
нормализации
задействуется
в ситуации,
когда нам
следует
получить век
тор вполне
определенной
длины,
указывающий в
том же
направлении,
что и исход
ный, сонаправленный
с заданным.
Проверка равенства двух векторов
Для
проверки
равенства
двух
векторов
программисту
достаточно
воспользоваться
стандартным
оператором=,
обратная
задача
решается с
помощью
оператора<>.
Сложение и
вычитание векторов
Мы с
вами знакомы
с векторами
еще по урокам
геометрии. В
средней
школе нам
объясняли, что
одна из
базовых
операций с
двумя векторами
- их
сложение, и обязательно
иллюстрировали
процесс примерно
таким
р.1;;1сунком (рис.
Пl.1).
Как
видите, в
результате
операции
суммирования
двух
векторов
результирующий
вектор 1\ +v2 окажется
в точке С.
Если
вам необходимо
добавить
вектор к
текущему, то лучшим
помощником
станет
символ
сложения+.
![]()
с
(О, О, О)
(О, О, О)
Рис. П1.1.
Сложение векторов 171 и 172 Рис. П1.2. Вычитание векторов ii1 и 172
Обратная задача- вычитание векторов выполняется за счет
операции - (лис
тинг
Пl.2).
i },J!,1it•,. ,, r - ,,\ " , ,,, \ л, , ,.11•.,1 , " , \ • , " .,,, , , , 'f,):Z ' :,.,, ;. ,w, :::,"f, ,•;i f
1 11
;.;. • ,,11: .•1;:. ,!At.i.,,.,"'Гl;J,tt 'fc"'- 'Jt-." .11::..il'ti "t1fll}i'fit. №.,/ .... ," t .,,_, ,жt1 , - ,.,-ill; .;,1 "',1.,,t, А 1 1 1,;,,: t .1:,t:.,",М,, i/'Jf;,J::;,1,: :11r."' 1141· 'i\l!'f.t
var vl,v2,v3,v4:TVector3D;
Ьegin
vl:=VectorЗD(l,0,0); v2:=Vector3D(-1,1,0)� v3:=vl+v2; //сложение векторов v4:=vl-v2;
//вычитание векторов
f/ "
Результат
операции
вычитания
диаметрально
противоположен операции
сложения.
Теперь из компонентов
(х, у, z) вектора i\ отнимаются
значения
соответствующих
компонентов вектора v2 . Если два вектора представляют положения точек в
про
странстве,
то вычитание
задает
направление и
расстояние,
которое
следует пройти,
чтобы
попасть из
одной точки в
другую (рис. Пl .2).
Завершая разговор
об операциях
сложения и вычитания,
вспомним еще
один близ
кий по духу
метод
function MidVector(const AVectorЗD: ТVectorЗD): TVectorЗD;
позволяющий
рассчитать усредненный вектор по формуле:
(v1.x + v2.x) / 2]
result = (v1.y + v2 .у)/ 2 .
[
(Пl .4)
( VJ .z + V2 .z) / 2
Расстояние между двумя векторами
Дз: я получения расстояния между текущим (хранящимся в
полях записи)
и внеш ним
вектором AVectorЗD предназначен
метод
function Distance(const AVectorЗD: TVectorЗD): Single;
Вектор ТVectorЗD 423
Расчеты осуществляются в
соответствии с
выражением
![]()
r = .Jv1.х - self .x + ..Jv1.у - self .y + ..Jv1.z - self .z,
здесь self- текущий вектор, v1 - внешний вектор.
(Пl.5)
Масштабирование
вектора (на
математическом
языке - умножение
вектора на
скаляр)
заключается в
умножении
каждого
компонента
вектора на
одно и то же число:
kv =r:::::1.
k·v.z
(Пl.6)
Графически операцию масштабирования иллюстрирует рис. Пl
.3, здесь, в резуль
тате
умножения
вектора на
скаляр, исходный
вектор
увеличился в два
раза.
![]()
после
масштабирования
(О, О, О) (0, О,
О)
Рис. П1.З. Масштабирование вектора
За умножение
вектора на
скаляр в Delphi
отвечает оператор
класса Mul tiply, вызы
ваемый с
помощью
классического
для арифметических
операций
умножения
сим вола *. В качестве
сомножителей
могут
выступать вектор и
вещественная
перемен ная или два вектора.
Заметим, что если в
качестве
множителя
параметр
передать -1, мы
инвертируем
направление
вектора.
Масштабирование вектора
может осуществляться и
в целях его уменьшения, в
этом случае
задействуем
оператор
деления /.
Векторное
произведение
(cross product) двух
векторов- это третий
вектор, назы ваемый вектором нормали (nonnal), который рассчитывается следующим образом:
Vl.у ·V2.Z-V1.Z· V2·Y1
n = V1.Z·V2.X-V1.X·V2.Z .
[
(Пl.7)
V1 .х·V2·Y-V1 .у·V2.X
У!
1
v1t -/ z
v2 х
Рис. П1.4. Векторное произведение векторов V1 и li2
Возвращаемый
в результате
выполнения
выражений (Пl.7)
вектор
нормали п
всегда
перпендикулярен
аргументам произведения - векторам 1\ и
v2 (рис. Пl .4).
Чтобы
мы не теряли
времени,
разрабатывая
функцию для расчета
векторного
про
изведения,
разработчики
FireMonkey дарят нам
уже готовый
метод
function CrossProduct(const AVectorЗD: TVectorЗD): TVectorЗD;
Чтобы
убедиться,
что
результатом
операции
векторного
произведения
окажется
вектор,
перпендикулярный
первым двум,
можно
повторить
листинг Пl.З.
r л -,,1 з:···· ltl81111111• . . .
• . .
. . . . • '
![]()
![]()
:1.•••••,•.•.......................................... • ........................,,•; -.,.,,.. .-•• ............a:- ••,.. ,n..-.................·.. ,.-.,:,.. ..,,..............-- .. ·-,- •J"'...U'\>-;l\. - ·""· --- - -·
varvl,v2:TVector3D;
Ьegin
.//------ 1 вариант-------------
vl:=VectorЗD(l,0,0,0);
v2:=Vector3D(0,1,0,0);
vl:=vl.CrossProduct(v2); //перпендикуляр vЗ=(О,О,1,1)
//-------- 2 вариант-----------
vl:=VectorЗD(l,0,0,0);
v2:=Vectoт3D(0,1,0,0);
v2:=v2.CrossProduct(vl); //перпендикуляр v3=(0,0,-1,1)
end;
Обязательно
обратите
внимание на то, что
направление
вектора
нормали
изменяет ся
при перестановке
множителей в операции
векторного
произведения.
ЗАМЕЧАНИЕ
Векторное произведение двух параллельных векторов возвратит нулевое значение.
Произведение,
которое
вычисляется
как сумма
попарных
произведений
компонен тов векторов, называется
скалярным
произведением
(dot product). Оно рассчитывает
не вектор, а
скаляр (одно
значение).
(Пl .8)
![]()
На основе выражения (Пl.8)
сконструирован метод
function DotProduct(const AVectorЗD: TVectorЗD): Single;
вычисляющий скалярное произведение двух векторов в
трехмерном пространстве.
Скалярное произведение двух векторов может
быть описано альтернативным вы ражением:
![]()
(Пl.9)
где
а - угол между
векторами v1 и v2 . В данном
случае в роли
множителей
вы ступают
длины
векторов и косинус
угла между ними. Результатом
выражения
ока жется не вектор,
а число
(скаляр), не зависящее
от системы
координат и характери
зующее длины
векторов-сомножителей
и угол между
ними.
Комбинируя
выражения (Пl.6)
и (Пl.7), мы можем
получить
косинус угла
между двумя
векторами, в свою
очередь
косинус
легко
переводится
в радианы или
граду сы. Впрочем, в Embarcadero тоже
читали
учебники по
геометрии,
поэтому и
разра ботали
метод
function AngleCosine(const AVector:
TVectorЗD): Single;
рассчитывающий значение косинуса.
Скалярное произведение векторов - достаточно полезная штука, благодаря ему мы
сможем
быстро
выяснить
следующее:
□
если
результат
скалярного
произведения
меньше О, то угол
между
векторами
больше 90°
(тупой);
□ если результат равен О,
то векторы перпендикулярны друг другу;
□ если результат больше О,
то угол между векторами меньше 90° (острый).
Чем
еще
интересно
скалярное
произведение?
Оно поможет
рассчитать
проекцию одного
вектора на
другой,
например
вектора i\ на вектор v2 . Для этого
нам доста
точно рассчитать длину вектора v1 и
вычислить косинус угла между векторами -
и проекция у
нас в "кармане" (рис. Пl.5).
1 IV1I cos(a) \ Результат
скалярного
произведения
Рис. П1.5. Скалярное произведение векторов ii1 и ii2
Операция
поворота
является
наиболее
сложной из всех
рассмотренных
ранее опе
раций с вектором. Для расчета
поворота
вектора в FireMonkey
задействуется
специ альная
структура - матрица
тмatrixЗD,
в
рамках
которой
осуществляется трехмер
ное
преобразование.
Для упрощения работы
обычного
программиста в
модуле ЕМХ. TypesЗD о?исана
простая в использовании
функция
function Rotate(Angle: Single; const axis: ТVectorЗD): TVectorЗD;
Она
поворачивает
вектор vector на угол Angle вокруг оси,
заданной
вторым векто
ром axis. Например,
axis:=VectorЗD (1, о, о)
заставит
вектор
вращаться
вокруг оси .х, axis :=VectorЗD
(О, 1, О) - вокруг у, и т.
д.
Для осуществления поворота следует помнить направление
вращения относительно
координатных
осей (рис. Пl .6).
у
х
Рис. П1.6. Направление вращения относительно осей координат
Пример из
листинга Пl .4
демонстрирует порядок вращения объекта вокруг коор
динатной оси у на 1° против
часовой
стрелки.
![]()
var V, axis: TVectorЗD; const angle=pi/180;
Ьegin
V:=VectorЗD(Cubel.Position.Vector.X,
CuЬel.Position.Vector.Y,
CuЬel.Position.Vector.Z);
Axis:=VectorЗD(0,1,0); //выбор оси у
дпя вращения объекта
Отражение вектора
При
проектировании
сложных
графических сцен
и игровых
приложений
зачастую
бывает необходимо знать, как
отразится луч света от
зеркальной поверхности или в
каком направлении отскочит теннисный мячик при
ударе о
стену. При
расчетах
![]()
![]()
![]()
![]()
![]()
![]()
отраженного вектора
хорошей
подсказкой
станет напоминание
о том, что
угол па
дения а.1 всегда
равен углу отражения
а.2 (рис. Пl. 7).
Как всегда, помощь в
расчетах может оказать Delphi,
а точнее функция
function Reflect(const AVectorЗD: TVectorЗD): TVectorЗD;
Метод возвращает отражение вектора AVectorЗD относительно плоскости, постро
енной на
основе
нормализованного
вектора N .
![]()
Рис. П1.7. Направление отражения относительно осей
координат
ПРИЛОЖЕНИЕ 2
TMatrixЗD
Для определения
положения
ЗD-объекта на плоском экране
необходимо
применить к его вершинам
преобразование, которое
отобразит
трехмерные
координаты
объек та в пространстве
на двухмерные
координаты
окна. Основную
роль в этом преобра
зовании
играет
квадратная
матрица
размера 4х4,
которая
является
суперпозицией
отдельных
преобразований
перспективы, масштабирования
и переноса.
В библиотеке FireMonkey за матричное представление отвечает класс тмatrixЗD,
физически матрица
представляет собой запись с
вариантным полем (листинг П2.1).
![]()
TMatrixЗD = record
private
�se Integer of
О: (М: TMatri ЗDType;);
1: (mll, m12, mlЗ, m14:
Single; m21, m22, m23, m24: Single; m31, m32,
mЗЗ, m34: Single; m41, m42, m43, m44: Single);
end;
Благодаря
вариантному
полю мы
получаем возможность
рассматривать
матрицу
преобразований
как
структуру TMatrixЗDType, храняшую
сведения о
четырех
векто рах тvectorЗD, или набор из
16
вещественных
чисел.
ЗАМЕЧАНИЕ
В
математике
используются
матрицы
различного
размера.
Говоря
языком
програм миста, в
роли матрицы
вправе
выступать
любой
двухмерный
массив размера
MxN. Но так как в FireMonkey размер матрицы тмatrixЗD четко определен, далее мы будем предполагать,
что все
используемые
нами матрицы
состоят из 4 колонок
и 4
рядов.
В
ранних
версиях FireMonkey
(ХЕ2/ХЕ3/ХЕ4) для
заполнения
полей
матрицы пре
образований
проще всего
воспользоваться
библиотечными
функциями
![]()
function MatrixЗD (const mll, ml2, mlЗ, ml4, m21, m22, m23, m24,
m31, m32, mЗЗ, m34, m41, m42, m43, m44:Single): TMatrixЗD; overload;
function MatrixЗD(const LArr: TSingleDynArray)
:ТМatrixЗD; overload;
Заметим, что перечисленные функции вынесены в
программный модуль FМХ. туреsЗD.
Начиная
с Delphi ХЕ5,
матрица
снабжена
десятком конструкторов
create ... , по
зволяющих
создавать
матрицу
смещения, вращения
просмотра и
т. п.
В матричной
математике
существуют
два краеугольных
понятия - нулевой и еди
ничной
матрицы.
Все
элементы
нулевой
матрицы (как
и следует из
названия)
равны О, таким
обра зом,
нулевая
матрица
выступает своего
рода
аналогом
нулевого
значения из
обыч ной
арифметики.
Например,
операция сложения
любой
исходной
матрицы с
нуле вой возвратит
исходную
матрицу, а
операция умножения
на нулевую
матрицу от
зовется
нулевой
матрицей.
Особенность
единичной
матрицы в
том, что это
квадратная
матрица, по
главной
диагонали которой
расположены
1, а все остальные
элементы
принимают
значения, равные О.
В ранних версиях FireMonkey (ХЕ2/ХЕЗ/ХЕ4) в модуле FМX.TypesЗD объ явлена
константа
(листинг П2.2),
призванная
играть роль
единичной
матрицы.
_._,.,,. ...,...,,.,....... ············--·····••-.•·...·-················,,,_,.,....,,....•,... ..'" _., ............'"''..,.................' ......,,_,.. ·•"\ ,........................,....... ' .....,..,...,,,,........,......,......-...... ....... ,..,.,.,............. .,.,,,,.
![]()
![]()
![]()
![]()
![]()
ln.. .....u.1............ ..1 ...-....................·•··················-······························.· !
const IdentityMatrixЗD: TMatrixЗD =
(mll: 1.0; ml2: О.О; mlЗ: О.О; ml4: О.О;
m21: О.О; m22: 1.0; m23: О.О; m24: О.О;
m31: О.О; m32: О.О;
m41: О.О; m42: О.О;
mЗЗ: 1.0; m34: О.О; m43: О.О; m4-4: 1.0;);
Операция
переноса
(трансляции)-
одна из основных
и
одновременно
простейших
матричных
операций,
применяемых
в FireMonkey. Задача
операции
заключается
в
перемещении
точки в ЗD-пространстве
в заданном
направлении
на определенное
расстояние.
Для
осуществления
операции
переноса
создается
специальная
матри ца - матрица
трансляции (translation matrix).
Основой
матрицы
трансляции
вы ступает единичная матрица, но с одной важной модификацией
- в ее элементы
m41,
m42 и m4з помещаются
значения
смещения,
определяющие
направление
и ха рактер
переноса по.соответствующим
осям
координат.
![]()
1 о о о
о 1 о о
Мт= о о 1 о (П2.1)
рх Ру pz
![]()
Программное решение этой задачи предоставлено функции
class function CreateTranslation(const ATranslation: TPointЗD): ТМatrixЗD;
Функция
передает
значения
переноса
через
параметр ATranslation и
возвращает
матрицу
трансляции.
Для
осуществления
поворота
вектора на заданный
угол нам
следует
создать мат рицу
вращения (rotation matrix). Матрицу
вращения MR интересует
выраженный в
радианах
угол поворота
вектора и координатная
ось,
относительно
которой осуще
ствляется
поворот.
Особо
отметим, что заполнение
элементов
матрицы
вращения зависит
от того,
относительно
какой из осей
координат
осуществляется
вращение:
ось х: MRX=
ось у: MRY=
ось z: MRZ=
(П2.2)
1 о о о о cos0 -sin0 о о sin0 cos0 о о о о cos0 о -sin0 о о 1 о о sin0 о cos0 о о о о 1 cos0 sin0 о о -sin0 cos0 о о о о 1 о о о о 1
(П2.3)
(П2.4)
Для
создания
матрицы
вращения
предусмотрен ряд
методов. В первую
очередь это
простейшие
методы
class function CreateRotationX(const AAngle: Single):
TMatrixЗD; class function CreateRotationY(const AAngle: Single):
TMatrixЗD; class function CreateRotationZ(const AAngle:
Single): TMatrixЗD;
осуществляющие поворот вокруг заданной оси на угол,
определяемый в
параметре
AAngle.
Во втором эшелоне следует метод
class function
CreateRotation(const AAxis: TPointЗD; const AAngle: Single): TMatrixЗD;
Вращение осуществляется
относительно оси,
исходящей из
точки (О,
О, О) и
прихо дящей
в точку AAxis.
![]()
![]()
Еще более сложный метод создан специально для поклонников авиасимуляторов:
class function CreateRotationYawPitchRoll(const AYaw, APitch, ARoll: Single):
TMatrixЗD; static;
Здесь
для создания
матрицы
вращения
методы
оперируют
такими
авиационными
понятиями,
как: рысканье
(yaw), тангаж (pitch) и крен (rol[).
ЗАМЕЧАНИЕ
Если вы далеки
от
аэронавтики
и термины
"рысканье",
"тангаж" и "крен"
вам не знако
мы, то мы это в
два счета
исправим.
Чтобы
разобраться
с а"Виационной
тематикой,
представьте,
что наблюдатель
смотрит на
нос самолета,
находясь за
его
хвостовым
оперением. В
этом случае
ось z идет от хвоста к
носу
самолета. Ось
х протянулась
от кончика
левого крыла
к кончику правого.
Ось у направлена
вверх
от земли. В
этом случае
тангаж
определяет вращение
вокруг оси х, рысканье - вращение
вокруг у и крен - вращение
вокруг z.
Для
пропорционального
увеличения
(уменьшения)
вектора
предусмотрена
матри
ца масштабирования. (П2.5). Значения
коэффициентов масштабирования
заносятся в элементы главной диагонали: коэффициент Sx масштабирования по оси х -
в ячейку т11; Sy - т22, Sz - тзз- Таким образом, матрица позволяет определять масштаб по отдельности для каждой из осей координат.
![]()
sx о о о
о Sy о о
Ms= о о sz о (П2.5)
о о о 1
Если
вам не
нравится
прямой
доступ к
матрице, то
воспользуйтесь
штатной функ
цией
function CreateScaling(const AScale:
TPointЗD): TMatrixЗD;
и передайте соответствующие коэффициенты в
параметр AScale.
В FMX предусмотрен еще один способ масштабирования, но на этот раз не отдель ного вектора, а всех элементов матрицы
,
prooedure Scale (const AFactor: Single);
Этот способ умножения
называют
скалярным, в результате
выполнения
процедуры
все ячейки
матрицы М умножаются
на
коэффициент AFactor.
Ни у кого не вызывает затруднений освоение операций сложения и масштабирова ния матриц, ведь они соответствуют обычным арифметическим действиям. Умно жение матриц - не столь прозрачный процесс. Здесь элементы из строки первой
![]()
матрицы
умножаются
на элементы в
столбце второй
матрицы и
полученный
ре зультат
складывается.
Чтобы
проиллюстрировать
суть этого
правила,
вместо ис
пользуемых в
FireMonkey матриц 4х4 воспользуемся матрицами
меньшего
размера:
![]()
![]()
a11h12+ a12h22 ]·
a21h12 +а22Ь22
(П2.6)
1 о о о 0,5 о о о 0,5 о о о о 1 о о о 0,5 о о о 0,5 о о о о о 1 о -2 1 о о 0,5 о о о 0,5 о о о о 1 0,5 о -1
Проиллюстрируем
правило
умножения на примере.
Допустим, нам
необходимо
пе- ремножить
матрицы Mr и Ms. ·
M1s
=Мт·Мs =
Чтобы завершить задуманное, умножим полученный результат на матрицу враще
ния MRY·
Mтs ·MRr =
0,5 о о о о 0,5 о о о о 0,5 о
0,5 о -1 1
О, 707 о :_о, 101 о
![]()
о 1 о о
О, 707 о 0,707 о =
о о о 1
|
0,354 о |
о 0,5 |
0,354 о |
о о |
|
-0,354 |
о |
0,354 |
о |
|
1,06 |
о |
-0,354 |
1 |
Таким
образом, мы
получили
комбинированную
матрицу,
содержащую
все три преобразования.
ВНИМАНИЕ!
При
выполнении
операции
перемножения
матриц
помните, что
при
перестановке
мес тами
сомножителей
результат
меняется!
Вполне
естественно,
что
создатели
библиотеки
FireMonkey
позаботились
о том, чтобы
мы с
вами, вникая
в тонкости
матричной
арифметики, не
сломали
головы, и за
счет
оператора класса*
внедрили в
класс тмatrixЗD
поддержку
умножения.
Этот
оператор
может применяться
не только при
умножении
матрицы на
матрицу, но и
подходит для операции умножения на матрицу точки TPointЗD или вектора
TVectorЗD.
ЗАМЕЧАНИЕ
Операция матричного умножения некоммутативна, т.
е. МтхМs* МsхМт.
![]()
Матрица преобразований TMatrix3D 433
Дополнительные матричные операции
В табл. П2.1 представлен краткий перечень функций, отвечающих за осуществле ние матричных операций.
Таблица П2.1. Дополнительные матричные функции
модуля FМХ. TypesЗD
|
Функция |
Описание |
|
function Transpose: TMatrixЗD; |
Транспонирует
исходную
матрицу,
заменяя строки
столбцами |
|
function Determinant(const
М: TMatrixЗD) : Single; |
Возвращает определитель (детерминант) матрицы |
|
function Inverse: TMatrixЗD; |
Соэдание
обратной
матрицы.
Основное
свойство
обратной
матрицы в том,
что при
умножении
ее на
исходную
матрицу на
выходе
получается единич-
ная матрица |
ПРИЛОЖЕНИЕ 3
Модуль System.lOUtils
Модуль
System. IOUtils содержит
определение
трех
кроссплатформенных
записей TDirectory, TFile и TPath,
предназначенных
для работы с
файловой системой
в опе
рационных
системах Windows, OS Х,
iOS и Android.
В табл. ПЗ.1
представлены
основные
методы
записи TDirectory. Запись
отвечает за управление
отдельным
каталогом
(папкой).
Таблица ПЗ.1. Работа с
каталогом средствами записи TDirectory
|
Метод |
Описание |
|
class procedure Copy(const SourceDirName, DestDirName: stri99"); |
Копирование
каталога
SourceDirName в каталог
DestDirName |
|
class prooedure CreateDirectory(Path: string); |
Создание каталога с путем Path |
|
class procedure Delete(const Path:
string); overload; class procedure Delete(const Path:
strinq;
const Recursive: Boolean); overload; |
Удаление
каталога Path. Вто-
рая версия метода
обеспечи:
вает
рекурсивный
вызов ме- тода
при
удалении
вложенных каталогов
(Recursive=true) |
|
class function Exists(const Path: string; FollowLink:
Boolean = True): Boolean; |
Проверка
существования
каталога Path |
|
class function GetAttributes(const Path: string; FollowLink: Boolean = True): TFileAttributes; |
Получение
атрибутов каталога |
|
class function GetCurrentDirectory: string; |
Выяснение текущего каталога |
|
class procedure SetCurrentDirectory(const Path: string); |
Установка текущего каталога |
|
class function GetLogicalDrives: TStringDynArray; |
Выяснение
текущих логических
дисков |
|
class function GetCreationTime(const Path:
string): TDateTime; static; class function GetCreationTimeUtc(const Path: string): TDateTime; |
Время создания каталога |
![]()
![]()
![]()
![]()
![]()
![]()
Таблица ПЗ.1 (продолжение)
|
Метод |
Описание |
|
class f'unction GetLastAccessTime(const Path: string): TDateTiпie; class function GetLastAccessTimeUtc(const Path: string): TDateTime; |
Время
последнего
доступа к
каталогу |
|
class function GetLastWriteTime(const Path:
string): TDateTime; class f'unction GetLastWriteTimeUtc(const Path: stri.ng) : TDateTime; |
Время
последней
записи в каталог |
|
class prooedure SetAttributes( const Path: string; const Attributes: TFileAttributes); |
Установка атрибутов каталога |
|
class prooedure SetCreationTime( const Path: string; const CreationTime: TDat�Time); class prooedure SetCreationTimeUtc(const Path: string; const
CreationTime: TDateTime); |
Установка
времени
создания каталога |
|
class prooedure SetLastAccessTime(const Path:
string; const LastAccessTime: TDateTime); class prooedure SetLastAccessTimeUtc(const Path: string; const LastAccessTime: TDateTime); |
Установка
времени
последне- го доступа
к каталогу |
|
class prooedure SetLastWriteTime(const Path:
string; const LastWriteTime: TDateTime); class prooedure SetLastWriteTimeUtc(const Path: string; const
LastWriteTime: TDateTime); |
Установка
времени
последней записи
в каталог |
|
class f'unction GetParent(const Path: string): string; |
Получение имени
родитель- ского
каталога |
|
class function GetDirectories(const Path: string): TStringDynArray; overload; class function GetDirectories( const Path: string; const Predicate: TFilterPredicate): TStringDynArray; overload; class
function GetDirectories(const Path, SearchPattern: string): TStringDynArray; overload;
class function GetDirectories(const Path, SearchPattern: string; const Predicate: TFilterPredicate): TStringDynArray; overload; - class f'unction GetDirectories(const Path, SearchPattern: string; const SearchOption: TSearchOption) : TStringDynArray; overload;
class function GetDirectories(const Path, SearchPattern: string; const
SearchOption: TSearchOption; const Predicate: TFilterPredicate): TStringDynArray; overload; class f'unction GetDirectories( oonst Path: string; const SearchOption: TSearchOption; const Predicate: TFilterPredicate): . TStringDynArray; overload; |
Сведения
о дочерних
каталогах, в
простейшем
случае
возвращается
полный перечень
каталогов. Расширенные версии
метода позволяют настраивать
критерии
выбора . |
![]()
Таблица ПЗ.1 (окончание)
|
Метод |
Описание |
|
class function GetDirectoryRoot(const Path:
string):
string; |
Получение
корневого каталога |
|
class function GetFiles(const Path: string): TStringDynArray; overload; class function GetFiles(const Path:
string; const Predicate: TFilterPredicate): TStringDynArray; overload; class function GetFiles(const Path, SearchPattern: string): TStringDynArray; overload; class function GetFiles(const Path,
SearchPattern: string; const Predicate: TFilterPredicate): TStringDynArray; overload; class function GetFiles(const Path,
SearchPattern: string; const SearchOption: TSearchOption): TStringDynArray; overload; class
function
GetFiles(const Path, SearchPattern: string; const SearchOption: T$earch0ption; const Predicate: TFilterPredicate): TStringDynArray; overload; class
function GetFiles(const Path:
string; const SearchOption: TSearchOption; const Predicate: TFilterPredicate): TStringDynArray; overload; |
Сведения
о дочерних
файлах.
В простейшей
нотации
метод возвратит
полный
перечень
файлов,
принадлежащих
ка талогу Path. Расширенные
версии функции позволяют
настраивать
критерии
отбора файлов |
|
class function GetFileSystemEntries(const Path: string): TStringDynArray; overload; class function GetFileSystemEntries(const Path: string; const Predicate: TFilterPredicate):
TStringDynArray; overload; class function GetFileSystemEntries(const Path, SearchPattern: string):
TStringDynArray; overload; class function GetFileSystemEntries(const Path, SearchPattern: string; oonst Predicate: TFilterPredicate): TStringDynArray; overload; class function GetFileSystemEntries(const Path: string; const SearchOption: TSearchOption; const Predicate: TFilterPredicate): TStringDynArray; overload; |
Сведения
о дочерних
файлах и каталогах. В
простейшей
нотации метод возвратит
полный
перечень файлов и
каталогов, принадлежащих
каталогу Path. Расширенные
версии
функции
позволяют
настраивать
критерии отбора
файлов
и каталогов |
|
class function IsEmpty(const Path: string): Boolean; |
Проверка на пустой каталог |
|
class function IsRelativePath(const Path: string): Boolean; |
Проверка относительного
пути |
|
class proaedure Move(const SourceDirName,
DestDirName: string); |
Перемещение
каталога |
Запись TFile предназначена для осуществления основных операций над файлами (создание, удаление, операции ввода/вывода, поиск). Методы записи представлены в табл. ПЗ.2.
Таблица ПЗ.2. Работа с
файлом средствами TFile
-
Метод
class function Create(const Path: string):
TFileStream; overload;
class function Create(const Path: string; const BufferSize: Integer) : TFileStream; overload;
class
procedure AppendAllText(const
Path, Contents: string); overload;
class procedure AppendAllText(const Path,
Contents: string; const Encoding:
TEncoding)
; overload;
class function AppendText(const
Path: string):
TStreamWriter;
class prooedure Copy(const
SourceFileName, DestFileName: string); overload;
class
prooedure Copy(const SourceFileNarne, DestFileName: string; const Overwrite:
Boolean); overload;
class function
CreateSymLink(const Link, Target:
string): ВOolean;
class function CreateText(const Path: string): TStreamWriter;
class
prooedure Decrypt(const Path: string);
class prooedure Delete(const Path: string); class
prooedure Encrypt(const Path: string);
class function Exists(const Path: string; FollowLink:
Boolean = True): ВOolean;
class function
GetAttributes(const Path: string; FollowLink: ВOolean = True): TFileAttributes;
class function GetCreationTime(const
Path: string):
TDateTime;
class function GetCreationTimeUtc(const Pa,th: string): TDateTime;
class function GetLastAccessTime(const Path: string): TDateTime;
class function
GetLastAccessTimeUtc(const Path: string): TDateTime;
class function GetLastWriteTime(const Path: string):
TDateTime;
class function GetLastWriteTimeUtc(const Path: string): TDateTime;
Описание
Создает
файл Path,
готовый
для
записи, и
возвращает
экземпляр потока TFileStream
Добавляет
к файлу
весь текст
из параметра Contents.
Расширенная версия функции
позволяет
обеспечить
шифрование
данных
Открывает
файл для добавле-
ния в него
текстовых
данных, возвращает
поток TStreamWriter
Копирует
файл SourceFileName в файл DestFileName
Создает ссылку на файл
Создает
текстовый
файл и
предоставляет
к нему доступ
в. виде потока TStreamWri ter
Расшифровывает файл
Удаляет файл Шифрует
файл
Проверяет существование
файла
Возвращает атрибуты файла
Возвращает время создания
файла
Возвращает время создания
файла
Возвращает время последнего
доступа к
файлу
Возвращает время последнего
изменения
файла
![]()
![]()
Таблица ПЗ.2 (продолжение)
|
Метод |
Описание |
|
class function GetSymLinkТarget(const FileName: string; SymLinkRec: TSymLinkRec) : Boolean;
overload; class function GetSymLinkTarget(const FileName: string; var TargetName: string): Boolean; overload; |
Дает ссылку на файл |
|
class proced:ure Move(SourceFileName, DestFileName: strinq); |
Перемещает файл |
|
class function Open(const Path:
string; const Mode: TFileМode) : TFileStream; overload; class function Open(const Path: strinq; const Mode: TFileMode; const Access: TFileAccess) : TFileStream; overload; class function Open(const Path: string; const Mode: TFileМode; const Access: TFileAccess; const Share: TFileShare) : TFileStream; overload; |
Открывает файл Path и пре-
доставляет
к нему
доступ при посредничестве файлового
потока TFileStream |
|
class function OpenRead(const Path: string): TFileStream; |
Открь1вает файл только для чтения |
|
class function OpenText(const Path:
string).: TStreamReader; |
Открывает
файл для
доступа к тексту |
|
class function OpenWrite(const Path:
string): TFileStream; |
Открывает файл для записи |
|
class function ReadAllBytes(const Path:
string): TBytes; |
Открывает
файл и предоставляет
к нему
доступ в виде массива
байтов |
|
class function ReadAllLines(const Path:
strinq): TStringDynArray; overload; class function ReadAllLines( const Path: string; const Encoding: TEncoding): TStringDynArray; overload; |
Читает
строки из
файла и воз-
вращает
их в виде массива
строк |
|
class function ReadAllText(const Path:
stri.ng): string; overload; class function ReadAllText(const Path:
string; const Encoding: TEncoding) : string; overload; |
Читает данные
из файла и
возвращает
их в
формате строки |
|
class
procedure Replace(const SourceFileName, DestinationFileName, DestinationBackupFileName: string); overload; class
prooedure Replace(SourceFileName,
DestinationFileName, DestinationBackupFileName: string; const IgnoreMetadataErrors: Boolean); overload; |
Заменяет
файл с именем
DestinationFileName файлом
SourceFileName |
|
class prooedure SetAttributes(const Path:
string; const Attributes: TFileAttributes); |
Устанавливает атрибуты
файла |
|
class procedure SetCreationTime(const Path:
string; const CreationTime: TDateTime); class proced:ure SetCreationTimeUtc(const Path: string; const
CreationTime: TDateTime); |
Устанавливает
время
создания файла |
![]()
|
Метод |
Описание |
|
class prooedure SetLastAccessTime(const Path: string; const LastAccessTime: TDateTime); class procedure SetLastAccessTimeUtc(const Path: string; const LastAccessTime: TDateTime); |
Устанавливает
время последнего
доступа к
файлу |
|
class prooedure SetLastWriteTime(const Path:
string; const LastWriteTime: TDateTime); class prooedure SetLastWriteTimeUtc(const Path: string; const
LastWriteTime: TDateTime); |
Устанавливает
время последнего
изменения файла |
|
class prooedure WriteAllBytes(const Path: string; const Bytes: TBytes); |
Записывает в файл
данные из
массива
байтов Bytes |
|
class
proaedure WriteAllLines(const Path:
string; const Contents: TStringDynArray); overload; static; class
procedure WriteAllLines(const Path:
string; const Contents: TStringDynArray; const Encoding:
TEncoding) ; overload; |
Записывает
в файл
все строки из массива • |
|
class prooedure WriteAllText(const Path, Contents: string); ovarload; class prooedure WriteAllText(const Path, Contents: string; const
Encoding: TEncoding); overload; |
Записывает
в файл
текст из
параметра Contents |
![]()
Запись TPath специализируется на обслуживании путей и
имен файлов и папок
(табл. ПЗ.3).
Таблица ПЗ.3. Путь TPath
|
Методы III свойства |
Описание |
|
class function IsValidPathChar(const AChar:
Char): Boolean; |
Проверка
символа на допустимость
применения в пути |
|
class function IsValidFileNameChar(const AChar: Char): Boolean; |
Проверка
допустимости
символа для использования
в
имени файла |
|
class function HasValidPathChars(const Path: string;
const UseWildcards: Boolean): Boolean; |
Проверка допустимости
символов
в пути |
|
class function HasValidFileNameChars(const FileName: string; const
UseWildcards: Boolean): Boolean; |
Проверка допустимости
символов в имени
файла |
|
class function GetExtendedPrefix(const Path: string): TPathPrefixType; TPathPrefixType = (pptNoPrefix, pptExtended, pptExtendedUNC); |
Возвращает
тип пути,
если путь
начинается
с префикса , \ \ ?\', будет возвращено pptExtended;ecли С '\\?\UNC\' - |
|
|
pptExtendedUNC; иначе - pptNoPrefix |
![]()
![]()
Таблица nз.з (продолжение)
|
Методы и
свойства |
Описание |
|
class function IsDriveRooted(const Path: string): Boolean; |
Проверяет, находится ли в корне пути имя логического
диска |
|
class function IsExtendedPrefixed(const Path:
string): Boolean; |
Проверяет, не начинается
ли путь с
префикса '\\?\UNC\' |
|
class function IsRelativePath(const
Path: string): Boolean; |
Проверяет,
является ли путь относительным |
|
class function IsUNCPath(const Path:
string):
Вoolean; |
Проверяется,
является ли
путь универсальным (UNC) |
|
class function IsUNCRooted(const Path: string): Boolean; |
Проверяется,
находится ли в
корне пути
префикс '\\?\UNC\' |
|
class function GetGUIDFileName(const UseSeparator: Boolean = False): string; |
Возвращает
глобальный
универсальный идентифика-
тор файла |
|
class function
DriveExists(const Path: string): Boolean; |
Проверяет, имеется
ли в
составе пути логический ДИСК |
|
class function MatchesPattern(const FileName, Pattern: string; const CaseSensitive: Boolean): Boolean; |
Проверяет
соответствие
имени FileName шаблону
Pattern с учетом
(или без
учета)
регистра CaseSensitive |
|
class function ChangeExtension(const
Path, Extension: string) : string; |
Изменяет расширение
имени файла |
|
class function ComЬine(const Pathl,
Path2: string): string; |
Проверяет
два пути к одно-
му и тому
же ресурсу
и воз-
вращает
абсолютный
путь |
|
class function GetDirectoryName(FileName: string): string; |
Возвращает путь к файлу FileName |
|
class functiorl" GetExtension (
const FileName: string): string; |
Возвращает расширение
имени файла |
|
class function GetFileName(const FileName: string): string; |
Возвращает только имя файла из полного пути |
|
class function GetFileNameWithoutExtension(const FileName: string): string; |
Возвращает
имя файла без
расширения
имени |
|
class function GetFullPath(const Path: string): string; |
Возвращает полный путь |
|
class function GetinvalidFileNameChars: TCharArray; |
Возвращает массив с недопустимыми символа- ми в имени файла |
|
class function GetinvalidPathChars: TCharArray; |
Возвращает массив с недопустимыми символа- ми в пути |
![]()
![]()
|
Методы и
свойства |
Описание |
|
class function GetPathRoot(const Path: string): string; |
Возвращает корневой эле-
мент в пути |
|
class function GetRandomFileName: string; |
Возвращает случайное имя файла |
|
class function GetTempFileName: string; |
Возвращает полный путь и имя для временного файла |
|
class function GetTempPath: string; |
Возвращает
путь к времен-
ной папке |
|
class function GetHomePath: string; |
Возвращает путь к папке Roaming |
|
class function GetDocшnentsPath: string; |
Путь
к документам |
|
class function GetSharedDocшnentsPath:
string; |
Путь к
общедоступным
документам |
|
class function GetLibraryPath: string; |
Путь
к папке с
библиотеками |
|
class function GetPicturesPath: string; |
Путь
к папке с
рисунками |
|
class function GetSharedPicturesPath: string; |
Путь к
общедоступной
папке с
рисунками |
|
class function GetAttributes(const Path: string; FollowLink: Boolean
= True): TFileAttributes; |
Возвращает атрибуты файла |
|
class proaedure SetAttributes(const Path:
string; const Attributes: TFileAttributes); |
Устанавливает атрибуты
файла |
|
class function
HasExtension(const Path: string): Boolean; |
Проверяет наличие расши-
рения имени |
|
class function IsPathRooted(const Path:
string): Boolean; |
Проверяет
наличие в пути
корневого
элемента |
|
class property ExtensionSeparatorChar: Char; |
Хранит
символ-разделитель
имени и
расширения
имени, по
умолчанию ' . ' |
|
class property AltDirectorySeparatorChar: Char; |
Альтернативный
символ-
разделитель между именами
каталогов,
по
умолчанию '/' |
|
class property DirectorySeparatorChar: Char; |
Символ-разделитель
между именами каталогов,
по
умолчанию ' \ ' |
|
class property PathSeparator: Char; |
Символ-разделитель между
путями, по
умолчанию ' ; ' |
|
class property VolumeSeparatorChar: Char; |
Символ-разделитель имени
диска,
умолчанию • : • |
ПРИЛОЖЕНИЕ 4
![]()
Программный
модуль system. Sensors содержит
определение
многочисленных
уст
ройств-датчиков,
которые моrут
быть задействованы
в мобильных
устройствах.
Доступ к
датчикам осуществляется
при
посредничестве
менеджера тsensorмanager.
Описание базовых
классов
датчиков представлено в табл.
П4.1-П4.6 и в
главе 25.
Таблица П4.1. Датчик движения TCustomМotionSensor
|
Свойства/методы |
Описание |
|
property Started: Вооlеаn;//только чтения |
Проверка активности датчика |
|
property SensorType: TMotionSensorType; ТМotionSensorType = (AccelerometerlD,
Accelerometer2D, AccelerometerЗD, MotionDetector, GyrometerlD, Gyrometer2D, GyrometerЗD, Speedometer); |
Тип датчика |
|
property AccelerationX: DouЬle; ' property AccelerationY: DouЬle; property AccelerationZ: DouЬle; |
Ускорение
по осям х, у и z |
|
property AngleAccelX: DouЬle; property AngleAccelY: DcuЬle; property AngleAccelZ: DcuЬle; |
Угловое ускорение по осям х, у и z в градусах/секунды2 |
|
property Motion: DouЬle; |
Состояние движения |
|
property Speed: DouЬle; |
Скорость в метрах в секунду |
|
property Updatelnterval: DouЬle; |
Частота обновления данных |
Таблица П4.2. Датчик электрического устройства тcustomElectricalSensor
|
Свойства/методы |
Описание |
|
property
SensorType: TElectricalSensorType;
TElectricalSensorType = (Voltage, Current, Capacitance, Resistance, Inductance, ElectricalPower, Potentiometer); |
Тип электрического датчика |
Датчики 443
Таблица П4.2 (окончание)
|
Свойства/методы |
Описание |
|
property Capacitance: DouЫe; |
Емкость в фарадах |
|
property Resistance: DouЬle; |
Сопротивление в
омах |
|
property Inductance: DouЫe; |
Индуктивность в генри |
|
property Current: DouЬle; |
Сила тока в амперах |
|
property Voltage: DouЫe; |
Напряжение в вольтах |
|
property Power: DouЬle; |
Мощность в ваттах |
Таблица П4.З. Датчик механического устройства тcustomМechanicalSensor
|
Свойства/методы |
Описание |
|
property SensorType: ТМechanicalSensorType; TMechanicalSensorType = (BooleanSwitch,
BooleanSwitchArray, MultiValueSwitch, Force,
Scale, Pressure, Strain); |
Тип механического датчика |
|
property SwitchState: Boolean; |
Состояние: включен/выключен |
|
property SwitchArrayState: Cardinal; |
Состояние для
многопозицион-
наго
переключателя |
|
property Force: DouЬle; |
Сила в ньютонах |
|
property AЬsPressure: DouЫe; |
Абсолютное
давление в паскалях |
|
property GaugePressure: DouЬle; |
Манометр, возвращает
давление в паскалях |
|
property Strain: DouЫe; |
Натяжение |
|
property Weight: DouЫe; |
Вес в килограммах |
Таблица П4.4. Биометрический датчик TCustomBiometricSensor
![]()
![]()
![]()
444 Приложение 4
Таблица П4.5. Датчик освещения TCustomLightSensor
|
Свойства/методы |
Описание |
|
property SensorType: TLightSensorType; TLightSensorType =
(AmЬientLight,) ; |
Тип датчика |
|
property Lux: DouЫe; |
Освещенность в люксах |
|
property Temperature: DouЬle; |
Цветовая температура |
|
property Chromacity: DouЬle; |
Цветность |
Таблица П4.6. Датчик-сканер TCustomScannerSensor
|
Свойства/методы |
Описание . |
|
property SensorType: TScannerSensorType;
TScannerSensorType = (RFID, Barcode); |
Тип датчика |
|
property RFIDТag: Uint64; |
40-битное
значение RFID-метки,
используемой для
радиочастотной
идентификации |
|
property BarcodeData: string; |
Отсканированные
данные штрихкода |
![]()
![]()
ПРИЛОЖЕНИЕ 5
По ссылке ftp://ftp.bhv.ru/9785977532891.zip можно скачать электронный архив
с
рассмотренными проектами
и исходными
кодами
примеров. Эта ссылка
доступна со
страницы
книги на сайте
www.bhv.ru.
Архив следует распаковать в папку c:\examples.
Папка
c:\examples содержит
каталоги с
исходными кодами
примеров,
названия
вложенных
папок построены
по
следующему принципу:
<сh><номер главы>_<номер примера в
главе>
Например:
ch0l_0l - глава 1 пример 1;
ch01_02 - глава 1 пример 2;
сhЗО_Оl-главаЗОпример 1.
Кроме того, в
папке ch00 вы найдете общие примеры для всего
материала книtи.
![]()
1.
Online Help for DelphiФ XES and C++BuilderФ ХЕ5. [Электронный
ресурс]
/ URL: http://docwiki.embarcadero.com/RADStudio/XE5/en/Мain_Page (дата
обращения: 05.11.2013).
2.
RAD Studio IOS
Tutorials: Delphi IOS Application Development. Version ХЕ4. - Embarcadero. Product Documentation, April, 2013.
- 142 р.
3.
Иванов В.
П., Батраков А.
С. Трехмерная компьютерная графика/ Под ред. Г. М. Полищука. -М.: Радио и
связь, 1995. -224 с: ил.
4.
MИJUiep Т.
DirectX 9 с
управляемым кодом. Программирование игр и
графи ка. -М.:
Издательский
дом "КомБук", 2005.
- 400 с.
5.
Осипов Д.
Л. Delphi ХЕ2. -СПб.: БХВ-Петербург, 2012. -912 с.: ил.
6.
ОсипQв Д.
Л. Базы данных и
Delphi. Теория и
практика. - СПб.: БХВ
Петербург, 2011. - 752 с.: ил.
7.
Хилл Ф. OpenGL. Программирование компьютерной графики. Для профессио
налов. - СПб.: Питер, 2002. - 1088 с.: ил.
8.
Шикин А. В., Боресков А. В. Компьютерная графика. Полигональные модели. - М.:
Диалог-МИФИ, 2001.
- 464 с.
ТAffineTransformEffect:
◊ Center 258
IFMXNotificationCenter: · ◊ Rotation 258
◊
CancelAllNotifications 363 ◊ Scale 258
◊
Cance!Notification 363 ТAlphaTrackВar:
◊
PresentNotification 363 ◊ Мах 218
◊
ResetlconBadgeNumber
363 ◊ Min218
◊
ScheduleNotification 363 ◊ OnChange 218
◊
SetlconВadgeNumber 363 ◊ Value 218
IFMXPhoneDialerService: TApplication:
◊
Call
368 ◊ DefaultTitle 90
◊
GetCarrier 367 ◊ FormFactor 94
◊
GetCuпentCalls 368 ◊ LastKeyPress 94
◊ LastUserActive 94
L ◊ MainForm 89
◊ OnActionUpdate 93
LiveBindings 61, 303 ◊ OnException 91
◊ Onldle 92
т ◊ Title 89
ТАпау<>:
TAction: ◊ BinarySearch 48
◊
ActionList 131 ◊ Sort 48
◊
Category 131 ТAudioCaptureDevice:
◊
Execute 130 ◊ Description 277
◊
Index 131 ◊ IsDefault 277
◊
OnExecute 130 ◊ MediaType 277
◊
OnUpdate 130 ◊ Name
277
ТActionList: ◊ StartCapture 277
◊
ActionCount 133 ◊ State 277
◊
Actions 133 ◊ StopCapture 277
◊
ExecuteAction 133 ◊ UniqueID 277
◊
OnChange 134 ТAudioCaptureDevice:
◊
OnExecute 130, 134 ◊ FilterString 277
◊
OnStateChange 134 ◊ FileName 277
◊
OnUpdate 134 TBandedSwirlEffect:
◊
State 134 ◊ AspectRatio 251
TBandedSwir\Effect (прод.):
◊
Bands 250
◊ Center 250
◊ Strength 250 TBandedSwir!TransitionEffect:
◊ Center 261
◊
Frequency 262
◊
Progress 261
◊
Strength 262
◊ Target 261 TBandsEffect:
◊
BandDensity 249
◊ Bandlntensity 249 TBindExpression:
◊ Active 314
◊ Contro!Component 313 ·
◊
Contro\Expression 313
◊
Direction 313
◊
Managed 315
◊ OnActivated 315
◊ OnActivating 315
◊ OnAssignedValueEvent 315
◊ OnAssigningValueEvent
315
◊ OnEva\ErrorEvent 315
◊ SourceComponent 313
◊ SourceExpression 313 TBindings:
◊ CreateAssociationScope
318
◊ CreateExpression 317
◊ CreateManagedBinding 318
◊ CreateUnmanagedBinding 318 TBindList:
◊
AutoFill 322
◊ Contro!Component 322
◊ FillList 322
◊ FormatExpressions 322
◊ SourceComponent 322 TBitmap:
◊ BitmapScale 237
◊
CanvasClass 237
◊ Clear 238
◊ ClearRect 238
◊ CopyFromBitmap 238
◊ Create 235
◊ CreateFromBitmapAndMask
235
◊ CreateFromFile 235
◊ CreateFromStream 235
◊ CreateThumbnail 237
◊
FlipHorizontal 237
◊
FlipVertical 237
◊ Handle 237
◊ Height 237
◊
InvertAlpha 238
◊
IsEmpty 236
◊
LoadFromFile 235
◊
LoadFromStream 235
◊
LoadThumbnailFromFile 237
◊ Мар 238
◊
PixelFormat 237
◊ ReplaceOpaqueColor 238
◊ Resize 237
◊
Rotate 237
◊
SaveToFile 235
◊ SaveToStream 235
◊ Unmap 238
◊ Width 237 TBitmapAnimation:
◊ Delay 265
◊
Duration 265
◊ Interpolation 266
◊
Inverse 267
◊
Loop
266
◊ OnFinish 266
◊ OnProcess 266
◊
Pause 266
◊
Running 266
◊ Start 266
◊ StartValue 268
◊ StopValue 268 TBitmapCodecManager:
◊ GetFilesTypes 236
◊
GetFilterString 236
◊ IsCodedExists 236 TBitmapListAnimation:
◊ AnimationBitmap 268
◊
AnimationCount 269
◊
Delay 265
◊
Duration 265
◊ lnterpolation 266 •
◊ Inverse 267
◊ Loop 266
◊ OnFinish 266
◊ OnProcess 266
◊
Pause 266
◊
Running 266
◊ Start 266
◊ Stop 266
![]()
TBloomEffect: ◊ DrawPolygon 230
◊
Baselntensity 247 ◊ DrawRect 229
◊ BaseSaturation 247 ◊ DrawRectSides 229
◊
Bloomlntensity 247 ◊ DrawThumbnail 234
◊
BloomSaturation 247 ◊ EndScene 223
TB\urEffect, Softness 248 ◊ ExcludeClipRect 234
TBoxB\urEffect, BlurAmount 248 ◊ Fil\
224
TBrush: ◊ Fil\Arc 228
◊ Bitmap 225 ◊ Fil\Ellipse 228
◊ Color225 ◊ Fil\Path 228
◊ Gradient 225 ◊ FillPolygon 228
◊
Kind 225 ◊ Fil\Rect 228
◊
OnChanged 225 ◊ Fil\Text 232
◊
OnGradientChanged 225 ◊ Font 224
◊
Resource 225 ◊ Height 224
TButton, Action 129 ◊ lntersectC\ipRect 234
ТВWTrackВar: ◊ LoadFontFromStream 232
◊
Мах 218 ◊ RestoreState 234
◊ Min 218 ◊ SaveState 234
◊ OnChange 218 ◊ Stroke 224
◊
Value 218 ◊ TextHeight 232
TCalendar: ◊ TextToPath 232
◊ Date 210 ◊ TextWidth 232
◊ FirstDayOfWeek 210 ◊ Width 224
◊
OnChange 210 TCaptureDeviceManager:
◊
OnDateSe\ected 21О ◊ Count 275
TCalendarEdit: ◊ Current 275
◊
Date 210 ◊ DefaultAudioCaptureDevice 276
◊
FirstDayOfWeek 210 ◊ DefaultVideoCaptureDevice 276
◊
OnChange 21О ◊ Devices 275
◊
OnClosePopup 211 ◊ GetDevicesByName 276
◊ OnDateSelected 21О TCarrier:
◊ OnPopup 211 ◊ GetCarrierName 368
TCamera, Target 383 ◊ GetlsoCountryCode 367
TCameraComponent: ◊ GetMoЬileCountryCode 367
◊
Active 280 ◊ GetMoЬileNetwork 368
◊
Kind 280 ◊ OnCarrierChanged 368
TCanvas: TCircleTransitionEffect:
◊ BeginScene 223 ◊ Center 261
◊
BufferBits 224 ◊ FuzzyAmount 262
◊
Buffered 224 ◊ Progress 261
◊
BufferHandle 224 ◊ Size 262
◊
Clear 224 ◊ Target 261
◊ ClearRect 224 TColorAnimation:
◊
DrawArc 229 ◊ Delay 265
◊ DrawBitrnap 233 ◊ Duration 265
◊
DrawEl\ipse 229 ◊ Interpolation 266
◊ DrawLine 229 ◊ Inverse 267
◊ DrawPath 229 ◊ Loop
266
TColorAnimation (прод.): TComboTrackВar:
◊ Onfinish 266 ◊ Decima!Digits 156
◊ OnProcess 266 ◊ Мах 156
◊
Pause 266 ◊ Min
156
◊ Running 266 ◊ OnChange 157
◊ Start 266 ◊ OnChangeTracking 157
◊ StartValue
268 ◊ TrackВar 157
◊ Stop 266 ◊ Value 156
◊ StopValue 268 ◊ ValueType 156
TColorBox, Color 217 TCommonCustomform, ObjectAtPoint 106
TColorComboBox: TCommonDialog:
◊
Color 216 ◊ OnClose 197
◊ OnChange 216 ◊ OnShow 197
TColorKeyAlphaEffect: TComponent:
◊ ColorKey 247 ◊ ComponentCount 39
◊
Tolerance 247 ◊ Componentlndex 39
TColorListВox: ◊ Components 39
◊
Color 216
◊
OnChange 216
◊
Create 39
TColorMaterialSource, Color 399 ◊ DestroyComponents 39
TColorPanel: ◊ FindComponent 40
◊
Color 216 ◊ InsertComponent 39
◊
OnChange 216 ◊ Name38
◊ UseAlpha 216 ◊ Owner39
TColorPicker: ◊ RemoveComponent 39
◊ Color 217 ◊ Tag38
◊
ColorQuad 217 TContext3D:
◊
Hue
216 ◊ AddLight 414
TColorQuad: ◊ BeginScene 407
◊
ColorBox 217 ◊ CurrentCameralnvMatrix 416
◊ Hue 217 ◊ CurrentCameraМatrix 415
◊ Lum 217 ◊ CurrentPixelShader
417
◊ OnChange 217 ◊ CurrentProjectionMatrix 416
◊ Sat 217 ◊ CurrentVertexShader 417
TColumn, Header 187 ◊ DeleteLight 414
TComboBox: ◊ DrawCube 409
◊ Clear 169 ◊ DrawLiqe 408
◊
Count 169 ◊ DrawLines 411
◊ DropDown 171 ◊ DrawPoints 411
◊ DropDownCount 170 ◊ DrawRect 409
◊ Itemlndex 169 ◊ DrawTriangles 411
◊ Items 169
◊
Listltems 169
◊ OnChange 171
◊
Placement 170
◊
EndScene 407
◊
FillCube 409
◊
FillPolygon 410
◊ Selected 169 ◊ Lights 413
TComboColorBox: ◊ SetMatrix_414
◊
Color 216 ◊ VertexShader 417
◊ OnChange 216 TContextНelper:
◊
UseAlpha 216 ◊ DrawCube 409
TComboEdit, Items 172 ◊ DrawLine 408
![]()
![]()
![]()
◊ DrawRect 409 TCropEffect:
◊ FillCube 409 ◊ LeftTop 258
◊ FillPolygon 410 ◊ RightBottom 259
TControl: TCustomBiometricSensor:
◊ Align 64 ◊ BarcodeData 443
◊ DragMode 79 ◊ HumanPresense 443
◊
Height 63 ◊ HumanProximity 443
◊
IsDragOver 70 ◊ Touch 443.
◊
IsFocused 70 TCustomEdit:
◊
IsMouseOver 70 ◊ ContentRect 151
◊ IsVisiЫe 70 ◊ CopyToClipboard 150
◊
MakeScreenshot82 ◊ CutТoClipboard 150
◊
Margins 64 ◊ FilterChar 148
◊
OnCanFocus 77 ◊ FontColor 151
◊
OnClick 70 ◊ GetCharХ 151
◊ OnDЫClick 70 ◊ KeyboardType 146
◊ OnDragDrop 81 ◊ OnChangeTracking 147
◊ OnDragEnd 82 ◊ OnTyping 150
◊ OnDragEnter 81 ◊ Password 148
◊
OnDragLeave 81 ◊ PasteFromClipboard 150
◊ OnDragOver 80 ◊ ReadOnly 148
◊ OnEnter 77 ◊ SelectAll 149
◊
OnExit 77 ◊ SelectionFill 151
◊
OnGesture 283 ◊ SelectWord 149
◊
OnKeyDown 73 ◊ SelLength 149
◊
OnKeyUp 73 ◊ SelStart 149
◊ OnMouseDown 75 ◊ SelText 148
◊
OnMouseEnter 75 ◊ Text 147
◊
OnMouseLeave 75 ◊ Text MaxLength 148
◊ OnMouseMove 75 ◊ Typing 150
◊ OnMouseUp 75 TCustomLightSensor:
◊ OnMouseWheel 77 ◊ BarcodeData 444
◊
OnPaint 82 ◊ Chromacity 444
◊
OnPainting 82 ◊ Lux 444
◊
OnResize 77 ◊ Temperature 444
◊
Opacity 68 'rCustomMechanicalSensor:
◊
Padding 64 ◊ AbsPressure 443
◊
PaintТo 82 ◊ BarcodeData 443
◊
PopupMenu 124 ◊ Force 443
◊ Position 63 ◊ GaugePressure 443
◊
RotationAngle 66 ◊ Strain 443
◊
RotationCenter 66 ◊ SwitchAпayState 443
◊
Scale 66 ◊ SwitchState 443
◊
Skew 66 ◊ Weight443
◊ VisiЫe 68 TCustomMesh:
◊ Width 63 ◊ Data
387
TCriticalSection: ◊ IndexВuffer 387
◊
Enter 336 ◊ Normals 387
◊
Leave 336 ◊ Points 387
![]()
TCustomMesh (прод.):
◊
Trianglelndices 387
◊ VertexBuffer 387 TCustomMotionSensor:
◊ AccelerationX 442
◊ Acceleration У 442
◊
AccelerationZ 442
◊
AngleAccelX 442
◊
AngleAccelY 442
◊
AngleAccelZ 442
◊
Motion 442
◊ SensorType 442
◊
Speed 442
◊ Updatelnterval 442 TCustomScannerSensor:
◊
BarcodeData 444
◊
RFIDTag 444
◊ SensorType
444 TDictionary:
◊
ContainsKey 54
◊
ContainsValue 54
◊
ToArray 55
◊ TryGetYalue
54 TDictionary<>:
◊
Add
54
◊
AddOrSetValue 54
◊
Clear 54
◊
Create 53
◊
ExtractPair 54
◊
Items 54
◊ Remove 54 TDirectionalВlurEffect:
◊
Angle 248
◊ BlurAmount 248
◊ Center 248
TDirectory:
◊ Сору 434
◊ CreateDirectory 434
◊ Delete 138, 434
◊
Exists 137,434
◊
GetAttributes 140, 434·
◊
GetCreationTime 434
О GetCurrentDir 138
◊
GetCurrentDirectory 434
◊
GetDirectories 435
◊
GetDirectoryRoot 436
◊
GetFiles 436
◊
GetFileSystemEntries 436
◊
GetLastAccessTime 435
◊
GetLastWriteTime 435
◊ GetLogicalDrives 135,434
◊
GetParent 435
◊
lsEmpty 436
◊
IsRelativePath 436
◊
Move436
◊ SetAttributes 140,435
◊
SetCreationTime 435
◊
SetCurrentDir 138
◊
SetCurrentDirectory 434
◊ SetLastAccessTime 435
◊ SetLastWriteTime 435 TEdit:
◊ CopyToClipboard 150
◊ CutТoClipboard 150
◊
KeyboardType 146
◊ OnChange 147
◊
PasteFromClipboard 150
◊ SelectAll 149
◊ SelectWord 149
◊
SelLength 149
◊ SelStart 149
◊
SelText 148
◊ Text 147 TElectricalSensorType:
◊ Capacitance 443
◊ Current 443
◊
Inductance 443
◊
Power 443
◊
Resistance 443
◊ SensorType 442
◊ Voltage 443 TEmbossEffect:
◊
Amount255
◊ Width 256
TEvent:
◊ Create 333
◊
LastError 334
◊
ResetEvent 334
◊ SetEvent 334
◊ WaitFor 334 TFadeTransitionEffect:
◊
Progress 261
◊ Target 261
TFile:
◊
AppendAIIText 437
◊ Сору 437
◊ Create 139,437
◊ CreateText 437
◊
Decrypt 437
◊
Delete 437
![]()
![]()
◊
Encrypt 437 ◊ Release 56
◊
Exists 137, 437 ◊ RemoveFreeNotify 57
◊
GetAttributes 140,437 ◊ RemoveObject 59
◊
GetCreationTime 437 ◊ Sort
59
◊
GetCreationTimeUtc 437 ◊ Touch 283
◊
GetLastAccessTime 437 TFont:
◊
GetLastWriteTime 437 ◊ Family 227
◊
GetSymLinkTarget 438 ◊ Size
227
◊
Move438 ◊ Style 227
◊ Open 140, 438 TForm:
◊
OpenRead 438 ◊ Caption 102
◊
OpenText 438 ◊ Close 101, 104
◊ ReadAllBytes 438 ◊ Create 99
◊ ReadAllLines 438 ◊ FormFactor 106
◊ Replace 438 ◊ FormFamily 106
◊
SetAttributes 140 ◊ FullScreen 100
◊ SetCreationTime 438 ◊ Hide
101
◊
SetLastAccessТime
439 ◊ Moda!Result 1О1
◊
SetLastWriteTime 439 ◊ ObjectAtPoint 106
◊ WriteAllLines 439 ◊ OnActivate 103
◊ WriteAl!Тext 439 ◊ OnClose 104, 105
TFillEffect, Color 246 ◊ OnCloseQuery 104 ,
TFillRGBEffect, Color 246 ◊ OnDeactivate 104
TFloatAnimation: ◊ OnDestroy 104, 105
◊ Delay 265 ◊ Position 102
◊ Duration
265 ◊ Quality 107
◊ Interpolation 266 ◊ Release 102
◊ Inverse 267 ◊ Show99
◊ Loop 266 ◊ ShowFullScreenlcon 100
◊ OnProcess
266 ◊ ShowModal 100
◊ Pause 266 ◊ StyleBook 114
◊ Running 266 ◊ Touch 283
◊
Start 266 ◊ WindowState 103
◊
StartValue
269 TForm3D:
◊
Stop 266 ◊ Camera 383
◊ StopValue
269 ◊ Color 110
TFmxObject: ◊ Context 109
◊
Action 61 ◊ Multisample 110 ·
◊ AddObject
58 ◊ OnRender 109
◊
Children 57 ◊ UsingDesignCamera 110,383
◊
ChildrenCount 57 TFormFactor:
◊
Clone 56 ◊ Devices 94
◊ DeleteChildren 60 ◊ Height 94
◊ Exchange 59 ◊ Orientations 94
◊ FreeNotification 57 ◊ Width 94
◊
HasParent 58 TGaussianBlurEffect, BlurAmount 248
◊ Index 58 TGeocoder:
◊ InsertObject 58 ◊ Coord 353
◊ IsChild 58 ◊ Current 355
◊ Parent 58 ◊ Geocode 353
![]()
TGeocoder (прод.): ТНueTrackВar:
◊
GeocodeReverse 353 ◊ Мах
218
◊
Geocoding 356 ◊ Min 218
◊
OnGeocode 354 ◊ OnChange 218
◊
OnGeocodeReverse 354 ◊ Value 218
TGloomEffect: ТlnnerGlowEffect:
◊
Baselntensity 247 ◊ GlowColor 255
◊
BaseSaturation 247 ◊ Softness 255
◊ Gloomlntensity 247 TLabel:
◊ GloomSaturation 247 ◊ AutoSize 144
TGlowEffect: ◊ Font 143
◊ GlowColor 255 ◊ FontColor 144
◊
Softness 255 ◊ StyledSettings 144
TGradient: ◊ Text 143
◊ Color 219 ◊ TextAlign 144
◊
InterpolateColor 219 ◊ TextSettings 144
◊
Points 219 ◊ VertTextAlign 144
◊
Radia!Тransform 219 ◊ WordWrap 144
◊
StartPosition 219 TLang:
◊
StopPosition 219 ◊ AddLang 344
◊
Style 219 ◊ FileName 344
TGradientAnimation: ◊ Lang
343
◊
Delay 265 ◊ LangStr 344
◊
Duration 265 ◊ LoadFromFile 344
◊
Interpolation 266 ◊ Original 344
◊
Inverse 267 ◊ Resources 344
◊
Loop 266 ◊ SaveToFile 344
◊
OnFinish 266 ◊ StorelnForm 344
◊
OnProcess 266 TLight:
◊
Pause 266 ◊ Color 381
◊
Running266 ◊ LightType 380
◊
Start 266 ◊ SpotCutOff381
◊
StartValue 268 ◊ SpotExponent 381
◊
Stop
266 TListBox, MultiSelect 165
◊
StopValue 268 TListBoxltem:
TGradientEdit: ◊ AddObject 162
◊
CuпentPoint 220 ◊ Clear
162
◊
Gradient 219 ◊ ClearSelection 166
·о OnChange 220 ◊ Columns 168
◊
OnSelectPoint 219 ◊ Data 159
TGrid: ◊ Index 160
◊
Cel!ControlByRow. 187 ◊ InsertObject 162
◊
ColumnByPoint 187 ◊ IsChecked 160
◊
ColumnCount 187 ◊ IsSelected 160
I ◊ Columns 187 ◊ ItemBylndex 165
◊
OnEdititingDone 188 ◊ ItemByPoint 165
◊
OnGetValue 188 ◊ ItemData 160
◊
OnSetValue 187 ◊ ItemHeight 168
◊
RowCount 187 ◊ Itemlndex 164
THueAdjustEffect, Hue 247 ◊ Items 161
![]()
◊
ItemWidth 168
◊ Listltems 165
◊ ListStyle 168
◊ OnChange 169
◊ OnChangeCheck 169
◊ OnCompare 167, 169
◊
RemoveObject 162
◊ SelectAll 166
◊ Selected 164
◊ Sorted 166
◊
Text 159 TListBoxltemData:
◊ Accessory 160
◊ Bitmap 160
◊ Detail 160
◊
Text 160 TLocationSensor:
◊
Active 350
◊ OnLocationChanged 351 TMagnifierGlass:
◊
BackgroundColor 361
◊
LoupeMode 361
◊
LoupeScale 361
◊ ZoomRegionCenter 361 TMagnifyEffect:
◊
AspectRatio 252
◊ Center 251
◊
Magnification 251
◊ Radius 251 TMagnifyTransitionEffect:
◊ Center 261
◊
Progress 261
◊ Target 261
TMatrix3D:
◊
CreateRotation 430
◊
CreateRotationX 430
◊
CreateRotationУ 430
◊ CreateRotationYawPitchRoll 431
◊ CreateRotationZ 430
◊ CreateScaling 431·
◊ CreateTranslateMatrix3D
430
◊
Determinant 433
◊
Inverse 433
◊
Scale 431
◊ Transpose
433 TMediaPlayer:
◊ Clear 274
◊
CurrentTime 274
◊ Duration 274
◊
FileName 273
◊
Play
274
◊
State 274
◊
Stop
274
◊
VideoSize 274
◊
Volume 274
TMediaPlayerControl, TMediaPlayer 274 TMemo:
◊ ClearSelection 154
◊ CopyToClipboard 154
◊ CutToClipboard 154
◊ GoToLineBegin 156
◊ GotoLineEnd 156
◊ GoToTextBegin 155
◊
GoToTextEnd 155
◊
Lines 153
◊
PasteFromClipboard 154
◊ SelectAll 154
◊ SelLength 154
◊ SelStart 154
◊ SelТext 154
◊ Text 153 TMenultem:
◊ Action 129
◊
AutoCheck 122
◊
Grouplndex 122
◊
IsChecked 121
◊
OnClick 121
◊
Radioltem 123
◊
ShortCut 121
◊
Text
120
TMesh, Data 387
TMeshData, CalcNormals 388 TModelЗD:
◊ MeshCollaction 395
◊ ReadModel
396 TMutex:
◊ Acquire 338
◊ Create 337
◊ Release 338
ТNormalBlendEffect,
Target 261 ТNumberBox:
I ◊ DecimalDigits 156
◊ Horzlncrement 157
◊
Мах 156
◊
Min 156
◊
Value 156
◊
ValueType 156
◊
Vertlncrement 157
![]()
![]()
![]()
![]()
TObject: ◊ DistanceY 357
◊ ClassName 35 ◊ DistanceZ 357
◊ ClassNameis 35 ◊ HeadingX 357
◊ ClassParent 35 ◊ HeadingY 357
◊ ClassType 35 ◊ HeadingZ 357
◊ Create 32 ◊ MagHeading 357
◊
Destroy 32 ◊ OnDataChanged 358
◊
Disposed 35 ◊ OnSensorChoosing 357
◊
DisposeOf35 ◊ SensorType 356
◊
FieldAddress 36 ◊ Start 356
◊ Free 33 ◊ Started 356
◊
InheritsFrom 35 ◊ Stop
356
◊ MethodAddress 36 ◊ TiltX 357
◊ MethodName 36 ◊ TiltY 357
◊
RefCount 35 ◊ TiltZ 357
◊
UnitName 35 ◊ TrueHeading 357
TObjectList<>: ◊ Updatelnterval 357
◊
Add
51 TPageSetupDialog:
◊
AddRange 51 ◊ Execute 197
◊ Count 51 ◊ MarginLeft 202
◊ Create 49 ◊ MinМarginBottom 202
◊
Delete 52 ◊ MinМarginLeft 202
◊
Exchange 53 ◊ MinМarginRight 202
◊
Extract 53 ◊ MinMarginTop 202
◊
Insert 51 ◊ PageHeight 202
◊
InsertRange 51 ◊ PageWidth 202
◊ Items 51 ТPaperSketchEffect, BrushSize
256
◊ Move 53 TPath:
◊
OnNotify 53 ◊ AltDirectorySeparatorChar 441
◊
OwnsObjects 50 ◊ ChangeExtension 440
◊
Sort
53 ◊ Comblne 440
TOpenDialog: ◊ DirectorySeparatorChar 441
◊
DefaultExt 198 ◊ DriveExists 440
◊
Execute 197, 200 ◊ ExtensionSeparatorChar 441
◊
FileName 200 ◊ GetAttributes 140,441
◊
Filter 198 ◊ GetDirectoryName 440
◊
Filterlndex 198 ◊ GetDocumentsPath 441
◊
Initia\Dir 199 ◊ GetExtendedPrefix 439
◊
OnCanClose 201 ◊ GetExtension 440
◊
OnFolderChange 201 ◊ GetFileName 440
◊ OnSelectionChange 201 ◊ GetFileNameWithoutExtension 440
◊
OnTypeChange 201 ◊ GetFullPath 440
◊
Options 199 ◊ GetGUIDFileName 440
◊
Title 199 ◊ GetНomePath 137,441
TOrientationSensor: ◊ GetlnvalidFileNameChars 440
◊ AvailaЬ\eProperties 357 ◊ Getlnva\idPathChars 440
◊
CompMagHeading 357 ◊ GetLibraryPath 441
◊ CompTrueHeading 357 ◊ GetPathRoot 441
◊
DistanceX 357 ◊ GetPicturesPath 441
◊ GetRandomFileName 441
◊
GetSharedDocumentsPatb 441
◊
GetSharedPicturesPath 441
◊
GetТempFileName 441
◊ GetТempPath 137,441
◊ HasExtension 441
◊
HasValidFileNameCbars 439
◊ HasValidPatbCbars 439
◊ IsDriveRooted 440
◊ IsExtendedPrefixed 440
◊
IsPathRooted 441
◊ IsRelativePath 440
◊
IsUNCPath 440
◊
IsUNCRooted 440
◊
IsValidFileNameCbar 439
◊
IsValidPathCbar 439
◊
MatchesPattem 440
◊
PathSeparator 441
◊ SetAttributes 140, 441
◊ VolumeSeparatorChar 441 TPathAnimation:
◊
Delay 265
◊
Duration 265
◊
Interpolation 266
◊
Inverse 267
◊
Loop
266
◊
OnFinish 266
◊ OnProcess 266
◊ Path 269
◊ Pause 266
◊
Rotate 269
◊
Running 266
◊
Start 266
◊ Stop 266
TPathData:
◊ Clear 230
◊ Count230
◊ IsEmpty 230
◊ Points 230
TPinchEffect:
◊
AspectRatio 253
◊ Center 252
◊
Radius 252
◊ Strength 252 TPixelateEffect:
◊
BlockCount 257
◊ BrushSize 257 TPlatforrnServices:
◊ Current 364
◊ GetPlatforrnService 364
◊ SupportsPlatforrnService 364 TPosition:
◊
DefaultValue 64
◊
Empty 64
◊
OnChange 64
◊ Point 64
◊
Vector 64
◊
Х63, 64
◊ У 63, 64
TPrintDialog:
◊ · Collate 205
◊ Copies 205
◊
Execute 197
◊
FromPage 204
◊
MaxPage 204
◊
MinPage 204
◊
Options 204
◊
PrintRange 204
◊ PrintToFile 205
◊
ToPage 204
TPrinterSetupDialog, Execute 197 TPrototypeBindSource:
◊ FieldDefs 305
◊
RecordCount 305
TProxyObject, SourceObject 383
TRadia!BlurEffect, BlurAmount 248 TRectAnimation:
◊
Delay 265
◊
Duration 265
◊
Interpolation 266
◊
lnverse 267
◊
Loop
266
◊
OnFinish 266
◊
OnProcess 266
◊
Pause 266
◊
Running 266
◊
Start 266
◊
StartValue 269
◊
Stop
266
◊ StopValue 269
ТReflectionEffect,
Length 254 TrippleEffect:
◊
Amplitude 253
◊
AspectRatio 253
◊ Center 253
◊
Frequency 253
◊
Phase 253
![]()
![]()
TSaveDialog:
◊
DefaultExt 198
◊
Execute 197, 200
◊
FileName 200
◊
Filter 198
◊
InitialDir 199
◊
OnCanClose 201
◊
OnFolderChange 201
◊
OnSelectionChange 201
◊
OnTypeChange 201
◊
Options 199
◊ Title 199 TSemaphore:
◊
Acquire 339
◊ Create 339
◊ Release 339 TSensorManager:
◊
Activate 359
◊
Active 359
◊ CanActivate 359
◊ Count 359
◊ Cuпent 359
◊ GetSensorsByCategory 359
◊ GetSensorsByFilter 359
◊ Sensors 359
TSepiaEffect, Amount 257 TShadowEffect:
◊
Amount 255
◊
Direction 255
◊
Distance 255
◊ ShadowColor 255
◊ Softness 255
TShape3D:
◊
Amblent 402
◊
Diffuse 402
◊
Emissive 402
◊ Shininess 404
◊ Specular 402
TSharpenEffect, Amount 258 TSmoothMagnifyEffect:
◊ AspectRatio 252
◊ Center 251
◊ InnerRadius 252
◊ Magnification 251
◊ OuterRadius
252 TSpinBox:
◊
DecimalDigits 156
◊
Horzlncrement 157
◊
Мах 156
◊
Min 156
◊ Value 156
◊
ValueType 156
◊
Vertlncrement 157 TSQLConnection:
◊
AfterConnect 290
◊ AfterDisconnect 290
◊ BeforeConnect 290
◊ BeforeDisconnect 290
◊ Close 288
◊ CloseDataSets 291
◊ Commit 292
◊
Connected 289
◊ ConnectionState 291
◊
DataSetCount 291
◊
DataSets 291
◊
DriverName 288
◊ Execute 292
◊ ExecuteDirect 292
◊ GetFieldNames 293
◊ GetlndexNшnes 293
◊ GetProcedureNшnes 293
◊ GetProcedureParams 293
◊
GetТaЬleNшnes 293
◊
InTransaction 292
◊
KeepConnection 290
◊
MultipleTransactionsSupported 291
◊ OnLogin 290.
◊
Open
288
◊ Parшns 288
◊
Rollback 292
◊ StartTransaction 292
◊ ТаЫеSсоре 293
◊ TransactionsSupported 291 TSQLDataSet:
◊ CommandText 294
◊ CommandType 294
◊
ExecSQL 296
◊
ParamCheck 299
◊ Parшns 299
◊
ParseDeleteSql 296
◊
ParselnsertSql 296
◊
ParseSelectSql 296
◊
ParseUpdateSql 296
◊ SQLConnection 294 TSQLQuery:
◊
ParшnCheck 299
◊
Parшns 299
◊
SQL
299
◊
SQLConnection 294
![]()
![]()
![]()
TSQLStoredProc:
◊ ExecProc 297
О ParamCheck 297
О Params 297
◊ PrepareStatement 297
◊ SQLConnection 294, 296
◊ StoredPro ame 297 TSQLТаЫе,
SQLConnection 294 TStringGrid:
◊
CelJControlByRow 187
◊
Cells 188
◊
ColumnByPoint 187
◊ ColumnCount 187
◊
Columns 187
◊
OnEdititingDone 188
О OnGetValue 188
О OnSetValue 187
О RowCount 187 TStrokeBrush:
◊
Сар 226
О Dash 226
◊
Join
226
◊ SetCustomDash 226
О Thickness
226 TStyleBook:
◊
FileName 115
◊ Resource 114
TSwirlEffect:
◊ AspectRatio 251
◊ Center 250
О SpiralStrength
250 ТТextControl:
О Font 143
◊
FontColor 144
О Text 143
О TextAlign 144
О VertTextAlign 144
◊ WordWrap
144 TTexture:
О BytesPerPixel 405
О Height 405
О IsEmpty 404
◊
MagFilter 405
О MinFilter 405
◊
PixelFormat 405
О Style 404
О Width 405
ТТextureMaterialSource, Texture 400 TThread:
◊ Create 325
◊
FatalException 326
◊ FreeOnTerminate 326
◊
IsSingleProcessor 327
◊ OnTerminate 326
◊ Priority 329
◊ ProcessorCount 327
◊ Queue 330
◊
Resume 325
О RetumValue 327
◊
SetRetumValue 327
◊ Sleep 325
◊
Start 325
О Suspend 325
◊ Synchronize 330
◊
Terminate 326
◊
Terminated 326
◊ WaitFor 328
TTilerEffect:
◊
Horizonta!Offset 260
◊
HorizontalTileCount 260
◊ VerticalOffset 260
◊ VerticalТileCount 260 ТТimer:
О EnaЫed 208
О Interval 208
◊ OnTimer 208
TTimeSpan:
◊ Create 207
О Days 207
О Hours 207
◊ Milliseconds 207
О Minutes 207
◊ Seconds 207
◊
Ticks 207
ТТoonEffect, Levels 258
ТТouchManager, GestureManager 283
ТТransform, RotationCenter 219 TTreeView:
◊ AddObject 180
◊ Clear 181
◊ CollapseAII 183
◊ Count 180
◊ CountExpanded 184
◊
ExpandAII 183
◊
GlobalCount 180
◊
HideSelectionUnfocused 180
О ItemByGloballndex 180
◊ ItemBylndex 180
◊ ItemByPoint 180
◊ ItemHeight 179
![]()
TTreeView (прод.): ◊ Reflect 427
◊ Items 180 ◊ Rotate 426
◊ MultiSelect 179 TVideoCaptureDevice:
◊
OnChange 180 ◊ Description 277
◊
OnChangeCheck 183 ◊ IsDefault 277
◊
OnCompare 184 ◊ MediaType 277
◊ RemoveObject 180 ◊ Name277
◊
Selected 179 ◊ StartCapture 277
◊ ShowCheckboxes 183 ◊ State 277
◊ Sorted 184 ◊ StopCapture 277
TTreeViewltem: ◊ UniqueID 277
◊
AddObject 178 TVideoCaptureDevice:
◊ Count 176 ◊ FlashMode 278
◊
Globallndex 178 ◊ FocusMode 278
◊
Index 178 ◊ HasFlash 277
◊
IsChecked 179 ◊ HasTorch 278
◊
IsExpanded 179 ◊ OnSampleBufferReady 278
◊
IsSelected 179 ◊ Position 277
◊
ItemBylndex 176 ◊ Quality 278
◊
ItemByPoint 177 ◊ SampleBufferToBitmap 278
◊
Items 176 ◊ TorchMode
278
◊
Parentltem 177 TWaterTransitionEffect:
◊
RemoveObject 178 ◊ Progress 261
◊
Text 176 ◊ Target 261
◊
TreeView 178 TWebBrowser:
TVector3D: ◊ CanGoBack 350
◊
Add 421 ◊ CanGoForward 350
◊
◊ Create 420 ◊ Navigate 349
AngleCosine 425 ◊ GoBack 350
◊ OnDidFinishLoad 349
◊ CrossProduct 424
◊ Distance 422
◊ DotProduct 425
◊ OnDidStartLoad 349
◊
URL349
◊
w
GetNormalize 421
◊ Length 420
◊ MidVector 422 Windows API, MoveFile
139
![]()
А
Анимация 263
Аудиопоток, захват 275, 277
Б
База данных
287
◊ регистрация
пользователя
290 О соединение
288
Буфер:
◊ вершинный 387
◊
индексный 387
Буфер обмена 150
в
Вектор 372,419
Взаимная блокировка 330
Видеопоток, захват 275, 277
Видимость 68
Визуальный дизайнер 305
Вращение
объекта 66
Геолокация 350
Градиентная заливка 219 Графика:
О двухмерная 222
◊
растровая 235
О трехмерная 369
Графический примитив 229
Графический эффект 240
д
Дата и
время 206
О интервал
времени 207
Датчик 442
О местоположения 350
О ориентирования 356
Дерево 175
О узел176
Диалоги и
сообщения:
О InputBox 196
О InputQuery
196
◊
MessageDlg 194
◊
MessageDlgPos 196
О MessageDlgPosHelp 196
О ShowMessage 192
О ShowMessageFmt 193
О
ShowMessagePos 193 О настройка
печати 203 Диск 135
Жест 281
О интерактивный 285
Заливка:
◊ замкнутой области 228
О трехмерного
объекта 399
Запрос 299
Звонок по телефону 367
Значок
приложения 89
Изображение:
◊ загрузка 235
О миниатюра 236
О свойства 237
Источник света 380
к
Календарь 209
Камера 280, 376, 381
Каталог 136
О дата/время создания 141
◊ проверка существования
137
◊ создание 138
Кисть 224 Класс:
О TAction 127
о Т Animation 265
о TComponent 38
о TMainMenu 124
.◊ TMenultem 120
о TObject 30
о TPersistent 37, 38
о TTextControl 143
Колонка 185 Команда:
О Т Action 127
О выполнение 130
![]()
![]()
Компоненг, TPrinterSetupDialog 203
Конгекст трехмерный 406, 408
Координаты, преобразование 352
Критическая секция 336
л
Линия 226
м
Макет 384
Масштабирование объекта 66
Матричные преобразования 414,428
Менеджер датчиков 358
Меню 118
◊ главное 124
◊
группировка
элеменгов 122
◊ конrекстное 124
◊ планка 124
◊
элеменr
II виде флажка 121
Метка 144
Метод класса
35
Модель трехмерная 394
◊
импорт 397 Модуль:
◊
IOUtils 135
◊
System.IOUtils 434
◊
System.Sensors 442
Мультимедиа 271
Мультиязычность 341
Мьютекс 337
н
Название приложения 89
Нормаль 374,393
Освещение 413
Отсечение
области 234
п
Пиксел
371
Подсистема уведомлений 361
Поле текстовое 146, 152
Поток 323
◊ приоритет 329
Приложение:
◊
для мобильной платформы 347
◊ многопоточное 323
Проекция 375
Прозрачность 68
Проигрыватель 273
р
Редактор многострочный
153
Рисунок 233
с
Свет отраженный 402
Семафор 338
Сенсорный ввод 283
Сетка 185, 187
◊
строк 188
Система координат 369 Событие:
◊
OnActionUpdate 93
◊
OnActivate 103
◊ OnActivated 315
◊
OnActivating 315
◊
OnAssignedValueEvent 315
◊
OnAssigningValueEvent 315
◊ OnCanClose 201
◊ OnCanFocus 77
◊ OnChange 134, 169,
171
◊ OnChangeCheck 169
◊ OnChangeTracking 147
◊ OnClick 70, 121.
◊ OnClose 104, 105, 197
◊
OnCloseQuery 104
◊
OnCompare 167, 169, 184
◊
OnDЫClick 70
◊
OnDeactivate 104
◊
OnDestroy 104, 105
◊
OnDidFinishLoad 349
◊
OnDidStartLoad 349
◊
OnDragDrop 81
◊
OnDragEnd 82
◊
OnDragEnter 81
◊
OnDragLeave 81
◊ OnDragOver 80
◊
OnEnter 77
◊
OnEvalErrorEvent 315
◊
OnExecute 130, 134
![]()
◊
OnExit 77
◊
OnFolderChange 201
◊
OnGeocode 354
◊ OnGeocodeReverse 354
◊ OnGesture 283
◊ OnKeyDown 73
◊ OnKeyUp 73
◊ OnLocationChanged 351
◊ OnМouseDown 75
◊ OnMouseEnter 75
◊ OnMouseLeave 75
◊ OnMouseMove 75
◊
OnMouseUp 75
◊
OnMouseWheel 77
◊
OnPaint 82
◊
OnPainting 82
◊
OnResize 77
◊
OnSampleBufferReady 278
◊
OnSelectionChange 201
◊ OnShow 197
◊ OnStateChange 134
◊ OnTenninate 326
◊
OnTimer 208
◊
OnTypeChange 201
◊
OnUpdate 130, 134
◊
обработка 70
Список 159
◊
выбор нескольких элементов 165
◊ выбора 161
◊
доступ к элементу:
а выделенному 164
а произвольному 165
◊ команд 133
◊ комбинированный:
а нередактируемый 169
а редактируемый 172
◊ компонент выбора значений 174
◊ оформление 168
◊ перестановка элементов 166
◊ события 169
◊ сортировка элементов 166
◊
текстовый поиск 167 Сцена
378
т
Таймер 208
Тексел 399
Текст:
◊
вывод на форму 232
◊ редактирование 154
Текстура
416
Текстурирование 399, 404
Тетраэдр 389
Точка 372
Траектория
230
у
Увеличительное стекло 360
ф
Файл:
◊ атрибуты 140
◊ дата/время создания 141
◊
проверка существования
137
◊
расширение:
а dfm 96
0 xfm 37
Форма 9
◊
3D
108
◊
HD
108
◊
главная 95
◊ доступная 99
◊ модальная 100
◊ немодальная 99
◊ стили оформления 114 Функция:
◊ ColorToVectorЗD 420
◊ DiskFree 136
◊
DiskSize 136
◊
GetCurrentDir 91
◊
HSLtoRGB 217
◊ MakeColor 213
◊ Matrix3D 429
◊
RGBtoHSL 217
◊
Vector3D 420
◊
Vector3DToColor 420
х
Холст 223
◊ восстановление 234
◊ сохранение 234
![]()
![]()
ц ш
Цвет 212
◊
модель:
0 ARGB 215
" HSL 216
0 RGB 212
Цветовые полосы 217
Центр уведомлений 362
ч
Число, ввод 156
Шейдер417
Шрифт227
э
Эффекты визуальные 69