Page 1

Дмитрий Осипов

Delphi

П рограм м и рован и е д л я

W in d o w s, OS X , iO S и A n d ro id

Санкт-Петербург «БХВ-Петербург» 2014


УДК 004.4'2 ББК 32.973.26-018.1 0-74 Осипов Д. Л. 0-74 Delphi. Программирование для Windows, OS X, iOS и Android. — СПб.: БХВ-Петербург, 2014. — 464 с.: ил. — (Профессиональное программирование) ISBN 978-5-9775-3289-1 Книга посвящена одному из самых совершенных языков программирования Delphi ХЕ5/ХЕ6. В ней подробно рассматривается новейшая кроссплатформенная библиотека FM, позволяющая создавать полнофункциональное программное обес­ печение для операционных систем Windows и OS X, а также для смартфонов и планшетных компьютеров, работающих под управлением Android и iOS. Проекты примеров из книги размещены на сайте издательства. Для программистов УДК 004.4'2 ББК 32.973.26-018.1

Группа подготовки издания: Главный редактор Зам. главного редактора Зав. редакцией Редактор Компьютерная верстка Корректор Дизайн серии Оформление обложки

Екатерина Кондукова Игорь Шишигин Екатерина Капалыгина Анна Кузьмина Ольги Сергиенко Зинаида Дмитриева Инны Тачиной Марины Дамбиевой

Подписано в печать 31.01.14. Формат 70x1001/1в. Печать офсетная. Уел. печ. л. 37,41 ■ Тираж 1000 экз. Заказ № 65 "БХВ-Петербург", 191036, Санкт-Петербург, Гончарная ул., 20. Первая Академическая типография "Наука" 199034, Санкт-Петербург, 9 линия, 12/28

ISBN 978-5-9775-3289-1

© Осипов Д. Л., 2014 © Оформление, издательство "БХВ-Г leiefxlypi

2014


Оглавление

В ведение...................................................................................................................................... 13 Г л а в а 1. П одготовка к р а б о т е ............................................................................................. 15 Выббр типа приложения............................................................................................................... 17 Выбор целевой платформы для проекта...................................................................................... 18 Выпуск приложения для OS X................................. .................................................................... 19 Выпуск приложения для iOS Mobile............................................................................................ 22 Выпуск приложения для Android................................................................................................. 23 Что делать, когда код зависит от платформы?............................................................................27 Г л а в а 2. Забы ваем V C L ?....................................................................................................... 30 Опорный класс VCL — T O bject ................................................................................................... 30 Управление жизненным циклом объекта............................................................................32 Механизм учета ссылок в мобильных проектах.................................................................34 Информирование о классе.................................................................................................... 35 Класс TPersistent............................................................................................................................ 37 Основа компонента TComponent.................................................................................................. 38 Владение объектами.............................................................................................................. 39 Г л а в а 3. К л ассы -ш аб л о н ы .................................................................................................... 41 Обобщенный тип данных в полях записей..................................................................................41 Обобщения в процедурах и функциях.........................................................................................43 Обобщенные типы данных в шаблонах классов.........................................................................44 Наследование шаблона класса.............................................................................................. 46 Перегрузка методов с параметром обобщенного типа.......................................................47 Шаблон массива, класс Т А г г а у О ................................................................................................ 47 Шаблон списка объектов, класс T O b je c tL is to ..........................................................................49 Шаблон словаоя T D ic tio n a r y o ................................................................................................... 53 Г л а в а 4. Б азовы е кл ассы F ire M o n k e y ............................................................... ..............56 Опорный класс TFmxObject.................. ....................................................................................... 56 Управление дочерними объектами......................................................................................57 Сопоставление дополнительных данных.............................................................................60 Поддержка LiveBindings........................................................................................................ 61


4

Оглавление

Поддержка анимации............................................................................................................. 61 Поддержка сенсорного ввода............................................................................................... 61 Взаимодействие с командами............................................................................................... 61 20-элементы управления, класс TControl.................................................................................... 62 Размещение 20-элемента управления..................................................................................63 Выравнивание объекта................................................................................................ 64 Группировка объектов и компонент TLayout.............................................................65 Масштабирование и вращение объекта...............................................................................66 Видимость и прозрачность элемента управления...............................................................68 Грани, фаски и визуальные эффекты...................................................................................68 Состояние элемента управления...........................................................................................70 Обработка событий................................................................................................................ 70 Простейшие события — щелчок.................................................................................70 Клавиатурные события................................................................................................. 73 События мыши.............................................................................................................. 74 События получения и потери фокуса ввода...............................................................77 Событие изменения размера........................................................................................ 77 События перетаскивания drag and drop......... ............................................................. 79 Особенности прорисовки элемента управления.................................................................82 Стилевое оформление, класс TStyledControl...............................................................................83 ЗО-элементы управления, класс TControBD ................................... ...........................................83 Размеры объекта.................................................................................................................... 84 Повороты объекта.................................................................................................................. 84 3D-события мыши.................................................................................................................. 85

Глава 5. Приложение FireMonkey.................................................................................. 88 Приложение TApplication ..............................................................................................................88 Значок приложения................................................................................................................ 89 Название приложения............................................................................................................ 89 Расположение исполняемого файла приложения...............................................................91 События приложения............................................................................................................. 91 Контроль активности пользователя......................................................................................93 Характеристики дисплея, класс TFormFactor.....................................................................94 Формы HD и 3D............................................................................................................................. 95 Описание формы в fmx-файле.............................................................................................. 96 Общие черты форм................................................................................................................ 98 Создание, отображение и уничтожение форм............................................................98 Состояние формы....................................................................................................... 102 Жизненный цикл формы............................................................................................ 103 Доступ к элементу управления по его координатам.........................................................106 Совмещение форм для разных мобильных устройств в одном приложении................ 106 Качество графического вывода.......................................................................................... 107 Форма HD FMX.Forms. TForm ............................................................................................ 108 Трехмерная форма FMX.Forms3D. TForm3D .....................................................................108 Пример 3D-проекта.................................................................................................... 110 Совместное применение 2D- и ЗО-компонентов.....................................................113 Стили оформления формы, компонент TStyleBook...................................................................114 Подключение ресурсов и изображений.....................................................................................115


Оглавление

5

Г л а в а 6. М еню прилож ения................................................................................................118 Элемент меню TMenuItem ........................................................................................................... 120 Элемент меню в виде флажка.................................................................................. ...........121 Группировка элементов меню............................................................................................ 122 Доступ к дочерним элементам меню................................................................................. 124 Главное меню TMainMenu .......................................................................................................... 124 Планка меню TMenuBar.............................................................................................................. 124 Контекстное меню Т Р орирМ епи ................................................................................................ 124 Г л а в а 7. К ом андны й и н терф ей с.......................................................................................126 Команда ТAction ........................................................................................................................... 127 Связь с элементом управления........................................................................................... 129 Выполнение команды.......................................................................................................... 130 Установка команды в актуальное состояние.....................................................................130 Связь команды с контейнером............................................................................................ 131 Предопределенные команды............................................................................................... 131 Список команд TActionList.......................................................................................................... 133 Г л а в а 8. У п равлен и е п ап кам и и ф а й л а м и ...................................................................135 Работа с дисками.......................................................................................................................... 135 Сбор сведений о каталогах и файлах......................................................................................... 136 Проверка существования файла и, каталога............................................................................... 137 Расположение системных каталогов.......................................................................................... 137 Создание, удаление, копирование и .перемещение...................................................................138 Запись в файл и чтение из файла................................................................................................ 139 Атрибуты файла и каталога........................................................................................................ 140 Дата и время создания файла и каталога................................................................................... 141 Г л а в а 9. К ом поненты д л я работы с т е к с т о м .............................................................. 142 Класс TTextControl....................................................................................................................... 143 Метка TLabel ................................................................................................................................ 144 Интерфейс IVirtualKeyboardControl........................................................................................... 146 Основа строк ввода, класс TCustomEdit..................................................................................... 146 Ограничения на ввод........................................................................................................... 148 Выделение части текста...................................................................................................... 148 Взаимодействие с буфером обмена....................................................................................150 Управляющие символы....................................................................................................... 150 Особенности оформления................................................................................................... 151 Строки ввода TEdit и TClearingEdit ........................................................................................... 152 Многострочный редактор ТМето .............................................................................................. 153 Позиция каретки.................................................................................................................. 154 Редактирование текста i....................................................................................................... 154 Быстрое перемещение по тексту........................................................................................ 155 Ввод чисел TNumberBox, TSpinBox и TComboTrackBar ...........................................................156 Г л а в а 10. К о м п о н ен ты -сп и ск и .........................................................................................158 Базовый элемент списка TListBoxItem ....................................................................................... 159 Список выбора TListBox .............................................................................................................. 161 Редактирование элементов.................................................................................................. 162 Доступ к выделенному элементу списка............................................................................164


6

Оглавление

Доступ к произвольному элементу списка........................................................................165 Выбор нескольких элементов............................................................................................. 165 Представление элементов в виде кнопки выбора.............................................................166 Перестановка элементов..................................................................................................... 166 Сортировка элементов......................................................................................................... 166 Текстовый поиск, элемент TSearchBox .............................................................................. 167 Особенности оформления списка....................................................................................... 168 Основные события списка................................................................................................... 169 Нередактируемый комбинированный список TComboBox ......................................................169 Редактируемый комбинированный список TComboEd.it..........................................................172 Компонент выбора значения ТРорирВох ................................................................................... 174 Г л а в а 11. И ерархи ч еская стр у кту р а............................................................................... 175 Узел дерева TTreeViewItem ......................................................................................................... 176 Управление дочерними узлами.......................................................................................... 176 Положение узла в дереве..................................................................................................... 178 Состояние узла..................................................................................................................... 179 Дерево TTreeView ........................................................................................................................ 179 Выделение узла.................................................................................................................... 179 Доступ к узлу........................................................................................................................ 180 Управление составом узлов................................................................................................ 180 Узел в роли флажка............................................................................................................. 183 Свертывание и развертывание узлов.................................................................................. 183 Упорядочивание узлов дерева............................................................................................ 184 Г л а в а 12. С е т к и .......................................................................................................................185 Колонки сетки.............................................................................................................................. 185 Сетка T G rid .................................................................................................................................. 187 Сетка TStringGrid ......................................................................................................................... 188 Пример обслуживания текстовых данных................................................................................. 188 Г л а в а 13. О к н а сообщ ений и д и а л о г и ............................................................................192 Окна сообщений.......................................................................................................................... 192 Окна выбора действия................................................................................................................. 194 Окна ввода данных...................................................................................................................... 196 Компоненты-диалоги................................................................................................................... 197 Открытие и сохранение файлов TOpenDialog и TSaveDialog ......................................... 197 Параметры страницы TPageSetupDialog ............................................................................202 Настройка печати TPrinterSetupDialog ..............................................................................203 Отправка задания на печать TPrintDialog ..........................................................................203 Г л а в а 14. Д ата и в р е м я ........................................................................................................ 206 Дата и время TDateTime .............................................................................................................. 206 Интервал времени TTimeSpan..................................................................................................... 207 Отсчет времени, таймер TTimer.................................................................................................. 208 Календари TCalendar и TCalendarEdit....................................................................................... 209 Г л а в а 15. У п равлен и е ц вето м ........................................................................................... 212 Представление цвета ARGB....................................................................................................... 212 Стандартные цветовые комбинации.......................................................................................... 214


Оглавление

7

Компоненты цветовой модели ARGB........................................................................................ 215 Компоненты цветовой модели HSL........................................................................................... 216 Компоненты TColorPicker и TColorQuad .......................................................................... 216 Цветовые полосы THueTrackBar, TAlphaTrackBar и TBWTrackBar........................................217 Градиентная заливка TGradientEdit ........................................................................................... 219 Г л а в а 16. Д вухм ерная гр аф и к а........................................................................................ 222 Управление холстом.................................................................................................................... 223 Кисть TBrush ................................................................................................................................ 224 Внешний вид линий..................................................................................................................... 226 Шрифт TFont................................................................................................................................ 227 Заливка замкнутых областей...................................................................................................... 228 Вывод простейших фигур........................................................................................................... 229 Траектория TPathData ......................................................................................................... 230 Вывод текста................................................................................................................................ 232 Отображение рисунков................................................................................................................ 233 Отсечение....,................................................................................................................................. 234 Сохранение и восстановление состояния холста......................................................................234 Работа с растровой графикой, класс TBitmap ............................................................................235 Загрузка и сохранение изображения.................................................................................. 235 Кодирование и декодирование графических форматов.............. .................................... 236 Получение миниатюры изображения................................................................................. 236 Свойства изображения........................................................................................................ 237 Простые манипуляции графическим образом...................................................................237 Редактирование битового образа........................................................................................ 238 Управление графической производительностью........ ............................................................. 239 Г л а в а 17. Г раф ические эф ф ек ты ......................................................................................240 Применение эффекта к файлам изображений...........................................................................242 Применение нескольких эффектов к файлам изображений.....................................................243 Простейшие корректирующие эффекты.................................................................................... 246 Заливка цветом TFillEffect и TFillRGBEffect......................................................................246 Яркость и контрастность TContrastEffect...........................................................................246 Регулировка оттенка цвета THueAdjustEffect.....................................................................247 Ясная TBloomEffect и пасмурная TGloomEffect погода.....................................................247 Прозрачность TColorKeyAlphaEfflect..................................................................................247 Эффекты размытия и искажения................................................................................................ 248 Размытие............................................................................................................................... 248 Искажения............................................................................................................................ 249 Вертикальные полосы TBandsEffect..........................................................................249 Водоворот TSwirlEffect и TBandedSwirlEffect...........................................................250 Увеличительное стекло TMagnijyEffect и TSmoothMagnifyEffect........................... 251 Стягивание области TPinchEffect...............................................................................252 Рябь на воде TRippleEffect.......................................................................................... 253 Волны TWaveEffect..................................................................................................... 254 Горизонтальная деформация краев текстуры TWrapEffect..................................... 254 Аддитивные эффекты.................................................................................................................. 254 Отражение TReflectionEffect................................................................................................ 254 Эффекты свечения TGlowEffect и TlnnerGlowEffect.........................................................255


8

Оглавление

Тень TShadowEffect.............................................................................................................. 255 Эффект тиснения TEmbossEffect......................................................................................... 255 Набросок на бумаге TPaperSketchEffect.............................................................................256 Карандашный набросок TPencilStrokeEffect............................. ........................................256 Пикселизация TPixelateEffect.............................................................................................. 257 Старая фотография TSepiaEffect......................................................................................... 257 Управление резкостью TSharpenEffect...............................................................................258 Глубина цвета TToonEffect.................................................................................................. 258 Геометрические эффекты............................................................................................................ 258 Аффинные преобразования TAffmeTransformEffect..........................................................258 Обрезка TCropEffect............................................................................................................. 258 Перспектива TPerspectiveTransformEffect..........................................................................259 Эффект плитки TTilerEffect................................................................................................. 260 Наложение изображений TNormalBlendEffect...................................................................260 Эффекты трансляции................................................................................................................... 261 Г л а в а 18. А н и м а ц и я ..............................................................................................................263 Простой пример анимации...., ................................................................................................... 263 Общие черты компонентов-аниматоров, класс ТAnimation .....................................................265 Индивидуальные особенности компонентов-аниматоров................................................267 Цветовая анимация, компонент TColorAnimation ....................................................268 Градиентная анимация, компонент TGradientAnimation ........................................ 268 Анимированная картинка, компонент TBitmapAnimation ...................................... 268 Анимированный ряд, компонент TBitmapListAnimation......................................... 268 Анимация числовых свойств, компонент TFloatAnimation.................................... 269 Анимация прямоугольной области, компонент TRectAnimation............................ 269 Анимация траектории, компонент TPathAnimation .................................................269 Г л а в а 19. М у л ьти м еди а....................................................................................................... 271 Воспроизведение мультимедиа.................................................................................................. 271 Менеджер кодеков TMediaCodecManager .........................................................................271 Проигрыватель TMediaPlayer и компонент TMediaPlayerControl.................................. 273 Захват аудио- и видеопотока.................................................. -................................................... 275 Менеджер устройств TCaptureDeviceManager ..................................................................275 Захват потоков мультимедиа.............................................................................................. 276 Аудиозахват ТАudioCaptureDevice .....................................................................................277 Видеозахват TVideoCaptureDevice ..................................................................................... 277 Камера TCameraComponent................................................................................................ 280 Г л а в а 20. С енсорны й в в о д ..................................................................................................281 Описание жеста............................................................................................................................ 281 Реакция на сенсорный ввод........................................................................................................ 283 Интерактивные жесты................................................................................................................. 285 Пример обработка стандартных жестов.................................................................................... 286 Г л а в а 21. In terB ase T o G o ................................................................................. "..................287 Соединение с БД TSQLConnection ............................................................................................. 288 Управление соединением.................................................................................................... 288 Регистрация пользователя................................................................................................... 290


Оглавление

9

Управление подчиненными наборами данных....................................... ..........................291 Управление транзакциями.................................................................................................. 291 Выполнение SQL-инструкций............................................................................................ 292 Информирование о Б Д ........................................................................................................ 293 Набор данных TSQLDataSet........................................................................................................ 293 Хранимая процедура TSQLStoredProc ....................................................................................... 296 Запрос TSQLQuery ....................................................................................................................... 299 Выпуск приложения.................................................................................................................... 301 Г л а в а 22. L iveB indings......................................................................................................... 303 Визуальный дизайнер............. .....................................................................................................305 LiveBindings в проектах баз данных................................................................................... 308 Binding Expressions — связь с помощью выражений...............................................................310 Класс TBindExpression ......................................................................................................... 313 Выражение LiveBindings..................................................................................................... 315 Класс TBindings.................................................................................................................... 317 Lists — связь между списками.................................................................................................... 319 Класс TBindList..................................................................................................................... 322 Г л а в а 23. М ногопоточны е п ри л ож ен и я........................................................................ 323 Поток TThread .............................................................................................................................. 323 Метод ожидания.................................................................................................................. 328 Управление приоритетом потока....................................................................................... 329 Синхронный и асинхронный вызовы внешнего метода...................................................330 Пример многопоточного приложения...............................................................................330 Синхронизация потоков в Windows........................................................................................... 332 Синхронизация событием TEvent....................................................................................... 333 Критическая секция TCriticalSection .................................................................................. 336 Мьютекс TMutex................................................................................................................... 337 Семафор TSemaphore ........................................................................................................... 338 Г л а в а 24. М у л ьти язы ч н ы е п р о е к т ы ..............................................................................341 Компонент языковой поддержки TLang .................................................................................... 341 Режим автоматического перевода......................................................................................345 Перевод меню.............................................................................................................................. 345 Г л а в а 25. М об и льн ая п л атф о р м а .................. *................................................................ 347 Интернет-браузер TWebBrowser........................................... ..................................................... 348 Привязка к местности.................................................................................................................. 350 Датчик местоположения TLocationSensor .........................................................................350 Прямое и обратное преобразования координат TG eocoder .............................................352 Датчик ориентирования ТOrientationSensor.............................................................................. 356 Менеджер датчиков TSem orM anager ........................................................................................ 358 Увеличительное стекло TMagnifierGlass ................................................................................... 360 Подсистема уведомлений............................................................................................................ 361 Пример вывода текстового уведомления в назначенное время...................................... 364 Вызов приложения из окна уведомления..........................................................................365 Пример размещения числа на значке приложения...........................................................366 Звонок по телефону..................................................................................................................... 367


10

Оглавление

Г л а в а 26. З ако н ы трехм ерного м и р а .............................................................................. 369 Система координат...................................................................................................................... 369 Единица измерения...................................................................................................................... 371 Точка............................................................................................................................................. 372 Вектор........................................................................................................................................... 372 Объект........................................................................................................................................... 373 Фрейм........................................................................................................................................... 374 Проекция...................................................................................................................................... 375 Г л а в а 27. П роектируем З Б -с ц е н ы ................................................................................... 378 Построение сцены....................................................................................................................... 378 Источник света, класс TLight...................................................................................................... 380 Камера, класс TCam era ......................... ..................................................................................... 381 Объект-заместитель, класс TProxyObject...................................................................................383 Макет, класс TDummy.................................................................................................................. 384 Г л а в а 28. Г еом етрическое описание фигур и m esh-о б ъ е к ты ................................ 385 Произвольный объект, классы TMesh и TMeshData ............. ................................................... 385 Проектируем треугольник................................................................................................... 388 Проектируем тетраэдр......................................................................................................... 389 Проектируем четырехугольник.......................................................................................... 391 Управление нормалями вершин.........................................................................................393 ЗБ-модель, класс T M odeB D ....................................................................................................... 394 Импорт модели во время выполнения программы...........................................................397 Г л а в а 29. М атери ал о б ъ е к т а ............................................................................................. 398 Заливка цветом, компонент TColorMaterialSource ...................................................................399 Текстурирование.......................................................................................................................... 399 Источник текстуры TTextureMaterialSource ......................................................................400 Управление координатами текстуры в TMesh ...................................................................400 Отраженный свет и компонент TLightMaterialSource ..................................................... 402 Дополнительная настройка текстур и класс TTexture.......................................................404 Г л а в а 30. З Б -ко н текст TContext3D............................................................................... 406 Управление графической сессией.............................................................................................. 407 Графические примитивы класса TContextHelper ......................................................................408 Графические примитивы класса TContextSD ............................................................................411 Освещение............................ ........................................................................................................413 Матрицы и матричные преобразования.....................................................................................414 Текстуры....................................................................................................................................... 416 Шейдеры....................................................................................................................................... 416 П рилож ение 1. В ектор TVector3D................................................................................. 419 Длина вектора.............................................................................................................................. 420 Нормализация вектора................................................................................................................ 421 Проверка равенства двух векторов............................................................................................ 421 Сложение и вычитание векторов................................................................................................ 421 Расстояние между двумя векторами.......................................................................................... 422 Масштабирование вектора.......................................................................................................... 423


Оглавление

11

Векторное произведение............................................................................................................. 423 Скалярное произведение............................................................................................................. 424 Поворот вектора.......................................................................................................................... 426 Отражение вектора...................................................................................................................... 426

Приложение 2. Матрица преобразований TMatrix3D.............................................428 Нулевая и единичная матрицы................................................................................................... 429 Матрица переноса........................................................................................................................ 429 Матрицы вращения...................................................................................................................... 430 Матрица масштабирования......................................................................................................... 431 Умножение матриц.............................................................................................................. 431 Дополнительные матричные операции...................................................................................... 433 Приложение 3. Модуль SystenuIOUtils.........................................................................434 Приложение 4. Датчики.................................................................................................. 442 Приложение 5. Описание электронного архива.......................................................445 Список литературы...........................................................................................................446 Предметный указатель................................................................................................... 447


Введение Совсем недавно среда проектирования Embarcadero RAD Studio совершила очеред­ ной эволюционный скачок— в составе языков Delphi и C++Builder появилась принципиально новая возможность разработки кроссплатформенных приложений. Современные версии Delphi позволяют создавать не только приложения для Win32 и Win64, но и полноценные программные продукты, которые предназначены для работы под управлением операционных систем, разработанных компанией Apple (OS X 10.7 Lion, OS X 10.8 Mountain Lion, iOS начиная с версии 5.1) и компанией Google (речь об Android с диапазоном версий от 1.5 до 4.3)! В основу кроссплат­ формы положена во всех отношениях' уникальная библиотека FireMonkey. Компа­ ния Embarcadero рекомендует называть "библиотеку FireMonkey" "платформой FM", но большинству читателей привычней использовать первый вариант, поэтому мы будем придерживаться в книге именно этого словосочетания. Книга, которую вы дгрш гге в руках, в большей степени рассчитана на подготов­ ленного программиста, имеющего представление о языке и возможностях так на­ зываемых классических версий Delphi. Такого читателя в первую очередь интере­ сует ответ на единственный вопрос: "Чего такого нового появилось в языке Del­ phi ХЕ5, чтобы я заинтересовался им?" Постараюсь во введении к книге ответить на этот вопрос как можно более кратко и формализовано, разбив ответ на пункты. □ Библиотека VCL ни в коем случае не умерла и по-прежнему поддерживается компанией Embarcadero, но по темпам своего развития (важнейшему показателю для претендующего на успех программного продукта) она замедлилась. Вряд ли раскрою вам секрет, утверждая, что в IT-индустрии остановка равносильна смерти, a VCL движется все медленнее... Поэтому на смену VCL неотвратимо приходит инновационная по своей сути библиотека FireMonkey. □ FireM onkey— это по-настоящему кроссплатформенная библиотека, которая на данный момент поддерживает Win32, Win64, OS X, iOS и Android. Очень важно, что вы можете использовать один и тот же код для компиляции проекта как под Windows, так и под OS X и Android! □ FireMonkey обладает непревзойденными графическими возможностями и позво­ ляет создавать приложения, опирающиеся в первую очередь на DirectX, OpenGL и GDI+ (напомню, что проекты VCL изначально ориентированы на устаревший GDI).


14

Введение

В продолжение темы с графикой отмечу, что FireMonkey позволяет создавать не только классические двухмерные приложения, но и способные впечатлить самого притязательного пользователя приложения с трехмерной графикой.

Еще одно замечание касается того, что все визуальные элементы управления FireMonkey в буквальном смысле нарисованы (представляют собой битовые образы), и это позволяет применять к ним как по отдельности, так и в целом самые нетривиальные визуальные эффекты.

□ Еще со времен Borland язык Delphi славился своим высокопроизводительным компилятором. Сегодня в Delphi ХЕ5 их шесть (Win32, Win64, OS X, эмулятор iOS для х86 и iOS ARM, Android). Важно отметить тот факт, что компиляторы iOS ARM и Android модульные. Модуль фронтального (front-end) компилятора ARM переводит исходный код программы на языке Delphi в промежуточный байт-код. Конечный модуль (back-end) компилятора представляет собой низко­ уровневую виртуальную машину (Low Level Virtual Machine, LLVM), преобра­ зующую промежуточный байт-код в машинный код целевой платформы. Ком­ пиляторы LLVM широко используются в компаниях Apple, Google и Adobe, что подсказывает направление дальнейшего развития Delphi. □ Язык Delphi всегда отличался своими возможностями по разработке приложе­ ний для БД. Библиотека FireMonkey вобрала все лучшее по работе с БД и под­ держивает все распространенные коммерческие и бесплатные системы управле­ ния данными. В этой книге мы поговорим об одной из новых разработок Embarcadero — настольной БД InterBase ToGo. Одним словом, за библиотекой FireMonkey большое будущее, в какой-то степени это пригодный для всех случаев жизни швейцарский нож, который должен быть всегда под рукой.


ГЛАВА 1

Подготовка к работе Для того чтобы программист, впервые столкнувшийся с платформой FireMonkey (FMX), сразу вошел в курс дела, предложу обобщенную схему библиотеки (рис. 1.1). Платформа FireMonkey включает в себя многочисленный набор классов и сервисных интерфейсов, написанных на языке Delphi, в их числе элементы управления для 2D- и 3D-приложений, высококачественная графическая подсисте­ ма, поддершса сенсорного ввода и многое другое. Замечу, что рис. 1.1 существенно упрощает представление о FireMonkey, на самом деле это гораздо более сложная и многоуровневая система, но для первого знакомства его вполне достаточно. Зам ечание

Стоит знать, что у истоков платформы FireMonkey стоит российский программист Евге­ ний Крюков.

Появление принципиально новой программной платформы, получившей несколько необычное название FireMonkey, существенно расширило возможности языка Delphi. Теперь, помимо классических приложений VCL, у программиста есть воз­ можность разрабатывать несколько типов кроссплатформенных проектов: 1. FireMonkey Desktop Application для Windows и OS X: •

приложения FireMonkey HD Application;

приложения FireMonkey 3D Application.

2. FireMonkey Mobile Application для iOS. 3. FireMonkey Mobile Application для Android. Приложения FireMonkey HD Application позволяют создавать программные про­ дукты с высококачественным двухмерным графическим интерфейсом для операци­ онных систем Windows и OS X. Проекты HD в первую очередь окажутся востребо­ ваны в качестве бизнес-приложений (прикладное программное обеспечение и кли­ ентские прщюжения баз данных). С некоторой степенью допущения возможности FireMonkey HD Applicauon можно сравнить с традиционными проектами VCL. Хо­ тя о знаке равенства между этими платформами речи идти не может. Взвешивая


16

Гпава 1

достоинства и недостатки двух знаковых библиотек, на чашу весов, оценивающую преимущества FireMonkey, стоит положить две внушительные "гири" с названиями "кроссплатформа" и "качественная высокопроизводительная графика". В пользу горячо любимой программистами Delphi и C++Builder платформы VCL я бы отнес такие достоинства, как исключительная проработанность библиотеки, огромное число компонентов и безусловная поддержка Win API, COM, ADO и т. д. (что по­ зволяет писать весьма эффективный код под Microsoft Windows).

Платф орма FireMonkey Базовые классы FMX Элементы управления 2D

Г раф ичеоа ie эффекты

Элементы управления 3D

Сенсорный ввод

Стилевое оф ормление

Анимация

W indows

OS Х/iOS

Граф ический механизм

Графический механизм J Carbon API

Win API DirectX

Фильтры

GDI+

Cocoa API

OpenGL

Quartz

I

Android

Виртуальная машина Dalvik

Г раф ический механизм

!

Surface M anager

j ]

OpenGL

Р и с . 1.1. П л а тф о р м а F ire M o n k e y

Зам ечание

Для разработчиков, ориентированных на новейшую операционную систему Windows 8, платформа FireMonkey предоставляет возможность создавать приложения с интер­ фейсом Metropolis — аналогом интерфейса Windows 8.

Приложение FireMonkey 3D A pplication— это еще один шаг, сделанный Embarcadero навстречу востребованному сегодня мультимедийному направлению. Нали­ чие аббревиатуры "3D" подсказывает, что на этот раз программист получает право создавать уникальный трехмерный пользовательский интерфейс. Заметим, что сама по себе идея 3D не нова, она широко применяется в общеизвестных системах


Подготовка к работе

17

DirectX и OpenGL. Но то, что сделали в Embarcadero, без стеснения можно назы­ вать инновацией. На момент написания этих строк ни одна из коммерчески успеш­ ных систем разработки ПО не была способна создавать полноценные кроссплатформенные бизнес-приложения 3D путем простого переноса компонентов на фор­ му! Насколько это стало удобно, вы поймете сразу, если у вас есть хотя бы небольшой опыт разработки интерфейсной части программных продуктов с по­ мощью инструментария DirectX или OpenGL... Зам ечание

Подчеркнем, что Embarcadero не позиционирует FireMonkey как "движок" для разра­ ботки игровых приложений. Однако пока речь идет о первых шагах платформы, по­ этому нельзя исключить, что в последующих версиях библиотеки она позволит рабо­ тать и в этом сегменте ПО.

Приложения FireMonkey Mobile Application предназначены для работы под управ­ лением операционных систем iOS и Android. Таким образом, благодаря Delphi ХЕ5 вы приобретаете уникальную возможность писать программное обеспечение для iPad и iPhone корпорации Apple и для многочисленных устройств, использующих операционную систему Android компании Google!

Выбор типа приложения Для создания приложения на базе библиотеки FireMonkey следует обратиться к элементу меню File | New | Other и в появившемся диалоговом окне New Items следует выделить необходимый значок с названием типа приложения (рис. 1.2).

Р и с . 1.2. Д и а л о го в ы е окн а в ы б о р а т и п а п р о е кта и с о з д а н и я пр о е кта


18

Глава 1

Обратите внимание (см. рис. 1.2), что в составе шаблонов имеется заготовка проек­ та FireMonkey Metropolis UI Application. Это разновидность проектов HD с модным сегодня "плиточным" интерфейсом Metropolis, применяемым в Windows 8.

Выбор целевой платформы для проекта Раз основная заслуга FireMonkey заключается в поддержке не только Windows, но и OS X, то изучение приложения FireMonkey начнем с определения целевой плат­ формы для реализации приложения. Создайте новый проект. Для этого воспользуйтесь пунктом меню File | New |

FireMonkey Desktop Application. После появления на свет нового проекта обратитесь к окну менеджера проекта (рис. 1.3). В дереве менеджера проекта найдите узел Target Platforms и, воспользо­ вавшись услугами контекстного меню узла, добавьте интересующую вас платфор­ му (32-разрядная Windows, 64-разрядная Windows или OS X). В результате у узла Target Platforms появится дочерний элемент с названием вновь добавленной плат­ формы.

Р и с . 1.3. В ы б о р ц е л е в о й п л а тф о р м ы д л я п р о е кта F ire M o n k e y


19

Подготовка к работе

Выпуск приложения для OS X Выпуск приложения для 32- и 64-разрядной Windows не вызывает никаких затруд­ нений. Программист указывает предпочтительную платформу и просто нажимает клавишу <F9>. Если же вы планируете создать релиз для OS X, то придется еще немного потрудиться. 1. По возможности полностью отладьте приложение под управлением Windows (это возможно, если вы не используете функции API OS X). Это исключит мно­ гие проблемы при создании исполняемого бинарного кода под OS X и в целом ускорит работу над выпуском релиза. 2. Соедините в сети два компьютера. На первом должна быть установлена опера­ ционная система Windows и развернуто ваше программное обеспечение Delphi. Второй компьютер должен работать под управлением операционной системы OS X. 3. Обращаемся к компьютеру с FireMonkey. Найдите в каталоге C:\Program Files\ EmbarcaderoYRAD StudioW МРAServer (или C:\Program Files (x86)\Embarcadero\ RAD StudioW «\PAServer, если вы работаете в 64-разрядной Windows) файл RADPAServerXE5.pkg (в версиях ХЕ2 и ХЕЗ это был архив setup_paserver.zip, в ХЕ4 — RADPAServerXE4.pkg). Можете перенести файл на MAC с помощью флешки, а лучше всего предоставьте к данному каталогу сетевой доступ, так чтобы папку с файлом смогла увидеть станция Мае (рис. 1.4). оо

в

Q PAServe rl

( Ц ; & | " Л:: "V- И

Щ

| Ф »j j

рдуош т S

Applitai.3ns ( Я O n k lo p 0

ШШ

rift"

All Му Files

l

Ш

CATCH

murtger. patch

ШрЯ

Ш

seiup_paserver.exe

Documents

<Q Download!

j§§ .iMovtes J | M ask H I Pictures m m m I P drrm-tS

.т м т ш

Ш

.

Щ Macintosh Р и с . 1.4. С е те в о й д о с т у п к ф а й л а м па пки P A S e rv e r со с та н ц и и М ае

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


20

Гпава 1

Н DesKtop Й? Documents

Preview

QuickTime Player

Reminders

Stickies

System Preferences

TextEdit

Utilities

CEESSSSS»

О Downloads Щ Movies

ДMusic {£} Picture*

SHAaeo

У

Цр d ron-t5

DWSCiS Я M acintosh

f td № . A

paserver — 6 0 x 16

Hi

Last login: Fri Sep 13 18:31:38 on console OLOs-Иас:— Dnm$ /Applications/RAD\ PA$erver\ XE5tapp/Content s/MacOS/paserver J exits Plat for® Assistant Server Version 4 . 8 . 1 Л 9 Copyright Cc) 20©9~2G13 Embarcadero Technologies, Inc. Connection Profile password «press Enter for no password»: Acquiring permission to support debugging..*succeeded Starting P l a t f o m Assistant Server on port 642X1 Type ? for available commands

Р и с . 1.5. З а п у с к R A D P A S e rv e rX E 5 в т е р м и н а л ь н о м окн е

6. Возвращаемся к компьютеру с Windows. В менеджере проектов Delphi создайте целевую платформу (Target Platforms) OS X и, дважды щелкнув по узлу, сде­ лайте ее активной (см. рис. 1.3). 7. Нажмите "священную" для программиста Delphi клавишу <F9>. И если с актив­ ной платформой OS X это делается впервые, то перед вами появится окно мас­ тера создания профиля (рис. 1.6). Придумайте для профиля имя, введите IP-адрес станции Мае, при желании поменяйте номер порта и (если вы верите в теорию заговора) придумайте пароль. Нажав кнопку Test Connection, протес­ тируйте соединение и завершите работу помощника, нажав кнопку Finish. На­ строив профиль, вновь "давим" <F9> — в ответ вы увидите свое первое прило­ жение для Мае (рис. 1.7). Откомпилированное приложение для платформы OS X вы обнаружите на станции Мае в папке /Users/u.M M _nojtb3oeame.w/RADPAServer/ scratch -/имя проф айла. Внимание!

Компания Embarcadero постоянно улучшает свое программное обеспечение, это утвер­ ждение также относится и к PAServer. Поэтому после любого обновления Delphi обя­ зательно переустановите и PAServer.


Подготовка к работе

21

@ Cre»te в connection rronw Profile inform ation The w a r d и * assist you in creating a connection prof*e. Connection p roves a re feou<red in order to A ctey a r v f o r a project o r a rerftote machine.

Piatform;

@ C rea te a C onnection P rofile

n ote m a d w te inform ation FSease specify either the remote machine name or th e remote mache-w’s SP Address. A connection profie епаЫе* you to connect to the target platform oniy when PAServer в rataSed and running on the rernote

Р и с . 1.6. М а с те р с о з д а н и я у д а л е н н о го пр оф и л я

Project!

U»»t \e gtni ft1 Sep 1J 1в;Я:5» en c w io lc Л .в$-И «:- №M /AppUc»»iort/lu£)v P«S«rver\ US.«0Q/C»nteMS/HlcOS/po «crver I n i l ; .P I» 'fo r* t t i i i t v i Server ye n ton 4 .*. 1.x* U ) Я М - М П EsaercKScro Technoloeitl. Inc.

;c ~ « . . . _аЛв^«ь«иаывмягя s* • Acquiring pe nt *t*rUng Pl»t»*r*

Рис. 1.7. Приложение FireM onkey для платф ормы OS X


Гпава 1

22

Зам ечание

Если в вашем распоряжении нет компьютера Мае, но необходимо осуществить тести­ рование приложения, то можно воспользоваться услугами облачного сервиса macindoud, который расположен по адресу http://www.macincloud.com/.

Выпуск приложения для iOS Mobile Для выпуска приложения, предназначенного для работы под управлением мобиль­ ной платформы iOS, нам вновь потребуется рабочая станция Мае с развернутой на ней операционной системой OS X 10.7 Lion или OS X 10.8 Mountain Lion. М обиль­ ное устройство (с операционной системой iOS 5.1 и выше) стоит подключить к компьютеру Мае через USB-порт, для того чтобы ускорить процесс тестирования приложения. Если вы намерены испытать свои силы в разработке приложений для мобильной платформы iOS Mobile, то в самом начале пути для настройки компьютеров реко­ мендую воспользоваться услугами специализированного помощника. Для его вы­ зова достаточно, обратившись к пункту меню File | New | FireMonkey Mobile Application, создать свое первое мобильное приложение. После того как вы выбе­ рите шаблон приложения и укажете папку, в которую следует сохранить файлы проекта, среда разработки выведет на экран окно iOS Mobile Help Wizard. С этого момента всеми остальными вашими действиями станет руководить помощ­ ник. Во-первых, он попросит вас установить на станции Мае среду разработки Xcode, позволяющую разрабатывать приложения для Mac, iPhone и iPad. На момент написания этих строк программное обеспечение Xcode 5 можно было свободно скачать со страницы https://developer.apple.com/xcode/ компании Apple. Во-вторых, вам придется развернуть Xcode Command Line Tools. Для этого следует запустить установленное на предыдущем этапе программное обеспечение Xcode, выбрать пункт меню Preferences и щелкнуть по расположенной на главной панели команде Downloads. В окне загрузок выбираем вкладку Components и щелкаем по кнопке Install. Внимание!

Для разработки приложений для Мае вы должны обладать учетной записыэ Apple Developer.

В-третьих, вам следует развернуть на станции Мае программу-ассистент Platform Assistant, которую вы обнаружите на жестком диске компьютера с Embarcadero RAD Studio ХЕ5. Интересующий нас файл RADPAServerXE5.pkg расположен в папке C:\Program FilesYEmbarcaderoYRAD StudioW w\PAServerV Завершив инстал­ ляцию программы-ассистента, найдите в папке Applications приложение RAD PAServer ХЕ5.арр и запустите его на выполнение В-четвертых, вам предстоит соединить компьютер с Embarcadero RAD Studio ХЕ5 и станцию Mac и сконфигурировать профиль соединения (connection profile). Для вы­ зова мастера конфигурации обратитесь к IDE Delphi и выберите пункт меню Tools |


Подготовка к работе

23

Options. В появившемся окне настройки опций выберите узел Environment Options | Connection Profile Manager. Щ елчок по кнопке Add вызовет на экран уже знакомый нам помощник (см. рис. 1.3).

Выпуск приложения для Android Процесс выпуска приложения для операционной системы Android (в сравнении с работой с iOS) имеет ряд существенных отличий, ведь на этот раз нам предстоит работать с операционной системой, разработанной не в компании Мае, а в Google. Если вы планируете написать приложение для конкретного мобильного устройства, то целесообразно установить на ваш персональный компьютер USB-драйвер этого смартфона или планшетного компьютера. Все необходимые драйверы вы обнару­ жите в Интернете, в первую очередь рекомендую ресурсы:

http://deveIoper.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 (кнопка Пуск | Все программы | Embarcadero RAD Studio | Android Tools) и отметьте "галочкой" в окне менеджера строку Google USB Driver (рис. 1.8). Щ елчок по кнопке Install packages автоматически отправит запрос на соответствующий сайт, с которого будет осуществлено бес­ платное скачивание драйверов. i Агк1гс,- SCXManager Packages

"

J

'

[,д ,

\

Tools

SDK Path: CAU$ers\Pobfcc\Ddcumenh\f?AD StudwU2tf\Pl3tfofmSDKs\adt-bundle-window$-jd8<>'?0130522\sdk Packages | ф» Name §3 Й 12 H gg Q 0 fjj В О IS О B S О 0 ВШ §3 H ШШ H Q ШО Show;

API Android Support Repository Android Support library Google AdMob Ads SDK Google Analytics App Tracking SDK IDeprecatedtj Google Cloud Messaging forAndroii Google Play services GoogteRepository Google PloyAPK Expansion Library Google Play Billing Library Google Ploy Licensing Library Google USB Driver; Google Web Driver EmulatorAccelerator (HAXM)

f|§ Updates/New n/j Installed

Sort by; Ш API level

© Repository

Rev. 2 18 11 3 3 10 2 3 4 2 8 2 3

ЦЗ Obsolete Select New or Updates Deselect Щ

Status □ Wof installed Installed £ j Not installed Not installed Q Not installed □ Not installed Not installed p i Not installed Г • Not installed Г Not installed §& Installed Not installed [ ' Not installed j

Xnstoti pfedugefc*.

j

Octet? paduqps...

Done loading packages,

Рис. 1.8. Установка USB-драйверов для устройств Android


24

Гпава 1

Хотя существует возможность непосредственной отладки приложения на реальном устройстве (подключаемом к компьютеру через интерфейс USB), лучшим решени­ ем станет развертывание на компьютере эмулятора Android — говоря языком про­ граммиста, Android Virtual Device (AVD). Для лучшей формализации процесса раз­ вертывания эмулятора вновь разобью наши действия по пунктам. 1. Установите на компьютер образ системы Android (Android system image). Для этого: •

нажмите кнопку Пуск Windows и выберите пункт Все программы | Embarcadero RAD Studio ХЕ5 | Android Tools;

в появившемся на экране окне Android SDK Manager отметьте "галочкой" элемент ARM EABI v7a System Image в интересующей вас версии ОС Android и нажмите кнопку Install nn package. Подтвердите свой выбор в оче­ редном окне Choose Packages to Install (рис. 1.9) и дождитесь установки с сайта Google виртуальной машины, SDK и других бесплатных модулей.

|Geog*M *.«а»7йЭД IGoes* Ю [SaxtttneWttp&Z ' S5nsK**G*®ty*W

....... ~ "

......-•

ывч&в.

SDKPath', C?’U

I«юл* {ЗЗФз»x m«si5 wj Pfess* * 40Qd&: M Рйопе 52-S0C3 * 43iop; topi)

I ДОМ р*SSJftKrmtpS Ю *ДОокnHilpi} и Teeiet /домр * 152435; ж а д

Toofe 15 / Andfoj

§ > An*#

ggjbAmM Ci/Androsdfl ooei ®#sor§ ШШмм® mжййгш

Package Description & license v " Android SDK ticem *

V Android SDKT«eh, revision 22.05 ч / Д/idixiid SDK PSatform-toots, revision j Documentation for Android SDK, APS •; SOJCPi«fc»mAfKtroki43aAP.U8, n*v| •4^ S*mp(« fewSDKAP518, rev»»» J ч / АРМ ЕАИ v?a System image, Andrcit.j V* Google APIs, Android APi Ift, л т м п { у * Sources for Android SDK API 18, r«ws; V* Android Support library, revision 18 4/ Googie USB Driver, revision S Inlei Android Sysimige license ’“x intei Atom System Image, Andros*

Andro'-d SDKToois, (wftion 22.05 •Android SDKPf*tfo«n-toeti- f«vision 13.91 Documentationfor Android SDK, APJ18, revision I SDK Platform Android 4.3, APi нм яоп 1 Sample? for SDK API18, revijsen t ARM tA6i v?a System Image, Android API Й . revision 2 Googie APJs, Andreid API 18, revkwm2 Sources for Android SDK APJ13, rewstwn 1 Android Support librasy, revatooiS •Gocgit USB Drtv«rr «w sonS Terms snd Conditions

Done folding pjeka

Р и с . 1.9. У с та н о в ка в и р т у а л ь н о й м а ш и н ы д л я п р и л о ж е н и й A n d ro id

2. Завершив инсталляцию необходимых библиотек, приступим к созданию вирту­ ального устройства. Для этого: •

при посредничестве кнопки Пуск Windows вновь доберитесь до элемента меню Android Tools;


Подготовка к работе

25

в меню появившегося на экране Android SDK Manager найдите элемент Tools | Manage AVDs;

нажав кнопку New в окне Android Virtual Device Manager, приступаем к оп­ ределению свойств виртуального устройства (рис. 1.10). В обязательном по­ рядке конкретизируйте тип устройства (раскрывающийся список Device) и целевую платформу (раскрывающийся список Target). Рекомендую устано­ вить флажок Use Host GPU, это заставит эмулятор при работе с OpenGL ис­ пользовать функционал вашего графического процессора. ------------------------------------------- , Edit Android Virtual Device (AVD) Щ Android Virtual Devke Mailed

..

а

aaaad

[Dendroid Virtual Devices ЦDevtcd .ist of existing Android Virtual | ------ ----------------- -----------AVD Name Target 1 —.

Ы&Ш1

Device:

j Nexus 4 (4.7'', 768 x 1280: xhdpi)

,T j

Target

j Android 42.2 • API tevet 17

~'

CPU/ABt

{/UtMf«me»bi-v7*5

Keyboard:

nssiri

Ц) Hardware keyboard present iijW et*.,

1 Skin: i ! i ? 11 11

Ш display a skin with hardware controls

Front Camera:

jNone

▼|

§j Back Camera:

None

<rj

\ Repaitw \

Г

Start-

II 1 |l

i

1 g I! 11 I ?1 71 1*

Msmofy Options:

RAIvl: 256

Internal Storag,.

m

j .

j Details.*.

|

VM H eap: 64

M|B

SD Card: • She

100

{MS *1 | Refresh j

! 1 X Ari Android Virtual Device Emut tion Options: J j g Sn5p,-ho!

, ^ U5i Ho5t GPlJ

О Override th e existing A¥r0 with the seme name

[

OK

j j

Cancel % j

Р и с . 1.10. С о з д а н и е в и р т у а л ь н о го у с т р о й с тв а

3. Проверим эмулятор устройства. Для этого найдите его в перечне сконфигуриро­ ванных нами устройств и нажмите кнопку Start (рис. 1.11). Зам ечание

Запуск эмулятора устройства на базе платформы Android — достаточно трудоемкое мероприятие и может занимать несколько минут!


26

Гпава 1

List of existing Android Virtual Devices located at C:\Users\dmnVandroid\avd AVO Name

Target Name

V ”AVD.Android.,.. Android 4,2,2 Android 43 S^AVD.43

Platform

API Level

CPU/АИ

A2.2

17

43

IS

ARM (armeabi-v„, ARM (afrncabi-v,.. j

\Я Ш ш Edit,,.

f ftcpair^rj I jS f e , и i V* A valid Android Virtual Device. §§} A repairable Android Virtual Device. X An Android Virtual Device that failed to load. Click Details' to seethe error.

Р и с . 1.11. З а п у с к в и р т у а л ь н о го у с т р о й с тв а

Рис. 1.12. Выбор эмулятора устройства для проверки приложения д ля Android


Подготовка к работе

27

4. Подготовив к работе эмулятор Android, мы можем приступать к программиро­ ванию. Для этого создайте новое приложение для мобильной платформы и в обязательном порядке в дереве менеджера проектов выберите узел с целевым эмулятором (рис. 1.12). Осталось нажать клавишу <F9>...

Что делать, когда код зависит от платформы? Несмотря на то, что библиотека FireMonkey предлагает нам универсальный инст­ румент, позволяющий создавать приложения, предназначенные для работы на раз­ ных программных платформах, надо понимать, что ни одна из библиотек не в со­ стоянии полноценно заменить родной API целевой операционной системы. Поэто­ му уже в данной главе позволю дать вам совет — если в вашем проекте потребуется воспользоваться функциями API, принадлежащими исключительно Windows, OS X или iOS, то перед обращением к ним необходимо явным образом указать компилятору об их использовании так, как предложено в листинге 1.1.

procedure TForml.FormShow(Sender: TObj ect); begin {$IFDEF MSWINDOWS} //код для операционной системы Windows Labell.Text:='Hello, Windows!'; {$ELSE} {$IFDEF MACOS} //код для операционной системы OS X Labell.Text:='Hello, Mac!'; {$ELSE} {$IFDEF IOS} //код для мобильной платформы iOS Labell.Text:='Hello, iPhone (iPad)!'; {$ELSE} {$IFDEF ANDROID} //код для мобильной платформы Android Labell.Text:='Hello, Android!'; {$ENDIF} {$ENDIF} {$ENDIF} {$ENDIF}

Во время компиляции вашего приложения Delphi разберется с подключением к проекту необходимых библиотек и создаст бинарный код для требуемой опера­ ционной системы. Еще одна задача, которую, возможно, придется решать разработчику кроссплатформенного приложения, связана с определением базовых характеристик операци­ онной системы, под управлением которой запускается его творение. Если речь идет о настольном приложении, предназначенном для работы под Windows или OS X, то


28

Гпава 1

наиболее универсальным помощником станет интеллектуальная запись TOSVersion, объявленная в программном модуле System. Sysutiis. С помощью этой записи мы в два счета выясним тип ОС и процессора (листинг 1.2). Пнстинг 1.2. Определение ОС и процесс

з нястолыс м приложении

procedure TForml.FormShow(Sender: TObject); var s:string; begin

//------------------------------------o s -----------------------------------case TOSVersion.Platform of pfWindows s: = 'Windows'; pfMacOS s:='0S X'; pfiOS s :='iOS'; pfAndroid s :='Android'; pfWinRT s :='Win RT'; pfLinux s :='Linux'; end; Labell.Text:='Операционная система '+s; Label2.Text:=TOSVersion.ToString;

/ / ------------------------------- CPU--------------------------------------------case TOSVersion.Architecture of TOSVersion.TArchitecture.arIntelX86 : s:='IntelX86'; TOSVersion.TArchitecture.arIntelX64 : s :='IntelX64'; TOSVersion.TArchitecture.arARM32 : s:='ARM32'; else s :='Неопределена'; end; Label3.Text:='Архитектура процессора '+s; Label4.Text:=Format('Ядер id',[ TThread.ProcessorCount]); end;

Если речь идет о мобильной платформе iOS, то для получения сведений об устрой­ стве и его ОС можно воспользоваться услугами API операционной системы, под­ ключив К Проекту МОДУЛЬ iOSapi.UIKit (листинг 1.3). ; Листинг 1.3. Операционная система и устройство в мобильном приложении uses iOSapi.UIKit; {$R *.fmx} procedure TForml.FormShow(Sender: TObj ect); var MobileDevice : UIDevice; s :string; begin MobileDevice := TUIDevice.Wrap(TUIDevice.OCClass.currentDevice);


Подготовка к работе

case MobileDevice.userlnterfaceldiom of UlCJserlnterfaceldiomPhone: s :=' iPhone'; UlUserlnterfaceldiomPad : s:='iPad'; else s :='Other'; end; Labell.Text: = 'ycT poiicTB O '+s; Label2.Text := Format('ОС: %s %s', [MobileDevice.systemName.UTF8String, MobileDevice.systemVersion.UTF8String]); end;

29


ГЛАВА 2

Забываем VCL? Идея платформы FireMonkey основана на идее объектно-ориентированного про­ граммирования (ООП), поэтому изучение недр FireMonkey стоит начать с обзора опорных классов библиотеки. И здесь нас ждет первый сюрприз. Одного взгляда на иерархию наследования окажется достаточно для того, чтобы понять, что новейшая библиотека проектировалась не с "нуля". В начале цепочки наследования вы обна­ ружите хорошо знакомые по классической библиотеке VCL классы TObject, TPersistent и, конечно же, TComponent (рис. 2.1), и только затем, на четвертой сту­ пени иерархии вам встретится первый "кроссплатформенный" класс TFmxObject. Так что даже если вы решили окончательно и бесповоротно перейти на разработке своих приложений исключительно на FireMonkey, классическую библиотеку VCL вам забыть не удастся! Вернемся к VCL. На рис. 2.1 представлен фрагмент иерархии наследования классов Delphi. Здесь вы обнаружите наиболее важные классы платформ VCL и FireMonkey (FMX). Почему поставлен акцент на слово "важные"? Потому что именно эти клас­ сы (за счет абстрагирования, инкапсуляции, наследования и полиморфизма и дру­ гих механизмов, составляющих концепцию ООП) определяют основные родовые черты своих многочисленных кроссплатформенных потомков. Изучив опорные классы, мы получим представление о ключевых особенностях всех классов библио­ теки. Внимание!

Несмотря на то, что библиотека FireMonkey базируется на библиотеке визуальных компонентов VCL, в проектах FireMonkey невозможно задействовать компоненты этой библиотеки.

Опорный класс VCL — TObject На вершине древа наследования расположен класс TObject. Отчасти это небожи­ тель, выше него нет никого. Класс TObject не без гордости взирает с Олимпа на свое многочисленное потомство, ведь в каждом из потомков класса есть его родо­ вые черты.


TObject (System)

TPersistent (Classes)

TComponent (Classes)

Платформа VCL Платформа FMX Главное и всплывающее меню ^ Контейнер для изображений, область для рисования, примитивные графические фигуры, траектория Ко.минированные списки, блокнот, календари, панели, инструментальные танки, блокноты

-

1

TFmxObject (FMX.Types)

TComrrranCustom Form (FMX.Forms)

TControl (FMX.Controls)

TStyledControl (FMX.Controls)

T Application (FMX.Forms)

TCustomForm (FMX.Forms)

TCustomForm3D (FMX Forms3D)

Формы h\1X Метки, кнопки выбора, флажки,■+юлас ти гр\ ппировки

TTextControl (FMX.Controls)

TCustomButton (FMX.StdCtrls)

[ TScrollBox | I (FMX.Layouts) 1

Списки, миогострочный текс то вы й редакт ор. дерево, сетки,

TControl3D (FMX.Types3D)

Т

TCustomEdit (FMX.Edit)

Строки ввода

Кнопки Р и с . 2.1. И е р а р х и я н а с л е д о в а н и я V C L -F M X в D e lp h i Х Е 5

т

Компоненты 3D TEffect (FMX.Filter. Effects)

Визуальные эффекты


32

Гпава 2

К задачам, решаемым классом TObject, в первую очередь стоит отнести: □ создание, поддержку и уничтожение объекта; распределение, инициализацию и освобождение памяти, необходимой для этого объекта; □ возврат всей информации об экземпляре класса, в том числе данных RTTI об опубликованных свойствах и методах объекта; □ поддержку взаимодействия объекта с внешней средой с помощью сообщений и интерфейсов.

Управление жизненным циклом объекта Исследование вклада класса TObject в классы VCL начнем с конструктора и дест­ руктора. Это наиболее важные методы для любого из класса VCL — они управляют существованием объекта. constructor Create; //создание объекта destructor Destroy; virtual; //разрушение объекта

Пока мы видим наиболее простое объявление конструктора и деструктора, в ряде более поздних классах методы усложнятся и дополнятся параметрами. Вам наверняка приходилось размещать на форме проекта Delphi различные компо­ ненты (среди них кнопки, строки ввода, метки). Это очень удобно: для создания элемента управления достаточно взять компонент с палитры и положить его на форму. А вы пробовали создавать элементы управления и размещать их на форме, не обращаясь к палитре компонентов? Если нет, то следующий пример окажется интересным. Выбрав пункт меню File | New | FireMonkey Desktop Application, создайте новое приложение HD. Убедитесь, что в строке uses проекта упомянут модуль FMX.stdctris (в нем расположен код стандартных элементов управления). В переч­ не событий формы Formi (на вкладке Event's Инспектора объектов) найдите собы­ тие Onshow. Двойной щелчок левой кнопкой мыши в строке напротив названия со­ бытия заставит Delphi создать код обработчика события, вызываемого в момент показа формы на экране. Вам осталось лишь повторить несколько строк кода, про­ веденных в листинге 2.1. Листинг 2 1. Демонстрация ваамох

ет Ико: зт

тс а

uses ..., FMX.StdCtrls; procedure TForml.FormShow(Sender: TObj ect); var Btn : TButton; //переменная класса кнопки TButton begin Btn:=TButton.Create(Forml); //вызов конструктора класса TButton Btn.Parent:=Forml; //размещаем кнопку на форме end;


33

Забываем VCL?

После запуска приложения в левом верхнем углу формы появится кнопка. Она воз­ никла не из воздуха, это следствие вызова конструктора create () для экземпляра класса TButton. Обязательно проведите небольшой эксперимент— замените в коде листинга 2.1 класс TButton на TEdit (или тмеяю, TLabei, TComboBox). Это придется сделать в двух местах кода события onShow (): в секции локальных переменных и в строке вызова конструктора. Вновь запустите проект на выполнение. В результате на свет появит­ ся элемент управления именно того класса, который вы указали. Как видите, унас­ ледованный от TObject конструктор выполняет свою задачу во всей иерархии клас­ сов Проведем еще один пример. На этот раз он посвящен исследованию деструктора класса— методу Destroy о. Для опыта нам понадобится помощь нового проекта HD Appi»cai;on. Разместите две кнопки TButton (вы их найдете на странице S ta n d ard палитры компонентов) на форме. Научим одну из кнопок уничтожать своего "коллегу". Для этого дважды щелкните левой кнопкой мыши по кнопке Buttoni и в появившемся в окне редактора кода обработчике события OnCiick на­ пишите всего одну строк}- кода (листинг 2.2).

proc'idurs TForml.ButtcnlCiick (Sender: TOb ject); bagin Eutton2.Destroy; //уничтожение экземпляра кнопки Button2 end;

Запустив проект, щелкните по кнопке Buttoni. Это действие вызовет деструктор объекта Button2, и элемент управления исчезнет с поверхности формы. Но на этом эксперимент не завершен. Вновь щелкните по кнопке Buttoni. В ответ разгневан­ ный отладчик Delphi выведет на экран примерно такое сообщение, как на рис. 2.2. Причина возмущения отладчика в том, что мы потребовали от Delphi невозможно­ г о — повторно уничтожить уже несуществующий объект. Для того чтобы впредь избежать подобных исключительных ситуаций, в наших программах вместо Destroy () для уничтожения экземпляра объекта следует использовать метод p ro c e d u re

Free;

D ebugger Exception Notification Prna«d P roject!,e:. raised exception dass 'C000Q005 with message ’access violation а ' Qx0Ga0cile3: read o f address 0*00000000’,

Continue

Ij

Wdp

P ; Ignore this exception type Р и с . 2.2. С о о б щ е н и е об о ш и б ке д о с т у п а E A c c e s s V io la tio n к н е с у щ е с т в у ю щ е м у о б ъ е кт у


34

Глава 2

Это более осторожный метод. Перед тем как вызвать деструктор объекта, он убе­ дится в факте существования этого объекта. Для проверки возможностей метода замените в листинге 2.2 обращение к деструктору D e s t r o y на F r e e и вновь повтори­ те опыт. На этот раз повторный щелчок по кнопке не приведет к исключительной ситуации.

Механизм учета ссылок в мобильных проектах Мобильные приложения для контроля жизненного цикла объектов ведут свт ом ат ический подсчет ссы лок (Automatic Reference Counting, ARC) на удаляемый объ­ ект. В момент создания объекта счетчику ссылок присваивается единичное значе­ ние, а в момент ухода объекта из области видимости компилятора (например, по завершению локальной процедуры, внутри которой был объявлен и создан объект) значение счетчика уменьшается на единицу, и если он оказывается равным нулю — объект уничтожается. Благодаря подсчету ссылок существенно расширяются наши возможности по управлению объектом. В простейшем случае об уничтожении локальных объектов можно просто не думать — они удалятся сами. При необходимости программист имеет возможность самостоятельно уменьшить счетчик ссылок. В современной версии Delphi ХЕ5 вы имеете возможность прекратить использова ть этот объект, не дожидаясь его выхода за область видимости (листинг 2.3). Зам ечание ARC

ни в коем сл уч а е не яв л я е тся экв и в а л е н то м

с б о р щ и ка м усо р а , и с п о л ь зу е м о го

в п л а т ф о р м е .N E T . В о т л и ч и е п л а т ф о р м ы о т .N E T ( в к о т о р о й п о с т о я н н о т р у д и т с я ф о ­ новы й

процесс,

очищ аю щ ий

освободивш ую ся

пам ять

спустя

к а к о е -т о

время

после

" у н и ч т о ж е н и я " о б ъ е к т а ) в A R C п а м я т ь о с в о б о ж д а е т с я в т о т я се м о м е н т , к а к о б н у л и т с я счетчи к.

Листинг 2.3. O c i - >бождение объоета а . :обилы: >ix проектах X E 5 0 b je c t := T X E 5 0 b je c t.C r e a te ( ) ; X E 5 0 b j e c t . X E 5 0 b j e c tM e th o d ; X E 5 0 b je c t := n il;

Передав объектной переменной неопределенное значение nil, мы говорим новому компилятору Delphi ХЕ5, что объект нам больше не нужен. После этого компиля­ тор самостоятельно позаботится о том, чтобы объект уничтожился после того, как завершится выполнение его метода. Внимание! В

новом

ком пилято ре

F r e e A n d N ill ()

с

по д д е рж кой

ARC

д еструкто ры

D e s tro y (),

F re e ()

и

не с м о гу т я в н ы м о б р а зо м у д а л и ть о б ъ е кт, а л и ш ь у м е н ь ш а т с ч е тч и к

ссы л о к. Т а ки м о б р а зо м , в м о б и л ьн ы х п р о е кта х в м е сто вы зо в а кл а сси че ски х д е с тр у к­ то р о в д остаточно об нулить ссы л ку — пр исво ив об ъ екту n i l .


35

Забываем VCL?

У всех потомков тоь j ect предусмотрено доступное только для чтения свойство property RefCount: Integer; //только для чтения

с помощью которого мы сможем узнать число ссылок на объект. Что делать, если программисту (скорее не просто программисту, а коварному про­ граммисту) требуется расправиться с объектом, невзирая на состояние счетчика ссылок? Для этого на уровне TObject в Delphi ХЕ5 появился весьма кровожадный деструктор procedure DisposeOf;

который уничтожает объект самым тоталитарным способом. С точки зрения чисто­ ты программирования это не очень хорошо, т. к. после работы DisposeOf () память после объекта не освобождается. Подобный объект будет вести себя неадекватно, с одной стороны его уже нет, а с другой вы можете обратиться к его свойствам (ведь в памяти объект остался). Если логика программы предполагает использова­ ние DisposeOf (), то, по крайней мере, перед обращением к объекту проверяйте со­ стояние свойства property Disposed: Boolean;//только для чтения

и если свойство возвратит true, лучше этот объект более не задействовать.

Информирование о классе В составе класса TObject имеется ряд полнофункциональных обычных методов и м ет од о в класса (class function), позволяющих получить подробную метаинформа­ цию о классе. Ключевые методы TObject представлены в табл. 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 ClassNamels(const Name: string): Boolean;

П р овер ка, яв л я е тся ли о б ъ е кт экзе м п л я р о м кл а сса

Name

class function InheritsFrom(AClass: TClass): Boolean;

AClass

class function ClassParent: TClass;

В о з в р а т и т н е п о с р е д с т в е н н о го п р е д ка о б ъ е кта

П р овер ка, яв л я е тся ли о б ъ е кт п о то м ко м кл асса


36

Гпава 2 Таблица 2.1 (окончание)

Метод

Описание

class function MethodName(Address: Pointer): string;

Возвращает имя метода по указателю. Метод должен быть опубликован в секции published

class function MethodAddress(const Name: string): Pointer;

Возвращает указатель на метод по его имени. Метод должен быть опубликован в секции published

function FieldAddress(const Name: string): Pointer;

Возвращает указатель на поле с именем Name. Поле должно быть опубликовано в секции published

Все перечисленные в табл. 2.1 методы являются мощным оружием, позволяющим подготовленному программисту не только создавать эффективный код, но и при необходимости проводить исследовательскую работу (листинг 2.4). Листинг 24. Исследуем цепочку наследования класса тиввю var C:TClass; begin С :=Memol.ClassType; while C onil do begin Memol.Lines.Add(C.ClassName+' -> '+C.UnitName); C:=C.ClassParent; end; end;

B листинге предложен код, демонстрирующий порядок получения полной цепочки наследования любого объекта из библиотек VCL и FireMonkey. В нашем примере в качестве "подопытного" мы взяли класс многострочного текстового редактора тмешо. Результат работы отражен на рис. 2.3. ^

О

T O s j!x t,

*■ от sjosa

TMamo -> FMXMemo TScrotSo* •> FMXLayouis TSlyiedContral -> FMX.Controts TControl •> FMX Controis TFm*Ot)ject •> FMXTypes TCon^Jonent ■> System. Classes TPersisteni > System Classes TO0)©ct •> System

Р и с . 2.3. П е р е ч е н ь п р е д ко в кл а сса Т М е т о


Забываем VCL?

37

Класс TPersistent Одну из наиболее значимых ветвей классов начинает ближайший сподвижник TObject— класс TPersistent (модуль System.classes). Основное предназначение класса —1научить своих потомков работать с памятью, что позволит им загружать данные из области в памяти и выгружать данные в другую область. Как и в случае с тоьj ect, основу рассматриваемого класса составляют только мето­ ды. Среди них наибольшего внимания заслуживают виртуальные методы присвое­ ния данных: procedure Assign(Source: TPersistent); virtual; procedure AssignTo(Dest: TPersistent); virtual;

Перечисленные процедуры позволяют разделить одну и ту же область памяти меж­ ду несколькими объектами— потомками TPersistent. Метод Assign о позволяет одному объекту подключиться к ресурсу другого, метод AssignTo о — передать ре­ сурс от одного компонента к другому. Для демонстрации этой полезной возможности создайте новый проект и разместите на его главной форме два компонента т image (они предназначены для хранения и отображения векторной и растровой графики) и кнопку TButton. Воспользовавшись свойством Bitmap компонента 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-разрядной ОС.

Осталось написать одну строку кода в обработчике события onciick кнопки Buttonl (листинг 2.5).

procedure TForml.ButtonlClick(Sender: TObject); begin Image2.Bitmap.Assign(Imagel.Bitmap);

Щ елчок по кнопке приведет к тому, что во втором компоненте image2 отобразится та же самая картинка, что и в imagel. Еще раз подчеркну, что метод Assign о не создал еще одну копию картинки, а разделил одну и ту же область памяти между двумя потомками класса TPersistent. Надо понимать, что методы Assign о и AssignTo о позволяют разделять ресурс только между совместимыми компонентами, способными прочитать этот ресурс. Встретив даже такую, абсолютно абсурдную строку кода Buttonl.Assign(Imagel.Bitmap);


38

Гпава 2

среда проектирования не станет сопротивляться. Но полученный исполняемый код окажется неработоспособным, т. к. мы попытались передать кнопке неприемлемую область памяти. Научив несколько объектов работать с общей областью памяти, класс TPersistent позаботился еще об одной важной детали — при необходимости любой из совме­ стно работающих объектов имеет право уйти в автономный режим работы, при этом не нарушая состояния своих коллег. Допустим, что у нас имеются текстовый редактор M e m o l:T M e m o , хранящий какой-то перечень текстовых строк, и его коллега мешо2: тмешо. После вызова строки кода Мешо2.Lines.Assign(Memol.Lines);

оба компонента разделят общую область памяти и станут отображать одинаковые текстовые строки. Но как только какой-то из компонентов захочет изменить хотя бы запятую в тексте, то для него моментально выделяется отдельная область памя­ ти, в которую переносятся все изначальные строки и новый текст.

Основа компонента TComponent Приступим к рассмотрению третьего по счету в цепочке наследования (см. рис. 2.1) класса — класса TComponent. Наш очередной знакомый построен на основе только ЧТО изученного класса TPersistent (модуль System. Classes). TComponent = class(TPersistent, Ilnterface, IlnterfaceComponentReference)

Обсудим вклад TComponent в библиотеки VCL и FireMonkey. Во-первых, от него бе­ рут начало все компоненты, которые вы найдете на палитре компонентов Delphi. Во-вторых, в логику класса заложено понятие "частной собственности". Другими словами, в TComponent предусмотрено право владения, когда один объект выступает владельцем другого объекта. В-третьих, класс расширяет возможности по хране­ нию и обслуживанию данных. В-четвертых, в отличие от своих предков (опери­ рующих только методами) класс TComponent обладает свойствами. Самое главное свойство, подаренное TComponent своим наследникам, — имя. property Name: TComponentName; // type TComponentName: string;

Каждый компонент, задействованный в проекте Delphi, обязан иметь уникальное имя. Имя присваивается автоматически в момент переноса элемента управления с палитры компонентов на поверхность рабочей формы проекта. Механизм выбора имени достаточно прост: берется имя класса (допустим, TButton), убирается первый символ "Т" и к окончанию имени добавляется порядковый номер элемента управ­ ления этого типа на форме. Так первая кнопка, размещенная на форме, получит на­ звание Buttonl, вторая — Button2 и т. д. У каждого компонента опубликовано свойство, никогда не используемое системой property Tag: Longint;


Забываем VCL?

39

Это свойство часто применяется программистами для дополнительной иденти­ фикации объекта или хранения ассоциированных с объектом целочисленных зна­ чений.

Владение объектами При разработке проекта на основе формы (класс TForm) мы располагаем на поверх­ ности формы кнопки, строки ввода, метки и другие компоненты, необходимые для работы приложения. Форма (дальний потомок TControl) становится владельцем (owner) этих компонентов. В свою очередь, каждый из компонентов знает, кому он принадлежит, для этого следует опросить свойство property Owner: TComponent;

Для того чтобы любой компонент прямо в момент своего рождения приобре­ тал владельца на уровне класса TComponent, несколько усовершенствован конст­ руктор constructor Create(AOwner: TComponent);

В объявлении появился параметр AOwner, благодаря ему устанавливается связь ком­ понента с его владельцем в момент вызова конструктора. Если вы вернетесь к пер­ вому примеру из этой главы (см. листинг 2.1), то увидите, что при создании кнопки мы указали, что ее владельцем становится форма Fomi. Внимание!

Уничтожение объекта-владельца влечет за собой уничтожение всех принадлежащих ему объектов.

Владелец компонентов имеет представление о числе принадлежащих ему компо­ нентов и в состоянии обратиться к каждому из них. Для этого предназначены соот­ ветствующие свойства: property ComponentCount: Integer; //число компонентов property Components[Index: Integer]: TComponent; //список компонентов

Каждый компонент знает свой индекс в списке компонентов своего владельца: property ComponentIndex: Integer;

Ряд методов позволяет манипулировать списком компонентов, для которых TControl выступает владельцем. Удаление компонента из списка обеспечивается процедурой procedure RemoveComponent(AComponent: TComponent);

Полная очистка списка с одновременным уничтожением компонентов: procedure DestroyComponents;

Вставка в конец списка нового компонента: procedure InsertComponent(AComponent: TComponent);


40

Гпава 2

Поиск компонента-потомка в списке: function FindComponent(const AName: string): TComponent; Зам ечание

Для создания группы из нескольких элементов управления с возможностью их совме­ стного использования (перемещения, выравнивания, вращения и т. п.) лучше всего воспользоваться помощью компонента TLayout.

Завершая эту главу, вновь повторюсь, что VCL не отвергнут, и фундамент "древ­ ней" визуальной библиотеки компонентов задействуется в интересах сверхновой библиотеки FireMonkey. Вместе с тем надо знать, что у FireMonkey интерес к VCL угасает сразу после прохождения уровня класса TComponent. Дальше начинают ра­ ботать исключительно классы новой платформы.


ГЛАВА 3

Классы-шаблоны Современный язык Delphi приобрел возможность работы с ш аблонам и или, говоря точнее, с обобщ енны м и т ипами (generics types) данных1. Своим появлением на свет шаблоны обязаны желанием разработчиков развить возможности ООП. Именно в языках программирования, основанных на ООП, код приобрел истинную универ­ сальность и стйл позволять программистам повторно использовать первоначально разработанный алгоритм для решения новых задач. Создав исходный класс для применения в какой-то прикладной задаче, мы имеем возможность перенести свой класс (или его потомков) в другой проект. Однако обычные классы, пусть даже усиленные за счет наследования, инкапсуляции и полиморфизма, далеко не универ­ сальны, ведь их методы и свойства нацелены на обслуживание строго определен­ ных типов данных. Концепция обобщенного типа данных как раз и предназначена для устранения этого недостатка — она позволяет программисту абстрагироваться от типа данных. Благодаря шаблонам разработчик получает возможность создавать алгоритмы, не указывая типы данных, с которыми он работает. Услуги обобщенно­ го типа данных окажутся весьма кстати при описании схожих алгоритмов, зани­ мающихся обработкой разноплановых данных. Например, это позволяет реализо­ вать классические алгоритмы сортировки массивов самых разнообразных типов данных в рамках одного и того же метода класса. Зам ечание

Благодаря шаблонам мы получаем удобную возможность отделения алгоритма от конкретных типов данных, с которыми он работает.

Обобщенный тип данных в полях записей Основные приемы работы с обобщенным типом данных наиболее просто проде­ монстрировать с помощью записи Delphi. Еще в первых версиях языка Pascal по­ явилась запись — структурный тип, специализирующийся на предоставлении дос­

1В некоторой литературе вместо термина "обобщенный тип" применяют термин "родовой тип".


42

Гпава 3

тупа к некоторому перечню разнотипных полей. В современных версиях Delphi программисту представилась уникальная возможность включать в состав записи не только строго типизированные поля, но и поля-обобщения, т. е. поля, тип данных которых заранее не определен. Описание шаблона записи с полями обобщенного типа выглядит следующим образом: type имя_типа_записи <имя_обобщенного_типа[,...]> = record имя_поля: имя_обобщенного типа; [Другие поля]

'

Наличие угловых скобок уведомляет нас, что запись намерена работать с обобщен­ ным типом данных. Внутри угловых скобок перечисляются парам ет ры типов (type parameters). С одной стороны, параметр ти п а — это имя переменной, которое мы задействуем внутри исходного кода класса в тех местах, где используется соответ­ ствующий тип данных. С другой стороны, параметр типа — это псевдоним типа данных, которым воспользуется программист при обращении к классу извне. Зам ечание

В синтаксических конструкциях языка Delphi угловые скобки < . . . > являются визитной карточкой обобщенного типа данных.

Отметим, что при определении имени обобщенного типа нет необходимости вспо­ минать о существовании предопределенных типов данных Delphi. Имя обобщенно­ го типа — это всего лишь псевдоним какого-то заранее неизвестного типа данных, во время выполнения программы им может оказаться целое или вещественное чис­ ло, строковый или символьный тип либо какой-то другой тип данных. Листинг 3.1 демонстрирует порядок объявления шаблона записи с полями обоб­ щенного типа. Перечень имен, используемых в записи обобщенных типов данных, приводится внутри угловых скобок, сразу за названием типа записи. i

Л исгагг ЭЛ. 0 >ъявЬение шаблона зал» ей с nw w ut; обобщение го типа

type TGenericRec<T,R>= record A : char; //в любом случае это символьное поле В,С : Т; //поля обобщенного типа Т D : R; //поле обобщенного типа R end; var GRecl:TGenericRec<integer, double>; //объявление переменной GRecl GRec2:TGenericRec<boolean, string>; //объявление переменной GRec2 begin GRecl.B:=15; //поле В хранит целое значение GRecl.D:=0.675; //поле D хранит вещественное значение

//... GRec2.B:=true; //поле В хранит булев тип данных GRec2.D:='Hello, World!'; //в поле D находится строка с текстом //. .. end.


Классы-шаблоны

43

Во время инициализации переменных имена обобщенных типов заменяются назва­ ниями реальных типов данных. В нашем примере при объявлении GReci (см. лис­ тинг 3.1) вместо обобщения т мы задействуем целочисленный тип integer, вместо r — вещественный тип double. При объявлении GRec2 мы меняем правила игры: теперь обобщенный тип т замещается типом boolean, r — string.

Обобщения в процедурах и функциях Если абсграгироваться от типа данных, окажется, что логика очень многих алго­ ритмов абсолютно одинакова. Например, алгоритм быстрой сортировки идентичен как для массива целых, так и для массива вещественных чисел. В подобных ситуа­ циях благодаря созданию обобщенной функции (функции с параметром обобщен­ ного типа) мы сможем определить смысл алгоритма, не отвлекаясь на обслужи­ ваемый тип данных. Поэтому обобщенный тип данных может оказаться весьма полезным при разработке шаблонов процедурных типов данных, способных вос­ принимать параметры любых типов. По аналогии с объявлением обычной процедуры или функции, при создании шаб­ лона процедуры или функции сначала указывают ключевое слово type, а затем имя типа. Сразу за именем следует синтаксический элемент, характерный для обобще­ ний — пара угловых скобок <. . . > с названием обобщенного типа данных. type тии_функШи <имя_обобщенного_типа[,...]> = function([параметр_обобщенного_типа имя_обобщенного_тила; type тип_процедуры <имя_обобщенного_типа [,...]> = procedure([параметр_обобщенного_типа [,...]]

Листинг 3.2 демонстрирует порядок создания шаблона функции. В примере объяв­ лена функция класса GetMaxValue () с двумя параметрами, тип которых не опреде­ лен. Задачей функции является сравнение двух значений и возврат наибольшего из них.

uses System.SysUtils, System.Generics.Defaults; type TGetMax = class class function GetMaxValue<T>(X, Y: T):T; end; class function TGetMax.GetMaxValue<T>(X, Y: T): T; var Comparer: IComparer<T>; begin {к сожалению, нельзя написать такой код: if X>Y then Result:=X else Result:=Y; ведь мы не знаем тип данных сравниваемых параметров} Comparer:=TComparer<T>.Default;


44

Гпава 3

if Comparer.Compare(X,Y)>0 then Result:=X else Result:=Y; end; var A :integer; В :extended; C:ansichar; begin A:=TGetMax.GetMaxValue<integer>(10,20); //сравниваем целые числа WriteLn(A); B:=TGetMax.GetMaxValue<extended>(l/3,2/3); //вещественные числа WriteLn(В); С :=TGetMax.GetMaxValue<ansichar>('A ','В ');//сравниваем символы WriteLn (С) ; ReadLn;

Реализация обобщенной функции требует ряда комментариев. К сожалению, срав­ нивая данные неизвестного типа, мы не имеем возможности воспользоваться обыч­ ными операторами сравнения (например: if x<y then ...). Вместо этого мы выну­ ждены обратиться за услугами к интерфейсу icomparer (он описан в модуле System.Generics.Defaults). Этот интерфейс (кстати, также основанный на идее шаблонов) является профессионалом в области сравнения двух величин, для этого у него есть метод Compare (). Все остальное дело техники — в листинге 8.2 мы срав­ нили значения трех различных типов данных.

Обобщенные типы данных в шаблонах классов Синтаксическая конструкция, объявления шаблона класса, способного работать с обобщенным типом данных, не многим отличается от унаследованной еще со времен языка Object Pascal конструкции описания обычного класса. Здесь также присутствуют ключевые слова type и class, и класс-шаблон, так же как и его обыч­ ный "коллега", содержит поля и методы. Однако внимательный читатель обнару­ жит отличительную черту шаблона — пару угловых скобок < . . . > сразу после име­ ни класса: type имя_класса<параметр_обобщенного_типа [, ...]>= class //поля и методы

Простейший пример использования обобщенного типа при объявлении шаблона класса предложен в листинге 3.3.


45

Классы-шаблоны

Листинг 3.3. Объявление и реализация шаблона класса* unit generics_unit; interface type TGeneri,cClass<T>=class fT :T ; procedure SetT(Value:T); function GetT:T; end;

//объявление шаблона класса //поле обобщенного типа //метод записи в поле //метод чтения значения из поля

implementation function TGenericClass<T>.GetT: T; begin Result:=fT; //узнаем содержимое поля

procedure TGenericClass<T>.SetT(Value: T); begin fT:=Value; //записываем данные в поле

Для объявления параметра типа мы воспользовались символом т и заключили его в угловые скобки. Класс содержит единственное поле fT универсального типа т и два метода, позволяющих записывать и считывать значение из поля. Разобравшись с объявлением и реализацией шаблона класса, способного работать с обобщенным типом данных, попробуем воспользоваться его услугами. Лис­ тинг 3.4 демонстрирует порядок обращения к экземпляру класса TGenericCiass с указанием того, что он необходим нам в роли хранилища целого числа. | Листинг 3.4. Применение шаблона клас .

■ ... I

,

................I

~ '.Л............. . .

л * : . . / ■*.”. «С*'

..

г ............ .

var gObject: TGenericClass<integer>; i :integer; begin gObj ect:= TGenericClass<integer>.Create; gObject.SetT(99); //записываем значение 99 i:=gObject.GetT; //считываем значение из класса .gObject.Destroy;

Ключевой элемент листинга 3.4 — это его первая строка. В ней указывается, какой тип данных должен будет задействован после создания экземпляра класса


46

Гпава 3

TGenericClass. Название типа записывается в угловых скобках после имени класса, в нашем случае это целое число integer. С таким же успехом мы можем создать объект любого типа, за небольшим исключением. В параметр типа не стоит переда­ вать статистические массивы, строки старого образца (short string) и записи, со­ держащие разнотипные поля.

Наследование шаблона класса Любой, построенный на основе концепции обобщенного типа данных, шаблон класса вправе выступать в качестве предка для своей цепочки классов-наследников. Объявляя дочерний класс, программист принимает решение о статусе параметра обобщенного типа. Предусмотрены два варианта наследования (листинг 3.5): □ наследование с сохранением обобщенного типа данных, другими словами, класс-наследник остается шаблоном; □ наследование с явной конкретизацией типа данных, который следует использо­ вать при работе дочернего класса. r.cv:j;;r М . Я еследоспсзсге шойлоиа класса

ш&ж

type TGenericParent<T>=class {родительский класс с открытым параметром Т} protected fGP:Т; public procedure SetGP(Value:Т); 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>)


47

Классы-шаблоны

Таким образом, в дочернем классе появился дополнительный параметр обобщенно­ го типа.

Перегрузка методов с параметром обобщенного типа Наличие у метода класса параметра обобщенного типа не служит препятствием при перегрузке одноименных методов. Листинг 3.6 демонстрирует порядок объявления и перегрузки обычного метода и шаблона метода с параметром обобщенного типа. l!i *тинг 3.0. Ш и м в Щ type TDemoClass = class procedure DemoMethod(X: double); overload; procedure DemoMethod<T>(X: T); overload; end;

//обычный метод //шаблон метода

В распоряжении демонстрационного класса имеются два метода DemoMethod (). Па­ раметр первого метода задан явным образом в виде действительного типа double, а параметр второго представлен в виде обобщенного типа т. Листинг 3.7 показывает порядок вызова перегружаемых методов.

var SC:TDemoClass; begin SC:=TDemoClas s .Create; SC.DemoMethod(3.1426); //обращение к DemoMethod(X: double) SC.DemoMethod<Integer>(500); //обращение к шаблону DemoMethod<T>(X: T)

I I ...

Обратите внимание на то, что при вызове шаблона метода с обобщенным парамет­ ром следует определиться с типом обрабатываемых данных, указав его в угловых скобках после названия метода. Зам ечание

Основные обобщенные типы данных Delphi объявлены в модуле Generics. Collections. Здесь вы обнаружите шаблоны для таких структур данных, как списки TList<T>, очереди TQueue<T>, стеки TStack<T>, словари TDictionary<TKey,TValue> ИТ. д.

Шаблон массива, класс ТАггауо В Delphi вы найдете немало классов, работающих с данными обобщенного типа. Давайте познакомимся с одним из наиболее показательных классов — шаблоном массива ТАггауо. Класс-шаблон ТАггауо описан в модуле System. Generics.


48

Гпава 3

Collections и предназначен для оказания помощи программистам при решении задач упорядочивания элементов массива по возрастанию значений и поиску дан­ ных в массиве.

Фрагмент объявления класса ТАггауо предложен в листинге 3.8. Листинг З.С. 0 6 t

вление класса ratera^ с

зС an ,r ic

. Coi: ctione

type TArray = class private class procedure Quicksort<T>(var Values: array of TVconst Comparer: IComparer<T>; L, R: Integer); public class procedure Sort<T>(var Values: array of T); overload; //другие версии процедуры сортировки Sort<T> class function BinarySearch<T>(const Values: array of T; const Item: T; out Foundlndex: Integer; const Comparer: IComparer<T>;Index, Count: Integer): Boolean; overload; //другие версии процедуры бинарного поиска BinarySearch<T> end;

Обратите внимание на то, что класс не предназначен для хранения данных, для это­ го у него нет ни одного поля. Зато в распоряжении тдггау имеются две группы од­ ноименных методов-шаблонов. Методы sort<T>() позволяют сортировать данные в массивах любого простого типа, а методы BinarySearch<T> () найдут в упорядо­ ченном массиве индекс ячейки с заданным элементом. Все процедуры и функции объявлены в качестве методов класса (напомню, что об этом можно судить по сло­ ву class вначале объявления метода), а это означает, что методы подлежат вызову без создания экземпляра класса. Воспользуемся услугами класса ТАггауо для упорядочивания значений в трех раз­ нотипных массивах (листинг 3.9). Для этого задействуем его метод Sort<T> (). )........................................ ................................................. Листинг 3.9. Сортировка маесивов с помощью шаблона класса тдггау

:.................

.........................

................... ' ..................................................................

program TArrayDemo; {$APPTYPE CONSOLE}

uses System.SysUtils, System.Math, Generics.Collections; const Size=10; //размер массивов var A:Array[0..Size] of Integer; //объявляем массивы B:Array[0.,Size] of Real; C:Array[0..Size] of AnsiChar; i:Word;


49

Классы-шаблоны

begin Randomize; //инициализируем генератор псевдослучайных чисел for i:=0 to Size do {заполним массивы случайными значениями} begin A[i]:=Random(100); //генерируем целое число В [i]:=Random(100)/RandomRange(1,100); //генерируем действительное число С [i]:=AnsiChar(RandomRange(33,255)); //генерируем символ end; TArray.Sort<integer>(А);

//упорядочим значения в массивах

TArray.Sort<Real>(В); TArray.Sort<AnsiChar>(С); for i := 0 to Size do //проверим результат сортировки begin Write (’A[',i, ']= ', #9, A[i],#9) ; Write ('В [', i, ']= ',#9,B[i],#9) ; Write ('C[',i, ']= ', #9, С [i] ,#9) ; WriteLn; end; ReadLn; end.

Зам ечание

При разработке класса ТАггауо программисты Embarcadero использовали одни из наиболее эффективных алгоритмов сортировки и поиска. Так упорядочивание эле­ ментов осуществляется с помощью метода, получившего название быстрой сорти­ ровки (quick sort), а поиск реализован методом бинарного поиска (binary search). Оба метода относятся к классу сложности алгоритмов с оценкой N ■Log(N), это визитная карточка наиболее производительных алгоритмов.

Шаблон списка объектов, класс TObjectListo Рассмотрим

очередной

шаблонный

класс,

входящий

в

состав

модуля

System.Generics.Collections. Класс TObjectListo легко справится с хранением лю ­

бого заданного программистом перечня объектов и предоставит доступ к любому из объектов по его индексу. По своей сути класс представляет собой список, способный выступать владельцем объектов. Именно поэтому, при создании экземпляра класса конструктор constructor Create(AOwnsObjects: Boolean = True);

по умолчанию устанавливает аргумент владения AOwnsObjects в состояние true. Впрочем, если по какой-то причине в момент создания списка вы передали в кон­


50

Гпава 3

структор аргумент false, никто не запретит вам передумать и сделать список вла­ дельцем объектов. Для этого обратитесь к свойству property OwnsObjects: Boolean;

из кода вашей программы. Среди примеров к книге есть небольшая программа-игра, демонстрирующая работу TObjectListo. Суть игры заключается в том, что пользователь должен ловить ле­ тящие вниз шарики — экземпляры класса TCircie (рис. 3.1).

1

§ )U n rt2

j

^ S y s te m .G « n e rtc s .C o » e c to n s

j

___

___

■! .11

Д г М О * Н 7р О Ц И Я T O b j ir t U r V t • 1 - (ГМ Д ДЮ Щ Ж .* Ш Щ М Л К И )

T lm e r 2

О

. я

Е

«6 : 4 7

о о

In

В сего

61

Число П о г н а н о ft П р о п у щ е н о 4 5

Скорость

ш ариков

Рис. 3.1. Экранный снимок приложения "падающие шарики"

Не трудно догадаться, что главная роль в коде отводится списку TObjectListo, ко­ торый возьмет на себя обязанности по обслуживанию падающих с "небес" объек­ тов. Работа приложения начинается с создания списка (листинг 3.10). ''"'30*-"*£•...... ...

1Листинг 3.10. Создание описка дяя

г.рг hoj:: я

экземпляров T C irc l »

var Forml: TForm; List: TObjectList<TCircle>; //список объектов TCircie implementation {$R *.fmx} procedure TForml.FormCreate(Sender: TObj ect); begin List:=TObjectList<TCircle>.Create; //здесь будут храниться шарики end;

Как видите, в момент объявления объектной переменной List: TObjectList<TCircie> мы явным образом указываем, что список намерен дружить исключительно с ша­ риками TCircie.


Классы-шаблоны

51

В программе задействованы два таймера. Первый из них, компонент Timeri, отве­ чает за создание очередной порции шариков, это происходит каждый раз, как у таймера генерируется событие отсчета времени (листинг 3.11). Листинг 3.11. Tireerl генерирует шарики procedure TForml.TimerlTimer(Sender: TObject); var Circle:TCircle; begin Randomize; Circle:=TCircle.Create(nil); //очередной шарик List.Add(Circle); //добавляем шарик в список Circle.Parent:= Forml; Circle.Position,Y:=0; //место появления шарика на форме Circle.Position.X:=Random(Round(Forml.ClientWidth-Circle.Width)); end;

В коде программы для добавления очередного объекта к списку задействуется метод function Add(const Value: T): Integer;

Однако это не единственный способ пополнения списка, кроме метода Add (), до­ бавляющего объект самым последним в список, в арсенале класса TObjectList<T> имеется метод procedure Insert(Index: Integer; const Value: T) ;

благодаря которому мы сможем явным образом указать позицию объекта, передав значение в параметр index. Кроме того, у класса предусмотрен целый ряд перегружаемых методов prooedure AddRange(const Values: array of T) ; procedure InsertRange(Index: Integer; const Values: array of T);

умеющих за один присест добавить/вставить целую партию объектов. Возвращаемся к нашей программе, а точнее, ко второму таймеру Timer2 . В круг обязанностей этого компонента входит: □ управление процессом постепенного опускания шариков от места их создания (верхней грани формы) вниз формы, где их попытается поймать пользователь; □ удаление пойманных и пропущенных шариков. Очевидно, что для решения этих задач мы должны уметь обращаться к элементу списка. Для этого стоит узнать общее количество элементов property Count: Integer;

и передать индекс интересующего нас элемента в свойство property Items [Index: Integer]: T;


Гпава 3

52

Пойманным считается шарик, попавший на поверхность биты, реализованной на основе компонента TRectangie, пропущенным — шарик, пролетевший мимо (лис­ тинг 3.12). Листинг 3.12. Timer2 опускает шарики вниз и удаляет вышедшие за границу формы procedure TForml.Timer2Timer(Sender: TObj ect); var i :integer; begin for i :=0 to List.Count-1 do //перебираем все объекты with List.Items[i] do begin Position.Y:=Position.Y+0.5; //опускаем шарик вниз //проверяем, не попал ли шарик на биту Rectanglel:TRectangie if (Position.Y+Height>=Rectanglel.Position.Y) and (Position.X>=Rectanglel.Position.X) and (Position.X<=Rectanglel.Position.X+Rectanglel.Width) then begin //элемент пойман List.Delete(i); //удаляем шарик //... подсчет пойманных шариков Break; //прерываем цикл end else if (Position.Y+Height>=Rectanglel.Position.Y) then begin //элемент не пойман List.Delete(i); //удаляем шарик II... подсчет пропущенных шариков Break; //прерываем цикл end; end;

Замечание

В Delphi ХЕ5 конструкция with, .do вполне приемлема, однако, возможно, очень скоро ее перестанут рекомендовать к использованию. Причина такого предположения в том, что with, .do изначально проектировалась для устаревших объектов.

В любом случае пойманный или пропущенный шарик подлежит удалению. Для этого мы воспользовались методом procedure Delete(Index: Integer);

Важно запомнить, что если список является владельцем объектов, то вызов метода Delete о, во-первых, уничтожит объект, а во-вторых, упакует список (удалит осво­ бодившиеся ячейки и уменьшит значение count).


Классы-шаблоны

53

Если бы логика программы предполагала не уничтожение объекта, а лишь изъятие его из списка, то лучше было бы воспользоваться методом function Extract(const Value: T): T;

Заметьте, что на этот раз в функцию передается не индекс объекта, а собственно подлежащий изъятию объект, и если изъятие возможно, этот же объект окажется на выходе функции. На этом функционал списка TObjectList<T> далеко не заканчивается. Классшаблон способен еще на многое. В частности, он умеет перемещать элемент из по­ зиции Cur Index В ПОЗИЦИЮ Newlndex С П О М О Щ Ь Ю процедуры procedure Move(Curlndex, Newlndex: Integer);

Чтобы поменять элементы местами, обращаемся к методу procedure Exchange(Indexl, Index2: Integer);

Сортировка объектов в списке производится методами procedure Sort; overload; procedure Sort(const AComparer: IComparer<T>); overload;

При желании весь список может быть представлен в формате массива-шаблона function ToArray: TArray<T>;

И наконец, список обладает событием property OnNotify: TCollectionNotifyEvent<T>; TCollectionNotifyEvent<T> = procedure(Sender: TObject; const Item: T; Action: TCollectionNotification) of object;

генерирующимся в момент пополнения списка новым объектом или удаления объ­ екта из списка. О характере события сигнализирует параметр Action, принимая зна­ чения cnAdded, cnRemoved ИЛИ cnExtracted.

Шаблон словаря TDictionaryo В заключительном разделе главы познакомимся с весьма неординарным шаб­ л он ом — словарем TDictionaryo. Словарь описан В модуле System.Generics. Collections и, как и положено любому словарю, предназначен для обслуживания пар "ключ — значение", поэтому в его объявлении присутствуют два обобщенных типа — TDictionary<TKey,TVaiue>. Ключ в словаре должен быть уникален, благода­ ря этому осуществляется быстрый и однозначный поиск соответствующего ему значения. Класс вооружен несколькими конструкторами, простейший из них constructor Create(ACapacity: Integer = 0) ; overload;

при создании экземпляра класса в аргументах не нуждается (он создаст пустой сло­ варь), передав в конструктор любое положительное значение. Мы заранее распре­ делим объем словаря, чем ускорим процесс его заполнения.


54

Гпава 3

Для добавления в словарь новой пары "ключ — значение" используют метод procedure Add(const Key: TKey; const Value: TValue); Bhhm ahheI

В соответствии со своим предназначением словари недолюбливают совпадение клю­ чей, поэтому при вставке новой пары с помощью метода Add() вероятно возникнове­ ние исключительной ситуации!

Если вы намерены просто изменить значение для определенного ключа, то рекомёндую процедуру procedure AddOrSetValue(const Key: TKey; const Value: TValue);

Метод находит ключ Key и заменяет его значение на value, если же заданный ключ отсутствует, то процедура заработает как рассмотренный ранее метод Add () — до­ бавит в словарь новую пару. Свойства с традиционными названиями property Count: Integer; property Items[const Key: TKey]: TValue;

позволят соответственно узнать число элементов в словаре и получить доступ к элементу по его индексу, но заметьте, что на этот раз индекс Key универсален и может быть задан практически любым типом данных. Изъятие из словаря пары по ключу осуществит процедура procedure Remove(const Key: TKey);

Метод function ExtractPair(const Key: TKey): TPair<TKey,TValue>;

также исключит пару из словаря, но на этот раз даст возможность программисту прочитать ключ/значение. Полная очистка словаря осуществляется процедурой procedure Clear;

Пара очень быстрых методов проверит наличие в словаре ключа function ContainsKey(const Key: TKey): Boolean;

и значения function ContainsValue(const Value: TValue): Boolean;

и в случае успеха они возвратят true. Для быстрого доступа к значению по его ключу вызываем функцию function TryGetValue(const Key: TKey; out Value: TValue): Boolean;

В случае наличия в словаре пары, описываемой ключом Key, в выходной параметр value поступит соответствующее ключу значение.


Классы-шаблоны

55

Традиционное для большинства классов-шаблонов свойство function ToArray: TArray<TPair<TKey,TValue»;

позволит представить словарь в виде массива. Замечание

Класс TDictionary<T> в разы быстрее, чем полюбившийся многим программистам список строк TStringList! Поэтому в своих новых программах по возможности отда­ вайте предпочтение TDictionary<T>.


ГЛАВА 4

Базовые классы FireMonkey В главе, посвященной базовым классам библиотеки FireMonkey, нам предстоит еще глубже погрузиться в иерархию наследования классов. На этот раз нашими героями станут характерные представители новой платформы (см. рис. 2.1): □ прародитель всей цепочки классов библиотеки — класс □ опорный класс для двухмерных элементов управления □ опорный

класс

для

трехмерных

элементов

fmx

.Types.т е ш х о ь ject;

f m x .controls.TControi;

управления

FMX.Controis3D.

TControl3D. Зам ечание

Если вы по какой-то причине на стали читать главы 2 и 3, то обязательно запомните их основной тезис— классы FireMonkey (FMX) построены не на пустом месте. В их основу положены фундаментальные классы более "древней" библиотеки VCL (речь идет о цепочке "TObject — TPersistent — TComponent"). Так что если вы рассчиты­ ваете работать исключительно на платформе FMX, то у вас это не получится... Знания VCL по-прежнему остаются актуальными.

Опорный класс TFmxObject В джентльменском наборе опорного класса тппхоьject мы найдем все необходимое для обеспечения работы классического программного объекта. В первую очередь это конструктор create () и деструктор Destroy () экземпляра класса. Если вам необходимо создать точную копию уже существующего объекта, то вызо­ вите метод function Clone(const AOwner: TComponent): TFmxObject;

Функция возвратит клон объекта и передаст его во владение объекту, определен­ ному в параметре AOwner. Наряду с классическим для объектов Delphi деструктором Destroy о предусмотрен необычный метод procedure Release(Delay: Single = 0.1);


Базовые классы FireMonkey

57

удаляющий объект не сразу, а через некоторый интервал времени, давая экземпля­ ру класса корректно завершить свой жизненный цикл, например, закончить опера­ цию перерисовки. Примерный период времени (по умолчанию 0,1 секунды), отво­ димый на исчезновение объекта, передается в параметр Delay. Сразу замечу, что бессмысленно использовать метод Release () в качестве "часовой бомбы", уничто­ жающей объект через полчаса после вызова, поэтому направляйте в параметр зна­ чение, не превышающее секунду. В современном, даже невысокой степени сложности приложении одновременно взаимодействуют десятки, а то и сотни объектов. Зачастую жизненный цикл одних объектов состоит в прямой зависимости от "жизни" или "смерти" других. Для того чтобы о факте уничтожения одного объекта узнал другой, в состав методов класса TFmxObject включена процедура procedure FreeNotification(AObject: TObject); virtual;

Единственный параметр метода указывает на объект, которому будет автоматиче­ ски направлено уведомление об уничтожении нашего компонента. Обратная задача (вычеркивание объекта из перечня объектов, подлежащих уведом­ лению) решается методом procedure RemoveFreeNotify(const AObject: IFreeNotification);

Отметим, что уведомление выбранных объектов осуществляется автоматически, для этого предназначен метод Notification о, объявленный на уровне класса TComponent.

Управление дочерними объектами Возможность одного объекта обладать другим была предусмотрена еще на уровне класса Tfontroi из библиотеки VCL. Здесь принадлежащие объекту компоненты хранились в свойстве components, и доступ к ним осуществлялся по их индексу. В базовом классе TFmxObject имеется еще один, альтернативный (а может, даже и правильнее сказать основной) подход к обладанию объектами. На этот раз доступ к принадлежащему владельцу списку дочерних объектов осуществляется при по­ средничестве свойства property Children: TFmxObjectList;

которое построено на основе списка-шаблона TFmxObjectList = System.Generics.Collections.TList<TFmxObject>

Благодаря такому подходу мы приобретаем дополнительные рычаги управления дочерними элементами, главный из них — универсальность, т. е. способность хра­ нить любой объект, построенный на основе TFmxObject. Общее число подчиненных объектов предоставит свойство property ChildrenCount: Integer; //только для чтения


58

Гпава 4

Дочерний объект также не остается безучастным к отношению "главный — подчи­ ненный", в частности он знает свой родительский компонент property Parent: TEmxObject;

и порядковый номер в списке родителя property Index: Integer; //только для чтения

Стоит отметить, что объект может быть вполне самостоятельным и не иметь роди­ теля, об этом сигнализирует метод function HasParent: Boolean;

Любой объект, имеющий право быть родительским, по отношению к другим вправе провести экспертизу с помощью метода function IsChild(AObject: TFmxObject): Boolean;

Передав в метод ссылку на произвольный объект AObject, мы узнаем, является ли он дочерним по отношению к объекту, вызвавшему метод. Все потомки TFmxObject имеют право выступать владельцами объектов, имеющих в своей иерархии наследования класс TFmxObject. Для добавления дочернего объек­ та вызываем метод procedure AddObject(AObject: TFmxObject);

procedure InsertObject(Index: Integer; AObject: TFmxObject);

Разница между методами заключается в том, что AddObject о добавит объект в ко­ нец списка дочерних объектов, a InsertObject — в позицию, определенную в пара­ метре Index. С методами AddObject () и insretobject () мы еще неоднократно столкнемся на страницах этой книги, ведь они своего рода визитная карточка всех объектов биб­ лиотеки FireMonkey (ничего подобного в VCL нет). Небольшой пример рассмотрим прямо сейчас. Действующими лицами примера окажутся один объект-аниматор TFloatAnimation, позволяющий в динамике воздействовать на одно из свойств сво­ его объекта-владельца, изменяя его состояние, и произвольное число фигур TRectangle (страница Shapes). Не стоит искать объект-аниматор на палитре компо­ нентов — мы его создадим автоматически, правда, стоит позаботиться о подключе­ нии к проекту модуля FMX.Ani. Разместите на форме проекта несколько компонен­ тов TRectangle и перейдите к обработчику события OnCreate () формы (листинг 4.1). ...... ...............

..-.w £?.

Листинг* л . фозданиао&'Эгагопмчматоог iFi-oatAniiM ci.on '

uses ..., FMX.Ani; var Forml: TForml; FA: TFloatAnimation;

! S3 3 s *

'

г

j, tf'"l

... » *

— t

...

S ■


Базовые классы FireMonkey

59

procedure TForml.FormCreate(Sender: TObj ect); begin FA: =TFloatAnimation.Create(nil); FA. PropertyName:='RotationAngle'; FA.Trigger:='IsMouseOver=true'; FA.TriggerInverse:='IsMouseOver=false'; FA.StartValue:=90; FA.StopValue:=0;

Сейчас мы не станем комментировать код из листинга 4.1, т. к. в этой книге ани­ мации посвящена отдельная гл а ва 18. Пока достаточно знать, что после созда­ ния формы проекта в нашем распоряжении появился экземпляр класса f a : TFioatAnimation, у которого нет владельца (конструктор аниматора был вы­ зван с аргументом nil). Для завершения примера нам осталось написать всего одну строку кода в обработ­ чике события OnMouseEnter () у любого ИЗ Прямоугольников TRectangle (ЛИС­ ТИНГ 4.2) и сделать это событие общим для остальных компонентов TRectangle.

\Листинг 4.2. Передача объекта-аниматора в распоряжение объекта TRectangle J ....... ......... I __ШШШЪ........... procedure TForml.RectanglelMouseEnter(Sender: TObject); begin TRnxObject(Sender).AddObject(FA); end;

Таким образом, в момент появления над любым из прямоугольников TRectangle указателя мыши он становится владельцем объекта-аниматора f a : TFioatAnimation. Что произойдет дальше, вы увидите, повторив представленный выше код или найдя этот пример в прилагаемом к книге электронном архиве (см. прилож ение 5). При необходимости дочерние объекты могут быть упорядочены, для этого вызыва­ ется метод procedure Sort(Compare: TFmxObjectSortCompare);

Два объекта могут просто поменяться местами, для этого следует обратиться к ме­ тоду procedure Exchange(AObject1, AObject2: TFmxObject);

Удаление дочернего объекта из списка осуществляет перегружаемый метод procedure RemoveObject(AObject: TFmxObject); overload; procedure RemoveObject(Index: Integer); overload;

В первой нотации производится удаление конкретного объекта AObject, во втором случае удаляется объект с индексом index.


60

Глава 4

Объект-владелец может провести полную очистку списка дочерних объектов, для этого он обращается к методу procedure DeleteChildren;

Отметим, что объекты не подлежат физическому уничтожению, а просто изымают­ ся из списка. Во время визуального проектирования важнейшим инструментом программиста выступает окно управления структурой проекта S tructure. В данном окне, переме­ щая компонент мышью, мы получаем возможность переподчинять объекты друг другу. Например, на экранном снимке (рис. 4.1) видно, что компоненту TabControil принадлежат три страницы (Tabitemi, Tabitem2, TaMtem3), которые в свою очередь являются владельцами других компонентов.

Сопоставление дополнительных данных У объектов VCL на уровне TComponent объявлено неиспользуемое системой свойст­ во Tag, позволяющее программисту связывать с объектом произвольное целочис­ ленное значение. В классах FMX на сопоставлении с объектом дополнительных данных специализируется целая группа дополнительных свойств: property TagFloat: Single; //вещественное значение property TagObject: TObject; //внешнйй объект property TagString: string; //текстовая строка


Базовые классы FireMonkey

61

Задействуйте перечисленные свойства для дополнительной идентификации эле­ ментов управления и созданных динамически объектов.

Поддержка LiveBindings Одной из визитных карточек новой Delphi стала технология живого связывания LiveBindings, позволяющая двум (и более) программным объектам осуществлять взаимодействие друг с другом на уровне RTTI. В интересах LiveBindings в рамках класса TFmxObject заложен ряд базовых методов и свойств, нацеленных на под­ держку LiveBindings. Более подробно об особенностях технологии LiveBindings мы поговорим в главе 1 7.

Поддержка анимации Анимация — это еще одно нововведение FireMonkey. Суть анимации заключается в способности практически любого визуального элемента управления изменять во времени одну или несколько своих характеристик (размеры, цвет, масштаб, поло­ жение и т. п.) по внешней команде. Анимированный объект в буквальном смысле оживает, чем достигается более дружественный интерфейс приложения. В рамках класса TFmxObj ect объявлено более десятка методов (в их названиях име­ ется слово Animation или Animate), предназначенных для управления эффектами анимации, их изучению мы посвятим гл а ву 18.

Поддержка сенсорного ввода У всех визуальных элементов управления, базирующихся на классе TFmxObject, имеется свойство property Touch: TTouchManager;

позволяющее

подключить к компоненту менеджер жестов — компонент TGestureManager. Благодаря менеджеру жестов компонент приобретет возможность реагировать на поддерживаемые современными экранами операции сенсорного ввода (см. гл а ву 20).

Взаимодействие с командами Уже давно существовавшая в VCL система централизованного управления прило­ жением с помощью командных объектов TAction (см. гл аву 6) в FireMonkey появи­ лась только вместе с выходом ХЕЗ. Для взаимодействия с командами на уровне класса TFmxOb ject объявлено свойство property Action: TBasicAction;

Дня хранения командных объектов в состав FMX введен хорошо знакомый нам список команд TActionList, в основном повторяющий возможности своего одно­ именного коллеги из VCL.


Гпава 4

62

20-элементы управления, класс TControl Все пользовательские элементы управления платформы FireMonkey берут начало от объявленного в модуле f m x .controls класса TControl. Несмотря на одинаковые имена, знак равенства между классическим TControl из состава VCL и классом TControl ставить преждевременно. По сравнению со своим "коллегой" наш новый знакомый обладает существенно возросшим функционалом. Если вы намерены досконально разобраться с классом, то сразу стоит взглянуть на его определение: type TControl = class(TFmxObject,.IControl, IContainerObject, IAlignRoot, IAlignableObject, IEffectContainer)

Как видите, TControl является не просто наследником опорного класса TFmxObject. В объявлении родоначальника всех элементов управления FireMonkey присутству­ ют пять интерфейсов. □ Благодаря IControl потомки класса TControl приобретают право получать фокус ввода и позволяют пользователю передавать потомкам f m x .Types.TControl фокус ввода (например, с помощью клавиши <ТаЬ>). Кроме того, интерфейс IControl обеспечит взаимодействие с клавиатурой и мышью (в VCL такими чертами об­ ладали только оконные элементы управления — потомки TWinControi). □ Благодаря интерфейсам IAlignRoot и iAiignabieObj ect реализовано улучшенное выравнивание элемента управления на поверхности родительского контейнера. А интерфейс IContainerObject в любой момент времени готов предоставить ин­ формацию о размерах родительского контейнера. □ Интерфейс IEffectContainer позволяет применять к элементу управления разно­ образные графические эффекты (см. гл а ву 19). В классе TControl принципиально изменился способ хранения местоположения элемента управления. Теперь допускается описывать позицию элемента управления в двух- и трехмерном пространствах с использованием векторных координат. Внимание!

Все визуальные элементы управления FireMonkey в буквальном смысле слова нари­ сованы, т. е. основаны на растровых образах. Благодаря этому к кнопкам, панелям, строкам ввода можно применять все мыслимые (а если сможете, то и немыслимые) графические эффекты! В этом вам помогут многочисленные компоненты со странички Effects.

Отметим еще тот немаловажный факт, что на уровне TControl осуществляется управление не только видимостью элемента управления, но и его прозрачностью, масштабом и вращением. Кроме того, на TControl возложен фирменный функцио­ нал FireMonkey по контролю за прорисовкой, анимацией и другими визуальными эффектами.


Базовые классы FireMonkey

63

Размещение 20-элемента управления Несмотря на внешнюю похожесть компонентов FMX и VCL отличий между ними больше, чем сходства. Одна из особенностей элементов управления FireMonkey связана с подходом к вопросу размещения и выравнивания элемента управления на поверхности родительского контейнера. Первое новшество заключается в отказе от целых величин при описании размеров и местоположения элемента управле­ ния. Хотя высота и ширина объекта определяются свойствами с привычными на­ званиями property Width: Single; //ширина объекта property Height: Single; //высота объекта

^

Но теперь свойства ориентированы на действительные числа. Такой подход суще­ ственно упрощает пересчет величин при проведении различных графических опе­ раций, поворотах, масштабировании и других преобразованиях объектов. Внимание!

При работе в двухмерном пространстве координаты объекта FMX (свойство Position) определяются в формате записи TPosition. В трехмерном пространстве местополо­ жение объекта описывается структурой TPosition3D.

Еще большие изменения постигли способ описания координат местоположения объекта. Очередное новшество FMX спрятано в недрах свойства property Position: TPosition;

отвечающего за определение местоположения компонента в двухмерном или трех­ мерном пространстве. Теперь это не просто свойства Тор и Left (как было в VCL), а свойства property X: Single; property Y: Single;

Если копнуть еще глубже, то выяснится, что интеллектуальный объект TPosition определяет позицию компонента как вектор (листинг 4.3).

type TVector = record //методы записи TVector case Integer of 0: (V: TVectorArray;); //type TVectorArray = array [0..2] of Single; 1: (X: Single; • Y: Single; W: Single;); end;

Переход к векторной алгебре — это весьма существенное новшество библиотеки, в особенности для трехмерных проектов (см. прилож ения 1 и 2).


Гпава 4

64 Внимание!

В трехмерном пространстве (проекты на основе TForm3D) размещаемые на форме элементы позиционируются не относительно левого верхнего угла (как это было в VCL или в двухмерных проектах FireMonkey HD), а относительно своего центра.

Основные свойства и методы класса TPosition представлены в табл. 4.1. Таблица 4.1. Основные свойства и методы класса TPosition

Свойство/метод

Описание

property DefaultValue: TPointF;

Возвращает значения координат по умолчанию — обычно (0, 0)

property X: Single;

Горизонтальная координата

property Y: Single;

Вертикальная координата

property Point: TPointF;

Представление координат в форме записи TPointF. Запись TPointF кроме хранения коорди­ нат (х, Y) предоставляет услуги по сравнению,

смещению, приращению координат property Vector: TVector;

Представление координат в форме вектора

function Empty: Boolean;

Возвращает true, если координаты х и Y уста­ новлены в 0

procedure Reflect(const Normal: TVector);

Отражает текущий вектор перпендикулярно ли­ нии, проведенной из точки (0, 0, 0) в точку Normal

property OnChange: TNotifyEvent;

Событие, генерируемое в момент изменения координат

Выравнивание объекта У объектов FMX имеется весьма обширный набор возможностей по выравниванию элемента управления на поверхности родительского контейнера. Для этого преду­ смотрено свойство 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; //по умолчанию (0, 0, 0, 0) property Padding: TBounds; //по умолчанию (0, 0, 0, 0)

Свойство Margins определяет отступы дочернего объекта от границ родительского контейнера. Отступы выступают в качестве запрещенной зоны для выравниваемого дочернего объекта. Например, если вы установите свойство Margins.тор располо-


Базовые классы FireMonkey

65

женной на форме панели в значение 20, то после этого она не сможет приблизиться к верхнему краю формы более чем на 20 пикселов. Свойство Padding развивает идею запретных зон альтернативным способом — со стороны контейнера (например, формы). Свойство определяет отбивку всех дочер­ них объектов, указывая, на какое число пикселов им разрешено приблизиться к границам своего контейнера-владельца (рис. 4.2).

Свойство Margins

Свойство Padding

Align=aiClient; M a r gins. M a r gins.Left=X? M a r gins.Right^Z; M a r g i n s .ТорНУ;

W

Р и с . 4 .2 .

Align=alClient; p a d ding. jfadeting.Left=X; jpa'dding .Rigfct^Z? jpackdinff. T q p ~ Y ;

Демонстрация свойств Margins и Padding

Группировка объектов и компонент TLayout Для объединения нескольких объектов в группу с последующей возможностью их совместного перемещения, вращения, выравнивания (см. свойства Align, Anchors, Padding, Margins и ряд других свойств) проще всего воспользоваться услугами компонента TLayout, который вы обнаружите на страничке L ayouts палитры ком­ понентов. « Класс f mx .Layouts.TLayout является прямым потомком рассматриваемого в этой главе класса TControl и обладает одной важной особенностью — экземпляр TLayout

Рис. 4.3. Подчинение кнопок компоненту L a y o u t l


66

Гпава 4

выполняет функционал невидимого во время выполнения приложения контейнера для других элементов управления. Для того чтобы элемент управления, допустим, кнопка Buttoni, стал принадлежать контейнеру Layouti:TLayout, недостаточно расположить кнопку на поверхности контейнера. Вместо этого следует воспользоваться отвечающим за структуру про­ екта редактором S tru c tu re и перетащить мышью узел кнопки в подчинение узла Layoutl (рис. 4.3).

Масштабирование и вращение объекта Размеры любого потомка класса TControi могут быть пропорционально изменены. Коэффициент масштабирования задается в свойстве property Scale: TPosition;

Напомним, что класс TPosition способен работать с координатами, заданными в форме вектора, поэтому свойство scale способно управлять масштабом в трех измерениях одновременно. Любой из потомков класса TControi может быть подвергнут вращению. Угол пово­ рота в градусах (-180...+180) задается в свойстве property RotationAngle: Single;

Объект поворачивается относительно центра вращения, который назначается в свойстве property RotationCenter: TPosition;

Заметим, что точка центра вращения задается относительно клиентских координат объекта, которые нормируются к 1. По умолчанию центр находится в точке (0.5, 0.5) — это не что иное, как геометрический центр вращаемого объекта. Если мы зададим в качестве центра точку (1, 1 )— центр вращения переместится в пра­ вый нижний угол объекта, (0, 0) — в левый верхний. Для разработчиков компонентов будет полезно знать, что на уровне TControi объ­ явлено свойство property Skew: TPosition;

позволяющее деформировать объект. Указанное свойство еще активно применяется в классе TTransform, отвечающем за осуществление различного рода преобразо­ ваний. Благодаря вращению элементов управления можно легко достичь весьма неорди­ нарных результатов. Воспользовавшись нехитрым набором компонентов: □ таймером TTimer для отсчета времени; □ слоем TLayout, выравненным по центру (Align:=aicenter) формы приложения для iPhone, слою будут принадлежать все визуальные элементы управления; □ тремя линиями TLine, которые превратятся в стрелки;


Базовые классы FireMonkey

67

□ несколькими эллипсами, кругами и текстовыми метками, создающими антураж циферблата (рис. 4.4), мы напишем приложение "Часы".

Тайм ер T i m e r l :T T i m e r

С л ой L a y o u t l :T L a y o u t

Ч а со ва я стр е л ка L i n e H o u r :T Line

М и н у тн а я стр е л ка L i n e M i n :T Line С е кун д н а я стр е л ка L i n e S e c :T Line

Р и с . 4 .4 . М о б и л ь н ы й п р о е кт "Ч а сы "

Во время визуального проектирования можно не беспокоиться о размещении часо­ вых стрелок. В момент создания формы указываем стрелкам, что их центр враще­ ния будет располагаться ровно по центру слоя Layoutl (листинг 4.4). Лйетийг '4Ж Центр вращения стрелок часо_

"..ЛЯШЯЯЯШЯШввШШЯШЯШЯЯЯШвШШШтШЯшшшШмяШшшШкШшти............... ъШ &Ш &й......... ........... .......... ................... p ro c e d u re

TfrmWatch.FormCreate(Sender: TObject);-

b e g in

LineSec.RotationCenter.X :=0; LineSec.RotationCenter.Y :=0; LineMin.RotationCenter.X :=0; LineMin.RotationCenter.Y :=0;


68

Гпава 4

LineHour.RotationCenter.X :=0; LineHour.RotationCenter.Y :=0;

С каждым отсчетом таймера осуществим перерасчет времени в углы поворота со­ ответствующих стрелок (листинг 4.5).

procedure TfrmWatch.TimerlTimer(Sender: TObject); var H,M,S,MS:word; begin DecodeTime(Time,H,M,S,MS); //декодируем время //переводим время в углы поворотов стрелок LineSec.RotationAngle :=S*6+180; //секунда LineMin.RotationAngle :=M*6+180; //минуты LineHour.RotationAngle:=H*30+180+M/2; //часы

Нам осталось убедиться, что таймер Timeri активен Enabled :=true, и запустить про­ ект на выполнение.

Видимость и прозрачность элемента управления У всех визуальных компонентов из состава FMX сохранилось традиционное свой­ ство property Visible: Boolean;

делающее компонент невидимым при установке в состояние false. Но это далеко не все. Программисты Embarcadero свои компоненты наделили уникальной воз­ можностью — постепенно становиться прозрачными. По умолчанию свойство property Opacity: Single; //по умолчанию 1

принимает значение 1, что свидетельствует о полной непрозрачности компонента. Уменьшая значение до 0, мы добьемся требуемой степени прозрачности объекта вплоть до полной невидимости.

Грани, фаски и визуальные эффекты Программисту, хорошо знающему элементы управления библиотеки VCL, при пер­ вой встрече с элементами управления FMX может показаться несколько удиви­ тельным отсутствие у визуальных компонентов FireMonkey свойств, определяю­ щих внешний вид граней и фасок компонента (напомним, что в VCL для оформле­ ния компонента широко использовались свойства BorderStyle, Bevellnner, BeveiKind, BeveiOuter и т. п.). Вместо этого в проектах FMX для оформления эле­ ментов управления используются альтернативные подходы — стили, анимация и


Базовые классы FireMonkey

69

визуальные эффекты. В частности, для имитации граней и фасок можно воспользо­ ваться услугами компонентов со страницы Effects палитры компонентов Delphi. На странице Effects вы обнаружите несколько десятков компонентов, в первую очередь специализирующихся на создании разнообразных графических фильтров, оказывающих воздействие на растровые изображения. Но среди них имеются клас­ сы, способные преобразить обычные элементы управления (кнопки, строки ввода, панели и т. п.). В первую очередь рекомендуем познакомиться с компонентами: □ TBevelEffeet — эффект фаски; □ TRefiectionEf feet — отражение; □ TGiowEffect — наружное свечение; □ TinnerGiowEf feet — внутреннее свечение; □ TShadowEf feet — тень; □ TBlurEffect — размытие. Возможности шести перечисленных компонентов отражает рис. 4.5, на котором визуальные эффекты применены к обычным панелям тPanel. Для сравнения в цен­ тре формы оставлена панель, не использующая визуальный эффект.

л ш ч у л я ь н ы х

S 3 Panefl

TF

ж п я п «.п &м

mЩPerte*2

a$pgydEflccti s-ШРтёз RefipcfeenEffcctl й-ШЗ SovCRecti фgj Par^S

StyteBockI

SnrMrGfawEfltrfl

В-Э

TBevelEffect

TBfurEffec?

TRefJecBonEffect

TShadowEffed

TGIowfcJtfea

ShsciewCffiectl Panel? S tvtefio o k i

TlnnerG low Effect

Рис. 4.5. П р и м е н е н и е

эф ф е кт о в д л я у п р а в л е н и я вн е ш н и м в и д о м в и зу а л ь н о го э л е м е н та

Зам ечание

Для задействования того или иного эффекта достаточно перетащить компонент на форму и с помощью окна управления структурой проекта Structure "подчинить" вы­ бранный эффект тому или иному визуальному элементу управления.


70

Гпава 4

Состояние элемента управления На уровне класса TControl объявлен ряд информационных свойств, позволяющих определить текущее состояние элемента управления (табл. 4.2). Таблица 4.2. Свойства, описывающие состояние элемента управления

Свойство

Описание

property IsMouseOver: Boolean;

Информирует о том, что над элементом управления расположен указатель мыши

property IsDragOver: Boolean;

Объект участвует в операции перетаскивания drag and drop

property IsFocused: Boolean;

Проверка наличия фокуса ввода

prqperty IsVisible: Boolean;

Проверка видимости объекта

property ParentedVisible: Boolean;

Проверка видимости родительского объекта

Обработка событий Программисту, имеющему опыт разработки проектов в VCL, не составит никакого труда разобраться с основными обработчиками событий, описанных на уровне класса TControl платформы FMX. События TControl во многом повторяют опорные события, имеющиеся в распоряжении классов TControl и TWinControi из дружеской библиотеки VCL. Основные отличия сопряжены с изменениями в позиционирова­ нии элементов управления (напомню, что FMX перешел на вещественные числа и способен описывать координатную точку в трехмерном пространстве).

Простейшие события — щелчок В любой операционной системе, поддерживаемой FMX, самыми востребованными событиями были и остаются одинарный и двойной щелчки по элементу управле//одинарный щелчок property OnClick: TNotifyEvent; property OnDblClick: TNotifyEvent; //двойной щелчок

Напомним, что инициатором щелчка могут стать левая кнопка мыши, а также на­ жатие клавиши <Enter> или <Пробел> при условии, что объект управления нахо­ дится в фокусе ввода. Все события, в том числе и простейшее из них TNotifyEvent = procedure(Sender: TObject) of object;

обладают хотя бы одним параметром Sender, благодаря которому программист мо­ жет идентифицировать объект-источник события. Вряд ли вы еще не встречались с простейшими примерами обработки события OnClick(), поэтому мы сразу рассмотрим более усложненный пример, демонстри­


Базовые классы FireMonkey

71

рующий возможность разделять один обработчик события сразу несколькими эле­ ментами управления и раскрывающий скрытые возможности параметра sender. Для реализации примера разместите на главной форме проекта кнопку Buttoni:TButton, многострочный редактор мешо1:ТМешо и несколько любых других визуальных эле­ ментов управления, которые вы найдете на палитре компонентов Delphi. Выберите кнопку Buttoni и напишите всего одну строку кода в обработчике собы­ тия (листинг 4.6). { Листинг 4.6. Получение имени компонента по параметру Sender procedure TForml.ButtonlClick(Sender: TObject); begin Memol.Lines.Add((Sender as TControl).Name); end;

Щ елчок no кнопке приводит к появлению в многострочном редакторе Memol строки с именем компонента, вызвавшего событие (в данном случае кнопки Buttoni). Предвижу вопрос: "А Не проще ЛИ было написать строку Memol.Lines.Add (' Buttoni1) ?" Нет, не проще. В особенности, если мы намерены научиться создавать разделяемый между несколькими компонентами код. Выберите на форме любой другой элемент управления (например, checkBoxl). Пе­ рейдите на вкладку Events (События) в Инспекторе объектов, в перечне событий найдите событие OnClick и сопоставьте с ним событие ButtonlClick (рис. 4.6). Наши действия приведут к тому, что два компонента станут разделять между собой один общий обработчик события. Аналогичным образом подключите к событию все ос­ тальные компоненты (включая форму) и запустите проект на выполнение. Щ елкни­ те по любому из компонентов — его имя сразу появится в отдельной строке много­ строчного редактора Memol. Внимание!

Параметр Sender имеется во всех обработчиках событий, благодаря ему программист сможет идентифицировать вызвавший событие элемент управления.

Благодаря наличию параметра Sender во всех обработчиках событий элементов управления Delphi и возможностью совместного использования нескольких объек­ тов одного и того же события можно создавать весьма эффективный код. Хорошим примером такого кода может стать приложение "Калькулятор". У любого калькулятора есть 10 кнопок, отвечающих за ввод цифровых значений. Для того чтобы пользователь смог визуально идентифицировать назначение каж­ дой из кнопок, в их заголовки (Text) помещается соответствующий вводимой циф­ ре символ (рис. 4.7). При нажатии на каждую из цифровых кнопок (в моем примере это кнопки TCornerButton) генерируется событие onciicko, в результате которого в метке Labeii:TLabei калькулятора отображается выбранная цифра. Как правило, начинающий программист решает подобную задачу в лоб и пишет для каждой из десяти кнопок примерно следующий код (листинг 4.7).


72

Гпава 4

|jp Check^*i

Ц

C bject Inspector

ICtbC&QX

ftroperses |Events jBjra£ng5eurce

XMw\%

'Betting®

fORApp^tyietGdajp

© ШШъ$лй\т1 0 йШоШтгй

21: 30

fOrCanFoo»

fQnCfoarsge »iOnCWc jOnDblCldc jOrOragDrap iOnOrsgEnd IQriDrvagErster lOnOragLeave

тлш

Insert

O OragOvw

lOnEnSef lOnExst

jOnKeyOown NwUveBndng... LWttoOeFieSd... OnClefe

' ~ ~ ~

AX* o w

Рис. 4.6. Р а з д е л е н и е

о д н о го о б р а б о т ч и ка с о б ы т и я м е ж д у н е ско л ь ки м и ко м п о н е н та м и

Ш» « и 1 Г j К-злькул«т«эр f MX 0 ?

8

9

н

4

S

6

. 4-

1

2

3

/

1; 1

* •' * 3

0 • а ш м ш ш м ■ ■ ММ ►•

с

Insert

Рис. 4.7. П ользовательский интерф ейс Калькулятора


Базовые классы FireMonkey

\

73

Листинг 4.7. Щелчок по кнопке "1"

procedure TFonnl.CornerButtonlClick(Sender: TObject); begin if Label1.Text='0' then Labell.Text:= '1' else Label1.Text:=Label1.Text+'1'; end;

Более опытный разработчик поступит гораздо хитрее, обойдется услугами всего одного общего события o n d i c k o для всех кнопок, отвечающих за цифровой ввод (листинг 4.8), и разделит это событие между оставшимися 9-ю кнопками. Но те­ перь, вместо явного указания цифры, программист воспользуется услугами пара­ метра Sender, содержащего ссылку на кнопку, по которой был произведен щелчок. ; Листинг 4.8. Общее событие для всех i procedure TForml.CornerButtonlClick(Sender: TObject); begin if Labell.Text='0' then Label1.Text:=(Sender as TCornerButton).Text else Labell.Text:=Labell.Text +(Sender as TCornerButton).Text; end;

Процедура самостоятельно разберется, какая из кнопок послужила источником со­ бытия, и добавит в заголовок метки Labell необходимую цифру.

Клавиатурные события Для того чтобы ни один из визуальных элементов управления FMX не остался без­ участным к нажатиям пользователем клавиш, на уровне TControl реализованы два самых важных клавиатурных события: property OnKeyDown: TKeyEvent; IIнажатие клавиши property OnKeyUp: TKeyEvent; //отпускание клавиши

Благодаря OnKeyDown. о и OnKeyUp о мы получаем возможность отследить как нажа­ тие, так и отпускание клавиши. Оба события типизированы одинаково: type TKeyEvent = procedure(Sender: TObject; var Key: Word; var KeyChar: WideChar; Shift: TShiftState) of object;

Здесь: Sender — ссылка на объект, вызвавший событие; Key — числовой код нажа­ той клавиши; KeyChar — параметр-переменная, с помощью которого можно считы­ вать (а при необходимости и редактировать) символ нажатой клавиши; s h if t обес­ печит контроль состояния служебных клавиш. Листинг 4.9 демонстрирует порядок применения события OnKeyDown о — в его рам­ ках форма отслеживает коды клавиш. Если пользователь нажимает клавиши управ­


74

Гпава 4

ления курсором и при этом удерживает клавишу <Ctrl>, то форма изменяет свое положение на экране. ЁШн-'А11 м

Ji

" ’■

Ъiidi'

I

1

"

———••■

|

Листинг 4.9. Перемещение фор::ы с по: ощью события OnKeyDown ()

.

procedure TForml.FormKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState); begin if (ssCtrl in Shift) then case Key of 37 Forml.Left:=Forml.Left-1; //курсор влево 38 Forml.Top:=Forml.Top-l; //курсор вверх 39 Forml.Left:=Forml.Left+l; //курсор вправо 40 Forml.Top:=Forml.Top+1; //курсор вниз end; end;

В примере из листинга 4.9 мы отслеживали цифровой код нажатой клавиши с по­ мощью параметра Key. А теперь переключим свое внимание на параметр KeyChar, который отслеживает не код клавиши, а выбранный пользователем символ. Благо­ даря этому можно проверять корректность вводимого текста (листинг 4.10). j

-

i Листинг 4Л0. Г

>дт

. ................ ...............

фонн

о

ШШШШШШШШйВкЁ

ШШЖжж

procedure TForml.EditlKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState); begin if ( (KeyChar<' 0' ) or (KeyChar>'9') or (Length(Editl.Text)>=11)) and (KEY<>8) and (KEY<>46) then {8 и 46 — код клавиш Backspace и Del} begin KeyChar:=#0; if (Length(Editl.Text)>=11)=false then ShowMessage('Допускаются только цифры!') else ShowMessage('Длина текста превысила 11 знаков!') end; end;

Событие осуществляет контроль верности ввода телефонного номера в строку Editl. Процедура допускает ввод только цифр, причем длина номера не должна превышать 11 символов. В противном случае ввод ошибочного символа отменяется (параметру-переменной KeyChar присваивается значение #о) и выдается сообщение об ошибке.

События мыши Разработка удобного пользовательского интерфейса современного приложения не­ возможна без активного использования мыши. Именно поэтому базовый класс


75

Базовые классы FireMonkey

TControi стал счастливым обладателем полудюжины событий, ориентированных на работу с мышью.

В первую очередь выделим события, связанные с фактом появления указателя мы­ ши над областью элемента управления и с выходом из этой области property OnMouseEnter: TNotifyEvent; //вхождение в область property OnMouseLeave: TNotifyEvent; //выход из области

Вторая категория событий позволяет отслеживать события нажатия и отпускания кнопок мыши property OnMouseDown: TMouseEvent; //нажатие кнопки property OnMouseUp: TMouseEvent; //отпускание кнопки

Перечисленные события типизируются процедурой type TMouseEvent = procedure(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single) of object;

Благодаря параметру Button мы получаем возможность узнать, какая именно кноп­ ка мыши была задействована в операции. Параметр shift позволяет отследить на­ жатия служебных клавиш. Параметры х и у информируют о координатах мыши. В табл. 4.3 представлен список параметров, позволяющих описывать реакцию эле­ мента управления на событие. Таблица 4.3. Описание параметров TMouseEvent

Параметр

Возможные значения

Описание

Sender

Ссылка на объект

Ссылка на источник сообщения

Button

irbLeft

Щелчок левой кнопкой мыши

mbRight

Щелчок правой кнопкой мыши

mbMiddle

Щелчок центральной кнопкой мыши

ssShift

Удерживается в нажатом состоянии клавиша <Shift>

ssAlt

Удерживается в нажатом состоянии клавиша <Alt>

ssCtrl

Удерживается в нажатом состоянии клавиша <Ctrl>

ssLeft

Нажимается/отпускается левая кнопка мыши

ssRight

Нажимается/отпускается правая кнопка мыши

ssMiddle

Нажимается/отпускается центральная кнопка мыши

ssDouble

Двойной щелчок любой кнопкой

Single

Координаты указателя мыши

Shift

X, Y

Для того чтобы получить возможность отследить движение указателя мыши, стоит обратить внимание на событие property OnMouseMove: TMouseMoveEvent; type TMouseMoveEvent = procedure(Sender: TObject; Shift: TShiftState; X, Y: Single) of object;


76

Гпава 4

позволяющее контролировать местоположение указателя. Назначения параметров нам уже известны, поэтому не станем повторяться. И сразу перейдем к заключи­ тельному событию мыши, описанному в рамках класса TControi. Воспользуемся услугами OnMouseMove () и напишем приложение, позволяющее пользователю перемещать компоненты по форме с помощью мыши. Для этого нам понадобится новый проект с любым визуальным элементом управления на главной форме (свой выбор Я остановил на обычном прямоугольнике Rectanglel: TRectangle). В разделе частных объявлений опишем две переменные — x o ff set и yo ff set, они позволят нам хранить отступы указателя мыши относительно левого верхнего угла перемещаемого компонента (листинг 4.11). Значения отступов запоминаются в мо­ мент нажатия кнопки в событии onMouseDown (). Перемещение осуществляется в рамках события OnMouseMove () (при условии, что пользователь удерживает в нажа­ том состоянии левую кнопку мыши). j Листинг 4.11. Перемещение объекта с помощью мыши var

Forml: TForml;

xOffset,yOffset: Single; implementation {$R *.dfm} procedure TForml.RectanglelMouseDown(Sender: TObj ect; Button: TMouseButton; Shift: TShiftState; X, Y: Single); begin xOffset:=X; //нажатие кнопки мыши yOffset:=Y; //запомнили отступы xOffset и yOffset end; procedure TForml.RectanglelMouseMove(Sender: TObj ect; Shift: TShiftState; X, Y: Single); begin //перемещение мыши с нажатой левой кнопкой if (ssLeft in Shift) then with (Sender as TControi) do begin Position.X := Position.X+X-xOffset; Pos ition.X := Position.X+X-yOffset; end; end; .

Благодаря тому, что в параметре Sender находится ссылка на объект, над которым движется указатель мыши, мы вновь написали универсальный обработчик события, способный перемещать любой экземпляр класса TControi. Контроль вращения колесика мыши осуществляет единоличный представитель — событие


Базовые классы FireMonkey

77

property OnMouseWheel: TMouseWheelEvent; type TMouseWheelEvent = procedure(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean) of object;

Благодаря параметру WheelDelta мы получаем сведения о направлении вращения колесика: отрицательное значение — вниз, положительное — вверх. Передав зна­ чение true в переменную Handled, мы уведомим систему, что программная логика отработала полностью и про событие можно забыть, в противном случае обработка события продолжится за счет вызова обработчика события по умолчанию.

События получения и потери фокуса ввода Среди компонентов FireMonkey нет строгого разграничения на оконные и графиче­ ские элементы управления, подобного тому, которое существует у потомков клас­ сов TWinControi и TGraphicControi платформы VCL. Строго говоря, все визуальные компоненты FMX являются графическими элементами управления, обладающими чертами оконных. Одно из подтверждений тому — способность всех наследников f m x .controls.TControi реагировать на получение фокуса ввода. У компонентов сохранена традиционная (как и у компонентов VCL) реакция на по­ лучение и потерю фокуса ввода, осуществляемых событиями property OnEnter: TNotifyEvent; //получение фокуса ввода property OnExit: TNotifyEvent; //потеря фокуса ввода

Кроме того, в FMX предусмотрено событие, позволяющее программисту запретить объекту реагировать на фокус ввода: property OnCar.Focus: TCanFocusEvent; type TCanFocusEvent = procedure(Sender: TObject; var ACanFocus: Boolean) of object;

Разрешение или запрет выдается с помощью параметра-переменной ACanFocus.

Событие изменения размера Изменение геометрических размеров любого потомка класса TControi незамедли­ тельно приводит к генерации события property CnResize: TNotifyEvent;

Данное событие окажется весьма полезным в тех ситуациях, когда логика програм­ мы предполагает "подгонку" элементов пользовательского интерфейса под размер формы. Одним из примеров такого подхода может стать код, предложенный в лис­ тингах 4.12 и 4.13. В листинге 4.12 отражен подготовительный этап примера, здесь в момент генера­ ции события oncreate () у главной формы проекта мы динамически создаем 64 эк­ земпляра объекта TRectangle (сразу раскроем секрет, что прямоугольники позднее станут клетками шахматной доски).


78

Гпава 4

.....ггтт:...:...............— т~...................................... ~.... ::z:;:-rr~-;r--rm а г д г .";

: Листинг 4.12. Динамическое создание объектов TRectangle const N=8; procedure TForml.FormCreate(Sender: TObject); var i:integer; begin for i :=1 to N*N do with TRectangle.Create(Forml) do Parent:=Forml; end;

Обратите внимание на то, что мы пока не определяем местоположение созданных прямоугольников TRectangle, это мы сделаем в момент изменения размеров формы (листинг 4.13). Fit.-ял г 4.13. С

>ытие<

* :->() <’ ^ м ы

procedure TForml.FormResize(Sender: TObject); var i, sWidth, sHeight, x, у :integer; begin sWidth :=Forml.ClientWidth div N; sHeight :=Forml .ClientHeight div N;

//ширина прямоугольника //высота прямоугольн!-:ка

x:=-sWidth+l; y:=l; for i :=0 to Forml.ComponentCount-1 do if Forml.Components[i] is TRectangle then with TRectangle(Forml.Components[i]) do begin Height:=sHeight; Width :=sWidth; inc(x, sWidth); if x+3<Forml.ClientWidth then //размещаем вдоль begin Position.X:=x; Position.Y:=y; end else begin //новый ряд фигур x:=l; Position.X:=x; inc(y, sHeight); Position.Y:=y; end; end; end;

линии


Базовые классы FireMonkey

79

Запустив программу, вы убедитесь, что теперь за размеры и расстановку шахмат­ ных клеток отвечает форма. Любое изменение геометрии формы приводит к немед­ ленному перестроению экземпляров класса TRectangle. Если еще немного поколдо­ вать над кодом, то окажется несложным и раскрасить прямоугольники в черно­ белые цвета, совсем как у шахматной доски (рис. 4.8).

Рис. 4.8. При изменении размера формы "шахматные клетки" подстраиваются под новые условия

События перетаскивания drag and drop Платформа FireMonkey поддерживает удобный интерфейс перетаскивания объек­ тов мышью. В операции участвуют два объекта: компонент — источник данных и компонент — получатель данных. Операцию перетаскивания инициирует компонент-источник. В его распоряжении имеется свойство, определяющее порядок инициализации процесса. property DragMode: TDragMode; // по умолчанию dmManual

Если свойство установить в состояние dmAutomatic, то любое перемещение над компонентом-источником указателя мыши (у которой удерживается в нажатом со­ стоянии левая или правая кнопка) автоматически активирует механизм drag and drop. В ручном режиме перетаскивания (dmManual) команду на перетаскивание дол­ жен дать программист. Процесс перетаскивания последовательно сопровождается цепочкой событий (табл. 4.4).


80

Гпава 4 Таблица 4.4. Последовательность событий в операции drag and drop

События

Инициатор

Описание

OnDragEnter()

Элементисточник

Над элементом-источником нажата кнопка мыши, и начато движение с удержанием кнопки в нажатом со­ стоянии

OnDragLeave()

Элементисточник

Указатель мыши покидает область над элементомисточником

OnDragEnter()

Элементприемник

Над приемником появилась мышь с "данными", пере­ таскиваемыми от элемента-источника

OnDragOver()

Элементприемник ■

Многократная генерация события, пока перетаскивае­ мый элемент находится над приемником

OnDragDroup()

Элементприемник

Над элементом-приемником отпущена кнопка мыши

OnDragEnd()

Элементисточник

Сигнализирует о завершении перетаскивания, исполь­ зуется для освобождения используемых ресурсов, от­ мены операции и т. п.

В минимальной нотации для осуществления перетаскивания достаточно услуг все­ го двух событий, генерируемых на стороне компонента-приемника: OnDragOver () и OnDragDrop (). Первое из событий осуществляет проверку приемлемости поступаю­ щих данных и дает согласие на получение данных от компонента-источника. 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.14. 4 14 Объявление записи D r a o O k n o a t в мс ш чпе о залиои iTDsagOb^at модуле

'

II

flJSsk Ml

type TDragObject = record Source: TObject; //объект-источник операции Files: array of string; //массив строк Data: Variant; //дополнительные данные end;

Второе обязательное событие механизма drag and drop отвечает за обработку по­ ступивших данных. Событие генерируется в момент отпускания кнопки мыши над компонентом-приемником.


81

Базовые классы FireMonkey

property OnDragDrop: TDragDropEvent; type TDragDropEvent = procedure(Sender: TObject; const Data: TDragObject; const Point: TPointF) of object;

Параметры события вам уже знакомы, поэтому в отдельных комментариях не нуж­ даются. , Для подтверждения теории на практике создайте новое приложение FireMonkey и разместите иа форме произвольное количество компонентов Timage, специализи­ рующихся на хранении и отображении рисунков. У всех компонентов установите свойство DragMode в автоматический режим. В любой из компонентов Timage загру­ зите произвольный рисунок. Листинг 4.15 содержит код, общий для всех компонентов. Здесь описаны оба рас­ смотренных ранее обработчика событий. ..... •-••••— *..................................................... ...................... *...... .

| Листинг 4.15. Пример перетаскивания картинки prooedure TForml.ImagelDragOver(Sender: TObject; const Data: TDragObject; const Point: TPointF; var Accept: Boolean); begin {проверка допустимости операции} Accept:=(Data.Source is Timage) and (Data.SourceOSender) and (Timage(Data.Source).Bitmap.IsEmpty=false); end; prooedure TForml.ImagelDragDrop(Sender: TObject; const Data: TDragObject; const Point: TPointF); begin Timage(Sender).Bitmap.Assign(Timage(Data.Source).Bitmap); {передаем картинку в приемник} TImage(Data.Source).Bitmap.Destroy; {удаляем исходную картинку у источника} Timage(Data.Source).Bitmap:=TBitmap.Create(0,0);{создаем новый пустой Bitmap} Timage(Data.Source).Repaint; end;

Завершая разговор о drag and drop, упомянем о наличии еще трех вспомогательных событий, имеющих отношение к перетаскиванию. В момент появления указателя мыши над компонентом, принимающим участие в процессе перетаскивания, гене­ рируется событие property OnDragEnter: TDragEnterEvenb;

В момент ухода указателя мыши вызывается еще одно событие property OnDragLeave: TNotifyEvent;


82

Глава 4

0 завершении процедуры перетаскивания сигнализирует событие property OnDragEnd: TNotifyEvent;

Особенности прорисовки элемента управления Визитной карточкой FireMonkey являются выдающиеся графические возможности. Немудрено, что каждый потомок класса TControi обладает способностью вывода своего изображения в любом подходящем для этого месте. Процесс вывода ини­ циируется методом procedure PaintTo(const ACanvas: TCanvas; const ARect: TRectF; const AParent: TFmxObject = nil);

Здесь, помимо ссылки на холст ACanvas, на поверхности которого будет осуществ­ лен вывод, следует указать прямоугольную область ARect, ограничивающую уча­ сток вывода (листинг 4.16). 1 Листинг 4.16. Вывод изображения элемента управления на форме Forml

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;

позволяющий создать экранный снимок элемента управления и представить его в формате растрового объекта TBitmap. Зам ечание

Метод MakeScreenshot () может пригодиться, если вы хотите сохранить в растровом образе TBitmap результат воздействия графических эффектов (палитра компонентов Effects) на элементы управления (см. главу 19).


Базовые классы FireMonkey

83

Стилевое оформление, класс TStyledControl Одним из достоинств библиотеки FireMonkey является возможность индивидуаль­ ного стилевого оформления элементов управления. Ответственность за это возло­ жена на описанный в модуле f m x .controls класс TStyledControl. Ключевое свойст­ во класса p ro p e rty StyleLookup: s tr in g ;

предоставляет разработчику возможность управлять внешним видом компонента одним щелчком мыши в Инспекторе объектов (рис. 4.9).

Рис. 4.9. Примеры стилевого оформления кнопки ТВи^опдля приложения iOS

30-элементы управления, класс TContro!3D Желание научить проекты Delphi работать в трех измерениях заставило програм­ мистов Embarcadero пересмотреть всю концепцию построения элементов управле­ ния. Во-первых, возникла необходимость модифицировать способ позиционирова­ ния объекта — теперь кроме координат X и Y приходится учитывать и координа­ ту Z. Во-вторых, в трехмерных проектах изначально рассчитанная на двухмерные сцены библиотека GDI здесь однозначно неприменима, поэтому принципиально


84

Гпава 4

изменилась механика графического вывода. В-третьих, амбициозное решение соз­ дать кроссплатформенный язык программирования, способный создавать програм­ мы не только для Windows, вынудило разработчиков принципиально перекроить внутренний механизм работы приложения уже на уровне RTTI. При желании к приведенному перечню проблем можно добавить и "в-четвертых", и "в-десятых". Одним словом, библиотека FireM onkey— это абсолютно новый, не имеющий ана­ логов инновационный продукт. В последних разделах главы, посвященной базовым классам FireMonkey, мы уделим внимание ключевым чертам компонентов, предназначенных для работы в 3 D-приложениях— трехмерных элементов управления, построенных на основе класса FMX.Controls3D.TControl3D.

Размеры объекта Появление у элементов управления третьего измерения отражается в дополнений традиционных свойств property Width: Single; property Height: Single;

отвечающих соответственно за ширину и высоту объекта еще одним свойством property Depth: Single;

позволяющим разработчику определить глубину объекта.

Повороты объекта Трехмерный объект может вращаться относительно каждой из своих осей, значение поворота в градусах направляется в свойство property RotationAngle: TPosition3D;

Координаты точки объекта, относительно которой осуществляется вращение, на­ значает свойство ' property RotationCenter: TPosition3D;

По умолчанию в качестве точки вращения выбирается центр объекта (точка (0.5, 0.5, 0.5)), при необходимости программист сможет изменить точку вращения, выбирая значения из диапазона от 0 до 1. При необходимости элемент управления может выяснить у другого объекта AObject значения его углов поворота и применить их к себе procedure CopyRotationFrom(const AObject: TControl3D);

Возврат повернутого элемента управления в исходное состояние осуществляет метод procedure ResetRotationAngle;


Базовые классы FireMonkey

,

85

30-события мыши У трехмерного объекта наиболее сложны (а поэтому и весьма интересны) события мыши. Сразу заметим, что набор событий ничем не отличается от перечня "мыши­ ных" событий для элемента управления VCL или FireMonkey HD, таким образом, у потомков класса TControi3D вы обнаружите: □ события нажатия OnMouseDown () и опускания OnMouseUp () кнопок мыши; □ событие перемещения мыши OnMouseMove (); □ событие вращения колесика мыши OnMousewheel (); □ события появления onMouseEnter () и исчезновения onMouseLeave () указателя мыши над объектом. Из представленного выше перечня существенным доработкам подверглись события OnMouseDown (), OnMouseUp () и OnMouseMove (). Например, события, связанные с нажа­ тием и отпусканием кнопок мыши property OnMouseDown: TMouseEvent3D; property OnMouseUp: TMouseEvent3D;

типизированы процедурой TMouseEvent3D = procedure(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single; RayPos, RayDir: TVector3D) of object;

Здесь нам встречаются как уже знакомые по табл. 4.3 параметры Sender, shift и х, Y, так и пара новых векторных параметров (RayPos и RayDir), собственно благодаря которым событие и становится "трехмерным". То же самое можно сказать и об об­ работчике события, обеспечивающем перемещение указателя мыши property OnMouseMove: TMouseMoveEvent3D; TMouseMoveEvent3D = procedure(Sender: TObject; Shift: TShiftState; X, Y: Single; RayPos, RayDir: TVector3D) of object;

В нем нам ВНОВЬ встречаются параметры RayPos И RayDir. Каково предназначение RayPos и RayDir? Это векторы, соответственно предостав­ ляющие программисту сведения об исходном положении и направлении движения указателя мыши над трехмерным элементом управления. 1

Попробуем разработать приложение, позволяющее перемещать потомка класса TControi3D (в нашем примере куб cubei:TCube). Первым шагом станет объявление трех глобальных переменных (листинг 4.17). | Листинг 4.17. Объявления глобальных переменных

_RayPos, _RayDir: TVector3D; //начало вектора и направление.вектора WorkPlane: TPosition3D; //определяет рабочую плоскость


86

Гпава 4

В векторах RayPos и RayDir мы намерены хранить промежуточные значения па­ раметров вектора. Рабочая плоскость определит координатные оси, в пределах ко­ торых пользователь сможет изменять положение трехмерного объекта. В нашем примере (листинг 4.18) ограничим пользователя плоскостью осей абсцисс и ор­ динат.

..шшшшшшшшшшшшшшашшш.. :..

| Листинг 4.18. Создание и уничтожение рабочей плоскости WorkPlane ................ .................... г...'..:..... ............... .

...... .. ;

procedure TForm2.Form3DCreate(Sender: TObj ect); begin WorkPlane:=TPosition3D.Create(Point3D(0, 0, 1)); end;

//создаем плоскость

prooedure TForm2.Form3DDestroy(Sender: TObject); begin WorkPlane.Free; end;

Перед началом перемещения объекта следует запомнить исходные значения пози­ ции и направления вектора (листинг 4.19). Позднее эти значения станут точкой от­ счета для движения объекта мышью. ймммаяшрм^армшиашршв^^

\Листинг 4.19. Запоминание параметров вектора в момент нажатия кнопки мыши procedure TForm2.CubelMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single; R&yPos, RayDir: TVector3D); begin _RayPos := Cubel.LocalToAbsoluteVector(RayPos); _RayDir := Cubel.LocalToAbsoluteVector(RayDir).GetNormalize; end;

Наконец подготовительные операции завершены, мы приступаем к самому важно­ му (листинг 4.20). | Лг ;тинг4.20. procedure TForm2.CubelMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single; RayPos, RayDir: TVector3D); var V, VI, V2, M: TVector3D; begin if ssLeft in Shift then begin RayPos := Cubel.LocalToAbsoluteVector(RayPos); RayDir := Cubel.LocalToAbsoluteVector(RayDir).GetNormalize;


Базовые классы FireMonkey

87

//учитываем рабочую плоскость V := WorkPlane.Vector; V.W := 0; V := Cubel.LocalToAbsoluteVector(V);

i f (RayCastPlanelntersect(_RayPos, _RayDir, Cubel.AbsolutePosition,V,VI)=false) or (RayCastPlanelntersect(RayPos, RayDir, Cubel.AbsolutePosition, V, V2)=false) then Exit; M := V2 - VI; //расчет вектора перемещения //перемещаем объект Cubel.Position.Vector := Cubel.Position.Vector + M; _RayPos := RayPos; _RayDir := RayDir;

end; end;

Как видите, перемещение объекта в трехмерном пространстве — не такая простая задача, однако если проявить немного настойчивости, то она вполне решаема.


ГЛАВА 5

Приложение FireMonkey При проектировании компонентов FMX специалисты Embarcadero постарались со­ хранить привычные по классической библиотеке визуальных компонентов VCL названия, функциональное назначение и, по возможности, набор свойств и методов новых классов. Поэтому вы вряд ли удивитесь, узнав, что приложение FireMonkey, будь это двухмерное приложение HD или трехмерное 3D, описывается классом TApplication.

Приложение TApplication Приложение f m x .Forms.TApplication несколько выбивается из стройных рядов классов FireMonkey. Несмотря на то, что в полном имени класса присутствует при­ ставка "FMX" (уведомляющая программиста, что он имеет дело с кроссплатформенной библиотекой), описанное в модуле f m x .Forms приложение строится на осно­ ве традиционного класса библиотеки VCE System.Classes.TComponent (см. рис. 2.1). Зам ечание

При знакомстве с классами FireMonkey любой программист станет сравнивать их с аналогами из VCL. На взгляд автора, в сравнении с классическим TApplication из состава VCL, класс f m x .Forms.TApplication несколько проигрывает. Впрочем, это объясняется объективной причиной. Нацеленная исключительно на Windows библио­ тека VCL позволила разработчикам Delphi спроектировать исключительно качествен­ ный специализированный класс, наделенный внушительным набором сервисных воз­ можностей. В свою очередь, при работе над универсальным приложением, способным одинаково хорошо трудиться как под управлением Windows, так и под контролем OS X, iOS и Android, специалисты Embarcadero были связаны обязательством — од­ новременно удовлетворить требования очень непохожих операционных систем. Так что по объективным причинам f m x .Forms.TApplication— это своего рода компро­ мисс между современной Delphi и поддерживаемыми операционными системами.

Для организации работы с приложением не стоит вызывать его конструктор. Объ­ ект приложения создается автоматически. Чтобы в этом убедиться, выберите пункт меню Project | View Source, и в редакторе кода откроется головной модуль проекта FireMonkey (листинг 5.1).


Приложение FireMonkey

89

\Листинг 5.1. Головной модуль проекта приложенйя FireMonkey program Projectl; uses FMX.Forms, Unitl in 'Unitl.pas' (Forml}; {$R *.res} begin Application.Initialize; //инициализация Application.CreateForm(TForml, Forml); //создание главной формы Application.Run; //старт приложения

Как видно из листинга, доступ к экземпляру приложения предоставляет глобальная (объявленная в модуле f m x .Forms) переменная Application. В головном модуле про­ екта приложение проходит инициализацию, создается главная форма и производит­ ся старт программного продукта. Приложение обязано знать свою главную форму. Ссылка на эту форму находится в свойстве property MainForm: TCommonCustomForm;

Для экстренного завершения приложения можно воспользоваться методом procedure Terminate;

Но следует понимать, что это слишком прямолинейный метод, поэтому им не стоит злоупотреблять.

Значок приложения Если вы разрабатываете настольное приложение для Windows или OS X, то вос­ пользуетесь пунктом меню Project | Options и получите доступ к редактору опций проекта (рис. 5.1), в котором сможете выбрать значок приложения. Заметьте, что настольное приложение FireMonkey способно оперировать как со значками в стиле Windows (это файл с рисунком с расширением ico), так и с фай­ лом, содержащим значки в духе OS X (расширение icns). При проектировании приложения для мобильных платформ помощник выбора значков несколько меняется (рис. 5.2).

Название приложения При необходимости с приложением может быть сопоставлено текстовое название property Title: string;

Однако данное свойство оставлено разработчиками новой платформы в составе TAppiication скорее по привычке и в современных версиях Windows и OS X не ото­ бражается.


90

Гпава 5 3

------------------'

Project Options for P'ojectl.exe (Win32 * Debt*

л ■Delphi Compter |

j- Comping j Hints «id Warnings \ Unking j Output-C/C++ * Resource Compter Drectones and CondSooais у-8u§d Events Forms ;■■■Appfcaikm : Version Info л Packages [ 1 Runtime Packages * Debugger ; ■Symbol Tabies Environment ffiock Provisioning

Target:

ЩЩЩ&хт piai&srm

^ ve— i

AppScatSon Icon Setfjngs

Icon:

loedkon...

l£ns icon.'

©

L_

Runtime Themes

iSMSK*!

i ____ ;

JR Output settings Target fiteextension;

g

; L ? sl J

I

Рис. 5.1. Раздел Application в редакторе опций проекта для приложений Windows и OS X ■ О

-- ■

Project Options for fcbProjectl.so (Android - Debug)

л \

*

------ -------- --------- ---- т а д Ш & Я И

------------------------- ------------------------- ------------------------- ------------------------- ------------------------- ------------------------- ------------------------- .-------------------------------------------------- ------------------------- ----------------

Deipbi Сотрйег \

Cowp&TlQ

and Warnings Linking Output - C/C++ * Resource Compder Directories and Conditional !■ Buld Events Uses Permissions : Forms Appfccabon Version Info л Packages Runtime Packages л Debugger . SymboiTabies Environment Sock Provisioning j

H in ts

Target: Debug configuration -Android platform

j Launcher icons Orientation^

*

Is < W * ~ i l k

s**~.

J

Preview с program f3es ,..у^_^гыпсЫу1соп_36х36.рП9

Launcher icon (36x36 (dpi):

Launcher icon (36x36 idp»}t

S{BDS} $*n\ArtworkWidro«d'fMJ,auncftef

Launcher icon (48x4Q mdp*): $ $ D S } ^ WtworfeVtodroidV^MJ-aunchef |g|j Launcher icon (72x72 Ьф|): $^)S)V3inWtwork\Androidf:M_Laundi« Q Launcher icon (96x% xhdpi): S(Bt)S^'wtworttWndroid!fMJ.amchei Ц Launcher icon (144x144 xxhdpi); $$DS)^\Artwork\Android¥^J.aunche« В Actual size

L ._ _ _

<* J L J L и* I ) | --------------------------------------------------------------------------------------------------- Й

Рис. 5.2. Раздел Application в редакторе опций проекта для мобильных приложений Android

Еще одно связанное с названием приложения свойство property DefaultTitle: string; //только для чтения

заполняется автоматически в момент запуска приложения. Здесь может оказаться строка "application", название связанного с приложением сервиса или свойство во­ обще может быть пустым.


Приложение FireMonkey

91

Расположение исполняемого файла приложения При многолетней работе с классической библиотекой VCL у многих из нас сложи­ лись собственные предпочтения и привычки. Например, в своих проектах для вы­ яснения расположения приложения автор зачастую обращался к свойству ExeName, имевшемуся у класса TAppiication из VCL. К сожалению, аналогичного свойства у приложения FireMonkey нет, посему из сложившейся ситуации приходится выкручиваться подручными средствами. В данном случае простейшим решением может стать задействование определенной в модуле System. Sysutils функции function GetCurrentDir: string;

Функция возвращает текущий относительно выполняемого приложения каталог. Если к ней обратиться в момент запуска приложения или в момент создания глав­ ной формы, мы получим полный путь к папке, в которой располагается исполняе­ мый файл.

События приложения Приложение FMX не в состоянии похвастаться богатым перечнем обработчиков событий — их всего три. В минимальный джентльменский набор вошло событие, позволяющее осуществить централизованную обработку исключительных ситуа­ ций (ИС): property OnException: TExceptionEvent; type TExceptionEvent=procedure(Sender: TObject; E: Exception) of object;

Здесь Sender — приложение, в котором возникла ИС; е — экземпляр исключитель­ ной ситуации. Так как в этот обработчик события станут стекаться сведения обо всех произошедших в приложении исключительных ситуациях, то OnException () обычно не специализируется на обработке частного вида ошибок. Наоборот, в его рамках описывается общая концепция защиты программного продукта. В качестве примера рассмотрим вариант применения события OnException () для ведения журнала ошибок приложения (листинг 5.2). Анализируя этот протокол, программист получит бесценную информацию о своих недоработках. Обратите внимание на. то, что наш код сосредоточен в головном модуле проекта (в файле с расширением имени dpr). Для того чтобы добраться до этого модуля, воспользуй­ тесь главным меню Delphi: P ro ject | View Source. .. program Projectl; uses FMX.Forms, System.SysUtils, System.IOUtils, Unitl in 'Unitl.pas' {Forml}; {$R *.res}


Гпава 5

92

type TAppExcept= class(TObject) //объявляем новый класс ИС private procedure ExceptionsControl(Sender:TObject; E: Exception); end; procedure TAppExcept.ExceptionsControl(Sender: TObject; E: Exception-); var s:string; begin s:=Format('%s'+#9+'%s',[DateTimeToStr(Now),E.Message]); //сообщение TFile.AppendAllText('errors.log',s+#10#13); //запись в файл Application.ShowException(E); //информируем пользователя об ошибке

var AppExcept : TAppExcept; begin Application.Initialize; AppExcept:=TAppExcept.Create; //создание нашего объекта Application.OnException:= AppExcept.ExceptionsControl; Application.CreateForm(TForml, Forml); Application.Run;

Главным действующим лицом листинга выступает класс TAppExcept, единственной особенностью которого является процедура ExceptionsControl () — по параметрам абсолютный аналог события OnException (). Единственный метод класса TAppExcept решает простейшую задачу— сохраняет содержимое свойства Message исключи­ тельной ситуации в файл протокола и выводит сообщение об ошибке. Обратите внимание, каким образом будет вызываться процедура TAppExcept. ExceptionsControl (). Сразу после инициализации объекта Application мы создаем объект AppExcept И накрепко связываем его метод ExceptionsControl () с процеду­ рой обработки события OnException о . Теперь при возникновении в приложении ИС ее обработка переходит в описанный нами метод и будет запротоколирована в файл errors.log. Второе событие приложения property Onldle: TIdleEvent; type TIdleEvent=procedure(Sender: TObject; var Done: Boolean) of object;

позволит программисту вызывать код фонового режима в момент простоя прило­ жения. На этом список событий исчерпывается. Внимание!

Событие Onidle () генерируется только у приложения, выполняющегося под управле­ нием Windows.

В листинге 5.3 представлен фрагмент кода, демонстрирующий порядок подключе­ ния к событию Onldle ().


Приложение FireMonkey

93

Листинг 5.3. Подключение к событию Onldle () приложения type TForml = class(TForm) procedure FormCreate(Sender: TObject); private procedure IdleThread(Sender: TObject; var Done: Boolean); public { Public declarations } end; var Forml: TForml; implementation { $R * . f m x }

prooedure TForml.FormCreate(Sender: TObject); begin Application.Onldle:=IdleThread;

prooedure TForml.IdleThread(Sender: TObject; var Done: Boolean); begin //код приложения, выполняемый в фоновом режиме

В блок кода фонового режима целесообразно включать небольшие программные конструкции, выполнение (или невыполнение) которых не является критичным для основной программной логики приложения. Кроме того, в фоновом режиме можно устанавливать в актуальное состояние элементы пользовательского интерфейса (пункты главного меню, кнопки быстрого доступа и т. п.), например, активировать пункт меню С охранить, если обрабатываемый документ был модифицирован, и, наоборот, выключать этот пункт меню, если в нем нет необходимости. Благодаря тому, что во второй версии библиотеки FireMonkey появилась возмож­ ность создавать командные объекты TAction, позволяющие обеспечить централи­ зованное управление приложением, у TAppiication имеется еще один обработчик событий property OnActionUpdate : TActionEvent;

Данное событие генерируется при каждом обновлении состояния командного объ­ екта (см. гл а ву 7).

Контроль активности пользователя Заключительная особенность приложения FMX, на которой стоит заострить внима­ ние разработчика, заключается в возможности обеспечения контроля активности


Гпава 5

94

пользователя. Пока это сводится к появлению двух новых, доступных только для чтения свойств property LastKeyPress : TDateTime; property LastUserActive : TDateTime;

Первое из свойств хранит сведения о том, когда в последний раз пользователь на­ жал любую клавишу в активном приложении. Второе свойство знает, когда пользо­ ватель в последний раз осуществлял любые действия с программой (нажатие кла­ виш, движение мыши, прикосновение к сенсорному экрану).

Характеристики дисплея, класс TFormFactor Кроссплатформенное приложение FireMonkey регулярно сталкивается с задачей выяснения характеристик устройства, на котором оно должно выполняться. Масло в огонь подливает тот факт, что приложение может оказаться развернутым не толь­ ко на персональным компьютере под управлением Windows или OS X, но и на мо­ бильном устройстве iOS или Android. В данном случае нашим помощником может стать свойство property FormFactor: TFormFactor;

дающее

доступ

к

инкапсулированному

в приложение

Application

объекту

TFormFactor. Зам ечание

Свойство FormFactor может быть настроено не только для приложения, но и индиви­ дуально для каждой из форм, входящих в проект.

Класс TFormFactor предоставит сведения, на каких устройствах может быть ото­ бражено приложение, для этого предназначено свойство property Devices: TDeviceKinds;

способное возвратить множество значений, элементами которого выступают dkDesktop, dkiPhone И dkiPad;

Владелец смартфона или планшета может как угодно повернуть свое устройство. Поддерживаемые ориентации экрана устройства хранятся в свойстве property Orientations: TFormOrientations;

Здесь вновь речь идет о множестве, значениями которого могут выступать: soPortrait, soLandscape, soInvertedPortrait И soInvertedLandscape. Перечисленные значения также могут быть настроены и в редакторе опций мобильного проекта (пункт меню P ro ject | O ptions), в этом вы можете убедиться, взглянув на рис. 5.3. Завершая разговор о TFormFactor, обязательно упомянем традиционные свойства property Width: Integer; property Height: Integer;

знающие все о разрешении устройства отображения.


Приложение FireMonkey

95

Pioject Options for iibP'ojectl.so iAndro’a - Debug) 4 Delphi Compiler CompSng j

f - Hints and Warnings

j ;

л

Uniting Output-C/C++ Resource Comptfer

I Directories and Conditionals : Buid Events

Ш

Ш 1

farget: Debug configuration - Android platform

I Launchfir icons * Orientation \ Check custom orientation and set the orientations) that you want your appfcabon to support.

Preview c:\program fiies ,,.^JjwncherIconJ$6x36.png launcher con (36x36 tdpi):

Uses Permissions : Forms Appfcabon Version Info

л -Packages *

Runtime Packages Debugger ?-• Symbol Tables

J

В Custom orientatjon HS&ortrait 111 k£xsde down Й landscape home right В landscape home Mft

Environment Block Provtstorung

Р и с . 5.3. Н а с тр о й ка о р и е н та ц и и экр ан а в р е д а кто р е оп ци й пр о е кта д л я м о б и л ь н ы х п р и л о ж е н и й

Формы HD и 3D Основу приложений FireMonkey с графическим интерфейсом пользователя состав­ ляет форма. Приложения FireMonkey могут использовать две разновидности форм: □ форму f m x .Forms.TForm, предназначенную для построения деловых приложений с высококачественным двухмерным визуальным интерфейсом (в том числе и с интерфейсом Metropolis, предназначенным для Windows 8); □ форму FMX.Foms3D.TForm3D, реализующую всю мощь трехмерной графики. Зам ечание

Для обеих разновидностей форм основным родительским классом выступает f m x .Forms.TCommonCustomForm. Затем цепочка наследования разделяется на две ветви (см. рис. 2.1) — TCustomForm и TCustomForm3D, соответственно отвечающие за построение двухмерной и трехмерной форм.

Основное назначение формы — служить контейнером для визуальных элементов управления, предоставляя программисту возможность создавать пользовательский интерфейс приложения, просто перенося подходящие компоненты со страниц па­ литры. После старта программы форма приобретает все необходимые качества полноценного окна приложения для Windows, OS X, iOS или Android. Число форм в проекте не ограничено, но в любом случае в приложении может быть только одна главная ф о р м а (main form) проекта. По умолчанию главной формой проекта назначается самая первая созданная ф орм а— форма с названием Forml. При запуске приложения на выполнение именно эта форма будет создана и выве­ дена на экран первой.


Гпава 5

96

При необходимости у проекта, обладающего несколькими формами, на роль глав­ ной может быть назначена другая форма. Для этого следует воспользоваться пунк­ том меню Project | Options и в появившемся дереве настройки опций проекта вы­ брать узел Forms (рис. 5.4). Главная форма выбирается в раскрывающемся списке Main form. Стоит заметить, что на роль главной формы могут претендовать только формы из списка Auto-create forms (Автоматически создаваемые формы). Форма из списка Available forms (Доступные формы) стать главной не сможет, т. к. она не подлежит автоматическому созданию в момент старта приложения.

Рис. 5.4. Окно опций со страницей выбора главной формы и перечнем доступных форм

При разработке пользовательского интерфейса наших приложений мы всегда будем помнить, что закрытие главной формы приводит к автоматическому закрытию всех остальных форм проекта и прекращению работы приложения.

Описание формы в fmx-файле Добавление к проекту формы сопровождается созданием файла ресурсов формы с расширением fmx. Именно в этом файле и хранится текстовое описание формы и принадлежащих ей компонентов. Для того чтобы взглянуть на описание во время визуального проектирования, следует воспользоваться контекстным меню формы и выбрать пункт View as Text (Представить как текст) или нажать комбинацию кла­ виш <Alt>+<F12>. Код с текстовым описанием ресурса формы с рис. 5.5 представлен в листинге 5.4. Обратите внимание на то, что кроме определения основных свойств формы ресурс fmx-файла содержит определения всех принадлежащих форме элементов управле­ ния (в нашем случае метки Labeli и кнопки Buttonl).


Приложение FireMonkey

97

uadi Edit Control Bind VreusUy... Position Flip Children £

Buttonl

T-b Окй*.

2

Creation

Ord«„.

ftevert to

Add to Repositon* View as T at ✓

Те* FMX Convert to Metropolis U1

Рис. 5.5. Обращение к контекстному меню формы во время визуального проектирования

| Лист» 1нг 5.4. object forml: TForml Left = О Top = О Caption = 'Forml' ClientHeight =1 92 ClientWidth = 362 Visible = False OnClose = FormClose StyleLookup = 'backgroundstyle' object Label1: TLabel Position.Point = '(40,24)' Width = 120.000000000000000000 Height = 15.000000000000000000 TabOrder = 1 Text = 'Labell' end object Buttonl: TButton Position.Point = '(144,104)' Width = 80.000000000000000000 Height = 22.000000000000000000 TabOrder = 1 0 Text = 'Buttonl' end


98

Гпава 5 Внимание!

Для того чтобы вернуть форму к обычному представлению, нажмите комбинацию кла­ виш <Alt>+<F12>.

Общие черты форм Все используемые в FMX формы опираются на фундамент родительского класса type TCommonCustomForm = class (TFmxObject, IRoot, IContainerObject,

IAlignRoot, IPaintControl, IStyleBookOwner, IDesignerStorage, IOriginalContainerSize)

Помимо уже знакомого нам класса TFmxOb ject в объявлении упоминается несколько интерфейсов, которые оказывают форме все необходимые услуги автономно и не требуют вмешательства со стороны обычного программиста. Зам еча ни е

Интерфейс высокоуровневых контейнеров IRoot оказывает форме всестороннюю поддержку по доступу к активным объектам и элементам управления, находящимся в фокусе ввода. Интерфейсы IContainerObject и IAlignRoot упрощают выравнивание объектов на поверхности контейнера. Интерфейс IPaintControl определяет, какой из элементов управления формы нуждается в перерисовке. Элементы управления фор­ мы, способные выступать владельцем книги стилевого оформления (компонент TStyleBook), обслуживаются интерфейсом IStyleBookOwner. При разработке форм для мобильных проектов задействуется интерфейс IDesignerStorage. Наконец, ин­ терфейс IOriginalContainerSize устанавливает корректные размеры для элемен­ тов управления.

На уровне TCommonCustomForm класса заложен минимально необходимый функцио­ нал: □ объявлены базовые конструкторы и деструктор формы; □ осуществляется управление размерами и местоположением формы; □ реализуется отображение формы (в том числе в модальном режиме) и скрытие формы с экрана.

Создание, отображение и уничтожение форм По умолчанию каждая подключаемая к проекту форма (пункт меню File | New | F ireM onkey HD F orm ) при старте программы заносится в список форм, подлежа­ щих автоматическому созданию. При запуске приложения первой на свет появля­ ется главная форма проекта, все остальные формы создаются согласно очередно­ сти, указанной программистом. Для изменения очередности создания форм надо заглянуть в опции проекта (Project O ptions) и расставить формы в надлежащем порядке в списке A uto-create form s (см. рис. 5.4). За создание форм из перечня A uto-create form s несет ответственность приложение Application. Воспользовав­ шись пунктом меню P ro ject | View Source, вы получите доступ к головному моду­ лю проекта и увидите, что для автоматически создаваемых форм приложение ис­ пользует метод CreateForm ().


Приложение FireMonkey

99

Для отображения формы на экране компьютера обычно применяют метод: procedure Show;

Такой способ вывода формы на экран называется нем одальны м . Это означает, что пользователь получает право свободно переключаться между окнами приложения. Среда проектирования позволяет нам отказаться от автоматического создания форм, такие формы переводятся в разряд дост упны х (список Available forms). Но в этом случае перед обращением к такой форме программист должен ее создать. По законам ООП для создания объекта требуется воспользоваться его конструкто­ ром, и форма не является исключением из правил: constructor Create(AOwner: TComponent);

Допустим, что описанная в программном модуле child.pas форма TfrmChild не явля­ ется автоматически создаваемой и входит в список доступных форм нашего проек­ та. В этом случае для создания экземпляра формы можно воспользоваться листин­ гом 5.5. Л-

ж г 5.5. Создание экземпляра формы с но»#ощью коне Щртора

var frmMain: TfrmMain; //главная форма implementation {$R *■.fmx} uses child; procedure TfrmMain.ButtonlClick(Sender: TObject); begin with TfrmChild.Create(Application) do //создание дочерней формы begin Caption:=Caption+'$ '+IntToHex(Handle,8);//текст заголовка Show; //вывод на экран end; end;

Внимание!

При ссылке одного модуля на другой не забудьте добавить в строку uses имя нового модуля. Для этого выберите пункт меню File | Use unit (<ALT>+<F11>) и в списке мо­ дулей найдите нужный модуль.

Форма TfrmChild создается по щелчку на кнопке Buttonl, расположенной на глав­ ной форме проекта frmMain. Основная особенность предложенного в листинге 5.5 КОДа В ТОМ, ЧТО будет СОЗДаНО РОВНО СТОЛЬКО КЛОНОВ формы TfrmChild, сколько вы сделаете щелчков по кнопке. Чтобы формы отличались друг от друга, мы выводим в заголовке создаваемых форм их дескриптор Handle.


100

Гпава 5 Зам ечание

Когда проект содержит несколько форм, хорошей практикой является присвоение формам осмысленных имен. Например, главную форму проекта можно называть frmMain, а соответствующий ей программный модуль — Main.pas.

Полноэкранный вывод При работе на планшетных компьютерах, смартфонах и телефонах приложение бу­ дет нуждаться в максимальном рабочем пространстве. Индикатором того, что фор­ ма находится в полноэкранном режиме, может выступать свойство property Fullscreen : Boolean; //по умолчанию false

Если приложение предназначено для работы под управлением OS X, то стоит поза­ ботиться об отображении значка полноэкранного режима (две расходящиеся по диагонали стрелки). Для этого предназначено свойство property ShowFullScreenlcon : Boolean;

Замечу, что в приложениях для OS X переход в полноэкранный режим при пассив­ ном состоянии свойства ShowFullScreenlcon невозможен, а для Windows это свойст­ во не имеет никакого значения.

Вывод формы в модальном режиме Для вызова формы в модальном режиме (режиме диалога) используйте метод function ShowModal: TModalResult;

Модальная форма отличается строптивым нравом. Она отображается поверх всех открытых окон. Более того, пользователь не сможет получить доступа к другим формам проекта, пока не будет закрыта модальная форма. Закрываясь, модальная форма возвращает целочисленное значение, называемое м одальны м результ ат ом . При необходимости в модальном результате можно закодировать решение пользо­ вателя, нажавшего ту или иную кнопку на форме. type TModalResult = Low(Integer)..High(Integer);

Возможные значения модального результата предложены в табл. 5.1. Таблица 5.1. Стандартный модальный результат TModalResult

Константа

Описание

mrNone ИЛИ 0

Значение по умолчанию

mrOk ИЛИ idOK

Пользователь нажал кнопку ОК

mrCancel или idCancel

Пользователь нажал кнопку Отмена (Cancel)

mrAbort ИЛИ idAbort

Пользователь нажал кнопку Прервать (Abort)

mrRetry или idRetry

Пользователь нажал кнопку Повторить (Retry)

mrlgnore или idlgnore

Пользователь нажал кнопку Пропустить (Ignore)

mrYes или idYes

Пользователь нажал кнопку Да (Yes)


Приложение FireMonkey

101 Таблица 5.1 (окончание)

Константа

Описание

mrNo или idNo

Пользователь нажал кнопку Нет (No)

mrAll ИЛИ mrNo + 1

Используется для определения последней константы

Для

передачи

результата

модальной

форме

обычно

задействуют

свойство

ModaiResuit кнопок. Анализируя модальный результат закрывающейся формы, про­

граммист получает возможность направлять поведение программы в определенное русло (листинг 5.6).

if Form2.ShowModal^mrOK then begin

{код для результата mrOK} end else {код для результата, отличного от mrOK};

Результат модальной операции заносится в свойство формы property ModaiResuit: TModalResult;

По умолчанию это свойство установлено в 0 (константа mrNone). Присвоение свой­ ству ModaiResuit ненулевого значения приводит к закрытию формы и возвращению значения этого свойства в качестве результата метода showModai. Если получено ненулевое значение ModaiResuit, осуществляется вызов метода закрытия формы.

Закрытие формы Простейшим способом спрятать (не уничтожая) форму может стать присвоение свойству visible значения false. Этот же результат достигается при обращении к процедуре procedure Hide;

Кроме того, у формы имеется метод procedure Close;

предназначенный для закрытия формы. Если этот метод применяется к главной форме проекта, то он приводит к закрытию всех выведенных на экран форм и за­ вершению работы приложения. Если метод close () вызывается для вторичных форм проекта, то форма просто скрывается с экрана. Зам еча ни е

С методом Close () связаны два важных события— OnCloseQuery () и QnCloseO, о них мы поговорим отдельно (см. табл. 5.3).


102

Гпава 5

Уничтожение формы Хотя

форма поддерживает классический для всех компонентов деструктор Destroy () и метод Free (), для уничтожения экземпляра формы целесообразно вы­ зывать метод

procedure Release;

Это ближайший соратник деструктора, который добивается тех же результатов — очищает память компьютера от формы, но в отличие от деструктора делает это са­ мым деликатным образом — дожидается завершения обработки событий, иниции­ рованных этой формой или ее компонентами.

Состояние формы Пояснительная надпись в заголовке формы назначается при обращении к свойству property Caption: string;

Местоположение формы на экране компьютера зависит от значения, установленно­ го в свойстве property Position: TFormPosition;//по умолчанию poDefaultPosOnly TFormPosition = (poDesigned, poDefault, poDefaultPosOnly, poDefaultSizeOnly,poScreenCenter, poDesktopCenter, poMainFormCenter, poOwnerFormCenter);

При

создании

новой

формы

свойство

Position

принимает

значение

poDefaultPosOnly. Это означает, что вертикальный и горизонтальный размеры фор­

мы (свойства width и Height) назначаются разработчиком, а место вывода формы на экран определяется операционной системой. Обратная ситуация сложится в том случае, если свойство Position установлено в poDefauitsizeOniy. Теперь операци­ онная система станет выводить форму в месте, указанном программистом (свойства тор и Left), но ответственность за установку размеров сторон заберет в свои руки. Форма появится точно в центре экрана при передаче в Position значения poScreenCenter, а для центровки относительно рабочего стола подойдет значение poDesktopCenter. На поведение вторичных форм проекта влияют poMainFormCenter и poOwnerFormCenter. В первом случае дополнительные формы позиционируются по центру главной формы, во втором — по центру формы-владельца. Интересно пове­ дение формы при установке этого свойства в poDefault. При первом запуске при­ ложения форма отображается в левом верхнем углу экрана, а при каждом после­ дующем выводе на экран форма станет автоматически смещаться вправо и вниз. Высота и ширина формы определяются операционной системой и не зависят от указаний программиста. Если вы намерены самостоятельно управлять как размера­ ми, так и местом вывода формы, то установите свойство в состояние poDesigned. Замечание

Мы уже привыкли, что свойство с названием Position предназначено для опреде­ ления местоположения объектов FireMonkey на поверхности контейнера-владельца. Однако в форме это название уже задействовано, поэтому точное позиционирование формы на экране осуществляется за счет свойств Left и Тор.


Приложение FireMonkey

103

Перечень стандартных кнопок, располагающихся в правой части заголовка окна Windows или в левой стороне окна OS X, задается свойством property Borderlcons: TBorderlcons;

Различают: кнопку закрытия окна biSystemMenu; кнопки сворачивания biMinimize и разворачивания biMaximize окна; кнопку обращения к справочной системе biHeip. Щ елчки пользователя по кнопкам сворачивания и разворачивания окна изменяют значение свойства property WindowState: TWindowState; //по умолчанию TWindowState.wsNormal

TWindowState = (wsNormal, wsMinimized, wsMaximized);

Состояние wsNormal говорит о том, что форма в нормальном состоянии; wsMinimized— форма свернута; wsMaximized— форма развернута до максимально возможного размера. Свойство WindowState не только информационное, оно позво­ ляет сворачивать, разворачивать и нормализовать размеры формы во время выпол­ нения приложения. Еще одно свойство property FormState: TFmxFormStates; //только для чтения

позволит программисту контролировать, не находится ли форма в процессе созда­ ния (TFmxFormState.f sRecreating) И не станет ли после создания модальным окном (TFmxFormState.fsModal).

Существенное влияние на внешний вид и поведение формы оказывает стиль об­ рамления окна property BorderStyle: TFmxFormBorderStyle; //по умолчанию bsSizeable;

TFmxFormBorderStyle = (bsNone, //размеры неизменяемые, границы невидимы bsSingle, //размеры неизменяемые, простой бордюр bsSizeable, //обычная форма с настраиваемыми размерами bsToolWindow, //размеры неизменяемые, уменьшенный заголовок bsSizeToolWin); //размеры изменяются, уменьшенный заголовок

Жизненный цикл формы Жизненный путь формы начинается в момент ее создания. В табл. 5.2 приведена последовательность событий, возникающих при создании и выводе на экран ком­ пьютера новой формы. Таблица 5.2. Процесс создания и вывода на экран форм Событие

Описание

OnCreate()

Создание формы. Обычно этот обработчик события используется для ини­ циализации глобальных переменных формы и других подготовительных операций, необходимых для дальнейшей работы с формой

OnActivate()

Форма становится активной

OnPaint()

Осуществляется перерисовка формы


104

Гпава 5

Не меньший интерес представляют события, возникающие при закрытии формы. Для запуска процесса закрытия формы необходимо закрыть форму или вызвать метод procedure Close;

Последовательность событий, возникающих у формы в процессе закрытия, пред­ ставлена в табл. 5.3. Таблица 5.3. Процесс закрытия и уничтожения формы Событие

Описание

OnCloseQuery()

Запрос разрешения на закрытие формы

OnClose()

Форма закрывается

OnDestroy()

Форма уничтожается

OnDeactivate()

Форма перестает быть активной

Перед закрытием формы производится вызов обработчика события: property OnCloseQuery: TCloseQueryEvent; type TCloseQueryEvent = procedure (Sender: TObject; var CanClo.se: Boolean) of object;

Присвоив параметру-переменной canciose значение false, программист отменяет процесс закрытия формы. В листинге 5.7 предложен пример, осуществляющий контроль за изменением текста в многострочном редакторе Memol. В случае если в элементе управления текст модифицировался, то при попытке закрытия формы отображается диалоговое окно, предлагающее сохранить изменения.

var filename:string; //переменная с именем обрабатываемого файла

TextModified:boolean; //признак, что текст подвергался изменениям procedure TForml.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin if TextModified=true then

MessageDlg('Сохранить изменения в тексте?', TMsgDlgType.mtConfirmation, [TMsgDlgBtn.mbYes] + [TMsgDlgBtn.mbNo]+ [TMsgDlgBtn.mbCancel], -1) OF mrYes : if FileExists(FileName)=false then

CASE

begin

{файл не определен, вызываем диалог Сохранить как...}

// end else Memol.Lines.SaveToFile(FileName);


Приложение FireMonkey

105

mrNo : CanClose := true; else CanClose := false; END;

Если форма получила разрешение на закрытие, то генерируется событие property OnClose: TCloseEvent;

TCloseEvent = procedure (Sender: TObject; var Action: TCloseAction) of object;

Изменяя значение переменной Action (табл. 5.4), программист сможет управлять поведением закрываемой формы. Таблица 5.4. Возможные значения параметра Action: TCloseAction Значение

Описание

caNone

Ничего не происходит

caHide

Форма не закрывается, а только становится невидимой, к данной форме при­ ложение имеет полный доступ

caFree

Форма закрывается и освобождает занимаемые ресурсы

caMinimize

Вместо закрытия форма сворачивается

Немного поозорничав с параметром Action в событии 6nCiose(), можно создать очень вредную программу, которая станет активно сопротивляться своему закры­ тию. Для этого достаточно переписать обработчик события главной формы проекта так, как предложено в листинге 5.8. j Яистинг 5.8. Реакция главной формы иа закрытие procedure TForml.FormClose(Sender: TObject; var Action: TCloseAction); begin

Action:=TCloseAction.caNone; with TForml.Create(Application) do Show(); end;

Реальное уничтожение формы и освобождение всех задействованных в ее интере­ сах системных ресурсов произойдет только в случае, если присвоить переменной Action значение caFree. Только тогда будет вызвано событие property OnDestroy: TNotifyEvent;

При этом форма и все объекты, ей принадлежащие, удаляются из памяти компью­ тера.


106

Гпава 5

Доступ к элементу управления по его координатам Помимо уже оговоренных ранее способов доступа к принадлежащим форме дочер­ ним объектам, реализованных с помощью свойств components и children, форма предоставляет программисту еще один способ обращения к расположенным на ее поверхности визуальным элементам управления. Для этого задействуется метод function ObjectAtPoint(Р: 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 передайте строковое значение Main. Внимание!

Хранящееся в свойстве FormFamily значение Main указывает на то, что форма явля­ ется главной для определенной в FormFactor мобильной платформы.


107

Приложение FireMonkey

4. Добавьте к проекту еще одну форму iOS, на этот раз назовите форму f rm iPad. 5. Воспользовавшись Инспектором объектов, укажите, ЧТО frm iPad рассчитана Ж работу С iPad (FormFactor. Devices := [dkiPad]).

Теперь приложение, проанализировав, на какой из мобильных платформ оно было запущено, самостоятельно определит, какую из форм задействовать.

frm_iPhone TfrmJPhone

(B ra s h )

(TFormFector) [dtdPhonc]

В Fab*

s e P o r lr f it

[ f| ]T ru e

«Landscape

f f iT r u e

" o ln v e r t e t P o r t r iit

® Т п |е

s o tn v e rte d L a n d e c a p e Я Т п к

И< = «Ье L I v e S M k i g s D e s ig n e r

frm JPhor* (T B o tm d j) c c S y j t e n C e f a u lt j S h o w A c t iv a t e d © s s F iilS c r e e n lc o n IS t

v. O p c r

ill T r u e

BFA* И

T ru e

B in d V is u a J .y .

Рис. 5.6. Настройка свойства FormFactor для формы iPhone

Качество графического вывода У формы имеется свойство, управляющее качеством графического вывода: property Quality: TCanvasQuality;

По умолчанию свойство установлено в состояние ccSystemDefauit, указывающее на то, что порядок прорисовки определяется текущими системными настройками. Кроме этого, предусмотрены еще два варианта поведения: повышение производи­


108

ГпавеI 5

тельности за счет снижения качества вывода ccHighPerformance и высокое качество графики за счет ухудшения производительности ccHighQuaiity.

Форма HD FMX.Forms.TForm Форма HD (High Definition) во многом похожа на обычную форму VCL, поэтому если вы имеете хотя бы небольшой опыт программирования в предыдущих версиях Delphi, то быстро разберетесь со всеми нововведениями. Ключевых изменений все­ го два. Во-первых, при позиционировании и определении размеров форма HD окончатель­ но перешла на вещественные числа. Во-вторых, у формы FMX.Forms.TForm принципиально изменен порядок работы с графикой. Теперь в своей работе форма HD по умолчанию не задействует механизм графического вывода Windows GDI. Вместо этого новая форма нацелена на работу с DirectX (если приложение выполняется под управлением Windows) или Quartz 2D (для приложения OS X). Доступ к универсальному графическому механизму пре­ доставляет интерфейс i Scene. Вполне естественно, что смена графического "движ­ ка" привела к кардинальным переменам в недрах класса-холста TCanvas (см. главу 16). Зам ечание

Если по каким-либо причинам работающее под управлением Windows приложение FireMonkey теряет возможность осуществлять графический вывод под управлением DirectX, оно переключается на библиотеку GDI+.

Трехмерная форма FMX.Forms3D.TForm3D Трехмерная форма FireM onkey— это без всякого преувеличения инновационный класс, позволивший нам создавать управляемые 3 D-сцены несколькими щелчками мыши. Код реализации трехмерной формы сосредоточен в рамках класса TCustomForm3D (модуля f m x .Forms3D). Объявление класса type TCustomForm3D = class (TCommonCustomForm, IContextObject, IViewport3D)

содержит очень важный интерфейс iviewport3D, несущий ответственность за пред­ ставление трехмерной сцены наблюдателю — позволяющий управлять освещени­ ем, камерой (точкой наблюдения за сценой), рендерингом модели и т. д. За м еча ни е

Будьте внимательны при переработке своих старых проектов. В Delphi ХЕ2/ХЕЗ трех­ мерная форма описывалась в модуле fmx.Forms, с выходом ХЕ4 форма TForm3D перекочевала в модуль fmx.Forms3D. Замечание

На страничке Viewports палитры компонентов Delphi имеется компонент TViewPort3D. Это область просмотра, способная самостоятельно создавать 30-сцену и решать все вопросы, связанные с ее прорисовкой. Благодаря TViewPort3D вы сможете создавать трехмерные области на поверхности двухмерной формы.


109

Приложение FireMonkey

Форма TForm3D призвана работать в трехмерных прямоугольных координатах, нача­ ла которых находятся в центре клиентской области формы. Ось абсцисс х направ­ лена из точки (0, 0, 0) вправо, ось ординат у — вниз, виртуальная ось аппликат z уходит в глубину формы (рис. 5.7).

Приложение 30

Рис. 5.7. Система координат TForm3D

Системы координат 3D-приложений FireMonkey делятся на два вида: глобальная и локальная. Глобальные координаты начинаются в центре сцены в точке (0, 0, 0), которая называется началом координат. Локальными называются координаты во­ круг области объекта, который мы разместим на сцене. Внимание!

Размещая на трехмерной форме ЗО-элементы управления, помните, что эти элемен­ ты позиционируются не относительно своего левого верхнего угла (как это было в двухмерных проектах FireMonkey), а относительно своего центра (свойство Position).

Еще одной важной особенностью ЗО-формы является то, что графический вывод осуществляется с помощью трехмерного контекста графического устройства f mx .Types3D.TContext3D. Доступ к контексту предоставляет свойство property Context: TContext3D;

Процесс графического вывода сопровождается генерацией события property OnRender:. TRenderEvent; type TRenderEvent = procedure(Sender: TObject; Context: TContext3D) of object;

В числе параметров события вы вновь обнаружите контекст context.


110

Гпава 5 Внимание!

Для построения трехмерных сцен вместо прямого обращения к контексту TContext3D проще воспользоваться услугами компонентов 3D (страницы палитры компонентов 3D Scene, 3D Shapes и 3D Layers).

В простейшем случае цвет заливки формы назначается свойством property Color: TAlphaColor;

Для просмотра трехмерных сцен форма инкапсулирует камеру (объект тсатега). К ам ера— это не что иное, как удобная абстракция наблюдателя. Выбрав ту или иную позицию наблюдения, вы сможете взглянуть на одну и ту же сцену с разных ракурсов. Интегрированная в состав формы камера установлена прямо перед сце­ ной. По умолчанию камера включена, для ее отключения установите в false свой­ ство property UsingDesignCamera: Boolean;//по умолчанию true Замечание

Если вас не устраивает камера по умолчанию, то обратитесь к странице палитры ком­ понентов 3D Scene. Здесь вы найдете самостоятельный компонент тсатега и ряд других компонентов, предназначенных для дизайна трехмерных сцен (см. главу 27).

От состояния свойства property Multisample: TMultisample; //по умолчанию Multisample.ms4Samples

TMultisample = (msNone, ms2Samples, ms4Samples);

зависит порядок устранения неприятного ступенчатого эффекта (antialiasing), воз­ никающего у наклонных линий и поверхностей. По умолчанию у формы установ­ лен режим ms4Sampies, возвращающий наилучшую результирующую картинку. Однако если вместо показателя качества рендеринга вашему приложению важнее производительность, то можно уменьшить (ms2Sampies) или даже отключить (msNone) сглаживание.

Пример ЗО-проекта Создайте новый проект 3D. Для этого следует воспользоваться пунктом меню File | New | FireMonkey Desktop Application и в открывшемся диалоге FireMonkey Desktop Application выбрать 3D FireMonkey Application. Сохраните проект под любым именем и перейдите к главной форме приложения. Сразу раскроем секрет нашего примера — мы создадим модель вымышленной пла­ нетарной системы. Для этого разместите на главной форме следующие компоненты (рис. 5.8): □ сфера Spheгe_Sun:т Sphe re возьмет на себя функции единственной звезды, вокруг которой станут двигаться планеты. Расположите сферу точно в центре формы (свойство position= (0,0,0)). Воспользовавшись свойствами width, Height и Depth, придайте светилу достойные размеры (например, 4, 4, 4); □ для того чтобы наша звезда смогла светиться, нам понадобится источник света Light :TLight, который вы найдете на странице 3D Scene. Поместите компонент


111

Приложение FireMonkey

на форму и присоедините к spherel (в результате его положение Position совпа­ дет с положением "звезды"). Переключите свойство LightType в состояние ltPoint, это заставит источник света испускать свои "фотоны" во всех направле­ ниях; □ разместите на форме несколько сфер-планет TSphere. В примере использованы три компонента, но вашу фантазию я ограничивать не собираюсь; □ компонент таймер Timeri:TTimer возьмет на себя управление процессом движе­ ния планет. Установите интервал отсчета равным 100— 300 мс. Задача планет — вращаться по эллиптическим орбитам вокруг звезды. Чтобы вы не утруждали себя поисками уравнения эллипса в учебниках геометрии или астроно­ мии, сразу предложу вашему вниманию скромное параметрическое выражение: \j x = a c o s t , \ у = bsint,

где 0 < ? < 2л. Здесь а и Ъ описывают большую и малую полуоси эллипса.

едя*»»* jI !-Я|«мва

j j-armODJ f 0~~Jsph*ftjun

’Source

ЬЯ uswi ф.-[ sS(!hee2

1 ■'*■* yghtl iat i ’Stitirc У -

4 UebtMatenaASoureeZ

| '-Ц телзс* Й - Н Spheres

j j..‘

-g ra te s S-J§| Sphere* ) *•

rextSCW««aSoura «3D5Mit»i>(Source

>ехИММйюййаиrat lerl

i;

1

In s e rt

Рис. 5.8. Макет формы 3D с планетарной системой


112

Гпава 5

Дело осталось за м алы м — изучить листинг 5.9, в котором содержится исходный код программы, моделирующей планетарную систему из звезды и трех планет. ! Листинг 5.9. Исходный код программы планетарной сиотемы var Forml: TForml;

tl,t2,t3:single; const al=3;

bl=4; a2=4; Ь2=8; аЗ=10; Ь3=5;

//полуоси эллиптических орбит

implementation

{$R *.fmx} procedure TForml.Form3DCreate(Sender: TObject); begin

tl:=0; //инициализация исходных значений t2:=pi/2; t3:=pi/4; end; procedure TForml.TimerITimer(Sender: TObject); begin

Sphere2.Position.X:=al*cos(tl); //орбита планеты 1 Sphere2.Position.Y:=bl*sin(tl); Sphere3.Position.Z:=a2*cos(t2); //орбита планеты 2 Sphere3.Position.Y:=b2*sin(t2); Sphere4.Position.X:=a3*cos(t3); //орбита планеты 3 Sphere4.Position.Z:=b3*sin(t3); //приращения определяют скорость движения по орбитам tl:=tl+0.1; t2:=t2+0.15; t3:=t3-0.25; end;

Основные расчеты осуществляются в рамках события OnTimerf). Самостоятельно подберите интервал срабатывания таймера, чтобы получить реалистичную картину перемещения планет. Теперь проявим заботу о внешнем виде планет и звезды (компонентов TSphere). С помощью Materj.aisource трехмерные компоненты Delphi могут подключаться к специализированным невизуальным компонентам, способным служить источни­ ками ДЗННЫХ ДЛЯ ЗШ1ИВКИ ИХ ВИДИМОЙ поверхности. Источники заливки вы обнару жите на вкладке M aterials палитры компонентов FireMonkey, в их число входят: способный управлять цветом заливки компонент TCoiorMateriaisource; текстура


Приложение FireMonkey

113

компонент TlightMaterialSource, отвечающий За отражение света от поверхности объекта (см. главу 29).

чалишси TTextnreMateri a 1Source;

Редактор структуры S tru c tu re (который на рис. 5.9 запечатлен поверх главного ок­ на нашего приложения) раскрывает суть подхода к визуальному проектированию. Для создания фона звездного неба мы задействовали источник текстурной заливки TextureMaterialSourcel, занесли в его свойство Texture файл с подходящей фото­ графией и подключили компонент к заднему плану нашей сцены pianei. Для ими­ тации звезды, находящейся в центре планетарной системы, мы присоединили к свойству Materiaisource сферы Sphere Sun компонент, отвечающий за текстуру, взаимодействующую со светом LightMateriaiSourcel, в которую также была загру­ жена подходящая текстура. Точно так же можно поступить и с другими сферамипланетами и текстовыми надписями.

Совместное применение 2D- и ЗО-компонентов Клиентская область формы TForm3D нацелена на работу с трехмерными объектами (потомками класса TControi3D). Если программист попробует расположить на по­ верхности формы любой двухмерный элемент управления, то он не встретит осо­ бых возражений, но и элемента управления форма не отобразит. Причина такой избирательности проста — двухмерный компонент не в состоянии объяснить 3Dформе ни места своего размещения, ни способа прорисовки. Для решения указанной проблемы обычно применяют компонент TLayer3D, это специалист по совмещению несовместимого — 20-объектов и трехмерной формы. Способ решения проблемы очень прост. С одной стороны, компонент тьауегзо по­ нятен трехмерной форме, ведь он опирается на фундамент родного для ЗБ-проектов класса TControi3D и легко позиционируется в трехмерной системе ко­ ординат формы TForm3D. С другой стороны, клиентская область компонента TLayer3D имитирует привычную для 20-компонентов плоскую двухмерную систему координат. Начала координатных осей (0, 0) находятся в левом верхнем углу ком­ понента, ось х направлена слева направо, а ось у — сверху вниз. Подобный подход устраивает кнопки, строки ввода, метки, списки и другие двухмерные визуальные компоненты со страниц палитры FireMonkey. Один из примеров совместного применения 2D- и ЗЭ-визуальных элементов управ­ ления предложен на рис. 5.9. Компонент Layer3Di: TLayer3D со свойством Projection, установленным в состояние pjscreen, предлагает свою поверхность в распоряжение классических двухмерных элементов управления. Обратите внимание на еще одну особенность проекта с рис. 5.9. При размещении ЗО-компонентов, отвечающих за обслуживание модели биплана времен Первой мировой войны, мы отказались от прямых услуг формы. Вместо этого оставшаяся свободной клиентская область окна была передана в распоряжение второго компо­ нента Layer3D2: TLayer3D. На Layer3D2, В СВОЮ очередь, расположилась область просмотра viewPort3Di: TViewPort3D. В результате функционал формы (связанный с размещением и рендерингом трехмерных моделей) перешел к области просмотра ViewPort3Dl.


114

Гпава 5

Рис. 5.9. Форма TForm3D с двухмерными и трехмерными компонентами

Стили оформления формы, компонент TStyleBook На

странице

Standard

палитры

компонентов

FMX

находится

компонент

TStyleBook, позволяющий осуществить индивидуальную настройку стиля оформле­

ния формы. Двойной щелчок по компоненту или обращение к свойству property Resource: TStrings;

активирует редактор стиля. В самом простейшем случае можно воспользоваться уже подготовленными стилями оформления. Для этого достаточно в редакторе щелкнуть по кнопке Load... и загрузить один из файлов с расширением имени style из каталога C:\Users\Public\Documents\RAD StudioW«\Styles, а затем нажать кнопку Apply and Close. В итоге редактор закроется, и изменения стиля применятся к ком­ поненту TStyleBook. Зам ечание

Вместе с Delphi поставляется несколько заранее подготовленных стилей оформления формы. По умолчанию файлы стилей расположены в каталоге C:\Users\Public\Documents\ RAD StudioV?.n\Styles.

При желании вы можете внести правки в правила оформления того или иного типа элемента управления, воспользовавшись утилитой Bitmap Style Designer, доступ к которой вы найдете в меню Tools среды проектирования. Теперь следует подключить компонент стилей TStyleBook к форме. Для этого дос­ таточно обратиться к свойству формы property StyleBook: TStyleBook;


Приложение FireMonkey

115

У компонента TStyieBook предусмотрено свойство property FileName: string;

которое позволяет легко изменять стилевое оформление формы даже во время ра­ боты приложения под управлением Windows, для этого в него следует передать имя файла *.style. Здесь нам понадобится лишь помощь диалога открытия файла openDiaiogi: TOpenDiaiog и кнопки, по щелчку которой этот диалог будет вызван (листинг 5.10). I Листинг 5.10. Изменение стиля оформления во время работы приложений uses System.IOUtils;

//... procedure TForml.FormCreate(Sender: TObject); const dir='C:\Users\Public\DocumentsYRAD Studio\ll.0\Styles'; begin

Forml.StyleBook:=TStyleBook.Create(Forml); if TDirectory.Exists(dir) then OpenDiaiogi.InitialDir:=dir; end; prooedure TForml.ButtonlClick(Sender: TObject); begin if OpenDiaiogi.Execute then begin

Forml.StyleBook.FileName:=OpenDialogl.FileName; Label2.Text:=TPath.GetFileName(OpenDiaiogi.FileName); end; end;

Зам ечание

В одном и том же приложении FireMonkey допускается раздельная настройка стилей оформления для каждой из форм проекта.

Подключение ресурсов и изображений Библиотека FireMonkey предоставляет программисту уникальные возможности по проектированию графического интерфейса пользователя, однако на взгляд автора ей все еще есть чему поучиться у VCL. В частности, в VCL имеется очень удобный компонент TimageList, способный выступать централизованным хранилищем для всех используемых в проекте пиктограмм. В FireMonkey пока аналогичного компо­ нента нет... Очень жаль, но и это не беда. Если в нашем приложении мы намереваемся использовать большое количество изображений, предназначающихся для повышения дружелюбности интерфейса пользователя, то стоит воспользоваться услугами редактора ресурсов проекта. С этой целью выбираем пункт меню P ro ject | Resources and Im ages, затем в по-


116

Гпава 5

явившемся на экране редакторе подключаем к приложению все необходимые кар­ тинки и нажимаем кнопку О К . В результате присоединенные пиктограммы немед­ ленно отобразятся в дереве Инспектора проекта (рис. 5.10). eh iS O ldp foj - Project Manager

© ' $ \ & ш -! Ш- Ф ! 4 * О » № ;§$ ProjectGra#l

| В | р chl9_0X.exe Ш^

|

Buikl Configurations феЬид)

® ■О Target Hatfbrms (vWi32)

I В u3 bitmaps : ЩШШЯ,

@ Resources for chl9_0I.dproj

$ § m4v.png

Щтov.png

Resource f

Й |тр 3 .р п д

Rename

igmjH.png в wav.pog igwna.png Ю wmv.png 3! :jj§ a b o u t . eas IB

Э

Properties Type

Identifier

RCDATA RCDATA

AVI

fm4v.png

ffifmov.png

RCDATA

mov

Jmp3.pr>g

RCOATA

mp3

RCDATA

mp4

^ 1 3

?Ш§тр4.рп5

Resource AVI Resource Jypei RCOATA

U m tl.p a s

Add... D; \_Books \J4reM 0ntey XES S

l^adiMLpi^foa -Proj,., Рис. 5.10. Менеджер проектов и окно подключения ресурсов к приложению

Теперь нам предстоит вновь попрактиковаться в программировании и научиться извлекать картинки из ресурса во время работы приложения (листинг 5.11). Листинг 5.11. Извлечение изображения из ресурса во время работы приложения var rs_avi, rs_mp3, ... : TResourceStream; procedure TForml.FormCreate(Sender: TObject); begin if FindResource(0, 'AVI', PChar(RT_RCDATA)) <> 0 then rs_avi:=TResourceStream.Create(0,'AVI',PChar(RT_RCDATA)); if FindResource(0, 'mp3', PChar(RT_RCDATA)) <> 0 then rs_mp3:=TResourceStream.Create(0,'mp3',PChar(RT_RCDATA));

//...

В коде примера мы создаем глобальные (доступные всем модулям приложении) потоки-ресурсы TResourceStream и загружаем в них изображения. В тот момент, когда нам потребуется вывести пиктограмму на экран или передать ее в какой-то элемент управления, достаточно просто считать ресурс из потока (листинг 5.12).


Приложение FireMonkey

117

Лиог :ir 3,12. Считываем и Imagel.Bitmap.LoadFromStream(rs_avi); Image2.Bitmap.LoadFromStream(rs_mp3);

Зам еча ние

При подключении изображения (или другого объекта) к ресурсу проекта старайтесь присваивать ему осмысленный текстовый идентификатор, такой подход существенно облегчит написание кода программы.


ГЛАВА 6

Меню приложения Без всякого преувеличения можно сказать, что меню приложения выступает наибо­ лее важной частью пользовательского интерфейса современного делового прило­ жения. По сравнению с большинством элементов управления, меню представляет одно из наиболее удачных дизайнерских решений программистов — его достоин­ ство заключается в том, что, практически не занимая места на форме, меню спо­ собно предоставить в распоряжение пользователя весь функционал программы. В FireMonkey различают три класса меню: □ главное меню, создаваемое на основе компонента TMainMenu; □ планка меню

(тмепиВаг);

□ контекстное меню

(трорирмепи).

речь идет о приложении Windows, то реализованное на основе класса главное меню размещается сразу под заголовком формы и, как правило, на самом верхнем уровне содержит пункты Ф айл, П р ав к а, О кно и П ом ощ ь. При выборе пункта верхнего уровня из него "выпадают" связанные с ним элементы нижнего уровня. В OS X поведение главного меню несколько изменяется — глав­ ное меню активного приложения сливается с меню рабочего стола операционной системы, а название первого пункта меню замещается названием исполняемого файла (рис. 6.1).

Е сли

TMainMenu

Рис. 6.1.

П о в е д е н и е гл а в н о го м е н ю в O S X

Планка тмепиВаг отличается от главного меню универсальностью и может быть размещена в любом месте формы. При желании планка меню может взять на себя функционал главного меню приложения, что в приложениях OS X позволит созда-


119

Меню приложения Щ

P r o je c t 1

ш .....Планка меню

ejw n ;О

Родактироеатъ

Помощь

Открыть

Нозый документ

'■ н j

Сохранить

*S !

^

Сохранить как

^

Настройка принтера

3

,

Параметры страницы 13

Печать

Выход

Р и с . 6.2. П о в е д е н и е пл а нки м е ню T M e n u B a r в O S X

вать интерфейс, схожий с традиционным интерфейсом Windows, в котором главное меню располагается сразу под заголовком окна (рис. 6.2). В отличие от главного меню и планки меню, контекстное меню возникает на экране только после щелчка правой кнопки мыши по связанному с ним элементу управле­ ния. Программисты стараются не перегружать этот тип меню избыточными пунк­ тами и заполняют его операциями, относящимися именно к конкретному элементу управления, которому принадлежит это меню. Несмотря на то, что для создания меню написано целых три класса, у них много общего: □ все компоненты меню предназначены для хранения управляющих элементов, создаваемых на основе класса TMenuitem; □ для контроля над элементами в распоряжении всех компонентов меню преду­ смотрены интерфейсы IltemsContainer И INativeControl, интерфейсы ИСПОЛЬзуются компонентом самостоятельно и не требуют вмешательства со стороны программиста. Компоненты меню снабжены специализированным редактором Items Designer, значительно упрощающим процесс построения меню. Для вызова редактора меню достаточно дважды щелкнуть левой кнопкой мыши по компоненту меню (рис. 6.3) или выбрать пункт Items Editor в контекстном меню компонента. Для создания пункта меню следует нажать кнопку Add Item и настроить параметры появившего­ ся элемента меню в Инспекторе объектов. Элементы меню способны обладать под­ меню, для создания которого достаточно воспользоваться кнопкой Add Child Item. При необходимости можно переупорядочить пункты меню — для этого предназна­ чены кнопки с изображениями стрелок. Для удаления лишнего пункта просто на­ жмите кнопку Delete.


Гпава 6

120

J &Р&Аакгир0шагь Ктпротть а буфйр

Вырезать0буфер Акзддоп*т буфера Иыёратв. асе

Ошт&

Рис. 6.3. Редактор меню Items Designer

Элемент меню TMenultem Вне зависимости о того, какой из компонентов меню (TMainMenu, тмепиВаг и л и трорирмепи) вы задействуете в своем приложении, в любом случае меню будет состоять из элементов, основанных на классе TMenultem. Поэтому рассмотрение по­ рядка работы с меню мы начнем с изучения их основного "строительного" блока — класса TMenultem. Внимание!

Хотя элемент меню TMenultem в состоянии самостоятельно обрабатывать событиещелчок, в наших проектах целесообразно подключать к элементу меню команду TAction. Для этих целей элемент меню вооружен свойством Action. Более подробно о командах мы поговорим в главе 7.

Как и большинство уже изученных элементов управления, пункт меню обладает з а г о л о в к о м , о п р е д е л я е м ы м свойством property Text: string;

Но по сравнению со всеми остальными компонентами Delphi, у свойства Text эле­ мента меню есть существенные особенности. Во-первых, если в зятпойог й у д м ’ введен один-единственный символ "тире" (-), то пункт меню превратится в разде­ литель. Обратите внимание на рис. 6.3. Здесь между элементами В ставить из буфера... и Выбрать всё вставлена горизонтальная черточка. Это и есть пункт ме-


Меню приложения

121

НЮ "разделитель". Он не способен нести функциональную нагрузку и реагировать

пользователя, его задача — улучшение наглядности приложения. Вторая особенность свойства Text — возможность определения клавиш-акселераторов, ускоряющих доступ пользователя к пункту меню при одновременном нажатии <АК> и клавиши-акселератора. На экране компьютера символ, соответствующий клавише-акселератору, выводится с подчеркиванием. Для назначения акселератора во время набора заголовка меню необходимо воспользоваться символом &. Ампер­ санд устанавливается перед символом, который программист предполагает сделать акселератором: "&Файл" или "&Открыть". на щ елчок

Зам ечание

Если на роль главного меню приложения был выбран компонент тмепиВаг, то клави­ ши-акселераторы использовать не следует.

Помимо акселераторов, с каждым пунктом меню можно связать так называемые "быстрые клавиши". Отличие быстрых клавиш от клавиш-акселераторов заключа­ ется в том,' что выбор акселератора только позволит добраться до необходимого пункта меню, а нажатие комбинации быстрых клавиш заставит приложение выпол­ нить сопоставленный с ними пункт меню. Быстрые клавиши определяются свой­ ством property Shortcut : TShortCut;

С каждым элементом меню могут быть сопоставлены небольшие картинки, даю­ щие визуальное пояснение о функциональном назначении того или иного элемента. Картинка загружается в свойство property Bitmap: TBitmap;

Оформление текстовой надписи опирается на свойства и методы, унаследованные ОТ класса TTextControl. Элемент меню может быть выделен программным способом или вручную, инфор­ мация об этом хранится в свойстве property IsSelected: Boolean;

Основное назначение пункта меню — среагировать на щелчок пользователя, в этом он практически ничем не отличается от обычных кнопок. Соответственно ничем не отличается и ключевой для элемента TMenuitem обработчик события property OnClick: TNotifyEvent;

Элемент меню в виде флажка При необходимости отдельный пункт меню можно превратить в элемент управле­ ния, напоминающий флажок (тсьесквох) или кнопку выбора (TRadioButton). Таким чудесным возможностям TMenuitem в первую очередь обязан свойству property IsChecked: Boolean;

Установив это поле в состояние true, мы увидим "галочку" слева от заголовка. Этим самым пункт меню сигнализирует нам, что он отмечен. Контролируя это


Глава 6

122

свойство (листинг 6.1), программист получит превосходную возможность приме­ нять конструкцию if..then..else В обработчике события OnClick (). Листинг 6 1 I procedure TForml.MenuItemlClick(Sender: TObject); begin if Menulteml.IsChecked=true then ...

//операция 1

else ...; //операция 2 end;

Для того чтобы пункт меню при щелчке по нему автоматически помечался галоч­ кой, следует перевести в true свойство: property AutoCheck: Boolean;

в противном случае придется делать это вручную внутри события OnClick о . В про­ стейшем случае это будет всего-навсего одна строка кода Menulteml.IsChecked:= NOT Menulteml.IsChecked;

инвертирующая предыдущее состояние пункта меню.

Группировка элементов меню Вторым действием по превращению элементов меню в переключатели станет уста­ новка (у всех входящих в группу элементов) свойства property Radioitem: Boolean;//по умолчанию false

в состояние true. Несколько элементов меню не сложно объединить в группу, превратив ее элементы в переключатели (по поведению повторяющие кнопки TRadioButton). Для того чтобы Научиться превращать несколько пунктов меню в группу выбора, рассмотрим крохотный пример (рис. 6.4). Создайте новый проект, разместите на нем компонент TMainMenu. Создайте пункт меню верхнего уровня с заголовком "Цвет формы" и четыре подчиненных ему пункта: □ элемент с заголовком "Белый". Имя элемента — miwhite, свойство tag=o; □ элемент "Красный", tag=i; □

элемент "Зелёный", tag=2;

□ элемент "Синий", tag=3. Наша задача— при щелчке по пункту меню перекрашивать фигуру Rectangiel: TRectangie в соответствующий цвет. Теперь одновременно выделите все четыре ТОЛЬКО ЧТО созданных пункта меню и в Инспекторе объектов найдите свойство property Grouplndex: Byte;


123

Меню приложения

Рис. 6.4. Группа выбора из элементов меню

Это свойство предназначено для создания логических групп пунктов меню. По умолчанию, каждый вновь создаваемый элемент TMenultem не входит ни в одну группу (Groupindex=o), но если атрибуту Groupindex двух (или более) пунктов меню присвоить отличное от нуля значение, то мы получим возможность объединять элементы в некоторые товарищества, что сейчас собственно и сделаем. Введите в это свойство любое положительное число, например 1. Следующим шагом по превращению четырех пунктов меню в группу выбора будет установка в true свойства: property Radioitem: Boolean;

Перевод свойства Radioitem в значение true приведет к тому, что пункт меню ста­ нет вести себя аналогично компоненту TRadioButton. Для того чтобы выяснить, не отмечен ли элемент меню флажком, надо обратиться к уже знакомому нам свойст­ ву isChecked, a Delphi позаботится о том, чтобы в одной группе не могло быть по­ мечено более одного элемента TMenultem одновременно. И в завершении убедитесь, что свойство AutoCheck всех четырех элементов установлено в true. Нам осталось описать щелчок по любому из пунктов меню, например по miwhite (листинг 6.2), и сделать это событие общим для всех оставшихся пунктов меню, входящих в группу выбора. Л:!ст::кг 6.2. Работе с меню г: режиме

ki:o;;o:: в>iGopa

.... ....... .............................

procedure TForml.miWhiteClick(Sender: TObject); begin case TMenultem(Sender).Tag of 0:Rectanglel.Fill.Color:=TAlphaColorRec.White; 1:Rectangle^.Fill.Color:=TAlphaColorRec.Red; 2:Rectanglel.Fill.Color:=TAlphaColorRec.Green; 3:Rectanglel.Fill.Color:=TAlphaColorRec.Blue; end; end;

//белый //красный //зеленый //синий

...


Гпава 6

124

Доступ к дочерним элементам меню Любой элемент меню имеет возможность обладать дочерними элементами. Для программного доступа к этим элементам следует воспользоваться свойством property Children[Index: Integer]: TEtaxObject; //только для чтения

единственный параметр метода определяет индекс интересующего нас пункта меню (в диапазоне значений от 0 до chiidrencount-i). Внимание!

Все рассматриваемые в главе компоненты также способны обратиться к дочерним элементам меню с помощью свойства Children.

Главное меню TMainMenu Главное меню приложения класса TMainMenu специализируется на хранении и пре­ доставлении доступа к элементарным пунктам меню (реализуемым из класса TMenuItem).

Обычно меню формируется программистом на этапе визуального проектирования, однако никто не запрещает нам создавать пункты меню в динамическом режиме. В последнем случае из кода программы следует вызвать метод procedure AddObject(AObject: TFmxObject);

Планка меню TMenuBar В библиотеке FireMonkey планка меню тмепиваг выступает своего рода усиленной версией главного (и не только главного) меню приложения. Ключевое отличие планки меню тмепиваг от его коллеги — компонента TMainMenu в том, что планка может быть размещена в любом месте формы, она может перемещаться и менять размеры. Главное меню TMainMenu такими возможностями похвастаться не в со­ стоянии. Зам ечание

В Delphi ХЕ2/ХЕЗ планка меню была способна легко принять "обличив" главного меню приложения с учетом особенностей поведения и отображения меню текущей опера­ ционной системы, для этого требовалось передать значение true в свойство UseOSMenu. Начиная с Delphi ХЕ4, у TMenuBar свойство UseOSMenu исчезло...

Контекстное меню ТРорирМепи Существенная часть построенных на основе класса TControl визуальных элементов управления обладает правом показа контекстного (всплывающего) меню. Для этого элементы оснащены свойством, предназначенным для подключения к н им к о м п о ­ нента ТРорирМепи property РорирМепи: TCustomPopupMenu;


125

Меню приложения

После подключения контекстное меню автоматически вызывается на экран в м о мент щ елчка правой кнопкой мыши по элементу управления. Если у элемента управления не предусмотрено свойство PopupMenu, то это не явля­ ется препятствием для работы с компонентом TPopupMenu. В данном случае про­ граммисту следует взять управление процессом вызова меню в свои руки и вос­ пользоваться методом prooedure Popup(X, Y: Single);

Единственное, о чем следует позаботиться— это указать экранные координаты (х, y) вывода меню (листинг 6.3). СП

'3.3.

*.

Конт ;с-.:юг© м з н ю из м щ а программы

procedure TForml.FormMouseDown(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Single); var P:TPointF; begin if Button=TMouseButton.mbRight then begin

P:=Forml.ClientToScreen(PointF(X,Y)); PopupMenu1.Popup(P .X,P.Y); end; end;

Наличие метода, отображающего контекстное меню, предполагает и существование метода-антипода procedure CloseMenu;

закрывающего меню.


ГЛАВА 7

Командный интерфейс Пользовательский интерфейс хорошо продуманного приложения позволит пользо­ вателю компьютера прийти к одному и тому же результату абсолютно разными пу­ тями. Например, для сохранения проекта Delphi программист может остановить свой выбор, по крайней мере, на одном из трех возможных вариантов действий: выбрать пункт главного меню File | Save All; щелкнуть по соответствующей кнопке на панели инструментов; нажать сопоставленную операции сохранения проекта комбинацию быстрых клавиш <Shift>+<Ctrl>+<Alt>. Наличие трех способов со­ хранения проекта не означает, что создатели IDE Delphi во время написания среды проектирования трижды повторили одни и те же строки кода. Такое решение было бы нелепым. На самом деле для сохранения проекта была создана однаединственная процедура, вызов которой осуществляется несколькими способами. В простейшем случае программистам Embarcadero достаточно было описать щел­ чок по пункту меню и затем подключить к этому событию все остальные элементы управления. Обратите внимание на еще одну особенность поведения элементов управления ви­ зуальной среды проектирования Delphi. После полного сохранения проекта отвеЧЗЮЩИЙ Ш ЭТу Операцию элемент меню И кнопка элементов у п р а в л е н и я п е р е с т а ю т быть активными — это свидетельствует о том, что все изменения в исходном коде успешно отправлены на жесткий диск компьютера. Но как только программист внесет малейшее исправление в листинг программы, соответствующие элементы управления вновь перейдут в режим готовности выполнить свою задачу — сохра­ нить изменения в коде. Элементы пользовательского интерфейса современного приложения должны не ту И Л И иную процедуру, НО И умеТЬ ОТрЯжать текущую обстановку. Для достижения поставленной цели в состав FireMonkey были введены командные объекты. В настоящей главе мы познакомимся с наибо­ лее распространенной реализацией команды — классом TAction.

просто обладать с п о с о бностью в ы з ывать

Командный объект TAction создается и хранится в специализированном контейне­ ре, в роли которого имеет право выступать список команд TActionList. СПИСОК команд TActionList обычно задействуется в приложениях, интерфейс которых


127

Командный интерфейс

строится на основе обычных компонентов меню (TMainMenu и TPopupMenu) и стан­ дартных элементах управления (кнопок TButton).

Команда TAction Наиболее востребованный тип командного объекта создается на основе класса TAction. Кроме решения основной задачи, поставленной перед командой (открытие файла, сохранение данных, вызов метода и т. п.), команда выполняет ряд дополни­ тельных заданий: □ централизация управляющего кода в рамках события OnExecute (); □ предоставление связанным с командой элементам управления права вызова управляющего кода; □ установка подключенных к команде элементов управления в актуальное состоя­ ние. Для того чтобы сразу ощутить все преимущества, которые приобретает приложе­ ние, разработанное на основе командных объектов, рассмотрим небольшой пример HD. Создайте новый проект и на его главной форме расположите следующие эле­ менты управления: □

СПИСОК

команд TActionList;

□ главное меню TMainMenu; □ контекстное меню TPopupMenu; □ три кнопки любого типа, например TButton; □ многострочный редактор тмешо. Наша задача— написать код, позволяющий копировать (или вырезать) текст из многострочного редактора в буфер обмена и вставлять текст из буфера в редактор. Дважды щелкните по менеджеру команд и трижды нажмите кнопку New Action в появившемся редакторе. Настройте свойства тройки новых команд: □

Name='асСору'; Caption='Копировать'; ShortCut='Ctrl+C';

□ Name='acCut'; Caption='Вырезать'; ShortCut='Ctrl+X'; □ Name='acPaste'; Caption='Вставить'; ShortCut=1Ctrl+V'.

Мы подошли к самому главному — опишем управляющий код в событиях OnExecute () каждой из команд (листинг 7.1). I Д и а т и к г 2.#.

полнея

оком нд, вобьине б№:

nstet)

procedure TForml.acCopyExecute(Sender: TObject); begin Memol.CopyToClipboard; //копируем текст в буфер end;


Гпава 7

128 procedure TForml.acCutExecute(Sender: TObject); begin

Memol.CutToClipboard; //вырезаем текст в буфер

procedure TForml.acPasteExecute(Sender: TObject); begin

Memol.PasteFromClipboard; //забираем текст из буфера end;

Создайте три пустых элемента в главном и всплывающем меню проекта и, восполь­ зовавшись свойством Action, подключите пункты меню к командам. То же самое сделайте и с кнопками. Сразу после установления связи между командой и пунктом меню с последним происходят чудесные метаморфозы. Заголовок и всплывающая подсказка переключаются на показ соответствующих свойств ассоциированного командного объекта (рис. 7.1). Но самое главное в том, что с этого момента щелчок по пункту меню и кнопке станет вызывать метод onExecuteO связанной с ними команды. Таким образом, вместо "размазывания" управляющего кода по многочис­ ленным элементам управления пользовательского интерфейса нашего приложения он сосредотачивается в руках командного объекта, а все подключенные к нему элементы управления получают право на вызов этого кода. Зам ечание

Командные объекты могут быть легко подключены к большинству элементов управле­ ния, необходимым условием подключения является наличие у элемента управления (пункта меню, кнопки) свойства Action.

сайл

Редактировать

Копировать

Помощь

вырешге

C p e n D t a 'o g l

вставить

Sa Categarjfss

Act ;:rs {r. K):

{No Category)

Файл (Ж Actions}

О РадеЗйирОШод!

Printe

acPaste acCut acSdectAI

scUndo

Рис. 7.1. Список команд TActionList с командами


Командный интерфейс

129

Нам осталось сделать последний важный штрих — научить наши команды оцени­ вать окружающую обстановку. Например, команды копирования и вырезки текста в буфер обмена не могут быть активны до тех пор, пока в текстовом редакторе не выделено ни одного символа. Команда вставки текста из буфера обмена в поле ре­ дактирования должна активироваться только в том случае, если в буфере находится текстовая строка. Для установки командных объектов в актуальное состояние предназначены их события onupdate (листинг 7.2).

uses FMX.Platform; //модуль для работы интерфейса IFMXClipboardService

И ... procedure TForml.acCopyUpdate(Sender: TObject); begin

acCopy.Enabled:=(Memol.SelLength>0);

procedure TForml.acCutUpdate(Sender: TObject); begin

acCut.Enabled:=(Memol.SelLength>0);

procedure TForml.acPasteUpdate(Sender: TObject); var ClipService: IFMXClipboardService;

s: string; begin if TPlatformServices.Current.SupportsPlatformService(

IFMXClipboardService, Ilnterface(ClipService)) then begin

s :=ClipService.GetClipboard.ToString; acPaste.Enabled:=s.Length>0; end else acPaste.Enabled:=false;

Демонстрационный проект готов. Запустите приложение и проверьте поведение пунктов меню и кнопок.

Связь с элементом управления Теперь мы знаем, что любой командный объект умеет взаимодействовать со стан­ дартными элементами управления, берущими свое начало от класса TControi. В первую очередь к таким элементам относятся пункты меню TMenultem и различ­ ного рода кнопки. Внешним признаком того, что элемент управления не прочь связать свою судьбу с командным объектом, служит наличие свойства property Action : TBasicAction;


130

Гпава 7

Свойство доступно в Инспекторе объектов, так что для подключения элемента управления к команде программисту достаточно пару раз щелкнуть кнопкой мыши. Визуально появление связи отразится на свойствах Text, Checked, Enabled, HelpContext, Hint, shortcut и visible нашего элемента управления. Содержимое перечисленных полей обновится в соответствии со значениями в аналогичных свойствах командного объекта TAction. Но самое главное, что с этого момента эле­ мент управления получит право на вызов метода Execute () команды. Как правило, для этого автоматически переопределяется обработчик события onciicko. Так что теперь щелчок по кнопке или пункту меню вызовет событие OnExecute () командно­ го объекта.

Выполнение команды Ключевым методом любого командного объекта выступает функция function Execute : Boolean;

Метод вызывается автоматически в момент щелчка по элементу управления, свя­ занному с командой. Выполнение команды сопровождается каскадом весьма схо­ жих по функциональной нагрузке событий у имеющих отношение к командному объекту элементов управления (табл. 7.1). Таблица 7.1. Генерирование событий при выполнении команды Источник

Событие

TActionList

property

Описание

OnExecute TAct io nE v e n t;

type T A c t i o n E v e n t procedure ( A c t i o n :

Событие генерируется у списка или менед­ жера команд в зависимости от того, кому из них принадлежит команда.

:

=

TBa s i c A c t i o n ;

var

Handled:

Boolean)

object;

TAction

property

OnExecute TNo t i f y E v e n t ;

:

of

В первом параметре A c t i o n окажется ссыл­ ка на команду, чей метод E x e c u t e () сейчас вызывается. Манипулируя параметром Handled, мы сможем разрешить (false) или запретить (true) дальнейшее выполнение команды Событие произойдет, если команда не была обработана в двух предыдущих событиях

Установка команды в актуальное состояние В перечне событий команды второе по значимости место занимает событие обнов­ ления статуса команды p ro p e rty

OnUpdate

: TNotifyEvent;

Благодаря O n U p d a t e () мы сможем установить команду (и, конечно же, связанные с ней элементы управления) в актуальное, отвечающее текущей обстановке состоя­ ние. Важно знать, что событие обновления вызывается именно в тот момент, когда связанный с командой элемент готовится к выводу на экран. Благодаря этому


Командный интерфейс

131

команда способна контролировать текущую обстановку с минимальным расходом системных ресурсов, такое решение называется обновлением по т ребованию (update-on-demand approach).

Связь команды с контейнером Командный объект должен храниться в специальном компоненте-контейнере TActionList. Для идентификации контейнера, которому принадлежит команда, предназначено свойство property

ActionList

: TCustomActionList;

Индекс команды в списке контейнера находится в свойстве property I n d e x : I n t e g e r ;

Исключительно для удобства программиста во время визуального проектирования команды могут группироваться по категориям. Название категории присваивается свойству property C a t e g o r y : S t r i n g ;

Анализируя хранящиеся в этом свойстве текстовые данные, контейнер T A c t i o n L i s t способен быстро отфильтровать команды определенной категории, тем самым зна­ чительно упростив поиск требуемой команды.

Предопределенные команды Чтобы командный интерфейс оказался еще более удобным, разработчики FireMonkey предоставили в наше распоряжение целую коллекцию предопределен­ ных команд, благодаря применению которых существенно сокращается время и упрощается процесс разработки приложения. Для того чтобы быстро научиться за­ действовать предопределенные команды, предлагаем написать небольшой пример. Создайте мобильное приложение для iPhone и разместите на рабочей форме сле­ дующие элементы управления: □

СПИСОК

команд

TActionList;

□ контейнер для изображений □ кнопку

Timage;

TButton.

Этих компонентов вполне достаточно для создания простейшего приложения, по­ зволяющего пользователю делать фотографии. Выберите кнопку и в Инспекторе объектов найдите свойство A c t i o n . Разверните комбинированный список и, пройдя по цепочке New Standard Action | Media Library, выберите предопределенную команду T T a k e P h o t o F r o m C a m e r a A c t i o n (рис. 7.2).

В окне Инспектора объектов перейдите на вкладку Events и найдите событие O n D i d F i n i s h T a k i n g () для только что созданного командного объекта (рис. 7.3).


132

Гпава 7

Object inspector Button! mutton |Properties jEvents; Align

ftctjanurti

jNew Action

(Mo Category)

AutoTr^^WJlnie Cancel

*1 ►I

Ed*

jfO False

Carrocus |BE)True

Window

*f

CanParentf: §JjFatee F ile

ОрОШгепI|jQFabe

►i

Tab

OpParent H F a s e Cursor ;crDefault Default ;0Fafce DesgnVsfc# [#]True

>r *J_^вЁШИШИшНШШЩШШЩЭ

V ie w M e d fa

►!

Library

LfveBindings

DisableFoaJ!Щ False

Mi

TTakePhctoFromLibraryAction rTi-.Pl’io^cFfomC ;mersAction

TSilowSh^r^SheetActiOfB

Bind Visuafiy...

Рис. 7.2. Использование предопределенной команды TTakePhotoFromCameraAction Object inspector Button 1 TButton P r o p e r tie s |E v e n t s j

ITakePhotoFnxnCameraActionl UvefSndrsgs OnCmActionExec DrOdCar-сеГГalong

jLivHSndings

i

OrOdFrtshTalong

OnUpdate ffl UveHndrigs OnApplyStyieLockup OnCariFocus GnCkk OnCWCkk CnOragDrop OnOragEnd

!

■; й г

Щ

iM

jlweKndkigs Рис. 7.3. Выбор события OnDidFinishTaking()

Bindvaualy,.. OnOadFnashTaking

Aishown

Двойной щелчок по строке события перенесет нас в редактор кода, в котором нам придется написать всего одну строку (листинг 7.3). j Листинг 7.3. Обработка команды получения фотографим procedure TForml.TakePhotoFromCameraActionlDidFinishTaking(Image: TBitmap); begin Imagel.Bitmap.Assign(Image); end;


133

Командный интерфейс

Как видите, за счет предопределенных командных объектов можно весьма уско­ рить процесс разработки приложений в Delphi.

Список команд TActionList Список команд T A c t i o n L i s t — невизуальный элемент управления, который вы най­ дете на страничке Standard палитры компонентов. Компонент обычно используют при проектировании приложений малой степени сложности с незначительным чис­ лом командных объектов и построенных на основе обычных компонентов меню и стандартных кнопок. Все свойства, методы и обработчики событий списка команд унаследованы от родительского класса T C u s t o m A c t i o n L i s t и поэтому нам уже хоро­ шо знакомы. По умолчанию список команд A c t i o n s пуст. Для заполнения списка командами дважды щелкните по компоненту левой кнопкой мыши. В результате такого дейст­ вия на экран компьютера выводится окно специализированного редактора (см. рис. 7.1). Для добавления команды T A c t i o n следует вызвать пункт New Action, а для добавления стандартных команд из заранее подготовленного перечня выбираем пункт New Standard Action..., Команды могут группироваться по определяемым программистом категориям. Выбор в левом списке редактора той или иной катего­ рии отфильтрует только команды указанной группы. При желании можно отказать­ ся от фильтрации команд, для этого в списке категорий Categories надо выбрать строку All Actions. Зам ечание

Список команд

TActionList

(TMainMenu, T M e n u B a r

пример кнопками

и

предназначен для совместной работы с обычными меню и со стандартными элементами управления, на­

TPopupMenu)

TButton.

Число хранимых в списке команд выясняется благодаря свойству property A c t i o n C o u n t

: Integer;

Доступ к коллекции команд обеспечивает свойство property A c t i o n s [ I n d e x :

Integer]:

TContainedAction;

Обращение к отдельной команде производится по ее индивидуальному индексу (начиная от 0 и заканчивая A c t i o n C o u n t - l ) . Список обладает некоторым перечнем прав по отношению к своим подопечным. Например, он способен отдавать приказ на выполнение принадлежащей ему команды. function E x e c u t e A c t i o n ( A c t i o n : T B a s i c A c t i o n ) : B o o l e a n ;

Выполнение метода приведет к генерации события O n E x e c u t e (). Функция вернет t r u e в случае, если в рамках вызванного обработчика события команда была успешно отработана. Ко всему прочему, список способен запретить всем принадлежащим ему командам реагировать на запросц пользователя. Для этого предназначено свойство


134

Гпава 7

property State : TActionListState;//по умолчанию asNormal

type TActionListState = (asNormal, asSuspended, asSuspendedEnabled);

В момент создания свойству присваивается значение asNormal. Это нормальное со­ стояние, разрешающее содержащимся в контейнере командам реагировать на дей­ ствия пользователя. В состоянии asSuspended и asSuspendedEnabled все принадле­ жащие элементу управления команды перестают выполнять запросы пользователя. Разница между последними двумя состояниями заключается в оказываемом влия­ нии на СВОЙСТВО Enabled компонентов TAction. Так перевод TActionList в состояние asSuspended ИЛИ asSuspendedEnabled просто запрещает компонентам TAction ВЫПОЛ­ НЯТЬ запросы пользователя. В дополнение к этому состояние asSuspendedEnabled устанавливает все свойства команд Enabled в true. Ключевые события TActionList перечислены в табл. 7.2. Таблица 7.2. Основные события

T A

c t i o n L i s t

Событие

Описание

property OnExecute: TActionEvent; type TActionEvent = procedure (Action: TBasicAction; var Handled: Boolean) of object;

Событие генерируется у списка (или менеджера) команд при обращении к команде. Параметр Action хранит ссылку на команду. Параметр Handled позво­ ляет прервать выполнение команды

property OnChange: TNotifyEvent;

Генерируется при любом изменении в списке команд

property OnStateChange:

Вызывается сразу за событием OnChange (), уведом­ ляет об изменении состояния менеджера

TNotifyEvent;

property OnUpdate: TActionEvent;

Событие для централизованной установки всех команд в актуальное состояние. Генерируется во время простоя приложения. В его рамках програм­ мист переводит команды в состояние, соответствую­ щее сложившейся обстановке -


ГЛАВА 8

Управление папками и файлами Одна из самых распространенных задач, с которой рано или поздно сталкивается разработчик программного обеспечения, — это управление файлами и каталогами компьютера. Основная трудность этой области программирования связана с высо­ кой вероятностью возникновения различного рода исключительных ситуаций (вы­ званных некорректным совместным использованием файлов и папок, ограничением свободного пространства, некорректным доступом к ресурсу, ошибками операций чтения и записи и т. п.). Понимая, что для начинающего программиста задача рабо­ ты с файлами и папками может оказаться неразрешимой, создатели Delphi реализо­ вали очень удобный модуль System, ioutiis. В нем расположились кроссплатформенные классы TDirectory, TFile и TPath, специализирующиеся на управлении пап­ ками и файлами. Зам ечание

Основные функции кроссплатформенной среды разработки Delphi, связанные с управ­ лением файлами, каталогами и дисками, сосредоточены в модуле System, ioutiis (см. приложение 3).

Работа с дисками При изучении файловой системы компьютера с Windows первое, что нас может за­ интересовать, — это состав логических дисков на компьютере. Предусмотрено не­ сколько способов сбора этих сведений. Наиболее простой заключается в задейство­ вании возможностей интеллектуальной записи TDirectory. Среди многочисленных методов записи имеется функция класса class function GetLogicalDrives: TStringDynArray;

создающая динамический массив строк, заполненный именами дисков в формате: <буква_диска;-. <символ_разделитель>. Меньше десяти строк кода (листинг 8.1) по­ зволят передать названия дисков в комбинированный список.


136

Гпава 8

j Листинг 8.1. Получение перечня Логических дисков с помощью ro ire c to ry - .......

.. -. .

.

.

.

,

. . ч^. ...-.,............. .......................................... . . .. ...... ..^.......................................

........................

..............................

.............

..................

uses System.IOUtils, System.Types; procedure TForml.ButtonlClick(Sender: TObject); var SDA:TStringDynArray;

i :integer; begin

ComboBoxl.Items.Clear; SDA:=TDirectory.GetLogicalDrives; for i:=0 to High(SDA) do ComboBoxl.Items.Add(SDA[i]); end;

Полезные сведения о размере диска и его свободном пространстве возвращают функции function DiskSize(Drive: Byte): Int64; function DiskFree(Drive: Byte): Int64;

Параметр Drive требует передачи номера накопителя. Например: 0 — текущий, 1 — А:, 2 — В:, 3 — С: и т. д. Если метод не в состоянии выяснить размер диска, то он возвратит -1 .

Сбор сведений о каталогах и файлах У описанной в модуле ioutils записи 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— сбор сведений только В текущем каталоге, soAHDirectories —


Управление папками и файлами

137

сбор сведений во всех вложенных каталогах). В результате выполнения собранные сведения помещаются в динамический массив строк (листинг 8.2).

var

SDA:TStringDynArray; i:integer;

begin S D A : = T D i r e c t o r y . G e t F i l e S y s t e i r i E n t r i e s ( ' C : \ ',

for i:=0 to High(SDA) do ListBoxl.Items.Add(SDA[i]);

Проверка существования файла и каталога Для того чтобы сразу отсечь достаточно большой объем ошибок, перед обращени­ ем к файлу или каталогу всегда проверяйте факт их существования. При работе с файлом задействуем метод class function

E x i s t s (const P a t h : FollowLink:

принадлежащий

string; B oole an = T r u e ) : Boolean;

TFiie.

Для проверки наличия папки вспоминаем о записи арсенале одноименной функции class function

E x i s t s (const P a t h : FollowLink:

Функции возвращают

true,

TDirectory

string; B o o l e a n = T r u e ) : Boolean;

если файл (каталог) действительно существует.

Для полноты картины упомянем имеющийся в распоряжении class function

и имеющейся в ее

D r i v e E x i s t s (const Path:

string):

проверяющий факт существования диска с именем

TPath

метод

Boolean;

Path.

Расположение системных каталогов Дополнительные сведения о размещении каталогов сможет предоставить специали­ зирующая на обслуживании имен файлов и папок запись TPath (модуль ioutiis). В арсенале T P a t h имеется метод class function

GetHomePath:

string;

готовый поделиться сведениями о папке с данными приложений. Еще одна тайна, которой владеет менных файлов class function

GetTempPath:

TPath,

string;

связана с расположением папки для вре­


138

Гпава 8

Путь к каталогу с документами откроет метод class function GetDocumentsPath: string;

Упомянем важное понятие "текущий к а т а л о г " — это каталог, в котором произво­ дятся текущие операции ввода/вывода. Для работы с текущим каталогом предна­ значены м еТ О Д Ы TDirectory: class function GetCurrentDir: string; class function SetCurrentDir (const Dir: string) : Boolean;

позволяющие выяснить, какой из каталогов является текущим, и назначить новый текущий каталог.

Создание, удаление, копирование и перемещение В FireMonkey основным специалистом по созданию и удалению папок считается TDirectory. Так, для создания новой папки следует вызвать метод class procedure CreateDirectory(Path: string);

За процесс копирования папки отвечает процедура class procedure Copy (const SourceDirName, DestDirName: string);

Здесь SourceDirName — путь к копируемой папке, DestDirName местоположение по­ лучателя. Близкий по духу метод class procedure Move (const SourceDirName, DestDirName: string);

осуществит перенос папки в новое место. Класс TDirectory обладает методом class procedure Delete(const Path: string; const Recursive: Boolean);

решающим задачу удаления папки со всеми вложенными элементами. Для активи­ зации рекурсивного удаления надо не забыть установить в состояние true параметр Recursive.

Когда речь заходит об управлении файлами, то на сцену выходит класс TFiie. Ко­ пирование файла осуществляет метод class procedure Copy (const SourceFileName, DestFileName: string);

Перенос файла на новое место выполнит метод class procedure-Move(SourceFileName, DestFileName: string);

Для замены одного файла другим с одновременным созданием резервной копии задействуйте метод class procedure Replace(const SourceFileName,

DestinationFileName, DestinationBackupFileName: string) ;


Управление папками и файлами

139

Наконец, самое разрушительное оружие класса TFile class procedure Delete(const Path: string);

удалит файл.

Запись в файл и чтение из файла Создание нового файла средствами TFile — это уже многоходовая комбинация, которая может стартовать с вызова конструктора class function Create (const Path: string): TFileStream;

создающего доступный для записи файловый поток TFileStream. После этого все остальные действия над файлом возлагаются на экземпляр TFileStream. Например, представленный в листинге 8.3 код демонстрирует процесс сохранения в файл с именем FileName координат левого верхнего угла главной формы приложения. яшшЯШМШШШШШЫтнШшЯ/ШШШШшШЯШШЯШШШшШшИЙШИШШШШНтяяЯШтШшШШт...

:

procedure TForml.SaveToFile(FileName: String); var fs:TFileStream;

Buf:integer; begin if TFile.Exists(FileName) then TFile.Delete(FileName);

fs:=TFile.Create(FileName); try

Buf:=Application.MainForm.Left; fs .Write (Buf, SizeOf (Buf) ); Buf:=Application.MainForm.Top; fs.Write(Buf,SizeOf(Buf)); finally

fs.Free; end; end;

Обратная задача, связанная с чтением данных из файла, также может быть решена непосредственно силами класса TFile и файлового потока TFileStream так, как это предложено в листинге 8.4.

procedure TForml.LoadFromFile(FileName: String); var fs:TFileStream;

Buf:integer; begin if TFile.Exists(FileName) then begin

fs:=TFile.Open(FileName,TFileMode.fmOpen);


140

Гпава 8 try

fs.Read(Buf,SizeOf(Buf)); Application.MainForm.Left:=Buf; fs.Read(Buf,SizeOf(Buf)); Application.MainForm.Top:=Buf; finally

fs.Free; end; end;

На этот раз нам помог метод класса class function Open (const Path: string; const Mode: TFileMode): TFileStream;

позволяющий загружать файл с именем Path в поток TFileStream. Помимо умения осуществлять операции чтения и записи при посредничестве фай­ ловых потоков класс TFiie способен работать с текстовыми и бинарными файлами опираясь только на свои силы. Например, для чтения всего содержимого текстово­ го файла проще всего обратиться к функции class function ReadAllLines(const Path: string): TStringDynArray;

Результат будет возвращен

в динамический строковый массив

в формате

TStringDynArray.

Наиболее универсальный метод чтения class function ReadAllBytes(const Path: string): TBytes;

загрузит любой файл в массив байт TBytes. Для записи данных в файл проще всего воспользоваться методами class procedure WriteAllLines(const Path: string; const Contents: TStringDynArray); class procedure WriteAllBytes(const Path: string; const Bytes: TBytes);

первый метод нацелен на работу исключительно с текстовыми данными, второй всеяден и в качестве источника данных воспринимает массив байтов.

Атрибуты файла и каталога В современном языке программирования Delphi основными специалистами по ра­ боте С атрибутами файла И ПаПКИ СЧИТаЮТСЯ КЛаССЫ TFiie, TDirectory И TPath.

У всех перечисленных классов предусмотрены одинаковые методы class function GetAttributes(const Path: string;

FollowLink: Boolean = True): TFileAttributes; class procedure SetAttributes(const Path: string; const Attributes: TFileAttributes);


Управление папками и файлами

141

Стоит особо отметить, что перечень атрибутов TFileAttributes определяется опе­ рационной системой и различается у Windows и OS X.

Дата и время создания файла и каталога Самый простой способ для знакомства с возрастом файла заключается в использо­ вании возможностей класса TFiie, а если речь идет о каталогах, то нашим помощ­ ником станет TDirectory. Оба класса располагают набором одноименных методов. В частности, за выяснение даты и времени создания файла (папки) отвечает метод class function GetCreationTime(const Path: string): TDateTime

Дату/время последнего обращения к файлу (папке) знает метод class function GetLastAccessTime(const Path: string) : TDateTime;

О том, когда произошли последние изменения, все известно методу class function GetLastWriteTime(const Path: string): TDateTime;

У только что рассмотренных методов есть коллеги, решающие обратную задачу по изменению атрибутов файла (папки), связанных с датой и временем. class procedure SetCreationTime(const Path: string; const CreationTime: TDateTime); class procedure SetLastAccessTime(const Path: string; const LastAccessTime: TDateTime); class procedure SetLastWriteTime(const Path: string; const LastWriteTime: TDateTime);

На этот раз, кроме параметра Path (в который следует направить путь и имя файла), в процедурах имеется второй параметр типа TDateTime, в который в зависимости от назначения функции следует направить дату/время создания, дату/время последне­ го доступа или дату/время последнего изменения.


ГЛАВА 9

Компоненты для работы с текстом Сложно привести пример современного приложения, которое было бы способно обойтись без услуг компонентов отображения и редактирования текстовых данных, ведь область применения текстовых данных практически необъятна. Она начинает­ ся с простейших окон регистрации пользователя и форм ввода и заканчивается сложными текстовыми редакторами. Библиотека FireMonkey предоставляет широчайший набор компонентов, специали­ зирующихся на отображении и обработке текстовых данных. Базовым элементом управления, способным выводить на экран пояснительные надписи, выступает мет­ ка TLabei. Список предназначенных для редактирования текста компонентов воз­ главляют строка ввода TEdit и строка с возможностью быстрой очистки текста TciearingEdit. Эти элементы управления позволят пользователю ввести простей­ шую строку текста. Элементы управления TNumberBox, TSpinBox и тсотьотгаскваг представляют различного рода вариации на тему ввода числовых значений. Еще один герой этой главы — многострочный текстовый редактор тмето, компонент способен хранить и обрабатывать неформатированные текстовые строки. Иерархия классов, на основе которых созданы рассматриваемые в главе компонен­ ты, предложена на рис. 9.1. Зам ечание

У всех компонентов (за исключением TLabei) предусмотрена возможность обращения к интерфейсам iTextServiceControl и IVirtualKeyboardControl. Первый из ин­ терфейсов осуществляет поддержку всех базовых операций с текстовой областью данных, а второй интерфейс разрешает элементу управления взаимодействовать с сенсорной клавиатурой, применяемой в iOS.

З амечание Концептуально компонент TComboEdit ближе к комбинированным спискам, чем к стро­ кам ввода, поэтому он будет рассмотрен в главе 10.


Компоненты для работы с текстом

143

TControi (FMX.Controls)

TStyledControl (FMX.Controls)

TTextControl (FMX.StdCtrls)

TCustomEdit (FMX.StdCtrls)

TScrollBox (FMX. Layouts)

TLabal (FMX.StdCtrls) Метка

Многострочный редактор TCustomEditBox (FMX. Edit)

TComboEditBase (FMX. Edit)

TComboEdit (FMX. Edit)

TComboTrack-\\ t f „ „ '( TNumberBox Bar (FMX. Edit) (FMX. Edit) J \I \\

Комбинированные строки ввода

TSpinBox (FMX.Edit)

Строки выбора значения

Рис. 9.1. Иерархия текстовых компонентов

Класс TTextControl Класс TTextControl выступает системообразующим классом по отношению к суще­ ственному количеству элементов управления, отвечающих за вывод текстовых надписей. Особо отметим, что, как правило, потомки TTextControl не наделяют пользователя правом редактирования текста. Поэтому такие элементы управления (в частности метка TLabei) специализируются на выводе пояснительных надписей. Для работы с потомками TTextControl достаточно запомнить менее десятка свойств, в первую очередь это свойство 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);

За вертикальное выравнивание отвечает свойство VertTextAlign, за горизонталь­ ное — TextAlign. Если текстовая строка очень велика и физически не может быть отображена цели­ ком, то установив в состояние true свойство property wordwrap: Boolean; //по умолчанию False;

можно обеспечить многострочный вывод. В ряде наследников TTextcontroi для управления атрибутами (влияющими на осо­ бенности отображения текстовых данных) можно обратиться к свойству property TextSettings: TTextSettings;

Здесь вы найдете описание шрифта, установки по горизонтальному и вертикально­ му выравниванию надписи, особенности переноса на новую строку и ряд других параметров. Некоторые особенности стилевого оформления потомков TTextcontroi, связанные с шрифтом, настраиваются свойством property StyledSettings: TStyledSettings;

Свойство представляет собой множество Tstyiedsetting. Включая (или отключая) элементы множества мы указываем — стоит ли при прорисовке элемента управле­ ния учитывать гарнитуру (ssFamily), размер (ssSize), начертание (ssstyle), цвет (ssFontCoior) и другие параметры (ssother) шрифта.

Метка TLabei Из всех рассматриваемых в этой главе компонентов метка TLabei является единст­ венным элементом управления, не способным получить фокус ввода в результате нажатия клавиши <ТаЬ> и не позволяющим редактировать свой текст пользовате­ лю. Дело в том, что задача метки существенно проще, она заключается в выводе пояснительной надписи. Все базовые свойства и методы метки (в том числе и ключевое свойство Text) унас­ ледованы от уже знакомого нам родительского класса TTextcontroi. Своих собственных "фирменных" свойств и методов у метки практически нет. В ы ­ делим свойство property AutoSize: Boolean; //по умолчанию False;


Компоненты для работы с текстом

145

позволяющее компоненту самостоятельно подогнать свой размер под размер под­ лежащего выводу текста. З

а м ечание

Если логика программы не предполагает задействование методов или обработчиков событий метки TLabei и вам особо не интересно стилевое оформление формы, то вместо метки стоит задействовать менее ресурсоемкий компонент TText.

Чтобы вы совсем не заскучали, предлагаем научиться задействовать метку в каче­ стве гиперссылки. Для этих целей мы сохраним в метке URL-адрес интересующего нас сайта (листинг 9.1). j Листинг! procedure TForml.FormCreate(Sender: TObj ect); begin Labell.Text:='Сайт компании Embarcadero'; Labell.TagString:='http://www.embarcadero.ru'; end;

При обработке события-щелчка по метке (листинг 9.2) обязательно учтем, что для вызова интернет-браузера в Windows и OS X потребуется задействовать разный исходный код.

procedure TForml.LabellClick(Sender: TObject); var Url:string;

{$IFDEF MACOS} Workspace: NSWorkspace; _Url: NSURL; {$ENDIF} begin

Url:=TLabel(Sender).TagString; {$IFDEF MSWINDOWS} // uses Winapi.ShellApi, Windows; ShellExecute(0, 'open', PChar(url), nil, nil, SW_SHOWNORMAL); {$ELSE} ($IFDEF MACOS} Workspace := TNSWorkspace.Wrap(TNSWorkspace.OCClass.sharedWorkspace); JJrl := TNSUrl.Wrap(TNSUrl.OCClass.URLWithString(NSStr(Url))); Workspace.openURL(_Url); {$ENDIF} {$ENDIF} end;


146

Гпава 9 Зам еча ни е

Честно говоря, у метки TLabel есть некоторые проблемы с реакцией на событие OnClick () (речь о RAD Studio ХЕ4 Version 18.0.4854.59655 и RAD Studio ХЕ5 Version 19.0.13476.4176). Если вы столкнетесь с аналогичной ситуацией, то можете заменить в примере метку другим компонентом, например TText.

Интерфейс IVirtualKeyboardControl У всех рассматриваемых в главе текстовых компонентов (за исключением метки TLabel) предусмотрен доступ к интерфейсу IVirtualKeyboardControl. Указанный интерфейс позволяет элементу управления взаимодействовать с виртуальной кла­ виатурой операционной системы iOS, устанавливаемой на смартфонах и планшетах от Apple (рис. 9.2). Таким образом, если вашими целевыми устройствами являются iPhone и iPad, то благодаря свойству property KeyboardType: TVirtualKeyboardType; TVirtualKeyboardType = (vktDefault, vktNumbersAndPunctuation, vktNumberPad, vktPhonePad, vktAlphabet, vktURL, vktNamePhonePad, vktEmailAddress);

вы сможете не только определить, какая именно виртуальная клавиатура подклю­ чена к компоненту, но сменить клавиатуру.

Рис. 9.2. Смена виртуальной клавиатуры

Основа строк ввода, класс TCustomEdit В проектах Delphi наиболее распространенным способом ввода текстовой инфор­ мации является использование строк ввода — компонентов, п о с т р о е н н ы х па ф у н ­ даменте класса TCustomEdit.


Компоненты для работы с текстом

147

R нем инкапсулированы свойства и методы, обеспечивающие: □ хранение и редактирование текста; □ выделение части текста с возможностью редактирования

только этой части;

□ реагирование на любые изменения в содержании текста. С основным свойством всех текстовых компонентов вас вряд ли надо знакомите Это свойство property Text: string;

Именно со свойством Text прямо или косвенно связана деятельность подавляющего боЛЬШЙНСТВа СВОЙСТВ И МеТОДОВ TCustomEdit. П рим ечание

В текстовые компоненты FireMonkey инкапсулирован класс TTextServi.ee, именно он содержит низкоуровневые методы обработки текстовых данных. На долю классов бо­ лее высокого уровня (например, строк ввода) остается лишь ставить классу TTextService задачи по вводу/выводу данных.

С процессом редактирования текста тесно связаны два события. Возможность от­ реагировать на изменение текста предоставляет событие property OnChange: TNotifyEvent;

Это событие генерируется в тот момент, когда пользователь (например, завершив ввод текста) нажмет клавишу <Enter>. Если нам надо обрабатывать любое изменение текста (будь это ручной ввод или модификация текста программным образом), то следует описывать код для обра­ ботчика события property OnChangeTracking: TNotifyEvent;

Это событие обычно задействуется для уведомления других элементов управления о процессе ввода или в качестве последней линии обороны от пользователя, пы­ тающегося передать приложению некорректные данные. Например, листинг 9.3 демонстрирует, как можно уведомить пользователя о том, что введенная им тексто­ вая строка достигла ограничения в 10 символов. ''•f i 'j Bltfr'iV I 1 В а м

ci : i r .

зммШий!

кс

тун г,

б Ь о « » н <5ёдв

г ' J.H.-I ...if ■ т т ш ш т яеш ш ш ьз

в Л ю е;

procedure TForml.FormCreate(Sender: TObj ect); begin

Edit1.MaxLength:=10; end; procedure TForml.EditlChangeTracking(Sender: TObject); begin if Editl.Text.Length>=Editl.MaxLength then

ShowMessage('Достигнут предел длины строки!');; end;

о

IM

S


148

Гпава 9

Предложенный код контролирует длину текстовой строки. Как только длина дос­ тигнет 10 символов, на экран выводится предупредительное сообщение.

Ограничения на ввод Класс TCustomEdit позволяет наложить некоторые ограничения на вводимые тек­ стовые данные. Самое серьезное ограничение (абсолютный запрет на редактирова­ ние текста) накладывает свойство property Readonly: Boolean;

//по умолчанию false

при установке его в true. Ограничение на максимальное количество символов, хранящихся в свойстве Text, поможет назначить свойство property MaxLength: Integer;

//по умолчанию 0 — ограничений нет

Нулевое значение говорит о том, что длина строки не ограничена. Если планируется применение потомка класса TCustomEdit для ввода конфиденци­ альной информации (например, пароля доступа) и пользователь программного обеспечения не планирует ознакомить с нею случайно заглянувшего в монитор прохожего, то для скрытия вводимого текста рекомендуется воспользоваться свой­ ством property Password: Boolean; //по умолчанию false

Если перевести свойство в состояние true, то при отображении содержимого стро­ ки реальные символы станут подменяться служебным символом, например точкой. Если строка ввода должна допускать ввод только определенное подмножество сим­ волов, то это подмножество следует передать в свойство property FilterChar: string;

Всего одна строка кода из листинга 9.4 ограничит права пользователя на ввод тек­ ста, теперь ему разрешается использовать только цифровые символы. Листинг 9.4. Фильтр допустимого набора символов Editl.FilterChar:= '0123456789';

Чтобы отключить фильтр ввода, очистите свойство FilterChar.

Выделение части текста У потомков класса TCustomEdit имеется возможность выделять часть текста и ре­ дактировать эту часть. Так, благодаря свойству property SelText: string;

возможно обратиться только к выделенному фрагменту текста.


Компоненты для работы с текстом

149

П р и ж е л а н и и в ы д е л е н и е текста производится не только мышью или клавишами управления курсором, но и программным способом. Для этого объявлены два свойства:

property SelStart: Integer; property SelLength: Integer;

Они определяют порядковый номер первого выделяемого символа и всю длину вы­ деляемого текста соответственно. Для выделения целого слова в текущей позиции вызывают процедуру procedure SelectWord;

Для выделения всего текста воспользуйтесь методом procedure SelectAll;

Для удаления выделенного текста пригодится процедура: procedure ClearSelection;

Для того чтобы при получении элементом управления фокуса ввода автоматически выделялся его текст, убедитесь, что свойство property AutoSelect: Boolean; //по умолчанию true;

установлено в состояние true. Благодаря выделению части текста мы можем помочь пользователю не только вы­ делять текст, но и быстрее завершить ввод текста. Предположим, что в нашем рас­ поряжении имеется большой список упорядоченных по алфавиту строк (в нашем примере хранящихся в компоненте тмешо) и строка ввода Editi (рис. 9.3).

„.им рош 1 t,wu<.r.S/ttrtSHBrl* Норвегия Объединенные Арабские Эмираты Оман Пакистан Палау Панама Папуа -Новая Гвинея Парагвай Перу Польша Португалия Россия

Яри воодэ тжет> cjpr г'вляется пс: сs к Зол подходящей строки подстановки Строк: 130

Рис. 9.3. Применение выделения части текста при поиске и подстановке

При вводе пользователем текста в строку ввода надо обеспечить быстрый поиск наиболее подходящей строки (строки, чьи первые символы совпали с символами, введенными пользователем) и подставить ее в компонент Editi (листинг 9.5).


150

Гпава 9

procedure TForml.EditlKeyUp(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState); var x, i : word; s :string; begin if DWord(KeyChar)>47 then {контролируем только символы с кодом больше 47} begin Editl.ClearSelection; //снимаем выделение х :=Length(Edit1.Text); //запоминаем число введенных знаков for i:=0 to Memol.Lines.Count-1 do //перебираем строки в Memol begin s:=Copy(Memol.Lines.Strings[i],l,x); //сравниваем if (LowerCase(Editl.Text,loUserLocale)=LowerCase(s,loUserLocale)) then begin //если нашли совпадение - помогаем пользователю Editl.Text:=Memol.Lines.Strings[i];//переносим строку в Editl Editl.SelStart:=x; //выделяем добавленную часть строки Editl.SelLength:=Edit1.Text.Length-x; break; end; end; end; end;

Как вы понимаете, в роли источника строк может выступать не только многостроч­ ный редактор тмешо, но и различные компоненты-списки (см. листинг 10.10), спи­ ски, хранящиеся в памяти, файлы и даже поля из таблиц баз данных.

Взаимодействие с буфером обмена Взаимодействие с буфером обмена производится с помощью процедур: procedure CopyToClipboard; //копировать в буфер procedure CutToClipboard; //вырезать в буфер prooedure PasteFromClipboard; //вставить из буфера

Управляющие символы По умолчанию строки ввода не воспринимают управляющие символы (символ табуляции, символ перевода строки и т. п.). Чтобы изменить ситуацию, воспользуй­ тесь свойством property Typing: Boolean; //по умолчанию false

установив его в состояние true. Дополнительный контроль за процессом ввода не­ печатных символов обеспечит событие property OnTyping: TNotifyEvent;


Компипопшы Оли работы с текстом

151

Например, это событие поможет описать реакцию компонента на нажатие пользо­ вателя управляющих клавиш <Backspace>, <Del> и т. п. во время редактирования текста в строке.

Особенности оформления Разрабатывая библиотеку FMX, программисты Embarcadero особое внимание уде­ ляли качеству графического вывода элементов управления. Строки ввода не стали исключением из этого правила. У этих компонентов, помимо стандартных для FireMonkey возможностей, есть методы, позволяющие программисту совершенст­ вовать внешний вид компонента. Свойства property FontColor: TAlphaColor;

property SelectionFill: TBrush;

соответственно отвечают за параметры кисти, которой выводится обычный текст и текст, попавший в область выделения. Координаты прямоугольника, в рамках которого выводится текст, поможет узнать метод function ContentRect: TRectF;

Метод function GetCharX(a: Integer): Single;

возвратит горизонтальную позицию символа с индексом а. Эти сведения могут пригодиться, например, при визуализации процедуры поиска подстроки в строке. Разместите на форме две строки ввода: □ строка

Editl :TEdit

будет содержать какой-то произвольный текст;

□ в строке Edit2: TEdit пользователь может ввести символ, сочетание символов или слово, которое он хочет обнаружить в компоненте Editl. Собственно с поиском подстроки никакой проблемы нет, в Delphi уже давно суще­ ствует функция Pos о, возвращающая индекс найденного символа. Но в VCL было проблематично дать пользователю визуальную подсказку, например, подчеркнуть найденное слово. В FMX такой проблемы не существует. Для этого достаточно воспользоваться событием onPaint () строки ввода E d itl (листинг 9.6). ........""........• ... ... -.. . ;...... .... .......................................................................................................Вй»=* № .... ........... - —.........: ; Листинг 9.6. Визуальное выделение найденного слова

procedure TForml.EditlPaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF); var a:integer; APtl, APt2: TPointF;


Гпава 9

152

begin a:=Pos(Edit2.Text,Editl.Text); //индекс первого найденного символа if а=0 then exit; //если 0, значит, подстрока не найдена //в GetCharX() отсчет начинается с 0, поэтому вычтем 1 APtl.X:=Editl.GetCharX(a-l); //координаты первого символа 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 begin Stroke.Color:=TAlphaColorRec.Crimson; DrawLine(APtl, APt2, 1); EndScene; end; end;

Для того чтобы подчеркнуть найденное слово, нам потребуются две координаты — начала и окончания подстроки в строке. Первая координата выясняется благодаря методу Getcharx (), для расчета второй координаты мы прибавляем к первой результат выполнения метода Textwidth (). Дело сделано — нам осталось провести линию (рис. 9.4). Поискподстроки ! 1

Исходная строка Hello, World!

i

Поиск слова

1

World . .— .

L.

Поисг ------------------------- -

. _

Рис. 9.4. Подчеркивание найденного слова

Строки ввода TEdit и TCIearingEdit Из всех компонентов, способных редактировать текст, у программистов наиболь­ шей популярностью пользуются строка ввода TEdit и ее коллега— строка ввода с возможностью быстрой очистки TCIearingEdit (для этого пользователю достаточ­ но щелкнуть по кнопке с изображением крестика в правой части компонента). Начиная с D elphi ХЕ4, у компонента TEdit появилась возможность подключения дополнительных элементов — кнопок, для этого следует вызвать контекстное меню компонента и выбрать элемент с названием подходящего класса. Подключаемая кнопка представляет собой самостоятельный элемент управления (наследник це­


Компоненты для работы с текстом

153

почки классов "TCustomButton— TEditButton"), который располагается в правой части строки ввода и снабжается соответствующей картинкой (рис. 9.5). Замечание

Набор свойств и методов TEdit, TCIearingEdit й ввода ничем не отличается от пе­ речня свойств и методов опорного класса TCustomEdit.

UL..-.......... { te rn s

Editor,..

Add Item

TOearEditSutton

Edit

TPasswcrdEdftSuttor»

Control Bind Visuatfy,,. Position

fftpChildren Рис. 9.5. П о д кл ю ч е н и е

TEditButton

TSearehEdftBi&ton

TDropDowntditButton

у п р а в л я ю щ и х кн о п о к к ко м п о н е н т у

TEdit

Многострочный редактор ТМето Многострочный редактор тмешо предназначен для ввода и редактирования много­ строчного неформатированного текста и представляет собой логическое объедине­ ние визуального элемента управления и класса TStrings, специализирующегося на хранении списка строк. Ядром компонента выступает свойство property Lines: TStrings;

предоставляющее построчный доступ к тексту. Предусмотрен и альтернативный способ обращения к обслуживаемому тексту, его предоставляет свойство property Text: string;

На этот раз мы сможем работать не построчно, а со всем текстом одновременно. Основными обработчиками событий элемента управления являются ь

property OnChange: TNotifyEvent; property OnChangeTracking: TNotifyEvent;

Оба события реагируют на изменения текста в компоненте тмешо, с той лишь раз­ ницей, что OnChange () генерируется при условии утраты элементом управления фо­ куса ввода, a OnChangeTracking () будет вызвано при любых обстоятельствах.


Гпава 9

154

Хотя многострочный редактор т м е ш о не является потомком рассмотренного в нача­ ле главы класса T C u s t o m E d i t , тем не менее, он вооружен тем же набором методов, позволяющих работать только с выделенной частью текста. procedure S e l e c t A l l ; property S e l S t a r t : I n t e g e r ; property S e l L e n g t h : I n t e g e r ; property S e l T e x t : s t r i n g ; procedure C l e a r S e l e c t i o n ;

//выделить

весь

текст

//индекс первого

выделенного

символа

//длина выделения //доступ

к выделенному тексту

//очистка

выделенной области

Взаимодействие с буфером обмена производится с помощью методов: procedure procedure prooedure

CopyToClipboard;

//копировать

CutToClipboard;

//вырезать

в буфер

в буфер

PasteFromClipboard;

//вставить

из

буфера

Позиция каретки В любом текстовом редакторе, в том числе и в тмето, место, в котором осуществля­ ется набор текста, отмечается мигающей вертикальной чертой— кареткой. Для выяснения позиции каретки следует воспользоваться свойством property

CaretPosition:

TCaretPosition;

Свойство возвращает запись из двух полей, идентифицирующих номер строки и позицию каретки в строке. TCaretPosition = Line,

Pos:

record

Integer;

//номер строки,

позиция

каретки

в строке

end;

Номер строки и позиция символа в строке применяются в ряде методов много­ строчного редактора. В частности, метод function

PosToTextPos(APostion:

TCaretPosition):

Integer;

получив координаты A P o s t i o n , подсчитает число символов от начала документа до обозначенной позиции. Обратная задача — преобразование числа символов в коор­ динаты T C a r e t P o s i t i o n — решается методом function

T e x t P o s T o P o s ( A P o s : Integer):

TCaretPosition;

Еще один метод fu n ction

GetPositionPoint(ACaretPos: TCaretPosition):

TPointF;

произведет пересчет координат символа в экранные координаты.

Редактирование текста Основной сервис по редактированию текста в компоненте тмето, безусловно, пре­ доставляет интегрированный в компонент класс T S t i n g s . Но кроме методов T s t i n g s нам разрешено пользоваться и методами, встроенными в тмето.


Компоненты для работы с текстом H a

dotobkc

procedure

блока

155

гскста в многострочный редактор специализируется метод

InsertAfter(Position:

const

S:

TCaretPosition;' string;

Options:

TInsertOptions);

В результате в позицию, указанную в аргументе P o s i t i o n , будет вставлен текст Особенности вставки определяют в опциях o p t i o n s (табл. 9.1).

s.

Таблица 9.1. Опции вставки текста в тмето TInsertQption

Описание

ioSelected

К вставленному тексту применяется эффект выделения

ioMov e C a r e t

Перемещает курсор в позицию после последнего вставленного символа

ioCanUndo

Допускает восстановление удаленного текста

ioUndoPairedWithPrev

Определяет, что данная операция вставки является продолже­ нием предыдущей и должна рассматриваться как единое целое

Обратную задачу по удалению фрагмента текста решает метод prooedure

DeleteFrom(Position: ALength:

TCaretPosition; Integer;

Options:

TDeleteOptions);

Начало области удаления определяется в первом параметре функции Po sition, длина удаляемой строки назначается в аргументе A L e n g t h . Опции предложены в табл. 9.2. Таблица 9.2. Опции удаления текста

в

ТМето

TDeleteOption

Описание

ioMov e C a r e t

Перемещает курсор в позицию после последнего вставленного символа

doCanUndo

Допускает восстановление удаленного текста

Весьма полезное качество компонента т м е ш о заключается в возможности отмены последних изменений в тексте. Для этого следует вызвать метод prooedure

UnDo;

Метод анализирует состояние специальной структуры T E d i t A c t i o n S t a c k , которую можно сравнить со стеком, в котором сохраняются все правки в тексте. И если от­ кат изменений возможен, то он осуществляется.

Быстрое перемещение по тексту Для организации быстрого перемещения по тексту в состав класса введены четыре метода. Процедуры procedure procedure

GoToTextBegin; GoToTextEnd;

соответственно осуществят переход к началу или к концу текста.


Гпава 9

156

У методов procedure prooedure

GoToLineBegin; GotoLineEnd;

расстояния для "прыжка" поменьше, они переведут каретку соответственно к нача­ лу или к окончанию строки.

Ввод чисел TNumberBox, TSpinBox и TComboTrackBar Среди текстовых компонентов есть специалисты, оказывающие пользователю по­ мощь при вводе целочисленных или вещественных числовых значений. Это компо­ ненты T N u m b e r B o x , T S p i n B o x И T C o m b o T r a c k B a r . По С В О ем у ф уН КЦ И О Н ЭЛ у Компоненты очень похожи (табл. 9.3) и отличаются лишь внешним видом и способом ввода зна­ чения (рис. 9.6).

нампонсмт"Ъ

о .

yw

'>го зн;

ТЭротво»

TNumberttox 55

*

9 ■

ТСсет »TrackBar »

38.25 eJ’"'”

S ,

ь

3 S ,2 5 4 5 2 3 0 4 5 6 5 4 3

Рис. 9.6. Текстовые компоненты для ввода числового значения

Таблица 9.3. Общие свойства компонентов TNumberBox, TSpinBox и TComboTrackBar

Свойство

Описание

property V a l u e T y p e : TNu m V a l u e T y p e ; type T N u m V a l u e T y p e = (vtlnteger, v t F l o a t ) ;

Тип вводимых данных может быть как целочисленным, так и вещественным

property

Value:

Числовое значение

property

D e c i m a l D i g i t s : Integer;

Single;

property Min: Single; property

Max:

Число знаков, отображаемых после запятой Диапазон допустимых значений

Single;

Конечно же, у каждого из рассматриваемых элементов управления имеются и спе­ цифичные черты. У компонентов T N u m b e r B o x и T S p i n B o x шаг приращения величины V a l u e зависит О Т С О С Т О Я Н И Я С В О Й С Т В


Компоненты для работы с текстом

157

property Horzlncrement: Single; property Vertlncrement: Single;

Параметры приращения играют роль при управлении значением value с помощью мыши. Попробуйте, удерживая в нажатом состоянии левую кнопку мыши, переме­ щать указатель мыши над поверхностью элемента управления. Если вы станете со­ вершать горизонтальные движения, то шаг приращения будет взят из свойства Horzlncrement, на вертикальную траекторию перемещения указателя откликнется СВОЙСТВО Vertlncrement.

При желании программист сможет получить доступ к ползунку компонента TComboTrackBar, для этого в коде программы стоит упомянуть свойство property

TrackBar:

TTrackBar;

Из событий компонента следует упомянуть событие property OnChangeTracking: TNotifyEvent;

генерируемое при изменении положения ползунка, и событие property OnChange: TNotifyEvent;

вызываемое при редактировании значения value.


ГЛАВА 10

Компоненты-списки Библиотека FireMonkey предлагает программисту набор компонентов-списков, специализирующихся на хранении некоторого набора текстовых строк (и, при же­ лании, любых других объектов) и позволяющих пользователю выбирать один или несколько элементов с данными. Список выбора строится на основе класса TListBox. Кроме того, существуют два типа комбинированных списков — нередактируемый тсошЬоВох и с редактируемой строкой ввода TComboEdit. Самое существенное различие между рассматриваемыми элементами управления в том, что обычный список выбора представляет собой ок­ но, в котором одновременно отображаются все его элементы. Если из-за ограни­ ченного размера окна текстовые элементы не могут быть выведены на экран в пол­ ном составе, то окно списка выбора снабжается полосой прокрутки. Комбиниро­ ванные списки по умолчанию отображают всего один свой элемент, щелчок по кнопке в правой части компонента заставляет его вывести на экран список с переч­ нем элементов, один из которых выберет пользователь (рис. 10.1). После выбора выпадающий список вновь свернется. Редактируемый список TComboEdit обладает еще одной сервисной возможностью. В отличие от своего статического собрата TComboBox, список TComboEdit позволяет пользователю вводить текст в строку ввода. Основными родовыми классами для списка выбора выступают f m x .Layouts. TScroiiBox и f m x .ListBox.TCustomListBox. Благодаря первому список TListBox при­ обретает способность выступать в роли контейнера, обладающего полосами про­ крутки, а благодаря второму— список выбора получает возможность управлять своими элементами.

Нередактируемый комбинированный список тсотЬовох создается на основе комби­ нации двух базовых классов (именно поэтому его и называют комбинированным). Непосредственный предок, класс TCustomComboBox, определяет всю программную логику элемента управления, кроме того, здесь описана верхняя часть элемента управления, соответствующая его свернутому состоянию. При развертывании комбтирош ного списка из него "выпадает" СПИСОК выбора, который создастся гга основе TComboListBox.


Компоненты-списки

159

•О* Ксмдсивчты -сдаскя fir*Monfc*y Список еъ Scoa TListBox Салават

К о м б и н и р о в а н н ы * СПИСКИ

А

Нередмсгидемый ТComSoao*

Самара

С,»9 ер<мв Северодвинск

Саранск

;Севере»; :Сергиев Посад |Серпухов

Сарапул Саратов Северодвинск Севере*

Ш

Сергиев Посад Серпухов Смоленск ■Сд-м— . ________

О Д ю и д о м ы й TComboEdit

1!:тщыт $j М;

| Старый Оскол

л .

Невинномысск . Нефтекамск ; Нефтеюганск

- Смоленск

:Сочи р с &т

~3; Наколов

;Нижневартовск Q»(

Нижнекамск

*;

; Нижний Тагил

РОД

ч

«у

Рис. 10.1. Внешний вид списка выбора и комбинированного списка в проекте для Windows

Внимание!

Важной объединяющей чертой списков TListBox и TComboBox выступает тот факт, что хранимые в них элементы строятся на основе класса TListBoxitem.

Редактируемый комбинированный список TComboEdit также объединяет в себе два класса: строку ввода (созданную на фундаменте класса TCustomEdit) и список выбо­ ра TComboEditListBox.

Базовый элемент списка TListBoxitem Сами по себе незаполненные данными списки малоинтересны для пользователя. По своей сути список представляет собой интеллектуальный футляр для главных дей­ ствующих л и ц — элементов списка. Именно манипулируя с элементами списков, пользователь осуществляет свой выбор, который позднее определит поведение приложения. Основной информационный элемент списка (как обычного, так и комбинированно­ го) строится на основе экземпляра класса TListBoxitem. Зам ечание

TListBoxitem— не единственный класс, представленный в списках. Кроме него спи­ ски способны работать с элементами: TMetropolisUIListBoxltem, TListBoxHeader, TSearchBox, TListBoxGroupHeader И TListBoxGroupFooter.

Ключевое свойство элемента property Text: string;

хранит текстовое описание элемента. Кроме текста с элементом списка разрешено сопоставить любой другой объект, ссылку на этот объект следует передать в свой­ ство property Data: TObject;


Гпава 10

160

В качестве идентификатора отдельного элемента списка может выступать его ин­ декс property

Index:

Integer;

Заметим, что индекс элемента не статичен и может измениться в результате удале­ ния или переупорядочивания принадлежащих списку элементов. Элемент списка может быть выбран пользователем или программным образом, в этом случае в его свойстве property

Boolean;

IsSelected:

окажется значение

true.

Кроме того, элемент может быть отмечен "галочкой" благодаря свойству property

IsChecked:

Boolean;

Внимание!

Элемент списка T L i s t B o x i t e m может выступать в роли контейнера для других эле­ ментов управления.

С каждым элементом списка может быть связана дополнительная детализирующая информация. Ответственность за ее подключение возлагается на свойство property

ItemData:

TListBoxItemData;

Данные детализации описываются четверкой свойств, которые представлены в табл. 10.1. Таблица 10.1. Основные свойства TListBoxItemData Описание

Свойство property

Text:

property

Detail:

property

Accessory:

string; string; T A c cessory;

//по умолчанию aNone

Основное текстовое описание элемента Дополнительное текстовое описание • aNone

— нет значка;

• амоге

— значок продолжения; — значок детализации;

• aDetail

• aCheckmark

property

Bitmap:

TBitmap;

— значок "галочки"

Дополнительная картинка

Чтобы проиллюстрировать возможности свойства детализации, лучше всего вос­ пользоваться проектом для мобильной платформы File | New | FireM onkey M obile A pplication, разместить на пустой форме компонент T L i s t B o x и поэкспериментиро­ вать со свойствами I t e m D a t a и s t y i e L o o k u p элементов списка (рис. 10.2). Внимание!

Предусмотрено несколько способов отображения информации детализации элемен­ том TListBoxitem . Для того чтобы выбрать наиболее подходящий споооб длп мо, следует поэкспериментировать со свойством s t y i e L o o k u p элемента списка. В проек­


Компоненты-списки

161

тах для мобильной платформы iOS и Android наиболее востребованными стилями окажутся: listboxitemnodetail, listboxitembottomdetail, listboxitemrightdetail И listboxitemleftdetail.

Документы

»j

Игры

>

[j&fc Музыка

1

файлы mp3

L i t Изображения |^ЯИ>, фотографии и картинки rJSL .... ....... ®

Рис. 10.2. Элементы списка в состоянии детализации

> >

[ « Я Сеть

Список выбора TListBox В простейшем случае список выбора обеспечивает пользователю возможность вы­ бора одного или нескольких элементов из набора строк TStrings, доступ к которо­ му реализуется при посредничестве свойства property Items: TStrings;

Манипуляции текстовыми строками осуществляются с помощью стандартных ме­ тодов, предоставляемых в наше распоряжение классом TStrings (листинг 10.1). В момент появления новой текстовой строки для ее визуализации автоматически создается очередной экземпляр класса TListBoxitem. j

sЛистинг

' ’

i

ListBoxl.Clear; ListBoxl.Items.LoadFromFile(1c:\city.txt1); ListBoxl.Sorted:=true;

О числе элементов в списке проинформирует доступное только для чтения свойство property Count: Integer;

Индекс текущего элемента списка (последнего элемента, выбранного пользовате­ лем) доступен благодаря свойству property Itemlndex: Integer;

Если в списке не выделен ни один элемент, то об этом свойство отрапортует значе­ нием -1 . Свойство доступно не только для чтения, но и для записи, поэтому оно позволяет программисту указать, какой из элементов списка должен стать теку­ щим.


162

Глава 10

Редактирование элементов Если задействованный в проекте компонент-список должен обладать заранее из­ вестным статическим набором элементов, то для построения перечня элементов достаточно воспользоваться контекстным меню компонента и обратиться к пункту меню Item s E d ito r (Редактор элементов). В результате на экране отобразится ре­ дактор, позволяющий добавить/удалить элемент из списка. В ситуации, когда состав списка динамичен и может подвергаться изменениям во время выполнения приложения, можно пойти по одному из двух альтернативных путей. Наиболее распространенный случай основан на возможностях свойства items. Свойство инкапсулирует набор строк TStrings, что позволяет добавлять (листинг 10.2) и удалять текстовые элементы методами класса TStrings. I Листинг 10.2. Динамическое заполнение списка текстовыми элементами ListBoxl.Items.Clear; ListBoxl.Items.Add('Москва'); ListBoxl.Items.Add('Санкт-Петербург');

Традиционный способ редактирования состава элементов не раскрывает все воз­ можности компонента-списка. Дело в том, что элемент списка TListBoxitem не ог­ раничивается обработкой текстовых данных и обладает весьма серьезными воз­ можностями, в том числе он может выступать в роли контейнера для других эле­ ментов управления. Элементы управления (кнопки, графические фигуры, изображения и т. п.) могут размещаться на поверхности элементов списка как во время визуального проектирования, так и программным способом. Однако в по­ следнем случае от разработчика приложения потребуется больше усилий— ему придется самостоятельно в коде программы создавать элементы списка и добавлять их в контейнер. Для добавления объекта в список следует воспользоваться методом procedure AddObject(AObject: TFmxObject); override; ИЛИ

procedure InsertObject(Index: Integer; AObject: TFmxObject); override;

Разница между процедурами заключается лишь в том, что AddObject () помещает н о в ы й э л е м е н т в к о н е ц списка, a InsertObject () вставляет В ПОЗИЦИЮ Index. Для удаления ненужного элемента следует обратиться к методу procedure RemoveObject(AObject: TFmxObject); override;

Кардинальная очистка списка осуществляется методом procedure Clear; v ir t u a l;

Предложенный в листинге 10,3 код демонстрирует порядок сбора в сп и сок ооаде ний о файлах-картинках в формате JPEG.


Компоненты-списки

163

Дття ппитпренш примера вам потребуются три компонента: □

СПИСОК ListBoxl :TLIstBox;

□ строка ввода E d iti:TEdit, сюда пользователь внесет путь к каталогу с картинками; кнопка Buttonl:TButton, щелчок по которой инициирует процесс построения списка.

г. -

д^зая щ

: Оистинг .1.3. Дкнаг/.^ч.сисэ злолиин::о cr.tso .a об'-а[ггами 5 Ы г< ~ г i t

a.

procedure TForml.ButtonlClick(Sender: TObject) ; var SDA:TStringDynArray; Item:TListBoxItem; Image:TImage;

Lbl:TLabel; i:integer; begin ListBoxl.BeginUpdate; ListBoxl.Clear; if DirectoryExists(Editl.Text) then begin SDA:=TDirectory.GetFiles(Editl.Text,'*.jpg'); for i :=0 to High(SDA) do begin Item: =TListBoxItem.Create(ListBoxl); Item.TagString:=SDA[i]; Item.Height:=40; Image:=TImage.Create(Item); //миниатюра изображения Image.Parent:=Item; Image.Position.X :=0; Image.Position.Y:=0; Image.Width:=4 0; Image.Height:=4 0; Image.Bitmap.LoadThumbnailFromFile(SDA[i],Width, Height); ■Lbl:=TLabel.Create(Item); //метка с именем файла Lbl.Parent:=Item; Lbl.Position.X:=50; Lbl.Pos ition.Y :=0; Lbl.Text:=System.IOUtils.TPath.GetFileName(SDA[i]); ListBoxl.AddObject(Item); end; end; ListBoxl.EndUpdate; end;


Глава 10

164

Если вы повторите код без ошибок, то увидите (рис. 10.3), что каждый из элемен­ тов списка станет обладателем миниатюры изображения (об этом позаботится экземпляр класса T i m a g e ) и названия файла (оно отобразится в метке TLabei).

Рис. 10.3. Элементы TListBoxltem списка с дополнительными объектами

Доступ к выделенному элементу списка Ссылка на выделенный пользователем элемент, а в ситуации, когда разрешен одно­ временный выбор нескольких элементов — ссылка на последний выделенный эле­ мент находится в свойстве property Selected: TListBoxltem;

Если в списке выбора не выделен ни один из элементов, то в свойстве окажется не­ определенное значение nil. Порядковый номер выбранного элемента доступен благодаря свойству property

Itemlndex:

Integer;

ЭТО СВОЙСТВО также может выступать в качестве индикатора наличия выбранных элементов, если таковых нет, то в свойстве окажется значение -1 . З

ам ечание

Свойства Selected и itemlndex могут применяться для выделения элемента списка из кода программы.


Компоненты-списки

165

Доступ к произвольному элементу списка Для доступа к любому (необязательно выделенному пользователем) элементу спи­ ска из кода программы проще всего воспользоваться методом function ItemByIndex(const Idx: Integer): TListBoxltem;

Функции необходим всего один аргумент— индекс интересующего нас элемента списка. Точно такого же результата можно добиться благодаря свойству property Listltems[Index: Integer]: TListBoxltem;

К числу альтернативных способов доступа к элементу TListBoxltem МОЖНО ОТНеСТИ

метод function ItemByPoint(const X, Y: Single): TListBoxltem;

позволяющий идентифицировать объект по его координатам. Последний метод очень удобен, когда вы хотите подсказать пользователю, какой именно элемент списка в данный момент расположен под указателем мыши (листинг 10.4). ! Листинг 10.4. Доступ к элементу списка по экранным координатам procedure TForml.ListBoxlMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single); var Item:TListBoxltem; begin Item: =ListBoxl.ItemByPoint(X,Y); if ItemOnil then Labell.Text:=Item.Text else Labell.Text:='';

Выбор нескольких элементов Для того чтобы компонент-список разрешил пользователю производить одновре­ менный выбор нескольких строк, следует перевести в режим true свойство property MultiSelect: Boolean; //по умолчанию false Зам ечание

Для одновременного выбора нескольких элементов списка пользователь должен удерживать в нажатом состоянии клавишу <Ctrl> или <Shift>.

Листинг 10.5 демонстрирует порядок сбора названий выделенных элементов спи­ ска, для повторения примера не забудьте установить у списка свойство MultiSelect в состояние true.


Глава 10

166

procedure TForml.ListBoxlClick(Sender: TObject); v a r i:integer; begin Label1.Text:=''; fo r i :=0 to ListBoxl.Count-1 do i f ListBoxl.ItemBylndex(i).IsSelected=true then Labell.Text:=Labell.Text+#0#13+ListBoxl.ItemByIndex(i).Text;

Для выбора диапазона элементов предназначен метод procedure SelectRange(Iteml, Item2: TListBoxitem);

Для одновременного выбора сразу всех элементов стоит обратиться за помощью к методу procedure SelectAll;

Обратную задачу — снятие выделения легко решит процедура procedure ClearSelection;

Представление элементов в виде кнопки выбора В той ситуации, когда пользователю следует отметить в списке несколько элемен­ тов, на помощь приходит свойство p ro p e rty ShowCheckboxes:boolean;

//по умолчанию false

Активация свойства приведет к появлению на каждом из элементов кнопки выбора (кнопки с флажком), щелчок по которой позволит пометить элемент "галочкой".

Перестановка элементов Для перемещения элементов внутри списка следует обращаться к методу procedure Exchange(Iteml, Item2: TListBoxitem);

Процедура поменяет местами элементы item l и item2.

Сортировка элементов Наиболее востребованная сортировка заключается в упорядочении элементов спи­ ска по текстовым значениям. Такая сортировка производится при посредничестве свойства property Sorted-.boolean; //по умолчанию false

По умолчанию, при переводе свойства в состояние true осуществляется алфавитная сортировка элементов списка по их свойству Text. Однако поведение списка не-


Компоненты-списки

167

сттпжнп И изменить, дня этого следует бытия

ВСПОМНИТЬ О СущеСТЕОВйНШ 0 В Щ 6 8 Ш Ш

со­

property OnCompare: TOnCompareListBoxItemEvent;

TOnCompareListBoxItemEvent = procedure (Iteml, Item2: TListBoxltem; var Result: Integer)' of object;

Событие (а точнее череда событий) OnCompare () генерируется в момент попарного сравнения элементов iteml и Item2 списка. Результат сравнения, от которого зави­ сит очередность элементов списка, нам следует определить самостоятельно в пара­ метре Result. Допустим, что в нашем распоряжении имеется список ListBoxl и пара кнопок вы­ бора — RadioButtonl и RadioButton2, определяющих порядок сортировки элементов в сииске (но возрастанию и по убыванию). В этом случае для решения задачи сор­ тировки нам подойдет пример, предложенный в листинге 10.6. [М t

г 10.6.

procedure TForml.RadioButtonlClick(Sender: TObject); begin

//общее событие для компонентов RadioButtonl и RadioButton2 ListBoxl.Sorted:=false; ListBoxl.Sorted:=true; end; procedure TForml.ListBoxlCompare (Iteml, Item2: TListBoxltem; var Result: Integer); begin if RadioButton2.IsChecked then if Iteml.Text>Item2.Text then Result:=1 else if Iteml.Text<Item2.Text then Result:=-l else Result:=0 else if RadioButtonl.IsChecked then if Iteml.Text<Item2.Text then Result:=1 else if Iteml.Text>Item2.Text then Result:=-l else Result:=0; end;

Зам ечание

Благодаря событию OnCompare () элементы списка могут быть упорядочены не только по текстовым, но и по любым другим типам данных.

Текстовый поиск, элемент TSearchBox Отслеживая развитие Delphi с самой ее первой версии, постепенно приходишь к выводу, что, возможно, в обозримом будущем разработчики языка программиро-


Гпава 10

168

вания вообще отучат нас от придумывания исходного кода. Не станем полемизиро­ вать на тему хорошо это или плохо. Просто предлагаем щелкнуть по компоненту TListBox правой кнопкой мыши и добавить к содержащему произвольные тексто­ вые элементы списку элемент поиска TSearchBox (рис. 10.4).

Рис. 10.4. Добавление элемента поиска TSearchBox

В общем-то, и все — на этом "программирование" завершилось... При вводе поль­ зователем текста в строку поиска список постепенно сокращает число отображае­ мых элементов, выводя на экран только те, которые совпадают с введенным шаб­ лоном.

Особенности оформления списка Список позволяет изменить размеры принадлежащих элементов и назначить цен­ трализованно при посредничестве свойств property ItemWidth: Single; //ширина элемента property ItemHeight: Single;//высота элемента Зам еч ан и е

При желании не СЛОЖНО присвоить индивидуальные размеры для каждого из элемен­ тов списка, в таком случае в свойствах ItemWidth и ItemHeight списка отобразятся нули.

Расположение элементов списка определяется состоянием свойства p ro p e rty

LisrStyle: TListStyle; //по умолчанию TListStyle.lsVertical;

По умолчанию оно предполагает классическое размещение элементов списка один над другим. Изменив состояние свойства в IsHorizontal, М Ы добьемся ТОГО, ЧТО

элементы развернутся на 90° против часовой стрелки и расположатся слева направо. Свойство p ro p e rty

Columns: Integer; //по умолчанию 1 колонка

позволит создать м ногоколоночны й список.


Компоненты-списки

169

Основные события списка Наряду с классическим набором событии, которыми обладают всс элементы управ­ ления FireMonkey (потомки класса T C o n t r o i ) , компонент-список вооружен рядом эксклюзивных событий, основные из которых предложены в табл. 10.2. Таблица 10.2. События списка TListBox Событие

Описание Генерируется в момент выбора элемента списка

property

O nChange:

property

O n C h a n g e C h e c k : TNo t i f y E v e n t ;

Генерируется в момент щелчка по кнопке выбора

property

O n C ompare:

Событие, сравнивающее элементы списка в момент сортировки

TNo t i f y E v e n t ;

TOnCompareListBoxItemEvent;

Нередактируемый комбинированный список

TComboBox В сравнении с обычным списком выбора у нередактируемого комбинированного списка T C c m b o B o x есть два важных отличия. Первое отличие— визуальное: по умолчанию комбинированный список свернут и практически не занимает места на форме. Это весьма важное преимущество, особенно в ситуации, когда пользова­ тельский интерфейс разрабатываемого приложения перенасыщен элементами управления. Второе отличие: компонент T C o m b o B o x не предназначен для одновре­ менного выбора нескольких значений, это бы противоречило самой логике работы комбинированного списка, ведь в свернутом состоянии список-компонент отобра­ жает всего один элемент. В остальном компоненты очень похожи, это подтвержда­ ет табл. 10.3, в которой представлены наиболее важные свойства и методы комби­ нированного списка. Таблица 10.3. Основные свойства и методы TComboBox Свойства и методы

Описание

property

Набор текстовых строк, отображаемых в списке. Отметим, что компонент не умеет хранить свои элементы, поэтому их следует загружать/сохранять в файле методами L o a d F r o m F i l e () и

Items:

TStrings;

S a v e ToFilef) '

property

Count:

property

Selected:

property

Itemlndex:

property

Listltems[Index:

Число элементов в списке

Integer; TLi s t B o x i t e m ; Integer; Integer]:

Выбранный элемент Индекс выбранного элемента Доступ к любому элементу списка по его индексу

TList B o x i t e m ;

prooedure

Clear;

Очистка списка


Гпава 10

170

Из наиболее интересных черт комбинированного списка отметим свойство property DropDownCount: Integer;

определяющее число элементов, отображаемых в выпадающем списке, однако сра­ зу заметим, что в проектах для iOS и Android вместо списка вы увидите вращаю­ щийся "барабан", который появляется внизу экрана в момент выбора пользователем интересующего его элемента (рис. 10.5). Допускается изменить место вывода списка на экран. В простейшем случае для этого следует "поколдовать" со свойством property Placement: TPlacement; TPlacement = (plBottom, plTop, plLeft, plRight, plCenter, plBottomCenter, plTopCenter, plLeftCenter, plRightCenter, plAbsolute, plMouse, plMouseCenter);

По умолчанию список выпадает под элементом управления (plBottom). Проявив еще немного настойчивости и воспользовавшись свойством property PlacementRectangle: TBounds;

можно более точно настроить область вывода списка.

----------------Yandex

http Jlwww.yande» ru

Карты Приложения Маркет Картинки DomboBox в период визуального

проектирования

Сгтсс»

TComboBox во время выполнения приложения

Google ШтиЛт

Rambler Modified

Р ис. 10.5. Поведение комбинированного списка TComboBox в проектах для мобильном платф ормы


Компоненты-списки

171

Чтобы заставить компонент показать выпадающий список в коде вашей програм­ мы, обратитесь к методу procedure DropDown;

В завершение недолгой истории комбинированного списка напомним ключевое событие компонента property OnChange: TNotifyEvent;

Событие вызывается в момент смены элемента списка. В составе компонентов FMX имеется комбинированный список TCoiorComboBox, позволяющий пользователю выбрать один из заранее предопределенных цветов. Интересно, а сможет ли с подобной задачей справиться классический тсотЬоВох? Листинг 10.7 доказывает, что никаких проблем в этом нет. | Листинг 10.7. Создание комбинированного списка выбора цвета

|

procedure TForml.FormCreate(Sender: TObj ect); var Item:TListBoxltem; Procedure CreateColorRect(ParentItem:TListBoxItem); var R:TRectangle; begin R:=TRectangle.Create(Parentltem); 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; begin Item: =TListBoxItem.Create(ComboBoxl); Item. Text:= 'Красный'; Item. Tag:=claRed; CreateColorRect (Item) ; Item. TextAlign:=TTextAlign.taCenter; ComboBoxl.AddObj ect(Item); // и т. д . end;

В момент создания формы мы динамически заполняем комбинированный список элементами TListBoxltem. На поверхности каждого из элементов размещаем окра­ шенный в соответствующий цвет прямоугольник TReCtangle. Для того чтобы воспользоваться услугами нашего комбинированного списка, на­ пример, чтобы перекрасить поверхность формы, выбираем событие OnChange (лис­ тинг 10.8).


1172

Гпава 10

Листинг 10.8. Выбор пользователем элемента в комбинированном списке procedure TForml.ComboBoxlChange(Sender: TObject); begin if ComboBoxl.Itemlndex<>-1 then Forml.Fill.Color:=ComboBoxl.Selected.Tag; end;

Редактируемый комбинированный список

TComboEdit В отличие от обычного комбинированного списка TComboBox его собрат TComboEdit в качестве своего опорного класса избрал специалиста по обработке текста — класс TCustomEdit. Благодаря этому поступку TComboEdit приобрел полезную способность редактировать текст в строке ввода. Мы

не

станем

повторяться

и

вновь

перечислять

свойства

и

м етоды

класса

TCustomEdit ( в ы и х н а й д е т е в главе 9), о т м е т и м л и ш ь т о , ч т о о с н о в н ы м с в о й с т в о м , унаследованны м редактируем ы м ком бинированны м списком от строки ввода, вы ­ с т у п а е т СВОЙСТВО Text.

Если вы внимательно прочитали страницы главы, посвященные списку TListBox и комбинированному списку TComboBox, то уже готовы работать с компонентом TComboEdit. Несмотря на то, что TComboEdit имеет несколько иную цепочку пред­ ков, создатели компонента оснастили его набором свойств и методов как по назва­ нию, так функционально повторяющих свойства и методы уже рассмотренных спи­ сков. Единственное, о чем стоит напомнить, так это свойство property Items: TStrings;

в котором хранится список строк компонента. Полагаем, что уже настало время рассмотреть пару примеров, раскрывающих осо­ бенности компонента TComboEdit. Листинг 10.9 демонстрирует один из способов заполнения списка во время выполнения приложения

procedure TForml.ComboEditlKeyDown(Sender: TObject; var Key:" Word; var KeyChar: Char; Shift: TShiftState) ; var s:string; bsgin i f (Key=13)

th e n

//код клавиши <Enter>

b e g in

s:=Trim(ComboEditl.Text); //введенная пользователем строка if so'' then //если строка не пуста, begin //добавим строку в список ComboEditl.Items.Add(s);


Компоненты-списки

173

ComboEditl.Text:='' ; end;

Если вы пробовали работать со списком выбора или с обычным комбинированным списком, содержащим большое число элементов, то уже наверняка убедились в ТОМ, ЧТО на поиск требуемого элемента может уйти много времени. Несколько строк кода из листинга 10.10 значительно упростят жизнь пользователя. Теперь для выбора текстового элемента из отсортированного по алфавиту списка достаточно набрать первые символы искомого текста. В ответ на это список самостоятельно отыщет нужный элемент и подставит недостающие символы. [ листинг 10.10. Подбор слова из списка по символам; введенным в procedure TForml.ComboEditlKeyUp(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState); var i,x:integer; s :string; begin if Key<>13 then begin //пользователь вводит первые символы в строку ввода x:=Length(ComboEditl.Text); //число введенных символов if х>0 then for i := 0 to ComboEditl.Count-1 do begin s:=Copy(ComboEditl.Items[i],l,x); if Uppercase(s,loUserLocale)= Uppercase(ComboEditl.Text,loUserLocale) then //начало слова совпало begin //копируем недостающую часть слова s:=Copy(ComboEditl.Items[i],x+1,Length(ComboEditl.Items[i])—x); ComboEditl.Text:=ComboEditl.Text+s; //в строку ввода ComboEditl.SelStart:=x; //выделяем скопированную часть ComboEditl.SelLength:=Length(ComboEditl.Text)-x; ComboEditl.CaretPosition:=x; //сохраним позицию каретки break; end; end; end else begin //пользователь нажал <Enter> и подтвердил свой выбор s:=UpperCase(ComboEditl.Text,loUserLocale); //еще раз убедимся, что такое слово есть for i:= 0 to ComboEditl.Count-1 do if s=UpperCase(ComboEditl.Items[i],loUserLocale) then

~


174

Глава 10 b e g in

ComboEditl.Itemlndex:=i; //выбираем элемент break; end; end; end;

Компонент выбора значения ТРорирВох Основу компонента выбора значения ТРорирВох составляет свойство property Items: TStrings;

предоставляющее доступ к списку строк, из которых пользователь выберет необ­ ходимое текстовое значение. Результат выбора окажется в свойстве property Text:String;

Порядковый номер выбранной строки отобразится в свойстве property Itemlndex: Integer;

В момент смены элемента генерируется событие property OnChange: TNotifyEvent;

Как видите, элемент управления ТРорирВох по своему духу очень близок к класси­ ческому комбинированному списку. Разница заключается лишь в том, что на этот раз список текстовых элементов не выпадает из компонента, а всплывает над ним (рис. 10.6). Берлин Бухарест ■я

ф

. Демонстрация ю м япоие*'

Варшава

— ------------------------------------------

Вена Киев

Компонент ТРорирВох

Лондон Мадрид Минск *

fo o t ■ ------------------------------------------------------- -------------------

©t Демонствация компонентов FireMonkey

Компонент ТРорирВох

Р и с . 10.6. Внешний вид компонента ТРорирВ ох


Г Л А В А 11

Иерархическая структура В окружающем нас мире иерархические структуры распространены весьма широ­ ко: это и структура предприятия, и дерево каталогов на жестком диске компьютера, и иерархия наследования классов FireMonkey. Можно продолжать приводить мно­ гочисленные примеры иерархически организованных данных, но лучше сразу за­ острить свое внимание на их объединяющей черте — наличии между элементами данных отношения "главный— подчиненный" (родительский— дочерний). Еще один важный отличительный признак иерархических структур заключается в том, что родительский узел дерева способен обладать неограниченным числом дочерних узлов. В свою очередь любой дочерний узел имеет право выступать владельцем целой ветви подчиненных узлов. Тот факт, что структура дерева заранее неизвест­ на, значительно затрудняет хранение иерархии в памяти и осуществление операций с узлами дерева. К счастью, в составе FireMonkey имеется элемент управления, с легкостью решающий задачу обслуживания иерархических данных: главный и единственный герой этой главы — компонент TTreeview. Компонент TTreeview (если следовать дословному переводу — дерево просмотра) представляет собой логическое объединение двух классов и интерфейса: □ компонент-дерево TTreeview, обеспечивающий визуализацию иерархической структуры и пользовательский интерфейс; □ класс TTreeviewitem, на котором строится узел иерархии; □ интерфейс utemsContainer позволяет как дереву, так и узлу дерева выступать в роли контейнера для других элементов. Дерево TTreeview является прямым вла­ дельцем узлов, описывающим самый верхний уровень иерархии. Узлы верхнего уровня способны выступать в роли главного узла по отношению к подчиненным узлам, которые, в свою очередь, владеют узлами следующей ступени. Если дерево изначально статично и состав его узлов не должен изменяться в ходе выполнения приложения, то для создания иерархии элементов проще всего вос­ пользоваться встроенным редактором Item s Designer, вызываемым из контекстного меню размещенного на форме компонента TTreeview (рис. 11.1). Однако мы с вами не станем искать простых путей и в этой главе напишем код для дерева, способного динамически создавать свои узлы.


Гпава 11

176

►TneeViewIteml ►ХптУ1ш1шгп2 г -J ;

i Items Designer ► TrteViewilteml

fJrecVwwltem

4 b'mYmvskmmZ Add Hem

TrfefeViewItemS

4

Add Ch*id Item TreeVi«wltem?

V

Tt»V*w!temS

Delete

Рис. 11.1. Редактор элементов Items Designer

Узел дерева TTreeViewltem Все элементы иерархической структуры создаются на основе класса TTreeViewltem, который в свою очередь является потомком текстового класса TTextControl. От своего текстового предка элемент иерархической структуры унаследовал базовое свойство property Text: string;

благодаря которому узел приобретает возможность хранить и отображать тексто­ вую надпись и ряд (уже знакомый нам по главе 9 ) свойств и методов, нацеленных на обслуживание текстовой надписи.

Управление дочерними узлами Все иерархические структуры строятся по принципу "главный — подчиненный", который определяет степень зависимости между узлами дерева. Каждый узел дере­ ва имеет право владеть произвольным набором дочерних элементов, доступ к кото­ рым реализуется с помощью свойства property Items[Index: Integer]: TTreeViewltem;

Точно такой же результат можно получить, передав индекс в метод fu n c tio n

ItemBylndex( c o n s t Idx: Integer): TTreeViewltem;

В OTBeT функция возвратит ссылку на запрошенный узел. Сведения о количестве подчиненных узлов хранит свойство Property Count: Integer; //только

ДЛЯ

чтения


Иерархическая структура

177

ВНИМАНИЕ!

Свойство I t e m s и метод i t e m B y I n d e x () отвечают за предоставление доступа только к непосредственно подчинвнным узлам (находящимся на следующем уровне п о с л е родительского узла). Это же замечание справедливо и для свойства Count.

При необходимости видимый узел можно идентифицировать по его экранным ко­ ординатам (х, y ), д л я этого потребуется помощь метода fu n c tio n

I t e m B y P o i n t (const X,

Y:

Single):

TTreeViewItem;

Дочерние узлы владеют информацией о своем родительском узле, она доступна благодаря методу fu n c tio n

Parentltem:

TTreeViewItem;

Благодаря свойству P a r e n t l t e m несложно проконтролировать всю цепочку роди­ тельских узлов для текущего узла. Например, предложенная в листинге 11.1 функ­ ция G a t P a t h O может применяться в приложении, которое использует компонент T T r e e V i e w в качестве дерева каталогов (рис. 11.2). Получив в качестве параметра ссылку на выбранный пользователем узел item, функция (перебирая последова­ тельность предков узла) построит полный файловый путь. &

Поиск фзйлое-дубдасгтов

Всего: 155 файжм-дуб/иеатое

C:\exanpies\ аьсл

i

*

gjSRecycSe.Sin

jl

О

£

|1

S’ ] &e*»m(te\e>SS.S7\e*06.07.epJoj.loc*(

cbcaia

^ Config M u

|b

й

Crt*XBmpses\esei56_06\ex06_06jJprcr^tocas

11

й

С:\еиатс<е5\«01.01\е)г0й.01Лр'О!.1оса1

jp} Documents and Settinss

1 .1 О

CAexamp!es\e*06_06\e*0t.06Jcon.(«>

gftlntei

®

С\всатрг*5\е*06.07\вЮ6.07.!с®г.ко

g}inter3«eXE3 ToGo

_|1 Группа файлов-дубяикатое, в кгжяем ф а й » $76 бгяго»)

gglazsrus

В

C:\ei5mples\fxll.02\exll_02.res

e

MSOCacne

В

C:\exswpiessexll.D3\eiil.03,'es

Prtlogs

В

C:\eamptes\txU_[>8ve)cll_Q8/es

...... ...... ..... i .

j

* ---

С:\еятр«5\«й9.11\е*гЭ_П.йрг<».1оа!

KCsfip

.

1

fy Группа ©айлев-дубликагое. e каждом файле '62 байтов)

Групп» фгйлов-дублхытов, 5 каждом файле {766 байтов}

*

Выбрано 7 файлов общи» размером 0 M B

gfj Ргод'зп Files jj# Program Fifes (*S6> fgPrasramData

,

1

1ЕГ~о»рт~Т!

Рис. 11.2. Компонент T T r e e V i e w в качестве дерева каталогов

»— ...... 'щщж.. i • 11.1.1

■ и ж ш ....н ш а н ..... m ...... :.... ’......... Г.о кррнёц oro узла

-

function T F o r m l .G e t P a t h (const var T e m p : T T r e e V i e w I t e m ; begin Temp:=Item; R e s u l t : = 1 ';

repeat

Item:

.......... ................ ....... .......................................

TTreeViewItem):

string;


Гпава 11

178

if temp.Parentltemonil then //пока есть родительский узел Result:=temp.Text+TPath.DirectorySeparatorChar+Result //уровень папок else Result:=temp.Text+Result; //уровень дисков Temp:=Temp.Parentltem; until Temp=nil;

Узлы самого верхнего уровня являются прямой собственностью дерева TTreeview, впрочем, каждому узлу известно, какому компоненту он принадлежит. Такие све­ дения можем получить и мы, запросив помощь у метода function TreeView: TCustomTreeView;

Для добавления нового узла самого верхнего (первого) уровня следует задейство­ вать метод procedure AddObject(AObject: TFmxObject); override;

Изъятие дочернего узла осуществит метод procedure RemoveObject(AObject: TFmxObject); override;

Положение узла в дереве Существует несколько взаимодополняющих способов определения положения узла в дереве. Во-первых, узел характеризуется уровнем, о котором нам расскажет метод function Level: Integer;

При обращении к узлу самого верхнего уровня функция возвратит 1, при обраще­ нии к узлу первого вложения — 2, и т. д. Все дочерние узлы знают свой порядковый номер в списке узла-владельца. Об этом заботится свойство p ro p e rty

Index: Integer;

Свойство index сохраняет работоспособность и для узлов самого верхнего уровня (их владельцем выступает компонент TTreeview). Кроме локального индекса узел обладает и глобальным номером, определяемым свойством p ro p e rty

Globallndex: Integer;

Свойство доступно не только для чтения, но и для записи, что позволяет переме­

щать узел. Стоит отметить, что глобальный индекс отражает порядковый номер видимого узла, если же узел невидим (например, свернут В составе родительского

узла), то он исключается из нумерации. Внимание!

После изменения положения узла (например, после процедуры сортировки) его ин­ дексы изменяются!


Иерархическая структура

179

Состояние узла Узел дерева обладает рядом важных характеристик, описывающих его текущее со­ стояние. В первую очередь, это сведения о том, развернут узел или свернут: property IsExpanded: Boolean;

По умолчанию узел свернут, а развернуться способен только узел, обладающий до­ черними узлами. Если в компоненте TTreeview активировано свойство Showcheckboxes (заставляющее каждый узел дерева отображать кнопку-переключатель), то стоит обратить внима­ ние на свойство property IsChecked: Boolean;

позволяющее получить сведения о том, отмечен ли "галочкой" узел, и, при жела­ нии, изменить состояние узла программным способом. Если компонент TTreeview позволяет пользователю одновременный выбор несколь­ ких узлов (см. свойство Multiselect), то проверку факта выбора узла позволит осу­ ществить свойство property IsSelected: Boolean;

Дерево TTreeView Хотя на основе класса TTreeviewitem можно создать вполне самостоятельный объ­ ект, он не сможет в полной мере выполнять возложенные на него задачи до тех пор, пока не попадет в распоряжение компонента TTreeview. Благодаря дереву TTreeview экземпляр класса TTreeviewitem визуализируется и превращается в узел, к которому сможет "прикоснуться" пользователь. Дерево способно централизованно назначить вертикальный размер всем своим элементам, для этого предназначено свойство property ItemHeight: Single;

Выделение узла По умолчанию дерево позволяет пользователю выделить только один узел, если вас это не устраивает, то переведите в состояние true свойство property MultiSelect: Boolean; //по умолчанию false

Для выяснения, какой из узлов выбран, следует проконтролировать свойство property Selected: TTreeviewitem;

Оно возвратит ссылку на выделенный узел. Если дерево допускает одновременное выделение нескольких узлов, то для определения, выделен ли узел, следует контро­ лировать состояние свойства IsSelected у каждого из узлов дерева.


Гпава 11

180

Если вы хотите, чтобы при утрате фокуса ввода компонент альное выделение узла, то установите в t r u e свойство

TTreeview

property

false

HideSelectionUnfocused:

Boolean;

/./по

умолчанию

снимал визу­

В момент выбора узла (как вручную с помощью мыши и клавиатуры, так и про­ граммным способом) генерируется самое главное событие компонента property

OnChange:

TNotifyEvent;

Доступ к узлу Компонент T T r e e v i e w на правах владельца иерархической структуры способен об­ ратиться к любому принадлежащему ему узлу. Если речь идет об узлах самого верхнего уровня, то нашим помощником станет свойство property

I t e m s [Index:

Integer]:

TTreeviewitem;

или метод function

I t e m B y l n d e x (const Idx:

Integer):

TTreeviewitem;

Как свойство, так и метод предоставят программисту ссылку на узел первого уров­ ня по его индексу. Если число узлов верхнего уровня неизвестно, то справку по этому вопросу предоставит свойство property

Count:

Integer;//только для чтения

Если нам следует работать с узлами более глубоких уровней вложения, то следует воспользоваться методом function

I t e m B y G l o b a l I n d e x (const Idx:

Integer):

TTreeviewitem;

Функция возвратит экземпляр узла по его глобальному индексу, но с одной суще­ ственной оговоркой — этот узел должен быть видимым. Число видимых элементов в дереве известно свойству property

GlobalCount:

Integer;

Кроме того, доступ к видимому узлу по его экранным координатам позволит осу­ ществить метод function

I t e m B y P o i n t (const X,

Y:

Single):

TTreeviewitem;

Перечень способов доступа к узлу завершает метод function

I t e m B y T e x t (const A T e x t :

string):

TTreeviewitem;

На этот раз поиск узла производится по содержимому его свойства

Text.

Управление составом узлов Если логика создаваемого приложения предполагает необходимость динамического изменения состава узлов, то разработчику программы не обойтись без услуг ме­ тодов procedure AddObject(AObject: TftnxObject); override; procedure RemgyeQfejgGt(AObject: TRnxObject); override;


Иерархическая структура

Первая процедура добавит узел рая — удалит указанный узел.

а о ь ject

в самый верхний уровень иерархии, а вто­

Внимание!

Методы AddObject () И RemoveObject () универсальны и способны работать не только С узлами TTreeViewltem, НО И С ЛЮбЫМИ ДРУГИМИ ОбЬеКТЯМИ ИЗ бИбЛИОТвКИ FMX.

Наиболее кардинальными возможностями обладает метод procedure Clear;

Он позволяет полностью освободить дерево от узлов. Решение идний из задач, демонстрирующей процесс динамического заполнения узлами дерева Treeviewl, предложено в листинге 11.2. В данном примере мы соби­ раем сведения о доступных логических дисках компьютера с Windows или папках верхнего уровня для станции с OS X и передаем сведения в иерархическую струк­ туру. а

ж

: ~

"".уу.jaS? ; : ' “ у г г у ................................................ ’ .

iitT .......................................................................................................................................................................... ........................................................................................

! Листинг 11.2. Сбор сведений о дисках/папках компьютера

...............

procedure TfrmMainDublicate.UpdateTopLevelFolders; var SDA:TStringDynArray; Item:TTreeViewltem; i :integer; begin try TreeViewl.BeginUpdate; TreeViewl.Clear; {$IFDEF MSWINDOWS} //------------------------ ДИСКИ WINDOWS --------------------------SDA:=TDirectory.GetLogicalDrives; {$ELSE} {$IFDEF MACOS} //------------ ---- КОРНЕВЫЕ ПОЛЬЗОВАТЕЛЬСКИЕ ПАПКИ OS X ------------SDA:=TDirectory.GetDirectories(TPath.GetHomePath, TSearchOption.soTopDirectoryOnly); {$ENDIF} {$ENDIF} for i :=0 to High(SDA) do begin Item:“TTreeViewltem.Create(Treeviewl); Item-. Text:=IncludeTrailingPathDelimiter (SDA[i] ); Item.Parent:=TreeViewl; Treeviewl.AddObject(Item) ; end; finally Treeviewl.EndUpdate; end; end;

1


Глава 11

182 Внимание!

Чтобы исключить лишние операции перерисовки дерева, операции добавления и уда­ ления узлов целесообразно заключать в программный блок, начинающийся с вызова метода BeginUpdate () и заканчивающийся обращением к методу EndUpdate ().

Воспользовавшись функцией Get Path о из листинга 11.1, мы сможем развить успех и научить дерево не только строить перечень дисков (или папок верхнего уровня OS X), но и собрать сведения о каталогах, размещенных на этих дисках. В листин­ ге 11.3 предложен пример функции, на вход которой поступает ссылка на ассоции­ рованный с диском или папкой узел Ра rent item, в ответ на это функция GetchiidFolders о собирает сведения о подчиненных каталогах и заносит их в де­ рево. истинг 11.3: Сбор сведений о дочерних папках procedure TForml.GetchiidFolders(Parentltem: TTreeviewitem); var SDA:TStringDynArray; Path:String; Item: TTreeviewitem; i :integer; begin TreeViewl.BeginUpdate; while Parentltem.Count>0 do

//удалим все дочерние узлы

Parentltem.Items[0].Destroy; Path:=GetPath(Parentltem); //узнаем файловый путь (см. листинг 11.1) if TDirectory.Exists(Path) then begin SDA:=TDirectory.GetDirectories(Path); //собираем сведения for i:=0 to High(SDA) do //создаем дочерние узлы-каталоги begin Item:=TTreeViewItem.Create(Parentltem); Item.Parent:=ParentItem; Item.Text:= TPath.GetFileName(SDA[i]); Parentltem.AddObject(Item) ; end;

end; TreeViewl.EndUpdate;

end;

Для того чтобы компонент TreeViewl смог превратиться в иерархию дисков и ката­ логов, нам осталось лишь научить приложение воспользоваться функциями из лис­ тингов 11.1 11.3. Для этого понадобится описать н есколько обработчиков coGdiтий формы и компонента-дерева (листинг 11.4).


Иерархическая структура

183

//создание формы-владельца компонента TreeViewl procedure TForml.FormCreate(Sender: TObj ect); begin UpdateTopLevelFolders; //сбор узлов верхнего уровня (см. листинг 11.2) end; //двойной щелчок по узлу дерева procedure TForml.TreeViewlDblClick(Sender: TObject); var Item:TTreeviewitem;

LKAjla Item:=TreeViewl.Selected; if ItemOnil then //если есть вьщеленный узел begin Item.IsExpanded:=NOT Item.IsExpanded; if TreeViewl.Selected.IsExpanded=true then GetchiidFolders(TreeViewl.Selected); (см. листинг 11.3) end; end;

Узел в роли флажка При желании все узлы дерева можно вооружить дополнительным функционалом — возможностью играть роль флажков, в которых пользователь сможет поставить или снять "галочку". Для реализации задуманного достаточно осуществить всего одно действие — установить в состояние true свойство property ShowCheckboxes: Boolean; //по умолчанию false

Зам ечание

Для проверки состояния переключателя следует обратиться к свойству isChecked узла дерева TTreeViewNode.

В момент включения/отключения переключателя у дерева вызывается событие property OnChangeCheck: TNotifyEvent;

в коде которого можно описать реакцию компонента TTreeview на обращение поль­ зователя к переключателю.

Свертывание и развертывание узлов В арсенале методов компонента TTreeview предусмотрены две процедуры, способ­ ные развернуть и свернуть всю иерархию узлов. procedure ExpandAll; procedure CollapseAll;


184

Гпава 11

Картину дополняет свойство дерева property CountExpanded: integer;

позволяющее программисту узнать число развернутых узлов.

Упорядочивание узлов дерева Компонент TTreeview обладает способностью упорядочивать свои узлы. В про­ стейшем случае элементы дерева размещаются в алфавитном порядке своих тек­ стовых заголовков. Для этого следует установить в состояние true свойство property Sorted: Boolean;//по умолчанию false

Если требуется задать более сложные правила сортировки, то стоит обратить вни­ мание на событие property OnCompare: TOnCompareTreeViewItemEvent; TOnCompareTreeViewItemEvent = function(Iteml, Item2: TTreeviewitem): integer of object;

В рамках события следует сравнить параметры двух узлов, iteml и item2, и сделать вывод об их порядке следования по возвращаемому функцией значению.


ГЛАВА 12

Сетки

Очень многие современные программные продукты нуждаются в табличном пред­ ставлении данных, среди них бухгалтерские приложения, статистические и анали­ тические программы, таблицы спортивных турниров, специализированные прило­ жения для различных отраслей научных знаний и, конечно же, базы данных. В составе элементов управления FireMonkey имеются два компонента, специализи­ рующихся на представлении данных в табличном виде. Это компоненты TGrid и TStringGrid. Оба элемента управления объявлены в модуле FMX.Grid и являются наследниками одного и того же опорного класса сеток type TCustomGrid = class(TScrollBox, IltemsContainer)

Основная разница между компонентами в том, что сетка TStringGrid нацелена на обслуживание исключительно текстовых данных, а сетка TGrid — более универ­ сальна и позволяет работать не только с текстом, но и булевыми значениями, гра­ фическими объектами, списками строк. Такое функциональное разделение не­ сколько напоминает ситуацию С сетками TStringGrid И TDrawGrid из библиотеки VCL, однако принцип построения сеток TGrid и TStringGrid абсолютно не похож на архитектуру их коллег из состава VCL. Внимание!

Компоненты-сетки FireMonkey предназначены для визуализации и редактирования данных, но они не способны обеспечить их хранение. Эта проблема ложится на плечи разработчика программного обеспечения.

Колонки сетки Основной строительный элемент сеток FMX — колонка. Колонка — это экземпляр класса TCoiumn или экземпляр одного из потомков этого класса. Например, сетка строк TStringGrid специализируется исключительно на текстовых колонках TStringCoiumn, а сетка TGrid помимо колонок строк умеет обслуживать весьма экзо­ тические колонки (табл. 12.1).


Гпава 12

186

Таблица 12.1. Колонки и ячейки сеток Описание

Тип колонки

Тип ячейки

TStringColumn

TTextCell =

TCheckColumn

TCheckCell =

TProgressColumn

TProgressCell =

TPopupColumn

TPopupCell =

c la s s (ТРорирВох)

Всплывающая панель

TimageColumn

TimageCeii =

c la s s (TImageControl)

Изображение

c la s s (TEdit)

Текст

c la s s (TCheckBox)

Флажок

c la s s (TProgressBar)

Шкала

Для создания колонок во время визуального проектирования проще всего восполь­ зоваться контекстным меню компонента-сетки и выбрать там пункт Item s E ditor. В ответ на это действие Delphi отобразит на экране окно дизайнера Item s D esigner (рис. 12.1). Дальше программист выбирает тип колонки и добавляет колонку в сет­ ку. Колонка является вполне самостоятельным объектом, обладающим свойствами, методами и обработчиками событий. form*

■>•■■■

1

!

............V. У

2

Item j D e strier

TimraeColtfron___ rj

Column1 ChedtCoiumnl S t i in q C o lu m n l

L ____Agdit£fr'_____ 1

ProgressCoiumnl

®3puoCobmnI

i

i 'S' . 1 Dtlate !

Рис. 12.1. Редактор колонок компонента-сетки TGrid

Каждая из ячеек колонки во время редактирования данных интерпретируется как элемент управления определенного класса (см. табл. 12.1). Например, в колонке TStringColumn, специализирующейся на обслуживании текста, каждая из ячеек фак­ тически является, строкой ввода TEdit, а колонка TimageColumn предоставляет дос­ туп к ячейкам TimageCeii, способным обслуживать графические данные. В

ним а н ие

!

Жизненный цикл соответствующих ячейкам элементов управления находится в пря­ мой зависимости от видимости ячейки. Если ячейка исчезает с экрана (например, скрывается за границей сетки после скроллинга), элемент управления удаляется при появлении ячеики элемент воссоздается. Надо понимать, что вместе о ИОч<*»,ODO,’, ассоциированного с ячейкой элементом управления исчезают и все его данные...


Сетки

187

Дпа м с,ида позволяют обратиться к элементу управления, ассоциированному с конкретной ячейкой колонки. fu n c tio n fu n c tio n

CellControlByPoint(X, Y: Single): TStyledControl; CelIGontrоlByRow(Row: Integer): TStyledControl;

Как видите, для идентификации достаточно передать экранные координаты (х, у) ячейки или номер строки Row в колонке. Опорный класс всех колонок TColumn предоставляет своим наследникам минималь­ ный базовый набор свойств и методов. В первую очередь это свойство, описываю­ щее заголовок колонки property Header: string;

Колонка может быть переведена в режим "только для чтения" с помощью свойства property Readonly: Boolean; //по умолчанию false

и скрыта с экрана с помощью свойства property Visible: Boolean;

//по умолчанию true

Сетка TGrid Размер сетки можно уточнить благодаря свойствам property RowCount: Integer; property ColumnCount: Integer; //только для чтения

Обратите внимание на то, что свойство RowCount позволяет изменять число строк в сетке. А свойство ColumnCount доступно только для чтения, т. к. количество коло­ нок в сетке определяется числом объектов TColuim. Доступ к экземпляру колонки проще всего производить по ее индексу property Columns[Index: Integer]: TColumn;

Кроме того, существует метод, идентифицирующий колонку по экранным коорди­ натам function ColumnByPoint(const X, Y: Single): TColumn;

Для доступа к интегрированному в ячейку элементу управления следует вызвать метод, возвращающий ссылку на объект function CellControlByRow(Row: Integer): TStyledControl;

Заметьте,

что

в

данном

случае

возвращается

обезличенный

тип

данных

TStyledControl, который следует привести к требуемому виду при посредничестве

классов TTextCell, TCheckCell, TProgressCell, TPopupCell ИЛИ TImageCell. Во время создания пользовательского хранилища данных окажутся полезными со­ бытия property OnSetValue: TOnSetValue; type TOnSetValue = procedure(Sender: TObject; const Col, Row: Integer; const Value: TValue) of object;


Гпава 12

188

property QnGetValue: TOnGetValue; type TOnGetValue = procedure(Sender: TObject; const Col, Row: Integer; var Value: TValue) of object;

Событие onSetvaiue () генерируется в момент получения ячейкой с координатами Col й Row значения Value. Обратное событие OnGetVaiue () вызывается во время чте­ ния значения value из ячейки. Зам ечание

Используемый в обработчиках событий OnSetValueO и OnGetValueO тип данных System.Rtti. TValue можно рассматривать как усовершенствованный аналог типа данных Variant, обладающий большим набором сервисных методов.

Упомянем еще одно событие сетки, связанное с обслуживанием данных property OnEdititingDone: •TOnEdititingDone; type TOnEdititingDone = procedure(Sender: TObject; const Col, Row: Integer) of object;

На этот раз речь идет о реакции сетки на любое изменение содержимого ячейки. ВниманиеI

Сетка TGrid не обладает навыками по хранению данных — эту задачу программист должен решить самостоятельно, создав собственное хранилище.

Сетка TStringGrid Полезное преимущество сетки строк TStringGrid над обычной сеткой TGrid за­ ключается в том, что разработчики компонента сохранили в нем традиционное (для сетки TStringGrid из состава VCL) свойство p ro p e rty

Cells[ACol, ARow: Integer]:

s trin g ;

Б лагодаря этом у свой ству сетку м ож но рассм атривать как двухм ерны й текстовы й м ассив, в котором каж дая ячей ка адресуется по и н дексу колонки и ряда.

Пример обслуживания текстовых данных Предположим, что в нашем распоряжении имеется типизированный файл area.dat, строки которого соответствуют записи ТАгеа, позволяющей хранить порядковый номер, название страны и сведения о ее площади (листинг 12.1).

type TArea=packed record

num :byte; country:string[40]; area:single; end;

//номер записи //название страны //площадь, занимаемая страной


Сетки

189

Попробуем научить сетку T G r i d отображать и редактировать содержимое файла. Для этого (воспользовавшись редактором колонок Item s E ditor) создайте три ко­ лонки T S t r i n g C o i u m n , специализирующиеся на обслуживании текстовых данных (рис. 12.2). r --------- ----------------

--------------------------------

ф Сетка TGrid №

*=» а

!

Страна „__ „„

Площад», кв. км

\2

Канада

9984670

[7~~

Китай

9596960

[4

Соединенные Штаты Амери 9372610

Ш Г

Л.

m caa

JL

iat

is

Бразилия

8547000

. 16 !7

Австралия

7686850

Индия

3287590

Рис. 12.2. Сетка T G r i d стремя колонками

is

Аргентина

2760990

TStringCoiumn

9

Казахстан

2717300

110

Судан

2505810

111

Алжир

2381740

(12

Конго, демократическая ре< 2345410

|

{ |l3

Саудовская Аравия

2218000

1

jll4

Мексика

1972550

j

Индонезия

1919440 17ВДЧДП

| IS m

Где j?

I....... j

Для работы приложения нам потребуется объявить одну глобальную переменную и одну глобальную константу (листинг 12.2). "■ ■ .... ..

■ ......

.................. ■ ............

I Листинг 12.2. Объявление переменных и констант var

F o r m l : TForml; FS:

const

TFileStream;

//файловый поток

filename='area.dat'; //имя файла

Файловый поток f s появляется на свет в момент создания формы (листинг 12.3). При вызове конструктора потока укажем на то, что поток должен допускать как чтение, так и запись данных в файл. .

| Листинг 12.3. Создание файлового потока procedure T F o r m l . F o r m C r e a t e ( S e n d e r : begin if F i l e E x i s t s ( f i l e n a m e ) then begin

TObject);

FS:=TFileStream.Create(filename,fmOpenReadWrite); Gridl.RowCount:=FS.Size

end else raise end;

div

SizeOf(TArea);

E x c e p t i o n . C r e a t e (' Ф а й л

'+filename+'

не обнаружен!');


Гпава 12

190

Для упрощения кода предположим, что файл с данными расположен в том же ката­ логе, что и исполняемый файл приложения. Если это не так, то вы можете развить идею подключения файла, например, воспользовавшись диалогом TOpenDialog (см. гл а ву 13).

Правила хорошего тона программирования предполагают, что разработчик прило­ жения не забудет уничтожить созданный вручную объект. Для этой цели восполь­ зуемся событием onDestroy () формы (листинг 12.4). >айло~ого пэто procedure TForml.FormDestroy(Sender: TObject); begin FS.Destroy; end;

После того как мы научили файловый поток загружать данные, перейдем к работе с сеткой. Нам предстоит решить две небольших задачи. В первую очередь, подго­ товим сетку к отображению данных из файлового потока. Для этой цели нам при­ годится событие сетки onGetvaiueO (листинг 12.5). Обратите внимание, что для поиска требуемой строки в файловом потоке нам понадобился параметр Row, имен­ но благодаря ему мы позиционируем курсор в необходимом месте потока.

procedure TForml.GridlGetValue(Sender: TObject; const Col, Row: Integer; var Value: TValue); var A:TArea; begin F S .S e e k ( R o w * S i z e O f ( T A r e a ) , s O B e g i n n i n g ) ; / / п о з и ц и о н и р у е м к у р с о р F S . R e a d ( A , S i z e O f ( T A r e a ) );

case 0: 1.' 2: end; end;

//читаем данные ИЗ файла

of Value:=TValue.FromVariant(A.num);

Col

Vilue:=TValue.FromVariant (A. country); Value:=TValue.FromVariant(A.area);

Вторая задача связана с обеспечением возможности редактирования данных в сетке и сохранения их в файл, на этот раз воспользуемся событием OnSetValueO (лис­ тинг 12.6). ... ] procedure TForml.GridlSetValue(Sender: TObject;

SSUSt Col, Row: Integer; const Value: TValue); var A:TArea;


Сетки

191

begin FS.Seek(Row*SizeOf(TArea)rsoBegirming); FS.Read(A,SizeOf(TArea)); FS.Seek(Row*SizeOf(TArea),soBeginning); case Col of 0: A.num:=Byte(Value.Aslnteger); 1: A.country:=Value.AsString; 2: A.area:=Value.AsVariant; end;

//переходим //считываем //переходим //считываем

в позицию записи старые данные строки в позицию записи исправления

FS.Write(A,SizeOf(TArea)); //записьваем новое значение end;

Программирование завершено. Мы научили сетку визуализирх^ И рёДЭКТНрОБЭТЬ данные, хранящиеся в типизированном файле.


Г ЛА В А 13

Окна сообщений и диалоги Любой интегративный программный продукт нуждается и в обратной связи с поль­ зователем. В первую очередь этот контакт необходим для уведомления пользовате­ ля о завершении определенной операции или для подтверждения какой-либо команды. В большинстве случаев для общения с оператором компьютера приложение выво­ дит на экран диалоговые окна. Диалоговое окно обладает весьма навязчивым ха­ рактером, и для того чтобы гарантированно достучаться до пользователя, в подав­ ляющем числе случаев оно выводится на экран в модальном режиме поверх всех окон приложения. Такой на первый взгляд назойливый способ общения гарантиру­ ет, что даже самый невнимательный пользователь уделит диалоговому окну хотя бы немного своего драгоценного времени, тем более что пока пользователь не за­ кроет окно, он не сможет вернуться к работе с программой. В этой главе мы обсудим несколько категорий диалоговых окон, от простейших окон сообщений и окон ввода текстовой информации до сложных диалогов доступа к файлам. Зам ечание

§

ёИбПИВТвКв FireMonkey программный к о д

о ко н с о о б щ е н и й и д и а л о го в с о с р е д о ш ч е н

в м о д у л е FM X. D i a l o g s .

Окна сообщений Самое простое, что может сделать программа — оповестить пользователя о каком-

либо событии текстовой строкой. Окно с текстовым сообщением выводится про­ цедурой); p ro c e d u re

ShowMessage( c o n s t Msg:

s trin g

В результате, если речь идет о Windows, то ровно в центре рабочего стола появится ОКНО е текстом, который В Ы ранее передали в параметр M s g , в случае OS X окно со­ общения отобразится под заголовком окна приложения.


О

сообщений и диалоги

193

Если вы пишете приложение для Windows, то, возможно, вместо showMessage () вам больше понравится процедура procsdura ShowMessagePos(const Msg: string; X, Y: Integer);

Процедура позволяет управлять местом вывода сообщения. Для этой цели в распо­ ряжение программиста передаются параметры (х, y ), задающие координаты левого верхнего угла окна. Если текстовое сообщение окна достаточно сложное, например содержит разно­ типные значения, то следует обратиться к процедуре procedure ShowMessageFmt(const Msg: string; Params: array of const);

Форматирование осуществляется в соответствии с правилами форматирования строк, применяемыми в функции Format (): в первый параметр передается шаблон сообщения с форматирующими символами, во второй параметр направляется мас­ сив с данными. Пример работы с процедурой предложен в листинге. 13.1. * Листинг 13.1. Пример вывода сообщения со сложным форматированием re s o u rc e s trin g

M s g = * Сложное

............ ;

сообщение содержит:' +

#13+'текст - %s'+#13+'целое - %d'+#13+'вещественное - %f'; var s :string; i :integer; r :real; begin s :='строка текста'; i:= 5 5 5 ;

r:=111/13; ShowMessageFmt(Msg,[s,i,r]); end;

В результате выполнения листинга на экране компьютера появится диалоговое окно, представленное на рис. 13.1 (слева для OS X, справа для Windows).

А

Ж £ *Мт

Сложное сообщение садершсг: текст - строка текста цеяое - SSS m m ttttm m * ~ $.54

1 Слсжное сое& де^е содерж а тает ■с-ре*э т*<с?а целое - 555 - 5,-54

k

Рис. 13.1. Пример окон сообщений, полученных с помощью процедуры ShowMessageEtat ()

«_J


194

Гпава 13

Окна выбора действия Все

рассмотренные ранее процедуры ShowMessageO, ShowMessagePos о , ShowMessageFmt () предназначены лишь для уведомления пользователя о том или ином событии и никак не влияют на дальнейшую логику выполнения приложения. В том случае, когда операция требует, чтобы пользователь подтвердил ее выполне­ ние, следует искать помощи у функции MessageDigO . В Delphi предусмотрены две перегружаемые версии этой функции, мы рассмотрим самую сложную: funotion MessageDlg(const “Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; HelpCtx: Longint; DefaultButton: TMsgDlgBtn): Integer; overload;

В результате обращения к функции в системе Windows на экран выводится диало­ говое окно, представленное на рис. 13.2. Параметр Msg содержит текст сообщения Параметр

•*Ql) Confirm

DlgType

определяет значок и текст заголовка окна

Ою-о выбор* Предогдет осуществит» («бор и наж4т» соответствующую «лотку

Параметр Buttons

tifiШЛ

назначает кнопки окна

Параметр DefaultButton указывает на кнопку по умолчанию Рис. 13.2. Окно выбора действия, вызванное функцией MessageDlg () в Windows

В OS X внешний вид окна несколько отличается (рис. 13.3), однако функциональ­ ная нагрузка остается прежней. В параметре Msg задается текст сообщения. Параметр DlgType определяет внешний вид диалогового окна В соответствии С ТИПОМ TMsgDlgType (табл. 13.1).

tnformniion Окно чбоп I П редлагает осуществить ш о ор и нажать соответствующую кнопку

Cancel

No

Рис. 13.3. О кно выбора действия, вызванное функцией M e s s a g e D lg () в OS X


Окна сообщений и диалоги

195 Таблица 13.1.

Оформление окна выбора

Значение

Особенности оформления окна

mtWarning

Тревожное оповещение

mtError

Оповещение об ошибке

mt Information

Информационное сообщение

mtConfirmation

Запрос подтверждения

mtCustom

Пользовательский вид

T M

s g D lg T y p e

Любое диалоговое окно как минимум содержит хотя бы одну кнопку, нажав кото­ рую пользователь известит программу, что он ознакомился с сообщением. Еще ча­ ще в окне диалога располагается несколько кнопок, нажатие которых определяет дальнейшее поведение программы. Например, окно, спрашивающее у пользователя подтверждение на удаление файла, должно содержать кнопки Да (Yes) и Нет (No). Нажатие кнопки Да подтверждает операцию, кнопка Нет отвергнет. В код про­ граммы диалоговое окно возвращает модальный результат, соответствующий на­ жатой пользователем кнопке: кнопка Д а - константа mrYes, кнопка Нет — mrNo, и т. д. Задачей программиста является обработка этого результата. Какие имен­ но кнопки будут размещены в нижней части окна, определяет параметр Buttons:TMsgDlgButtons. type TMsgDlgBtn = (mbYes, mbNo, mbOK, mbCancel, mbAbort, mbRetry, mblgnore, mbAll, mnNoToAll, mbYesToAll, mbHelp); TMsgDlgButtons = set of TMsgDlgBtn;

Параметр функции Heipctx предназначен для взаимодействия со справкой прило­ жения, в нем задается индекс соответствующей диалоговому окну страницы справ­ ки. Если справка отсутствует, то в параметр передается -1 . Параметр DefaultButton определяет, какая из кнопок окна будет находиться в фокусе ввода в момент выво­ да окна на экран. В листинге 13.2 приведен пример процедуры, удаляющей файл с именем FileName. Файл исчезнет с жесткого диска только в том случае, если пользователь подтвердит удаление нажатием кнопки Да.

procedure FileDelete(FileName : string); begin if MessageDlg('Удалить файл '+TPath.GetFileName(FileName)+' ?', TMsgDlgType.mtConfirmation, [TMsgDlgBtn.mbYes] + [TMsgDlgBtn.mbNo], 0) = mrYes then TFile.DeleteFile(FileName); end;


196

Гпава 13

По умолчанию в Windows диалоговое окно выбора позиционируется в центре экра­ на. Для того чтобы нарисовать окно в альтернативном месте, используйте функцию function MessageDlgPos(const Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; HelpCtx: Longint; X, Y: Integer): Word;

Все параметры метода нам уже знакомы, единственная новость — координаты ле­ вого верхнего угла окна определяются аргументами х и у. Еще одна похожая функция function MessageDlgPosHelp(const Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; HelpCtx: Longint; X, Y: Integer; const HelpFileName: string; DefaultButton: TMsgDlgBtn): Integer;

позволяет динамически подключать файл со справкой прямо к окну диалога в мо­ мент его создания.

Окна ввода данных Очень часто для работы приложения недостаточно получать односложные ответы пользователя: "Да", "Нет" или "Отмена". Программе могут потребоваться фамилия пользователя, адрес электронной почты, пароль или другие данные. В таком случае окажется полезной функция function InputBox(const ACaption, APrompt, ADefault: string): string;

В результате вызова InputBox о создается форма, содержащая строку ввода и две кнопки: О К и О тм ена (Cancel). В параметре ACaption определяется заголовок диалогового окна, APrompt содержит надпись над строкой ввода, в ADefault пере­ дается значение по умолчанию. Пример работы с функцией предложен в листин­ ге 13.3. 13.3. Деме.

,)

var s: string; begin s := InputBox('Создание файла', 'Имя файла', 'Новый файл.txt');

//. . . Еще более удобная, на взгляд автора, функция function InputQuery(const ACaption, APrompt: string; var Value: string ): Boolean;

возвращает не только содержимое текстовой строки (для этого предназначен пара­ метр Value), а еще и логическое значение, соответствующее нажатой пользователем кнопки: O K — true, О тм ена (Cancel) — false. Эта особенность функции позволяет улучшать код наших программ (листинг 13.4).


197

Окна сообщений и диалоги

1 Листинг 13.4. Демонстрация функции InputQusryC) Д В » *.ДВ Е” *.~ .

ВДШ1flWtJu. * .Г .. -dSSR. > . : . ; * $ > » •

**-

I.v.'rr* \,...тг....... .л..-. -

........

.

var s: string; begin s := 'Новый пользователь'; if InputQuery('Регистрация', 'Фамилия', s) = true then {действия, если нажата кнопка OK} else {действия, если нажата кнопка Cancel};

Компоненты-диалоги Открыв страницу Dialogs палитры компонентов FireMonkey, вы обнаружите пять компонентов-диалогов. Два компонента, TOpenDiaiog и TsaveDiaiog, вызывают диа­ логовые окна открытия/сохранения файлов. Диалоги TPrinterSetupDiaiog и TPrintDiaiog соответственно помогут подготовить принтер к печати и отправить задание на печать. Настройку размера, полей и ориентации страницы документа осуществит компонент TPageSetupDialog. Все компоненты-диалоги построены на основе класса TCommonDiaiog, важнейшим методом которого считается function Execute: Boolean;

Вызов потомками TCommonDiaiog функции Execute () приводит К ВЫВОДУ на Экран стандартного диалогового окна, работая с которым пользователь взаимодействует с операционной системой. При нажатии кнопки О К в окне диалога функция воз­ вращает значение true, в противном случае — false. В классе описаны два базовых обработчика событий property OnShow: TNotifyEvent; property OnClose: TNotifyEvent;

вызываемых соответственно в момент показа и закрытия диалогового окна.

Открытие и сохранение файлов TOpenDiaiog и TSaveDialog Для вызова стандартных диалоговых окон, осуществляющих выбор имени файла для обеспечения дальнейшего чтения или записи, предназначены компоненты TOpenDiaiog И TSaveDialog. Зам ечание

Не стоит понимать названия диалогов открытия и сохранения файлов буквально. Все эти компоненты всего лишь позволяют пользователю выбрать имя файла для его от­ крытия (сохранения), собственно программную логику открытия (сохранения) файла следует реализовывать самостоятельно.


198

Глава 13

При описании диалога открытия или сохранения файла первым действием про­ граммиста должно быть определение ограничений на имя допустимых файлов. При отображении диалоговое окно на основе фильтра property Filter: string;

произведет отбор только необходимых пользователю файлов. Фильтр может быть настроен как в Инспекторе объектов, так и во время выполнения программы. До­ пустим, для приложения, повторяющего функционал Блокнота, фильтр диалогов открытия и сохранения файлов можно подготовить в момент создания главной формы приложения (листинг 13.5). . с г

-

:

procedure TForml.FormCreate(Sender: TObject); begin OpenDialogl.Filter:='Текстовые файлы|*.txt)'; OpenDialogl.DefaultExt:='txt'; SaveDialogl.Filter:=OpenDialogl.Filter; SayeDialogl.DefaultExt:= OpenDialogl.DefaultExt; end;

Обратите внимание на то, что текстовая строка фильтра включает два раздела: тек­ стовое описание фильтра и допустимое расширение в имени файла, а между разде­ лами устанавливается специальный символ | . Допустимо определение сразу нескольких типов файлов в одной строке фильтра: OpenDialogl.Filter := 'Графические файлы| *. jpg; *. jpeg; * .prig';

Если требуется программным образом описать несколько строк фильтра, то отде­ ляйте строку от строки все тем же символом-разделителем — вертикальной чер­ той ( |): OpenDialogl.Filter:= 'Текстовые файлы|*.txt|Все файлы I*.*)';

В листинге 13.5 упоминается еще одно свойство диалога: property DefaultExt: string;

Предполагается, что при вводе имени файла в окне диалога пользователь должен ввести не только имя, но и расширение имени файла. В случае если этого не было сделано, диалог подставляет расширение по умолчанию, заранее определяемое в свойстве DefaultExt. Внимание! П ри вво д е р а сш и р е н и я им ени по ум о л ча ни ю по м ните, что он о вкл ю ча е т то л ько си м ­ в о л ы р а с ш и р е н и я , то ч ку -р а з д е л и те л ь у ка зы в а ть не сл е д уе т.

Если используемый ф ильтр— многострочный, то следует определить, какая из строк фильтра станет использоваться по умолчанию при появлении диалогового окна. Индекс этой строки передается в свойство property Filterlndex: Integer;


Окна сообщений и диалоги

199

Для определения имени папки, открываемой в момент первого запуска диалога, воспользуйтесь свойством property

InitialDir:

string;

При повторных запусках выбор каталога зависит от флага диалогового окна (табл. 13.2).

ofNoChangeDir

в свойстве

Options

Свойство property

HistoryList:

TStrings;

позволяет хранить историю о предыдущих прочитанных/записанных файлах. Текст, подлежащий выводу в заголовке диалога, определяется в свойстве property

Title:

string;

Наиболее широкий спектр по настройке внешнего вида диалогового окна предос­ тавляет свойство property

Options:

TOpenOptions;

выступающее множеством опций

TOpenOption,

представленных в табл. 13.2.

Таблица 13.2. Основные опции диалогов открытия и сохранения файлов

Значение TCpenCption

Описание

ofReadOnly

Открывает окно в режиме "только для чтения"

ofOverwritePrompt

Играет роль в диалогах записи файлов, запрашивает разре­ шение на перезапись при совпадении имени сохраняемого и существующего файлов

ofHideReadOnly

Скрывает флажок R e a d o n l y

ofNoChangeDir

Если флаг установлен, то при повторных запусках диалога он открывает папку, определенную в свойстве InitialDir. Иначе будет открыт каталог, с которым велась работа в последний раз

ofShowHelp

Дополняет диалог кнопкой помощи

ofNoValidate

Отключает контроль за вводом недопустимого символа в имя файла

ofAllowMultiSelect

Допускает одновременный выбор нескольких файлов

o f E x t e n s i o n D i fferent

Наличие флага обычно контролируется программистом во время выполнения приложения. Он автоматически устанавли­ вается приложением в случае, когда расширение выбранного файла отличается от заданного в свойстве Defau l t E x t

ofPathMustExist

Вызывает сообщение об ошибке, если пользователь указал неверный путь к файлу

ofFileMustExist

Используется только в диалогах открытия файла. Вызывает сообщение об ошибке, если пользователь указал имя несуще­ ствующего файла

ofCreatePrompt

Работает совместно с ofFileMustExist, запросит подтвер­ ждение на создание несуществующего файла

в


200

Гпава 13 Таблица 13.2 (окончание)

Значение TOpenCption

Описание

of S h a r e A w a r e

Разрешает совместный доступ к одному и тому же файлу. Если флаг отключен, то запрещает открывать файл, уже используемый другим процессом

ofNoReadOnlyReturn

Инициирует сообщение об ошибке при попытке обратиться к файлу с атрибутом "только для чтения"

ofNoTestFileCreate

Опция применяется при работе с разделяемым сетевым ресурсом, в котором пользователь обладает правом создания новых файлов, но не может модифицировать существующие. Флаг отключает проверку возможности записи в сетевом ресурсе

ofNoNetworkButton

Скрывает кнопку доступа к сетевому ресурсу (используется только совместно с флагом ofOldStyleDialog)

ofNoLongNames

Показывает файлы с форматом имени 8.3 (используется только совместно с флагом ofOldStyleDialog)

ofOldStyleDialog

Флаг обратной совместимости, создает диалоговое окно старого стиля

ofNoDereferenceLinks

Определяет порядок взаимодействия с ярлыками файлов. Если флаг отключен, то диалог возвращает реальное имя файла, определенное в ярлыке. Иначе диалог возвращает имя ярлыка

ofEnablelnclucteNotify

Используется для активизации обработчика события O n l n c l u d e l t e m ()

ofEnableSizing

Разрешает изменять размеры диалогового окна

ofDontAddToRecent

Пока не используется, в будущих версиях Delphi будет управ­ лять добавлением ссылки на файл в список недавно исполь­ зованных документов

ofForceShowHidden

Пока не используется, в будущих версиях Delphi станет при­ нудительно включать показ скрытых файлов в окне диалога

Стоит напомнить, что фундаментальным методом диалогов открытия и сохранения файла является функция E x e c u t e о . Она осуществляет вызов диалогового окна, в котором пользователь производит выбор файла для открытия или указывает имя сохраняемого файла. Если после работы с диалогом была нажата кнопка О К , метод вернет true. После успешного вызова диалогового окна имя выбранного файла окажется в свойстве % property

FileName:

TFileName;

Допустим, что мы разрабатываем проект текстового редактора. На главной форме проекта F o r m i расположены диалоги открытия и сохранения файлов, редактор мно­ гострочного текста м е т о 1 : т м е т о . За вызов диалога открытия файла отвечает собы­ тие-щелчок по пункту меню m i o p e n , диалог сохранения вызывается щелчком по кнопке m i S a v e (листинг 13.6).


201

Окна сообщений и диалоги

ч-.v a r FileName:string; //переменная для хранения имени файла procedure TForml.miOpenClick(Sender: TObject); //открытие файла begin i f OpenDialogl.Execute then begin Memol.Lines.Clear; Memol.Lines.LoadFromFile(OpenDialogl.FileName); FileName:=OpenDialogl.FileName; Text1.Text:='Документ: '+ExtractFileName(FileName);

end;

prooedure TForml.miSaveClick(Sender: TObject); begin i f SaveDialogl.Execute then begin FileName:=SaveDialogl.FileName; i f FileNameO' ' then Memol.Lines.SaveToFile(FileName);

end; end;

Щ елчок по меню misave вызывает метод Execute () диалога открытия файла, в ре­ зультате на экране вашего компьютера отобразится окно, позволяющее пользовате­ лю выбрать текстовый файл. Теперь уделим немного времени обработке событий в компонентах открытия и со­ хранения файла. Оба компонента унаследовали события OnShow () и Onciose () от своего предка — класса TComonDiaiag. Помимо этого во всех диалогах имеется ряд вспомогательных событий (табл. 13.3). Таблица 13.3. События диалогов открытия и сохранения файлов

Событие

Описание

property OnFolderChange: TNotifyEvent;

Происходит при открытии или закрытии папки в диалоговом окне

property OnSelectionChange: TNotifyEvent;

Возникает при выборе пользователем но­ вого файла в списке файлов, применении нового фильтра, создании новой папки

ft property OnTypeChange: TNotifyEvent;

Вызывается при установке пользователем нового фильтра

property OnCanClose: TCloseQueryEvent; type TCloseQueryEvent = procedure (Sender: TObject; var CanClose: Boolean) o f object;

Происходит при попытке закрыть диалого­ вое окно (без отмены, т. е. без нажатия кнопки Отмена (Cancel)), параметр CanClose разрешает (true) или запрещает (false) закрытие окна


202

Глава 13

Параметры страницы TPageSetupDialog Диалог настройки параметров страницы предоставляет удобный интерфейс управ­ ления основными параметрами бумажной страницы и может пригодиться в проек­ тах текстовых и графических редакторов, при определении основных характери­ стик бумажных отчетов в проектах баз данных и при подготовке документа к печа­ ти. Обращение к стандартному для всех диалогов методу Execute () вызывает окно настройки страницы перед печатью. Работу с параметрами страницы следует начинать с определения размеров страни­ цы. Для удобства пользователя диалоговое окно обладает раскрывающимся спи­ ском Разм ер, в котором хранятся элементы с заранее предустановленными разме­ рами (АЗ, А4, А5 и т. п.), ориентация страницы выбирается в группе О риентация. Настроенные пользователем высота и ширина страницы передаются в пару свойств: property PageHeight: Single; property PageWidth: Single;

Используемые в диалоге единицы измерения определяются свойством property Units: TPageMeasureUnits; type TPageMeasureUnits = (pmDefault,

//локальные установки системы pmMillimeters, //миллиметры pmlnche s); II дюймы

Кроме размеров страницы диалог позволяет пользователю определять поля. Для этого предназначена четверка свойств: property MarginLeft: Integer;

//левый отступ property MarginRight: Integer; //правый отступ property MarginTop: Integer; //верхний отступ property MarginBottcm: Integer; //нижний отступ

Минимально допустимые значения полей ограничиваются соответствующими свойствами: property MinMarginLeft: Integer; property MinMarginRight: Integer; property MinMarginTop: Integer; property MinMarginBottom: Integer;

Особенности диалогового окна определяет классическое свойство property Options: TPageSetupDialogOptions;// [psoDefaultMinMargins]

Опции представлены в табл. 13.4. Таблица 13.4. Опции диалога TPageSetupDialogOptions Опция

Описание

psoDefaultMinMargins

Определяет ограничение на минимальное значение полей страницы, которые могут быть назначены пользователем. Значения определяются установками текущего принтера


Окна сообщений и диалоги

203 Таблица 13.4 (окончание)

Опция

Описание

psoDisableMargins

Запрещает пользователю настраивать поля страницы

psoDisableOrientation

Запрещает изменять ориентацию страницы

psoDisablePagePainting

Отменяет режим прорисовки диалога по умолчанию

psoDisablePaper

Запрещает изменять размер бумаги и настраивать особен­ ности ее подачи

psoDisablePrinter

Отключает кнопку вызова дополнительного диалога настройки принтера

psoMargins

Устанавливает все поля страницы в 1 дюйм

ps o M i n M a r g i n s

Устанавливает минимальные поля из свойств MinMarginLeft, и MinMarginTop, в против­ ном случае эти поля определяются параметрами принтера

M i n M arginRight, M i n M a r g i n B o t t o m

psoShowHelp

Показывает кнопку помощи

psoWarning

Отключает сообщение об ошибке при отсутствии установлен­ ного принтера

psoNoNetworkButton

Скрывает и отключает кнопку сетевых устройств

Настройка печати TPrinterSetupDialog Класс T P r i n t e r S e t u p D i a l o g вызывает стандартное окно настройки параметров прин­ тера. Из всех существующих в Delphi компонентов-диалогов диалог настройки принтера самый неприхотливый в программировании. После выбора компонента . в Инспекторе объектов мы обнаружим 5 опубликованных свойств (Name, Tag, H e l p C o n t e x t , B i n d i n g N a m e И S t y l e N a m e ) И 2 события ( O n C l o s e И OnShow). СТОЛЬ CKpOMный список свойств объясняется тем, что все сделанные пользователем настройки принтера не возвращаются в наше приложение, а автоматически обрабатываются системой и учитываются при отправке задания на печать.

Отправка задания на печать TPrintDialog В сравнении с диалогом настройки принтера диалог печати задания отличается су­ щественным спектром параметров. Как и у всех диалогов, любимым методом диа­ лога печати является функция E x e c u t e (). В результате на экране компьютера воз­ никнет стандартное диалоговое окно, позволяющее выбрать принтер, установить диапазон распечатываемых листов и число копий. Внимание!

Диалог печати только формирует параметры задания для печати, сам процесс печати должен быть реализован силами программиста. Помощь в этом ему сможет оказать программный модуль FMX. Printer.


204

Глава 13

Особенности поведения диалогового окна определяются опциями, устанавливае­ мыми в свойстве property Options: TPrintDialogOptions;

Значения флагов свойства приведены в табл. 13.5. Таблица 13.5. Опции диалога печати TPrintDialogOptions

Значение

Описание

poPrintToFile

Выводит флажок перенаправления задания печати в файл

poDisablePrintToFile

Запрещает печать в файл (делая флажок Печать в файл неак­ тивным)

poHelp

Отображает кнопку помощи в окне диалога

poPageNums

Разрешает пользователю выбирать диапазон страниц, отправ­ ляемых на печать. В противном случае будет отправлен весь перечень страниц

poSelection

Разрешает пользователю печатать только выделенный фрагмент текста

poWarning

Генерирует сообщение об ошибке при попытке отправить зада­ ние на неустановленный принтер

Большинство свойств диалога тем или иным образом взаимодействует с опциями окна печати. При использовании флагов poPageNums и poseiection пользователь по­ лучает право отправлять на печать несколько страниц или фрагмент задания. Ре­ зультат выбора пользователя отразится в свойстве property PrintRange: TPrintRange;

При выборе для печати только фрагмента текста свойство PrintRange примет значе- ■ ние prSeiection, при выборе нескольких страниц — prPageNums. Если на печать от­ правляется весь документ, то результат будет соответствовать prAHPages. Совместно с опцией диалога печати poPageNums трудятся свойства property FromPage: Integer; property ToPage: Integer;

Значения свойств определяют начальную и конечную страницы диапазона печати. В случае если оба свойства установлены в 0, то на печать направляется весь доку­ мент. При особом желании допустимо ограничить возможности пользователя по опреде­ лению диапазона направляемых на печать страниц. property MinPage: Integer; property MaxPage: Integer;

По умолчанию значения свойств равны 0, т. е. ограничения отсутствуют. При вы­ ходе за границы генерируется сообщение об ошибке.


Окна сообщений и диалоги

205

Если допустимо перенаправление задания печати в файл и пользователь воспользо­ вался такой возможностью, то свойство property PrintToFile: Boolean;

примет значение true. Количество копий предназначенного для печати документа вы обнаружите в свой­ стве property Copies: Integer;

Если число соответствует 0 или 1, то будет отпечатан только один экземпляр. При печати нескольких копий имеет значение свойство property Collate: Boolean;

Благодаря ему вы укажете принтеру порядок подбора листов в копии документа.


ГЛАВА 14

Дата и время

Сейчас нам предстоит изучить компоненты FireMonkey, связанные с обработкой таких специфичных данных, как дата и время. Но прежде чем мы приступим к рас­ смотрению героев этой главы, на палитре компонентов нам предстоит получить ответ на самый важный вопрос: каким образом Delphi хранит значения даты и вре­ мени?

Дата и время TDateTime Разработчики Delphi для работы с датой и временем стали использовать обычный вещественный тип данных Double. Правда, для того чтобы акцентировать внимание программиста на то, что все-таки речь идет о именно дате и времени, а не о рядо­ вом действительном числе, в модуле System был объявлен базовый тип данных type TDateTime = type Double;

Идея хранения значений даты и времени в TDateTime заключается в следующем: целая часть числа предназначена для запоминания даты, а значение после запя­ т о й — времени. Нулевому значению соответствует время: 30.12.1899 г. 00 ч. 00 м. 00 с. 000 мс. Для того чтобы "сдвинуть" дату ровно на сутки вперед и получить значение 31.12.1899 г. 00:00:00:000, достаточно прибавить к переменной типа TDateTime единицу, вычитание единицы повернет дату вспять, и мы окажемся в 29.12.1899 г. Значению 36526,0417 соответствует 01.01.2000 г. 01:00:00. Зам ечание

Верхний ( предел типа данных TDateTime соответствует значению 31.12.9999 г. 23:59:59:999. Кроме того, TDateTime допускает работу и с отрицательными значениями.

В дополнение к классу TDateTime в Delphi прилагаются производные типы данных: type TDate = type TDateTime; //целая часть, только для хранения даты type TTime = type TDateTime; //дробная часть, только для хранения времени


Дата и время

»

207

Тип данных TDateTime является основным, но далеко не единственным способом описания даты и времени. Например, для представления времени с точностью больше чем TDateTime (до миллисекунд) предназначена запись type TTimeStamp = record Time: Integer; //миллисекунда от полуночи Date: Integer; //количество дней, начиная с 01.01.0001 г.

При работе с интервалом времени пригодится еще более сложная структура TTimeSpan.

Интервал времени TTimeSpan В той ситуации, когда программная логика предполагает работу не с текущим вре­ менем, а с временным интервалом (например, при оценке текущей позиции вос­ производимого трека в медиаплеере TMediaPiayer из состава компонентов 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 property property property property property

Ticks: Int64; //значение в тактах Days: Integer; //значение в сутках Hours: Integer; //значение в часах Minutes: Integer; //значение в минутах Seconds: Integer; //значение в секундах Milliseconds: Integer; //значение в миллисекундах


208

«

Гпава 14

Отсчет времени, таймер TTimer Невизуальный компонент TTimer мы обнаружим на странице System палитры ком­ понентов, компонент описан в модуле f m x .Types и построен на базе класса TFmxObject. Таймер предназначен для генерации особых данны х— сообщений об изменении времени. Для этой цели таймер с заданной периодичностью вызывает событие property OnTimer: TNotifyEvent;

в котором следует описать реакцию нашей программы на изменение времени. Ключевое свойство таймера property Interval: Cardinal; //по умолчанию 1000 миллисекунд

определяет периодичность срабатывания таймера. Таймер управляется единственным свойством property Enabled: Boolean;

Установив Enabled в true, мы заставим компонент отсчитывать миллисекунды, пе­ ревод свойства в состояние false остановит таймер. Пример из листинга 14.1 демонстрирует процесс создания секундомера. На этот раз, кроме метки и таймера нам понадобится еще пара кнопок TButton. Кнопка btnstart возьмет на себя ответственность за старт, а кнопка btnstop — за останов секундомера (рис. 14.1). На этот раз для реализации секундомера предлагаю задействовать структуруинтервал TTimeSpan, для этого не забудьте подключить в строку uses модуль System.TimeSpan. ... ........ ... ....... ............ *.. -.... -.............. .

j Листинг 14.1. Приложение "Секундомер''

uses ..., System.TimeSpan; var

Forml: TForml ; TS:TTimeSpan; //интервал времени implementation {$R *.fmx}

prooedure TForml.FormCreate(Sender: TObject); //инициализация begin Timerl.Enabled:=false; //таймер отключен Timerl.Interval:=50; //период генерации события - 50 мс Labell.Text:='00:00:00.000'; end; prooedure TForml.btnStartClick(Sender: TObject); //Старт begin TS:=TTimeSpan.FromMilliseconds(0); //сброс значения интервала


209

Дата и время

Labell.Text:='00:00:00.ООО'; //сброс текста метки Timerl.Enabled:=True; //старт таймера end; procedure TForml.TimerlTimer(Sender: TObject); //Такт таймера begin TS:=TS+TTimeSpan.FromMilliseconds(Timerl.Interval); //приращение Labell.Text:=Format('%.2u:%.2u:%.2u.%.3u', [TS.Hours,TS.Minutes,TS.Seconds,TS.Milliseconds]); end; procedure TForml.btnStopClick(Sender: TObject); //Стоп begin Timerl.Enabled:=false; //стоп таймера end;

Uni?2,pas

Ц|>№

SystemTimeSpanj;

10:57 A M

Секундомер

Tsmerl

Стоп

44: 33

Insert

Medved

; Code '

iMBSlsfy /

Рис. 14.1. Секундомер для мобильной платформы iOS

Календаре TCalendar и TCalenaarEait На странице A dditional палитры компонентов FireMonkey обосновались два ком­ понента, способные исполнять роль календарей. Это простой календарь T C a l e n d a r и элемент управления T C a i e n d a r E d i t , представляющий собой симбиоз комбиниро­ ванного списка и выпадающего из него календаря.


210

Гпава 14

Оба наших компонента построены на фундаменте общего предка — классе TStyiedControi, отвечающего за стилевое оформление визуальных компонентов. Целевая функция календарей — позволять пользователю производить выбор даты, поэтому большинство методов и свойств календарей направлены на обеспечение этой цели. Значения выбранных в календаре даты и времени мы обнаружим в свойproperty Date: TDate;

Свойство Date может применяться и для установки текущей даты календаря из кода программы. Два базовых события, связанных с деятельностью календаря, обеспечивают реак­ цию элемента управления на выбор пользователем даты property OnDateSelected: TNotifyEvent;

и на изменение текущей даты property OnChange: TNotifyEvent;

Под выбором даты следует понимать выделение пользователем даты на календаре с помощью указателя мыши или выбор даты на сенсорном экране. В России трудовая неделя стартует в понедельник, в США и Англии — в воскре­ сенье. Длг того чтобы исключить путаницу, программисты Embarcadero предложи­ ли нам самостоятельно определиться, с какого дня должка начинаться неделя в на­ ших программах. Для этого в праотце всех календарей объявлено свойство property FirstDayOfWeek: TCalDayOfWeek; type TCalDayOfWeek = (dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday, dowSunday, dowLocaleDefault);

Здесь: dowMonday — понедельник, ..., dowSunday — воскресенье, dowLocaleDefault — день недели определяется автоматически локализацией версии Windows

Рис. 14.2. Поведение календаря T C a le n d a rE d it в iOS


Дата и время

211

Отметим метаморфозу, на которую способен компонент T C a l e n d a r E d i t . когда он задействуется в приложениях для iOS. Здесь, в момент редактирования даты вызы­ вается фирменный календарь мобильных устройств Apple (рис. 14.2). К дополнительным особенностям календаря TCalendarEdit стоит отнести наличие специфичных событий, связанных с "выпадением" календаря из комбинированного списка property OnPopup: TNotifyEvent;

и свертыванием календаря property OnClosePopup: TNotifyEvent;


ГЛАВА 15

Управление цветом Любой из цветов радуги, который вы увидите на экране монитора, получается в результате сложения (в той или иной пропорции) всего-навсего трех базовых цве­ товых составляющих: красного, синего и зеленого цветов. Такую цветовую модель называют RGB, по первым буквам английских названий цветов (Red, Green и Blue). Почему выбраны именно эти цвета? Все объясняется физиологическими особенно­ стями строения человеческого глаза, он воспринимает цвета в диапазоне длин волн примерно 400 + 700 нанометров. Излучения с длинами волн от 380 + 470 нм имеют фиолетовый и синий цвета, от 480 + 500 н м — сине-зеленый, от 510 + 560 нм — зеленый, от 570 + 590 нм — желто-оранжевый, от 600 + 760 нм — красный. Вос­ приятие цвета глазом человека осуществляется за счет трех типов цветочувстви­ тельных фоторецепторов (колбочек). Существуют всего три типа этих фоторецеп­ торов — колбочки, реагирующие на красный, зеленый или синий цвет. В зависимо­ сти от интенсивности той или иной цветовой составляющей соответствующий фоторецептор отправляет сигнал в наш головной мозг, где и формируется резуль­ тирующая картинка.

Представление цвета ARGB Представление цвета в FireMonkey изначально ориентировано на полноцветную 32-битную модель, сочетающую в себе три классических 8-битных канала цветно­ сти RGB и альфа-канал А, отвечающий за прозрачность закрашиваемого пиксела. Таким образом, информация о цвете хранится в формате ARGB и описывается типом данных: TAlphaColor = type Cardinal;

В младшем байте находится значение синей составляющей, во втором байте — зеленой, затем следует красная составляющая цвета и шествие завершается стар­ шим байтом альфа-канала. Нулевое значение альфа-канала соответствует пол­ ностью прозрачному режиму, значение 255 ($f f ) — абсолютно непрозрачному ре­ жиму работы.


Управление цветом

213

Для назначения цвета можно сразу воспользоваться записью в шестнадцатеричном формате, например, значение $ f f o o o o f f соответствует непрозрачному чистому си­ нему цвету, $ f f f f o o o o — непрозрачному красному, значение $ 7 f o o f f o o — наполо­ вину прозрачному зеленому. Однако это не всегда удобно, особенно когда следует получить тот или иной оттенок. Поэтому, чтобы сразу усвоить правила формирова­ ния цвета в формате ARGB, стоит познакомиться с небольшим примером. Для это­ го нам понадобятся: □ четыре компонента-ползунка ттгаскваг, которые будут отвечать за регулировку интенсивности того или иного цвета и уровня прозрачности. Переименуйте ком­ поненты в tbRed, tbGreen, tbBiue и tbAipha. Присвойте свойству мах всей четвер­ ки значение 255; □ фигура □ текст

circiei: TCircie

Texti:TText

позволит увидеть изменения в цвете;

отобразит числовое значение выбранного цвета.

Выберите событие OnChange () у любого из компонентов строки кода, предложенные в листинге 15.1.

ттгаскваг

и внесите в него

илтииг ю 4 С лл . V i iеa t i и агсчэв Ар Л В jП1ИСТИНГ т rрmа ^вoлnеoнu и цid в еoтvоaм

function A R G B (const A,R,G,В:byte):TAlphaColor; begin Result :=B + G SHL 8 + R SHL 16+ A SHL 24;

procedure TForml.tbRedChange(Sender: TObject); var R,G,B,A:byte; A C :TAlphaColor;

begin R:=Round(tbRed.Value);

//значение R

G :=Round(tbGreen.Value); //значение G B:=Round(tbBiue.Value);

//значение В

A:=Round(tbAipha.Va1ue); //значение A AC:=ARGB(A,R,G,В ) ; Circlel.Fill.Color:=AC;

//формирование значения цвета ARGB //перекраска фигуры

Text1.Text:='h '+IntToHex(A C ,8);

Если вам не понравилась придуманная нами функция a r g b o и з листинга 15.1, то для формирования значения цвета в формате TAlphaColor можете воспользоваться библиотечной функцией function MakeColor(R, G, В: Byte; A: Byte = $FF): TAlphaColor;

Для завершения задуманного осталось сделать событие OnChange () общим для всех компонентов-ползунков и нажать клавишу <F9>. Результат нашей работы пред-


214

Гпава 15

Рис. 15.1. Интерфейс приложения ARGB

ставлен на рис 15.1. К сожалению, черно-белая печать книги не способна передать полученную комбинацию цвета hFF84CA47...

Стандартные цветовые комбинации Создавая собственные цветовые оттенки, мы сможем раскрасить приложение во все цвета радуги, но далеко не всегда стоит так делать. В бизнес-проектах более полез­ ным окажется не вычурный, а строгий деловой стиль с цветовой гаммой, подобран­ ной специалистами. Существенное подспорье в этом сможет оказать объявленная В модуле System. UITypes запись TAlphaColorRec. В составе записи TAlphaColorRec определено несколько десятков констант (лис­ тинг 15.2), обращаясь к которым программист получит значение заранее предопре­ деленных цветов по ж символьным именам. ......

Листинг 15.2. Фрагмент o€

LorRs-C

TAlphaColorRec = record

const Alpha = TAlphaColor($FF000000); Aliceblue = Alpha or TAlphaColor($F0F8FF); Antiquewhite = Alpha or TAlphaColor($FAEBD7); Aqua = Alpha or TAlphaColor($00FFFF); Aquamarine = Alpha or TAlphaColor($7FFFD4);

Г -г т щ г ? ,-» . ...1


Управление цветом

215

Компоненты цветовой модели ARGB Библиотека визуальных компонентов VCL и не могла мечтать о таком разнообра­ зии компонентов, нацеленных на предоставление пользователю возможности выбора цветового значения, как в библиотеке FireMonkey. Чтобы проверить это утверждение, предлагаем открыть страницу компонентов Colors, здесь вы обнару­ жите более десятка элементов, способных удовлетворить самые притязательные запросы разработчиков. Среди них 4 компонента специализируются на представлении цвета в формате ARGB (рис. 15.2): 1. Цветовая панель TCoiorPanel наиболее удобна с точки зрения быстрого форми­ рования цвета любого оттенка и прозрачности. Панель представляет собой ком­ бинацию двух компонентов-ползунков (управляющих оттенком цвета и альфаканалом) и собственно цветовой панели. 2. Комбинированная цветовая панель TComboColorBox. В свернутом виде элемент управления отображает выбранный цвет, в развернутом выводит на экран па­ нель, аналогичную TCoiorPanel. 3. Комбинированный цветовой 4. Цветовой —

СПИСОК TColorComboBox.

СПИСОК TCoXorListBox.

1"г*...г1 "г."".,1■.тч

-------------------

Щ Компоненты» цветовой модели АЯШ

TCotoritawf

__

TComteoCokwSox

шшШш -S i

TCotofComboSo*

TCotorUsiBo*

Е ) Aquamarine

Q

IT*] Aiieebifue

f~ l AMIquewhlte

И

Antjquewtifte

Aqua

f

Aouamarine

i

-

!

am*

L J Belee

□ I Мкк

AltceWue

D?

I Aqua □ 4urt

П 13 B,we

WF7FFTO

Рис. 15.2. Компоненты цветовой модели ARGB

Перечисленные компоненты обладают схожим перечнем свойств и методов, основ­ ные из которых представлены в табл. 15.1. Зам ечание

Компоненты-списки TColorListBox и TColorComboBox можно считать нашими давни­ ми знакомыми, в главе 10 мы подробно изучили все их родовые черты.


216

Гпава 15 Таблица 15.1. Основные свойства и события TColorPanel, TCowboColorBox,

TColorListBoxи TColorComboBox

Свойства/события

Описание

p ro p e rty

Color:

В ы бранны й цвет

p ro p e rty

OnChange:

p ro p e rty

U s e A l p h a : Boolean;

T A l p h aColor; TNo t i f y E v e n t ;

С о б ы ти е , ге н е р и р у е м о е в м о м е н т и зм е н е н и я ц в е та И сп ол ьзо ван ие канал а прозрачн ости (т о л ь к о д л я T C o l o r P a n e l и TComboColorBox)

Компоненты цветовой модели HSL Модель воспроизведения цвета на основе красного, зеленого и синего цветов весь­ ма распространена, но далеко не единственна. Кроме RGB существует еще не­ сколько часто используемых цветовых моделей. Одна из них — модель HSL (Hue, Saturation, Lightness (Intensity)). Если вы хорошо знакомы с английским, то уже до­ гадались, что в цветовой модели HSL главными действующими лицами выступают не значения цветов (как в RGB), а тон, насыщенность и интенсивность.

Компоненты TColorPicker и TColorQuad В составе FireMonkey имеется ряд элементов управления, специализирующихся на работе с цветом в формате HSL. В первую очередь это T C o l o r P i c k e r и T C o l o r Q u a d . Представленные компоненты-союзники обычно эксплуатируются совместно (рис. 15.3). Компонент T C o l o r P i c k e r представляет собой шкалу, состоящую из всех цветов ра­ дуги (от красного до фиолетового оттенка). Для того чтобы пользователь подобрал требуемый цветовой тон, в распоряжении элемента управления имеется свойство p ro p e rty

Hue:

Single;

//диапазон

от

0 до

1

способное изменять цветовой оттенок в диапазоне от 0 до 1. ф U**tow*

HSL

"

TColorPicker

-- T C o l o r Q u a d

TColorBox H at

48»

| SataraSon$вЭ95238ДЖ 77075 | usfc&wssptmnssstam Рис. 15.3. Выбор цвета в формате HSL с помощью T C o l o r P i c k e r и T C o lo r Q u a d


Управление цветом

217

При необходимости текущее значение цвета можно считать (или записать) в более привычном для программиста Delphi формате ARGB. Для этого предназначено свойство property

C o l o r : TAlphaColor;

Для установления С В Я З И между T C o l o r P i c k e r И компонентом T C o l o r Q u a d , отвечаю­ щим за управление насыщенностью и интенсивностью, воспользуемся свойством property

ColorQuad:

TColorQuad;

В свою очередь компонент T C o l o r Q u a d , получив от своего коллеги значение цвето­ вого тона, помещает его в свое свойство property

Hue:

Single;

и предоставляет в распоряжение пользователя квадратное поле, в границах которо­ го изменяется насыщенность property

Sat:

Single;

//диапазон

значений от

0 до

1

//диапазон значений от

0 до

1

и интенсивность property

Lum:

Single;

исходного цвета. В момент изменения параметров цвета у компонента бытие property

OnChange:

TColorQuad

генерируется со­

TNotifyEvent;

При желании (если требуется дополнительная визуализация результирующего цве­ та) можно воспользоваться услугами дополнительного компонента T C o i o r B o x . Для подключения к элементу управления T C o i o r B o x задействуется свойство property

ColorBox:

TCoiorBox;

На T C o i o r B o x возложена самая простая задача — пересчитать цвет из формата HSL в формат ARGB и возвратить полученное значение в свойстве property

Color:

TAlphaColor;

Впрочем, для конвертации цвета из формата HSL в формат ARGB необязательно задействовать элемент управления T C o i o r B o x , вместо этого можно воспользоваться библиотечными функциями, обеспечивающими взаимное преобразование цвета из модели HSL в RGB, и наоборот function H S L t o R G B ( H , S, procedure R G B t o H S L ( R G B :

L:

Single):

TAlphaColor;

TAlphaColor;

out

H,

S,

L:

Single);

Цветовые полосы THueTrackBar, TAIphaTrackBar и TBWTrackBar Объединив три элемента компонента в категорию "цветовые полосы", мы несколь­ ко преувеличили. По большому счету из анонсированной тройки только один ком­


Гпава 15

218

понент имеет прямое отношение к цвету — это полоса цветового тона тниетгаскваг. Оставшиеся два компонента отвечают за прозрачность (TAiphaTrackBar) и градации серого (TBWTrackBar). Однако перечисленные компоненты функционируют по еди­ ному принципу (они являются наследниками цепочки классов "ттгаскваг — TBitmapTrackBar"), посему логически правильно рассмотреть их совместно. Строго говоря, отличие между THueTrackBar, TAiphaTrackBar И TBWTrackBar ЧИСТО косметическое. Фактически это один и тот же элемент управления, отличающийся от своих коллег лишь внешним видом.(рш. 15.4).

Рис. 15.4. Приложение с компонентами TAiphaTrackBar, THueTrackBar и TBWTrackBar

Все рассматриваемые компоненты обладают идентичным перечнем свойств и ме­ тодов. Наиболее важные из них property Min: Single; property Max: Single;

//по умолчанию О //по умолчанию 1

определяют границы интервала и информируют property Value: Single;

о текущем положении ползунка. В момент перемещения ползунка генерируется событие property OnChange: TNotifyEvent;

Все остальное в руках программиста. Например, листинг 15.3 демонстрирует поря­ док назначения градации серого цвета с помощью компонента TBWTrackBar. Листинг 15.3. Назначение серого и.

.?.

procedure TForml.BWTrackBarIChange(Sender: TObj ect); var GRAY:byte; begin GRAY:=Round($FF*BWTrackBar1 .Value); Rectanglel.Fill.Color:=GRAY + GRAY SHL 8 + GRAY SHL 16+ $FF SHL 24;

end;


Управление цветом

219

Градиентная заливка TGradientEdit В составе компонентов FireMonkey предусмотрен элемент управления T G r a d i e n t E d i t позволяющий устанавливать правила градиентной заливки областей. Ключевое свойство компонента property

Gradient:

TGradient;

представляет ссылку на объект строения градиента (табл. 15.2).

TGradient,

отвечающий за описание правил по­

Таблица 15.2. Класс TGradient Свойства и м е тоды

Описание

property

Color:

Начальный цвет градиентной заливки

property

Colorl:

TAl p h a C o l o r ; TAl p h a C o l o r ;

Конечный цвет градиентной заливки

InterpolateColor(Offset: S i n g l e ) : TAlp h a C o l o r ;

Функция интерполирует цвет градиента. Пара­ метр o f f s e t должен находиться в диапазоне значений от 0 до 1

property

Points:

Коллекция точек, которые используются для доступа к цвету и отступу области градиентной заливки

property

Style:

property

StartPosition:

property

Sto p P o s i t i o n :

property

Ra d i a l T r a n s f o r m :

function

TGradientPoints;

TGradientStyle;

T P osition;

T P osition;

T T r ansform;

Стиль заливки: линейный альный g s Radial

gsLinear

или ради­

Отправная точка для линейной градиентной заливки назначается в виде прямоугольной области с размерами, нормированными к еди­ нице (координаты левого верхнего угла — (0, 0), координаты правого нижнего угла — (1. 1)) Конечная точка линейной градиентной заливки (от (0, 0) до (1. 1)) Правила преобразований, применяемых для радиальной заливки. В данном случае среди свойств класса T T r a n s f o r m самое важное ме­ сто занимает RotationCenter. Именно оно определяет центр заливки

Внимание!

Для настройки направления линейной градиентной заливки и шага интерполяции цве­ та (плавности заливки) следует воспользоваться свойствами s t a r t P o s i t i o n и S t o p P o s i t i o n (см. табл. 15.2). По умолчанию эти свойства установлены в состояние (О, 0) и (0, 1) соответственно и указывают на то, что градиент направлен сверху вниз.

Компонент T G r a d i e n t E d i t представляет собой полосу с двумя точками-ползунками (рис. 15.5), при выборе любого из ползунков генерируется событие property

O n S e l e c t P o i n t : TNotifyEvent;


Глава 15

220

Для того чтобы определить, какой именно из ползунков был выбран, стоит проин­ спектировать свойство property

C u r r e n t P o i n t : Integer;

Если выбран левый ползунок, то свойство возвратит нулевое значение. О выборе правого ползунка будет свидетельствовать единица. В момент изменения любых параметров градиентной заливки компонент генериру­ ет событие property

OnChange:

TNotifyEvent;

Для того чтобы оценить возможности компонента T G r a d i e n t E d i t , предлагаем по­ вторить небольшой пример. Для этого разместите на форме компонент GradientEditi:TGradientEdit и два комбинированных списка выбора цвета T C o m b o C o l o r B o x (рис. 15.5).

TComboColorBox

TGradientEdit

Рис. 15.5. Демонстрационное приложение управления градиентной заливкой с помощью T G r a d i e n t E d i t

Комбинированные списки T C o m b o C o l o r B o x выбора цвета предназначены для управ­ ления цветом заливки. Для назначения цвета мы воспользуемся событиями O n C h a n g e () Э Т И Х К О М П О Н е Н Т О В ( Л И С Т И Н Г 15.4). Листинг 15.4.; procedure begin

T F o r m l .C o m b o C o l o r B o x l C h a n g e ( S e n d e r : T O b j e c t ) ;

G r a d i e n t E d i t 1 . G r a d i e n t . C o l o r := C o m b o C o l o r B o x l .C o l o r ;

end; procedure begin

TForml.ComboColorBox2Change(Sender:

TObject);

G r a d i e n t E d i t 1 . G r a d i e n t .C o l o r 1 : = C o m b o C o l o r B o x 2 .C o l o r ;

end;


Управление цветом

221

Процесс заливки лучше всего осуществить в рамках события O n P a i n t (), генерируе­ мого в момент перерисовки единственной формы проекта (листинг 15.5). Л

, .J

procedure

....— ..........Г”........................................................ ........... -йЗйШ Щ*Я...« !к-............. ят й ssS заливкой фа даы ?Л«1зШ ssk-',&лЖи.л...&шш!гй«.I ■.»....... и..... ........................ .................

.

T F o r m l .F o r m P a i n t ( S e n d e r : T O b j e c t ;

const

ARect:

Canvas:

TCanvas;

TRectF);

begin with C a n v a s do if B e g i n S c e n e then begin F i l l .K i n d : = T B r u s h K i n d . b k G r a d i e n t ; F i l l .G r a d i e n t := G r a d i e n t E d i t l .G r a d i e n t ; F i l l R e c t ( A R e c t , 0,0,

[],1);

EndScene;

end; end;

Для того чтобы форма немедленно откликалась на все манипуляции пользователя, воспользуемся событием O n C h a n g e о компонента G r a d i e n t E d i t l (листинг 15.6). ! Л»:с." г 13.6. ’ : procedure begin

щ е я переривое** о о р : п

T F o r m l .G r a d i e n t E d i t l C h a n g e ( S e n d e r : T O b j e c t ) ;

F o r m l .O n P a i n t ( S e n d e r , F o r m l .C a n v a s , F o r m l . C l i e n t R e c t ) ; Expanderl.Repaint;

end;


ГЛАВА 1 6

Двухмерная графика

В классических приложениях VCL для Windows весь механизм графического вы­ вода построен на фундаменте GDI (Graphics Device Interface, интерфейс графиче­ ских устройств). Для того чтобы нарисовать линию или вывести строку текста, программисту достаточно получить доступ к контексту нужного графического уст­ ройства и воспользоваться услугами стандартных функций двухмерной графики Windows. Несмотря на относительную "древность", GDI превосходно справляется с большинством задач, которые решают деловые приложения. А если программист ставит перед собой более амбициозные цели, то в качестве инструментария Microsoft предлагает воспользоваться GDI+, Direct2D и Direct3D. Скажем несколько слов о графическом механизме OS X. Не стоит доказывать, что эта ОС ничего не знает ни о GDI, ни о DirectX. В OS X работа с двухмерной графи­ кой осуществляется силами QuickDraw и Quartz 2D, а трехмерные сцены создаются на основе графической библиотеки OpenGL. В проектах VCL объектно-ориентированным воплощением контекста графического устройства GDI выступал, выступает и наверняка еще очень долго будет выступать хорошо нам знакомый класс TCanvas. В двухмерных проектах FireMonkey на по­ прище графики трудится одноименный TCanvas (модуль FMX. Types), но для того чтобы одновременно стать полезным OS X, Windows и Android, он организован подругому. Для достижения универсальности принципиально изменена иерархия предков. Те­ перь в родительском списке мы обнаружим интерфейс type TCanvas = class abstract (TInterfacedPersistent)

Интерфейс TInterfacedPersistent является прародителем всех объектов, способных сохранять в памяти и загружать из памяти значения своих полей. Изменились и приемы программирования с холстом. Так листинг 16.1 демонстри­ рует порядок вывода обычной линии на поверхности формы.


Двухмерная графика

223

...................................... Листинг 13.1. i ii одпинии«р дет' -ми тс nv procedure T F o r m l . F o r m P a i n t ( S e n d e r : oonat A R e c t : T R e c t F ) ; v a r APtl, APt2: TPointF; begin

TObject;

у » * г ж м -•*■*-• • * ............................................. ................................................................................

Canvas:

TCanvas;

A P t 1: = F o r m l .C l i e n t R e c t .T o p L e t t ; A P t 2 := F o r m l .C l i e n t R e c t .B o t t o m R i g h t ;

if

C a n v a s .B e g i n S c e n e

then

//создание

сцены

try

C a n v a s .D r a w L i n e ( A P t l ,

APt2,

1);

//рисование линии

f in a lly Canvas.EndScene;

//завершение

сцены и вывод изображения на

экран

end; end;

Внимание!

Графический вывод FireMonkey осуществляется в рамках отдельных сцен. Прорисов­ ка на поверхности физического устройства начинается только после полного форми­ рования сцены. Такое решение позволяет улучшить качество отображаемой картинки, т. к. рисование осуществляется в памяти, и только затем уже сформированный образ переносится на экран.

Управление холстом Первая, бросающаяся в глаза особенность листинга 16.1 — необходимость уведом­ лять холст о начале и завершении прорисовки. Графический вывод предваряется обращением к методу fu n c tio n

BeginScene

(AClipRects:

PClipRects

=

n il;

A C o n t e x t H a n d l e : T H a n d l e = 0):

Boolean;

В момент открытия сцены устанавливается в исходное состояние свойства холста (в первую очередь кисти заливки областей и рисования линий) и при необходи­ мости назначается регион отсечения. После этого холст переходит в готовность воспринимать графические команды. Одновременно на холсте может сосуществовать несколько открытых графических сцен, об их количестве проинформирует свойство p ro p e rty

B e g i n S c e n e C o u n t : integer;

Команда на закрытие сцены procedure

EndScene;

уведомляет, что все необходимые команды отправлены в систему, и она может приступать к выводу графики.


Гпава 16

224

Предусмотрены два метода, позволяющих быстро очистить холст (точнее, буфер, в котором хранится изображение). Полную очистку всей площади холста осущест­ вит процедура procedure

C l e a r (const C o l o r :

TAlphaColor);

Единственный параметр метода

Color

содержит новый цвет заливки холста.

Для очистки только заданной прямоугольной области воспользуйтесь методом procedure

C l e a r R e c t (const A R e c t :

TRectF;

const

границы области, подлежащей перерисовке,

AColor:

TAlphaColor =

0) ;

A R ect.

Ш ирину и высоту холста возвращают свойства property property

Width:

Integer;

Height:

Integer;

//только для чтения //только для чтения

Для улучшения качества графического вывода FireMonkey перед выводом изобра­ жения на холст формирует картинку в памяти и только после этого переносит ее на экран. Признаком того, что холст работает в режиме буферизации, является значе­ ние true, возвращаемое свойством property

Buffered:

Boolean;

//только для чтения

Доступ к указателю на область памяти буфера и к дескриптору буфера соответст­ венно предоставляют свойства property property

BufferBits:

Pointer;

B u f f e r H a n d l e : THandle;

Для управления особенностями прорисовки графических примитивов при работе с холстом VCL мы пользовались услугами кисти, пера и шрифта. У холста FMX помощники те же самые, однако работать с ними существенно интереснее (табл. 16.1). Таблица 16.1. Атрибуты холста FMX. Types. TCanvas Описание

Свойство property

Fill:

property

Stroke:

property

Font:

TBrush;

TSt r o k e B r u s h ;

TFont;

Кисть, используемая для заливки замкнутых областей Кисть, используемая для рисования линий Шрифт

Кисть TBrush Первой и едва ли не основной помощницей кроссплатформенной версии холста можно считать кисть TBrush. Вас не должно вводить в заблуждение знакомое по VCL название класса кисти, кисть етук.T y p e s . T B r u s h на голову превосходит своего коллегу из VCL. Это подтверждают свойства новой кисти.


Двухмерная графика

Класс

TBrush

225

позволяет создать несколько инструментов для закраски поверхности:

property K i n d : T B r u s h K i n d ; type T B r u s h K i n d = (bkNone,

//пустая кисть

bkSolid,

/ / с п л о ш н а я кисть

bkGradient,

//кисть

с градиентной

bkBitmap,

//кисть

на основе растрового образа

заливкой

bkResource);

//кисть

из ресурса

В зависимости от вида кисти задействуется то или другое свойство класса При определении цвета сплошной кисти обращаемся к свойству property

Кисть

умеет хранить описание градиентной заливки

Gradient:

bkBitmap

property ИЛИ

TAlphaColor;

bkGradient

property

Кисть

Color:

TGradient;

способна интегрировать в себя растровый образ

Bitmap:

TBrushBitmap;

произвольный ресурс, как кисть

property

T Brush.

Resource:

bkResource

TBrushResource;

Порядок заливки эллиптической области сплошной кистью демонстрирует лис­ тинг 16.2. Ш ш Ш Ш Ш А Ш Щ ак ш : ■v' e ... ' Ж ........... :.... -г-.......

16.2. Заливка злг. пса сплошной кй©т ;о procedure

T F o r m l .F o r m P a i n t ( S e n d e r : T O b j e c t ;

const begin if C a n v a s .B e g i n S c e n e () then try

ARect:

Canvas:

!

TCanvas;

TRectF);

//создание

графической

сцены

C a n v a s .F i l l .K i n d : = T B r u s h K i n d .b k S o l i d ; C a n v a s .F i l l .C o l o r : = T A l p h a C o l o r s . A l i c e B l u e ; C a n v a s .F i l l E l l i p s e ( A R e c t , 1);

finally C a n v a s . E n d S c e n e ();

//завершение

сцены и вывод изображения на

экран

end; end;

Немаловажной особенностью кисти является способность реагировать на события, в частности на изменения в параметрах кисти property

OnChanged:

TNotifyEvent;

А если кисть предназначена для работы градиентом, то и на изменения в настрой­ ках градиента property

OnGradientChanged:

TNotifyEvent;


Гпава 16

226

Внешний вид линий Читатель, изучивший табл. 16.1, наверняка заметил, что в графике FireMonkey роль пера также отводится кисти. Однако на этот раз экземпляр кисти-пера построен не на TBrush, а на классе-наследнике TStrokeBrush = class (TBrush)

и передается в свойство stroke (см. листинг 16.3). Если вы уже попробовали пора­ ботать с кистью f m x .Types.strokeBrush, то поняли, что возможности FireMonkey по выводу линий поистине безграничны. Чего только Стоит градиентная линия... Кро­ ме того, внешний вид линий зависит от состояния квартета свойств. Толщина линии определяется свойством property Thickness: Single;

Особенности начертания определяет свойство property Dash: TStrokeDash; type TStrokeDash = (sdSolid, sdDash, sdDot, sdDashDot, sdDashDotDot, sdCustom);

При желании можно создать собственную модель штриховки линии, для этого при­ годится свойство procedure SetCustomDash (const Dash: array of Single; Offset: Single);

Наконечники линий могут быть плоскими и скругленными property Cap: TStrokeCap; type TStrokeCap = (scFlat, scRound);

Наконец, места соединения нескольких линий также могут настраиваться свойстproperty Join: TStrokeJoin; type TStrokeJoin = (sjMiter, sjRound, sjBevel);

Соединения могут быть ограничены, скруглены или отсечены. Порядок работы с пером отражен в листинге 16.3. ................................................ ................................................................. I

•(ертим к(--------- гоя

г-ояы пса а элл тсо

procedure TForml.FormPaint(Sender: TObj ect; Canvas: TCanvas; const ARect: TRectF); var R:TRectF; b e g in

R:=RectF(10,10,200,200);

if Canvas.BeginScene then try

canvas. Stroke,Color ; =TAlphaColorRec.Crimson; 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:=1; Canvas.DrawEllipse(aRect,1);

finally Canvas.EndScene;

end;

Шрифт TFont С точки зрения конечного программиста, нововведения FMX не сильно затронули устоявшийся со времен VCL подход к представлению шрифта. В проектах FireMonkey класс шрифта сохранил традиционное имя TFont, но на этот раз объяв­ ление класс перенесено в модуль f m x .Types. Три базовых свойства шрифта обеспечивают управление гарнитурой, размером и стилем: property Family: TFontName; //гарнитура шрифта property Size: Single; //размер шрифта property Style: TFontStyles; //стиль шрифта type TFontStyle = (fsBold, fsltalic, fsUnderline, fsStrikeOut);

В листинге 16.4 предложен код, позволяющий вывести строку текста в заданной прямоугольной области. Обратите внимание, что цвет надписи определяется цве­ том кисти. | Листнкг 18.4. Вывод текстовой и^лписк procedure TForml.FormPaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF); var R:TRectF; begin R:=RectF(10,10, 200, 40) ; if Canvas.BeginScene then

try Canvas.Fill.Color:=TAlphaColors.Red; Canvas.Font.Style:= [TFontStyle.fsltalic]; Canvas.Font.Family:='Arial'; Canvas.Font.Size:=20; Canvas.FillText(R,'Привет, Мир!1,True,1,[],TTextAlign.taCenter);


228

Глава 16

fin a lly C a n v a s .E n d S c e n e ;

end;

Заливка замкнутых областей Все методы графического вывода содержат параметр A O p a c i t y : single, опреде­ ляющий степень прозрачности рисуемого примитива. Параметр должен принимать значения в пределах от 0 до 1, где 0 соответствует абсолютно прозрачному изобра­ жению, а 1 — непрозрачному. На любых графических платформах наиболее быстрой графической операцией счи­ тается заливка замкнутой области текущей кистью (кистью, выбранной в свойство F i l l холста FMX). Методы заливки предложены в табл. 16.2. Таблица 16.2. Методы заливки областей Метод

Описание

procedure F i l l R e c t (const ARect: TRectF; const XRadius, YRadius: Single; const A Co r n e r s : T Corners; const AOpa c i t y : Single; const A C o r n e r T y p e :

Заливка прямоугольной области ARect. Допус­ кается вывод скругленных углов, в этом слу­ чае следует определить радиус — X R a d i u s и YRadius. Параметр A C o r n e r s уточняет, какой именно угол (или углы) следует нарисовать скругленным. Особенности вывода назнача­ ются параметром A C o r n e r T y p e

T C o r n e r T y p e = T C o r n e r T y p e .c t R o u n d ) ;

procedure F i l l E l l i p s e ( c o n s t ARect: TRectF; const A O p a c i t y : S i n g l e ) ;

Заливка эллиптической области в границах прямоугольника A R e c t

procedure

Заливка сектора окружности с центром C e n t e r и радиусом R a d i u s Раскрытие сектора опре­ деляется двумя лучами, проведенными из центра В точке S t a r t A n g l e И S w e e p A n g l e

TPointF; Single;

F i l l A r c (const Center, Radius: StartAngle, SweepAngle: const A O p a c i t y : Single);

procedure TPathD a t a ;

procedure TPolyg o n ;

F i l l P a t h (const APath:

const

AOpacity:

Single);

F i l l P o l y g o n (const Points: AOpacity: S i ng l e );

const

Заливка траектории, описанной в структуре APath

Заливка произвольного многоугольника с вершинами, заданными в массиве Points

Листинг 16.5 предлагает простой пример заливки прямоугольной области гради­ ентной кистью. Обратите внимание, что точки s t a r t P o s i t i o n и s t o p P o s i t i o n опре­ делят координаты вектора заливки. Значения координат вектора нормируются к 1. Например, если бы мы захотели направить градиент из левого верхнего угла формы в правый нижний, то следовало передать координаты (0, 0) и (1, 1). [ Листинг 16.5. Градиентная заливка клиентской области формы p r o c e d u r e T F o r m l .F o r r a P a i n t ( S e n d e r : T O b j e c t ;

Canvas:

const ARect: TRectF); begin

TCanvas;

j


Двухмерная графика

if

C a n v a s .B e g i n S c e n e

229

then

try C a n v a s .F i l l .K i n d := T B r u s h K i n d .b k G r a d i e n t ;

With C a n v a s .F i l l . G r a d i e n t do begin S t a r t P o s i t i o n . X : = 0 . 5;

StartPosition.Y:=0;

S t o p P o s i t i o n .X :=0.5;

S t o p P o s i t i o n .Y :=1;

S t y l e := T G r a d i e n t S t y l e .g s L i n e a r ; C o l o r := T A l p h a C o l o r s .W h i t e ; C o l o r l := T A l p h a C o l o r s .R e d ;

end; C a n v a s .F i l l R e c t ( F o r m l . C l i e n t R e c t , 0,0, TCorner.crTopRight,

[ T C o r n e r .c r T o p L e f t ,

TCorner.crBottomLeft,

T C o r n e r . c r B o t t o m R i g h t ] ,1);

finally C a n v a s .E n d S c e n e ;

end; end;

Вывод простейших фигур Вывод графических примитивов осуществляется кистью, выбранной в свойстве stroke. Методы, отвечающие за вывод простейших геометрических фигур, пред­ ставлены в табл. 16.3. Таблица 16.3. Методы черчения простейших фигур Метод

Описание

procedure D r a w L i n e (const const AOpa c i t y : S i n g l e ) ;

APtl,

APt2:

TPointF;

Чертит отрезок из точки A P t l в точку A Pt2

procedure

D r a w R e c t (const ARect: TRectF; const XRadius, YRadius: Single; const A Cor n e r s : TCorners; const A Opa c i t y : Single; const

Выводит прямоугольник. Параметры метода идентичны параметрам F i l l E l l i p s e (см. табл. 16.2)

ACornerType: TCornerType = TCornerType.ctRound);

procedure

D r a w A r c (const Center,

TPointF; StartAngle, S w e e pAngle: const A Opa c i t y : S i n g l e ) ;

Radius: Single;

procedure D r a w R e c t S i d e s (const ARect: TRectF; const XRadius, YRadius: Single; const A Corn e r s : TCorners; const AO p a c i t y : Single; c onst ASid e s : TSides; const A C o r n e r T y p e :

Выводит сектор. Параметры метода идентичны параметрам Fi l l A r c (см. табл. 16.2) Чертит стороны прямоугольника ASides

TCornerType = TCornerType.ctRound);

procedure D r a w E l l i p s e (const const AOpa c i t y : S i n g l e ) ; procedure D r a w P a t h (const const A O p a c i t y : S i n g l e ) ;

ARect:

APath:

TRectF;

TPathData;

Чертит эллипс в границах прямоуголь­ ной области Чертит траекторию A P a t h с прозрачностью A O p a c i t y


230

Глава 16 Таблица 16.3 (окончание)

Метод

Описание

procedure D r a w P o l y g o n (const const A O p a c i t y : S i n g l e ) ;

Points:

TPolygon;

Выводит многоугольник с вершинами P oints

Траектория TPathData Из предложенных в табл. 16.3 методов вывода графических примитивов наиболь­ ший интерес представляет метод D r a w P a t h (), позволяющий осуществлять графиче­ ский вывод весьма неординарных объектов-траекторий, реализуемых классом F M X .T y p e s .T P a t h D a t a .

Траекторией можно описать практически любую геометрическую фигуру. Важно понимать, что сведения о произвольной фигуре в объекте-траектории T P a t h D a t a бу­ дут храниться не попиксельно (такой подход противоречит концепции векторной графики), а в виде набора кривых и линий. Во время отображения фигуры на экран траектория осуществит последовательный вызов сохраненных кривых и линий. Если траектория уже содержит информацию, то свойство fu n c tio n

IsEmpty:

Boolean;

окажется в состоянии

false.

Доступ к опорным точкам описанной в траектории геометрической фигуры можно получить благодаря свойству p ro p e rty

Points[Alndex:

Integer]:

TPathPoint;

//только для чтения

Общее число точек в траектории известно свойству p ro p e rty

Count:

Integer;

//только для чтения

Для очистки траектории проще всего воспользоваться свойством procedure

Clear;

Для определения начальной точки траектории следует вызвать метод prooedure

M o v e T o (const Р:

TPointF);

Кроме того, метод M o v e T o () потребуется в том случае, когда необходимо оторвать виртуальное перо от холста и переместить его в другую точку. Для того чтобы провести линию из последней точки траектории в новую, проще

всего вызвать метод procedure

L i n e T o ( c o n s t Р:

TPointF);

Полученных знаний уже вполне достаточно, чтобы предоставить пользователю возможность рисовать с помощью мыши на поверхности формы. Листинг 16.6 со­ держит код создания экземпляра траектории, вызываемый в момент создания глав­ ной формы проекта.


Двухмерная графика____________________________________________

231

тшяшктлшамшшшвкшшатвю ; Листинг 16.6. Создаем экземпляр траектории var

P a t h :TPathData,;

procedure begin

//глобальная переменная

T F o r m l .F o r m C r e a t e ( S e n d e r : T O b j e c t ) ;

Path:=TPathData.Create;

//создаем траекторию

end;

Очередной листинг 16.7 описывает три обработчика мыши (нажатие кнопки, пере­ мещение курсора и отпускание кнопки мыши), с помощью которых пользователь начертит свою фигуру. liiiM iiM M fa iii ' щ ш т ш т

ш ш

■н

ш

ш

к

| Листинг 1й.7. « - ^ Ш руем траекторию с помощью procedure Shift:

■ утз

T F o r m l .F o r m M o u s e D o w n ( S e n d e r : T O b j e c t ; TShiftState;

X,

Y:

шшт

тэл:

?ышп

Button:

TMouseButton;

Single);

begin Path.Clear;

//очистка предыдущей траектории

P a t h . M o v e T o ( P o i n t F ( X , Y ) );

//начальная

procedure

точка траектории

T F o r m l .F o r m M o u s e M o v e ( S e n d e r : T O b j e c t ; X,

begin if s s L e f t in begin

Shift

then

Y:

Shift:

TShiftState;

Single);

//строим траекторию

P a t h . L i n e T o ( P o i n t F ( X , Y ) ); //!!!

событие перерисовки

FormPaintO

пока отсутствует

F o r m P a i n t ( S e n d e r , F o r m l .C a n v a s , F o r m l . C l i e n t R e c t ) ; / /!!!

см.

листинг

16.8

end; end; procedure Shift:

T F o r m l .F o r m M o u s e U p ( S e n d e r : T O b j e c t ;

TShiftState;

X,

Y:

Button:

TMouseButton;

Single);

begin Path.ClosePath;

//пользователь

отпустил кнопку мыши —

закрыли

траекторию

end;

Для визуализации жеста пользователя нам осталось воспользоваться событием перерисовки главной формы проекта (листинг 16.8). .... •............................

Листинг 16.0. Визуализация трз- сггорим procedure

T F o r m l .F o r m P a i n t ( S e n d e r : T O b j e c t ;

const

ARect:

Canvas:

TRectF);

TCanvas;


232

Гпава 16

begin if ( P a t h . C o u n t > 0 ) and try

then

(C a n v a s . B e g i n S c e n e )

C a n v a s .C l e a r ( T A l p h a C o l o r R e c .W h i t e ) ; C a n v a s .S t r o k e .C o l o r := T A l p h a C o l o r R e c .B l a c k ; C a n v a s .S t r o k e T h i c k n e s s :=0.5; C a n v a s .D r a w P a t h (P a t h , 1);

finally C a n v a s .E n d S c e n e ;

end; end;

B рассмотренном примере для построения траектории нам оказалось достаточно услуг единственного метода L i n e T o (). Изучив класс T P a t h D a t a , вы обнаружите бо­ лее десятка методов, позволяющих составлять траекторию не только из линий, но и из кривых, дуг, эллипсов и прямоугольников.

Вывод текста Одна из сложнейших графических задач вывода текстовых данных в FireMonkey превращается в пару пустяков. Гарнитура, стиль и размер шрифта назначаются в свойстве F o n t холста, цвет шрифта определяется цветом текущей кисти. Методы холста T C a n v a s , отвечающие за работу с текстом, представлены в табл. 16.4. Таблица 16.4. Вывод текстовых данных

Метод fu n ctio n

Описание Загрузка шрифта из потока A S t r e a m

LoadFontFroitiStream(AStream:

T S t r e a m ) : Boolean;

fu n ctio n

T e x t w i d t h (const ATex t :

s t r in g ) :

Расчет ширины текстовой строки

Single;

fu n ctio n

T e x t H e i g h t (const ATex t :

s t r in g ) :

Расчет высоты текста

Single;

procedure F i l l T e x t (const A R e c t : TRectF; const AText: s trin g ; const word w r a p : Boolean; const AOp a c i t y : Single; const Flags: TFil l T e x t F l a g s ; const A T e x t A l i g n : T T e x tAlign; const A V T e x t A l i g n : T T e x t A l i g n = TTextAlign.taCenter);

fu n ctio n

Te x t T o P a t h f P a t h ;

T P a thData;

const

A Rect: TRectF; const AText: s trin g ; const W ordWr a p : Boolean; const A T e x t A l i g n : T T extA l i g n ; const A V T e x t A l i g n : T T e x t A l i g n =

TTextA lign.taC enter) : Boolean;

Вывод текста A T e x t в области ARect. Перенос текста на новую строку опре­ деляется параметром wordwrap. На­ правление вывода (по умолчанию сле­ ва направо)устанавливается в Flags. Горизонтальное выравнивание опре­ деляется параметром ATextAlign, вертикальное выравнивание — пара­ метром A V T e x t A l i g n Вывод текста по траектории

Path


Двухмерная графика

233

Отображение рисунков Для прорисовки на поверхности холста графического образа следует применять метод procedure

D r a w B i t m a p (const A B i t m a p :

const const const

SrcRect,

TBitmap; DstRect:

AOpacity:

TRectF;

Single;

Highspeed:

Boolean = False);

Процедура выводит изображение A B i t m a p полностью или его часть. Подлежащая выводу область изображения определяется параметром Src R e c t . Место вывода и размеры результирующего изображения назначаются в параметре Dst R e c t . Пара­ метр H i g h s p e e d позволяет программисту отдать предпочтение скорости ( true) или качеству ( f a l s e ) вывода. Листинг 16.9 демонстрирует порядок работы с методом D r a w B i t m a p о . В представ­ ленном примере мы загружаем графический образ из файла и выводим его на по­ верхности формы. Если нам потребуется отобразить только фрагмент исходного рисунка, то следует определить границы интересующего нас фрагмента в парамет­ ре Sr c R e c t . Управляя размерами области вывода Dst R e c t , мы сможем увеличить или уменьшить рисунок. Листинг 16.9. Выыод графического образа бьз масштгчжровамли ............. .......................................'

var

I

....................................... ............. ....................

ABitmap:TBitmap; a W i d t h , a H e i g h t :i n t e g e r ; SrcRect,

DstReqt:

TRectF;

begin if O p e n D i a l o g l .E x e c u t e then with F o r m l .C a n v a s do begin A B i t m a p : = T B i t m a p . C r e a t e F r o m F i l e ( O p e n D i a l o g l .F i l e N a m e ) ; a W i d t h := A B i t m a p .W i d t h ; a H e i g h t := A B i t m a p .H e i g h t ; S r c R e c t : = R e c t F (0,0,a W i d t h , a H e i g h t ) ; D s t R e c t := S r c R e c t ;

if B e g i n S c e n e then begin DrawBitmap(ABitmap,SrcRect,DstRect,1,t r u e ) ; EndScene;

end; end; end;

Для организации быстрого просмотра изображения методом

ABitmap

стоит воспользоваться


234

Глава 16

procedure

D r a w T h u m b n a i l (const A B i t m a p :

const

Width,

TBitmap;

Height:

Single);

создающим графическую миниатюру заданного размера.

Отсечение Как в Windows, так и в OS X в качестве основного инструмента, позволяющего на­ строить границы области, в которых будет осуществляться вывод, выступает реги­ он отсечения. Библиотека FireMonkey порадовала возможностью простого управ­ ления регионами отсечения. В ХЕ4/ХЕ5 в распоряжении программиста имеются два метода. Изменить текущий регион отсечения, создав новый на основе пересечения текуще­ го региона и прямоугольной области A R e c t , позволит процедура procedure

I n t e r s e c t C l i p R e c t (const A R e c t :

TRectF);

Метод procedure

E x c l u d e C l i p R e c t (const A R e c t :

TRectF);

наоборот исключает из текущего региона отсечения прямоугольник ARect.

Сохранение и восстановление состояния холста При необходимости мы можем сохранить текущее состояние холста (сведения о кистях, шрифте, матрице преобразований и т. п.). Для этой цели предназначена функция function

SaveState:

TCanvasSaveState;

Сведения о состоянии холста представляются в формате объекта Для восстановления состояния холста вызываем метод procedure

RestoreState(State:

TCanvasSaveState.

TCanvasSaveState);

Методы сохранения/восстановления должны работать вместе так, как продемонст­ рировано в листинге 16.10. тт .............................. .................... ........... ........ ■

var

Canvas: TCanvas;

state: TCanvasSaveState;

begin State

:= C a n v a s .S a v e S t a t e ;

//воздействие на свойства

холста

//другие операции Canvas.RestoreState(State);

end;

| ............................ШИШ .................... Щ .................... .................................j


Дву... зрная графика

235

Работа с растровой графикой, класс TBitmap Если вы имеете опыт работы с растровой графикой в VCL, то, вне всякого сомне­ ния, вам приходилось сталкиваться с классом T B i t m a p , выступающим интеллекту­ альным контейнером для битовых образов для широко распространенных в Windows файлов с расширением имени bmp. Разработчики библиотеки FMX решили не ломать сложившихся традиций и для обслуживания растровой графики в FireMonkey создали одноименный класс T B i t m a p (модуль Емх. Grap h i c s ) . Несмотря на то, что классы VCL и FMX являются тёзками, возможности обновленной версии T B i t m a p в корне отличаются от его кол­ леги из VCL, превосходя его практически по всем параметрам. Так, обновленная версия е м х .T y p e s . T B i t m a p способна: □ обслуживать графические файлы не только в простейшем формате BMP, но и в форматах JPG, JPEG, PNG, GIF, TIF, ICO (в приложениях Windows) и JP2, PSD, TGA, ICNS (в приложениях OS X и iOS); □ изменять геометрию изображения (размеры и вращение); □ осуществлять графический вывод на своей рабочей поверхности благодаря ин­ капсулированному холсту T C a n v a s . Созданием экземпляра класса обслуживания растровых изображений ведает квар­ тет конструкторов. Вы можете определить пустой рисунок, ограничившись лишь указанием его размеров constructor

C r e a t e (const A W i d t h ,

AHeight:

Integer);

создать объект, загрузив его данные из потока или файла constructor constructor

C r e a t e F r o m S t r e a m (const A S t r e a m :

TStream) ;

C r e a t e F r o m F i l e (const A F i l e N a m e :

string);

и, наконец, сформировать новый растровый объект на основе другого рисунка и его монохромной маски constructor

C r e a t e F r o m B i t m a p A n d M a s k (const B i t m a p ,

Mask:

TBitmap);

Загрузка и сохранение изображения Традиционно растровый объект обладает возможностями загрузки данных из файла и потока procedure

L o a d F r o m F i l e (const A F i l e N a m e :

const procedure

Rotate:

L o a d F r o m S t r e a m (S t r e a m :

string;

Single =

0);

TStream);

и сохранения картинки в файл и поток. procedure

S a v e T o F i l e (const A F i l e N a m e :

const procedure

SaveParams:

SaveToStream(Stream:

string; PBitmapCodecSaveParams

TStream);

=

nil);


Глава 16

236

Если загрузка изображения осуществилась успешно, то метод function

IsErapty: B o o l e a n ;

возвратит значение

false.

Зам ечание

В момент сохранения изображения допускается его преобразование в другой графи­ ческий формат. Помощь в этом окажет определенный в модуле Е М Х . T y p e s класс TBitmapCodecManager.

Кодирование и декодирование графических форматов Класс T B i t m a p далеко не всемогущ, и при обработке поддерживаемых им графиче­ ских форматов зачастую нуждается в услугах сторонних "лиц", в частности класса T B i t m a p C o d e c M a n a g e r , обеспечивающего кодирование и декодирование хранящихся в графическом контейнере данных. Перечень расширений имен файлов, поддерживаемых классом возвратит метод class function

GetFileTypes:

TBitmapCodecManager,

string;

Если вы работаете под управлением Windows, то получите текстовую строку при­ мерно следующего содержания: * . b m p ; * . j p g ; * . j p e g ; * . p n g ; * . g i f ; * . t i f ; * . i c o ; * . wmp

Если приложение выполняется под управлением OS X, то менеджер возвратит * .bmp;*. i c n s ; *.j p g ; *.jp2 ; * . j p e g ; * .png;*.g i f ;*.t i f ; *.tga

Заметьте, что в возвращаемой строке расширения имен файлов разделяются точкой с запятой. Для упрощения работы программиста, использующего в своем проекте диалоги открытия и сохранения файлов, в T B i t m a p C o d e c M a n a g e r предусмотрен метод class function

GetFilterString:

string;

передающий в программу перечень поддерживаемых форматов в виде, приемлемом свойства F i l t e r компонентов ( T O p e n D i a l o g И T S a v e D i a l o g ) .

ДЛЯ

Если текстовой строки с перечнем расширений поддерживаемых файлов вам не­ достаточно, то для того чтобы убедиться в наличии необходимого кодека, доста­ точно просто направить имя файла в функцию класса class function

I s C o d e d E x i s t s (const A F i l e N a m e :

string):

Boolean;

Получение миниатюры изображения Если в разрабатываемом вами приложении предусматривается предварительный просмотр рисунков, то вместо загрузки больших файлов с изображениями более рациональным решением может стать получение их уменьшенных копий — миниа­ тюр.


237

Двухмерная графика

С такой задачей справится процедура procedure L o a d T h u m b n a i l F r o m F i l e (const A F i l e N a m e : string; const AFitwidth, A F i t H e i g h t : S i n g l e ; const U s e E m b e d d e d :

B o o l e a n = True);

Кроме обязательного имени файла с растровой картинкой, метод потребует от вас явным образом указать геометрические размеры (параметры AFitwidth и A F i t H e i g h t ) миниатюры. Еще одну операцию, связанную с созданием миниатюры, осуществляет функция function

C r e a t e T h u m b n a i l (const W i d t h ,

Height:

Integer):

TBitmap;

Она также формирует миниатюру заданного размера, но на этот раз не из файла, а из уже загруженного в контейнер рисунка.

Свойства изображения Если в графический контейнер T B i t m a p загружена картинка (проверку этого можно произвести с помощью метода isEmpty), то мы получаем возможность оценить ее ключевые параметры с помощью свойств, представленных в табл. 16.5. Таблица 16.5. Основные свойства FMX. Types. TBitmap Свойство

Описание

property

Handle: //только чтение

Дескриптор изображения

TBitmapHandle;

property

Width:

property

Height:

property

P i x e l Format:

Ширина картинки

Integer;

Высота картинки

Integer; TPixelFormat;

Глубина цвета

BitmapScale: //только чтение

Single;

Масштаб

property

TCanvasClass;

Класс холста

property

CanvasClass:

/ /т о л ь к о ч т е н и е

Простые манипуляции графическим образом В классе T B i t m a p максимально упрощены все наиболее востребованные операции с графическим образом. Для изменения геометрического размера достаточно обра­ титься к методу procedure

R e s i z e (const A W i d t h ,

AHeight:

Integer);

Для поворота рисунка на заданный градус вызываем процедуру procedure

R o t a t e (const A n g l e :

Single);

Для вращения вокруг горизонтальной и вертикальной оси задействуются методы procedure procedure

FlipHorizontal; FlipVertical;


238

Гпава 16

Инвертирование альфа-канала осуществит метод prooedure

InvertAlpha;

Замена прозрачного цвета выполняется процедурой procedure

R e p l a c e O p a q u e C o l o r (const C o l o r :

TAlphaColor);

Редактирование битового образа Одно из неоспоримых преимуществ класса f m x .Ty p e s . T B i t m a p над его тёзкой из VCL заключается в том, что он позволяет программисту рисовать на поверхности изображения, используя удобный интерфейс холста property

Canvas:

TCanvas;

Внесенные изменения могут быть сохранены в файл или в поток с помощью уже анонсированных ранее методов S a v e T o F i l e () И S a v e T o S t r e a m (). При необходимости весь рисунок или его отдельная прямоугольная область могут быть очищены, точнее говоря, залиты определенным цветом. Эту сервисную воз­ можность обеспечивают методы procedure procedure

C l e a r (const A C o l o r :

TAlphaColor);

C l e a r R e c t (const A R e c t :

TRectF;

const

AColor:

T A l p h a C o l o r = 0);

К процессу изменения картинки имеет отношение еще одна пара перегружаемых методов: procedure procedure

C o p y F r o m B i t m a p (const S o u r c e :

TBitmap);

C o p y F r o m B i t m a p (const S o u r c e :

TBitmap;

SrcRect:

TRect;

D stX,

overload;

DstY:

Integer);

overload;

Перечисленные процедуры копируют рисунок из образа Source. Единственное ус­ ловие корректной работы методов заключается в равенстве геометрических разме­ ров образа-источника и получателя. Для любителей экстремальных способов редактирования рисунка упомянем о функции function

Map

(const

Access:

TMapAccess;

var

D ata:

T B i t m a p D a t a ) : Boolean;

предоставляющей низкоуровневый доступ к битовой карте изображения через параметр Data. Особенности доступа ( m a R e a d — только чтение; m a w r i t e — только запись, m a R e a d W r i t e — чтение и запись) определяются параметром Ac c e s s . Завершив работу с битовой картой, следует закрыть сессию, воспользовавшись

процедурой procedure

U n m a p (var D a t a :

TBitmapData);


239

Двухмерная графика

Управление графической производительностью При работе с графикой приложения FireMonkey в первую очередь стараются задей­ ствовать всю вычислительную мощь процессора видеокарты, и только если это не­ возможно, нагруз а перекладывается на центральный процессор. Вместе с тем, нам как разработчикам программного обеспечения следует понимать, что не каждый пользователь будет готов немедленно отправиться в магазин за очередным видео­ адаптером, лишь бы насладиться изысканным интерфейсом наших с вами про­ грамм. Для того чтобы пользователь смог оптимизировать распределение задач между графическим и центральным процессорами, программисту стоит позволить ему управлять состоянием ряда глобальных переменных, объявленных в модуле F M X . T y p e s (табл. 16.6). Таблица 16.6. Настройка графической производительности Windows Переменная

Умолчание

Описание

G l o b a l D i s a b l e F o c u s E f f e e t : Boolean;

false

Эффект фокуса ввода. Для устройств с низкой производительностью уста­ новить в true

Globa l U s e D i r e c t 2 D :

true

Использовать аппаратный Direct2D

GlobalUseDirect2DSoftware: Boolean;

f alse

Установить программную эмуляцию Direct2D

G l o b a l U s e H W E f f e c t s : Boolean;

true

Использовать аппаратное ускорение там, где это возможно

GlobalUseGDIPlusClearType:

t rue

Использовать технологию ClearType для улучшенного отображения шрифтов в Windows

Boolean;

Boolean;


ГЛАВА 1 7

Графические эффекты Платформа FireM onkey— это в первую очередь мощная графическая библиотека, предназначенная для создания современного привлекательного пользовательского интерфейса. Тому подтверждением выступает огромная плеяда специализирован­ ных компонентов со страницы Effects палитры компонентов, реализующих тот или иной графический алгоритм (от простого управления яркостью и контрастностью рисунка до сложнейших алгоритмов затуманивания, свертки, зыби на воде и т. п.). Более шести десятков невизуальных компонентов со страницы Effects построены на фундаменте цепочки классов " T E f f e c t — T F i l t e r E f f e c t — T I m a g e F X E f f e c t " . Вне зависимости от "классовой" принадлежности, все графические эффекты можно раз­ делить на пять категорий: 1. Простейшие корректирующие эффекты, в которых результирующая окраска пиксела зависит только от его исходного цвета и некой функции преобразова­ ния. В качестве примеров подобных эффектов можно привести обычное инвер­ тирование изображений, когда цвет пикселов меняется на противоположный. 2. Эффекты, в которых для расчета итогового цвета функция преобразования ана­ лизирует не только целевой пиксел, но и пикселы, входящие в его ближайшее, а иногда и дальнее окружение. К таким эффектам относят эф ф ект ы зат ум ан ива­ ния (blur) и эф ф ект ы искаж ения (distortions) изображения (например, свертка в спираль). 3. А ддит ивны е эф ф ект ы (additive effects), привносящие в изображение новые эле­ менты (например, зеркальное отражение). 4. Геометрические эффекты, изменяющие форму и пропорции изображения. 5. Разнообразные эф ф ект ы т рансляции (transition effects), позволяющие получать итоговый рисунок путем перехода от одной текстуре к другой. В период визуального проектирования подключение компонентов-эффектов к сво­ им целевым объектам (в первую очередь компонентам, работающим с текстурами в формате T B i t m a p ) осуществляется традиционным для библиотеки FireMonkey спо­ собом — с помощью окна управления структурой проекта (рис. 17.1). Програм-


241

Графические д Щ д и и ь

Рис. 17.1.

Подключение компонента-эффекта

InvertEf fectl

к целевому объекту I m a g e l

мисту достаточно просто перетащить с помощью мыши требуемый графический фильтр в подчинение заинтересованному в визуальных "фокусах" объекту. Если разрабатываемое вами приложение предполагает динамическое включе­ ние/отключение различных визуальных эффектов во время выполнения программы, то стоит познакомиться со строками кода из листинга 17.1. ■ Листинг 17.1. Под :пю«- «и >зф о .. тг

-* ври -:i ы >л:.

.... :

нпрогр мМЫ

var i:integer; begin //находим и отключаем старый эффект

for i :=0 to Imagel.ChildrenCount-1 do if Imagel.Children[i] is TImageFXEffect then bagin I m a g e l .R e m o v e O b j e c t (I m a g e l .C h i l d r e n [i ]);

break; end; //подключаем новый

эффект

Imagel.AddObject(BlurEffectl); end;

Предложенный фрагмент кода решает две задачи. Во-первых, благодаря циклу f o r среди дочерних компонентов объекта i m a g e 1 мы находим ранее подключенный компонент-эффект (потомок класса т е f f e e t ) и отсоединяем его от объекта. Вовторых, с помощью метода A d d O b j e c t о к изображению i m a g e l мы присоединяем новый эффект, в нашем примере это B a n d s E f f e c t i .


242

Гпава 17

Применение эффекта к файлам изображений Даже беглое знакомство с набором компонентов fireMonkey, реализующих алго­ ритмы разнообразных визуальных фильтров, впечатлит любого программиста, ко­ торый сталкивался с разработкой графических редакторов. Сразу возникает непре­ одолимое желание засучить рукава и написать приложение, способное составить конкуренцию таким грандам IT-индустрии, как Adobe Photoshop и Corel PhotoPaint. Нет ничего невозможного, однако на первых порах придется решить одну непростую задачу — научиться применять эффекты не просто к элементам управ­ ления, а к хранящимся в файлах картинкам и сохранять полученные результаты в файл. Так в чем проблема? Если мы попробуем вызвать метод сохранения картинки (к которой применен тот или иной графический фильтр) в файл, например Imagel.Bitmap.SaveToFile(<M!№ файла>); то, просмотрев файл, к своему разочарованию получим исходное изображение без единого намека на фильтрацию. Все дело в том, что после подключения к любому элементу управления, в том числе и контейнеру изображений т image, компонентыэффекты никак не воздействуют на физическую структуру рисунка, а лишь управ­ ляют процессом вывода картинки на экран. Зам ечание

Компоненты-эффекты никак не воздействуют на физическую структуру рисунка, а лишь управляют процессом вывода картинки на экран. Отключение эффекта (напри­ мер, с помощью свойства Enabled) возвращает результирующее зображение в ис­ ходное состояние.

Одним из решений задачи сохранения эффекта в файл может стать небольшой об­ ман. Суть идеи заключается в том, что после применения графического фильтра (например, коррекции контрастности ContrastEffectl: TContrastEffeet) к картинке (хранящейся в компоненте image 1: т image) мы скопируем видимую область изо­ бражения в другой объект — растровый рисунок TBitmap. И только затем отправим результаты, полученные таким "мошенническим" путем, в файл (листинг 17.2). Листинг 17.2. Сохранение результатов в файл var TempForm:TForm;

TempImage:TImage; TempBitmap:TBitmap; begin if SaveDialogl.Execute then begin //создаем временную невидимую форму

TempForm:=TForm.CreateNew(Application); TempForm.ClientWidth:= Imagel. Bitmap.Width; TempForm.ClientHeight:=Imagel. Bitmap.Height; / / н а ф о р м е р а з м е щ а е м в р е м е н н ы й к о н т е й н е р TImage TempImage:=TImage. Create(TempForm);


243

Графические эффекты T e m p I m a g e .P a r e n t := T e m p F o r m ; T e m p I m a g e . W i d t h := I m a g e l . B i t m a p . W i d t h ; T e m p I m a g e .H e i g h t := I m a g e l .B i t m a p .H e i g h t ; / / п е р е н о с и м в к о н т е й н е р рисунок,

хранящийся

в I m a g e l :ТI m a g e

Templmage.Bitmap.Assign(Imagel.Bitmap); //применяем к контейнеру эффект

ContrastEffectl:TContrastEffectl

TempImage.AddObject(ContrastEffectl); //теперь

в п а м я т и есть

//рисунком,

невидимая

форма

с невидимым пользователю

к которому применен графический фильтр

//создаем пустой битовый образ TempBitmap:=TBitmap.Create(bWidth,bHeight); //переносим в образ изображение из невидимой

формы

' T e m p B i t m a p .C a n v a s .B e g i n S c e n e () ; TempForm.PaintTo(TempBitmap.Canvas);//!!! рисуем именно

так

!!!

T e m p B i t m a p .C a n v a s .E n d S c e n e ; //сохраняем результат

в файл

T e m p B i t m a p . S a v e T o F i l e ( S a v e D i a l o g l .F i l e N a m e ) ; //освобождаем ресурсы T e m p B i t m a p .F r e e ; TempForm.Release;

end;

Листинг достаточно подробно прокомментирован, но сделаю еще одно очень важ­ ное замечание. Картинка с эффектом должна быть нарисована на холсте целевого битового образа T e m p B i t m a p именно методом P a i n t T o о формы, а не каким-либо другим способом!

Применение нескольких эффектов к файлам изображений Предлагаю провести один простой, но очень поучительный эксперимент. Попро­ буйте одновременно подключить к компоненту т i m a g e по крайней мере пару разно­ типных графических фильтров, например T W a v e E f f e c t и T P i x e i a t e E f f e c t . Каков ре­ зультат? Идеалисты наверняка надеялись, что получат картинку со сложением эф­ фектов водной глади и пикселизации. В свою очередь скептики отошли подальше от компьютера, предполагая, что он расплавится при решении непомерно сложной вычислительной задачи. Как всегда истина находится посредине — компьютер ос­ тался цел, а к изображению применился всего один, первый по счету графический фильтр. Разочарованы? Замечание

Независимо от числа присоединенных к визуальному элементу управления компонентов-эффектов, к нему будет применяться только один графический фильтр.


244

Гпава 17

В том, что визуальный элемент управления готов сотрудничать всего с одним гра­ фическим фильтром, а на остальные не обращает внимания, нет ничего удивитель­ ного. Это разумное ограничение, не позволяющее превратить проект FireMonkey в неработающую новогоднюю елку, ведь наверняка найдется "программист", кото­ рый подключит ко всем визуальным компонентам все компоненты-эффекты. В ито­ ге процессоры компьютера посвятят все свое время перерасчету фильтров. Однако нет ничего невозможного. При остром желании можно решить задачу при­ менения нескольких графических фильтров к одному изображению, но это надо делать последовательно. В качестве основы можно воспользоваться идеей из лис­ тинга 17.2, но ее придется немного доработать. Предположим, что наш графический редактор активно пользуется услугами не­ скольких фильтров, среди них: □ фильтр контрастности/яркости □ фильтр цветового тона

Co n t r a stEffectl: TContrastEffeet;

HueAdjustEffectl:

□ ряд других компонентов,

ПОТОМКОВ

THueAdjustEffect;

класса

TImageFXEffeet.

Пользователь рассчитывает изменять характеристики этих фильтров при редакти­ ровании одного и того же изображения, при этом результат его манипуляций дол­ жен немедленно отображаться на экране. В первую очередь объявим глобальную переменную-список, способную хранить произвольный перечень компонентов-эффектов, и инициализируем ее в момент создания главной формы проекта (листинг 17.3). i Листинг 17.3. Список объектов TlmageEXEffect uses

...,

System.Generics.Collections;

II... var

Forml: TForml; EffectsList:

TObjectList<TImageFXEffect>;

//список TImageFXEffect

implementation {$ R * . fmx}

p r o c e d u r e T F o r m l .F o r m C r e a t e (S e n d e r : T O b j e c t ); begin E f f e c t s L i s t := T O b j e c t L i s t c T I m a g e F X E f f e c t > .C r e a t e (f a l s e ) ; EffectsList.Add(ContrastEffectl); EffectsList.Add(HueAdjustEffectl); E f f e c t s L i s t . A d d (<другие

графические

фильтры>);

Обратите внимание на то, что после создания списка мы сохраняем в нем ссылки на все имеющиеся в приложении фильтры, но при этом не делаем список их вла­ дельцем.


245

Гоафические эффекты

А теперь разработаем функцию A p p iayE ffects (), на вход которой будет направлено подлеж ащ ее редактированию изображ ение source:TBitmap и перечень эффектов L is t: TObjectList<TimageFXEffect>. А на вы ходе функции долж но оказаться и зм е­ ненное изображ ение TBitmap (листинг 17.4). : П: тин г

ПА. Применение списка эфе*

истов к изображению

Function A p p ia y E ffe c ts (const Source:TBitmap; const L is t: TObjectList<TIm ageFXEffect>) :TBitmap; var TempForm:TForm;

TempImage:TImage; TempBitmap:TBitmap; i : in te g e r ; begin / / -------

невидимая форма

с в р е м е н н ы м и з о б р а ж е н и е м ----------

TempForm:=TForm.CreateNew(Application); TempForm!ClientWidth:=Source.Width; TempForm.ClientHeight:=Source. H eig h t; TempImage:=TImage. Create(TempForm); TempImage. P aren t:=TempForm; TempImage. Width:=Source. Width; TempImage. H eig h t:=Source. H eigh t; Tem pIm age.Bitm ap.Assign(Source);

//----------------------------------------------------------------------------Tem pBitm ap:=TBitm ap.C reate(Source.W idth,Source.H eight); // ----------- п о с л е д о в а т е л ь н о п р и м е н я е м в с е ф и л ь т р ы ------------

for i:= 0 to L ist.C o u n t-1 do begin TempImage.AddObject(List.Items[i]);//применяем фильтр T e m p B i t m a p . C a n v a s .B e g i n S c e n e ();

TempForm.PaintTo(TempBitmap.Canvas); TempBitmap. Canvas. EndScene; T em pIm age.R em oveO bject(L ist.Item s[i] ); TempImage.Bitmap.Assign(TempBitmap);

//перерисовка

//снимаем фильтр

end;

/ / -------------------------------------------------------------------------------R e s u lt: =TempImage. Bitmap; TempBitmap. F ree; TempForm.Release; end;

Нам осталось лишь регулярно вызывать разработанную функцию при лю бом д ей ­ ствии пользователя, и все хранящ иеся в списке L i s t фильтры по порядку применят­ ся к редактируем ому изображ ению .


246

Гпава 17

Простейшие корректирующие эффекты Задействованные в корректирующих эффектах математические модели анализиру­ ют только исходное состояние изменяемого пиксела и не обращают внимания на остальные пикселы текстуры, поэтому корректирующие алгоритмы относительно просты и нересурсоемки. Ряд компонентов, реализующих простейшие эффекты, практически не нуждаются в дополнительной настройке свойств. Это утверждение относится к: □ компоненту

инвертирующему цвет рисунка;

TinvertEffect,

□ компоненту T M a s k T o A i p h a E f f e c t , превращающему в белые (путем добавления альфа-канала) все серые пикселы изображения; □ компоненту хромный.

TMonochromeEffect,

конвертирующему цветной рисунок в моно­

Подключив перечисленные компоненты к целевому объекту с текстурой, мы сразу, без каких-либо дополнительных настроек, получим требуемый результат.

Заливка цветом TFillEffect и TFillRGBEffect Простейшую задачу по заливке пикселов изображения определенным цветом ре­ шают компоненты T F i l l E f f e c t и T F i l l R G B E f f e c t . Разница между классами заключа­ ется в том, что первый из них закрасит заданным цветом всю поверхность визуаль­ ного объекта. Второй компонент немного хитрее и применит заданный цвет только к непрозрачным пикселам. Не трудно догадаться, что наиболее важное свойство обоих компонентов property

Color

: TAlphaColor;

устанавливает цвет заливки.

Яркость и контрастность TContrastEffect Наиболее востребованная корректировка изображения, связанная с управлением яркостью и контрастностью, производится с помощью компонента T C o n t r a s t E f f e c t . За установку яркости отвечает свойство property

Brightness

: Single;

/ / д и а п а з о н з н а ч е н и й о т -1 д о

1

Для повышения яркости следует выбрать значения выше нуля, отрицательная часть диапазона соответствует пониженной яркости. Управление контрастностью осуществляет свойство property

Contrast

: Single;

//диапазон

значений от

0 до 2

Значение, превышающее 1,5, увеличивает контрастность, значение меньше 1,5 — уменьшает.


Гр& ф ичоонио оф ф онт ы

т

Регулировка оттенка цвета THueAdjustEffect Компонент T H u e A d j u s t E f f e c t позволяет управлять оттенком цвета изображения, для этих целей класс снабжен свойством property

Hue

: Single;

//диапазон

от

-1

до

1

Ясная TBIoomEffect и пасмурная TGIoomEffect погода Два родственных компонента T B I o o m E f f e c t и T G I o o m E f f e c t нацелены на придание изображению двух противоположных качеств — имитации яркого солнечного или хмурого пасмурного дня. Для изменения параметров рисунка каждый из компонентов обладает четверкой свойств. Так, компонент T B I o o m E f f e c t для настройки интенсивности свечения за­ действует свойства property proparty

Bloomlntensity Baselntensity

Цветовое насыщение property property

: Single; : Single;

TBIoomEffect

BloomSaturation BaseSaturation

//диапазон

з н а ч е н и й от

0 до

1

//диапазон

з н а ч е н и й от

0 до

1

зависит от состояния свойств

: Single; : Single;

//диапазон

значений от

0 до

1

/ / д и а п а з о н з н а ч е н и й от

0 до

1

Свойства T G I o o m E f f e c t практически повторяют свойства его коллеги, интенсив­ ность свечения определяется состоянием свойств property property

Gloomlntensity Baselntensity

: Single; : Single;

//диапазон значений

от

0 до

1

/ / д и а п а з о н з н а ч е н и й от

0 до

1

Насыщенность цветом определяется свойствами property property

GloomSaturation BaseSaturation

: Single; : Single;

//диапазон

значений

от

0 до

/ / д и а п а з о н з н а ч е н и й от

1

0 до. 1

Прозрачность TColorKeyAlphaEffect Для того чтобы сделать определенный цвет текстуры прозрачным, стоит восполь­ зоваться услугами компонента T C o l o r K e y A l p h a E f f e c t . Значение ключа, определяю­ щего, какой именно цвет должен стать прозрачным, задается в свойстве property

ColorKey

: Single;

//диапазон

значений от

-1 д о

1

Допустимая степень отклонения цвета от значения заданного в свойстве определяет свойство property

Tolerance

: Single;

//диапазон

значений от

0 до

ColorKey

1

Если свойство принимает нулевое значение, то эффект прозрачности отключается, с возрастанием значения число цветов, попадающих под критерий прозрачности, возрастает.


248

Гпава 17

Эффекты размытия и искажения По сравнению с алгоритмами, специализирующимися на создании простых коррек­ тирующих эффектов, алгоритмы размытия и искажения обрабатывают куда боль­ ший объем исходных данных. Дело в том, что в их основу положен более глубокий математический анализ растрового изображения. В алгоритмах размытия и искаже­ ния решение о закраске результирующего пиксела основывается на обработке группы пикселов, входящих в его ближайшее окружение.

Размытие Эффекты размытия (затуманивания) достигаются при активном участии компо­ нентов: T B l u r E f f e c t , T D i r e c t i o n a l B l u r E f f e c t , T B o x B l u r E f f e e t , T G a u s s i a n B l u r E f f e e t И T R a d i a i B i u r E f f e c t . Суть эффекта размытия заключается в том, что все изображение становится расфокусированным без выделения какой-либо области. Каждый из компонентов решает задачу по-своему. Так, самый непритязательный из них (ком­ понент T B l u r E f f e c t ) просто затуманивает изображение за счет смещения основных контуров рисунка. Степень размытия определяет свойство property

Softness

: Single;

/ / п о у м о л ч а н и ю 0 ,3

способное принимать значение из диапазона от 0 до 9. Более сложный класс T G a u s s i a n B i u r E f f e c t создает эффект дымки путем расфокуси­ ровки изображения в соответствии с законом распределения Гаусса. В результате работы алгоритма, заложенного В компонент T G a u s s i a n B i u r E f f e c t , сведения о пик­ селах распределяются от центра наружу по колоколообразным кривым. У всех рассматриваемых компонентов (за исключением T B l u r E f f e c t с его свойст­ вом S o f t n e s s ) базовым свойством, управляющим степенью размытия изображения,

является property

BlurAmount

: Single;

Кроме того, у компонентов предусмотрен ряд вспомогательных свойств, оказы­ вающих дополнительное воздействие на текстуру объекта. В частности у компо­ нента T D i r e c t i o n a l B l u r E f f e c t , позволяющего конкретизировать, в каком направле­ нии будет смещен рисунок для получения эффекта затуманивания, предусмотрено свойство p r o p e r t y Angle : Single;

определяющее угол смещения. В свою очередь реш аю щ ий сх о ж у ю задачу компонент T R adiaiB iurE ffect предлага­ ет программисту свойство property

Center

: TPointF;

позволяющее определиться с координатами центра эффекта.


249

Графические эффекты

Искажения Если вам необходимо изменить текстуру объекта до неузнаваемости, то стоит по­ ближе познакомиться с компонентами, отвечающими за искажения. Среди них вы найдете специалистов по увеличению выбранной области рисунка, скручиванию рисунка, имитации эффекта водной глади и по многому другому.

Вертшсальные полосы TBandsEffect Компонент T B a n d s E f f e c t позволяет покрыть изображение вертикальными полоса­ ми, в результате получится эффект, в какой-то степени напоминающий жалюзи (рис. 17.2). Управление эффектом осуществляется с помощью свойства property

BandDensity

: Single;

//диапазон от

0 до

1 50

определяющего число вертикальных полос, и свойства property

Bandlntensity

: Single;

//диапазон от

0 до

1

управляющего интенсивностью засветки границ полос.

Рис. 17.2. Пример эф фекта вертикальных полос, компонент T B a n d s E ffe c t


Глава 17

250

Водоворот TSwirlEffect и TBandedSwirlEffect Если вы поклонник неординарных решений, то эффект водоворота, создаваемый компонентами T S w i r l E f f e c t и T B a n d e d S w i r l E f f e c t , подойдет вам как нельзя лучше (рис. 17.3). Центр водоворота определяется координатами точки ' property

У

Center

TSwirlEffect

property

: Т PointF;

интенсивность скручивания определяется свойством

SpiralStrength:

Single;

У T B a n d e d S w i r l E f f e c t название свойства, решающего аналогичную задачу, немного другое: property

Strength

: Single;

От знака значения, хранимого в свойстве, зависит направление вращения водово­ рота. Если вы работаете с более "продвинутым" компонентом T B a n d e d S w i r l E f f e c t , то по­ лучаете возможность управлять дополнительным параметром— числом полос (кругов от водоворота) вокруг центральной точки эффекта property

Q

Bands

: Single;//диапазон значений от

0 д о 20

Э ф ф е к ты н е о с н о в е а н а л и з ? г р у п п п и к с е л е й

О

IB iu iP fect

0

TQ rsc t о™ '

©твокаши* © © ТЯММЬяЕ

Р ис. 17.3. Пример эф фекта водоворота, компонент TBandedSwirlEffect


251

Гоафи1 goi э эффекты

Еще одна особенность водоворота зависит от состояния свойства proparty AspectRatio

: Single;

// д и а п а з о н з н а чений от

0.5 до 2

которое определяет наклон результирующего изображения.

Увеличительное стекло TiyiagnifyEffect и TSmoothMagnifyEffect Компоненты T M a g n i f y E f f e c t и T S m o o t h M a g n i f y E f f e c t ПОЗВОЛЯЮТ Программисту "под­ нести" к рисунку увеличительное стекло. Разница между компонентами заклю­ чается в том, что T M a g n i f y E f f e c t просто увеличивает заданную область, а T S m o o t h M a g n i f y E f f e c t , кроме того, вносит искажения, имитируя реальную стеклян­ ную линзу (рис. 17 4).

Рис. 17.4. Увеличение области без искажения ( T M a g n i f y E f f e c t ) и с искажением (1 S m o o t h M a g n i f у Е f f ect)

Коэффициент увеличения задается свойством prof-arty

Magnification

: Single;

/ / д и а п а з о н от

1 до

5

Увеличиваемая область представляет собой окружность с центром, определяемым свойством property

C e n t e r - : TPointF;

Если вы работаете с T M a g n i f y E f f e c t , то радиус увеличиваемой области зависит от состояния единственного свойства property

Radius

: Single;

/ / д и апазон от

0 до

1


Гпава 17

252

Второй компонент, T S m o o t h M a g n i f y E f f e e t — это размер увеличиваемой области, он назначается исходя из состояния пары свойств: property property

InnerRadius

: Single;

//диапазон значений от

0 до

1

OuterRadius

: Single;

//диапазон

0 до

1

значений от

соответственно определяющих радиусы внутренней и внешней границ эффекта. Если при выводе на экран пропорции рисунка нарушаются, то увеличиваемая об­ ласть из круга превращается в эллипс. Чтобы устранить искажения, следует обра­ титься к свойству property

AspectRatio

: Single;

//диапазон от

0.5 до

2.00

Стягивание области TPinchEffect Компонент T P i n c h E f f e c t реализует еще один эффект, искажающий не всю рабочею поверхность текстуры, а лишь область (рис. 17.5) в форме окружности с центром в точке с координатами property

Center

: TPointF;

Радиус искажаемой области property

Radius

: Single;

/ / д и а п а з о н от

0 до

1

Степень искажения области зависит от состояний свойства property

Strength

: Single;

//диапазов

>т 0 д о 20

Рис. 17.5. Пример Эффекта сдавливания области, компонент TPinchEffect


253

Графические эффекты

Для восстановления пропорций окружности следует задействовать свойство property

AspectRatio

: Single;

//диапазон от

0.5 д о

2.00

Рябь на воде TRippleEffect Для создания эффекта ряби, которая появляется на воде после падения в нее ка­ кого-то предмета (рис. 17.6), стоит воспользоваться услугами компонента T R i p p l e E f f e c t . Основные свойства класса позволяют управлять координатами цен­ тра эффекта property

Center

: TPointF;

амплитудой зыби property

Amplitude

: Single;

//диапазон

значений

от

0 до

1

: Single;

//диапазон

значений от

0 до

1 00

частотой волн property

Frequency

и фазой property

Phase

: Single;

Кроме того, в распоряжении программиста имеется свойство prof arty

AspectRatio

: Single;

позволяющее управлять пропорциями эффекта. . ф Эффекть* «а oo*ose а>*ая»*за групп пикселей

' Эфф*с?№.......................... © Т&гщсШ&бш&Я*.

Ш TM*gnify£ff#ct

ФTBoxS^ffect

# IРтФ&т

Ш ?S*usfcan8iiMfffect

# TRioipft&fect

0 7Refi?;a;gfu?£JSs«

ф т$вмев$*ШЯеа О TSmootnM sg-";iyEff< © TSwirfSftKt 0 TWaveSffect

Amjtf?tude*Q.13 regency*77.35

Рис. 17.6. Пример эф ф екта ряби на воде, компонент T R ip p le E f f e c t


Гпава 17

254

Волны TWaveEffect Еще одним специалистом по "водным" эффектам можно считать класс T W a v e E f f e c t , его задача заключается в создании иллюзии водной глади, покрытой волнами. Расстояние между волнами на воде определяется свойством. property

WaveSize

: Single;

//диапазон

значений от

32 д о

256

Управляя параметром property

Time

: Single;

//диапазон от

0 до 2048

мы сможем моделировать перемещение волн по поверхности.

Горизонтальная деформация краев текстуры TWrapEffect Компонент T W r a p E f f e c t позволяет применить к левому и правому краям изображе­ ния эффект горизонтальной деформации. Управление эффектом осуществляется отдельно для каждого из края. Так, состоянием левой грани ведают свойства: property property property property

LeftStart

: Single;

//верхняя опорная

точка левой

грани

LeftControll

: Single; / / п ервая о т к л о н я ю щ а я

LeftControl2

: Single; //в т о р а я о т к л о н я ю щ а я т о ч к а л е в о й

LeftEnd

: Single;

точка левой грани

//нижняя опорная точка левой

грани

грани

Свойства определяют координаты точек невидимой кривой Безье, проходящей по левой границе рисунка. Координаты кривой представлены не в виде физических значений (X , У), а в виде вещественного числа из диапазона значений от 0 до 1 Если вы заполните все свойства L e f t . .. нулями, то эффект горизонтальной дефор­ мации для левой грани отключается и она выпрямляется. Увеличение значений приводит к смещению левой границы изображения вправо. Степенью деформации правой грани ведает квартет аналогичных свойств: property p ro p erty p ro p arty proparty

RightStart

: Single;

//верхняя опорная

точка правой грани

RightControll

: Single; // п ервая о т к л о н я ю щ а я точка п р а в о й

грани

RightControl2

: Single, / / в т о р а я о т к л о н я ю щ а я

грани

RightEnd

: Single;

точка правой

//нижняя опорная точка правой

грани

Правила управления правой гранью несколько отличаются. На этот раз единичные значения соответствуют недеформированной грани, а уменьшение значений приво­ дит к смещению правой грани рисунка влево по горизонтали.

Аддитивные эффекты Аддитивны е эффекты основаны на принципе добавления к исходному изображе­

нию каких-либо дополнительных графических элементов.

Отражение TReflectionEffect

Эффект

Отражения ВЫВОДИМОГО В нижней части изображения достигается с по­

мощью компонента

TReflectionEffect.

p roperty Length: Single;

Размер отражения определяется свойством


255

Графические эффекты

Свойство воспринимает значения из диапазона от 0 до 1 ( м а к с и м а л ь н ы й ражения).

разм ер от­

Эффекты свечения TGIowEffect и TlnnerGlowEffect Два схожих по задачам компонента, создающие эффект свечения с той лишь разни­ ц е й , Ч Т О T G I o w E f f e c t предназначен для создания свечения вокруг рамки с изобра­ жением, a T l n n e r G l o w E f f e c t подсветит рамку изнутри. Цвет свечения определяется свойством property

GlowColor:

TAlphaColor;

Плавность полутоновых переходов и размер области свечения зависят от состояния свойства property

Softness:

Single;

//диапазон значений от

0 до

9

Тень TShadowEffect Компонент T S h a d o w E f f e c t позволяет программисту научить графический объект отбрасывать тень. Управление тенью осуществляется при посредничестве четверки свойств. В первую очередь это свойство propsrty

ShadowColor:

TAlphaColor;

определяющее цвет тени Протяженность и направление тени зависит от состояния свойств property

Distance:

pr o p e r t y Direction:

Single; Single;

//от

0 до

359

градусов

По умолчанию расстояние, на которое отбрасывается тень, равна трем единицам, а угол падения тени соответствует 45 градусам. Плавность цветовых переходов настраивается с помощью свойства prop arty

Softness:

Single;

Эффект тиснения TEmbossEffect Эффект тиснения, благодаря которому рисунок приобретает выпуклые формы, по­ зволяет обеспечить компонент T E m b o s s E f f e c t . Заложенный в компонент алгоритм находит в изображении ключевые контурные линии, создает их дубликаты и добавляет к ним тень, в результате наблюдателю начинает казаться, что контуры приподняты (или вдавлены). Глубина тиснения определяется состоянием свойства property

Amount:

S i n g l e ; //по у м о л ч а н и ю

0,5

Свойство воспринимает значения из диапазона от 0 до 1.


Гпава 17

256

Также можно управлять расстоянием между линиями контура и дубликатами этих линий, для этого предназначено свойство p ro p e rty

Width:

Single;

// д и а п а з о н от

0 до

10

Набросок на бумаге TPaperSketchEffect Интересный художественный эффект предлагает компонент T P a p e r S k e t c h E f f e c t . В результате его применения к изображению оно превращается в черно-белый на­ бросок, который художник мог сделать углем на бумаге Единственный параметр, который управляет результирующим рисунком p ro p e rty

BrushSize:

Single;

//по умолчанию 3

определяет размер кисти. Результаты работы алгоритма, заложенного в компонент T P a p e r S k e t c h E f f e c t , отражает экранный снимок, предложенный на рис. 17.7 — по­ лученное изображение можно сравнить с оттиском гравюры на плотной бумаге

Эффект*.... TGiowEfSee © TfteftectionEffeGt

ШTSfcado»Etfect ф T£rs".bossEff«t # 7Pa<*7Stetth£ffect

Q TPe*iC'

О О ~S<PaEffec О

ОTToorttbct 8rus*Siz*«419

Рис. 17.7. Пример эффекта наброска на бумаге, компонент T P a p e r S k e t c h E f f e c t

Карандашный набросок TPencilStrokeEffect

Компонент TPencilStrokeEffect придает изображению вид нарисованного в р у ч н у ю . обработку изображения так, что в результате вместо цвет­ ной картинки получается эффект черно-белого карандашного наброска (рис. 17.8).

А лгоритм осущ ествляет


257

Г р а ф / iu&rvu& зфф&кгт*)

Базовое свойство компонента property

BrushSize:

S i n g l e ; //по у м о л ч а н и ю

5

отвечает за грубость рисунка. Свойство способно принимать значения из диапазона от 1 до 19.

Рис. 17.8. Пример эффекта карандашного наброска на бумаге, компонент T P e n c i l S t r o k e E f f e c t

Пикселизация TPixelateEffect Компонент T P i x e l a t e E f f e c t отвечает за создание эффекта пикселизации. Пиксели­ зация — это способ искажения изображения, при котором отдельные пикселы ста­ новятся различимы для невооруженного глаза или группы пикселов отображаются в виде блоков цветов. Управление эффектом осуществляется с помощью свойства property

BlockCount:

Single;

/ / п о у м о л ч а н и ю 25

По мере увеличения значения свойства степень пикселизации снижается.

Старая фотография TSepiaEffect Компонент T S e p i a E f f e c t придает изображению эффект пожелтевшей от старости фотографии. Степень старения зависит от состояния свойства property

Amount:

Single;

//по у м о л ч а н и ю 0,5


258

Гпава 17

Свойство воспринимает значения из диапазона от 0 до 1, где 1 соответствует ими­ тации наибольшего возраста фото.

Управление резкостью TSharpenEffect Компонент T S h a r p e n E f f e c t предназначен для управления резкостью целевого изо­ бражения. Для этого оказывается воздействие на интенсивность свечения пикселов картинки с целью достичь максимальной (или минимальной) разницы между со­ седними пикселами. Управление резкостью осуществляется с помощью свойства property

Amount:

Single;//по умолчанию

1

Диапазон допустимых значений заключен в пределы от 0 до 1.

Глубина цвета TToonEffect Компонент T T o o n E f f e c t управляет глубиной цвета, используемой при выводе изо­ бражения. В результате существенно упрощается цветопередача, чем и достигается "мультяшный" эффект. Для удобства управления эффектом глубина цвета разделе­ на на несколько цветовых уровней, которые управляются свойством компонента property

Levels:

Single;//по умолчанию

5

Свойство принимает значения из диапазона от 3 (минимум) до 15 (максимум).

Геометрические эффекты В соответствии со своим названием геометрические эффекты в первую очередь на­ целены на изменение параметров изображения, определяющих его геометрическую форму.

Аффинные преобразования TAffineTransformEffect Компонент T A f f i n e T r a n s f o m E f f e c t за счет аффинных преобразований позволяет повернуть изображение на заданный угол и управлять уровнем пикселизации тек­ стуры объекта. Для управления вращением следует воспользоваться свойствами p ro p e rty

Center: TPointF;

//координаты центра

property

Rotation:

//угол поворота

Single;

вращения

За уровень пикселизации текстуры отвечает свойство property

Scale:

Single;

//диапазон

от

0,05

до

4

Обрезка TCropEffect

Компонент

TCropEffect позволяет осуществить обрезку части изображения. Для этого надо указать координаты левой верхней

property LeftTop: TPointF;


Графичвскив эффекты

25?

и правой нижней точек отсечения property

RightBottom:

TPointF;

Все, что останется за пределами точек отсечения, выводу не подлежит. Позиции отсечения определяются в физических единицах измерения — пикселах. Внимание!

Для корректной реализации эффекта обрезки изображения последнее должно выво­ диться без искажения пропорций. Например, если в качестве целевого компонента ис­ пользуется T I m age, ТО его свойство W r a p M o d e желательно установить в состояние T I M a g e W r a p M o d e .i w O r i g i n a l .

Перспектива TPerspectiveTrans form Effect Коадюиейт T P e r s p e c t i v e T r a n s f o r m E f r e c t позволяет вносить геометрические иска­ жения в форму изображении, придавал последнему вид прямоугольника, ромба, трапеции или любого другого четьшехуголъниха. В частности, благодаря T P e r s p e c t i v e T r a n s f o r m E f f e c t можно получить преобразование перспективы (рис. 17.9).

Геометрически*,.»

Эффекты ф TAffineTransformEffect Ф TCropEffect Ф 1Normal BiendEftect О TPerspectivel ransfor nittfect # TTilerEMcct

Рис. 17.9. Э ф ф е к т

перспективы, компонент T P e r s p e c t i v e T r a n s f o j - m E f f e c t

Форма результирующего четырехугольника формируется за счет квартета точек: prop rty property

TopLeft: TcpRight:.

TPointF, TPointF;

//левый верхний угол //правый

верхний угол


Глава 17

260

property property

BottomRight: BottomLeft:

TPointF; TPointF;

//правый нижний угол //левый нижний угол

Заметим, что на результат оказывают влияние не только значения вершин четырех­ угольника, но и параметры объекта, содержащего изображение. Например, приме­ няя эффект к компоненту т image, стоит поэкспериментировать с его свойствами A l i g n И wordwrap.

Эффект плитки TTilerEffect Компонент T T i l e r E f f e c t реализует эффект плитки, размножая исходное изображе­ ние. Количество клонов рисунка определяется свойствами property property

HorizontalTileCount:

Single;

V e r t i c a l T i l e C o u n t : Single;

которые соответственно определяют число рядов и колонок. При желании программист может назначить отступы между рядами и колонками, для этого следует воспользоваться свойствами property property

H o r i z o n t a l O f f s e t : Single;

//горизонтальный отступ

VerticalOffset:

//вертикальный отступ

Single;

Наложение изображений TNormalBlendEffect Сознаюсь, что отнеся класс T N o r m a l B l e n d E f f e c t к компонентам, осуществляющим геометрические преобразования, я пошел на поводу способа классификации эффек-

Щ?

Геометрические эффекты

I -г— ^

Рис. 17.10. П ример эф ф екта наложения, создаваемого компонентом T N o r m a lB le n d E ffe c t


261

Г р а ф и ч е с к и е эф ф ект ы

в стенах Embarcadero Программисту всегда с т о и т с ч и т а т ь с я мнением компании разработчика, ХОТИ, на МОЙ ВЗГЛЯД, TNomalBlendEftect ближе к компонентам, предназначенным для проведения операций трансляции, в которых участвует пара изображений Впрочем, судите сами Опорное свойство компонента

ТОВ, П р е д л о ж е н н ы х с

property

Target:

TBitmap;

предназначено для хранения второго изображения, которое б у д е т наложено на ис­ ходное, Т. е. изображение, К КОТОрОМу П О Д 'Ш Ю Ч ен КОМПОНеНТ T N o r m a l B l e n d E f f e c t . Накладываемое изображение желательно создавать в формате PNG (напомним, что формат PNG поддерживает альфа-канал). Все остальное — дело техники. На рис. 17.10 производится сложение фотографии горного озера и надписи "FireMonkey", сохраненной в формате PNG

Эффекты трансляции Эффекты трансляции предназначены для создания управляемого плавного перехо­ да от исходного изображения ко второй — целевой картинке. На роль специалистов по трансляции претендуют два десятка компонентов Мы с вами остановимся на изучении лишь наиболее показательных из них— эффект, создающий завихрение;

TBandedSwirlTransitionEffect

T F a d e T r a n s i t i o n E f f e c t — эффект постепенного исчезновения исходного изобра­ жения с последующим выводом целевого,

TCircieTransitionEffect—

эффект проявления целевого изображения внутри

эллиптической области; □

TMagnifyTransitionEffect—

плавное проявление увеличенного целевого изо­ бражения внутри эллиптической области (рис. 17.11);

TWaterTransitionEffect

осуществляет переход между двумя изображениями с использованием эффекта волнистой водной поверхности.

Все наши новые знакомые снабжены парой важных свойств. В первую очередь это свойство property

Target:

TBitmap;

в котором хранится целевое изображение. Второе свойство property

Progress:

Single;

//диапазон от

0 до

100%

управляет процессом трансляции. Выбирая значение от 0 до 100, программист оп­ ределит процент выполнения эффекта. Компоненты

TCircieTransitionEffect,

TMagnifyTransitionEffect

И TBandedSwirl­

помимо управления степенью выполнения эффекта разрешают программисту провести дополнительные настройки. В частности, тройка перечис­ ленных компонентов способна управлять позицией центра эффекта, TransitionEffect

property

Center:

TPointF;


262

Гпава 17

ШЛИШИ

Щ Transition effects - переход между та$рвжянтыи Опорное изоъра>'сии€

Целевое изображение Эффекты

Ф TSsndeoSwirfTrarisitioncffect 0 TfadeTrar>s<t!on£ffetf

Ф ■JCirc^eTrarsrtioriEffect

ф

T?v4agr..fyTfansitronF ffeet

Progress=38,9 таймер s o

Рис. 17.11. Пример эффекта, создаваемого компонентом T M a g n i f y T r a n s i t i o n E f f e c t

Для T C i r c l e T r a n s i t i o n E f f e c t И T M a g n i f y T r a n s i t i o n E f f e c t ЭТС> центр ::руга, И З кото­ рого проявляется второе изображение (рис. 17.11), а Д Л Я T B a n d e d S w i r l T r a n s i t i o n E f f e c t это центр пульсации. Кроме того, компонент расплывчивости p ro p e rty

FuzzyAmount:

TCircleTransitionEffect

Single;

//от

0 до

1,

умеет определять коэффициент

п о у м о л ч а н и ю 0.1

и размер круга p ro p e rty

S ize:

Single;

//по умолчанию

1

Компонент T B a n d e d S w i r i T r a n s i t i o n E f f e e t обладает дополнительной парой свойств, управляющих силой скручивания p rd p e rty

Strength:

Single;

и частотой скручивания в спираль property

Frequency:

Single;


ГЛАВА 1 8

Анимация Библиотека FireMonkey позиционируется не только как средство разработки кроссплатформенных приложений, но и как инструмент построения изысканного поль­ зовательского интерфейса. Особо отрадно, что для создания привлекательного ин­ терфейса от вас, как от программиста, потребуется минимум затрат. В какой-то степени будет корректнее говорить не столько о программировании, сколько о ди­ зайне. Еще один плюс FireMonkey в том, что если вы обладаете достаточно высо­ ким художественным вкусом, то внешний вид и привлекательность ваших проектов окажется вне всякой конкуренции. А это очень важно для победы над конкурентом на рынке программного обеспечения, ведь, как гласит поговорка, встречают по одежке... Как сделать интерфейс наглядным и интуитивно понятным? Существует множест­ во рекомендаций по размещению и группировке элементов управления, по заданию очередности передачи фокуса ввода, по использованию пояснительных надписей, по подбору цветовых схем и по многому другому. Давайте рассмотрим еще одно направление в разработке пользовательского интерфейса— анимированные эле­ менты управления. За м еча ни е

В FireMonkey под анимацией понимается управляемое воздействие на элементы пользовательского интерфейса, приводящее к постепенному изменению их местопо­ ложения, размеров и визуальных характеристик во времени.

Простой пример анимации Механизм анимации настолько прост, что в минимальной нотации не потребует от нас ни одной строки кода. Создайте новый проект FireMonkey и разместите на форме круг c i r c l e l :T C i r c l e (страница Shapes палитры компонентов). Теперь обра­ титесь к странице A nim ations палитры компонентов и перенесите на форму компо­ ненты: □

F i o a t A n i m a t i o n i :T F i o a t A n i m a t i o n , этот компонент позволяет воздействовать на описываемые вещественными числами свойства элемента управления;


264 □

Гпава 18 C o i o r A n i m a t i o n i :T C o i o r A n i m a t i o n ,

этот компонент предназначен для управления

цветом компонента. Подключите компоненты-аниматоры к кругу c i r c i e i . Для этого следует восполь­ зоваться услугами окна Structure, отображающего структуру нашего проекта (рис. 18.1). Убедившись, что отвечающие за анимацию компоненты попали в нуж­ ные "руки", перейдите к Инспектору объектов.

Рис. 18.1.

Подключение объектов анимации к фигуре T C i r c l e

Выберите в Инспекторе объектов компонент F i o a t A n i m a t i o n i . Научим его управ­ лять горизонтальным размером круга c i r c i e i . Для этих целей, вооружившись мышью, осуществим 4 операции: 1. В свойстве P r o p e r t y N a m e находим анимируемое свойство горизонтальный масштаб компонента.

S c a l e . х,

отвечающее за

2. Настраиваем свойства, задающие диапазон изменяемых значений масштаба Sta r t V a l u e = l И StopValue=2.

3. Выбираем событие-триггер, выступающее инициатором анимации. Установив в свойство T r i g g e r в состояние I s M o u s e O v e r = t r u e , мы укажем, что анимация на­ чинается в момент появления над компонентом указателя мыши. 4. Выбираем

в

свойстве T r i g g e r i n v e r s e событие, завершающее I s M o u s e O v e r = f a i s е (указатель мыши покинул анимируемый объект).

анимацию:

На этом настройка анимации для компонента F i o a t A n i m a t i o n i завершена. Теперь выберите компонент CoiorAnimationi и в Инспекторе объектов настройте его пове-


265

Анимация

ObjectInspector

O bject Inspector

TTF

PjatAnenatftral IFtaattrtsnafisn

2i

Propertiesj Events!

J Praperies j Events I AfsmaftjriType AutoReverse Delay ' jraSon Enabled IntsrpoSaBon Inverse -iveanctngs Designer Loop feae Proper tyName SOrtfroraCurrent StarWakie StopVabe StyieName Tag Trigger Triggerlnverse

C ohrA m m aH ont TCehrkrima&n

AnenatiooType AutoReverse

atln

fapabe №0 ISf 0,2 IB f * WJnear £TP*e

Way

iHFafc* Scale.*

IB Fats* й 1 Щг IsMouseOv er~ true

IIsHeuseQver=fatee

Duration Enabled Interpolation Inverse UveBindings Designer :loop » Г М >v oper tyName StartfromCurrent Starttalue StopVSIue Styiefiame Tag fngget Triggerlnverse

iSndVisiAjy

BMrtatV,,

$$фтт

Mshown

iettn lEfFaftse If::| о

0,2

|OP*e (Шпеаг iUveSmdingsDesigner r' Fafee FiHCotor

Я Fates i j О VeBow i! : Ц Firebrick :IsMoi»eOver=true iIsMoaseOver=faise

Рис. 18.2. Настройка свойств компонентов F l o a t A n i m a t i o n l и C o l o r f t n i m a t i o n l

дение в соответствии с рис. 18.2. Анимированный проект готов к старту. Согласи­ тесь, что программисты Embarcadero потрудились на славу, оставив на нашу долю лишь визуальную сторону проектирования.

Общие черты компонентов-аниматоров, класс TAnimation Все компоненты-аниматоры (которые вы обнаружите на странице Animations па­ литры компонентов Delphi) берут начало от класса f m x .T y p e s .T A n i m a t i o n . Соответ­ ственно большинство свойств и методов наследники "впитывают" от своего предка. Рассмотрим наиболее важные из них. Длительность анимации определяется значением свойства property

Duration:

Single;

//единица измерения - секунды

При необходимости перед началом анимации можно выждать небольшую паузу. Время задержки устанавливается в свойстве property

Delay:

Single;

//по умолчанию

0 секунд

Закон, в соответствии с которым анимируемый объект станет переходить из одного состояния в другое, определяется свойством


266

Гпава 18

property I n t e r p o l a t i o n : type T I n t e r p o l a t i o n T y p e

TInterpolationType; =

(itLinear,

itQuintic,

itSinusoidal,

itElastic,

itBack,

itQuadratic,

itExponential,

itCubic,

itQuartic,

itCircular,

itBounce);

По умолчанию установлен обычный линейный переход i t L i n e a r , приводящий к последовательной смене состояний (например, цвета или размера). Вместе с тем тип данных T I n t e r p o l a t i o n T y p e предоставляет такой большой выбор допустимых вариантов поведения, что законом интерполяции наверняка стоит поэксперименти­ ровать. На математический аспект интерполяции оказывает влияние вид анимации: property A n i m a t i o n T y p e : T f t n i m a t i o n T y p e ; / / п о у м о л ч а н и ю type T A n i m a t i o n T y p e = (atln, a t O u t , a t l n O u t ) ;

atln

Сторонникам бесконечного цикла смены размеров (местоположения, цвета и т. п.) стоит установить в t r u e свойство property

Loop:

Boolean;

//по умолчанию

false

Управление анимацией обычно осуществляется за счет свойств-триггеров property property

Trigger:

TTrigger;

Triggerlnverse:

//старт

TTrigger;

анимации

//завершение анимации

указывающих, какое именно событие и при каких условиях послужит поводом для начала и завершения анимации. В качестве примера таких событий можно привести получение и утрату элементом фокуса ввода ( i s F o c u s e d = t r u e и i s F o c u s e d = f a i s e ) или появление над объектом указателя мыши ( i s M o u s e O v e r = t r u e ) и уход указателя (lsMouseOver=false).

Если процесс анимации в данный момент активен, то свойство property

Running:

Boolean;

возвратит значение

//только для чтения

true.

При необходимости старт и остановка анимации осуществляются из кода: procedure procedure procedure

Start;

//старт

анимации

Stop;

//остановка анимации

StopAtCurrent;

//остановка анимации

в текущем состоянии

Временно приостановить процесс позволит свойство property

Pause:

Boolean;

Как и положено полноценным объектам, все компоненты-аниматоры способны генерировать события. Старт анимации сопровождается вызовом события property

OnProcess:

TNotifyEvent;

О завершении анимации уведомляет событие property

OnFinish:

TNotifyEvent;

Полученных знаний вполне достаточно для того, чтобы попробовать анимировать ОбъеКТ Не В период визуальной разработки, а во время выполнения программы. Во


Анимация

267

фрагменте кода, предложенном в листинге 18. 1, динамически создается и подклю­ чается К панели P a n e l l экземпляр класса T F l o a t A n i m a t i o n . | Л ;т:.: 1г 13.1.

озд л:е

ни аг;.и во вреюя а»№олнения программы

with T F l o a t A n i m a t i o n . C r e a t e ( P a n e l l ) do begin P a r e n t := P a n e l l ; P r o p e r t y N a m e : = ' K o t a t i o n A n g l e '; StartValue:=0; StopValue:=180; T r i g g e r : = ' I s M o u s e O v e r = t r u e '; T r i g g e r l n v e r s e : = ' I s M o u s e O v e r = f a l s e ';

end;

Обратите внимание на то, что для подключения к анимируемому объекту нам по­ требовалось передать ссылку на этот объект в свойство Parent. Свойство property

Inverse:

позволяет

Boolean;

//по у м о л ч а н и ю

инвертировать анимацию В обратном порядке).

false

(например,

заставить

сменяться

кадры

TBitmapAnimation

Индивидуальные особенности компонентов-аниматоров На странице A nim ations палитры компонентов Delphi расположилось 9 компонен­ тов, предоставляющих программисту разноплановые услуги по анимации объектов FMX. Каждый из компонентов обладает индивидуальными чертами, определяю­ щими область применения того или иного потомка класса T A n i m a t i o n . После подключения к анимируемому объекту (во время выполнения программы для этого достаточно воспользоваться свойством Parent, а во время визуального проектирования просто перетащить мышью узел компонента-аниматора в подчи­ нение обслуживаемого объекта (см. рис. 18.1)) следует выбрать управляемое свой­ ство. Для этого предназначено свойство property

PropertyName:

AnsiString;

Надо понимать, что далеко не все свойства могут быть анимированы. Так, если вы пытаетесь управлять заливкой объекта с помощью компонента T C o l o r A n i m a t i o n , то в P r o p e r t y N a m e могут быть переданы только названия свойств, работающих с цве­ том КИСТИ ( F i l l И Stroke). Зам ечание

Во время визуального проектирования Инспектор объектов подскажет программисту, какие именно свойства могут быть задействованы в анимации.


268

Гпава 18

Для управления правилами анимации у большинства компонентов-аниматоров имеется пара свойств: s ta rtv a iu e и stopvaiue, однако тип обслуживаемых значений у каждого из аниматоров индивидуальный, зависящий от стоящих перед компонен­ том задач.

Цветовая анимация, компонент TColorAnimation Компонент T C o l o r A n i m a t i o n позволяет управлять цветовыми характеристиками кис­ ти заливки (свойство F i l l ) или кисти вывода графических примитивов (свойство stroke). Анимируются два цвета, значения которых указываются в свойствах: property property

StartValue: StopValue:

TAlphaColor; TAlphaColor;

Градиентная анимация, компонент TGradientAnimation Благодаря

градиентной анимации, реализуемой с помощью компонента T G r a d i e n t A n i m a t i o n , можно значительно улучшить внешний вид объектов, способ­ ных управлять своими цветовыми характеристиками с помощью свойств F i l l и Stroke.

Параметры анимации назначаются при посредничестве свойств: property property

StartValue: StopValue:

TGradient; TGradient;

Но теперь вместо данных о цвете (как это было в зать градиентные настройки T G r a d i e n t .

TColorAnimation)

мы должны ука­

Анимированная картинка, компонент TBitmapAnimation Компонент T B i t m a p A n i m a t i o n может работать совместно с объектами, способными отображать растровые картинки (обладающие свойством B i t m a p ) . Эффект анима­ ции достигается путем смены пары картинок, хранимых в свойствах property property

StartValue: StopValue:

TBitmap; TBitmap;

Анимированный ряд, компонент TBitmapListAnimation Возможности компонента T B i t m a p A n i m a t i o n меркнут по сравнению с его "коллегой" T B i t m a p L i s t A n i m a t i o n , ведь последний умеет управлять не парой, а целым списком картинок. Так что при остром желании T B i t m a p L i s t A n i m a t i o n способен показать пользователю короткометражный мультфильм. К кадрам анимации не предъявля­ ется особых требований за исключением одного — все они должны быть одинако­ вого размера и склеены друг за другим в одну ленту. Лента с картинками передается в свойство p ro p e rty

AnimationBitmap:

TBitmap;


Анимация

269

Для того чтобы компонент-аниматор смог корректно воспроизводить наш "шедевр" мультипликации, следует уведомить его о количестве кадров в ленте: property AnimationCount: Integer;

Анимация числовых свойств, компонент TFloatAnimation Компонент TFloatAnimation предназначен для управления свойствами компонентов, обслуживающих числовые значения вещественного типа. Среди потенциальных клиентов компонента-аниматора свойства, отвечающие за определение местополо­ жения элемента управления, размеры элемента управления, угол поворота, мас­ штаб, пррзрачность и т. п. Стартовое и конечное значения анимации заносятся в традиционные свойства prop arty StartValue: Single; property StopValue: Single;

Анимация прямоугольной области, компонент TRectAnimation Задача компонента TRectAnimation заключается в воздействии на значения свойств Margins или Padding элемента управления (напомню, что названные свойства опре­ деляют величины отступов краев дочернего объекта от границ клиентской области родительского контейнера). В результате мы получаем эффект изменения размеров анимируемого объекта относительно его контейнера-владельца. Вновь нашими основными помощниками станут свойства property StartValue: TBounds; property StopValue: TBounds; НО на ЭТОТ раз ОНИ типизируются структурой TBounds.

Анимация траектории, компонент TPathAnimation Проявив немного терпения можно заставить объект двигаться по заданной траекто­ рии. Для анимации этого весьма занятного действа стоит воспользоваться услугами компонента TPathAnimation. Траектория движения задается с помощью свойства property Path: TPathData;

Свойство предоставляет доступ к классу T P a t h D a t a , предоставляющему услуги по определению контрольных точек траектории. Стоит отметить существование еще одного свойства: property Rotate: Boolean; //по умолчанию false

которое в состоянии true заставит вращаться анимируемый объект вокруг своей Проиллюстрируем работу с анимированной траекторией на небольшом примере. Для этого нам понадобится помощь фигуры circiei:TCircie, к которой следует ПОДКЛЮЧИТЬ компонент PathAnimationl:TPathAnimation.


270

Гпава 18

Настройте компонент

PathAnimationi

следующим образом:

□ триггер T r i g g e r активации анимации переведите в состояние (анимация включена, если объект виден); □

п р о д о л ж и т е л ь н о с т ь а н и м а ц и и Duration=6;

а к т и в и р у е м а в т о р е в е р с AutoReverse=true;

□ выберем

isvisibie=true

с и н у с о и д а л ь н у ю и н т е р п о л я ц и ю interppoiation=itsinusoidai.

Точки траектории заполним в коде программы, для этого подойдет событие созда­ ния формы (листинг 18.2). В качестве контрольных точек мы выберем четыре угла родительского контейнера, которому принадлежит окружность c i r c i e i : T C i r c i e . j Листинг 18.2. Настройка параметров траектории with PathAnimationi do begin 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 )); Path.MoveTo(PointF(TControl(Circlel.Parent).Width Circlel.Width,0 )); Path.MoveTo(PointF(TControl(Circlel.Parent).Width-Circlel.Width, T C o n t r o l ( C i r c l e l .P a r e n t ) .H e i g h t - C i r c l e l . H e i g h t ) ); P a t h . M o v e T o ( P o i n t F ( ( T C o n t r o l ( C i r c l e l .P a r e n t ) . W i d t h

- C i r c l e l . W i d t h ) /2,

T C o n t r o l ( C i r c l e l .P a r e n t ) . H e i g h t - C i r c l e l . H e i g h t ) ) ; P a t h . M o v e T o ( P o i n t F (0,T C o n t r o l ( C i r c l e l .P a r e n t ) . H e i g h t - C i r c l e l . H e i g h t ) ); P a t h . M o v e T o (P o i n t F (0,0));

Path.ClosePath; Start; end;

После старта приложения окружность начнет свое движение вдоль границ контей­ нера.


ГЛАВА

19

Мультимедиа Пользователи компьютера далеко не всегда набирают деловые письма или готовят сложные бухгалтерские отчетности. Вместо этого довольно много времени человек проводит за компьютером, не нажимая клавиши, а просто откинувшись на спинку кресла и включив любимую мелодию или фильм. В этом ему помогают многочис­ ленные мультимедийные проигрыватели, написанные специалистами своего дела. Язык Delphi предлагает вам присоединиться к гонке по разработке приложений с функцией поддержки мультимедиа, в этом вам помогут очень удобные и простые в обращении компоненты и классы. Реализуемую в рамках f m x .Media работу с мультимедиа можно разделить на два направления. Первое из них нацелено на воспроизведение аудио- и видеофайлов, второе посвящено захвату аудио- и видеопотоков, поступающих от аппаратных устройств компьютера или их программных эмуляторов.

Воспроизведение мультимедиа С целью воспроизведения мультимедийных данных создателями FireMonkey разра­ ботана пара визуальных элементов управления — TMediaPlayer И TMediaPlayerControl. Их вы обнаружите на вкладке Additional палитры компонентов Delphi. Компо­ нент TMediaPlayer обеспечивает доступ к файлам с данными мультимедиа, а TMediaPlayerControl отвечает за вывод видеопотока на экран. Кроме того, в составе FMX имеется класс TMediaCodecManager, осуществляющий общее руководство над декодерами мультимедиа.

Менеджер кодеков TMediaCodecManager Менеджер кодеков предназначен для управления имеющимися в системе кодеками мультимедиа. По умолчанию при работе под управлением операционной системы Windows менеджер кодеков поддерживает аудиоформаты WMA, MP3, WAV и ви­ деоформаты AVI и WMV. Если приложение предназначено для OS X, то вы полу­ чите доступ к звуковому кодеку MP3 и видеокодекам и MOV, M4V, и MP4.


Гпава 19

272

Для того чтобы избежать возникновения исключительной ситуации при попытке воспроизведений неподдерживаемого формата файла, следует проверить наличие соответствующего кодека. Данная задача решается при посредничестве метода class function IsCodedExists(const AFileName: string): Boolean;

В единственный параметр метода передается имя файла или только расширение имени (например, ".mp3"), если файл может быть воспроизведен— метод возвра­ тит true. Внимание!

Для своей работы компоненты TMediaPlayer и TMediaPlayerControl используют имеющиеся в вашей операционной системе кодеки. Поэтому качество воспроизведе­ ния мультимедиа в первую очередь определяется предустановленным программным обеспечением.

Теоретически никто не запрещает нам попытаться воспроизвести и неподдержи­ ваемые по умолчанию форматы, но в подобном случае следует попробовать зареги­ стрировать новый кодек. Такая задача по плечу методу class procedure RegisterMediaCodecClass(const Extension, Description: string; MediaType: TMediaType; MediaCodecClass: TCustomMediaCodecClass);

Однако перед обращением к методу вы должны быть уверены, что соответствую­ щее программное обеспечение развернуто на компьютере пользователя. Класс TMediaCodecManager окажется весьма полезным в той ситуации, когда вы предполагаете предоставлять доступ к зарегистрированным файлам мультимедиа с помощью диалога открытия файлов В таком случае метод класса class function GetFilterString: string;

возвратит строку, отформатированную в соответствии с требованиями свойства Filter диалога TOpenDiaiog. Нам останется только инициализировать названное свойство компонента во время запуска приложения. Если вам достаточно лишь узнать типы поддерживаемых файлов, то стоит восполь­ зоваться более простым методом class function GetFileTypes: string;

Метод возвратит текстовую строку с допустимыми расширениями имен файлов, разделенными точкой с запятой. При желании сведения о приемлемых расширениях имен файлов можно запраши­ вать отдельно для каждого из типа кодеков TMediaType = (Audio, Video);

для этого предназначены методы class function GetFilterStringByType(MediaType: TMediaType): string; class function GetFileTypesByType(MediaType: TMediaType): string;


273

i мультимедиа

Проигрыватель TMediaPlayer и компонент TMediaPlayerControl Для построения приложения, способного воспроизводить аудио- и видеофайлы, следует разместить на форме проигрыватель TMediaPlayer и его помощник — ком­ понент TMediaPlayerControl, специализирующегося исключительно на отображении видеорада (рис 19 1).

Рис. 19.1. Проигрыватель на основе компонентов TMediaPlayer и TMediaPlayerControl

Работа мультимедийного приложения начинается с отправки имени подлежащего восприизведеккю файла в свойство property Filename

string;

проигрывателе TMediaPlayer Если в системе предустановлен кодек, способный ра­ ботать с содержащимися з файле данными, то в этом случае в недрах компонента создаете г экземпляр :<л»сса TMedia, представляющий собой объектно-ориенти­ рованное воплощение мультимедийных данных, загруженных из файла. Доступ к экземпляру TMedia обеспечивает свойство property Media: TMedia; //только для чтения

Получив экземпляр класса TMedia, мы приобретаем возможность управлять воспро­ изведением мультимедийных данных. В простейшем случае программисту доста­ точно знать о существовании методов


274

Глава 19

procedure Play; procedure Stop;

соответственно начинающих и останавливающих воспроизведение. Вызов пере­ численных методов сразу отражается в свойстве property State: TMediaState; //только для чтения

TMediaState = (Unavailable, Playing, Stopped);

отражающем текущее состояние проигрывателя TMediaPlayer. Кроме старта и остановки воспроизведения TMediaPlayer предоставляет возмож­ ность изменять громкость звуковой дорожки property Volume: Single; //диапазон от 0 до 1

Кроме того, совсем несложно отследить (а при необходимости и изменить) пози­ цию воспроизведения, для этого предназначено свойство эгор ',rty CurrentTime: TMediaTime;

Для того чтобы убедиться, что не достигнуто ли окончание мультимедийного фай­ ла, следует проверять состояние свойства prop rty Duration: TMediaTime;

хранящего общую продолжительность файла. Зам ечание

Свойства CurrentTime и Duration ориентированы на тип данных TTimeSpan, пред­ ставляющий время как интервал, в котором один такт соответствует 100 наносекундам

Если воспроизводится видеоряд, то обратившись к свойству property VideoSize: TPointF; //только для чтения

мы сможем уточнить размер отображаемой картинки. Когда работа с мультимедийным объектом завершается, то следует обратиться к методу procedure Clear;

освобождающему задействованные ресурсы и удаляющему экземпляр TMedia. Второй ингредиент приложения мультимедиа— компонент TMediaPlayerControl просто отвечает за вывод видеоизображения на экран, для этого он подключается к проигрывателю при посредничестве свойства property MediaPlayer : TMediaPlayer;

на этом настройки компонента заканчиваются — он готов отображать загруженное в медиаплеер видео.


Мультимедиа

275

Захват аудио- и видеопотока Приложения FireMonkey способны не только воспроизводить мультимедийные файлы, но и перехватывать аудио- и видеопотоки, формируемые с помощью имеющихся в распоряжении компьютера устройств мультимедиа (видеокамер и микрофонов). Для обслуживания подобных аппаратных устройств в FireMonkey был создан ряд классов, наиболее важные из них: □

TCaptureDeviceManager отвечает за доступ к аппаратным устройствам;

TAudioCaptureDevice предоставляет в распоряжение базовый функционал теку­

щего аудиоустройства; □

TvideoCaptureDevi.ee позволяет управлять текущим видеоустройством.

Кроме того, в составе библиотеки предусмотрен очень простой в использовании компонент-камера TCameraComponent, нацеленный на мобильные приложения.

Менеджер устройств TCaptureDeviceManager Класс TCaptureDeviceManager напелен на сбор сведений об установленных в системе устройствах мультимедиа (поток данных которых может быть захвачен и задейст­ вован в интересах приложения) и предоставлении доступа к этим устройствам из программ FireMonkey. Для того чтобы воспользоваться услугами менеджера, не стоит вызывать его кон­ структор, вместо этого гораздо проще обратиться к свойству класса property Current: TCaptureDeviceManager;

и получить доступ к менеджеру текущих аудио- и видеоустройств. Число доступных мультимедийных устройств уточняется благодаря свойству property Count integer;

Зная количество устройств, мы сможем обратиться к любому из них по индексу property Devices[Index: Integer]: TCaptureDevice;

и в качестве ответа получить доступ к устройству и его описание в формате класса TCaptureDevice. Такая идея реализована в листинге 19.1, в коде которого мы собра­ ли полный набор текущих мультимедийных устройств, установленных в компью­ тере, И передали его В СПИСОК ListBoxl:TListBoxl. . . .

.........................................................

.................................................,

.

Листинг 19.1. Сбор сведении о мультимедийных устройства};

.

J

шяшшшшяяяяшшшшшш/шшшшшшявшшшшяяшяшшяшшяяшшяшшшяшшшшяяшшшяяшшшяшяяяяяшяшяшшшшшяяяяшшшш var CDM : TCaptureDeviceManager;

CD : TCaptureDevice; LBI : TListBoxItem; i : integer; begin

CDM:=TCaptureDeviceManager.Current;


276

Глава 19

for i := 0 to CDM.Count-1 do begin

CD:=CDM.Devices[i] ; LBI:=TListBoxItem.Create(ListBoxl); ListBoxl.AddObject(LBI); end; end;

Еще один способ захвата текущего аудио- и видеоустройства обеспечивают свойства property DefaultAudioCaptureDevice: TAudioCaptureDevice; //аудио property DefaultVideoCaptureDevice: TVideoCaptureDevice; //видео

Если устройства отсутствуют, то свойства возвратят неопределенные указатели иначе в ваше распоряжение соответственно поступят экземпляры классов TAudioCaptureDevice И TVideoCaptureDevice, способные передавать В приложение мультимедийные данные (листинг 19.2).

null,

Листинг 19.2. Захват щр CDM AC VC

var

о-и

TCaptureDeviceManager; TAudioCaptureDevice; TVideoCaptureDevice;

begin

CDM AC VC

= TCaptureDeviceManager.Current; = CDM.DefaultAudioCaptureDevice; = CDM.DefaultVideoCaptureDevice;

if Assigned(AC) and Assigned(VC) then begin

//работаем с устройствами end; end;

Для программистов, желающих получить доступ к строго определенному устрой­ ству, подходит метод function GetDevicesByName(Name: string): TCaptureDevice;

В единственный параметр метода следует передать название аудио- или видеоуст­ ройства.

Захват потоков мультимедиа В FireMonkey специалистами по захвату аудио- и видеопотоков считаются классы TAudioCaptureDevice И TVideoCaptureDevice. Оба класса построены на фундаменте абстрактного класса TCaptureDevice и поэтому вооружены идентичным базовым набором свойств и методов (табл. 19.1).


277

Мультимедиа

Таблица 19.1. Свойства и методы, унаследованные от класса

TCaptureDevice

Свойство/метод

Описание

property MediaType : TMediaType;

Тип устройства (Audio или Video)

property Name : String;

Название устройства

property UniquelD : String;

Уникальный идентификатор устройства

property Description : String;

Дополнительное описание устройства

property State : TCaptureDeviceState;

Текущее состояние (Capturing или Stopped)

property IsDefault : boolean;

В состоянии true сигнализирует, что это устройство по умолчанию

Procedure StartCapture;

Начать захват потока мультимедиа

Procedure StopCapture;

Завершить захват потока мультимедиа

Ауд иозахват ТА udioCaptureDevice Наиболее важные профессиональные черты классов аудио- и видеозахвата реализо­ ваны самостоятельно. Так, у устройства захвата аудиопотока (кроме унаследован­ ных от родительского класса TCaptureDevice свойств и методов) имеются два клю­ чевых свойства. Первое из них property FilterString : String;

содержит перечень поддерживаемых расширений имен файлов (*.mp3; *.wav; *.wma). Второе свойство property FileName : String;

определяет путь и имя к файлу, в который будет отправлен аудиопоток. Установив имя файла, нам остается лишь вызвать метод startcapture (), и файл начнет запол­ няться данными.

Видеозахват TVideoCaptureDevice В качестве основного устройства видеозахвата в большинстве случаев рассматри­ вается видеокамера. Если мы работаем со смартфоном или планшетным компьюте­ ром, оснащенным дополнительной фронтальной камерой, то перед тем как сделать первый снимок проверьте, что выбрано нужное устройство. Для этого предназна­ чено свойство property Position: TDevicePosition;

TDevicePosition = (dpUnspecified, dpFront, dpBack);

Если камера оснащена вспышкой, то свойство property HasFlash: Boolean;


278

Гпава 19

возвратит значение true. Убедившись в наличии вспышки, можно установить предпочтительный режим ее функционирования property FlashMode: TFlashMode;

Предусмотрены три варианта настроек: автоматический (fmAutoFiash), вспышка отключена (fmFiashOff) или вспышка постоянно включена (fmFiashOn). Наличие дополнительного освещения (предназначенного для устранения эффекта красных глаз при портретной съемке) проверяется свойством property HasTorch: Boolean;

При положительном результате программист может установить режим этого осве­ щения property TorchMode: TTorchMode;

Допустимых вариантов три: отключено (tmModeOff), включено (tmModeOn), автома­ тическое включение (tmModeAuto). Способ фокусировки камеры определит свойство property FocusMode: TFocusMode;

TFocusMode = (fmAutoFocus, fmContinuousAutoFocus, fmLocked);

Качество фото- и видеоматериала, возвращаемого камерой, также подлежит на­ стройке, для этого предназначено свойство property Quality: TVideoCaptureQuality;

TVideoCaptureQuality = (vcPhotoQuality, vcHighQuality, vcMediumQuality, vcLowQuality); С

В отличие от устройства аудиозахвата, выдающего непрерывный поток данных, видеозахват осуществляется с некоторой периодичностью — покадрово. В тот мо­ мент времени как устройство видеозахвата (например, веб-камера) сформирует очередной фотоснимок, у нашего программного объекта TVideoCaptureDevice гене­ рируется событие property OnSampleBufferReady : TSampleBufferReadyEvent;

типизированное следующим образом TSampleBufferReadyEvent = procedure (Sender: TObject; const ATime: TMediaTime) of object;

Первый параметр события sender содержит ссылку на камеру, а второй ATime — номер такта времени. Для того чтобы мы смогли перехватить полученный камерой снимок, в коде обра­ ботки события OnSampleBufferReady () следует вызвать еще один метод устройства видеозахвата procedure SampleBufferToBitmap(const ABitmap: TBitmap; const ASetSize: Boolean);

Снимок возвращается методом через параметр ABitmap, во второй параметр следует заносить значение true (это заставит метод самостоятельно настроить размеры снимка).


Мультимедиа

279

Практическая сторона решения задачи получения фотоснимка с помощью устрой­ ства видеозахвата выглядит следующим образом. Сначала в заголовочном файле с описанием главной формы проекта следует объявить прототип метода, совмести­ мого с T S a m p l e B u f f e r R e a d y E v e n t . В нашем примере этот метод станет называться V i d e o B u f f e r R e a d y () (ЛИСТИНГ 19.3). Листинг 19.3. Метод VideoB ufferR eady О

■,

procedure TForml.V i d e o B u f f e r R e a d y ( S e n d e r : v a r A B i t m a p :TBitmap; VCD

TObject;

const

ATime:

int64);

: TVideoCaptureDevice;

begin A B i t m a p := T B i t m a p .C r e a t e (0,0); VCD:=Sender

as

TVideoCaptureDevice;

VCD.SampleBufferToBitmap(ABitmap,true); {дальнейшая обработка полученных растровых картинок

при желании можно

сохранить

файлы с фотографиями на диск

}

A B i t m a p . S a v e T o F i l e ('с : W p h o t o ' + I n t T o S t r ( A T i m e ) + ' b m p ' , n i l ) ;

end;

В

рамках

представленного кода мы создаем объект растровой графики T B i t m a p * A B i t m a p и передаем в него снимок с видеокамеры. Дальнейшие действия зависят от стоящих перед приложением задач, в простейшем случае снимки можно просто сохранять на диске компьютера.

Для того чтобы устройство видеозахвата смогло самостоятельно вызывать разрабо­ танный нами метод, следует подключить его в качестве обработчика события O n S a m p l e B u f f e r R e a d y () . Для этого достаточно добавить всего одну строку кода в рассмотренный ранее листинг 19.4. \

Листинг 19.4. Связь события OnSampleBufferReady ( ) с методом

var

CDM

: TCaptureDeviceManager;

VC

: TVideoCaptureDevice;

begin CDM

:= T C a p t u r e D e v i c e M a n a g e r . C u r r e n t ;

VC

if

:= C D M . D e f a u l t V i d e o C a p t u r e D e v i c e ;

(V C O n il) then VC.OnSampleBufferReady:=VideoBufferReady;

end;

Теперь, для получения видеопотока от камеры нам остается вызвать метод S t a r t c a p t u r e () устройства.


280

Глава 19

Камера TCameraComponent Компонент-камера T C a m e r a C o m p o n e n t описан в модуле f m x .Me d i a и предназначен для использования в мобильных приложениях. Компонент очень прост в обращении, и зачастую используется совместно с компонентом T A c t i o n L i s t (см. гл аву 7), в кото­ ром вы найдете несколько заранее заготовленных команд для управления камерой. Стоит отметить, что компонент позволяет выбрать фронтальную или основную камеру мобильного устройства property K i n d : T C a m e r a K i n d ; TCameraKind =

(ckDefault,

ckFrontCamera,

ckBackCamera);

Включение камеры осуществляется свойством property A c t i v e : B o o l e a n ;

После осуществления снимка генерируется событие property O n S a m p l e B u f f e r R e a d y : T S a m p l e B u f f e r R e a d y E v e n t ; TSampleBufferReadyEvent =

procedure (S e n d e r : T O b j e c t ; const A T i m e : T M e d i a T i m e )

of object;

В этот момент и следует извлечь снимок из буфера камеры, для этого предназначен метод procedure S a m p l e B u f f e r T o B i t m a p (const A B i t m a p : T B i t m a p ; const A S e t S i z e : B o o l e a n ) ; Замечание У кам еры TCameraComponent TVideoCaptureDevice TorchMode.

вы о б н а р уж и те уж е зна ко м ы е по кл а ссу ви д е о за хва та

свойства

Quality,

HasFlash,

FlashMode,

FocusMode

и


ГЛАВА

20

Сенсорный ввод Если еще три десятилетия назад персональная ЭВМ для большинства из нас была невиданной диковинкой, к которой боялись даже прикоснуться, то сегодня перед компьютером не спасует даже ребенок. Почему так произошло? Неужели нынеш­ ние дети умнее детей, появившихся на свет четверть века назад? Надеюсь, что да. Но скорее всего отсутствие боязни перед электронной техникой объясняется про­ ще — современные компьютеры очень дружелюбны. В первую очередь дружелюбие компьютера проявляется через развитый пользова­ тельский интерфейс. И с каждым десятилетием этот интерфейс приобретает новое качество Судите сами. В середине XX века основным способом ввода данных бы­ ли перфокарты и перфоленты, полагаю, что об удобстве здесь можно даже не гово­ рить Позднее появились клавиатуры, еще позднее— манипуляторы-мыши. Пер­ вые монохромные устройства отображения на электронно-лучевых трубках посте­ пенно сменились цветными мониторами, а сегодня они уже почти полностью вытеснены жидкокристаллическими дисплеями. Но прогресс не стоит на месте. Пользователи проявляют все возрастающий интерес к интеллектуальным устройст­ вам, обладающим сенсорными функциями ввода данных. Это в первую очередь многочисленные платежные терминалы, планшетные компьютеры, электронные доски и смартфоны Как правило, перечисленные устройства не снабжены отдель­ ной клавиатурой или же возможности их клавиатуры сильно ограничены. Но это не беда, программисты научили эти устройства воспринимать естественный ввод или, говоря проще, жесты человека. ЗАМЕЧмНИЕ Под

жестом ( g e s tu r e ) м ы с т а н е м п о н и м а т ь п р о и з в о л ь н у ю г е о м е т р и ч е с к у ю ф и г у р у , н а ­

р и с о в а н н у ю п о л ь зо в а те л е м на у с т р о й с тв е с е н с о р н о го в в о д а с п о м о щ ь ю э л е ктр о н н о го пера или просто пальцем .

Описание жеста Большинство построенных на базе класса T C o n t r o i элементов управления Delphi способно реагировать на некоторый набор заранее предопределенных жестов. Жест


282

Гпава 20

представляет собой некую геометрическую фигуру, хранимую в памяти в формате структуры T S t a n d a r d G e s t u r e D a t a (листинг 20.1), определение которой вы обнаружи­ те В модуле F M X . G e s t u r e s . ;Зк:« Т И Н Г : i typ e

Состав полей ЗЧПИСИ rrSc indazdG '^tur-D: ^

TStandardGestureData Points:

record

TGesturePointArray;

GesturelD: Options:

=

Щ

TGesturelD;

TGestureOptions;

//массив

т о ч е к TPoint,

//идентификатор жеста //опции

описывающих жест

,


Сенсорный ввод Deviation:

283 Integer;

ErrorMargin:

Integer;

//допустимое

отклонение жеста

от

стандартного

//максимальное число ошибок

end;

Ключевое поле структуры — Poin t s . Это динамический массив, хранящий коорди­ наты точек геометрической фигуры жеста. Второе поле содержит идентификатор жеста. В Delphi имеется более трех десятков предустановленных жестов (рис. 20.1). При анализе жеста учитывается не только его соответствие геометрической фигуре, но и ряд других характеристик. В частности, поле опций O p t i o n s контролирует на­ правление g o D i r e c t i o n a l , наклон g o S k e w и факт совпадения начальной и конечной точек g o E n d p o i n t .

Реакция на сенсорный ввод В библиотеке FireMonkey реакция на сенсорный ввод обеспечивается на уровне класса T F m x O b j e c t . Для того чтобы элемент управления приобрел способность реа­ гировать на предопределенные жесты, ему понадобится помощ ник— менеджер жестов, компонент T G e s t u r e M a n a g e r . Для подключения менеджера жестов к элемен­ ту управления необходимо сделать несколько шагов. Сначала следует обратиться к свойству p ro p e rty

Touch:

TTouchManager;

Свойство предоставляет доступ к инкапсулированному в элемент управления объ­ е к ту — менеджеру прикосновений (экземпляру класса T T o u c h M a n a g e r ) . В свою оче­ редь у менеджера прикосновений имеется свойство p ro p e rty

GestureManager:

TGestureManager;

позволяющее ассоциировать с элементом управления интересующий нас менеджер жестов T G e s t u r e M a n a g e r . Процесс подключения компонента T G e s t u r e M a n a g e r к форме проекта отражает эк­ ранный снимок Инспектора объектов (рис. 20.2). Обратите внимание на то, что воспользовавшись разделом T ouch | G estures | S tandard, программист определяет, на какие именно жесты должен реагировать элемент управления. Для этого доста­ точно поставить "галочку" рядом с соответствующим изображением. Для описания реакции на жест программисту следует воспользоваться событием потомков класса f m x .Ty p e s . T C o n t r o i : p ro p e rty O n G e s t u r e : T G e s t u r e E v e n t ; typ e T G e s t u r e E v e n t = procedure (Sender: T O b j e c t ; const E v e n t l n f o : T G e s t u r e E v e n t l n f o ; v a r H a n d l e d :

Boolean)

of

object;

Событие генерируется в тот момент, когда пользователь (воспользовавшись сен­ сорным экраном, электронным пером или просто мышью) нарисует над поверх­ ностью элемента управления какую-то геометрическую фигуру. Основная задача обработчика события заключается в двух вещах:


284

Гпава 20

□ провести анализ введенного жеста и найти ему наиболее точное соответствие среди жестов, имеющихся в распоряжении элемента управления (свойство Touch);

□ выполнить соответствующую жесту операцию. Object inspector F o rm l

Vrormi

'.i i

liwti

“TopMost

SlTouth

jOFaise |(TTouchManager)

SjgestaeM anage :GestureManagerl

jrteWaw

J Ta» SiGestures »SiSfe4 ' -d

QntureMsnagerl

jleft iRjght jup jDown

W-

jupLeft jupRight jOonaiLeft

i

Downfix^n

> • ■

Н 12; 42

:o

W !

!F I

ГГ r j TL

1

Insert S te a d re!

Айshown Р и с . 20.2. Присоединение м е н е д ж е р а T G e s t u r e M a n a g e r к ф о р м е

Ключевой параметр события — E v e n t i n f o , именно он уведомляет нас о том, какая фигура была нарисована пользователем. Параметр представляет собой запись T G e s t u r e E v e n t i n f o , объявление которой вы найдете в листинге 20.2. Установив по­ следний параметр события H a n d l e d в состояние true, мы уведомим систему, что жест в обработке более не нуждается. Листинг 20.2. Состав полей записи TGestureEventinfo type T G e s t u r e E v e n t i n f o = record GesturelD: Location:

TGesturelD; TPointF;

Flags: TInteractiveGestureFlags; Angle:

Double;

InertiaVector: Distance:

TPointF;

Integer;

TapLocation:

TPointF;

Назначение полей структуры

TGestureEventinfo

предложено в табл. 20.1.


Сенсорный ввод

285

Таблица 20.1. Описание полей записи TGestureEventlnfo Поле записи

Описание

GesturelD

Идентификатор жеста

Location

Координаты текущей точки на поверхности устройства ввода

Flags

Набор флагов (gfBegin, gfInertia, gfEnd), доступных только в момент ввода жеста

Angle

Угол движения электронного пера (курсора мыши, пальца пользователя) относительно координатных осей устройства ввода

InertiaVector

Пара значений х и Y, благодаря которым можно идентифицировать на­ правление движения электронного пера. Положительное значение х сви­ детельствует о движении пера к правой границе, отрицательное — к левой. Положительное значение Y говорит о том, что перо опускается вниз экрана, отрицательное — поднимается вверх

Distance

Расстояние в пикселах глежду текущей (Location) и предыдущей точками

TapLocation

Местоположение начальной точки фигуры жеста

Зам ечание

Стоит заметить, что значения идентификаторов стандартных жестов имеют положи­ тельные значения (от 1 и далее), значения идентификаторов пользовательских жестов всегда отрицательные (от-1 и далее).

Интерактивные жесты Кроме стандартных жестов, предложенных разработчиками FireMonkey, приложе­ ние способно реагировать и на многоточечные касания (multi-touch). Для этой цели стоит в Инспекторе объектов развернуть свойство Touch и изучить вкладку interactiveGestures Среда проектирования позволит нам подключить 5 дополни­ тельных жестов (табл. 20.2) Таблица 20.2. Интерактивные жесты fmx. Types.TlnteractiveGesture Точек касания

Win

OS X

iOS и Android

Увеличение

2

+

+

+

igPan

Прокрутка

1

+

+

+

igRotate

Вращение элементов пользо­ вательского интерфейса

2

+

+

+

2

+

+

+

+

Значение

Описание

igZoom

igTwoFingerTap

'Касание двумя пальцами

igPressAndTap

Касание и нажатие

2

idDoubleTap

Двойное касание

2

idLongTap

Долгое нажатие

+ +


286

Гпава 20

Пример обработки стандартных жестов Специалисты Embarcadero приложили все усилия для того, чтобы программист чувствовал себя максимально комфортно при использовании в проекте механизма сенсорного ввода. Подтверждение тому — простота создания приложения, способ­ ного общаться с пользователем с помощью жестов. Для нашего примера понадобятся новый проект и компонент TGestureManager, ко­ торый Следует ПОДКЛЮЧИТЬ К форме Forml С П О М О Щ Ь Ю СВОЙСТВ Touch — GestureManager. Отметьте те жесты, на которые должна реагировать форма (см. рис. 20.2). Разместите на форме метку Labeii, этот компонент проинформирует нас о жесте пользователя. Собственно обработка жеста будет осуществлена з рамках события onGestureO единственной формы проекта (листинг 20 3).

procedure TForml.FormGesture(Sender: TObject; const Eventlnfo: TGestureEventlnfo; var Handled: Boolean); begin i f Eventlnfo.GestureID>-l then Labell.Text:=IntToStr(Eventlnfo.GesturelD) e ls e Labell.Text:= '?' end;

Теперь, нарисовав одну из стандартных геометрических фигур в клиентской облас­ ти формы, вы моментально узнаете ее идентификационный номер Результаты работы приложения, способного распознавать стандартные жесты, представлены на рис. 20 3. В данном случае форма распознала состоящий из двух петель стандартный жест с номером 26

Рис. 20.3. Экранный снимок формы распознавания жестов


ГЛАВА

21

InterBase ToGo Давным-давно, в середине 90-х годов прошлого века при появлении на свет самой первой версии Delphi (в те времена еще компании Borland) о новой среде проекти­ рования в первую очередь говорили как о превосходной платформе для разработки настольных и клиент-серверных приложений для баз данных (БД). Спустя почти 20 лет Delphi яе только не утратила своих позиций в этом сегменте компьютерного рынка, но и существенно нарастила свои возможности. Судите сами, Delphi ХЕ5 позиционируется как среда проецирования, поддерживающая самые распростра­ ненные и успешные системы управления базами данных (СУБД), такие как Oracle, Informix, Microsoft SQL Server, DB2, Sybase, MySQL, Firebird, PostgreSQL и конечг но же собственное детище — InterBase Server. Разработка приложений БД в Delphi — весьма интересная и многогранная тема; достойная отдельной книги. К сожаленью, сейчас азтор вынужден ограничить­ ся всего одной главой, знакомящей читателя с настольной базой данных InterBase ToGo Излагая материал, будем исходить из предположения, что читатель хорошо знаком с процессом разработки БД, работал с сервером InterBase и имеет хотя бы начальное представление о проектировании приложений с помощью ком­ понентов dbExpress; Внимание!

Для работы с базами данных InterBase в Delphi предусмотрены специализированные компоненты InterBase Express Если в вашей поставке Delphi ХЕ4 они отсутствуют, то обратитесь к ссылке http://cc.umbaroftdero corn/item/29408, перейдя по которой вы смокете скачать архив с этими компонентами.

Механизм управления данными InterBase ToGo предназначен для создания про­ стейших однопользовательских БД, для функционирования которых совсем не тре­ буется программное обеспечение сервера InterBase, в простейшем случае окажется достаточным воспользоваться единственной библиотеке»!. Это кроссплатформенная система, способна': работать под управлением Windows, OS X, Linux и Solaris и не требующая никакого администрирования!


Гпава 21

288 Зам ечание

В Delphi ХЕ4/ХЕ5 механизм InterBase ToGo представлен двумя платформами: полнофункциональной ToGo и упрощенной IBLite (предназначенной для встраивания в мо­ бильные приложения iOS и Android). Упрощения сводятся к ограничению у IBLite раз­ мера БД (до 100 Мбайт), отсутствию механизма шифрования, невозможностью под­ держки сервисов API. Во всем остальном и первая, и вторая платформы основаны на идентичной модели разработки, поэтому с точки зрения программиста отличия между ними незначительны.

Соединение с БД TSQLConnection Наличие в проекте БД компонента TSQLConnection — это обязательное условие для организации взаимодействия между клиентским приложением и базой данных. Компонент отвечает за установку соединения между приложением и БД, регистра­ цию пользователя на БД и управление транзакциями. Зам ечание

Для создания схемы и объектов базы данных InterBase ToGo проще всего воспользо­ ваться консолью управления СУБД IBConsole, которую вы найдете в папке c:\Program Files (x86)\Embarcadero\RAD StudioV? n\lnterBaseXE3\bin\. Благодаря интуи­ тивно понятному интерфейсу утилита окажется полезной даже для начинающего про­ граммиста.

Работа с компонентом начинается с выбора драйвера, для этого предназначено свойство property DriverName: string;

Драйвер определит, какие именно библиотеки должны использоваться для обслу­ живания специфичной СУБД и для клиентской стороны, в нашем случае (рис. 21.1) мы воспользуемся драйвером IBLite/ToGo. Подбором драйверов и библиотек этап предварительной настройки соединения за­ вершается, и мы переходим к этапу описания нюансов обслуживания конкретной базы данных. Для этого нам следует обратиться к свойству property Params: TStrings;

В результате среда программирования вызовет редактор соединения, позволяющий назначить его параметры и протестировать их корректность. Список параметров зависит от конкретной реализации сервера, но в самом общем случае нам прихо­ дится определиться с именем хоста HostName, на котором развернут сервер, именем базы данных Database, именем UserName И паролем пользователя Password.

Управление соединением Разобравшись с базовыми свойствами компонента, обсудим его главное назначе­ ние — процесс установки соединения. Компонент TSQLConnection обладает парой методов, осуществляющих подключение к БД и отключение от БД procedure Open;. procedure Close;


InterBase ToGo

289 .JataModuleUn tp a s

I main: ЦоаОМкМеипК: Object Inspector

SQLConnectionl TSQLConnection

"SI

jp ro p a ie s] Events! __ _ Connected (O Faise Connect!»#

W:

ШЯе/ToGo'

; ;Perfwmerjsdect;

Dj!v f-jese

:\m itsk .gdb

Password m a s te r k e y ServerChj UserName sy sd b a

PROCJWFORHERJNSBIT :

Delegated

ШDetegateC KeepComvx / True U v eB M n g ^ ljv eS M n g s Designer

mxPBUFcmmjJKimt::

loadParama Q False LogriProfnplf.,: False

PftOC^PERFORMER„DELETE ::

Name

jSQLConoectxwl

Parana

i(TSinngs)

ТаЫеЗсоре i[tsTabfc,tsView]

|o

Tag

► •

Щ

61: 18

Insert

>jv- connection ре д и Ь л Rebx3 connection p ra uteters !<MConoecflonStmg Param

Driver

— г

———

~

A3 shown

Рис. 21.1. Подключение драйвера к компоненту TSQLConnection

Тот же результат достигается за счет обращения к свойству property Connected: Boolean;

Кроме того, по состоянию Connected можно судить о факте соединения с сервером. Простейший пример подключения к файлу БД MUSIC.GDB, расположенному в том же каталоге, что и исполняемый ехе-файл, представлен в листинге 21.1. В ключах параметров Params указываются все тонкие настройки сервера и регистрационные данные пользователя. ..тгл'”

..

1истинг 21.1. Настрейкэ осмоннмх параметров совдинзм *" 1.......л:.й'. :*Ж £Ш Ж Ш Л ..кЖйШшШВ^sSL.'..'.4.I.W..’... ".л ‘ var FileName:string; begin

FileName:= GetCurrentDir+TPath.DirectorySeparatorChar+'MUSIC.GDB'; if TFile.Exists(FileName) then begin

SQLConnectionl.LoginPrompt:=false; SQLConnectionl.Params.Values['HostName'] :='localhost'; SQLConnectionl.Params.Values['DataBase'] :=FileName;


290

Глава 21

SQLConnectionl.Connected:=true; end «Is* raise

Exception.Create('Файл '+TPath.GetFileName(FileName)+' не найден!');

При желании настроечные данные можно редактировать и во время проектирова­ ния. Для этого предназначен редактор соединения, вызываемый щелчком по свой­ ству Params компонента в Инспекторе объектов. С процессом установки и разрыва соединения связан классический (для всех ком­ понентов Delphi, отвечающих за подключение к БД) перечень событий (табл. 21.1). Таблица 21.1. События, связанные с соединением и разрывом соединения Событие

Описание

property BeforeConnect: TNotifyEvent;

Генерируется перед установкой соединения

property AfterConnect: TNotifyEvent;

Генерируется после установки соединения

property BeforeDisconnect: TNotifyEvent;

Генерируется перед разрывом соединения

property AfterDisconnect: TNotifyEvent;

Генерируется после разрыва соединения

Если программная логика приложения требует постоянного удержания контакта с БД, даже в случае, если нет ни одного активного открытого набора данных, то проследите, чтобы свойство KeepConnection оставалось в состоянии true. Иначе с закрытием последнего набора данных соединение будет разорвано.

Регистрация пользователя Если для работы пользователя с БД необходимо осуществить ввод имени и пароля, то свойство property LoginPrompt: Boolean; //по умолчанию true

должно оставаться в состоянии true (настройка по умолчанию), в этом случае бу­ дет автоматически отображен диалог регистрации пользователя. Если необходимо­ сти регистрации нет — переведите свойство в false. З ам ечание По умолчанию административный доступ к InterBase ToGo осуществляется под учет­ ной записью "sysdba" с паролем "masterkey".

Кроме того, для передачи в адрес СУБД имени и пароля пользователя (а также основных параметров соединения) можно задействовать событие property OnLogin: TSQLConnectionLoginEvent; type TSQLConnectionLoginEvent = procedure (Database: TSQLConnection; LoginParams: TStrings) of object;


InterBase ToGo

291

З амечание В череде событий компонента TSQLConnection событие OnLogin () генерируется сра­ зу после BeforeConnect (). Событие будет вызвано только при условии, что свойство LoginPrompt установлено в состояние true.

О текущем состоянии соединения можно судить по свойству property ConnectionState: TConnectionState;

Возможные варианты состояний вы найдете в табл. 21.2. Таблица 21.2. Возможные значения T C o n n e c t i o n S t a t e Состояние

Описание

csStateClosed

Соединение отсутствует

csStateOpen

Компонент соединен с БД

csStateConnecting

Процесс соединения инициирован, но еще не завершен

csStateExecuting

Компонент отправил в адрес СУБД инструкцию SQL, и она выпол­ няется

csStateFetching

Компонент получает информацию от сервера

csStateDisconnecting

Момент разрыва соединения, но оно еще не завершено

Управление подчиненными наборами данных Компонент TSQLConnection способен оказывать влияние на подключенные к нему компоненты-наборы данных. Весь массив этих элементов управления хранится в СВОЙСТВеproperty DataSets[Index: Integer]: TCustomSQLDataSet;

Количество использующих соединение компонентов мы выясним благодаря свой­ ству property DataSetCount: Integer;

Для закрытия всех открытых наборов данных достаточно вызвать процедуру. procedure CloseDataSets;

Управление транзакциями Прежде чем попытаться управлять транзакциями, стоит убедиться, что целевая СУБД их поддерживает. Для этого предназначено свойство property TransactionsSupported: LongBool;

Значение true свидетельствует о том, что в один и тот же момент времени сервер способен выполнять по крайней мере одну транзакцию. Если же одновременно мо­ гут быть запущены несколько транзакций, то об этом просигнализирует свойство property MultipleTransactionsSupported: LongBool;


292

Гпава 21

Для старта новой транзакции обратитесь к процедуре procedure StartTransaction(TransDesc: TTransactionDesc);

Завершение транзакции с сохранением результатов работы обеспечит процедура procedure Commit(TransDesc: TTransactionDesc);

Откат транзакции procedure Rollback(TransDesc: TTransactionDesc);

В качестве параметра во все три метода передается одна и та же запись TTransactionDesc. Эта структура позволяет идентифицировать транзакцию и опре­ делять ее основные характеристики. type TTransIscflationLevel = (xilDIRTYREAD, xilREADCOMMITTED,

xilREPEATABLEREAD, xilCUSTOM); TTransactionDesc = packed record TransactionID GloballD IsolationLevel Customlsolation

: : : :

LongWord; //уникальный идентификатор транзакции LongWord; //глобальный идентификатор для ORACLE TTransIsolationLevel;//уровень изоляции LongWord; //пользовательский уровень изоляции

Поля с идентификаторами транзакций заполняются системой сразу после старта транзакции, поэтому на долю программиста остается только настройка уровня изо­ ляции. Во время выполнения транзакции свойство property InTransaction: Boolean;

переходит в состояние true.

Выполнение SQL-инструкций При острой необходимости компонент TSQLConnection вполне способен самостоя­ тельно отправить в адрес сервера команду SQL. В простейшем случае (когда команда не содержит параметров) воспользуемся методом function ExecuteDirect(const SQL: string): Integer;

При корректном выполнении метода он возвратит нулевое значение. В противном случае функция передаст нам код ошибки. Если же SQL-команда включает пара­ метры и, более того, возвращает какое-то результирующее множество, то выбираем одну из перегружаемых версий метода: function Execute(const SQL: string, Params: TParams): Integer; overload,function Execute(const SQL: string, Paratas: TParams, var ResultSet: Object): Integer; overload;


293

InterBase ToGo

Информирование о БД Ряд методов компонента специализируется на получении информации об имею­ щихся в распоряжении базы данных объектах (таблицах, процедурах, индексах и т. п.). Перечень этих процедур представлен в табл. 21.3. Таблица 21.3. Информационные методы компонента TSQLConnection Метод

Описание

procedure GetTabieNames(List: TStrings; SystemTables: Boolean = False);

Метод предоставит список имеющихся в БД таблиц. Перечень таблиц будет передан в параметр List. Если список должен содержать информацию о сис­ темных таблицах, то установите в true второй параметр процедуры

procedure GetFieldNames(const TableName: S trin g ; List: TStrings);

Процедура построит список полей таб­ лицы TableName и направит его в па­ раметр List

procedure GetlndexNames(const TableName: string; List: TStrings);

Список индексов таблицы TableName передается в параметр List

procedure GetProcedureNames(List: TStrings); overload;

Метод построит список хранимых про­ цедур

procedure GetProcedureParams( ProcedureName: string; List: TProcParamList); overload;

Получение информации о параметрах хранимой процедуры

procedure GetProcedureParams( ProcedureName: string; PackageName: string; List: TProcParamList); overload;

Получение информации о параметрах хранимой процедуры Oracle

procedure GetProcedureParams ( ProcedureName: string; PackageName: string; SchemaName: string; List: TProcParamList); overload;

Совместно с методом GetTabieNames () трудится свойство p ro p e rty TableScope: TTableScopes; //по умолчанию tsTable и tsView type TableScope = (tsSynonym, tsSysTable, {системные таблицы} tsTable, {обычные таблицы} tsView); {представления}

оно определяет тип таблиц, который может увидеть компонент.

Набор данных TSQLDataSet Компонент TSQLDataSet фактически объединяет в себе функциональные возможно­ сти триумвирата компонентов — таблицы, запроса, хранимой процедуры. Благода­ ря такой самодостаточности компонента TSQLDataSet необходимость в использо­ вании В наших проектах TSQLTable, TSQLQuery И TSQLStoredProc При построении любого проекта dbExpress можно поставить под сомнение.


294

Гпава 21

В нимание! Все наборы данных dbExpress — это однонаправленные наборы данных. Это означа­ ет, что компоненты не кэшируют записи в локальном буфере. Поэтому перебор запи­ сей в результирующем наборе может осуществляться только вперед, от первой к по­ следней.

Работа с компонентом начинается с подключения его к БД (точнее говоря, к ком­ поненту TSQLConnection) с помощью свойства p ro p e rty SQLConnection: TSQLConnection;

Наличие свойства p ro p e rty CommandType: TSQLCommandType; TPSCommandType = (ctUnknown, ctQuery, ctTable, ctStoredProc, ctServerMethod, ctSelect, ctlnsert, ctUpdate, ct^Delete, ctDDL) ;

позволяет нам применять TSQLDataSet в роли таблицы, запроса или хранимой про­ цедуры. Например, установив свойство CommandType в состояние ctQuery, мы превращаем компонент в запрос, работающий на основе SQL. В этом случае щелчок по свой­ ству p ro p e rty CommandText: s trin g ;

в Инспекторе объектов вызывает редактор запросов (свойство commandType=ctQuery), который окажет помощь при проектировании простейших инструкций. Возможно­ сти редактора далеко не безграничны, но, по крайней мере, он позволит начинаю­ щему разработчику не перепутать названия таблиц и полей (рис. 21.2). ......рвх:: : :: Ш ' ■-

©

SQLConnection

.i iai

..

Com m andText Editor

SonnectSon:

Table Scape:

О tsSynonym

:

т

Performer .select

Si

sysdba

О tsSysTable tsTable Й tsView

Tables:

SO-:

Schema Name:

j o et Database Objects

Select * from performer order by performer PROC „PERFORMER.

m ss

Add Table to SQL

PROC .PERFORMERJJPO /

Bdd?L PROC_PERFORMER.

ОЕ'.Г

CONCERT CONCERTJ D CONCERTOATE PERFORMERJ D A ddH eidtoSQ i

Hdp Рис. 21.2. Встроенный редактор SQL


InterBase ToGo

295

Немного разовьем идею использования TSQLDataSet в качестве запроса, возвра­ щающего строки таблицы. В качестве основы возьмем таблицу performer из БД, предназначенной для хранения данных "Исполнитель — альбом" (рис. 21.3). З амечание При изучении процесса создания приложения БД вместо этого примера можно вос­ пользоваться демонстрационной базой данных dbdemos.gdb, которую вы найдете в папке C:\Users\Public\Documents\RAD Studio\11.0\Samples\Data\. psrformer РК

performer id

concert PK

concert id

FK1

concert concertdate p e rfo rm e rjd

performer

Рис. 21.3. Концептуальная модель БД "Исполнитель — альбом"

Присоедините набор данных Performer_seiect: TSQLDataSet к базе данных (для это­ го понадобится свойство SQLConnection). Воспользовавшись свойством commandText, передайте в компонент текст запроса: Select * from performer order by performer. Убедитесь, что свойство commandType установлено в состояние ctQuery. Разместите на форме компонент ibxPerformers:TListBox, именно в него мы направим результа­ ты запроса. Все остальное вы найдете в листинге 21.2. Ж v a r LBI:TListBoxItern; begin lbxPerformers.Clear; //очистка списка w ith Performer_select do begin Open; //открываем запрос w h ile n o t eof do //пока не прочитаны все строки begin //создаем элемент списка с данными LBI:=TListBoxItem.Create(lbxPerformers); LBI.Text:= FieldByName('performer').AsString; //исполнитель LBI,Tag:= FieldByName('performer_id').Aslnteger; //ключ //---- Пока не снимайте комментарий с этих строк! ---// LBI.OnClick:=PeformersItemClick; //---см. листинг 21.5 ---lbxPerformers.AddObject(LBI);//добавим элемент в список Next; //переход к очередной строке end; Close; //закрываем запрос end; end;


296

Гпава 21

Обратите внимание на то, что кроме названия исполнителя в динамически созда­ ваемый элемент списка заносится значение первичного ключа записи, для этого мы приспособили свойство Tag. Значение ключа понадобится для идентификации запи­ си при описании процедур редактирования и удаления, а также для сбора сведений об альбомах исполнителя (см. листинг 21.5). Если команда SQL основана на инструкции s e l e c t , т о д л я выполнения запроса используют унаследованный от далекого предка TDataSet метод орепо. Во всех остальных случаях задействуем метод fu n c tio n ExecSQL(ExecDirect: Boolean = False): Integer;

Передав в параметр ExecDirect значение true, мы просигнализируем СУБД, что наша команда не нуждается в предварительной подготовке. Большинство событий компонента TSQLDataSet получено в наследство (правда, в весьма ограниченном виде) от класса TDataSet. Компонент способен реагировать на следующие события: подключение к таблице BeforeOpen() и AfterOpeno; отключение от таблицы BeforeClose () и Afterclose (); перемещение по запи­ сям BeforeScroll о И AfterScroll (); обновление данных BeforeRef resh () И Af terRef resh (); работа С вычисляемым полем OnCalcFields (). Кроме того, у компонента TSQLDataSet имеются четыре собственных обработчика событий. Все они предназначены для осуществления анализа отправляемых на вы­ полнение инструкций SQL. Выполнение команды i n s e r t возможно отследить с по­ мощью события: p ro p e rty ParselnsertSql: TParselnsertSqlEvent; TParselnsertSqlEvent = procedure(v& r FieldNames: TWideStrings; SQL: UnicodeString; v a r BindAllFields: Boolean; v a r TableName: UnicodeString) o f object;

Здесь: FieldNames — имена полей, задействованных при выполнении инструкции; s q l — текст инструкции; BindAllFields — признак, надо ли задействовать все поля; TableName — ИМЯ таблицы. Команды SELECT, UPDATE И DELETE обсл уЖ И В аЮ Т С Я

СОбыТИЯМИ

p ro p e rty ParseSelectSql: TParseSqlEvent; p ro p e rty ParseUpdateSql: TParseSqlEvent; p ro p e rty ParseDeleteSql: TParseSqlEvent; TParseSqlEvent = p ro ced u re(var FieldNames: TWideStrings; SQL: UnicodeString; v a r TableName: UnicodeString) of object;

Хранимая процедура TSQLStoredProc Хранимая процедура TSQLStoredProc специализируется на вызове заранее подготов­ ленных на стороне сервера процедур. При подготовке к работе компонент следует соединить с базой данных, поэтому в процессе создания проекта самым первым шагом программиста должно быть подключение к TSQLConnection с помощью свой­ ства SQLConnection.


297

InterBase ToGo

Имя хранимой процедуры передается в свойство: prop »rty StoredProcName: s trin g ; Если свойство ParamCheck компонента находится в состоянии true, то при измене­ нии имени процедуры компонент самостоятельно обращается к базе данных и со­ бирает сведения о параметрах процедуры и заносит их в свойство Params. Для принудительной предварительной подготовки процедуры к выполнению на стороне сервера можно вызвать функцию: function PrepareStatement( vex RecordsAffected: Integer): TCustomSQLDataSet;

Впрочем, вместо этого метода разработчики Delphi рекомендуют обращаться к свойству Prepared. В этом случае гарантируется, что сервер не станет без всякой на то надобности перекомпилировать уже давно готовую к работе процедуру. Входные параметры процедуры передаются в свойство property Params: TParams;

Это же свойство служит хранилищем для возвращаемых в результате выполнения процедуры параметров. Если процедура не основана на инструкции s e l e c t (не возвращает результирующе­ го набора данных или возвращает результаты в виде выходных параметров), то для ее вызова обращаемся к методу function ExecProc: Integer;

в противном случае надо использовать метод open () либо установить в true свойст­ во Active. Вернемся к нашему примеру "Исполнитель — альбом". Для вставки новой записи с исполнителем в БД создадим хранимую процедуру proc performer insert с един­ ственным входным параметром aPerformer и одним возвращаемым значением new id — первичным ключом только что вставленной записи (листинг 21.3). ■м

яJ _ w

.■

_

; Листинг 21.3. Хранимая процедура вставки записи на языке SQL

create procedure proc_performer_insert(aPerformer varchar(50)) returns (new_id int) begin

insert into performer (performer) values (:aPerformer); select max(performer_id) from performer into :new_id;

Для того чтобы обратиться к этой процедуре из приложения FireMonkey, можно вновь воспользоваться услугами TSQLDataSet или отдать свои предпочтения спе­


298

Гпава 21

циализированному компоненту TSQLStoredProc. Для того чтобы разнообразить наш код, пойдем по второму пути. Перенесите на модуль данных компонент TSQLStoredProc, соедините его с базой данных С П О М О Щ Ь Ю свойства SQLConnection И Передайте В СВОЙСТВО StoredProcName название хранимой процедуры (в нашем случае p r o c p e r f o r m e r i n s e r t ). Убедитесь в корректности подключения, если все сделано верно, то после щелчка по свойству Ра rams перед вами появится окно с параметрами хранимой процедуры.

Рис. 21.4. Подготовка компонента TSQLStoredProc к работе

Порядок использования компонента TSQLStoredProc в проектах FireMonkey раскры­ вает листинг 21.4, в нем мы вносим нового исполнителя в БД и создаем соответст­ вующий ему элемент В списке lbxPerforrners: TListBox. Лиоткнг 21.4. Вста:::;п »:о гой записи в таблицу parfarmar var id:integer;

LBI:TListBoxItem; s :string; begin if InputQuery('Новый исполнитель','Исполнитель',s) then begin

PROC_PERFORMER_INSERT.ParamByName('aPerformer').Value:=s.Trim; PROC PERFORMER INSERT.ExecProc;


InterBase ToGo

299

id:=PROC_PERFORMER_INSERT.ParamByName('new_id').Value; LBI:=TListBoxItem.Create(lbxPerformers); LBI.Text:=s.Trim; LBI.Tag:=id; lbxPerformers.AddObject(LBI); end; end;

В соответствии с классическими традициями наборов данных Delphi доступ к пе­ речню параметров обеспечивает свойство property Params: TParams;

Для того чтобы компонент самостоятельно регенерировал список параметров (име­ на, типы данных и т. д.) при вводе или любом изменении SQL команды в поле CommandText, убедитесь, чтобы свойство property ParamCheck: Boolean;

было установлено в состояние true. В нимание! При работе с хранимыми процедурами старайтесь объявлять параметры в той же по­ следовательности, в какой они описаны на серверной стороне.

Запрос TSQLQuery Компонент-запрос специализируется на отправке серверу инструкции на языке SQL. Организация работы с компонентом не сильно отличается от работы с одно­ типными компонентами из других технологий доступа к данным. После подключе­ ния к БД с помощью свойства SQLConnection следует определить текст запроса: property SQL: TStrings;

Запрос может редактироваться как во время проектирования БД, так и во время вы­ полнения программы. Если запрос содержит параметры, то доступ к ним осуществ­ ляется с помощью свойства property Params:TParams;

Для того чтобы параметры компонента самостоятельно подстраивались под все из­ менения в тексте запроса, убедитесь, что свойство property ParamCheck;

установлено в true. Если запрос работает с инструкциями i n s e r t , u p d a t e и d e l e t e ,т о для отправки SQLзапроса к серверу применяют метод ExecSQLO, запрос, нацеленный на выборку данных (инструкция s e l e c t ), активируется методом open (). Вновь возвратимся к нашему примеру "Исполнитель— альбом" и научим наше приложение отображать список альбомов после выбора пользователем элемента


300

Гпава 21

с названием исполнителя (щелчок по элементу списка lbxPerformers). Для этого разместите на модуле данных компонент TSQLQuery (не забыв подключить его к БД), перенесите на главную форму проекта еще один список TListBox и переиме­ нуйте его в lbxConcerts. Так как элементы списка lbxPerformers создаются динами­ чески в момент сбора сведений об исполнителях, нам придется подключать обра­ ботчик события OnCiick () вручную. Для этого в секции частных объявлений с главной формой проекта объявим процедуру procedure PeformersItemClick(Sender: TObject);

Код процедуры вы найдете в листинге 21.5. ; Листинг 21.5. Событие-щелчок по элементу списка lb x P e rfo m a rs procedure TfrmMain.PeformersItemClick(Sender: TObject); var Performer_id :integer; ConcertLBI:TListBoxItem; lb:TLabel; begin

Performer_id:=TListBoxItem(Sender).Tag; //узнаем первичный ключ if Performer_id>0 then with SQLQueryl do begin

SQL.Clear; //готовим новый текст запроса SQL.Add('SELECT * FROM CONCERT WHERE'); SQL.Add('PERFORMERID=' + Performer_id.ToString()+' ORDER BY CONCERTDATE'); Open; //выполняем запрос lbxConcerts.Clear; //очищаем список while not eof do //перебор записей, возвращенных запросом begin

ConcertLBI:=TListBoxItem.Create(lbxConcerts); lbxConcerts.AddObject(ConcertLBI); ConcertLBI.Text:= {дата} FormatDateTime('yyyy', FieldByName('concertdate').AsDateTime); ConcertLBI.Tag:=FieldByName('concert_id').Aslnteger; //ключ lb:=TLabel.Create(ConcertLBI); //метка для названия альбома lb.Parent:=ConcertLBI; lb.Position.X:=40; ■ lb.Text:=FieldByName('concert').AsString; Next; //к следующей записи end;


301

InterBase ToGo

Close;

//закрываем набор данных

end;

Для того чтобы код стал работоспособным, следует подключить процедуру к обра­ ботчику события onCiick () элементов списка lbxPerformers. Это нам придется делать в динамическом режиме, поэтому возвратитесь к листингу 25.2 и снимите комментарии со строки //LBI.OnClick:=PeformersItemClick;

Выпуск приложения Если зы имеете хотя бы небольшой опыт разработки клиентских приложений баз данных, работающих под управлением InterBase, то наверняка знаете, что при пе­ реносе исполняемого файла на компьютер заказчика в дистрибутив надо обяза­ тельно включить динамическую библиотеку gds32.dll. В названной библиотеке со­ средоточен весь необходимый функционал для доступа и обработки данных, об­ служиваемых сервером InterBase 3 InterBase ToGo ситуация очень похожая, но на этот раз в папке с исполняемым файлом должна оказаться библиотека ibtogo.dll (или ibtogo64.dll), если речь идет о Windows, библиотека libibtogo.dylib для OS X и мобильной платформы iOS. З амечание Разработчики клиентской библиотеки для проектов InterBase ToGo утверждают, что интерфейсная асть ibtogo.dll (ibtogo64 dll, libibtogo.dylib) на 98% совпадает с интер­ фейсом gds32.dll, что существенно упрощает переносимость настольных и клиентсерверных приложений

Длг включения необходимых файлов в дистрибутив следует воспользоваться м ен едж ером разверт ы ван ия (Deployment Manager) приложения, для этого надо

обратиться к элементу меню Project | Deployment среды проектирования. В комби­ нированном списке менеджера необходимо выбрать целевую платформу или вы­ брать универсальный элемент АН configurations — All Platforms. Затем щелкаем по кнопке Add Featured Files и в появившемся на экране окне с наиболее часто ис­ пользуемыми файлами отметить флажки (рис. 21.5) интересующих нас строк (в нашем случае InterBase ToGo и DBExpress InterBase Driver). Для присоединения файла с базой данных в окне Deployment нажимаем кнопку Add Files, с помощью диалога выбора файлов находим необходимый файл БД и подключаем его к проекту. З амечание Если логика работы программы требует размещения ваших файлов в строго опреде­ ленных папках, то в окне Deployment следует отредактировать ячейку Remote Path. Путь можно указать раздельно для каждой из поддерживаемых платформ, например в iOS файл БД должен оказаться в папке StartUp\Documents\.


30?,

Гпава 21

З * D»toyti»*nt ToGopMusfc { Э D e p lo y m e n t T o G o p M u S c

O t? < & 2 Ш Щ \ % <Ь, *>Ш Ш Loq f В KIBRHMSTOIR)^. В W0REDB7DIR)Vi«:

f Вa S 0Sx;2Pw ug\ В OSX32\Debue\

Я Wt^EDBTDK)V»*:. *TERB3ISTOIR;bcx3. n l(BOS)Wn\ D

Q S0BRHDISTDtR)Vn3.

E ттшоюыпз. П tOBRBDISTDIR)\ow3. 4

\ш я ш ш я ш ш ш ш ш я

, Local Nam e

jQ Inwrt. jKden* - fiB InterBase ToGo $■ Я

Win32

ф В «Ш64 ffl SB 05X32 ffi в

lOSSimutetor

ff! S3 iOSDevKe SB-Ш Android I S DBExjx m s InterBase Ortver -H В

ОВ6фгемОв2 Driver DeExpre!» Freb*d Onver

■m

C8Express MySQL Driver

■4Deptovroer.r ToGopMu»c

В

DBexpres* Or»de Ortver

3

DBExpre*» Infbnrtx Driver

■в

DBExpre» M5SQL Driver

■ H МЕхргем MSSQL9 Driver

Р и с . 2 1 .5. П о д кл ю ч е н и е к п р о е кт у м о д у л е й , н е о б х о д и м ы х д л я р а б о ты In te rB a se T o G o

З амечание Тематика разработки баз данных будет продолжена в главе 22, но на этот раз в кон­ тексте технологии живого связывания LiveBindings.


ГЛАВА

22

LivaBindings Объем принципиально новых возможностей, появившихся в Embarcadero RAD Studio с выходом FireMonkey, без преувеличения можно сравнить с прорывом пер­ вых версий Delphi в середине 1990-х годов. В этой главе нам предстоит обсудить очередную новацию Embarcadero — технологию "живого связывания" LiveBmdings, позволяющую программисту организовать передачу данных в рамках приложения между произвольным источником и объектом-получателем данных. Несмотря на то, что собственно идея живого связывания нашла свое полноценное программное воплощение в FireMonkey, фундамент LiveBindings был заложен до выхода новой платформы — еще в 2008 году. Именно тогда вышло в свет принци­ пиально усовершенствованное ядро RTTI (Run-time type information). Не станем перечислять положительные качества нового RTTI. отметим лишь, что с этого мо­ мента появилась возможность создать универсальную технологию, способную обеспечить взаимодействие между объектами приложения, строго говори, без опо­ ры на конкретную операционную систему. Поэтому технология LiveBindings оди­ наково хорошо функционирует как в классической библиотеке VCL, так и в новой кроссплатформенной FireMonkey. Что такое LiveBindings? Если кратко, то это технология взаимодействия двух и бо­ лее объектов. Связь может быть как односторонней, так и двусторонней. В качестве участника взаимодействия выступают два типа объектов: управляем ы е объект ы (control objects) и источники данны х (source objects). Обмен данными осуществля­ ется между свойствами объектов, а правила обмена определяются с помощью регу­ лярных вы раж ений (expression). Что немаловажно, в выражения разрешается включать ссылки на другие внешние объекты. В нимание! Строго говоря, за связь между двумя объектами LiveBindings отвечает третий объект, например, экземпляр класса TBindExpression.

В LiveBindings предусмотрено более двух десятков классов, специализирующихся на обслуживании определенных типов связей, их можно разделить на 4 современ­ ных и одну устаревшую на сегодня ветвь:


304

Гпава 22

1. Ветвь Quick Bindings содержит группу специализированных классов, позво­ ляющих создавать живые связи практически без программирования. Например, класс T L i n k C o n t r o i T o P r o p e r t y позволяет связать элемент управления со свойст­ вом управляемого объекта так, что любые изменения в источнике данных не­ медленно отразятся в выбранном свойстве управляемого объекта. Ключевое достоинство классов Quick Bindings в том, что они поддерживаются визуаль­ ным дизайнером живых связей (LiveBindiuigc Designer). 2. Ветвь Binding Expressions включает классы . T B m d E x p r e s s i o n и T B i n d E x p r i t e m s , контролирующие процесс взаимодействия между управляемым объектом и ис­ точником данных в соответствии с правилами, описанными в форме регулярных выражений. В простейшем случае связь организуется между двумя свойствами разных объектов. 3. Тип связей Lists объединяет классы T B i n d L i s t и T B i n d G r i d L i s t . Задача указанных классов — обеспечить связь не просто между парой свойств, содержащих по одному атомарному значению, а между списками, хранящими большой объем данных.

Links зхлючаег классы T B i n d L i n k , T B i n d L i s t L i n k , T B i n d G r i d L i n k И Перечисленные классы позволят нам создавать ссылки ме;:сду списками, сетками и другими участниками живой сзязи (обычно полями таб­ лиц). В отличие от классов, входящих в состав DB Lin'is, классы Links требуют от программиста явным образом задать выражения, в которых должны быть оп­ ределены правила взаимодействия между полем и визуальным элементом управления.

4. Тип

СВЯЗИ

TBindPosition.

З амечание В LiveBindings существует и устаревшее направление DB Links. Оно было предназна­ чено для построения клиентских приложений БД в первой версии FireMonkey (Delphi ХЕ2). В состав данного типа связей LiveBindings входят T B i n d D B E d i t L i n k , TBindDBTextLink,

TBindDBListLink,

TBindDBImageLxnk,

TBindDBMemoLink,

и T B m d D B G r i d L i n k . Кахадый из перечисленных классов позволяет формировать связь мехсду полем таблицы соответствующего типа и подходящим для обслуживания этого типа данных элементом управления, например T B i n d D B E d i t L i n k с легкостью соединит текстовое поле и строку ввода TEdit. TBindDBCheckLink

Палитра компонентов F ireMonkey насчитывает несколько компонентов, обеспечи­ вающих функционирование технологии LiveBindings, среди них: □ список связей T B i n d i n g s L i s t представляет собой контейнер для анонсированных выше экземпляров классов, осуществляющих связь LiveBindings; □ наблюдатель T B i n d S c o p e , упрощающий организацию взаимодействия между визуальными элементами управления; □ компонент T B i n d S o u r c e D B используется в проектах баз данных, он обеспечит взаимодействие между набором данных (любым потомком класса TD ataSet^n другими компонентами; □ компонент T B i n d S o u r c e D B X также используется в проектах баз данных, но на этот раз имеет жесткую специализацию — он нацелен исключительно на технологию доступа к базам данных dbExpress (DBX);


305

LiveBindings

□ для д оступанеструктурированны м в виде БД данным задействуется компонент TAdapterBindSource, например указанный компонент способен работать с кол лекциями различны' объектов, □ генератор

TDataGeneratorAdapter

предназначен для

совместной

работы

с

TAdapterBindSource;

□ источник произвольных данных TPrototypeBindSource позволит программисту создать некоторый временный случайный набор данных, используемый для от­ ладки приложения, позднее вместо него можно использовать реальные данные; □ навигатор для проектов баз данных TBindNavigator — единственный визуальный элемент управления LiveBindings, обеспечивающий интерфейс пользователя по управлению табличными данными (перемещение по набору данных, добавление записей, удаление записей и т п..)

Визуальный дизайнер Продемонстрируем возможности визуального дизайнера LiveBmdmgs на примере приложения FireMonkey с интерфейсом Metropolis С этой целью воспользуемся элементом меню New | FireMonkey Metropolis U I Application и в окне выбора ти­ па приложения Delphi New FireMonkey Metropolis U I Application отметим шаб­ лон Grid Metropolis U I Application. В результате этих действий среда проектиро ваш и создаст приложение для Windows 8 с пользовательским интерфейсом Metropolis Указанное приложение будет состоять из двух модулей главной формы GridViewi pas и дочерней формы DataViewl pas. Главная форма обладает списком ListBoxl с некоторым перечнем элементов, щелчок по тому или иному элементу списка послужит командой для вызова формы детализации с более подробной информацией Разместите

на

главной

форме

проекта

тестовый

источник

данных

PrototypeBindSourcel TPrototypeBindSource, который ПОЗВОЛИТ смоделировать не­

который произвольный набор данных. Для того чтобы определить структуру гене­ рируемых данных, следует воспользоваться контекстным меню компонента и обра­ титься к пункту Add Field (Добавить поле) В ответ на зтс действие на экране ком­ пьютера окажется одноименный диалог добавления поля к набору данных Add Field, выберите в списке несколько произвольных полей (рис. 22.1) и нажмите кнопку О К . Вновь созданные поля заполнят коллекцию peoparty FieldDefs: TGeneratorFieldDefs;

и станут имитировать поведение полей из обычного набора данных (например, таб­ лицы базы данных). Сразу ограничим ЧИСЛО записей, создаваемых В компоненте PrototypeBindSourcel, для этого следует передать значение (допустим, число 5) в свойство property RecordCount: integer; //по умолчанию -1

Отметим, что по умолчанию свойство принимает значение равное -1 , и это указы­ вает нз отсутствие ограничений.


306

Гпава 22

Grid Application ,, G roup Title: 1

Рис. 22.1. Имитация набора данных с помощью TPrototypeBindSource

Теперь нам следует вызвать на сцену визуальный дизайнер связей LiveBindings Для этого (предварительно убедившись, что главная форма проекта активна) выбе­ рите пункт меню View | LiveBindings Designer. В результате на вашем дисплее отобразится окно редактора со схематичным представлением компонентов главной формы, способных поддерживать друг с другом живую связь Как вы уже догада­ лись, В качестве источника данных выступит компонент PrototypeBindSourcel, остальные элементы управления станут претендовать на роль получателей данных Продемонстрируем процесс создания живой связи Дл* этого найдите на схеме прототип PrototypeBindSourcel и левой кнопкой мыши выберите в нем поле с име­ нем BitmapNamei. Удерживая кнопку мыши в нажатом состоянии, перетащите поле к списку ListBoxl и отпустите кнопку в тот момент, когда указатель мыши окажется над свойством item. Text. В результате на схеме появится "стрелка", соединяющая выбранное поле и свойство (рис. 22.2). Данна-' "стрелка" визуализирует реальный объект-связь LinkFiiiControiToFieldl, в данном случае созданный на основе класса TLinkFiiiControiToFieid. В этом вы сможете убедиться, взглянув на Инспектор объектов. Обращение к Инспектору объектов являете.': не только праздным любо­ пытством, нам еще следует изменить состояние свойства ListitemStyle, переведя его в режим MetropolisUI. Обратите внимание на то, что после установки типа связи в состояние MetropolisUI у представления ListBoxl на схеме LiveBindings Designer заметно расширился


LiveBindings

307

LiokFWCootrofrof iefctl

UWrtControtTofieM

_ _ Г „ _

Д

cthial

' True

AuloPC Bin» BufferCOur*! -1 Q u id :. n d ln g i

Category

Control List, o x l a .tomForrt O. rtomPars

T Lay! ,

GridVtevrfFofm 'DefaltLayer

С -tsSource С'чсвоп

jwetfri«aortal

tn te n x t

-

SeleO edV alue

AlphaColorl

Synnh

---- -----------»

fiffireakOej

ПЮгмММ (ft

a \< jr o v ;)

ter* Тея

Г- OataSour P rototyp . sdSo u tcel

Sitmapl

Пенс L ookucO m b

HHXsptayGj

B o o ffieM l

4em H eader.T»(*

Bitm spNam el

Color 1

g

ColoraNaniel

r ? ...

■’oader 8>eek

''ШШИЛ

D a te F i» « ! "ч1’

, * ч. t i» i ( iaro el з (П с п -.- t E x p m ik m i )

,

iCTTbrm

nJNeaderCt!

.U x p r a n 'm s )

щЩ expressions... NIMClMrUtt

Al shown Р и с . 22.2, С о з д а н и е св язи « и щ у

перечень свойств Bitmapl —

полум

д а н н ы х и с “ ой ст oit. ко м п о н е н та

Воспользуемся этим и добавим к схеме еще пару связей:

Item. Icon И ColorsNamel — .Item. SubTitle.

Переходим к дочерней форме проекта, В первую очередь (воспользовавшись пунк­ том меню File | Uses) подключим к дочерней форме модуль GridViewl.pas, что по­ зволит дизайнеру связей LiveBindmgs увидеть принадлежащий главной форме про­ екта прототип данных PrototypeBindSourcel После зтого (щелкнув правой кнопкой мыши по клиентской области LiveBindings Designer) вс всплывающем меню выбе­ рем пункт Refresh Designer (Обновить Дизайнер) В результате к схеме (до сих пор отражавшей лишь принадлежащие дочерней форме компоненты) добавится PrototypebindSourcei. Для того чтобы подчеркнуть тот факт, что прототип принад­ лежит другому модулю, LiveBindings Designer охраечт его в оранжевый цвет. Нам осталось связать поля прототипа со свойствами соответствующих компонентов формы детализации так, как предложено на рис 22.3. В завершение нашего примерг зам предстоит немного потренироваться в програм­ мировании. Возвращаемся к модулю глазной формы и находим на ней компонент ListBoxl:TListBox, он нам поможет вызывать форму детализации С этой целью


308

Гпава 22

опишем событие OnChange (), генерируемое компонентом в момент выбора того или иного элемента (листинг 22.1). '

Листинг 22.1. Вызов формы детализации .5

1 1Vs

.. ..... ..'4

.. я н и я в м Р и

procedure TGridViewForm.ListBoxlChange(Sender: TObject); begin i f (ListBoxl.Itemlndex<>-1) then begin PrototypeBindSourcel.Itemlndex:=ListBoxl.Itemlndex; Application.GetDeviceForm('DetailView') Show(); end; end;

Uve6indings Designer DetailVtewForm - Default Layer

4*

▼ Layers

| iUusttbofii A:^hsCok>r1 В й г т р Н ш г т1

I

BSmsp1 SooffteKH С ою И

ffrrnSufrm» Text

CobfsMamel

CufflHteyFtrtll 0et*F*ld1

Рис. 22.3. Создание связи пежду прототипом и компонентами дочерней формы

Проект закончен. Теперь щелчок по элементу списка ListBoxl вызывает форму детализации с соответствующими данными.

UveBindings в проектах баз данных Еще с первых версий среда проектирования и язык Delph предоставляли разработ­ чику ни с чем несравнимые (как по удобству, так и по скорости проектирования) возможности по построению проектов ба? данных Долгое время казалось, что уже достигнут предел совершенства и ничего более успешного в технологии разработки клиентских приложений БД (с точки зрения RAD сред) создать невозможно. Одна­ ко в F eMonkey компания Embarcadero превзошли саму себя, поставив LiveBindings на службу приложений БД Чтобы подтвердить это утверждение, предлагаем написать демонстрационный проект БД в FireMonkey Чтобы не заставлять вас разворачивать на компьютере сложные СУБД, для пробно­ го проекта LiveBindings для БД предлагаю временно отказаться от Мае и написать


LiveBmclings

309

приложение FireMonkey исключительно для Windows. Более того, советую остано­ виться на распространенной настольной БД Microsoft Access и создать в ней одно­ табличную базу данныл контактов (рис. 22.4). Предлагаемый пример — не догма, при желании вы можете выбрать любые другие альтернативные источники данных, чапример -фоссплатформенную базу данных InterBase ToGo (см. гл аву 21), но сей­ час исключительно ради быстроты освоения идеи работы LiveBindings с БД реко­ мендуем ненадолго отклониться от генеральной линии _

_

?

_

■КмяaaWTmaif,;.,Г.ЛЯИпщим Счетчик Текстовый Дата/времр

contacts id pplname oday phone ohoto

Описав*» -

и;

Первичннй ключ ФИО Д ень рождения Номер телефоне

Текстовый Поле объекта OLE Фотография

Р и с 2 2 .4. О п р е д е л е н и е по л ей та б л и ц ы ко н т а кто в c o n ta cts

Создайте приложение FireMonkey HD Application и разместите на главной форме яроекта следующие компоненты (рис 22 5) □ таблиц}’ А Э О Т а Ы е ! T A D O T a b l e , воспользовавшись свойством C o n n e c t i o n S t r i n g , сформируйте строку соединения с файлом БД, а в свойстве T a b l e N a m e укажите имя обслуживаемой таблицы; □

данных B m d S o u r c e D B l .T B i n d S o u r c e D b , обратившись присоедините компонент к таблице д п о т а Ы е т ;

ИСТОЧНИК

К

свойству

Da t a S e t ,

□ навигатор данных B m d N a v i g a t o r i , r B m d N a v i q a t o r , навигатор следует присоеди­ нить к наблюдателю B i n d S o u r c e D B i , для этого предназначено свойство D a t a S c o p e ; □ контейнер связей

B i n d i n g s ! j stl : T B i n d i n g s L i s t ;

П|>с«ап ЬД UveBmdmgs - ; BmdSoufceOB

*<

Ш

<

Оъчмт и инициалы

АОСГэЫе!

<

X

()

Фото

BfaciSourceDSl BmdingsUstl

Шяяшшашшшшшамшашштшштшшшшшашттяштшшшмшшшшянтю Рис. 22.5. Интерфейс приложения БД с компонентом

TBindSourceDB


310

Гпава 22

□ строки ввода

TEdit

(по числу текстовых полей в таблице) и изображение

ImageControll:TImageControl.

З амечание В современных версиях Delphi в распоряжении программиста появился компонент TBindSourceDB. По своей сути TBindSourceDB — это два компонента в одном флако­ не: с одной стороны, TBindSourceDB способен работать с технологией UveBindings, а с другой стороны, это замена источника данных TDataSource, существовавшего с первых версий Delphi.

Установив свойство Active в состояние true, активируйте таблицу ADOTabiei. Вызо­ вите контекстное меню компонента BindSourceDBi и найдите в нем пункт Bind Visually. В появившемся на экране визуальном дизайнере связей создайте связи между полями данных и соответствующими им строками ввода (рис. 22.6). UveBindings Designer

----- ------ —-------------------- -...... . -

Forml - Default Layer

-■ ■ ■ ■ ■T

Layers

---------------■§»

Рис. 22.6. Создание связей между полями данных и элементами управления

"Программирование" заверш ено— смело нажимаем клавишу <F9> и отправтяем проект на компиляцию.

Binding Expressions — связь с помощью выражений Несмотря на то, что визуальный дизайнер LiveBindngs дает возможность даже "по­ трогать руками" саму идею применения живых связей в наших проектах, автор является сторонником того, чтобы (простите за тазтологию) программист програм­ мировал, а не прятался за спиной различных визуальных программ-помощниког) Тем более что полноценного результата можно добиться только путем написания собственного кода.


LiveBindings

311

Начнем с малого. Предлагаем создать небольшое приложение, позволяющее осу­ ществить прямой обмен данными между двумя визуальными компонентами. Запускаем новый проект HD FireMonkey Application и размещаем на главной форме строку ввода TEdit, метку TLabei и список связей TBindingsList. В нашем примере метке отведена роль управляемого объекта — в ее свойство Text станут поступать данные из свойства Text управляющего объекта TEdit. Все подготовительные операции завершены, теперь нам следует осуществить свя­ зывание объектов. Распишем эту операцию по пунктам. 1. Дважды щелкните по компоненту BindingsListl, в результате чего на экране компьютера будет отображен редактор связей. 2. Для создания новой "живой связи" нажмите клавишу <Ins> (рис. 22.7).

*1йииГ

@ New UwBioding

LMwfi

gindSng Casses:

■BiftdingslSti | Editing Forml.8mdmgsUstl

Categories:

6md Components:

I> Quick BncSnes 4 Briding Expressions ; ! U TBindExpression ] <-THndExprltems * Links j

Name

!

A

j! I';: j|

TBmdUnk

; j' I TBmdListLink !■TBhdGridUnk !

t

I

TBndPositjon

1 ! TBindControlValue * Lists I mnAnt 1 L IBindGrldList

И

A DBLinks (deprecated) S- TBjndDBEdie.tnk !- THndDBTextLr*. t TMXLBtUnk i TBindDBImageLir*

j

OK

\ |

Р и с . 22 .7. С о з д а н и е новой "ж и в о й с в я зи " д л я ко м п о н е н та

C an a l

I I I

1 |

Ip - j

j {

Help

Labell

3. В диалоге New LiveBinding выберите узел TBindExpression (это укажет среде проектирования на то, что мы намерены создать связь на основе выражения) и нажмите кнопку О К (см. рис. 22.7). 4. Найдите в Инспекторе объектов связь BindExpressionl: TBindExpression и пе­ рейдите на страницу свойств Properties. Теперь нам предстоит настроить ряд свойств связи (рис. 22.8): ControlComponent:=Labell; ■ //управляемый объект ControlExpression:=Text; //получатель — заголовок Direction:=dirSourceToControl; //направление связи "источник->элемент"


312

Гпава 22

Managed:=true; SourceComponent:=Editl; SourceExpression:=Text;

//выражение

активно

//источник данных //свойство источника данных

Рис. 22.8. Настройка свойств связи T B i n d E x p r e s s i o n

Для завершения задуманного нам осталось немного поработать за клавиатурой. Листинг 22.2 содержит обработчик события o n C h a n g e T r a c k i n g () строки ввода. Это событие генерируется с каждым изменением содержимого компонента Editi.

implementation uses S y s t e m . B i n d i n g s . H e l p e r ; {$ R * . fmx}

procedure T F o r m l . E d i t l C h a n g e T r a c k i n g ( S e n d e r : T O b j e c t ) ; begin TBindings.Notify(Editl,'Text');

end;


UveBindings

313

На первый взгляд может показаться, что заданного результата (передачи текста из строки ввода в метку) мы достигли слишком высокой ценой. Но пример пресле­ довал другую цель — обратить ваше внимание на то, что текст из строки ввода в заголовок метки отправился неявно, ведь в коде даже нет и намека на Labell.Text:=Editl.Text!

З амечание В прилагаемом к книге архиве (см. приложение 5) вы найдете пример, в котором связь организуется . нежду компонентами, расположенными на разных формах. Особенность упоминаемого примера в том, что благодаря UveBindings объект-источник передает данные управляемому объекту, даже не подозревая о его существовании!

Класс TBindExpression Повторяя предыдущий пример, вы наверняка заметили, что основным действую­ щим лицом первого приложения стал экземпляр класса TBindExpression (модуль Data.Bind.Components). Именно TBindExpression взял на себя ответственность за ор­ ганизацию взаимодействия между двумя компонентами: объектом-источником и управляемым объектом. Для этого понадобилось лишь внести ряд настроек в свой­ ства объекта. Ссылка на объект-источник заносится в свойство property SourceComponent: TComponent;

Сведения об управлении окажутся неполными до тех пор, пока мы не опишем входное управляющее выражение в свойстве property SourceExpression: string;

В простейшем случае управляющее выражение будет хранить имя свойства объек­ та-источника с данными, которые следует передать управляемому объекту. Ссылка на второго участника взаимодействия LiveBindings заносится в свойство prop arty ControlComponent: TComponent;

Кроме имени управляемого объекта следует описать выходное управляющее выра­ жение, для этого предназначено свойство property ControlExpression: string;

Вновь заметим, что в простейшей ситуации здесь достаточно упомянуть название свойства, в которое поступят "живые" данные. Направление потока данных между объектами определяется свойством property Direction: TExpressionDirection;//dirSourceToControl

По умолчанию поток данных направлен от источника к управляемому объекту, од­ нако существуют еще два варианта направлений: type TExpressionDirection = (

dirSourceToControl, //источник —> управляемый объект dirControlToSource, //управляемый объект —> источник dirBidirectional); //двустороннее взаимодействие


314

Гпава 22

Для построения примера двустороннего взаимодействия от нас не потребуется осо­ бых трудозатрат. Разместите на форме два компонента ттгаскваг. Выбрав любой из компонентов, создайте связь LiveBindings. Данные должны передаваться между свойствами value (отвечающими за местоположение ползунка), а направление свя­ зи Direction установите В состояние dirBidirectional (рис. 22.9). Object Inspector BtodExpressionT rack B arl 1 TBsndExpression Properties | Events 1 AutoActJvate Category ControiComponent ControiExpresaon

\Binding E xpressions T rack B ari \ Value

rrectson

«

Managed

•■>/• True

Name

:BmdExpressionTrackBar 1 1

NotifyOutputs

ШSourceComponent Sourceexpresaon

!®T ru e

ВИ Д

......a |

J O Fsstse j T r a c 'S a r J V alue

SourceMemberName Tag

Evc'jat

D irecti on

BirtingsUsti AHshown

Ри с. 22.9. Организация двусторонней связи LiveBindings

Для завершения примера выберите обработчик события onchange () одного из ком­ понентов и внесите в него всего одну строку кода (листинг 22.3). ... .......... i'Tbl"" Листинг 22.3. Отправка уведомления в событии OnChange О . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .......................М Я М Н Я

ИИмИВ....... I ..............В ....

implementation uses System.Bindings.Helper; {$R *.fmx} procedure TForml.TrackBarIChange(Sender: TObj ect); begin

TBindings.Notify(Sender, ''); end;

Заключительный штрих таков — сделайте событие общим для обоих компонентов. Продолжим рассмотрение характеристик класса TBindExpression. Для деактивации объекта TBindExpression устанавливаем в false СВОЙСТВО property Active: Boolean;


LiveBindings

315

В нимание! Во время визуального проектирования свойство Active следует переводить в состоя­ ние false, если в связи задействованы элементы управления с разных форм и дина­ мически создаваемые объекты. В этом случае активация связи осуществляется про­ граммным способом, после создания всех форм и объектов.

Еще один способ приостановки взаимодействия обеспечивает свойство property Managed: Boolean; //по умолчанию true

Отказавшись от значения true, мы изымем связь из-под контроля менеджера связей. Как и положено полноценному объекту, выражение обладает способностью реаги­ ровать на события (табл. 22.1). Таблица 22.1. События TBindingExpression Событие

Описание

property OnActivating: TNotifyEvent;

Связь активируется

property OnActivated: TNotifyEvent;

Связь активирована

property OnAssigningValueEvent:

В объект поступает новое значе­ ние Value. Установив перемен­ ную Handled в состояние true, указываем объекту, что обработка события завершена

TBindingAssigningValueEvent; type TBindingAssigningValueEvent = procedure(AssignValueRec: TBindingAssignValueRec; var Value: TValue; var Handled: Boolean) of object; property OnEvalErrorEvent: TBindingEvalErrorEvent; type TBindingEvalErrorEvent = procedure(AException: Exception) of object; property OnAssignedValueEvent:

TBindingAssignedValueEvent; type TBindingAssignedValueEvent = procedure(AssignValueRec: TBindingAssignValueRec; const Value: TValue) of object;

Генерируется в момент возникно­ вения исключительной ситуации AException В объект поступило новое значе­ ние Value

Выражение LiveBindings Взаимодействие между объектами LiveBindings осуществляется в форме регуляр­ ных выражений. Повторив вводные примеры, мы практически не задумывались над порядком создания выражения — за нас все сделала Delphi. Однако пытливого про­ граммиста такое положение вещей вряд ли устроит. З амечание По своей сути выражение LiveBindings представляет собой обычную текстовую строку, отформатированную в соответствии с заданными правилами.

Поучительной иллюстрацией работы метода создания простейшего выражения может стать пример программы, способной осуществлять расчет арифметических


Гпава 22

316

выражений, записанных в форме обычной текстовой строки. Для этого нам потре­ буется помощь трех элементов управления: □ строки ввода □ кнопки □ метки

E d i t i :TEdit,

B u t t o n i :T B u t t o n ,

L a b e l l :T L a b e i ,

которая предназначена для ввода выражения;

она даст команду на расчет выражения;

которая отобразит результат расчета.

Полагаете, что далее последует сложный пример с элементами синтаксического разбора сложных предложений? Нет! Технология LiveBindings сделает это без посторонней помощи! На нашу долю выпадет только написать две строки кода в событии щелчка по кнопке B u t t o n i (листинг 22.4).

uses S y s t e m . B i n d i n g s . H e l p e r ; {$ R * . d f m }

procedure T F o r m l . B u t t o n l C l i c k ( S e n d e r : T O b j e c t ) ; begin with T B i n d i n g s . C r e a t e E x p r e s s i o n ( [], E d i t l . T e x t )

do

L a b e l l .T e x t := E v a l u a t e .G e t V a l u e .T o S t r i n g ;

end;

Единственная особенность нашего примера в том, что при вводе арифметического выражения пользователю требуется соблюдать знакомые ему по начальной школе правила, например: (10 + 2 ): 3 + 1. Кроме знаков основных математических операций в строке выражения могут ис­ пользоваться встроенные методы. На момент написания этой главы, технология Ж И В О Г О Связывания поддерживала методы: F o r m a t (), F o r m a t D a t e T i m e (), L o w e r c a s e (), Math_Max(),

Math_Min(),

R o u n d (),

S t r T o D a t e T i m e (),

T o N o t i f y E v e n t (),

ToStrO,

и u p p e r c a s e (). Кроме того, более двух десятков методов отвечают за преобразование типов данных.

ToVarianto

З амечание Для получения списка зарегистрированных методов LiveBindings во время визуально­ го проектирования приложения следует в Инспекторе объектов обратиться к свойст­ вам M e t h o d s И O u t p u t C o n v e r t e r s компонента T B i n d i n g s L i s t .

Названия методов говорят сами за себя и вряд ли требуют отдельных комментариев для программиста, имеющего опыт работы в Delphi. Например, метод F o r m a t () по­ зволяет форматировать текстовую строку, и его синтаксис очень напоминает поря­ док обращения к одноименной функции из модуля S y s t e m . S y s u t i i s . Допустим, что в нашем распоряжении есть связь T B i n d E x p r e s s i o n между компонентомисточником A r c D i a i i и компонентом-получателем Labell. Наша задача — сформу­ лировать выражение, которое позволит отобразить в метке L a b e l l значение угла поворота A r c D i a i i . V a l u e . Решение задачи достаточно простое: s o u r c e E x p r e s s i o n : = F o r m a t ( 'Угол:

Value).


317

LiveBindings

З амечание К сожалению, методы ми алфавитами.

Lowercase

() и

Uppercase

() пока не работают с национальны­

Не исключено, что в дальнейшем перечень методов возрастет, поэтому предлагаем пример, с помощью которого вы сможете самостоятельно опросить главного спе­ циалиста П О методам В технологии LiveBindings— класс T B i n d i n g M e t h o d s F a c t o r y (листинг 22.5). ; Листинг 22.5. Получение списка доступных методов uses S y s t e m . B i n d i n g s . M e t h o d s ;

{$R *.fmx} procedure T F o r m l .F o r m C r e a t e ( S e n d e r : T O b j e c t ) ; var MD: T M e t h o d D e s c r i p t i o n ; begin for M D in T B i n d i n g M e t h o d s F a c t o r y . G e t R e g i s t e r e d M e t h o d s do if M D . D e f a u l t E n a b l e d then L i s t B o x l .I t e m s . A d d ( M D . N a m e ) else L i s t B o x l .I t e m s . A d d ( M D . N a m e + ' ( о т к л ю ч е н о ) ' ) ; end;

Для повторения кода вам понадобится помощь списка граммный модуль методов S y s t e m . B i n d i n g s . M e t h o d s .

TListBox

и ссылка на про­

Класс TBindings Если логика приложения предполагает создание связи LiveBindings не поверхност­ ным визуальным, а программным образом, то разработчику стоит познакомиться с классом T B i n d i n g s . Сразу отметим тот факт, что T B i n d i n g s попросту является владельцем некоторого набора методов, специализирующихся на создании живых связей. Все методы объявлены как методы класса ( c l a s s f u n c t i o n ) и поэтому не нуждаются в физическом существовании экземпляра T B i n d i n g s . З амечание Основной код программной реализации технологии живого связывания сосредоточен В двух модулях: S y s t e m . B i n d i n g s . E x p r e s s i o n И S y s t e m . B i n d i n g s . H e l p e r .

Для создания простейшего выражения LiveBindings следует вызвать метод class function T B i n d i n g s .C r e a t e E x p r B i n d G r i d L i n k l e s s i o n ( const I n p u t S c o p e s : array of I S c o p e ; / / м а с с и в и с т о ч н и к о в д а н н ы х const B i n d E x p r S t r : string //строка выражения ): T B i n d i n g E x p r e s s i o n ; overload;

Параметр I n p u t S c o p e s содержит массив ассоциаций, представляющий собой пары "объект источник данны х— псевдоним объекта в выражении". Текст выражения, описывающего поведение источников данных, передается в параметр B i n d E x p r S t r .


Гпава 22

318

З амечание Еще одна особенность TBindings в том, что в результате действий класса на свет по­ является нерассмотренный несколькими страницами ранее 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;//выходное преобразование

BindingEventRec: TBindingEventRec; //событие Manager: TBindingManager = nil; //ссылка на менеджер Options: TCreateOptions = [coNotifyOutput]//опции ): TBindingExpression; overload;

Параметры InputScopes и BindExprStr нам уже знакомы. Параметр OutputScopes со­ держит массив пар "управляемый объект— псевдоним объекта в выходном выра­ жении". Выходное выражение заносится в параметр OutputExpr. Правила преобра­ зования данных определяются параметром OutputConverter. Ш естой по счету пара­ метр BindingEventRec предоставляет возможность подключения к созданному объекту связи обработчика события. Седьмой по счету параметр Manager требует более подробного рассмотрения. В результате выполнения функции появившийся на свет экземпляр связи сразу пе­ редается под опеку менеджера взаимодействия (TBindingManager). Менеджер управ­ ляет всеми связями в приложении и уведомляет их обо всех изменениях с ассоции­ рованными с этими связями объектами. В распоряжении менеджера могу] состоять подчиненные менеджеры (субменеджеры), которым также рассылаются уведомле­ ния об изменениях в объектах. Если вы явным образом не укажете на экземпляр менеджера и передадите в параметр Manager неопределенный указатель nil, то связь автоматически передается под опеку глобальному менеджеру связей прило­ жения. З амечание Технически связь может быть создана без зависимости от менеджера взаимодейст­ вия. Для этого применяется метод класса CreateUnmanagedBindirig ().

Для создания ассоциации между реальным объектом Delphi и его псевдонимом в строке выражения задействуется метод class function TBindings.CreateAssociationScope( Assocs: array of TBindingAssociation): IScope;

В самом простейшем случае в единственный параметр метода — массив ассоциа­ ций Assocs — передаются пары "объект— текстовый псевдоним". Эти пары легко формируются сервисной функцией function Associate(RealObject: TObject; const ScriptObject: String) : TBindingAssociation;


LiveBindings

319

После успешного формирования связи между объектами обсудим самый главный (уже знакомый по предыдущим примерам) метод-уведомление: class procedure TBindings.Notify(Sender: TObject; PropName: string =

Manager: TBindingManager = nil);

Метод уведомляет об изменении свойства или объекта, чем стимулирует один из взаимодействующих объектов на выполнение выражения. Здесь: sender— объект, инициирующий взаимодействие объектов; PropName — участвующее во взаимодей­ ствии свойство; Manager — необязательная ссылка на менеджер взаимодействия.

Lists — связь между списками Технология LiveBindings позволяет не только связывать отдельные свойства пары объектов, но и способна еще на более существенные достижения. Одно из них — организация взаимодействия между списками. Тот факт, что списочное представ­ ление данных предполагает наличие не одного, а целого ряда взаимоувязанных значений, существенно повышает сложность задачи, решаемой LiveBindings. Вновь возьмемся за программирование, на этот раз напишем пример, демонстри­ рующий возможности технологии живой связи при работе со списками. Правда, сначала нам придется немножко подготовиться и создать список, специализирую­ щийся на обслуживании произвольных данных. Этот список позднее станет источ­ ником данных для LiveBindings. Листинг 22.6 представляет исходный код класса TVaiues, предназначенного для хранения тройки разнотипных значений. Повторяя пример, вы можете добавить или удалить поля класса, выбрать свои типы данных — это лишь подчеркнет все­ ядность LiveBindings. ШЯятИИяШяШ type TValues=class private

fRValue:Real; //поле вещественного типа fIValue:Integer; //целочисленное поле public constructor Create(aRValue:Real;alValue:Integer); property RValue:real read fRValue; property IValue:Integer read fIValue; end;

//конструктор класса TVaiues constructor TVaiues.Create(aRValue: Real; alValue: Integer); begin

fRValue:=aRValue ; fIValue:=aIValue; end;


Гпава 22

320

После описания класса TVaiues, который возьмет на себя обязанности элемента списка, перейдем к описанию собственно списка, который станет источником дан­ ных для LiveBindings (листинг 22.7). ; Листинг 22.7. Создание списка источника данных для LiveBindings

.... ....................... Ш

uses ..., System.Generics.Collections; var Forml: TForml;

ValuesList:TList<TValues>; //объявление списка источника данных implementation

{$R *.fmx} prooedure TForml.FormCreate(Sender: TObj ect); var Values:TVaiues;

i :integer; begin R a n d o m i z e () ;

ValuesList:=TList<TValues>.Create; //создаем исходный список //заполняем 10 элементов списка произвольными данными for i :=0 to 9 do begin

Values:=TValues.Create(Random(),Random(100)); ValuesList.Add(Values); end; end;

Основная изюминка предложенного в листинге 22.7 кода заключается в том, что на роль списка М Ы выбрали объявленный В модуле S y s t e m . G e n e r i c s . C o l l e c t i o n s класс TList. Это класс-шаблон (см. гл а ву 3), создающий список, способный обслуживать практически любой тип данных. Мы воспользовались универсальностью T L i s t и заставили его работать с элементом типа T V a i u e s . После создания списка случай­ ным образом заполним его значениями и на этом ставим точку в подготовительном этапе. Наконец мы подошли к этапу организации связи между списком ValuesList и ком­ понентом ListBoxi. Для налаживания отношений между списками мы воспользуем­ ся элементом управления списком и парой компонентов со страницы LiveBindings, нам понадобятся: □

ListBoxi:TListBox;

BindingsListl: TBindingsList;

BindScopel: TBindScope;

Дважды щелкнув левой кнопкой мыши по компоненту B i n d i n g s L i s t l , войдем во встроенный редактор и создадим новую связь— элемент T B i n d L i s t (рис. 22.10). Новому объекту по умолчанию будет присвоено имя B i n d L i s t i .


LiveBinaifh

321

Рис. 22.10. Создание связи между списками — T B i n d L i s t

Найдите связь B i n d L i s t i в Инспекторе объектов и приведите ее свойства в следую­ щее состояние: □ автоматическая активациг

AutoActivate=True;

□ автоматическое заполнение целевого списка □ управляемый компонент □ компонент-источник

AutoFiii=Faise;

ControlComponent=ListBoxi;

SourceComponent=BindScopei;

□ щелкнув по кнопке с многоточием напротив свойства F o r m a t E x p r e s s i o n s , вы по­ лучите доступ к редактору выражений (рис. 22.11). Заполните два поля единст­ венного выражения следующим образом: •

ControlExpression=Text;

SourceExpression=ToStr(Current.IValue)

+ " " + ToStr(Current.RValue).

По большому счету связь уже полностью организована. Чтобы вы не слишком рас­ слаблялись, разбавим процесс визуального проектирования LiveBindings кодирова­ нием, нам предстоит написать целых три строки кода (листинг 22.8). ..... .... v.. ... *....

j Л ;т! |г 22.8. Создание in г .скг; сточнг : ;а рг.: ;ых р \ ч UvsBincDnge procedure T F o r m l . B u t t o n l C l i c k ( S e n d e r : T O b j e c t ) ; begin BindScopel.DataObject

:= V a l u e s L i s t ;

B i n d L i s t i .F i l l L i s t ; BindScopel.DataObject

end;

//подключаем исходный //передаем данные

:=

nil;

список

в целевой список

//удаляем ссылку на объект


322

Гпава 22 Object Inspector B in d L w t l . F o r m a t f x p r « s io n s [ 0 ] T B ip r e s M f it o i

J Properties] Events I__________________________

6eotraE)$ressa» fTect______________ SourceExpression

ToStr(CurrenLIValue) + " * -f To!

*— © Editing BindUstl.formatExpfessiom

to » ! Name

sr

Control Expression

Source Expression

Text

± м?(сигг; о

С o n I r o K x p r e s s io i

Alsfaown Я Н Н Рис. 22.11. Редактор выражения элемента T B i n d L i s t

Щ елчок по кнопке заставит отвечающие за связь компоненты B i n d S c o p e i и заполнить список L i s t B o x l данными из целевого списка v a i u e s L i s t .

BindListi

Класс TBindList Класс T B i n d L i s t обеспечивает связь между двумя списками данных. В качестве ис­ точника может выступать практически любой набор значений, оформленный в виде списка (именно так мы и поступили в листинге 22.6), ссылка на список-источник данных передается в свойство property S o u r c e C o m p o n e n t : T C o m p o n e n t ;

Второй участник связи (управляемый объект) должен быть упомянут в свойстве property C o n t r o l C o m p o n e n t : T C o m p o n e n t ;

Правила взаимодействия между списками описываются в свойстве property F o r m a t E x p r e s s i o n s : T E x p r e s s i o n s ;

Экземпляр

класса T E x p r e s s i o n s представляет собой коллекцию элементов специализирующуюся на обслуживании пар управляющих выра­ жений C o n t r o l E x p r e s s i o n и s o u r c e E x p r e s s i o n . Правила формирования выражений зависят от вида обслуживаемых данных. TExpressionitem,

Для отправки данных от списка-источника к получателю задействуется метод procedure F i l l L i s t ;

Впрочем, если свойство property A u t o F i l l : B o o l e a n ;

//по у м о л ч а н и ю true

установлено в состояние по умолчанию, то управляемый объект станет получать данные от источника автоматически.


ГЛАВА

23

Многопоточные приложения

Сегодня однопоточное приложение — большая редкость, ведь в таком случае поток приложения выступает единственным связующим звеном между всеми сторонами, заинтересованными в его работе. Поток берет на себя обязательства за взаимодей­ ствие приложения с ОС, за рендеринг окон, за реакцию нажатий пользователем клавиш, за осуществление расчетов, одним словом — за все! Не трудно догадаться, что произойдет с подобным приложением, если его очень трудолюбивый, но, увы, не имеющий помощников поток столкнется с ресурсоемкой вычислительной зада­ чей. На время выполнения этого задания программа не сможет заниматься всеми остальными делами, как следствие — приложение перестанет откликаться на дей­ ствия пользователя, прекратит взаимодействовать с ОС, одним словом, окажется в состоянии, близком к "зависанию". К счастью как в Windows, так и в OS X, кроме обязательного потока управления процесс имеет возможность обладать набором вспомогательных потоков. Напри­ мер, текстовый процессор Microsoft Word (в котором набираются эти строки) одно­ временно с вводом текста разбивает документ на страницы, контролирует знания языка (безжалостно подчеркивая мои ошибки), отправляет задание на печать и ре­ шает ряд других, менее очевидных для обычного пользователя задач. Все это про­ исходит одновременно благодаря тому, что в Word запущено несколько потоков. Кахедый из потоков выполняет поставленную перед ним подзадачу и при желании может быть приостановлен или запущен вновь. Как видите, возможность создания в приложении нескольких потоков предоставля­ ет нам определенные преимущества, в первую очередь связанные с повышением производительности. Дело осталось за малым — научить приложения FireMonkey работать с потоками.

Поток TThread Для программной реализации потока в модуле S y s t e m , c l a s s e s объявлен абстракт­ ный класс T T h r e a d , на основе которого программист создаст свой полнофункцио­ нальный поток.


324

Гпава 23 Внимание!

При проектировании многопоточных приложений для OS X надо учитывать тот факт, что данная ОС поддерживает две технологии потоков: потоки Cocoa и потоки POSIX. Представленный в RAD Studio ХЕ5 класс S y s t e m . c l a s s e s . T T h r e a d нацелен на работу с технологией потоков POSIX.

На палитре компонентов элемент управления T T h r e a d вы не найдете. Для того что­ бы самое обычное приложение сделать многопоточным, требуется к уже сущест­ вующему приложению добавить специальный программный модуль. Для этого по­ сле создания нового приложения выберите пункт меню File | New | O th e r и в разде­ ле D elphi Files диалогового окна New Item s найдите значок T hread O bject. После нажатия кнопки О К в новом диалоговом окне требуется ввести имя нового клас­ са — потока. Например, T D e m o T h r e a d , как показано на рис. 23.1. 0

New Items !>-Сз C + rnM et Projects * : Delphi Projects j j--C3 ActiveX j f " ' 0 0»t»Snap Server ! Г : Mpfe Fits ! i Inheritable Items ! i - C l Intraweb i-- Q WeberotoC3 iVebSerwces i--C3 WebSnap

; L-CjXML

j~ C 3

Q , S earch

Component

Data Modute

FireMonkey Form

FreMqnkey Metropolis...

TeeChart Wizard

g jU j j U j j

Unit

BBS MSBufld Targets Hie

Design Project!

i--C3 Intraweb

■-СЭ l- С З j- С З l~C3

Other Files ProfSng Unit Test Web Documents

Mew Thread Object

QassName: TDemoTtiread --1 i Г ; flamed Thread

TbmsriMare: j ■

r~ Z 3

CaoaH Jf

Р и с. 23.1. Добавление потока к приложению

В результате выполненных действий Delphi создает представленный в листин­ ге 23.1 шаблон, предназначенный для описания потока. Обратите внимание, что в секции P r o t e c t e d среда проектирования уже подготовила процедуру E x e c u t e о . В теле этой процедуры программист располагает программный код, который станет исполняться в отдельном потоке. Если подзадача вызывается многократно, то строки кода помещаются внутрь тела цикла, выход из которого завершает работу потока. \ Л ш ги я г 23.1. Модуль потока TDemoThread unit U n i t 2 ; interface uses S y s t e m . C l a s s e s ;


Многопоточные приложения

325

type T D e m o T h r e a d = class (TThread) private { Private declarations

}

protected procedure E x e c u t e ; override; end; implementation { TDemoThread

}

procedure T D e m o T h r e a d . E x e c u t e ; begin { код потока

}

При старте приложения автоматическое создание потока не произойдет, для этого следует явно обратиться к конструктору потока constructor C r e a t e ( C r e a t e S u s p e n d e d : B o o l e a n ) ;

Единственный параметр конструктора определяет, каким образом производится запуск потока. Если параметр C r e a t e S u s p e n d e d установлен в false, то поток старту­ ет немедленно, сразу после создания экземпляра класса. Иначе поток создается в спящем режиме. В последнем случае для запуска потока вызывайте метод proc.-dur:

Start;

Говоря образно, эта процедура заставит биться сердце потока. Для временной приостановки выполнения потока используйте процедуру procedure S u s p e n d ;

Для возобновления работы приостановленного потока procedure S t a r t ;

Возобновление выполнения потока произойдет именно с того места (с той строки кода), где он был приостановлен. Два названных метода дублируются свойством property S u s p e n d e d : B o o l e a n ;

Если вы установите свойство в визируется.

true,

то поток приостановится,

false

— вновь акти­

З амечание В более ранних версиях Delphi вместо метода s t a r t () применялся метод В FireMonkey применение этого метода не рекомендуется.

Resume

().

Для временной приостановки потока программисты Delphi часто пользуются про­ цедурой класса class procedure S l e e p ( T i m e o u t : I n t e g e r ) ; static;


326

Гпава 23

В качестве параметра передается число миллисекунд, на которые необходимо за­ морозить подзадачу. З амечание Основное достоинство метода s l e e p () в том, что поток, переведенный в состояние сна, не снижает производительность системы, т. к. не требует выделения ему квантов времени.

Признаком того, что поток завершил выполнение своей задачи, выступает значение true, возвращаемое свойством property F i n i s h e d : B o o l e a n ;

Для полной остановки потока вызывайте метод procedure T e r m i n a t e ;

Этот способ остановки называется мягким. Процедура просто-напросто присваива­ ет значение t r u e свойству property T e r m i n a t e d : B o o l e a n ;

В этом случае Delphi не настаивает на немедленном прекращении работы потока и разрешает ему достичь логического конца. Очень часто проверка состояния свойства служит сигналом к выходу из цикла об­ работки задачи потока (листинг 23.2). ; Листинг 23.2. Шаблон кода основного метода потока procedure T D e m o T h r e a d .E x e c u t e ; begin repeat {исполняемый ко д потока}

until T e r m i n a t e d ;

Останов потока сопровождается вызовом обработчика события property O n T e r m i n a t e : T N o t i f y E v e n t ;

Если установить в

true

свойство

property F r e e O n T e r m i n a t e : B o o l e a n ;

то сразу после останова уничтожится экземпляр потока. Иначе за вызов деструкто­ ра потока отвечает программист. Если выполнение метода E x e c u t e () прервалось в результате ошибки, и ошибка не была обработана в рамках этого метода, то в свойство только для чтения property F a t a l E x c e p t i o n : T O b j e c t ;

будет записан объект ошибки.

//только для чтения


Многопоточные приложения

327

Еще одним способом, косвенно проверяющим корректность завершения работы потока, является использование свойства p ro p e rty ReturnValue: Integer;

Это служебная переменная, в которую можно записывать данные, например, о ко­ личестве запусков потока. Свойство ReturnValue часто применяется совместно с методом ожидания waitFor (). Функция предназначена для организации ожидания, пока какой-нибудь другой поток не выполнит поставленную перед ним задачу. Существует альтернативный доступ к значению метод класса

ReturnValue,

его обеспечивает

clas s procedure SetReturnValue(Value: Integer); s ta tic ;

Благодаря методу мы сможем изменить возвращаемое потоком значение, даже не имея прямого доступа к экземпляру потока. Современные версии Windows и OS X поддерживают работу с несколькими про­ цессорами, распределяя задачи между ними. Класс TThread способен проинформи­ ровать программиста о процессоре компьютера. Свойство class p ro p erty IsSingleProcessor: Boolean;

в состоянии true уведомляет разработчика, что поток будет запущен на однопро­ цессорной станции, если же свойство возвратит false, то стоит обратиться к свой­ ству clas s p ro p e rty ProcessorCount: Integer;

возвращающему число процессоров или ядер процессора. В

н им а н ие

!

Р а зр а б о тчи ку р е ко м е н д уе тся не за п уска ть в п р и л о ж е н и и б о л е е 16 п о то ко в на од ин п р о ц е с с о р , т . к. п р и п р е в ы ш е н и и э т о г о з н а ч е н и я б у д е т с у щ е с т в е н н о с н и ж е н а п р о и з в о ­ д и те л ь н о сть си стем ы .

Еще одна полезная функция класса class fu n c tio n GetCPUUsage (var PrevSystemTimes: TSystemTimes): Integer;

позволит программисту выяснить степень загрузки процессора. Единственная осо­ бенность метода в том, что поступающая в его параметр структура PrevSystemTimes : TSystemTimes должна быть объявлена как глобальная переменная. В самом про­ стейшем случае, разместив на форме шкалу TProgressBar, метку TLabei и таймер TTimer, вы сможете получать регулярные сведения о занятости центрального про­ цессора (листинг 23.3). Л j

г 2S.3, Стоп:

зсг; ::;онности центрального процессора

v a r Forml: TForml; ST: TThread.TSystemTimes;


328

Гпава 23

procedure T F o r m l . T i m e r l T i m e r ( S e n d e r : T O b j e c t ) ; var X: I n t e g e r ; begin X

:= T T h r e a d . G e t C P U U s a g e ( S T ) ;

ProgressBarl.Value Labell.Text

:= X;

:= F o r m a t ('% d % % [ X ] );

Если в приложении допускается исполнение нескольких потоков, то мы имеем пра­ во попросить текущий процессор перейти на обслуживание очередного потока. Для этого предназначен метод класса class procedure Y i e l d ; static;

Заметим, что окончательное решение на постановку потока на обслуживание всегда останется за операционной системой.

Метод ожидания При проектировании многопоточного приложения, в котором несколько потоков обслуживают один и тот же ресурс (файл, коммуникационный порт и т. п.) или вы­ полняют общий участок кода, надо исключить конфликт совместного доступа к ресурсу. Большую помощь в этом оказывает метод ожидания, позволяющий одному потоку дождаться завершения другого. function W a i t F o r : L o n g W o r d ;

Функция возвращает значение, содержащееся в свойстве потока (табл. 23.1).

ReturnValue

ожидаемого

Таблица 23.1. Значения, возвращаемые методом

WaitFor ()

Значение

Описание

WAIT_OBJECT_0

Контролируемый объект перешел в сигнальное состояние. Это признак того, что выполняющийся ранее поток корректно завершил свою задачу

WAIT TIMEOUT

Хотя время тайм-аута истекло, но состояние опрашиваемого объекта син­ хронизации несигнальное. Это означает, что ресурс пока захвачен другим потоком

WAIT_ABANDONED

Выполнение обладавшего объектом синхронизации потока завершилось, но объект по-прежнему захвачен этим потоком. Ввиду некорректности такого положения вещей объект синхронизации передается во владение вызвавшего его потока и переводится в несигнальное состояние

WAIT_FAILED

Ошибка выполнения метода

З амечание Метод W a i t F o r () применяется для совместной работы не только с потоками, но и с такими объектами синхронизации Windows, как события, мьютексы и семафоры. Ключевая особенность метода ожидания заключается в том, что он не возвращает ре-


Многопоточные приложения

329

зультат немедленно, а ждет, пока не будет удовлетворен некоторый перечень задан­ ных программистом критериев. Таким образом поток, обратившийся к объекту синхро­ низации при помощи метода W a i t F o r (), превращается в пленника, ожидая от метода ответа. Соответственно выполнение потока приостанавливается и таким образом ре­ шается задача синхронизации

Управление приоритетом потока Обычный поток приложения (точно так, как и поток управления приложения) об­ ладает определенным приоритетом, от которого зависит, сколько квантов времени будет выделено процессором компьютера для обработки подзадачи. Приоритет потока определяется значениями двух составляющих: приоритетом процеесавладельца потока и собственно приоритетом этого потока. Поэтому приоритет обычного потока часто называю^ от носит ельным приорит ет ом . Такое название объясняется тем, что два потока, скажем, с приоритетом t p N o r m a i , но порожденные процессами с различными приоритетами, не смогут претендовать на одинаковое процессорное время. Реальный приоритет потока определяется приоритетом поро­ ждающего его процесса и своим приоритетом. При определении приоритета потока следует учитывать некоторые различия Windows и OS X. Так в приложениях Windows приоритет потока определяется именованной константой type TThreadPriority =

(tpldle, tpLowest,

//фоновый приоритет //низкий приоритет

tpLower,

//пониженный приоритет

tpNormai,

//нормальный приоритет

tpHigher,

//повышенный приоритет

tpHighest,

//высочайший приоритет

tpTimeCritical); //приоритет реального

времени

в свойстве p ro p e rty

Priority:

TThreadPriority;

//по у м о л ч а н и ю tpNormai

В проектах для OS X при определении приоритета потока POSIX мы воспользуемся целым числом p ro p e rty

Priority:

integer;

Внимание!

Без особой на то необходимости старайтесь не создавать потоков с приоритетом вы­ ше нормального. Такой поступок может привести к тому, что системные ресурсы будут нацелены на обслуживание только этой задачи. Замечание

*

В состав Delphi входит специальная утилита, предназначенная Для просмотра потоков приложения. Для обращения к утилите выберите пункт меню View | Debug Windows |

Threads.


330

Гпава 23

Синхронный и асинхронный вызовы внешнего метода Если из тела основного метода потока E x e c u t e () необходимо осуществить вызов внешнего метода или обратиться к свойству компонента библиотеки VCL, то не следует это делать напрямую, вместо этого надо использовать специализированные методы s y n c h r o n i z e () или Q u e u e (). Задача м етодов— исключение взаимных бло­ кировок потоков. З амечание Блокировка потоков возникает в том случае, когда поток или несколько потоков пыта­ ются одновременно получить доступ к одному и тому же ресурсу. Если этот ресурс захвачен третьей стороной, например главным потоком приложения, и корректный за­ хват ресурса невозможен, то потоки попадают в тупик. Такая ситуация называется

взаимной блокировкой. Существует несколько перегружаемых версий методов, мы остановимся на наибо­ лее простых: procedure S y n c h r o n i z e ( M e t h o d : T T h r e a d M e t h o d ) ; overload; procedure Q u e u e ( A M e t h o d : T T h r e a d M e t h o d ) ; overload;

Методы гарантируют, что к объекту (методу объекта) одновременно получит дос­ туп только один поток. В качестве аргумента процедуры передается имя вызывае­ мого метода. Разница между S y n c h r o n i z e () или Q u e u e о заключается в порядке ожидания ответа от вызванного внешнего метода. Процедура S y n c h r o n i z e () работа­ ет в синхронном реж и м е. Это означает, что поток, обратившись к внешнему мето­ ду, приостановит свою работу до тех пор, пока не получит ответа о том, что внеш­ ний метод выполнен. Процедура Queue () отдает предпочтение асинхронном у р еж и м у, поток просто ставит задачу в очередь и, не дожидаясь ответа о ее выпол­ нении, продолжает свою работу.

Пример многопоточного приложения Для демонстрации возможности многопоточной обработки данных разработаем приложение, позволяющее собирать сведения о находящихся в определенной папке файлах с растровыми изображениями JPG и создающее их миниатюры. Для реали­ зации задуманного нам понадобится новое приложение HD, на единственной фор­ ме которого следует расположить: □ строку ввода

Ed itl,

в ней пользователь введет путь к папке с файлами;

□ область с полосами прокрутками S c r o l i B o x i , внутри этой области мы станем динамически создавать миниатюры изображений; □ кнопку

Buttonl,

отвечающую за запуск процесс сбора.

В листинге 23.4 представлен предельно упрощенный исходный код потока T T h u m b T h r e a d , отвечающего за создание миниатюры.


Многопоточные приложения

331

".

щ

и ш Iш т ш т ж

I П 4СТИНГ 23.4. Код пото.:) созд^ н;*я зьнжторы

кйй*йа»а& ■' ...... ...............................■ TThumbThread =

class ( TThread)

private FileName:TFileName;

//имя файла

Image

//ссьшка

: TImage;

с изображением

на компонент TImage

protected procedure E x e c u t e ; override; end; procedure T T h u m b T h r e a d . E x e c u t e ; begin Image.Bitmap.LoadThumbnailFromFile(FileName,Image.Width,Image.Height); Terminate;

end;

В секции частных объявлений потока T T h u m b T h r e a d мы ввели два дополнительных пол?. В поле F i l e N a m e заносится имя файла, для которого следует создать миниатю­ ру, а в поле i m a g e окажется ссылка на компонент T I m a g e , отображающий картинку. Процесс сбора миниатюр начинается в момент щелчка пользователя по кнопке (листинг 23.5). ; Листинг 23.5. СОор файлов и динамической созаанио компонентов, 71шадо procedure T F o r m l . B u t t o n l C l i c k f S e n d e r : T O b j e c t ) ; var SDA:TStringDynArray; i :i n t e g e r ; T h u m b I m a g e :T I m a g e ;

begin SDA:=TDirectory.GetFiles(Editl.Text, ' * . j pg',

for i : = 0 to H i g h ( S D A ) begin

TSearchOption.soTopDirectoryOnly);

do

T h u m b l m a g e := T I m a g e .C r e a t e ( S c r o l l B o x l ) ; T h u m b I m a g e .P a r e n t := S c r o l l B o x l ; T h u m b l m a g e .W i d t h :=100;

T h u m b l m a g e .H e i g h t :=100;

with T T h u m b T h r e a d . C r e a t e ( t r u e ) begin

do

FileName:=SDA[i]; Image

:= T h u m b I m a g e ;

F r e e O n T e r m i n a t e := t r u e ; {$IFDEF M S W I N D O W S } //код для Windows P r i o r i t y := T T h r e a d P r i o r i t y .t p L o w e r ; {$ELSE}


332

Гпава 23 {$IFDEF POSIX}

//код для потоков

POSIX

P r io r it y : =10; {$ E N D I F } {$ E N D I F }

S ta r t; end;

end;

Воспользовавшись услугами метода G e tF ile s о класса TD irectory, мы собираем все файлы с расширением имени jpg в динамический массив sda. Затем, перебирая эле­ менты массива, создаем экземпляры класса т image для каждого файла с картинкой и размещаем их на поверхности компонента s c r o liB o x i. После этого наступает черед формирования отдельного потока — он создается на основе описанного в листин­ ге 23.4 класса TThumbThread. Потоку передаются сведения об имени файла и ссылка на объект ТImage. Проект почти готов, вам осталось самостоятельно решить задачу размещения кар­ тинок на поверхности s c r o liB o x i или обратиться к архиву с примерами к этой гла­ ве. В результате мы получим приложение, представленное на рис. 23.2. 0 Мои файлы 033 № < 1 * 2

! ® -j

С о зд а н и е м и н и а т ю р ш п о ц м е T T h re a d

DSC00'

DSCGOOO! JP G

O SC 0G003JPG

DSCCC005 JPG

DSC0G0D6 JP G

D SC 00009.JPG

DSCG001Q.JPG

D SC 00012 JP G

DSCQ0013JPG

DSC0Q0l5.JPG

DSCOO016 JP G

DSCQQ0t7.JPG

D SC00018.JPG j ;

DSCOO;

DSCOOi

Путь к папке с ф ай л ам и . * ^ р Л ^ Л # д Л 1 р 2 Л № е д /- р п д / .8 й Л « ; \ « 8 а

Рис. 23.2. Экранный снимок работающего приложения

Синхронизация потоков в Windows Одной из серьезных проблем, с которой сталкиваются разработчики многопоточ­ ных приложений, является организация совместного доступа двух (и более) пото­ ков к общему ресурсу. Конкурируя за обладание общим ресурсом, потоки из по­


333

Многопоточные приложения

мощников превращаются в хулиганов, инициируя неприятные ошибки. Поэтому при реализации взаимодействующих потоков программист обязан решить задачу корректного доступа потоков к разделяемому ресурсу. Для исключения ошибок при подключении к общему ресурсу потокам необходимо синхронизировать свои дей­ ствия так, чтобы ресурс принадлежал не более чем одному потоку одновременно. При попытке обращения к захваченному ресурсу остальных потоков они должны быть приостановлены до момента его освобождения. Операционная система Windows — большая мастерица по синхронизации процес­ сов и потоков. Для этого реализовано несколько механизмов управления процесса­ ми и потоками, при которых диспетчер задач не выделяет ожидающим освобожде­ ния ресурса ни кванта лишнего времени. Синхронизация производится с помощью объ