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

 

 

 

 

 

 

Delphi.

х,·

 
Проrраммирование дnя

Windows, 05  iOS   и  Android

 

 

 

Санкт-Петербург

«БХВ-Петербург» 2014


УДК ББК


004.4'2

32.973.26-018.1

0-74

 

ОсиповД.Л.

О-74          Delphi. Программирование для Windows, OS Х, IOS и Android. - СПб.: БХВ-Петербург, 2014. -464 с.: ил. -(Профессиональное программирование)

ISBN 978-5-9775-3289-1

Книга посвящена одному из самых совершенных языков программирования Delphi ХЕ5/ХЕ6. В ней подробно рассматривается новейшая кроссплатформенная библиотека FM, позволяющая создавать полнофункциональное программное обес­ печение для операционных систем Windows и OS Х, а также для смартфонов и планшетных компьютеров, работающих под управлением Android и IOS. Проекты примеров из книги размещены на сайте издательства.

Для программистов

 

УДКОО4.4'2 ББК 32.973.26-018.1

 

 

 

Группа подготовки издания:


 


Главный редактор

Зам. главного редактора Зав.редакцией

Редактор Компьютерная верстка Корректор

Дизайн серии Оформление обложки


Екатерина Кондукова Игорь Шишигин Екатерина Капалыгина Анна Кузьмина

Ольги Сергиенко Зинаида Дмитриева Инны Тачиной Марины Дамбиевой


 

 

 

 

 

 


Подписано в печать 31.01.14.

Формат 70х1001/16. Печать офсетная. Усл. печ. л. 37,41.

Тираж 1000 экз. Заказ 65

"БХВ-Петербург", 191036, Санкт-Петербург, Гончарная ул., 20.

Первая Академическая типография "Наука" 199034, Санкт-Петербург, 9 линия, 12/28

 

 

 

 

 

ISBN 978-5-9775-3289-1                                                              © Осипов Д. Л., 2014

© Оформление, издательство "БХВ-Петербурr", 2014

 

 

 



 

 

 

 

 

 

 

 

 

 

 

 


Оглавление

 

Введение.................................................................................................................. 13

Глава 1. Подготовка к работе................................................................................ 15

Выбор типа приложения.............................................................................................................................. 17

Выбор целевой платформы для проекта.................................................................................................. 18

Выпуск приложения для OS Х..................................,............................................................................... 19

Выпуск приложения для iOS MoЬile........................................................................................................ 22

Выпуск приложения для Android............................................................................................................... 23

Что делать, когда код зависит от платформы?....................................................................................... 27

Глава 2. Забываем VCL?........................................................................................ 30

Опорный класс VCL -  TObject................................................................................................................. 30

Управление жизненным циклом объекта....................................................................................... 32

Механизм учета ссылок в мобильных проектах........................................................................... 34

Информирование о классе.................................................................................................................. 35

Класс TPersistent............................................................................................................................................. 37

Основа компонента TComponent................................................................................................................ 38

Владение объектами............................................................................................................................. 39

Глава 3. Классы-шаблоны...._................................................................................ 41

Обобщенный тип данных в полях записей............................................................................................. 41

Обобщения в процедурах и функциях...................................................................................................... 43

Обобщенные типы данных в шаблонах классов................................................................................... 44

Наследование шаблона класса.......................................................................................................... 46

Перегрузка методов с параметром обобщенного типа............................................................... 47

Шаблон массива, класс ТArray<>............................................................................................................. 47

Шаблон списка объектов, класс TObjectList<>...................................................................................... 49

Шаблон словаря TDictionary<>.................................................................................................................. 53

Глава 4. Базовые классы FireMonkey.................................................................... 56

Опорный класс TFmxObject......................................................................................................................... 56

Управление дочерними объектами................................................................................................... 57

Сопоставление дополнительных данных........................................................................................ 60

Поддержка LiveBindings..............................................._...................................................................... 61


Поддержка анимации........................................................................................................................... 61

Поддержка сенсорного ввода............................................................................................................ 61

Взаимодействие с командами............................................................................................................ 61

2D-элементы управления, класс TControl............................................................................................... 62

Размещение 2D-элемента управления............................................................................................. 63

Выравнивание объекта.............................................................................................................. 64

Группировка объектов и компонент TLayout...................................................................... 65

Масштабирование и вращение объекта.......................................................................................... 66

Видимость и прозрачность элемента управления........................................................................ 68

Грани, фаски и визуальные эффекты............................................................................................... 68

Состояние элемента управления...........................................,.......................................................... 70

Обработка событий.............................................................................................................................. 70

Простейшие события -  щелчок............................................................................................ 70

Клавиатурные события.............................................................................................................. 73

События мыши............................................................................................................................ 74

События получения·и потери фокуса ввода........................................................................ 77

Событие изменения размера.................................................................................................... 77

События перетаскивания drag and drop..........,..................................................................... 79

Особенности прорисовки элемента управления........................................................................... 82

Стилевое оформление, класс TSty/edControl........................................................................................... 83

3D-элементы управления, класс TControlЗD ....................................•.................................................. 83

Размеры объекта.................................................................................................................................... 84

Повороты объекта................................................................................................................................. 84

3D-события мыши................................................................................................................................ 85

Глава 5. Приложение FireMonkey.......................................................................... 88

Приложение ТApp/ication .....................................................................................,................................... 88

Значок приложения............................................................................................................................... 89

Название приложения........................................................................................................................... 89

Расположение исполняемого файла приложения......................................................................... 91

События приложения........................................................................................................................... 91

Контроль активности пользователя.................................................................................................. 93

Характеристики дисплея, класс TFormFactor................................................................................ 94

Формы HD и 3D.."......................................................................................................................................... 95

Описание формы в fi:nх-файле.......................................................................................................... 96

Общие черты форм ................................:··················................................................................... 98

Создание, отображение и уничтожение форм.................................................................... 98

Состояние формы..................................................................................................................... 102

Жизненный цикл формы........................................................................................................ 103

Доступ к элементу управления по его координатам.................................................................. 106

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

Качество графического вывода...................................................................................................... 107

Форма HD FМX.Forms. TForm......................................................................................................... 108

Трехмерная форма FМX.FormsЗD.TFormЗD................................................................................ 108

Пример 3D-проекта.................................................................................................................. 110

Совместное применение 2D- и 3D-компонентов.............................................................. 113

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

Подключение ресурсов и изображений................................................................................................. 115


Глава 6. Меню приложения.................................................................................. 118

Элемент меню TMenultem.......................................................................................................................... 120

Элемент меню в виде флажка.......................................................................................................... 121

Группировка элементов меню......................................................................................................... 122

Доступ к дочерним элементам меню............................................................................................. 124

Главное меню TMainMenu......................................................................................................................... 124

Планка меню TMenuBar............................................................................................................................. 124

Контекстное меню ТРорирМепи............................................................................................................... 124

Глава 7. Командный интерфейс........................................................................... 126

Команда TAction.......................................................................................................................................... 127

Связь с элементом управления........................................................................................................ 129

Выполнение команды........................................................................................................................ 130

Установка команды в актуальное состояние............................................................................... 130

Связь команды с контейнером......................................................................................................... 131

Предопределенные команды............................................................................................................ 131

Список команд TActionList........................................................................................................................ 133

Глава 8. Управление папками и файлами.......................................................... 135

Работа с дисками......................................................................................................................................... 135

Сбор сведений о каталогах и файлах...................................................................................................... 136

Проверка существования файла и, каталога.......................................................................................... 137

Расположение системных каталогов....................................................................................................... 137

Создание, удаление, копирование и,перемещение.............................................................................. 138

Запись в файл и чтение из файла............................................................................................................. 139

Атрибуты файла и каталога....................................................................................................................... 140

Дата и время создания файла и каталога............................................................................................... 141

Глава 9. Компоненты для работь1 с текстом....................................................... 142

Класс ПextControl........................................................................................................................................ 143

Метка TLabel ................................................................................,............................................................ 144

Интерфейс IVirtua/KeyboardControl......................................................................................................... 146

Основа строк ввода, класс TCustomEdit................................................................................................. 146

Ограничения на ввод.......................................................................................................................... 148

Выделение части текста.................................................................................................................... 148

Взаимодействие с буфером обмена................................................................................................ 150

Управляющие символы..................................................................................................................... 150

Особенности оформления................................................................................................................. 151

Строки ввода TEdit и TClearingEdit........................................................................................................ 152

Многострочный редактор ТМето........................................................................................................... 153

Позиция каретки································································································· 154

Редактирование текста ,.................................................................................................................... 154

Быстрое перемещение по тексту.................................................................................................... 155

Ввод чисел TNumberBox, TSpinBox и TComboTrackBar..................................................................... 156

Глава 10. Компоненты-списки............................................................................. 158

Базовый элемент списка TListBoxltem..................................................................................................... 159

Список выбора TListBox............................................................................................................................. 161

Редактирование элементов............................................................................................................... 162

Доступ к выделенному элементу списка...................................................................................... 164


Дос1УП к произвольному элементу списка.................................................................................. 165

Выбор нескольких элементов.......................................................................................................... 165

Представление элементов в виде кнопки выбора....................................................................... 166

Перестановка элементов................................................................................................................... 166

Сортировка элементов....................................................................................................................... 166

Текстовый поиск, элемент TSearchBox.......................................................................................... 167

Особенности оформления списка................................................................................................... 168

Основные события списка................................................................................................................ 169

Нередактируемый комбинированный список ТСотЬоВох............................................................... 169

Редактируемый комбинированный список TComboEdit.................................................................... 172

Компонент выбора значения ТРорирВох.............................................................................................. 174

Глава 11. Иерархическая структура.................................................................... 175

Узел дерева ТТreeView/tem......................................................................................................................... 176

Управление дочерними узлами....................................................................................................... 176

Положение узла в дереве.................................................................................................................. 178

Состояние узла.................................................................................................................................... 179

Дерево ТТrееView......................................................................................................................................... 179

Выделение узла................................................................................................................................... 179

Доступ к узлу....................................................................................................................................... 180

Управление составом узлов............................................................................................................. 180

Узел в роли флажка............................................................................................................................ 183

Свертывание и развертывание узлов............................................................................................. 183

Упорядочивание узлов дерева........................................................................................................ 184

Глава 12. Сетки..................................................................................................... 185

Колонки сетки.............................................................................................................................................. 185

Сетка TGrid................................................................................................................................................... 187

Сетка TStringGrid......................................................................................................................................... 188

Пример обслуживания текстовых данных............................................................................................ 188

Глава 13. Окна сообщений и диалоrи.................................................................. 192

Окна сообщений........................................................................................................................................... 192

Окна выбора действия................................................................................................................................ 194

Окна ввода данных...................................................................................................................................... 196

Компоненты-диалоги................................................................................................................................... 197

Открытие и сохранение файлов TOpenDialog и TSaveDialog................................................. 197

Параметры страницы TPageSetupDialog....................................................................................... 202

Настройка печати TPrinterSetupDialog........................................................................................... 203

Отправка задания на печать TPrintDia/og.................................................................................... 203

Глава 14. Дата и время......................................................................................... 206

Дата и время TDateTime............................................................................................................................. 206

Интервал времени ТТiтеSрап.................................................................................................................. 207

Отсчет времени, таймер ТТiтеr............................................................................................................... 208

Календари TCalendar и TCalendarEdit................................................................................................... 209

Глава 15. Управление цветом.............................................................................. 212

Представление цвета ARGB..................................................................................................................... 212

Стандартные цветовые комбинации....................................................................................................... 214


Компоненты цветовой модели ARGB.................................................................................................... 215

Компоненты цветовой модели HSL....................................................................................................... 216

Компоненты TColorPicker и TColorQuad...................................................................................... 216

Цветовые полосы THueTrackВar, TAlphaTrackВar и TBWТrackBar............................................... 217

Градиентная заливка TGradientEdit......................................................................................................... 219

Глава 16. Двухмерная rрафика............................................................................ 222

Управление холстом................................................................................................................................... 223

Кисть TBrush................................................................................................................................................. 224

Внешний вид линий.................................................................................................................................... 226

Шрифт TFont................................................................................................................................................ 227

Заливка замкнутых областей.................................................................................................................... 228

Вывод простейших фигур......................................................................................................................... 229

Траектория TPathData....................................................................................................................... 230

Вывод текста................................................................................................................................................. 232

Отображение рисунков.............................................................................................................................. 233

Отсечение....................................................................................................................................................... 234

Сохранение и восстановление состояния холста................................................................................ 234

Работа с растровой графикой, класс TВitmap...................................................................................... 235

Загрузка и сохранение изображения.............................................................................................. 235

Кодирование и декодирование графических форматов............................................................ 236

Получение миниатюры изображения............................................................................................. 236

Свойства изображения....................................................................................................................... 237

Простые манипуляции графическим образом............................................................................. 237

Редактирование битового образа.................................................................................................... 238

Управление графической производительностью................................................................................. 239

Глава 17. Графические эффекты......................................................................... 240

Применение эффекта к файлам изображений...................................................................................... 242

Применение нескольких эффектов к файлам изображений............................................................. 243

Простейшие корректирующие эффекты................................................................................................ 246

Заливка цветом TFillEjfect и TFillRGBE.ffect............................................................................... 246

Яркость и контрастность TContrastEjfect..................................................................................... 246

Регулировка опенка цвета THueAdjustE.ffect.............................................................................. 247

ЯснаяТВlоотЕjfесt и пасмурная TGloomEjfect погода............................................................. 247

Прозрачность TColorKeyAlphaEjfect............................................................................................... 247

Эффекты размытия и искажения·........................................................................................................... 248

Размытие................................................................................................................................................ 248

Искажения............................................................................................................................................. 249

Вертикальные полосы TBandsE.ffect.................................................................................... 249

Водоворот TSwirlEjfect и TBandedSwirlEjfect..................................................................... 250

Увеличительное стекло TMagnifyE.ffect и TSmoothMagnifyEjfect.................................. 251

Стягивание области TPinchEjfect.......................................................................................... 252

Рябь на воде TRippleEjfect....................................................................................................... 253

Волны TWaveEjfect................................................................................................................... 254

Горизонтальная деформация краев текстуры TWrapE.ffect........................................... 254

Аддитивные эффекты................................................................................................................................. 254

Отражение TReflectionEjfect.............................................................................................................. 254

Эффекты свечения TGlowE.ffect и ТlnnerGlowE.ffect.................................................................. 255


в                                                                                                                   Оглавление

Тень TShadowEffect .........................................................'................................................................ 255

Эффект тиснения TEmbossEffect.................................................................................................... 255

Набросок на бумаге TPaperSketchEffect........................................................................................ 256

Карандашный набросок TPencilStrokeEffect................................................................................ 256

Пикселизация TPixelateEffect.......................................................................................................... 257

Старая фотография TSepiaEffect..................................................................................................... 257

Управление резкостью TSharpenEjfect......................................................................................... 258

Глубина цвета ТТoonEjfect............................................................................................................... 258

Геометрические эффекты.......................................................................................................................... 258

Аффинные преобразования TAffineTransformEffect.................................................................. 258

Обрезка TCropEffect.......................................................................................................................... 258

Перспектива TPerspectiveTransformEffect..........................................................................259 

Эффект плитки ТТilerEffect ................................_............................................................................ 260

Наложение изображений ТNormalВlendEffect............................................................................ 260

Эффекты трансляции................................................................................................................................. 261

Глава 18. Анимация............................................................................................. 263

Простой пример анимации ..,................................................................................................................... 263

Общие черты компонентов-аниматоров, класс TAnimation............................................................. 265

Индивидуальные особенности компонентов-аниматоров...................................................... 267

Цветовая анимация, компонент TColorAnimation........................................................... 268

Градиентная анимация, компонент TGradientAnimation............................................... 268

Анимированная картинка, компонент TВitmapAnimation............................................. 268

Анимированный ряд, компонент TВitmapListAnimation................................................ 268

Анимация числовых свойств, компонент TFloatAnimation........................................... 269

Анимация прямоугольной области, компонент TRectAnimation.................................. 269

Анимация траектории, компонент TPathAnimation........................................................ 269

Глава 19. Мультимедиа........................................................................................ 271

Воспроизведение мультимедиа.............................................................................................................. 271

Менеджер кодеков TMediaCodecManager................................................................................... 271

Проигрыватель TMediaPlayer и компонент TMediaPlayerControl......................................... 273

Захват аудио- и видеопотока...................................................,............................................................... 275

Менеджер устройств TCaptureDeviceManager........................................................................... 275

Захват потоков мультимедиа.......................................................................................................... 276

Аудиозахват TAudioCaptureDevice····································:··..····..···................................ 277

Видеозахват TVideoCaptureDevice................................................................................................. 277

Камера TCameraComponent............................................................................................................. 280

Глава 20. Сенсорный ввод................................................................................... 281

Описание жеста........................................................................................................................................... 281

Реакция на сенсорный ввод...................................................................................................................... 283

Интерактивные жесты............................................................................................................................... 285

Пример обработки-стандартных жестов............................................................................................... 286

Глава 21. InterBase ToGo...................................................................................... 287

Соединение с БД TSQLConnection......................................................................................................... 288

Управление соединением................................................................................................................. 288

Регистрация пользователя................................................................................................................ 290


Управление подчиненными наборами данных........................................................................... 291

Управление транзакциями............................................................................................................... 29 l

Выполнение SQL-инструкци:й........................................................................................................ 292

Информирование о БД...................................................................................................................... 293

Набор данных TSQLDataSet...................................................................................................................... 293

Хранимая процедура TSQLStoredProc.................................................................................................... 296

Запрос TSQLQuery....................................................................................................................................... 299

Выпуск приложения.................................................................................................................................... 301

Глава 22. LiveBindings........................................................................................... 303

Визуальный дизайнер ..............;···............................................................................................................ 305

LiveBindings в проектах баз данн:ых............................................................................................. 308

Binding Expressions -  связь с помощью выражений........................................................................ 31О

Класс TBindExpression........................................................................................................................ 313

Выражение LiveBindings................................................................................................................... 315

Класс TBindings.................................................................................................................................... 317

Lists- связь между списками............................................................................................................... 319

Класс TBindList.................................................................................................................................... 322

Глава 23. Многопоточные приложения............................................................... 323

Поток ТГhread.............................................................................................................................................. 323

Метод ожидания.................................................................................................................................. 328

Управление приоритетом потока................................................................................................... 329

Синхронный и асинхронный вызовы внешнего метода........................................................... 330

Пример многопоточного приложения........................................................................................... 330

Синхронизация потоков в Windows........................................................................................................ 332

Синхронизация событием TEvent................................................................................................... 333

Критическая секция TCritica/Section.............................................................................................. 336

Мьютекс TMutex................................................................................................................................ 337

Семафор TSemaphore......................................................................................................................... 338

Глава 24. Мультиязычные проекты.................................................................... 341

Компонент языковой поддержки TLang................................................................................................ 341

Режим автоматического перевода.................................................................................................. 345

Перевод меню............................................................................................................................................... 345

Глава 25. Мобильная платформа......................................................................... 347

Интернет-браузер TWebBrowser ............................................,............................................................... 348

Привязка к местности................................................................................................................................. 350

Датчик местоположения TLocationSensor..................................................................................... 350

Прямое и обратное преобразования координат TGeocoder..................................................... 352

Датчик ориентирования TOrientationSensor.......................................................................................... 356

Менеджер датчиков TSensorManager...................................................................................................... 358

Увеличительное стекло TMagnifierGlass................................................................................................ 360

Подсистема уведомлений......................................................................................................................... 36 l

Пример вывода текстового уведомления в назначенное время............................................. 364

Вызов приложения из окна уведомления...................................................................................... 365

Пример размещения числа на значке приложения.................................................................... 366

Звонок по телефону..................................................................................................................................... 367


10                                                                                                                                                      Оглавление

Глава 26. Законы трехмерного мира.................................................................. 369

Система координат.................................................................................................................................... 369

Единица измерения..................................................................................................................................... 371

Точка.............................................................................................................................................................. 372

Вектор............................................................................................................................................................ 372

Объект............................................................................................................................................................ 373

Фрейм............................................................................................................................................................. 374

Проекция....................................................................................................................................................... 375

Глава 27. Проектируем ЗD-сцены........................................................................ 378

Построение сцены....................................................................................................................................... 378

Источник света, класс TLight................................................................................................................... 380

Камера, класс TCamera............................................................................................................................. 381

Объект-заместитель, класс TProxyObject.............................................................................................. 383

Макет, класс TDummy............................................................................................................................... 384

Глава 28. Геометрическое описание фигур и mеsЬ-объекты.............................. 385

Произвольный объект, классы TMesh и TMeshData.......................................................................... 385

Проектируем треугольник............................................................................................................... 388

· Проектируем тетраэдр...................................................................................................................... 389

Проектируем четырехУ :ольник..................................................................................................... 391

Управление нормалями вершин..................................................................................................... 393

3D-модель, класс TMode/ЗD..................................................................................................................... 394

Импорт модели во время выполнения программы.................................................................... 397

Глава 29. Матерцал объекта................................................................................ 398

Заливка цветом, компонент TColorMateria/Source............................................................................. 399

Текстурирование......................................................................................................................................... 399

Источник текстуры ТТextureMateria/Source................................................................................ 400

Управление координатами текстуры в TMesh............................................................................ 400

Отраженный свет и компонент TLightMateria/Source............................................................... 402

Дополнительная настройка текстур и класс ТТехtиrе.............................................................. 404

Глава 30. 3D-контекст TContextЗD....................................................................... 406

Управление графической сессией.......................................................................................................... 407

Графические примитивы класса TContextНelper................................................................................ 408

Графические примитивы класса TContext3D....................................................................................... 411

Освещение .............................,...................................................................................................................... 413

Матрицы и матричные преобразования................................................................................................ 414

Текстуры....................................................................................................................................................... 416

Шейдеры....................................................................................................................................................... 416

Приложение 1. Вектор TVectorЗD......................................................................... 419

Длина вектора.............................................................................................................................................. 420

Нормализация вектора.............................................................................................................................. 421

Проверка равенства двух векторов........................................................................................................ 421

Сложение и вычитание векторов............................................................................................................ 421

Расстояние между двумя векторами...................................................................................................... 422

Масштабирование вектора....................................................................................................................... 423


Векторное произведение............................................................................................................................ 423

Скалярное произведение............................................................................................................................ 424

Поворот вектора........................................................................................................................................... 426

Отражение вектора...................................................................................................................................... 426

Приложение 2. Матрица преобразований TMatrixЗD......................................... 428

Нулевая и единичная матрицы................................................................................................................. 429

Матрица переноса........................................................................................................................................ 429

Матрицы вращения..................................................................................................................................... 430

Матрица масштабирования........................................................................................................................ 431

Умножение матриц............................................................................................................................. 431

Дополнlffелъные матричные операции.................................................................................................. 433

Приложение 3. МодулЬ System.lOUtils.................................................................. 434

Приложение 4. Датчики······································································ 442

Приложение 5. Описание электронного архива.................................................. 445

Список литературы............................................................................................... 446

Предметный указатель......................................................................................... 447


 


 

 

 

 

 


Введение

 

 

Совсем недавно среда проектирования Embarcadero RAD Studio совершила очеред­ ной эволюционный скачок - в составе языков Delphi и C++Builder появилась принципиально новая возможность разработки кроссплатформенных приложений. Современные версии Delphi позволяют создавать не только приложения для Win32 и Win64, но и полноценные программные продукты, которые предназначены для работы под управлением операционных систем, разработанных компанией Apple (OS Х 10.7 Lion, OS Х 10.8 Mountain Lion, IOS начиная с версии 5.1) и компанией Google (речь об Android с диапазоном версий от 1.5 до 4.3)! В основу кроссплат­ формы положена во всех отношениях уникальная библиотека FireMonkey. Компа­ ния Embarcadero рекомендует называть "библиотеку FireMonkey" "платформой FM", но большинству читателей привычней использовать первый вариант, поэтому мы будем придерживаться в книге именно этого словосочетания.

Книга, которую вы держите в руках, в большей степени рассчитана на подготов­ ленного программиста, имеющего представление о языке и возможностях так на­ зываемых классических версий Delphi. Такого читателя в первую очередь интере­ сует ответ на единственный вопрос: "Чего такого нового появилось в языке Del­ phi ХЕ5, чтобы я заинтересовался им?" Постараюсь во введении к книге ответить на этот вопрос как можно более кратко и формализовано, разбив ответ на пункты.

   Библиотека VCL ни в коем случае не умерла и по-прежнему поддерживается

компанией Embarcadero, но по темпам своего развития (важнейшему показателю для претендующего на успех программного продукта) она замедлилась. Вряд ли раскрою вам секрет, утверждая, что в IТ-индустрии остановка равносильна смерти, а VCL движется все медленнее... Поэтому на смену VCL неотвратимо приходит инновационная по своей сути библиотека FireMonkey.

  FireMonkey- это по-настоящему кроссплатформенная библиотека, которая на данный момент поддерживает Win32, Win64, OS Х, IOS и Android. Очень важно,

что вы можете использовать один и тот же код для компиляции проекта как под Windows, так и под OS Х и Android!

   FireMonkey обладает непревзойденными·графическими возможностями и позво­

ляет создавать приложения, опирающиеся в первую очередь на DirectX, OpenGL и GDI+ (напомню, что проекты VCL изначально ориентированы на устаревший GDI).


 



14                                                                                                                                                           Введение

 

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

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

   Еще со времен Borland язык Delphi славился своим высокопроизводительным компилятором. Сегодня в Delphi ХЕ5 их шесть (Win32, Win64, OS Х, эмулятор iOS для х86.и iOS АRМ, Android). Важно отметить тот факт, что компиляторы iOS АRМ и Android модульные. Модуль фронтального (front-end) компилятора

АRМ переводит исходный код программы на язь1ке Delphi в промежуточный байт-код. Конечный модуль (back-end) компилятора представляет собой низко­ уровневую виртуальную машину (Low Level Virtual Machine, LLVM), преобра­ зующую промежуточный байт-код в машиннь1й код целевой платформы. Ком­ пиляторы LLУМ широко используются в компаниях Apple, Google и Adobe, что подсказывает направление дальнейшего развития Delphi.

   Язык Delphi всегда отличался своими возможностями по разработке приложе­ ний для БД. Библиотека FireMonkey вобрала все лучшее по работе с БД и под­ держивает все распространенные коммерческие и бесплатные системы управле­

ния данными. В этой книге мы поговорим об одной из новых разработок Embar­ cadero - настольной БД InterBase ToGo.

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


 

 

 

 

 

 

ГЛАВА 1

 

 

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

 

 

 

 

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

 

ЗАМЕЧАНИЕ

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

 

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

1.    FireMonkey Desktop Application для Windows и OS Х:

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

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

2.    FireMonkey Moblle Application для IOS.

3.    FireMonkey Moblle Application для Android.

Приложения FireMonkey НD Application позволяют создавать программные про­ дукты с высококачественным двухмернь м графическим интерфейсом для операци­ онных систем Windows и OS Х. ПрQекты HD в первую очередь окажутся востребо­

ваны в качестве б,изнес-приложений (прикладное программное обеспечение и кли­

ентские приложения баз дан»ых). С некоторой степенью допущения возможности FireMonkey НD Application можно сравнить с традиционными проектами VCL. Хо­ тя о знаке равенства между этими платформами речи идти не может. Взвешивая


 


 

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

.

 

 

 

 

 

 

 

 

 

 

 

 

 

Cuadro de texto: WnAPI	Графический механизм
	
1
OirectX	GOI+
1

Cuadro de texto: Cartюn API	
Сосоа API	Графический механизм
		
OpenGL	Quartz
1

 

 

 

 

Виртуальная

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

1

машина 0alvik

0    nGL     I      Surface

ре           !  Manager

 

 

 

Рис. 1.1. Платформа FireMonkey

 

 

ЗАМЕЧАНИЕ

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

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


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

 

ЗАМЕЧАНИЕ

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

·этому нельзя исключить, что в последующих версиях библиотеки она позволит рабо­ тать и в этом сегменте ПО.

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

 

Выбор типа приложения

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


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


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

 

Выбор целевой платформы для проекта

Раз основная заслуга FireMonkey заключается в поддержке не только Windows, но и OS Х, то изучение приложения FireMonkey начнем с определения целевой плат­ формы для реализации приложения.

Создайте  новый проект. Для  этого воспользуйтесь пунктом  меню File I  New 1

FireMonkey Desktop Application.

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

 

 

 

 

 

 

 


 

Proj«tl.dproj - Praject М•

 

--      ·-·-·-----·-- -

Ъ)3 Тt:э 1 11

Fh

DProje(.tGraцH

е 6iJProjt!Ct1


 

 

1 4


-о -


 

---


 

 

•••     1: 1         "-t


004 аА1с1 Configuratians (Del)uQ}

S O Tiw,;etl'letforms (OSX32)

· 1'132.МWhdows

s

 
64-hlt Wi'ldow$

1         llosx

 
е t:i Conf\iµ'•aon

W ,t,pgla11on SIDre

!& U11tl,pa


 

 

 

 


Рис. 1.3. Выбор целевой nлатформы для nроекта FlreMonkey


Выпуск приложения для OS Х

Выпуск приложения для 32- и 64-разрядной Windows не вызывает никаких затруд­ нений. Программист указывает предпочтительную платформу и просто нажимает клавишу <F9>. Если же вы планируете создать релиз для OS Х, то придется еще немного потрудиться.

1.    По возможности полностью отладьте приложение под управлением Windows (это возможно, если вы не используете функции API OS Х). Это исключит мно­ гие проблемы при создании исполняемого бинарного кода под OS Х и в целом ускорит работу над выпуском релиза.

2.    Соедините в сети два компьютера. На первом должна быть установлена опера­ ционная система Windows и развернуто ваше программное обеспечение Delphi. Второй компьютер должен работать под управлением операционной системы

osx.

3.    Обращаемся к компьютеру с FireMonkey. Найдите в каталоге C:\Program Files\ Embarcadero\RAD Studio\n.n\PAServer (или C:\Program Files (x86)\Embarcadero\ RAD Studio\n.n\PAServer, если вы работаете в 64-разрядной Windows) файл RADPAServerXE5.pkg версиях ХЕ2 и ХЕ3 это был архив setup_paserver.zip, в ХЕ4- RADPAServerXE4.pkg). Можете перенести файл на МАС с помощью флешки, а лучше всего предоставьте к данному каталогу сетевой доступ, так чтобы папку с файлом смогла увидеть станция Мае (рис. 1.4).


Рис. 1.4. Сетевой доступ к файлам папки PAServer со станции Мае

 

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

5.    Запустите в терминальном окне компьютера с OS Х установленное программное обеспечение (рис. 1.5).


Рис. 1.5. Запуск RAD PAServer XES в терминальном окне

 

6.    Возвращаемся к компьютеру с Windows. В менеджере проектов Delphi создайте целевую платформу (Target Platforms) OS Х и, дважды щелкнув по узлу, сде­ лайте ее активной (см. рис. 1.3).

7.    Нажмите "священную" для программиста Delphi клавишу <F9>. И если с актив­ ной платформой OS Х это делается впервые, то перед вами появится окно мас­ тера создания профиля (рис. 1.6). Придумайте для профиля имя, введите IР-адрес станции Мае, при желании поменяйте номер порта и (если вы верите в теорию заговора) придумайте пароль. Нажав кнопку Test Connection, протес­ тируйте соединение и завершите работу помощника, нажав кнопку Finish. На­ строив профиль, вновь "давим" <F9> - в ответ вы увидите свое первое прило­ жение для Мае (рис. 1.7). Откомпилированное приложение для платформы OS Х вы обнаружите на станции Мае в папке /Users/uмя_noлыoвameля/RADPAServer/ scratch-/uмя_профайла.

 

ВНИМАНИЕ!

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

 

 



 


(:} с,,..,,•Conn,ct,on P;;,file

 

 

1--

.1                 1181IIIZlrdМ ..-iYoUа Cl'N comкtio,\ profle. ComecЬon pro•aretequred :nordt, to

r          drdov and n.l't project on а /'О'№ rnactw. Мqс: юfum;.,Wf! виfrо Р\т-а:

11

11

!

 

1

 

1

10

Cr •t а Connectюn Profi;e

Reмot мadliм. tiof-t ------------------------------------------------------------------------------------------ --.

C)edfyci the:rtmoteroadWNll'Dr.ot hretrott- ';:IP Acom«ьonp-oflt­

,,_,,..,,._,,..,_c,,getpotfo<mo,yy-PASo<-,w ";,stoled,nd""'11ngonll,c,_.,

mad'll,e. ма  vЩrm:ФonооМО1!!9 P№:tm:

 

&emc,tl:_(IP_r,_,,..,.),

192,168.0,1

 

 
i

r

 


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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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


 

ЗАМЕЧАНИЕ

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

 

Выпуск приложения для iOS Moblle

Для выпуска приложения, предназначенного для работы под управлением мобиль­ ной платформы IOS, нам вновь потребуется рабочая станция Мае с развернутой на ней операционной системой OS Х 10.7 Lion или OS Х 10.8 Mountain Lion. Мобиль­ ное устройство операционной системой IOS 5.1 и выше) стоит подключить к компьютеру Мае через USB-nopт, для того чтобы ускорить процесс тестирования приложения.

Если вы намерены испытать свои силы в разработке приложений для мобильной платформы IOS MoЬile, то в самом начале пути для настройки компьютеров реко­

мендую воспользоваться услугами специализированного помощника. Для его вы­ зова достаточно, обратившись к пункту меню File I New I FireMonkey Moblle Application, создать свое первое мобильное приложение. После того как вы выбе­

рите шаблон приложения и укажете папку, в которую следует сохранить файлы проекта, среда разработки выведет на экран окно iOS Moblle Help Wizard.

С этого момента всеми остальными вашими действиями станет руководить помощ­ ник. Во-первых, он попросит вас установить на станции Мае среду разработки Xcode, позволяющую разрабатывать приложения для Мае, iPhone и iPad. На момент написания этих строк программное обеспечение Xcode 5 можно было свободно скачать со страницы https://developer.apple.com/xcode/ компании Apple.

Во-вторых, вам придется развернуть Xcode Cornrnand Line Tools. Для этого следует запустить установленное на предыдущем этапе программное обеспечение Xcode, выбрать пункт меню Preferences и щелкнуть по расположенной на главной панели команде Downloads. В окне загрузок выбираем вкладку Components и щелкаем по кнопке Install.

 

ВНИМАНИЕ/

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

 

В-третьих, вам следует развернуть на станции Мае программу-ассистент Platform Assistant, которую вы обнаружите на жестком диске компьютера с Ernbarcadero RAD Studio XES. Интересующий нас файл RADPAServerXES.pkg расположен в папке C:\Prograrn Files\Embarcadero\RAD Studio\n.n\PAServer\. Завершив инстал­ ляцию программы-ассистента, наЙдите в папке Applications приложение RAD PAServer XES.app и запустите его на выполнение.

В-четвертых, вам предстоит соединить компьютер с Ernbarcadero RAD Studio XES и станцию Мае и сконфигурировать профиль соединения (connection profile). Для вы­ зова мастера конфигурации обратитесь к IDE Delphi и выберите пункт меню Tools 1


 

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

 

Выпуск приложения для Android

Процесс выпуска приложения для операционной системы Android сравнении с работой с iOS) имеет ряд существенных отличий, ведь на этот раз нам предстоит работать с операционной системой, разработанной не в компании Мае, а в Google.

Если вы планируете написать приложение для конкретного мобильного устройства, то целесообразно установить на ваш персональный компьютер USВ-драйвер этого смартфона или планшетного компьютера. Все необходимые драйверы вы обнару­ жите в Интернете, в первую очередь рекомендую ресурсы:

http://developer.android.com/sdk/win-usb.html https://developer.amazon.com/sdk/fire/connect-adb.html http://developer.android.com/tools/extras/oem-usb.html

Если же вы нацелены на разработку ПО для разнотипных устройств, то запустите Android SDK Manager (кнопка Пуск I Все программы I Embarcadero RAD Studio I Android Tools) и отметьте "галочкой" в окне менеджера строку Google USB Driver (рис. 1.8). Щелчок по кнопке Install packages автоматически

отправит запрос на соотве!ствующий сайт, с которого будет осуществлено бес­ платное скачивание драйверов.


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


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

1.    Установите на компьютер образ системы Android (Android system image). Для этого:

        нажмите  кнопку  Пуск  Windows  и  выберите  пункт  Все проrраммы 1

Embarcadero RAD Studio ХЕ5 1 Android Tools;

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

 

 

 

 

 

 

 

f f н  ;1_<!! 1' ,.._

i SOI( Plth: - •f,:_blк-.W.vм '..JJ.At"JStuФб".i.J. Pt ..-5€Ж$',<tlrt- -,...t\".М' -Jf/6-X113C .21'is,di

! Pa.c.Jo:196


,,t, №me

Tooi,


 

 

 

 

 

 

J Andro16 SDKLktn$C:


АР!


 

 

 

 

 

-

 
P-кfage Descriptlon 81. Li«мt


у' ДncffoidWkTeok, ,.-лtiorтU.D.S

у" д11dтс,d SO,: PlattarrrH , 1ion

Oocume-,t.t:ion fOJ t..rнkoid SDK, АР! SOJ; fo.rm.дti(ltoid4.3.AP11S,'C\I\ v:- l«sforSDKдPl!!, J

V ММ[AВlv71Sya-emtm.ge.д.Мt-CIO

..,- Googlt.;$ Android /Jlfll, tmsюn

,,1" Sou1c.e-:f1YAndroed.SIЖ.APl1В,,eoм

.,,/ дndtoкl Support Lihr-Ny. ,                     18

.,/GooфttSIOriv«,fМ'OOf'l8

h'кdдм  LКеtШ

!ntd.S Atorn S)lltem lm;,gt, droll


- Aцdf"OIO SОК Tools, 1wi\ion 12.О

.Aмkoid stж Pi,ttom,-took, lМЧ/1'1,18.0J

Ooc:1.1merrt•ticn for Anctro1d SOt:;. дPIJI, revиn1

, 'S1Ж Pilrtorrn, Androicf 43, АР!11. ision1

-s.,npiк forsm: APJU, f'Мlon1

-AR.MEA611171 em&N9t.AndtoidдAJ&_ф"$Cl!,1

· Gocig APls,AmfrwAPJ18, 1

•Soufo;;esfo,AndrotdSDt(.APlli, 1

· Andtoid 5црро11 lO-,y, rr,,mon18

usв 

 

 

This isthtAndюid'SoftweaOt11do l(it Lkenи t.


 

 

 

 

 

 

 


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

 

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

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


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

Tools I Manage АVDs;

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


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

 

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

 

ЗАМЕЧАНИЕ

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


fi Android Virtllat Devicc МАмgеr

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

 
Android Virtuol Devicos Device Definiticюs

List of msting Android Virtual Devicos located 11 C:\Users\dmn\.1ndroid\avd

AVD№mo

v' AVD_дndroid_...

Target№me Android 4.2.2.

Pl1tform 4.2.2

APILcvel

17

СРU/АВ!

ARM (1rmcabi•v..,

 

(:;;;AVD 4.3

Android43

                            4.3                    

18                       

ARM (armeaЫ-v•..

,,;1 А valid Android Virtual Device.           А repoiraЫe Android Virtual Device.

Х An Android Virtual Devicethat fail•d to lood. Click 'Dct1ils' to seethc error.

 

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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


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

 

Что делать, когда код зависит от платформы?

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


prooedure TFoпnl.FoпnShow(Sender: TObject);

Ьegin

{$IFDEF MSWINDOWS}

//код дпя операционной системы Windows Labell.Text:='Hello,  Windows!';

{$ELSE} {$IFDEF МАСОS}

//код дпя операционной системы OS Х Labell.Text:='Hello,  Мае!';

{ $ELSE} { $IFDEF IOS}

//код дпя мобильной платформы iOS Labell.Text:='Hello, iPhone  (iPad) ! ';

{$ELSE} {$IFDEF ANDROID}

//код дпя мобильной платформы Android Labell.Text:='Hello,  Android! ';

{$ENDIF}

{$ENDIF}

{$ENDIF}

{$ENDIF}

end;

 

 

Во время компиляции вашего приложения Delphi разберется с подключением к проекту необходимых библиотек и создаст бинарный код для требуемой опера­ ционной системы.

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


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


procedure TForml.FormShow(Sender: TObject);

var s:string;

Ьegin

//                                                              os

case TOSVersion.Platform of


pfWindows pfМacOS pfiOS pfAndroid pfWinRT pfLinux

end;


s:='Windows';

s :='OS Х';

s:=' ios•·;

s : = 'Android' ;

s:='Win RT';

s:='Linux';


LаЬеll.Теft:='Операционная система '+s; Label2.Text:=TOSVersion.ToString;

 

//                                                  CPU

case TOSVersion.Architecture of

TOSVersion.TArchitecture.arintelXBб     s:='IntelX86';


TOSVersion.TArchitecture.arintelX64 TOSVersion.TArchitecture.arARМ32

else s:='Неопределена';

end;


s:='IntelX64'; s:='ARМ32';


LаЬеlЗ.Техt:='Архитектура процессора '+s; Label4.Text:=Format('Ядep %d', [ TThread.ProcessorCount]); end;

 

Если речь идет о мобильной платформе IOS, то для получения сведений об устрой­ стве и его ОС можно воспользоваться услугами API операционной системы, под­ ключив к проекту модуль iOSapi.urкit (листинг 1.3).

 

/.. и8'Имr 1.3.

uses iOSapi.UIKit;

{$R*.fm.x}

 

procedure TForml.FormShow(Sender: TObject);

var MobileDevice: UIDevice; s:string;

Ьegin

MobileDevice := TUIDevice.Wrap(TUIDevice.OCClass.currentDevice);


 


case MobileDevice.userinterfaceidiom of UIUserinterfaceidiomPhone: s:='iPhone'; UIUserinterfaceidiomPad        s: =' i·Pad' ; else s:='Other';

end;

LаЬеll.Техt:='Устройство '+s;

Label2.Text .-  Format('OC: %s %s',  [MobileDevice.systemName.UTF8String,

MobileDevice.systemVersion.UTF8String]);

end;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 



 

 

ГЛАВА 2

 

 

Забываем VCL?

 

 

 

 

Идея платформы FireMonkey основана на и.цее объектно-ориентированного про­ граммирования (ООП), поэтому изучение недр FireMonkey стоит начать с обзора опорных классов библиотеки. И здесь нас ждет первый сюрприз. Одного взгляда на иерархию наследования окажется достаточно для того, чтобы понять, что новейшая библиотека проектировалась не с "нуля". В начале цепочки наследования вы обна­ ружите хорошо знакомые по классической библиотеке VCL классы тоьjесt, TPersistent и, конечно же, тcomponent (рис. 2.1), и только затем, на четвертой сту­ пени иерархии вам встретится первый "кроссплатформенный" класс TFmxObj ect. Так что даже если вы решили окончательно и бесповоротно перейти на разработке своих приложений исключительно на FireMonk y, классическую библиотеку VCL вам забыть не удастся!      ·

Вернемся к VCL. На рис. 2.1 представлен фрагмент иерархии наследования классов Delphi. Здесь вы обнаружите наиболее важные классы платформ VCL и FireMonkey (FМХ). Почему поставлен акцент на слово "важные"? Потому что именно эти клас­ сы (за счет абстрагирования, инкапсуляции, наследования и полиморфизма и дру­ гих механизмов, составляющих концепцию ООП) определяют основные родовые черты своих многочисленных кроссплатформенных потомков. Изучив опорные классы, мы получим представление о ключевых особенностях всех классов библио­ теки.

 

ВНИМАНИЕ!

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

 

Опорный класс VCL -  TObject

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


 

 

 

(А)

ТОЬjесt

(System)

 

l

 
Q)

 

 

Q)

Q)

ф

TPersistent (Clasвes)

 

р

 
3:

 

....,

 

 


 

 

Платформа VCL Платформа FMX

 

 

Контейнер д.1Я юображе1111й, об.1асть для рис,юания, 11рю,итив11ые граф11ческие фигуры, траектория


 

 

 

 

 

Глаsиоеи

нrплывающ е ,.\l(!Ш()


TComponent (Classes)

 

 

 

TFmxObject

(FMXTypes)


 

 

 

 

ТApplication (FMX.Forms)

 

 

 

TControlЗD

(FMX.TypesЗD)

 
TCommonCustom Form (FMX.Forms)


 


 

TControl (FMX.Controls)

 
Ко.ибш,ировшшые с11иски, б,101,,,·1ют, калеидари, naueJlu, и11струлfентш,ьные 1иа11ки, блокноты

 

 

ТТextControl (FMX.Controls)

 
Метки, к11опю, выбора, фла:жжи, об.шсти группировки


TStyledControl (FMX.Controls)

 

 

 

Cuadro de texto: TCustomEdit (FMX.Edit)

 

 

 

TCustomForm (FMX.Forms)

 
Сm,ски, .1111огострочный Строки ввода

текстовый редактор.

дерево, сетки,


 

 

 

 

ФорАtыРМХ


KDAиw11eJ1mы

 

TCustomFormЗD (FMX.FormsЗD)

 
ЗD,    _.

TEffect (FMX.Filter.Effects)

 

 

TCustomButton (FMX.StdCtrls}

 
В11зуа.1ьные эффекты


 

К11011ки

 


Рис. 2.1. Иерархия наследования VCL-FMX в Delphi ХЕ5


.(.А...)



К задачам, решаемым классом тоьject, в первую очередь стоит отнести:

    создание, поддержку и уничтожение объекта; распределение, инициализацию и освобождение памяти, необходимой для этого объекта;

    возврат всей информации .об экземпляре класса, в том числе данных RTТI об опубликованных свойствах и методах объекта;

    поддержку взаимодействия объекта с внешней средой с помощью сообщений и

интерфейсов.

 

Управление жизненным циклом объекта

Исследование вклада класса 1:0bject в классы VCL начнем с конструктора и дест­ руктора. Это наиболее важные методы для любого из класса VCL - они управляют существованием объекта.

constructor Create;  //создание  объекта

destructor Destroy; virtual;  //разрушение объекта

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

· более поздних классах методы усложнятся и дополнятся параметрами.

Вам наверняка приходилось размещать на форме проекта Delphi различные компо­ ненты (среди них кнопки, строки ввода, метки). Это очень удобно: для создания элемента управления достаточно взять компонент с палитры и положить его на форму. А вы пробовали создавать элементы управления и размещать их на форме, не обращаясь к палитре компонентов? Если нет, то следующий пример окажется интересным.

ыбрав пункт меню File I New I FireMonkey Desktop Application, создайте новое приложение HD. Убедитесь, что в строке uses проекта упомянут модуль FМX.StdCtrls (в нем расположен код стандартных элементов управления). В переч­ не событий формы Fonnl (на вкладке Events Инспектора объектов) найдите собы­ тие onshow. Двойной щелчок левой кнопкой мыши в строке напротив названия со­ бытия заставит Delphi создать код обработчика события, вызываемого в момент показа формы на экране. Вам осталось лишь повторить несколько строк кода, про­ веденных в листинге 2.1.


,; Rис-тинr 2.1. .....,..,.....,.•••••                                            s

······-·· ··········•~ - ..................""'·••·····"···· ..,,.;. ·······•·•·•·•····......··-···· "      ............ ,,._._,,     .............................,.......,.ь,.,................. А,' ···········"·••'>•;·············

uses ... , FМX.StdCtrls;

 

procedure TFonnl.FonnShow(Sender: TObject);

var Btn: TButton; //переменная класса кнопки TButton Ьegin

Btn:=TButton.Create(Fonnl); //вьвов конструктора класса TButton Btn.Parent:=Fonnl;                                                               //размещаем кнопку на форме

end;


 

После запуска приложения в левом верхнем углу формы появится кнопка. Она воз­ никла не из воздуха, это следствие вызова конструктора create () для экземпляра класса твutton.

Обязательно проведите небольшой эксперимент - замените в коде листинга 2.1 класс твutton на TEdit (или ТМеmо, TLabel, тсоmЬоВох). Это придется сделать в двух местах кода события onshow ( ) : в секции локальных переменных и в строке вызова конструктора. Вновь запустите проект на выполнение. В результате на свет появит­ ся элемент управления именно того класса, который вы указали. Как видите, унас­ ледованный от тоьject конструктор выполняет свою задачу во всей иерархии клас­ сов.

пишите всего одну строку кода (листинг 2.2).

 
Проведем еще один пример. На этот раз он посвящен исследованию деструктора класса - методу Destroy (). Для опыта нам понадобится помощь нового проекта НD Application. Разместите две кнопки твutton (вы их найдете на странице Standard палитры компонентов) на форме. Научим одну из кнопок уничтожать своего "коллегу". Для этого дважды щелкните левой кнопкой мыши по кнопке Buttonl и в появившемся в окне редактора кода обработчике события OnClick на­

-•••"'•·-•...,..:.••-u::• ..............,,,q.,•P\'1',IY--•••·--•• ,,_.,,,.........,...... J-r ...............,••,,.,. •"'"-НJ""'''' f-•-•-4••••.. •t,!,::,C•fH•••·•,•.•• JV,-" •••••< ••" r ,                            s1'   ;, .-1,<  'f-0  -1

.......... «.................,, •• .................. .....                                                                                                                                                     ..................--.,--,-·-·--·w.е--.--'<"..д ·"""-........,..,i,,..............,,,.,...-   .............,.,......... i:

procedure TForml.ButtonlClick(Sender: TObject);

Ьegin

Button2.Destroy;  //уничтожение  экземпляра кнопки Button2

end;

 

 

Запустив проект, щелкните по кнопке вuttonl. Это действие вызовет деструктор объекта Button2, и элемент управления исчезнет с поверхности формы. Но на этом эксперимент не завершен. Вновь щелкните по кнопке Buttonl. В ответ разгневан­ ный отладчик Delphi выведет на экран примерно такое сообщение, как на рис. 2.2.

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

·  Destroy () для уничтожения экземпляра объекта следует использовать метод

procedure Free;

 

Debuggt!r Exc_eption Notification

 

 

Pro t Project:1.exe rl!ised exceplion dass $COOOOOOS wi!h message 'access lliolaltion at Ox00at0dle3: reвd af

address ОхОООООООО'.

 

 

CJ lgnor!! this excepiion !,уре                                                                                    о--    JI        Jl        J

Рис. 2.2. Сообщение об ошибке доступа EAccessViolation к несуществующему объекту


Эго более осторожный метод. Перед тем как вызвать деструктор объекта, он убе­ дится в факте существования этого объекта. Для проверки возможностей метода замените в листинге 2.2 обращение к деструктору Destroy на Free и вновь повтори­ те опыт. На этот раз повторный щелчок по кнопке не приведет к исключительной ситуации.

 

Механизм учета ссылок в мобильных проектах

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

Благодаря подсчету ссьшок существенно расширяются наши возможности по управлению объектом. В простейшем случае об уничтожении локальных объектов можно просто не думать - они удалятся сами. При необходимости программист имеет возможность самостоятельно уменьшить счетчик ссьmок. В современной версии Delphi ХЕ5 вы имеете возможность прекратить использовать этот объект, не дожидаясь его выхода за область видимости (листинг 2.3).

 

ЗАМЕЧАНИЕ

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


XE50bject:=TXE50bject.Create(); XE50bject.XE50bjectMethod; XE50bject:=nil;

 

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

 

ВНИМАНИЕ!

В новом компиляторе с поддержкой ARC деструкторы Destroy () , Free () и FreeAndNill () не смогут явным образом удалить объект, а лишь уменьшат счетчик ссылок. Таким образом, в мобильных проектах вместо вызова классических деструк­ торов достаточно обнулить ссылку - присвоив объекту nil.


 

У всех потомков тоьject предусмотрено доступное только для чтения свойство

property RefCount: Integer; //только дпя чтения

с помощью которого мы сможем узнать число ссылок на объект.

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

procedure DisposeOf;

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

property Disposed:  Вооlеаn;//только дпя чтения

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

 

Информирование о классе

В составе класса тоьject имеется ряд полнофункциональных обычных методов .и методов класса (class function), позволяющих получить подробную метаинформа_­ цию о классе. Ключевые методы тоьjесt представлены в табл. 2.1.

 

ЗАМЕЧАНИЕ

Методы класса разрешено вызывать без создания экземпляра самого класса.

 

Таблица 2.1. Получение сведений о классе

 

Метод

Описание

class function ClassName: string;

Функции возвратят текстовую строку с названием класса

function ToString: string;

class function UnitName: string;

Имя модуля, в котором описан класс

function ClassType: TClass;

Возвратит описание класса

class function InstanceSize: Longint; inline;

Размер в байтах, необходимый для создания экземпляра класса

class function ClassNameis(const Name: string): Boolean;

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

class function InheritsFrom(AClass: TClass-) : Boolean;

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

AClass

class function ClassParent: TClass;

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


 

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

 

Метод

Описание

class function MethodName(Address: Pointer): string;

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

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

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

function FieldAddress(const Name: string): Pointer;

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

 

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


var C:TClass;

Ьegin C:=Memol.ClassType; while C<>nil do Ьegin

Memol.Lines.Add(C.ClassName+' -> '+C.UnitName);

C:=C.ClassParent;

end; end;

 

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

 

-ТМ-еm-о  ·>-FМ-Х.Моmо

Т&:tollВox ·> FМX.Layou1$ ТS!yledConlrol-> FМX.Controls ТConlrol->FМX.Conltols

ТFmxOЬject ·> FМХ.Туреа TComponent ·> System.Classes ТP01$1sten1 •> System.Cluses TT)t)jec;t•>System

 

 

 

 

 

 

 

 

 

·-      

 

 

..          .

 

-·-

·-

 

J

 

 

                                                                                                               '

 

 
•n.c.                           ---.-.


 

 

Рис. 2.3. Перечень предков класса 'ГМеmо

 

 

 



 

Класс TPersistent

Одну из наиболее значимых ветвей классов начинает ближайший сподвижник TObject- класс TPersistent (модуль System.Classes). Основное предназначение класса -' научить своих потомков работать с памятью, что позволит им загружать данные из области в памяти и выгружать данные в другую область.

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

procedure Assign(Source: TPersistent); virtual; procedure AssignTo(Dest: TPersistent); virtual;

Перечисленные процедуры позволяют разделить одну и ту же область памяти меж­ ду несколькими объектами -                                потомками TPersistent. Метод Assign () позволяет одному объекту подключиться к ресурсу другого, метод Assignтo () -                                          передать ре-· суре от одного компонента к-другому.

Для демонстрации этой полезной возможности создайте новый проект и разместите на его главной форме два компонента Timage ( они предназначены для хранения и отображения векторной и растровой графики) и кнопку твutton. Воспользовавшись свойством Bi tmap компонента Imagel, загрузите в контейнер любой файл с изобра­ жением.

 

ЗАМЕЧАНИЕ

После установки Embarcadero RAD Studio на компьютер файлы с рисунками вы обна­ ружите в каталоге c:\Program Files\Embarcadero\RAD Studio\x.x\lmages\, если вы рабо­ таете в 32-разрядной Windows, или в каталоге c:\Program Files (x86)\Embarcadero\RAD Studio\x.x\lmages\ при работе в 64-разрядной ОС.

Осталось написать одну строку кода в обработчике события OnClick кнопки вuttonl

(листинг 2.5).


procedure TForml.ButtonlClick(Sender: TObject);

Ьegin

Image2.Bitmap.Assign(Imagel.Bitmap);

end;

 

Щелчок по кнопке приведет к тому, что во втором компоненте Image2 отобразится та же самая картинка, что и в Imagel. Еще раз подчеркну, что метод Assign () не создал еще одну копию картинки, а разделил одну и ту же область памяти между двумя потомками класса TPersistent.

Надо понимать, что методы Assign () и Assign'Гo () позволяют разделять ресурс только между совместимыми компонентами, способными прочитать этот ресурс. Встретив даже такую, абсолютно абсурдную строку кода

Buttonl.Assign(Imagel.Bitmap);


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

Научив несколько объектов работать с общей областью памяти, класс TPersistent позаботился еще об одной важной детали - при необходимости любой из совме­ стно работающих объектов имеет право уйти в автономный режим рабо:rы, при этом не нарушая состояния своих коллег. Допустим, что у нас имеются текстовый редактор Memol : тмеmо, хранящий какой-то перечень текстовых строк, и его коллега меmо2 : тмеmо. После вызова строки кода

Memo2.Lines.Assign(Memol.Lines);

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

 

Основа компонента TComponent

Приступим к рассмотрению третьего по счету в цепочке наследования (см. рис. 2.1) класса - класса тcomponent. Наш очередной знакомый построен на основе только что изученного класса TPersistent (модуль System. Classes).

TComponent = class(TPersistent, IInterface, IInterfaceComponentReference)

 

Обсудим вклад тcomponent в библиотеки VCL и FireMonkey. Во-первых, от него бе­ рут начало все компоненты, которые вы найдет на палитре компонентов Delphi. Во-вторых, в логику класса заложено понятие "частной собственности". Другими словами, в тcomponent предусмотрено право владения, когда один объект выступает владельцем другого объекта. В-третьих, класс расширяет возможности по хране­ нию и обслуживанию данных. В-четвертых, в отличие от своих предков (опери­ рующих только методами) класс тcomponent обладает свойствами.

Самое главное свойство, подаренное тcomponent своим наследникам, -  имя.

property Name: TComponentName; // type TComponentName: string;

 

Каждый компонент, задействованный в проекте Delphi, обязан иметь уникальное имя. Имя присваивается автоматически в момент переноса элемента управления с палитры компонентов на поверхность рабочей формы проекта. Механизм выбора имени достаточно прост: берется имя класса (допустим, твutton), убирается первый символ "Т" и к окончанию имени добавляется порядковый номер элемента управ­ ления этого типа на форме. Так первая кнопка, размещенная на форме, получит на­ звание Buttonl, вторая -  вutton2 и т. д.

У каждого компонента опубликовано свойство, никогда не используемое системой

prope ty Tag: Longint;


 

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

 

Владение объектами

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

property Owner: TCornponent;

Для того чтобы любой компонент прямо в момент своего рождения приобре­ тал владельца на уровне класса тcornponent, несколько усовершенствован конст­ руктор

constructor Create(AOwner: TCornponent);

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

 

ВНИМАНИЕ!

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

Владелец компонентов имеет представление о числе принадлежащих ему компо­ нентов и в состоянии обратиться к каждому из них. Для этого предназначены соот­ ветствующие свойства:

property CornponentCount: Integer; //число компонентов

property Cornponents[Index: Integer]: TCornponent; //список компонентов Каждый компонент знает свой индекс в списке компонентов своего владельца: property Cornponent!ndex: Integer;

Ряд методов позволяет манипулировать списком компонентов, для которых тcontrol выступает владельцем. Удаление компонента из списка обеспечивается процедурой

prooedure RernoveCornponent(AComponent:  TComponent);

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

prooedure DestroyComponents;

Вставка в конец списка нового компонента:

procedure InsertCornponent(AComponent: TCornponent);


Поиск компонента-потомка в списке:

function FindComponent(oonst AName: stri.ng.): TComponent;

 

ЗАМЕЧАНИЕ

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

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


 

 

ГЛАВА 3

 

 

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

 

 

 

 

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

 

ЗАМЕЧАНИЕ

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

 

 

Обобщенный тип данных в полях записей

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


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


 

 



42                                                                                                                                                              Глввс1 З

 

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

type имя_типа_записи <имя_обобщенного_ типа [ , .•.  ] > = record

имя_поля:  имя_обобщенного_ти 1с1;

[Другие поля]

end;

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

 

ЗАМЕЧАНИЕ

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

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

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


type TGenericRec<T,R>= record

А

в,с

char;

Т;

//в любом случае это символьное поле

//поля обобщенного типа Т

D

R;

//поле обобщенного типа R

end;

 

 

 

var GRecl:TGenericRec<integer, douЫe>; //объявление переменной GRecl GRec2:TGenericRec<boolean,  string>;  //объявление  переменной  GRec2

Ьegin

GRecl.B:=15;            //поле В хранит целое значение

GRecl.D:=0.675; //поле D хранит вещественное значение

// ...

GRec2.B:=true; //поле в хранит булев тип данных

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

// ...

end.

 

 



 

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

 

Обобщения в процедурах и функциях

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

По аналогии с объявлением обычной процедуры или функции, при создании шаб­ лона процедуры или функции сначала указывают ключевое слово type, а затем имя типа. Сразу за именем сле·дует синтаксический элемент, характерный для обобще­ ний - пара угловых скобок <... > с названием обобщенного типа данных.

type тип_функции <имя_обобщенного_типа[, ... ]> =                                  ,

funсtiоn([параметр_обобщенного_типа  [, ...  ]]) :имя_обобщенного_типа;

type тип_процедуры <имя_обобщенного_типа [, ... ]>  =

procedure ([параметр_обобщенного_типа [, .. :  ] ]

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

 

uses System.SysUtils, System.Generics.Defaults; type TGetMax = class

class function GetMaxValue<T>(X, У: Т) :Т;

end;

 

class function TGetMax.GetMaxValue<T>(X, У: Т): Т;

var Comparer: IComparer<T>;

Ьegin

сожалению, нельзя написать такой код: if  Х>У th�n Result:=X else Result:=Y;

ведь мы не знаем тип данных сравниваемых параметров} Comparer:=TComparer<T>.Default;


44                                                           Глава 3

if Comparer.Compare(X,Y)>O then Result:=X

else Result:=Y;

end;

 

var А: integer;

B:extended;

C:ansichar;

Ьeg;i.n

A:=TGetMax.GetMaxValue<integer>(l0,20); //сравниваем целые числа Wri teLn (А) ;

 

B:=TGetMax.GetMaxValue<extended>(l/3,2/3); //вещественные числа WriteLn (В);

 

C:=TGetMax.GetMaxValue<ansichar>('A', 'В');//сравниваем символы WriteLn (С) ;

 

ReadLn;

end.

 

 

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

 

Обобщенные типы данных в шаблонах классов

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

type имя_класса<параметр_обобщенного_ типа [,  ...  ] > = class

//поля и методы

end;

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



unit generics_unit;

 


interface

type TGener�cClass<T>=class fT:T;

procedure SetT(Value:T);

function GetT:T;


//объявление шаблона класса

//поле обобщенного типа

//метод записи в поле

//метод чтения значения из поля


end; implementation

function TGenericClass<T>.GetT: Т;

Ьegin

Result:=fT; //узнаем содержимое поля

end;

 

procedure TGenericClass<T>.SetT(Value: Т);

Ьegin

fT:=Value;  //записываем данные в поле

end; end.

 

Для объявления параметра типа мы воспользовались символом т и заключили его в угловые скобки. Класс содержит единственное поле fT универсального типа т и два метода, позволяющих записывать и считывать значение из поля.

Разобравшись с объявлением и реализацией шаблона класса, способного работать с обобщенным типом данных, попробуем воспользоваться его услугами. Лис­ тинг 3.4 демонстрирует порядок обращения к экземпляру класса TGenericClass с указанием того, что он необходим нам в роли хранилища целого числа.


var gObject: TGenericClass<integer>; i:integer;

Ьegin

gObject:= TGenericClass<integer>.Create;

 

gObject.SetT(99); //записываем значение 99 i:=gObject.GetT; //считываем значение из класса

 

. gObject.Destroy; end;

 

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



46                                                                                                                                                              Глава З

 

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

 

Наследование шаблона класса

Любой, построенный на основе концепции обобщенного типа данных, шаблон класса вправе выступать в качестве предка для своей цепочки классов-наследников. Объявляя дочерний класс, программист принимает решение о статусе параметра обобщенного типа. Предусмотрены два варианта наследования (листинг 3.5):

   наследование с сохранением обобщенного типа данных, другими словами, класс-наследник остается шаблоном;

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


type TGenericParent<T>=class' {родительский класс с открытым параметром Т}

protected

fGP:T;

puЫic

procedure SetGP(Value:T); function GetGP:T;

end;

(1 вариант наследования - объявляется новый класс-шаблон} TGenericChild<T>=class(TGenericParent<T>)

//поля и методы класса TGenericChildl

end;

{2 вариант - объявляется обычный класс} TNotGenericChild<T>=class(TGenericParent<integer>)

//поля и методы класса TNotGenericChild

end;

 

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

Напротив, класс TNotGenericChild перестал быть шаблоном, он явным образом ука­ зывает, что класс-предок в состоянии воспринимать только целочисленные данные.

При описании дочернего класса-шаблона разрешается вводить дополнительные параметры, способные работать с обобщенным типом данных, например:

TGenericChild2<T, C>=class(TGenericParent<T>)


 

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

 

Перегрузка методов с параметром обобщенного типа

Наличие у метода класса параметра обобщенного типа не служит препятствием при перегрузке одноименных методов. Листинг 3.6 демонстрирует порядок объявления и перегрузки обычного метода и шаблона метода с параметром обобщенного типа.

 

                                   '•" lil ,,;1t::: •;'.ff"'-"9\', I ,,/,r.•r,ь "'N'."' •, "C'                             '                                         . ;,!'       1  1,               1,              1                                                      . ,

._ ,о,;,                        "                                                        1  •,  ..,.  JII"',              "            4,,.,,  r, ,         ',.... ,::• ,..  "•""•t _, ' '•                 ,                                         1                 !i/,

 

type TDemoClass = class

procedure DemoMethod(X: douЬle); overload; //обычный метод

prooedure DemoMethod<T>(X: Т);         overload; //шаблон метода

end;

 

В распоряжении демонстрационного класса имеются два метода Demoмethod () . Па­ раметр первого метода задан явным образом в виде действиtельного типа douЬle, а параметр второго представлен в виде обобщенного типа т.

Листинг 3.7 показывает порядок вызова перегружаемых методов.


var SC:TDemoClass;

Ьeqin

SC:=TDemoClass.Create;

SC.DemoMethod(З.1426);                  //обращение к DemoMethod(X: douЬle) SC.DemoMethod<Integer>(SOO); //обращение к шаблону DemoMethod<T>(X: Т)

// ...

 

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

 

ЗАМЕЧАНИЕ

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

 

Шаблон массива, класс TArray<>

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



48                                                                                                                                                              Глава 3

 

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

Фрагмент объявления класса TArray<> предложен в листинге 3.8.


type TArray = class

private

class prooedure QuickSort<T>(var Values: array of Т;

const Cornparer: ICornparer<T>; L, R: Integer);

puЬlic

class prooedure Sort<T>(var Values: array of Т);  overload;

//другие версии процедуры сортировки Sort<T>

 

class function BinarySearch<T>(const Values: array of Т; const Item: Т; out Foundlndex: Integer;

const Comp�er: IComparer<T>;Index, Count: Integer): Boolean; overload;

//другие версии процедуры бинарного поиска BinarySearch<T>

end;

 

Обратите внимание на то·,что класс не предназначен для хранения данных, для это­ го у него нет ни одного поля. Зато в распоряжении TArray имеются две группы од­ ноименных методов-шаблонов. Методы Sort<T> ( J позволяют сортировать данные в массивах любого простого типа, а методы Binarysearch<T> () найдут в упорядо­ ченном массиве индекс ячейки с заданным элементом. Все про'цедуры и функции объявлены в качестве методов класса (напомню, что об этом можно судить по сло­ ву class вначале объявления метода), а это означает, что методы подлежат вызову без создания экземпляра класса.

Воспользуемся услугами класса TArray<> для упорядочивания значений в трех раз­ нотипных массивах (листинг 3.9). Для этого задействуем его метод sort<T> ( J.


program TArrayDemo;

 

{$АРРТУРЕ CONSOLE}

uses System.SysUtils, System.Math, Generics.Collections;

 

.

 
const Size=l0; //размер массивов

 

var A:Array[0 .. Size] of Integer; //объявляем массивы B:Array[0 .• Size] of Real;

C:Array[0 .. Size] of AnsiChar; i:Word;

 

 



 

Ьegin

Randornize; //инициализируем генератор псевдослучайных чисел for i:=0 to Size do {заполним массивы случайными значениями} Ьegin

A[i] :=Randorn(l00);                                               //генерируем целое число

B[i] :=Randorn(l00)/RandornRange(l,100); //генерируем действительное число C[i]:=AnsiChar(RandornRange(ЗЗ,255));      //генерируем символ

end;

 


TArray.Sort<integer>(A); TArray.Sort<Real>(B); TArray.Sort<AnsiChar>(C);


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


 


for  i  := О to   Size do //проверим результат  сортировки

Ьeqin

Write ('А[',i, '] =',#9,А [i],#9);

Wri te ( [ ' , i, ' ] =' , # 9, В[i] , # 9) ;

Write ('С [ ', i, '] =', #9, С [i],#9);

WriteLn; end; Read.Ln;

end.

 

 

ЗАМЕЧАНИЕ

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

 

 

Шаблон списка объектов, класс TObjectList<>

Рассмотрим очередной шаблонный класс, входящий в состав модуля Systern. Generics. Collections. Класс тоьjectList<> легко справится с хранением лю­ бого заданного программистом перечня объектов и предоставит доступ к любому из объектов по его индексу.

По своей сути класс представляет собой список, способный выступать владельцем объектов. Именно поэтому, при создании экземпляра класса конструктор

constructor Create(AOwnsObjects: Boolean = True);

по умолчанию устанавливает аргумент владения AOwnsObj ects в состояние true.

Впрочем, если по какой-то причине в момент создания списка вы передали в кон-



50                                                                                                                                                              Глава З

 

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

property OwnsObjects: Boolean;

из кода вашей программы.

Среди примеров к книге есть небольшая программа-игра, демонстрирующая работу тoьjectList<>. Суть игры заключается в том, что пользователь должен ловить ле­ тящие вниз шарики- экземпляры класса тcircle (рис. 3.1).


 

Не трудно догадаться, что главная роль в коде отводится списку тobjectList<>, ко­ торый возьмет на себя обязанности по обслуживанию падающих с "небес" объек­ тов. Работа приложения начинается с создания списка (листинг 3.10).


var Foпnl: TFoпn;

List:  TObjectList<TCircle>;      //список объектов  TCircle

implementation

{$R *. fmx}

prooedure TFoпnl.FoпnCreate(Sender:  TObject);

Ьegin

List:=TObjectList<TCircle>.Create;  //здесь  будут  храниться шарики

end;

 

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


 

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


procedure TForml.TimerlTimer(Sender: TObject);

var Circle:TCircle;

Ьegin

Randomize;

Circle:=TCircle.Create(nii); //очередной шарик List.Add(Circle);        //добавляем шарик в список Circle.Parent:= Forml;

Circle.Position,Y:=o;· //место появления шарика на форме Circle.Position.X:=Random(Round(Forml.ClientWidth-Circle.Width));

end;

 

 

В коде программы для добавления очередного объекта к списку задействуется метод

function Add(const Value: Т): Integer;

Однако это не единственны\i·способ пополнения списка, кроме метода Add (), до­ бавляющего объект самым последним в список, в арсенале класса тoьjectList<T> имеется метод

procedure Insert(Index: Integer; const Value: Т);

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

Кроме того, у класса предусмотрен целый ряд перегружаемых методов

procedure AddRange(const Values: array of Т);

procedure InsertRange(Index: Integer; const Values: array of Т);

умеющих за один присест добавить/вставить целую партию объектов.

Возвращаемся к нашей программе, а точнее, ко второму таймеру Timer2. В круг обязанностей этого компонента входит:

  управление процессом постепенного опускания шариков от места их создания (верхней грани формы) вниз формы, где их попытается поймать пользователь;

. удаление пойманных и пропущенных шариков.

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

property Count: Integer;

и передать индекс интересующего нас элемента в свойство

property Items[Index: Integer]: Т;



52                                                                                                                                                              Глава 3

 

Пойманным считается шарик, попавший на поверхность биты, реализованной на основе компонента TRectangle, пропущенным - шарик, пролетевший мимо (лис­ тинг 3.12).


prooedure TForml.Timer2Timer(Sendeт: TObject); var i:integer;

Ьegin

for i :=О to List.Count-1 do //перебираем все объекты

with List.Items[i] do

Ьegin

Position.Y:=Position.Y+0.5;  //опускаем шарик  вниз

//проверяем, не попал ли шарик на биту Rectanglel:TRectangle

if     (Position.Y+Height>=Rectanglel.Position.Y)      and (Position.X>=Rectanglel.Position.X) and (Position.X<=Rectanglel.Position.X+Rectanglel.Width) then

Ьegin

//элемент пойман List.Delete(i);  //удаляем шарик

// ...      подсчет пойманных шариков Break;                //прерываем цикл

end else

if     (Position.Y+Height>=Rectanglel.Position.Y)  then

Ьegin

//элемент не пойман List.Delete(i); //удаляем шарик

// ...      подсчет пропущенных шариков Break;              //прерываем цикл

end;

end;

end;

 

 

ЗАМЕЧАНИЕ

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

 

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

procedure Delete (Index: Integer) ;

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


 

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

function Extract(const Value: Т):  Т;

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

На этом функционал списка TObjectList<T> далеко не заканчивается. Класс­ шаблон способен еще на многое. В частности, он умеет перемещать элемент из по­ зиции Curlndex в позицию Newlndex с помощью процедуры

prooedure Move(Curlndex, Newlndex: Integer);

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

procedure Sort; overload;

procedure Sort(const AComparer: IComparer<T>); overload;

При желании весь список может быть представлен в формате массив.а-шаблона

function ToArray:  TArray<T>;

И наконец, список обладает событием

PFoperty OnNotify: TCollectionNotifyEvent<T>; TCollectionNotifyEvent<T> = procedure(Sender: TObject;

const Item: Т; Action: TCollectionNotification) of object;

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

 

Шаблон словаря TDictioп·ary<>

В заключительном разделе главы познакомимся с весьма неординарным шаб­ лоном - словарем TDictionary<>. Словарь описан в модуле System. Generics. collections и, как и положено любому словарю, предназначен для обслуживания пар "ключ- значение", поэтому в ero объявлении присутствуют два обобщенных типа TDictionary<TKey,тvalue>. Ключ в словаре должен быть уникален, благода­ ря этому осуществляется быстрый и однозначный поиск соответствующего ему значения.

Класс вооружен несколькими конструкторами, простейший из них

constructor Create(ACapacity: Integer  = О);  overload;

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



54                                                                                                                                                              Глава З

 

Для добавления в словарь новой пары "ключ -  значение" используют метод

prooedure Add(const Кеу: ТКеу; const Value: TValue);

 

ВНИМАНИЕ/

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

 

·Если вы намерены просто изменить значение для определенного ключа, то реко­ мендую процедуру

prooedure AddOrSetValue(const Кеу: ТКеу; const Value: ТValue);

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

Свойства с традиционными названиями

property Count: Integer;

property Iterns[const Кеу: ТКеу]: TValue;

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

Изъятие из словаря пары по ключу осуществит процедура

prooedure Remove(const Кеу: ТКеу);

Метод

function ExtractPair(const Кеу:  ТКеу):  TPair<TKey,TValue>;

также исключит пару из словаря, но на этот раз даст возможность программисту прочитать ключ/значение.

Полная очистка словаря осуществляется процедурой

prooedure Clear;

Пара очень быстрых методов проверит наличие в словаре ключа

function ContainsKey(const Кеу: ТКеу): Boolean;

и значения

function ContainsValue(const Value: TValue): Boolean;

и в случае успеха они возвратят true.

Для быстрого доступа к значению по его ключу вызываем функцию

function TryGetValue(const Кеу: ТКеу; out Value: TValue): Boolean;

В случае наличия в словаре пары, описываемой ключом Кеу, в выходной параметр

value поступит соответствующее ключу значение.


Клвссы-швблоны                                                                                                                                              55

 

Традиционное для большинства классов-шаблонов свойство

function ToArray: TArray<TPair<TKey,TValue>>;

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

 

ЗАМЕЧАНИЕ

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


 

 

ГЛАВА 4

 

 

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

 

 

 

 

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

   прародитель всей цепочки классов библиотеки -     класс FМХ. Types. TFmxObject;

   опорнь1й класс для двухмерных элементов управления FМХ. Controls. тcontrol;

   опорный    класс    для    трехмерных     элементов    управления     FМХ. controlsЗD.

TControlЗD.

 

ЗАМЕЧАНИЕ

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

 

Опорный класс TFmxObject

В джентльменском наборе опорного класса TFmxObj ect мы найдем все неоqходимое для обеспечения работы классического программного объекта. В первую очередь это конструктор Create () и деструктор Destroy () экземпляра класса.

Если вам необходимо создать точную копию уже существующего объекта, то вызо­ вите метод

function Clone(const AOwner: TComponent): TFmxObject;

Функция возвратит клон объекта и передаст его во владение объекту, определен­ ному в параметре AOwner.

Наряду с классическим для объектов Delphi деструктором Destroy () предусмотрен необычный метод

procedure Release(Delay: Single = 0.1);


удаляющий объект не сразу, а через некоторый интервал времени, давая экземпля­ ру класса корректно завершить свой жизненный цикл, например, закончить опера­ цию перерисовки. Примерный период времени (по умолчанию О,1 секунды), отво­ димый на исчезновение объекта, передается в параметр Delay. Сразу замечу, что бессмысленно использовать метод Release () в качестве "часовой бомбы", уничто­ жающей объект через полчаса после вызова, поэтому направляйте в параметр зна­ чение, не превышающее секунду.

В современном, даже невысокой степени сложности приложении одновременно взаимодействуют десятки, а то и сотни объектов. Зачастую жизненный цикл одних объектов состоит в прямой зависимости от "жизни" или "смерти" других. Для того чтобы о факте уничтожения одного объекта узнал другой, в состав методов класса TFmxObj ect включена процедура

procedure FreeNotification(AObject: TObject); virtual;

Единственный параметр метода указывает на объект, которому будет автоматиче­ ски направлено уведомление об уничтожении нашего компонента.

Обратная задача (вычеркивание объекта из перечня объ ктов, подлежащих уведом­ лению) решается методом

procedure RemoveFreeNotify(oonst AObject:  IFreeNotification);

 

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

TCornponent.

 

Управление дочерними объектами

Возможность одного объекта обладать другим бьша предусмотрена еще на уровне класса т�ontrol из библиотеки VCL. Здесь принадлежащие объекту компоненты хранились в свойстве cornponents, и доступ к ним осуществлялся по их индексу. В базовом классе TFmxObject имеется еще один, альтернативный (а может, даже и правильнее сказать основной) подход к обладанию объектами. На этот раз доступ к принадлежащему владельцу списку дочерних объектов осуществляется при по­ средничестве свойства

property Children: TFmxObjectList;

которое построено на основе списка-шаблона

TFmxObjectList = System.Generics.Collections.TList<TFmxObject>

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

Общее число подчиненных объектов предоставит свойство

property ChildrenCount: Integer; //только для чтения



58                                                                                                                                                             Глава 4

 

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

property Parent: TFrnxObject;

и порядковый номер в списке родителя

property Index: Integer; //только мя чтения

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

function HasParent: Boolean;

Любой объект, имеющий право быть родительским, по отношению к другим вправе провести экспертизу с помощью метода

function IsChild(AObject: TFrnxObject): Boolean;

Передав в метод ссылку на произвольный объект AObj ect, мы узнаем, является ли он дочерним по отношению к объекту, вызвавшему метод.

Все потомки TFrnxObj ect имеют право выступать владельцами объектов, имеющих в своей иерархии наследования класс TFrnxObj ect. Для добавления дочернего объек­ та вызываем метод

procedure AddObject(AObject: TFrnxObject);

или

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

Разница между методами заключается в том, что AddObj ect ( ) добавит объект в ко­ нец списка дочерних объектов, а InsertObj ect - в позицию, определенную в пара­ метре Index.

С методами AddObj ect () и InsretObj ect () мы еще неоднократно столкнемся на страницах этой книги, ведь они своего рода визитная карточка всех объектов биб­ лиотеки FireMonkey (ничего подобного в VCL нет). Небольшой пример рассмотрим прямо сейчас. Действующими лицами примера окажутся один объект-аниматор TFloatAnimation, позволяющий в динамике воздействовать на одно из свойств сво­ его объекта-владельца, изменяя его состояние, и произвольное число фигур TRectangle (страница Shapes). Не стоит искать объект-аниматор на палитре компо­ нентов - мы его создадим автоматически, правда, стоит позаботиться о подключе­ нии к проекту модуля FМX.Ani. Разместите на форме проекта несколько компонен­ тов TRectangle и перейдите к обработчику события oncreate () формы (листинг 4.1).


uses ..., FМX.Ani;

var

Forml : TForml;

FA: TFloatAnimati�n;


procedure TForml.FormCreate(Sender:  TObject);

Ьegin

FA:=TFloatAnimation.Create(nil); FA.PropertyName:='RotationAngle'; FA.Trigger:='IsMouseOver=true'; FA.Triggerinverse:='IsMouseOver=false'; FA.StartValue:=90;

FA.StopValue:=0;

end;

 

Сейчас мы не станем комментировать код из листинга 4.1, т. к. в этой книге ани­ мации посвящена отдельная глава 18. Пока достаточно знать, что после созда­ ния формы проекта в нашем распоряжении появился экземпляр класса FA : TFloatAnimation, у которого нет владельца (конструктор аниматора был вы­ зван с аргументом nil).

Для завершения примера нам осталось написать всего одну строку кода в обработ­ чике события OnМouseEnter () у любого из прямоугольников TRectangle (лис­ тинг 4.2) и сделать это событие общим для остальных компонентов TRectangle.


procedure TForml.RectanglelMouseEnter(Sender: TObject);

Ьegin

TFrnxObject(Sender) .AddObject(FA);

end;

 

Таким образом, в момент появления над любым из прямоугольников TRectangle указателя мыши он становится владельцем объекта-аниматора FA : TFloatAnimation. Что произойдет дальше, вы увидите, повторив представленный выше код или найдя этот пример в прилагаемом к книге электронном архиве (см. прwюжение 5).

При необходимости дочерние объекты могут быть упорядочены, для этого вызыва­ ется метод

procedure Sort(Compare: TFrnxObjectSortCompare);

Два объекта могут просто поменяться местами, для этого следует обратиться к ме­ тоду

procedure Exchange(AObjectl, AObject2: TFrnxObject);

Удаление дочернего объекта из списка осуществляет перегружаемый метод

procedure RemoveObject(AObject: TFrnxObject); overload; prooedure RemoveObject(Index: Integer); overload;

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



60                                                                                                                                                              Гпава 4

 

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

procedure DeleteChildren;

Отметим, что объекты не подлежат физическому уничтожению, а просто изымают­ ся из списка.

Во время визуального проектирования важнейшим инструментом программиста выступает окно управления структурой проекта Structure. В данном окне, переме­ щая компонент мышью, мы получаем возможность переподчинять объекты друг другу. Например, на экранном снимке (рис. 4.1) видно, что компоненту тaьcontroll принадлежат три страницы (тaыteml, таыtеm2, таЫtеmЗ), которые в свою очередь являются владельцами других компонентов.

 

 

 

 

 

 


l,. lluttonl


.,J


 

 

 

 

 

1

&il  +             

 
1  ·sForm1

, '4StyieSookl

е    T.tlControll


 

    1785: 18       lnlert


i¾)

StyleВool(1

 

 

 

 

 

"f_ode) . ---


8        taЬiteml 1o)Вuttor11 i;jfdtl

ii)Edit2

1\1

 
1111   

Ю          Т..Ь!t.11112

(ii raыtemз


 

Рис. 4.1. Управление дочерними объектами с помощью окна Structure

 

 

Сопоставление  дополнительных данных

У объектов VCL на уровне тcomponent объявлено неиспользуемое системой свойст­ во таg, позволяющее программисту связывать с объектом произвольное целочис­ ленное значение. В классах FМХ на сопоставлении с объектом дополнительных данных специализируется целая группа дополнительных свойств:

property TagFloat: Single;            //вещественное значение property TagObject: TObject; //внешний объект property TagString: string;            //текстовая строка


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

 

Подцержка LiveBindings_

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

 

Подцержка анимации

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

В рамках класса TFmxObject объявлено более десятка методов (в их названиях име­ ется слово Animation или Animate), предназначенных для управления эффектами анимации, их изучению мы посвятим главу 18.

 

Подцержка сенсорного ввода

У всех визуальных элементов управления, базирующихся на классе TFmxObject,

имеется свойство

property Touch: TTouchМanager;

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

 

Взаимодействие с командами

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

property Action:  TBasicAction;

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


 

2D-элементы управления, класс TControl

Все пользовательские элементы управления платформы FireMonkey берут начало от объявленного в модуле FМХ. Controls класса тcontrol. Несмотря на одинаковые имена, знак равенства между классическим тcontrol из состава VCL и классом тcontrol ставить преждевременно. По сравнению со своим "коллегой" наш новый знакомый обладает существенно возросшим функционалом.

Если вы намерены досконально разобраться с классом, то сразу стоит взглянуть на его определение:

type TControl  class(TFmxObject,. IControl, IContainerObject,

IAlignRoot, IAlignaЬleObject, IEffectContainer)

Как видите, тcontrol является не просто наследником опорного класса TFmxObject. В объявлении родоначальника всех элементов управления FireMonkey присутству­ ют пять интерфейсов.

   Благодаря rcontrol потомки класса тcontrol приобретают право получать фокус ввода и позволяют пользователю передавать потомкам FМХ. Types . тсоntrol фокус ввода (например, с помощью клавиши <ТаЬ>). Кроме того, интерфейс rcontrol обеспечит взаимодействие с клавиатурой и мышью (в. VCL такими чертами об­ ладали только оконные элементы управления - потомки тwinControl).

   Благодаря интерфейсам IAlignRoot и IAlignaЬleObject реализовано улучшенное

выравнивание элемента управления на поверхности родительского контейнера. А интерфейс rcontainerObj ect в любой момент времени готов предоставить ин­ формац,по о размерах родительского контейнера.

    Интерфейс IEffectContainer позволяет применять к элементу управления разно- образные графические эффекты (см. главу 19).

В классе. тcontrol принципиально изменш: ся способ хранения местоположения элемента управления. Теперь допускается описывать по?ицию элемента управления в двух- и трехмерном пространствах с использованием векторных координат.

 

ВНИМАНИЕ!

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

 

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


                                                                                   

 

 

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

 

Размещение 2D-элемента управления

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

property Width: Single;        //ширина объекта

property Height: Single; //высота объекта

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

 

ВНИМАНИЕ!

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

Еще большие изменения постигли способ описания координат местоположения объекта. Очередное новшество FМХ спрятано в недрах свойства

property Position: TPosition;

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

property Х: Single;

property У: Single;

Если копнуть еще глубже, то выяснится, что интеллектуальный объект TPosition

определяет позицию компонента как вектор (листинг 4.3).


type TVector = re?ord

//метоЩ,I записи ТVector

case Integer of

О: (V: TVectorArray;); //type TVectorArray = array [0.. 2] of Single; 1: (Х: Single;

У: Single;

W: Single;);

end;

 

 

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


 

ВННМАННЕ!

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

 

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

 

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

 

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

Описание

property DefaultValue: TPointF;

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

property Х: Single;

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

property У: Single;

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

property Point: TPointF;

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

property Vector: TVector;

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

function Empty: Boolean;

Возвращает true, если координаты х и У уста- новлены в О

procedure Reflect(const Normal: тvector);

Отражает текущий вектор перпендикулярно ли- нии, проведенной из точки (О, О, О) в точку Noпnal

property OnChange: TNotifyEvent;

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

 

 

Выравнивание объекта

У объектов FМХ имеется весьма обширный набор возможностей по выравниванию элемента управления на поверхности родительского контейнера. Для этого преду­ смотрено свойство

property Align: TAlignLayout; //по умолчанию alNone

но вариантов выравнивания больше- целых 20 (против 6 в VCL):

type TAlignLayout = (alNone, alTop, alLeft, alRight, alBottom, alMostTop, alMostBottom, alMostLeft, alMostRight, alClient, alContents, alCenter, alVertCenter, alHorzCenter, alHorizontal, alVertical, alScale, alFit, alFitLeft, alFitRight);

К вопросу выравнивания объекта имеют отношение еще два свойства:

property Margins: TBounds; //по умолчанию (О, О, О, О}

property Padding: TBounds; //по умолчанию (0, О, О, О}

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


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

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


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

 

 

Группировка объектов и компонент TLayout

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

Класс FМХ. Layouts. TLayout является прямым потомком рассматриваемого в этой главе класса тcontrol и обладает одной важной особенностью - экземпляр TLayout


 

Рис. 4.3. Подчинение кнопок компоненту Layoutl


 

 



бб                                                                                                                                                              Глава 4

 

выполняет функционал невидимого во время выполнения приложения контейнера для других элементов управления.

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

 

Масштабирование и вращение объекта

Размеры любого потомка класса тcontrol могут быть пропорционально изменены.

Коэффициент масштабирования задается в свойстве

property Scale: TPosition;

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

Любой из потомков класса тcontrol может быть подвергнут вращению. Угол пово­ рота в градусах (-180...+180) задается в свойстве

property RotationAngle: Single;

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

property RotationCenter: TPosition;

Заметим, что точка центра вращения задается относительно клиентских координат объекта, которые нормируются к 1. По умолчанию центр находится в точке (0.5, 0.5)- это не что иное, как геометрический центр вращаемого объекта. Если мы зададим в качестве центgа точку (1, 1) - центр вращения переместится в пра­ вый нижний угол объекта, (О, 0)-  в левый верхний.

Для разработчиков компонентов будет полезно знать, что на уровне тcontrol объ­ явлено свойство

property Skew: TPosition;

позволяющее деформировать объект. Указанное свойство еще активно применяется в классе ттransform, отвечающем за осуществление различного рода преобразо­ ваний.

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

   таймером TTimer для отсчета времени;

  слоем .TLayout, выравненным по центру (Align:=alCenter) формы приложения для iPhone, слою будут принадлежать все визуальные элементы управления;

   тремя линиями TLine, которые превратятся в стрелки;


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

мы напишем приложение "Часы".

 

 

 

 

 

 

 

Таймер

Timerl:TTimer

 

Слой

Layoutl:TLayout

 

 

Часовая стрелка

LineHour:TLine

 

 

Минутная стрелка

LineMin:TLine

Секундная стрелка

LineSec:TLine

 

 

 

 

 

 

 

 

 

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

 

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

 

.... ,                                                     •····•·•·.,,м•-••·..·····•·•·•····•·•··••••·•"0 ....................._. •••••••••••_,.,••,. ·••••···•·•·•,. ••._ ..........,. ..мf.!'., .....,,..,м_,.,...,.,......, •,.,,,..,,.".............

 ,,                                   ..............,_                                                                                          i

"''••••                                                        •••••••••• ,,._,,,..,••·ч,,...............................................................,,' -r1. - .............,., , ••- ...........'Fi.. ••••••..........w...............v......,••••••••••••, ••  ··•••••••••• .. ,., .. ,,.,.,.,,.,•••,,,

 

prooedure TfrmWatch. FormCreate (Sender: TObject)

Ьegin

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


 

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

end;

 

 

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

 


 

prooedure TfпnWatch.TimerlTimer(Sender: TObject);

var H,M,S,MS:word;

Ьegin

DecodeTime(Time,H,M,S,MS); //декодируем время

//переводим время в углы поворотов стрелок LineSec.RotationAngle :=S*6+180;                                                                                     //секунды LineMin.RotationAngle :=М*б+180;            / /минуты LineHour.RotationAngle:=H*З0+lB0+M/2;  //часы

end;


1

.                        !·


 


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

 

Видимость и прозрачность элемента управления

У всех визуальных компонентов из состава FMX сохранилось традиционное свой­ ство

property VisiЬle: Boolean;

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

property Opacity: Single; //по умолчанию 1

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

 

Грани, фаски и визуальные эффекты

Программисту, хорошо знающему элементы управления библиотеки VCL, при пер­ вой встрече с элементами управления FМХ может показаться несколько удиви­ тельным отсутствие у визуальных компонентов FireMonkey свойств, определяю­ щих внешний вид граней и фасок компонента (напомним, что в VCL для оформле­ ния компонента широко использовались свойства BorderStyle, Bevelinner, вevelKind, BevelOuter и т. п.). Вместо этого в проектах FMX для оформления эле­ ментов управления используются альтернативные подходы -  стили, анимация и

 

 



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

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

   TBevelEffect  -     эффект фаски;

   TReflectionEffect  -     отражение;

   TGlowEffect -      наружное свечение;

   TinnerGlowEffect  -     внутреннее свечение;

   TShadowEffect -      тень;

  TВlurEffect -      размытие.

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

 

r 1-- ,....... _.

EJ

 
l sr;i ffю1


cc•t::)Pan,13

-1R.e-REcbanE:ff-.!ct1

,,;,.r;]p..,.,1

1

Е !;] Pane!S

8',!мerG\owEl!m!

l:;]P.-..11;

4ShodlмEffюl

;,,G] Ponof7

tr,\Styi,!lool:1


St,'e!loot1


 

 

 

 

 

 

TReflectionE t


 

 

 

 

 

 

 

 

 

Рис. 4.5. Применение эффектов для управления внешним видом визуального эл_емента

 

 

ЗАМЕЧАНИЕ

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


 

Состояние элемента управления

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

 

Таблица 4.2. Свойства, описывающие состояние элемента управления

 

Свойство

Описание

property IsMouseOver: Boolean;

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

property IsDragOver: Boolean;

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

property IsFocused: Boolean;

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

property IsVisiЫe: Boolean;

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

property ParentedVisiЫe: Boolean;

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

 

 

Обработка событий

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

 

Простейшие события -     щелчок

В любой операционной системе, поддерживаемой FMX, самыми востребованными событиями были и остаются одинарный и двойной щелчки по элементу управле­ ния.

property OnClick: TNotifyEvent;               //одинарный щелчок

property OnDЫClick:  TNotifyEvent;  //двойной щелчок

Напомним, что инициатором щелчка могут стать левая кнопка мыши, а также на­ жатие клавиши <Enter> или <Пробел> при условии, что объект управления нахо­ дится в фокусе ввода.

Все события, в том числе и простейшее из них

TNotifyEvent = procedure(Sender: TObject) of object;

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

Вряд ли вы еще не встречались с простейшими примерами обработки события

OnClick (), поэтому мы сразу рассмотрим более усложненный пример, демонстри-


рующий возможность разделять один обработчик события сразу несколькими эле­ ментами управления и раскрывающий скрытые возможности параметра sender. Для реализации примера разместите на главной форме проекта кнопку Buttonl: твutton,· многострочный редактор Memol: тмеmо и несколько любых других визуальных эле­ ментов управления, которые вы найдете на палитре компонентов Delphi.

Выберите кнопку Buttonl и напишите всего одну строку кода в обработчике собы­ тия (листинг 4.6).


procedure TForml.ButtonlClick(Sender: TObject);

Ьegin

Memol. Lines. Add ( ( Sender as TControl) . Name) ;

end;

 

 

Щелчок по кнопке приводит к появлению в многострочном редакторе Memol строки с именем компонента, вызвавшего событ е (в данном случае кнопки вuttonl). Предвижу вопрос: не проще ли было написать строку мemol. Lines .Add ( 'вuttonl') ?" Нет, не проще. В особенности, если мы намерены научиться создавать разделяемый между несколькими компонентами код.

Выберите на форме любой другой элемент управления (например, CheckBoxl). Пе­ рейдите на вкладку Events (События) в Инспекторе объектов, в перечне событий найдите событие onclick и сопоставьте с ним событие ButtonlClick (рис. 4.6). Наши действия приведут к тому, что два компонента станут разделять между собой один общий обработчик события. Аналогичным образом подключите к событию все ос­ тальные компоненты (включая форму) и запустите проект на выполнение. Щелкни­ те по любому из компонентов - его мя сразу появится в отдельной строке много­ строчного редактора Memol.

 

ВНИМАНИЕ/

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

Благодаря наличию параметра Sender во всех обработчиках событий элементов управления·Delphi и возможностью совместного использования нескольких объек­ тов одного и того же события можно создавать весьма эффективный код. Хорошим примером такого кода может стать приложение "Калькулятор".

У любого калькулятора есть кнопок, отвечающих за ввод цi.фровых значений. Для того чтобы пользователь смог визуально идентифицировать назначение каж­ дой из кнопок, в их заголовки (техt) помещается соответствующий вводимой циф­ ре символ (рис. 4.7). При нажатии на каждую из цифровых кнопок (в моем примере это кнопки тcornerButton) генерируется событие OnClick (), в результате которого в метке Labell:TLabel калькулятора отображается выбранная цифра. Как правило, начинающий программист решает подобную задачу в лоб и пишет для каждой из десяти кнопок примерно следующий код (листинг 4.7).


 

 

 

 

 

 

 

 

 

 

 


 

 

 

L.ммl\

 

0 fl.>d.c,l!цtto l

0 Rмi,e9ut1<> l

 

 

   21: 20      hwt                  Мocifled


Pr       1ieS  E-'==r:===;:=====;-t

.

 
Ltti

! t-,4,t.cckц)

!OnCanFocus

iOnChiwige

»!OnClclt

1:::ор

!QO

10n0rilgfnter

\°"°""'-мvе

i0n0rllg()ve"

jOnenllr

1=                   

l.мencfng... 1пс"' 08 Fiold.••


 

 

 

 

 


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

 


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


f, ••••••••••••••••••t,,••••••••....•••••.. ••••••••••........ .,• -•••••.. ·•..•..•••.,.•••••••••• ·•.'••••••••••••.........9'•••••••••••••.. J _.,_, , .. ,,,,,.,                                                                                                                                                          •••-••U'-°"',,.. .е,•••••••,Ц,•!•!.р,,1;

-  ...•,_ ....,, ··-·--·............. .....'!!..!"...  !!"- ':! ···"·""·•..-•···-·"···...-.-....................................... i

procedure TForml.CornerButtonlClick(Sender: TObject);

Ьegin

if Labell.Text='0' then Labell.Text:= '1' else Labell.Text:=Labell.Text+'l';

end;

 

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


procedure TForml.CornerButt0nlClick(Sender: TObject);

Ьegin

if  Labell.Text='0' then

Labell.Text:=(Sender as TCornerButton) .Text else

Labell.Text:=Labell.Text +(Sender as TCornerButton) .Text;

end;

 

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

 

Клавиатурные события

Для того чтобы ни один из визуальных элементов управления FMX не остался без­ участным к нажатиям пользователем клавиш, на уровне тcontrol реализованы два самых важных клавиатурных события:

property OnКeyDown: TKeyEvent; //нажатие клави!Ш1 property OnКeyUp: TKeyEvent;                                                 //отпускание клави!Ш1

Благодаря OnКeyDown ( ) и OnKeyup ( ) мы получаем возможность отследить как нажа­ тие, так и отпускание клавиши. Оба события типизированы одинаково:

type TKeyEvent = procedure(Sender: TObject; var Кеу: Word;

var KeyChar: WideChar; Shift: TShiftState) of object;

Здесь: sender - ссылка на объект, вызвавший событие; Кеу - числовой код нажа­ той клавиши; KeyChar - параметр-переменная, с помощью которого можно считы­ вать при необходимости и редактировать) символ нажатой клавиши; Shift обес­ печит контроль состояния служебных клавиш.

Листинг 4.9 демонстрирует порядок применения события OnKeyDown () - в его рам­ ках форма отслеживает коды клавиш. Если пользователь нажимает клавиши управ-


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


procedure TForml.FoпnКeyDown(Sender: TObject; var Кеу: Word;

var KeyChar: Char; Shift: TShiftState);

Ьegin

if (ssCtrl in Shift) then case Кеу of


37

38

39

40

end; end;


Forml.Left:=Forml.Left-1; Forml.Top:=Forml.Top-1; Forml.Left:=Forml. Left+l; Forml.Top:=Forml.Top+l;


//курсор влево

//курсор вверх

//курсор вправо

//курсор вниз


 


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


prooedure TForml.EditlKeyDown(Sender: TObject; var Кеу: Word;

var KeyChar: Char; Shift: TShiftState);

Ьegin

i0f ((KeyChar<'0') or (KeyChar>'9') or (Length(Editl.Text)>=ll))

and (КЕУ<>8) and (КЕУ<>46) then {8 и 46 - код клавиш Backspace и Del}

Ьegin

KeyChar:=#0;

if (Length(Editl.Text)>=ll)=false then

ShоwМеssаgе('Допускаются только цифры!')

else

ShowMessage('Длинa текста превысила 11 знаков!')

end; end;

 

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

 

События мыши

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


 

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

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

property OnМouseEnter: TNotifyEvent; //вхождение в область

property OnМouseLeave:  ТNotifyEvent;  //выход из области

Вторая категория событий позволяет отслеживать события нажатия и отпускания

кнопок мыши

property OnМouseDown: ТМouseEvent; //нажатие кнопки

property OnМouseUp: TMouseEvent;            //отпускание  кнопки

Перечисленные события типизируются процедурой

type TMouseEvent = procedure(Sender: TObject;

Button: TMouseButton; Shift: TShiftState; Х, У: Single) of oЬject;

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

В табл. 4.3 представлен список параметров, позволяющих описывать реакцию эле­ мента управления на событие.

 

Таблица 4.3. Описание параметров тмouseEvent

 

Параметр

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

Описание

Sender

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

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

Button

mЬLeft

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

mЬRight

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

mЬMiddle

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

Shift

ssShift

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

ssAlt

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

ssCtrJ.

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

ssLeft

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

ssRight

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

ssMiddle

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

ssDouЫe

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

х, у

Single

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

 

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

property OnМouseMove: TMouseMoveEvent;

type TMouseMoveEvent = procedure(Sender: TObject;

Shift: TShiftState; Х, У: Single) of object;


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

Воспользуемся услугами OnМouseMove ( ) и напишем приложение, позволяющее пользователю перемещать компоненты по форме с помощью мыши. Для этого нам понадобится новый проект с любым визуальным элементом управления на главной форме (свой выбор я остановил на обычном прямоугольнике Rectanglel:TRectangle).

В разделе частных объявлений опишем две переменные - xOffset и yOffset, они позволят нам хранить отступы указателя мыши относительно левого верхнего угла перемещаемого компонента (листинг 4.11). Значения отступов запоминаются в мо­ мент нажатия кнопки в событии OnМouseDown () . Перемещение осуществляется в рамках события OnМouseMove () (при условии, что пользователь удерживает в нажа­ том состоянии левую кнопку мыши).


var Forml: TForml; xOffset,yOffset: Single;

implemenation

{$R *.dfm}

procedure TForml.RectanglelMouseDown(Sender:  TObject;

Button: TMouseButton; Shift: TShiftState; Х, У:  Single);

Ьegin

xOffset:=X;  //нажатие кнопки мыши

yOffset:=Y; //запомнили отступы xOffset и yOffset

end;

 

procedure  TF�rml.RectaпglelM9useMove(Sender:  TObject;

Shift: TShiftState; Х, У: Single);

Ьegin

//перемещение мьш и с нажатой левой кнопкой if         (ssLeft in Shift) then

with (Sender as TControl) do

Ьegin

Position.X:= Position.X+X-xOffset; Position.X:=  Position.X+X-yOffset;

end;

end;

 

 

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

Контроль вращения колесика мыши осуществляет единоличный представитель - событие


 

property OnМouseWheel: ТМouseWheelEvent;

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

 

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

 

События получения и потери фокуса ввода

Среди компонентов FireMonkey нет строгого разграничения на оконные и графиче­ ские элементы управления, подобного тому, которое существует у потомков клас­ сов TWinControl и TGraphicControl платформы VCL. Строго говоря, все визуальные компоненты FМХ являются графическими элементами управления, обладающими чертами оконных. Одно из подтверждений тому - способность всех наследников FМХ. Controls. тcontrol реагировать на получение фокуса ввода.

У компонентов сохранена традиционная (как и у компонентов VCL) реакция на по­ лучение и потерю фокуса ввода, осуществляемых событиями

property OnEnter: ТNotifyEvent;  //получение фокуса ввода

property OnExit: ТNotify�vent;           //потеря фокуса ввода

Кроме того, в FMX предусмотрено событие, позволяющее программисту запретить объекту реагировать на фокус ввода:

property OnCanFocus: TCanFocusEvent;

type TCanFocusEvent = procedure(Sender: TObject;

var ACanFocus: Boolean) of object;

 

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

 

Событие изменения размера

Изменение геометрических размеров любого потомка класса TC6ntrol незамедли­ тельно приводит к генерации события

property OnResize:  ТNotifyEvent;

 

Данное событие окажется весьма полезным в тех ситуациях, когда логика програм­ мы предполагает "подгонку" элементов пользовательского интерфейса под размер формы. Одним из примеров такого подхода может стать код, предложенный в лис­ тингах 4.12 и 4.13.

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


const N=B;

procedure TForml.FormCreate(Sender: TObject);

var i:integer;

Ьegin

for i :=1 to N�N do

with TRectangle.Create(Forml) do Parent:=Forml;

end;

 

 

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


procedure TForml.FoпnResize(Sender: TObject);

var i,     sWidth, sHeight, х, у :integer;

Ьegin

sWidth :=Forml.ClientWidth div N;           //ширина прямоугольника sHeight:=Forml.ClientHeight div  N;       //высота прямоугольника

 

x:=-sWidth+l; у:=1;

 

for i  :=О to Forml.CornponentCount-1 do

if Forml.Components[i] is TRectangle then with TRectangle(Forml.Components[i]) do

Ьegin

Height:=sHeight; Width :=sWidth; inc (х, sWidth) ;

 

if x+З<Forml.ClientWidth then //размещаем вдоль одной линии

Ьegin

Position.X:=x;

Position.Y:=y;

end else

Ьegin   //новый ряд фигур х:=1;

Position.X:=x; inc(y, sHeight); Position.Y:=y;

end; end;

end;


 

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


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

 

 

События перетаскивания drag and drop

Платформа FireMonkey поддерживает удобный интерфейс перетаскивания объек­ тов мышью. В операции участвуют два объекта: компонент - источник данных и компонент - получатель данных.

Операцию перетаскивания инициирует компонент-источник. В его распоряжении имеется свойство, определяющее порядок инициализации процесса.

property DragMode: TDragMode; // по умолчанию drnМanual

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

· Процесс перетаскивания последовательно сопровождается цепочкой событий (табл. 4.4).


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

 

События

Инициатор

Описание

OnDrag.Enter ( )

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

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

OnDragLeave()

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

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

OnDragEnter ( )

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

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

OnDragOver ()

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

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

OnDragDroup ()

Элемен,- приемник

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

OnDragEnd ()

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

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

 

В минимальной нотации для 'осуществления перетаскивания достаточно услуг все­ го двух событий, генерируемых на стороне компонента-приемника: onoragOver () и onoragDrop (). Первое из событий осуществляет проверку приемлемости поступаю­ щих данных и дает согласие на получение данных от компонента-источника.

property OnDragOver: TDragOVerEvent;

type TDragOverEvent = procedure(Sender: TObject; const Data: TDragObject; const Point: TPointF; var Accept: Boolean) of object;

Здесь Sender - компонент-получатель, вызвавший событие. Ссылка на объект, вы­ ступивший инициатором операции перетаскивания, находится в параметре Data. Это запись, состоящая из трех полей, ключевое из которых Source - именно в нем находятся все сведения об объекте-источнике операции drag and drop (лис­ тинг 4.14). Координаты указателя мыши можно прочитать в параметре Point. Свое согласие на получение данных компонент-получатель дает, передав в параметр Accept значение true.

•-с.,4.

 
г------·•·· .....,...... . ....·····-                           -..


type TDragObject = record

Source: TObject;                     //объект-источник операции Files: array of string; //массив строк

Data: Variant;                           / /дополнительные данные

end;

 

 

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


 

property OnDragDrop: TDragDropEvent;

type TDragDropEvent = procedure(Sender: TObject;

const Data: TDragObject; const Point: TPointF) of object;

Параметры события вам уже знакомы, поэтому в отдельных комментариях не нуж- даются.  *

Для подтверждения теории на практике создайте новое приложение FireMonkey и разместите на форме произвольное количество компонентов тrmage, специашrзи­ рующихся на хранении и отображении рисунков. У всех компонентов установите свойство DragMode в автоматический режим. В любой из компонентов тrmage загру­ зите произвольный рисунок.

Листинг 4.15 содержит код, общий для всех компонентов. Здесь описаны оба рас­ смотренных ранее обработчика событий.


prooedure TFonnl.ImagelDragOver(Sender: TObject; const Data: TDragObject;

const Point: TPointF; var Accept: Boolean);

Ьegin

(проверка допустимости операции}

Accept:=(Data.Source is Timage)  and (Data.Source<>Sender) and

(Tlmage(Data.Source) .Bitmap.IsEmpty=false);

end;

 

procedure TFonnl.ImagelDragDrop(Sender: TObject; const Data: TDragObject;

const Point: TPointF);

Ьegin

Tlmage(Sender) .Bitmap.Assign(Timage(Data.Source) .Bitmap);  (передаем

картинку в приемник}

Tlmage(Data.Source) .Bitmap.Destroy; (удаляем исходную картинку

у источника} Timage(Data.Source).Bitmap:=TBitmap.Create(0,0);(coздaeм новый пустой

Bitmap}

Tlmage(Data.Source) .Repaint;

end;

 

 

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

property OnDragEnter:  TDragEnterEvent;

 

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

property OnDragLeave: TNotifyEvent;


 



82                                                                                                                                                              Глввн 4

 

О завершении процедуры перетаскивания сигнализирует событие

property OnDragEnd: TNotifyEvent;

 

Особенности прорисовки элемента управления

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

prooedure PaintTo(const ACanvas: TCanvas;

const ARect: TRectF;

const AParent: TFrnxObject = nil);

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


Forml.Canvas.BeginScene; Buttonl.PaintTo(Forml.Canvas,

RectF(0,0,Buttonl.Width * 2,Buttonl.Height*2));

Forml.Canvas.EndScene;

 

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

property OnPainting: TOnPaintEvent;

property OnPaint: TOnPaintEvent;

Оба события типизированы одинаково:

type TOnPaintEvent = procedure(Sender: TObject; Canvas: TCanvas;

const ARect: TRectF) of object;

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

Завершая речь об операции прорисовки, отметим полезный метод

function MakeScreenshot: TBitmap;

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

 

ЗАМЕЧАНИЕ

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


 

Стилевое оформление, класс TStyledControl

Одним из достоинств библиотеки FireMonkey является возможность индивидуаль­ ного стилевого оформления элементов управления. Ответственность за это возло­ жена на описанный в модуле FМX.Controls класс тstyledcontrol. Ключевое свойст­ во класса

property StyleLookup: string;

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


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

 

 

ЗD-элементы управления, класс TContro/ЗD

Желание научить проекты Delphi работат в трех измерениях заставило програм­ мистов Embarcadero пересмотреть всю концепцию построения элементов управле­ ния. Во-первых, возникла необходимость модифицировать способ позиционирова­ ния объекта -  теперь кроме координат Х и У приходится учитывать и координа­ ту Z. Во-вторых, в трехмерных проектах изначально рассчитанная на двухмерные сцены библиотека GDI здесь однозначно неприменима, поэтому принципиально


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

В последних разделах главы, посвященной базовым классам FireMonkey, мы уделим внимание ключевым чертам компонентов, предназначенных для работы в ЗD-приложениях - трехмерных элементов управления, построенных на основе класса EМX.ControlsЗD.-TControlЗD.

 

Размеры объекта

Появление у элементов управления третьего измерения отражается в дополнении традиционных свойств

property Width: Single;

property Height: Single;

отвечающих соответственно за ширину и высmу объекта еще одним свойством

property Depth: Single;

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

 

Повороты объекта

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

property RftationAngle: TPositionЗD;

Координаты точки объекта, относительно которой осуществляется вращение, на­ значает свойство ·

property RotationCenter: TPositionЗD;

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

При необходимости элемент управления может выяснить у другого объекта AObject

значения его углов поворота и применить их к себе

prooedure CopyRotationFrorn{oonst AObject: TControlЗD);

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

procedure ResetRotationAngle;


 

ЗD-события мыши

У трехмерного объекта наиболее сложны (а поэтому и весьма интересны) события мыши. Сразу заметим, что набор событий ничем не отличается от перечня "мыши­ ных" событий для элемента управления VCL или FireMonkey НD, таким образом, у потомков класса тcontrolЗD вы обнаружите:

   события нажатия OnМouseDown ( ) и опускания OnМouseUp ( ) кнопок мыши;

   событие перемещения мыши OnМouseMove ();

    событие вращения колесика мыши OnМouseWheel ();

    события появления OnМouseEnter () и исчезновения OnМouseLeave () указателя мыши над объектом.

Из представленного выше перечня существенным доработкам подверглись события OnМouseDown (), OnМouseUp () и OnМouseMove () . Например, события, связанные с нажа­ тием и отпусканием кнопок мыши

property OnМouseDown: ТМouseEventЗD; property OnМouseUp: TMouseEventЗD;

типизированы процедурой

TMouseEventЗD = proaedure(Sender: TObject; Button: ТМouseButton;

Shift: TShiftState; Х, У: Single; RayPos, RayDir: TVectorЗD) of oЬject;

Здесь нам встречаются как уже знакомые по табл. 4.3 параметры sender, Shift и х, У, так и пара новых векторных параметров (RayPos и RayDir), собственно благодаря которым событие и становится "трехмерным". То же самое можно сказать и об об­ работчике события, обеспечивающем перемещение указателя мыши

property OnМouseMove:  ТМouseМoveEventЗD;

TMouseMoveEventЗD = proaedure(Sender: TObject; Shift: TShiftState;

Х, У: Single; RayPos, RayDir: TVectorЗD) of object;

В нем нам вновь встречаются параметры RayPos и RayDir.

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

Поп'робуем разработать приложение, позволяющее перемещать потомка класса

тcontrolЗD (в нашем примере куб cubel:TCuЬe). Первым шагом станет объявление трех глобальных переменных (листинг 4.17).


var

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


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


procedure TForm2.Foпn3DCreate(Sender: TObject);

Ьegin

WorkPlane:=TPositionЗD.Create(PointЗD(O, О, 1));          //создаем плоскость

end;

 

prooedure TForm2.Foпn3DDestroy(Sender: TObject);

Ьegin

WorkPlane.Free;

end;

 

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


prooedur• TFoпn2.CubelMouseDown(Sender: TObject; Button: ТМouseButton; Shift: TShiftState; Х, У: Single; RayPos, RayDir: TVectorЗD);

Ьegin

_RayPos := CuЬel.LocalToAЬsoluteVector(RayPos);

_RayDir .-  CuЬel.LocalToAЬsoluteVector(RayDir) .GetNoпnalize;

end;

 

 

Наконец подготовигельные операции завершены, мы присrупаем к самому важно­

' -

 
му (листинг 4.20).

,·-·· ..........

 

prooedure TForm2.CuЬelMouseMove(Sender: TObject; Shift: TShiftState; Х, У: Single; RayPos, RayDir: ТVectorЗD);

var V, Vl,  V2, М: ТVectorЗD;

Ьegin

if ssLeft in Shift then

Ьegin

RayPos  : = CuЬel. LocalToAЬsoluteVector (RayPo_s) ;

RayDir .- CuЬel.LocalToAЬsoluteVector(RayDir) .GetNoпnalize;


 

//учитываем рабочую плоскость

V := WorkPlane.Vector;

V.W := О;

V :=  Cubel.LocalToAЬsoluteVector(V);

 

if (RayCastPlaneintersect(_RayPos, _RayDir,

CuЬel.AЬsolutePosition,V,Vl)=false)

or

(RayCastPlaneintersect(RayPos, RayDir,

CuЬel.AЬsolutePosition, V, V2)=false) then

Exit;

 

М := V2 - Vl;    //расчет вектора перемещения

//перемещаем объект

Cubel.Position.Vector := CuЬel.Position.Vector + М;

_RayPos := RayPos;

_RayDir := RayDir; end;

end;

 

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


 

 

ГЛАВА 5

 

 

Приложение FireMonkey

 

 

 

 

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

TApplication.

 

Приложение TApplication

Приложение FМХ. Forms. TApplication несколько выбивается из стройных рядов классов FireMonkey. Несмотря на то, что в полном имени класса присутствует при­ ставка "FMX" (уведомляющая программиста, что он имеет дело с кроссплатфор­ менной библиотекой), описанное в модуле FМХ. Forms приложение строится на осно­ ве традиционного класса библиотеки УС(  system. Classes. тcomponent (см. рис. 2.1).

 

·ЗАМЕЧАНИЕ

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

Для организации работы с приложением не стоит вызывать его конструктор. Объ­ ект приложения создается автоматически. Чтобы в этом убедиться, выберите пункт

меню Project I View Source, и в редакторе кода откроется головной модуль проекта FireMonkey (листинг 5.1).



 


program Projectl;

 

uses FМХ. Forms,

Unitl in 'Unitl.pas'  {Foпnl};

 

{$R    *. res}

Ьegin

Application.Initialize; //инициализация Application.CreateFoпn(TFoпnl, Foпnl); //создание главной формы Application.Run; //старт приложения

end.

 

Как видно из листинга, доступ к экземпляру приложения предоставляет глобальная (объявленная в модуле FМХ. Foпns) переменная Application. В головном модуле про­ екта приложение проходит инициализацию, создается главная форма и производит­ ся старт программного продукта.

Приложение обязано знать свою главную форму. Ссылка на эту форму находится в свойстве

property MainFoпn:  TConmonCustomFoпn;

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

prooedure Teпninate;

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

 

Значок приложения

Если вы разрабатываете настольное приложение для Windows или OS Х, то вос­ пользуетесь пунктом меню Project I Options и получите доступ к редактору опций проекта (рис. 5.1), в котором сможете выбрать значок приложения.

Заметьте, что настольное приложение FireMonkey способно оперировать как со значками в стиле Windows (это файл с рисунком с расширением ico), так и с фай­ лом, содержащим значки в духе OS Х (расширение icns).

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

 

Название приложения

При необходимости с приложением может быть сопоставлено текстовое название

property Title: string;

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


,,,



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

 

 

Еще одно связанное с названием приложения свойство

property DefaultTitle:  string;  //только для чтения

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


 

Расположение исполняемого файла приложения

При многолетней работе с классической библиотекой VCL у многих из нас сложи­ лись собственные предпочтения и привычки. Например, в своих проектах для вы­ яснения расположения приложения автор зачастую обращался к свойству ExeName, имевшемуся у класса TApplication из VCL. К сожалению, аналогичного свойства у приложения FireMonkey нет, посему из сложившейся ситуации приходится выкручиваться подручными средствами. В данном случае простейшим решением может стать задействование определенной в модуле system. sysUtils функции

function GetCurrentDir: string;

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

 

События приложения

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

property OnException:  TExceptionEvent;

type TExceptionEvent=procedure(Sender: TObject; ·Е: Exception) of object;

Здесь Sender - приложение, в котором возникла ИС; Е - экземпляр исключитель­ ной ситуации. Так как в этот обработчик события станут стекаться сведения обо всех произошедших в приложении исключительных ситуациях, то OnException ( ) обычно не специализируется на обработке частного вида ошибок. Наоборот, в его рамках описывается общая концепция защиты программного продукта.

В качестве примера рассмотрим вариант применения события OnException () для ведения журнала ошибок приложения (листинг 5.2). Анализируя этот протокол, программист получит бесценную информацию о своих недоработках. Обратите внимание на. то, что наш код сосредоточен в головном модуле проекта файле с расширением имени dpr). Для того чтобы добраться до этого модуля, воспользуй­ тесь главным меню Delphi: Project I View Source.

 

program Projectl; uses

FМX.Forms, System.SysUtils, System.IOUtils, Unitl in 'Unitl.pas' {Forml};

 

{$R *.res}


type TAppExcept= class(TObject) //объявляем новый класс ИС

private

procedure ExceptionsControl(Sender:TObject; Е: Exception);

end;

 

procedure TAppExcept.ExceptionsControl(Sender: TObject; Е: Exception-);

var s:string;

Ьegin

s:=Foпnat('%s'+#9+'%s', [DateTimeToStr(Now),E.Message]); //сообщение TFile.AppendAllText('errors.log',s+#l0#lЗ);                                      //запись в файл Application.ShowException(E); //информируем пользователя об ошибке

end;

 

var AppExcept:  TAppExcept;

Ьegin

Application.Initialize;

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

Application.Run;

end.

 

 

Главным действующим лицом листинга выступает класс TAppExcept, единственной особенностью которого является процедура ExceptionsControl() - по параметрам абсолютный аналог события OnException () . Единственный метод класса TAppExcept решает простейшую задачу - сохраняет содержимое свойства Message исключи­ тельной ситуации в файл протокола и выводит сообщение об ошибке. Обратите внимание, каким образом будет вызываться процедура TAppExcept. ExceptionsControl(). Сразу после инициализации объекта Application мы создаем объект AppExcept и накрепко связываем его метод Ex.ceptionscontrol () с процеду­ рой обработки события OnException () . Теперь при возникновении в приложении ИС ее обработка переходит в описанный нами метод и будет запротоколирована в файл errors.log.

Второе событие приложения

property Onldle: TidleEvent;

type TidleEvent=procedure(Sender: TObject; var Done: Boolean) of object;

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

 

ВННМАННЕ!

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

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


 


 

 

 

type

TForml       class (TForm)

procedure FormCreate(Sender: TObject); private


''""'t•·•······ ..··i

i

...,.....................-, ··•···..···"' ........~.,,... " ....,.,......... '-


procedure IdleThread(Sender: TObject; var Done: Boolean); puЫic

{ PuЫic declarations } end;

var Forml: TForml; implementation

{$R*.frnx}

procedure TForml.FormCreate(Sender: TObject); Ьegin

Application.Onidle:=IdleThread; end;

 

procedure TForml.IdleThread(Sender: TObject; var Done: Boolean); Ьegin

//код nриложения, выполняемый в фоновом режиме end;

 

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

Благодаря тому, что во второй версии библиотеки FireMonkey появилась возмож­ ность создавать командные объекты TAction, позволяющие обеспечить централи­ зованное управление приложением, у TApplication имеется еще один обработчик событий

property OnActionUpdate: TActionEvent;

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

 

Контроль активности пользователя

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


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

property LastKeyPress        TDateTirne;

property LastUserActive:  TDateTime;

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

 

Характеристики дисплея, класс TFormFactor

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

property FormFactor: TFormFactor;

дающее  доступ  к  инкапсулированному  в  приложение  Application  объекту

TFormFactor.

 

ЗАМЕЧАНИЕ

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

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

property Devices: TDeviceKinds;

способное возврат.ить множество значен11й, элементами которого выступают

dkDesktop, dkiPhone И dkiPad;

Владелец смартфона или планшета может как угодно повернуть свое устройство.

Поддерживаемые ориентации экрана устройства хранятся в свойстве

property Orientations:  TFormOrientations;

Здесь вновь речь идет о множестве, значениями которого могут выступать: soPortrait, soLandscape, soinvertedPortrait и soinvertedLandscape. Перечисленные значения также могут быть настроены и в редакторе опций мобильного проекта (пункт меню Project I Options), в этом вы можете убедиться, взглянув на рис. 5.3.

Завершая разговор о TFormFactor, обязательно упомянем традиционные свойства

property Width: Integer; property Height: Integer;

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


 

6) Project Options

for

liЬProj«tl.so

(Android OeЬug)

 

,. Coщ,kor

!  !···Ccщ,iling

Нi'I ilnd Wairnngs

, ... Linlcing

,  i.. tput-C/C++

" Resowae Compiler

<  l- O..ecto,if,s IJnd CondibONls

I !Ud Ev<!nts

: Use P!!rmissions

·•Forms

'  Appic,stlon

V!!rsion lnfo

,.р

·-Runtin<,р

.. Dehщig«

·.  '-Syml,olTa№s

;       !  Envi'onme1t В1осk

Provisl<n,g

I.-vet: ОеЬ<,о conng,sAtlon - Android p!Atfurm

lalnl  Oril!ntalion                                    Preview

1-Check==-cus=tom=.iorientabon and -tlhe--- c,-.,,ogrom fies ···'fМJ.вuncht-rlcon_Збx36.png

orienaitlon(s) lhat you want your                               t.-,d,er icon _(36 36- : _

appkJttioo support,

 

 

 

!'l) \;.ustom orientabon

 

Eo,tralt

down

L nome(iфt

Е)Londscзpe home!eft

 

 

lf]Adual!IZI!

 

 

ок

11с.а111

 

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

 

 

Формы HD и 3D

Основу приложений FireMonkey с графическим интерфейсом пользователя состав­ ляет форма. Приложения FireMonkey могут использовать две разновидности форм:

   форму FМХ. Forms TForm, предназначенную для построения деловых приложений с высококачественным двухмерным визуальным интерфейсом том числе и с интерфейсом Metropolis, предн наченным для Windows 8);

   форму FМХ. FormsЗD. TFormЗD, реализующую всю мощь трехмерной графики.

ЗАМЕЧАНИЕ

Для обеих разновидностей форм основным родительским классом выступает FМХ. Forms. TCommonCustomForm. Затем цепочка наследования разделяется на две ветви (см. рис. 2.1)- тcustomForm и тcustomFormЗD, соответственно отвечающие за построение двухмерной и трехмерной форм.

Основное назначение формы - служить контейнером для визуальных элементов управления, предоставляя программисту возможность создавать пользовательский интерфейс приложения, просто перенося подходящие компоненты со страниц па­ литры. После старта программы форма приобретает все необходимые качества полноценного окна приложения для Windows, OS Х, IOS или Android.

Число форм в проекте не ограничено, но в любом случае в приложении может быть только одна главная форма (main form) проекта. По умолчанию главной формой проекта назначается самая первая созданная форма - форма с названием Forml. При запуске приложения на выполнение именно эта форма будет создана и выве­ дена на экран первой.


 

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

брать узел Forms (рис. 5.4). Главная форма вьrбирается в раскрывающемся списке Main form. Стоит заметить, что на роль главной формы могут претендовать только формы из списка Auto-create forms (Автоматически создаваемые формы). Форма

из списка AvailaЫe forms (Доступные формы) стать главной не сможет, т. к. она не


подлежит автоматическому созданию в момент старта приложения.

Cuadro de texto: Q Proj«t Options for P,ojectl.exe (Win32 · DeЬug) Cuadro de texto: 1 Cuadro de texto: ·-


--1


!

 
М,.,fonn:                                                                                                                                                         1

!i:-1                                                           ·1

1                                                                                                                   1

Cuadro de texto: Form1
Form2
Cuadro de texto: FormЗ

i\lJll><re.111! forms:                                                         Avillllble {oons:

L<.J

w

Cuadro de texto: i

!·• Envronment В1осk

 
в

Cuadro de texto: ·-- Provisioning

 

                      -    --

1.

'

 
..,,- ();     1 1     с.ке1  ! !  н.1R   1

 

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

 

 

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

 

Описание формы в fmх-файле

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

. выбрать пункт View as Text (Представить как текст) или нажать комбинацию кла­ виш <Alt>+<Fl2>.

Код с текстовым описанием ресурса формы с рис. 5.5 представлен в листинге 5.4. Обратите внимание на то, что кроме определения основных свойств формы ресурс fmх-файла содержит определения всех принадлежащих форме элементов управле­ ния (в нашем случае метки Labell и кнопки вuttonl).


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

I

 

 

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


object f!'orml : TForml Left = О

Тор=  О

Caption = 'Forml' ClientHeight = 192

ClientWidth  =  362

VisiЫe = False OnClose = FormClose

StyleLookup = 'backgroundstyle'

oЬject Labell: TLabel Position.Point = '(40,24)'

Width = 120.000000000000000000

Height = 15.000000000000000000

TabOrder = 1 Text = 'Labell'

end

oЬject Buttonl: TButton Position.Point = '(144,104)'

Width = 80.000000000000000000

Height = 22.000000000000000000

TaЬOrder = 10 Text = 'Buttonl'

end

end


 

ВНИМАНИЕ!

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

 

Общие черты форм

Все используемые в FMX формы опираются на фундамент родительского класса

type TCoпrnonCustomFonn = class(TFmxObject, IRoot, IContainerObject,

IAlignRoot, IPaintControl, IStyleBookOwner, IDesignerStorage, IOriginalContainerSize)

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

 

ЗАМЕЧАНИЕ

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

На уровне тcoпrnonCustomFonn класса заложен минимально необходимый функцио­ нал:

   объявлены базовые конструкторы и деструктор формы;

   осуществляется у : равление размерами и местоположением формы;

   реализуется отображение формы (в том числе в модальном режиме) и скрытие формы с экрана.

 

Создание, отображение и уничтожение форм

По умолчанию каждая подключаемая к проекту форма (пункт меню File I New 1 FireMonkey НD Form) при старте программы заносится в список форм, подлежа­ щих автом тическому созданию. При запуске приложения первой на свет появля­ ется главная форма проекта, все остальные формы создаются согласно очередно­ сти, указанной программистом. Для изменения очередности создания форм надо заглянуть в опции проекта (Project Options) и расставить формы в надлежащем порядке в списке Auto-create forms (см. рис. 5.4). За создание форм из перечця Auto-create forms несет ответственность приложение Application. Воспользовав­

шись пунктом меню Project I View Source, вы -получите доступ к головному моду­ лю проекта и увидите, что для автоматически создаваемых форм приложение ис­

пользует метод createFonn().


 

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

procedure  Show;

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

Среда проектироваНИ5! позволяет нам отказаться от автоматического создания форм, такие формы переводятся в разряд доступных (список AvailaЫe forms). Но в этом случае перед обращением к такой форме программист должен ее создать.

По законам ООП для создания объекта требуется воспользоваться его конструкто­ ром, и форма не является исключением из правил:

constructor Create(AOwner: TComponent);

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


var frmМain: TfrmМain; //главная форма implementation

{$R *. frnx}

uses child;

 

procedure TfrmМain.ButtonlClick(Sender: TObject);

Ьegin

with TfrmChild.Create(Application) do //создание дочерней формы Ьegin

Caption:=Caption+'$'+IntToHex(Handle,8);//тeкcт заголовка Show; //вывод на экран

end; end;

 

 

ВНИМАНИЕ!

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

 

Форма TfrmChild создается по щелчку на кнопке вuttonl, расположевной на глав­ ной форме проекта frmМain. Основная особенность предложенного в листинге 5.5 кода в том, что будет создано ровно столько клонов формы TfrmChild, сколько вы сделаете щелчков по кнопке. Чтобы формы отличались друг от друга, мы выводим в заголовке создаваемых форм их дескриптор нandle.


ЗАМЕЧАНИЕ

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

 

Полноэкранный вывод

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

property FullScreen: Boolean; //по умолчанию false

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

property ShowFullScreenicon: Boolean;

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

Вывод формы в модальном режиме

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

function ShowМodal: TModalResult;

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

type ТМodalResult = Low(Integer) .. High(Integer);

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

 

Таблица 5.1. Стандартный модальный результат ТМodalResul t

 

Константа

Описание

mrNone или О

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

mrOk ИЛИ idOK

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

mrCancel или idCancel

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

mrAЬort или idAЬort

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

mrRetry или idRetry

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

mrignore или idignore

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

mrYes или idYes

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


 

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

 

Константа

Оnисание

mrNo или idNo

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

mrAll или mrNo + l

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

 

Для передачи результата модальной форме обычно задействуют свойство ModalResult кнопок. Анализируя модальный результат закрывающейся формы, про­ граммист получает возможность направлять поведение программы в определенное русло (листинг 5.6).


if     Foпn2.ShowModal=mrOK then

Ьegin

{код для результата mrOK)

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

 

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

property ModalResult: ТМodalResult;

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

 

Закрытие формы

Простейшим способом спрятать (не уничтожая) форму может стать присвоение свойству VisiЫe значения false. Этот же результат достигается при обращении к процедуре

proaedure Hide;

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

proaedure Close.;

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

 

ЗАМЕЧАНИЕ

С методом Close () связаны два важных собьiтия -      OnGloseQuery () и OnClose () ,

о них мы поговорим отдельно (см. табл. 5.3).


 

Уничтожение формь1

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

prooedure Release;

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

 

Состояние формы

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

property Caption: string;

Местоположение формы на экране компьютера зависит от значения, установленно­ го в свойстве

property Position: TFormPosition;//пo умолчанию poDefaultPosOnly TFormPosition = (poDesigned, poDefault, poDefaultPosOnly,

poDefaultSizeOnly,poScreenCenter,  poDesktopCenter,

poMainFormCenter, poOwnerFormCenter);

При создании новой формы свойство Position принимает значение poDefaultPosOnly. Это означает, что вертикальный и горизонтальный размеры фор­ мы (свойства Width и Height) назначаются разработчиком, а место вывода формы на экран определяется операционной системой. Обратная ситуация сложится в том случае, если свойство Position установлено в poDefaultSizeOnly. Теперь операци­ онная система станет выводить форму в месте, указанном программистом (свойства тор и Left), но ответственность за установку размеров сторон заберет в свои руки. Форма появится точно в центре экрана при передаче в Position значения poScreencenter, а для центровки относительно рабочего стола подойдет значение poDesktopCenter. На поведение вторичных форм проекта влияют poMainFormcenter и poOwnerFormcenter. В первом случае дополнительные формы позиционируются по центру главной формы, во втором - по центру формы-владельца. Интересно пове­ дение формы при установке этого свойства в poDefault. При первом запуске при­ ложения форма отображается в левом верхнем углу экрана, а при каждом после­ дующем выводе на экран форма станет автоматически смещаться вправо и вниз. Высота и ширина формы определяются операционной системой и не зависят от указаний программиста. Если вы намерены самостоятельно управлять как размера­ ми, так и местом вывода формы, то установите свойство в состояние poDesigned.

ЗАМЕЧАНИЕ

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


 

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

property Bordericons:  TBorder�cons;

Различают: кнопку закрытия окна ЬiSystemМenu; кнопки сворачивания ЬiMinimize и

разворачивания ЬiMaximize окна; кнопку обращения к справочной системе ЬiHelp.

Щелчки пользователя по кнопкам сворачивания и разворачивания окна изменяют значение свойства

property WindowState: TWindowState; //по умолчанию TWindowState.wsNormal TWindowState = (wsNormal, wsMinimized, wsMaximized);

Состояние wsNormal говорит о том, что форма в нормальном состоянии; wsMinimized - форма свернута; wsMaximized - форма развернута до максимально возможного размера. Свойство Windowstate не только информационное, оно позво­ ляет сворачивать, разворачивать и нормализовать размеры формы во время выпол­ нения приложения.

Еще одно свойство

property FormState: TFmxFormStates; //только дпя чтения

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

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

property BorderStyle: TFmxFormВorderStyle; //по умолчанию bsSizeaЬle;

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

 

Жизненный цикл формы

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

 

Таблица 5.2. Процесс создания и вывода на экран форм

 

Событие

Описание

OnCreate ()

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

операций, необходимых для дальнейшей ра.боты с формой

OnActivate ()

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

OnPaint ()

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


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

procedure Close;

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

 

Таблица 5.3. Процесс закрытия и уничтожения формы

 

Собь1тие

Описание

OnCloseQuery ()

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

OnClose ()

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

OnDestroy ( )

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

OnDeactivate ()

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

 

Перед закрытием формы производится вызов обработчика события:

property OnCloseQuery: TCloseQueryEvent;

type TCloseQueryEvent = procedure(Sender: TObject;

var CanClose: Boolean) of oЬject;

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


var filename:striпg; //переменная с именем обрабатываемого файла TextModified:boolean; //признак, что текст подвергался изменениям

 

procedure TForml.FormCloseQuery(Sender:  TObject;  var CanClose:  Boolean);

Ьegin

if TextModified=true then

CASE MessageDlg('Coxpaнить изменения в тексте?', TMsgDlgType.mtConfirmation,

[TMsgDlgBtn.mЬYes] + [TMsgDlgBtn.mЬNo]+ [ТМsgDlgBtn.mЬCancel],

-1)      OF

mrYes : if     FileExists(FileName)=false then Ьegin

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

//

end  else Memol.Lines.SaveToFile(FileName);


 

mrNo: CanClose := true;

else CanClose := false;

END;

end;

 

 

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

property OnClose: TCloseEvent; TCloseEvent = procedure(Sender: TObject;

var Action: TCloseAction) of oЬject;

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

 

Таблица 5.4. Возможные значения параметра Action: TCloseAction

 

Значение

Описание

caNone

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

caHide

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

caFree

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

caMinimize

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

 

Немного поозорничав с параметром Action в С<?бытии 6nClose (), можно создать очень вредную программу, которая ст_анет активно сопротивляться своему закры­ тию. Для этого достаточно переписать обработчик события главной формы проекта так, как предложено в листинге 5.8.


procedure TForml.FormClose(Sender: TObject; var Action: TCloseAction);

Ьegin

Action:=TCloseAction.caNone;

with TForml.Create(Application)  do Show(};

end;

 

 

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

property OnDestroy: TNotifyEvent;

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


Доступ к элементу управления ·по его координатам

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

function ObjectAtPoint(P: TPointF): IControl;

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

 

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

Одна из задач, с которой рано или ПОЗДНО столкнется разработчик проектов для мо­ бильных платформ, связана с адаптацией приложения к экранам с различным раз­ решением. Возьмем хотя бы размеры экранов у "наладонника" iPad и смартфона iPhone - они существенно отличаются. Как быть в таком случае?

Поставленная задача в Delphi имеет весьма элегантное решение. Нам только надо вспомнить·о существовании у формы следующих свойств:

property FormFactor: TFormFactor;

property FormFamily:  string;

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

 

ЗАМЕЧАНИЕ

Свойства FormFactor и FormFamily позволяют не только объединять в одном прило­ жении различные формы IOS, но и создавать настольное приложение, предназначен­ ное для работы на разных операционны_х системах.

Для демонстрации возможностей FormFactor и FormFamily даже не нужно програм­ мирования. Достаточно просто запомнить порядок действий.

1.    Создайте новое мобильное приложение, которое (как мы надеемся) будет со­ вместимо и с iPhone, и с iPad.

2.    Переименуйте пока единственную формы проекта в frm_iPhone - это главная форма для iPhone.

3.    Обратитесь к Инспектору объектов и в свойстве FormFactor. Devices формы (вы­ брав элемент dkiPhone и сняв флажки с dkDesktop и dkiPad) укажите, что она предназначена для работы только с iPhone (рис. 5.6). А в свойство FormFamily передайте строковое значение мain.

 

ВНИМАНИЕ!

Хранящееся в свойстве FormFamily значение Main указывает на то, что форма явля­

ется главной для определенной в FormFactor мобильной платформы.


,,

 

Приложение FireMonkey                                                                                            107

 

4.    Добавьте к проекту еще одну форму iOS, на этот раз назовите форму frm _ iPad.

5.    Воспользовавшщ:ь Инспектором объектов, укажите, что fпn_iPad рассчитана на

работу С iPad (FormFactor. Devices: = [dkiPadJ ).

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

 

 

 

 

 

 

 

 

 

 

 

 

DevlCeC                          1 (dflil'-hwle]

'lrJFe _ -·

       J

 

J Orlenta\iont                                                    мdscllpe,

soPortrillt                              Т1uе

10L1nd1c1pe                    True

IO!nverll!<f>artrillt       Тnм, solnvertecl.andsclp,,         Тnм,

jWidth                      '3:Ю

;wnly                            Ни,

t'!ЗF-

.               Desq,er

mn_Fhone

(Пoands)

cxSyst8!'0efalt

 

 

 

 

11.shown

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

 

 

Качество графического вывода

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

property Quality: TCanvasQuality;

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


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

 

Форма HD FMX.Forms. TForm

Форма НD (High Definition) во многом похожа на обычную форму VCL, поэтому если вы имеете хотя бы небольшой опыт программирования в предыдущих версиях Delphi, то быстро разберетесь со всеми нововведениями. Ключевых изменений все­ го два.

Во-п_ервых, при позиционировании и определении размеров форма НD окончатель­ но перешла на вещественные числа.

Во-вторых, у формы FМХ. Fonns. TFoпn принципиально изменен порядок работы с графикой. Теперь в своей работе форма НD по умолчанию не задействует механизм графического вывода Windows GDI. Вместо этого новая форма нацелена на работу с DirectX (если приложение выполняется под управлением Windows) или Quartz 2D (для приложения OS Х). Доступ к универсальному графическому механизму пре­ доставляет интерфейс rscene. Вполне естественно, что смена графического "движ­ ка" привела к кардинальным переменам в недрах класса-холста тcanvas (см. главу 16).

 

ЗАМЕЧАНИЕ

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

 

Трехмерная форма FMX.FormsЗD. TFormЗD

Трехмерная форма FireMonkey -     это без всякого преувеличения инновационный               ,

класс, позволивший нам создавать управляемые ЗD-сцены несколькими щелчками

мыши.  Код  реализации  трехмерной  формы  сосредощч н     в  рамках  класса

тcustomFoпnЗD (модуля FМХ. FonnsЗD). Объявление класса

type TCustomFoпnЗD =

class(TCommonCustomFoпn, IContextObject, IViewportЗD)

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

 

ЗАМЕЧАНИЕ

Будьте внимательны при переработке своих старых проектов. В Delphi ХЕ2/ХЕЗ трех­ мерная форма описывалась в модуле FМХ. Foпns, с выходом ХЕ4 форма TFormЗD перекочевала в модуль FМХ. FonnsЗD.

 

ЗАМЕЧАНИЕ

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


 

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

]Jена из точки (О, О, О) вправо, ось ординат у -     вниз, виртуальная ось аппликат z

уходит в глубину формы (рис. 5.7).


Рис. 5.7. Система координат TFormЗD

 

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

 

ВНИМАНИЕ!

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

Еще одной важной особенностью ЗD-формы является то, что графический вывод осуществляется с помощью трехмерного контекста графического устройства FМХ. TypesЗD. тcontextЗD. Доступ к контексту предоставляет свойство

property Context: TContextЗD;

Процесс графического вывода сопровождается генерацией события

property OnRender:. TRenderEvent;

type TRenderEvent =

procedure(Sender: TObject; Context: TContextЗD) of object;

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


 

ВНИМАНИЕ/

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

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

property Color: TAlphaColor;

Для просмотра трехмерных сцен форма инкапсулирует камеру (объект тcamera). Камера - это не что иное, как удобная абстракция наблюдателя. Вь брав ту или иную позицию наблюдения, вы сможете взглянуть на одну и ту же сцену с разных ракурсов. Интегрированная в состав формы камера установлена прямо перед сце­ ной. По умолчанию камера включена, для ее отключения установите в false свой­ ство

property UsingDesignCamera: Boolean;//пo умолчанию true

 

ЗАМЕЧАНИЕ

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

От состояния свойства

property Мultisample: ТМultisample; //по умолчанию Multisample.rns4Samples TMultisample = (rnsNone, rns2Samples, ms4Samples);

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

 

Пример ЗD-проекта

Создайте новый проект ЗD. Для этого следует воспользоваться пунктом меню File 1 New I FireMonkey Desktop Application и в открывшемся диалоге FireMonkey Desktop Application выбрать 3D FireMonkey Application. Сохраните проект под любым именем и перейдите к главной форме приложения.

Сразу раскроем секрет нашего примера - мы создадим модель вымышленной пла­ нетарной системы. Для этого разместите на главной форме следующие компоненты (рис. 5.8):

   сфера Sphere_sun: TSphere возьмет на себя функции единственной звезды, вокруг которой станут двигаться планеты. Расположите сферу точно в центре формы

(свойство Position= (О, о, О)). Воспользовавшись свойствами wictth, нeight и oepth, придайте светилу достойные размеры (например, 4, 4, 4);

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

Light:TLight, который вы найдете на странице 3D Sсепе. Поместите компонент


 

на форму и присоедините к Spherel (врезультате его положение Position совпа­ дет с положением "звезды"). Переключите свойство LightTyp@ в состояние ltPoint, это заставит источник света испускать свои "фотоны" во всех направле­ ниях;

   разместите на форме несколько сфер-планет TSphere. В примере использованы три компонента, но вашу фантазию я ограничивать не собираюсь;

    компонент таймер Timerl: TTimer возьмет на себя управление процессом движе- ния планет. Установите интервал отсчета равным 100-300 мс.

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

f х =acost,

LY = bsint,

где О t     21t.

Здесь а и Ь описывают большую и малую полуоси эллипса.


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


 

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


var Foпnl: TFoпnl; tl,t2,t3:single;

 

const аl=З;      Ь1=4;     //полуоси эллиптических орбит а2=4;      Ь2=8;

аЗ=lО; Ь3=5;

implementation

 

{$R *.frnx}

 

proaedure TFoпnl.FoпnЗDCreate(Sender:  TObject);

Ьegin

tl:=0;            //инициализация исходных значений t2:=pi/2;

tЗ:=pi/4;

end;

 

proaedure TFoпnl.TimerlTimer(Sender: TObject);

Ьegin

Sphere2.Position.X:=al*cos(tl); //орбита планеты 1 Sphere2.Position.Y:=Ы*sin(tl);

 

Sphere3.Position.Z:=a2*cos(t2); //орбита планеты 2 Sphere3.Position.Y:=b2*sin(t2);

 

Sphere4.Position.X:=a3*cos(t3); //орбита планеты З Sphere4.Position.Z:=ЬЗ*sin(t3);

 

//приращения определяют скорость движения по орбитам tl:=tl\0.1; t2:=t2+0.15;  tЗ:=tЗ-0.25;

end;

 

 

Основные расчеты осуществляются в рамках события onтimer () . Самостоятельно подберите интервал срабатывания таймера, чтобы получить реалистичную картину перемещения планет.

Теперь проявим заботу о внешнем виде планет и звезды (компонентов TSphere). С помощью MaterialSource трехмерные компоненты Delphi могут подключаться к специализированным невизуальным компонентам, способным служить источни­ ками данных ДЛЯ заливки их видимой поверхности. Источники заливки вы обнару­

жите на вкладке Materials палитры компонентов FireMonkey, в их число входят: способный управлять цвето заливки компонент  TColorMaterialSource; текстура


sшлиRки 'Г'ГF>xtлrF>MaterialSource; компонент TlightMaterialSource, отвечающий за отражение света от поверхности объекта (см. главу 29).

Редактор структуры Structure (который на рис. 5.9 запечатлен поверх главного ок­ на нашего приложения) раскрывает суть подхода к визуальному проектированию. Для создания фона звездного неба мы задействовали источник текстурной заливки TextureMaterialSourcel, занесли в его свойство тexture файл с подходящей фото­ графией и подключили компонент к заднему плану нашей сцены Planel. Для ими­ тации звезды, находящейся в центре планетарной системы, мы присоединили к свойству MaterialSource сферы Sphere_Sun компонент, отвечающий за текстуру, взаимодействующую со светом LightMaterialSourcel, в которую также была загру­ жена подходящая текстура. Точно так же можно поступить и с другими сферами­ планетами и текстовыми надписями.

 

Совместное применение 2D- и ЗD-компонентов

Клиентская область формы TFoпnЗD нацелена на работу с трехмерными объектами (потомками класса тcontrolЗD). Если.программист попробует расположить на по­ верхности формы любой двухмерный элемент управления, то он не встретит осо­ бых возражений, но и элемента управления форма не отобразит. Причина такой избирательности проста - двухмерный компонент не в состоянии объяснить ЗD­ форме ни места своего размещения, ни способа прорисовки.

Для решения указанной проблемы обычно применяют компонент TLayerЗD, это специалист по совмещению несовместимого - 2D-объектов и трехмерной формы. Способ решения проблемы очень прост. С одной стороны, компонент TLayerЗD по­ нятен трехмерной форме, ведь он опирается на фундамент родного для ЗD-проектов класса тcontrolЗD и легко позиционируется в трехмерной системе ко­ ординат формы TFoпnЗD. С другой стороны, клиентская область компонента TLayerЗD имитирует привычную для 2D-компонентов плоскую двухмерную систему координат. Начала координатных осей (О, О) находятся в левом верхнем углу ком­ понента, ось х направлена слева направо, а ось у - сверху вниз. Подобный подход устраивает кнопки, строки ввода, метки, списки и другие двухмерные визуальные компоненты со страниц палитры FireMonkey.

Один из примеров совместного применения 2D- и ЗD-визуальных элементов управ­ ления предложен на рис. 5.9. Компонент LayerЗDl: TLayerЗD со свойством Projection, установленным в состояние pj Screen, предлагает свою поверхность в распоряж ние классических двухмерных элементов управления.

Обратите внимание на еще одну особенность проекта с рис. 5.9. При размещении ЗD-компонентов, отвечающих за обслуживание модели биплана времен Первой мировой войны, мы отказались от 'Прямых услуг формы. Вместо этого оставшаяся свободной клиентская область окна бьmа передана в распоряжение второго компо­ нента Layer3D2: TLayerЗD. На Layer3D2, в свою очередь, расположилась область просмотра ViewPortЗDl: TViewPortЗD. В результате функционал формы (связанный с размещением и рендерингом трехмерных моделей) перешел к области просмотра

ViewPortЗDl.



!/11/ffl1/l1fl,'l!//ll  lllllfllll,'lllilflllffll/l/lllffl/l!lfff//lll,,,,..,ЮD,,.,и,,,,,,,....._...,m,.;""N,ш•aw1•,,.,,,.;..,..,.,,..,_.........."...".".'""'w•-1',1,                                                        -·-·---·-----·--·-·-·--·-·---·-·--·-·----·---·-·-----

 

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

 

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

На странице Standard палитры компонентов FМХ находится компонент тstyleBook, позволяющий осуществить индивидуальную настройку стиля оформле­ ния формы. Двойной щелчо'к по компоненту или обращение к свойству

property Resource: TStrings;

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

 

ЗАМЕЧАНИЕ

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

При желании вы можете внести правки в правила оформления того или иного типа элемента управления, воспользовавшись утилитой Bitmap Style Designer, доступ к которой вы найдете в меню Tools среды проектирования. ·

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

property StyleBook:  TStyleBook;


У компонента 'PStyleBook предусмотрено свойство

property FileName: string;

которое позволяет легко изменять стилевое оформление формы даже во время ра­ боты приложения под управлением Windows, для этого в него следует передать имя файла *.style. Здесь нам понадобится лишь помощь диалога открытия файла OpenDialogl: TOpenDialog и кнопки, по щелчку которой этот диалог будет вызван (листинг 5.10).


uses System.IOUtils;

// ...

procedure TForml.FormCreate(Sender: TObject);

const dir='C:\Users\PuЬlic\Documents\'RAD Studio\11.0\Styles';

Ьegin

Forml.StyleBook:=TStyleBook.Create(Forml);

if TDirectory.Exists(dir) then OpenDialogl.InitialDir:=dir;

end;

 

prooedure TForml.ButtonlClick(Sender: TObject);

Ьegin

if OpenDialogl.Execute then Ьegin

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

end;

 

 

ЗАМЕЧАНИЕ

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

 

 

Подключение ресурсов и изображений

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

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

С этой целью выбираем пункт меню Project I Resources and Images, затем в по-


 

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

 

 

 

 ,  ·                      а

-l;-jj - t5                !                            

··(-,_h.,; Тt Ъэ 1 il•it;!4 с.

Rle

•:@ Projec!Groupl

i В i) ch19_01.exe

·         (fJ4 Вuik! Configurations (PeЬug)

$ С Target Platforms (Win32)

6t E'J bltmaps

1111

фJm'!v,l'fl9

@mov.png ri)mpз.png Щ§mp4.png

@wav.png @wma.png

@v.mv.png

ffiifjJ aЬout.pas

_, ffi ifj} Unitl,pas , ,

,._,  ,   ,_,_,_,_,                     ---,-·--·-

01\_Вooks\_Areмonkey XESВII

h                ,                                    1

ii?i'Qch19_01.d;i"oJ •ProJ...

ф Reюurces for ch19_01.dP")j

Resource !les

 

Filename

  -..:::::.J

png

png @mpЗ.png @mp4.png

 

 

Туре

!dentifler         .

дV!

m+,                    j!

mov

mрЗ                  .

mp4

1 ! в.--е j

1  ок       ) 1

Pr

Resource igent!fier: --

AVI

 

 Resource typ,.!:                      

RCDATA

 

 

с.1С111  1 1      ...

 

 

RCDATд

RСDдТд

 

RCDATA

RCOATA

 

.1

RCDATA

 

1     lldd-..

 

 

1

 

 
ch19_01.dproj - Project Maмger

 

 

'

 

--

 


 


 

   


 

 

Рис. 5.10. Менеджер проектов и окно подключения ресурсов к приложениtq

 

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

 

 

••••••••••••••.. •••••••••••••••••••••••••.. ••••......., ·•¼'"'<·- ,''"""" •··•', ''-•-••00,0,,,.,.,.._,.'ff.-- .. •••••••=                      ••-•- ''- •·--•••....••••••·.. •••••••••••••••,,•,. ••••••••••,-•••••••••.. •••••••!.! ....•••••.. •••••••,,.,,..,.•.,...... _...,.,,,••

-1.... ..............

 
!nиc...     1.11.-........,.s111111-•p11••.....,1    А    ,

........,.. .............,., ·· ...- ••••••••••• .......................,..-····'"•··.........................,.. .......................................... ..............................................................,. .......\о...                                                         '"4 .-.,.-...

 

vars_ravi, rs_mpЗ, ...       : TResourceStream;

 

procedure TForml.FormCreate(Sender: TObject);

Ьegil'l

if FindResource(0, 'AVI', PChar(RT_RCDATA)) <> О then rs_avi:=TResourceStieam.Create(0, 'AVI',PChar(RT_RCDATA));

if     FindResource(0,  'mрЗ', PChar(RT_RCDATA)) <> О then

rs_mpЗ:=TResourceStream.Create(0, 'mpЗ',PChar(RT_RCDATA));

// ...

end;

 

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


,......•.. •J.•· ',r,:J"-•••••.............................................. H• ••••••••••••..

 

.,                                                                                                                                            ,,,,r'/l'ff-Hlf,,lfUlhilH//ClcRll;l./iU:i..,,;i................................... 1,f;-,,,-h.t•;-,.,_:,,;,,i;,;,,·,,·, •-••••••.. •;••·•••>">i,;

Irnagel.Bitrnap.LoadFromStrearn(rs_avi); Irnage2.Bitrnap.LoadFromStrearn(rs_mp3);

 

ЗАМЕЧАНИЕ

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


 

 

ГЛАВА 6

 

 

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

 

 

 

 

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

В FireMonkey различают три класса меню:

    главное меню, создаваемое на основе компонента тмainМenu;

    планка меню (тмenuBar);

    контекстное меню (тPopupMenu).

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


Рис. 6.1. Поведение главного меню в OS Х

 

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



Рис. 6.2. Поведение планки меню TMenuBar в OS Х

 

вать интерфейс, схожий с традиционным интерфейсом Windows, в котором главное меню располагается сразу под заголовком окна (рис. 6.2).

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

Несмотря на то, что для создания меню написано целых три класса, у них много общего:

    все компоненты меню предназначены для хранения управляющих элементов,

создаваемых на основе класса тмenuitem;

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

Компоненты меню снабжены специализированным редактором Items Designer, значительно упрощающим процесс построения меню. Для вызова редактора меню достаточно дважды щелкнуть левой кнопкой мыши по компоненту меню (рис. 6.3) или выбрать пункт Items Editor в контекстном меню компонента. Для создания пункта меню следует нажать кнопку Add Item и настроить параметры появившего­ ся элемента меню в Инспекторе объектов. Элементы меню способны обладать под­ меню, для создания которого достаточно воспользоваться кнопкой Add Child Item. При необходимости можно переупорядочить пункты меню - для этого предназна­ чены кнопки с изображениями стрелок. Для удаления лишнего пункта просто на­ жмите кнопку Delete.



120                                                                                                                                                            Глава 6

 

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

 

 

Элемент меню TMenultem

Вне зависимости о того, какой из компонентов меню (тмainМenu, тмеnuВаr или TPopupMenu) вы задействуете в своем приложении, в любом случае меню будет состоять из элементов, основанных на классе тмenuitem. Поэтому рассмотрение по­ рядка работы с меню мы начнем с изучения их основного "строительного" блока - класса TMenuitem.

 

ВНИМАНИЕ!

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

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

property Text: string;

Но по сравнению со всеми остальными компонентами Delphi, у свойства техt эле­

!!!' !!!9 сть }'!Цl;J TB1;JHHЫ1;J особенности. Во-первых, если R ягrнтп1ю11" f>уд,,,., введен один-единственный символ "тире" (-), то пункт меню превр тится в разде­ литель. Обратите внимание на рис. 6.3. Здесь между элементами Вставить из буферй... и ВыбрйТL всё вставлена горизонтальная черточка. Это и есть пункт ме-

 

 



 

ню "разделитель". Он не способен нести функциональную нагрузку и реагировать па Щ!Ш'ЮК пользователя, его з дача - улучшение наглядности приложения. Втор я особенность свойства техt - возможность определения клавиш-акселераторов, ускоряющих доступ пользователя к пункту меню при одновременном нажатии

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

 

ЗАМЕЧАНИЕ

Если на роль главного меню приложения был выбран компонент TMenuBar, то клави­ ши-акселераторы использовать не следует.

 
Помимо акселераторов, с каждым пунктом меню можно связать так называемые "быстрые 1rnавиши". Отличие быстрых клавиш от клавиш-акселераторов заклю а­ ется в том; что выбор акселератора только позволит добраться до необходимого пункта меню, а нажатие комбинации быстрых клавиш заставит приложение выпол­ нить сопоставленный с ними пункт меню. Быстрые клавиши определяются свой­ ством

property ShortCut : TShortCut;

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

Картинка загружается в свойство

property Bi trnap: TBitrnap;

Оформление текстовой надписи опирается на свойства и методы, унаследованные от класса ттextControl.

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

"property IsSelected: Boolean;

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

property OnClick: TNotifyEvent;

 

Элемент меню в виде флажка

При необходимости отдельный пункт меню можно превратить в элемент управле­ ния, напоминающий флажок (тсhесkВох) или кнопку выбора (тRadioButton). Таким чудесным возможностям тмenurtem в первую очередь обязан свойству

property IsChecked: Boolean;

Установив это поле в состояние true, мы увидим "галочку" слева от заголовка. Этим самым пункт меню сигнализирует нам, что он отмечен. Контролируя это


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


procedure TFoпnl.MenuitemlClick(Sender: TObject);

Ьegin


if Menuiteml.IsChecked=true then

else


//операция 1

//операция 2


end;

 

Для того чтобы пункт меню при щелчке по нему автоматически помечался галоч­ кой, следует перевести в true свойство:

property AutoCheck: Boolean;

в противном случае придется делать это вручную внутри события OnClick () . В про-

            стейшем случае это будет всего-навсего одна строка кода

Menuiteml.IsChecked:= NОТ Menuiteml.IsChecked;

инвертирующая предыдущее состояние пункта меню.

 

Группировка элементов меню

Вторым действием по превращению элементов меню в переключатели станет уста­ новка (у всех входящих в группу элементов) свойства

property Radioitem: Boolean;//пo умолчанию false

в состояние true.

Несколько элементов меню не сложно объединить в группу, превратив ее элементы в,переключатели (по поведению повторяющие кнопки TRadioButton).

Для тбfб чтобы научиться превращать несколько пунктов меню в группу выбора, расс отрим крохотный пример (рис. 6.4). Создайте новый проект, разместите на нем компонент тмainМenu. Создайте пункт меню верхнего уровня с заголовком "Цвет формы" и четыре подчиненных ему пункта:

   элемент с заголовком "Белый". Имя элемента -  miWhite, свойство tag=0;

О элемент "Красный", tag=l;

   элемент "Зелёный", tag=2;

  элемент "Синий", tag=З.

Наша задача -  при щелчке по пункту меню перекрашивать фигуру Rectanglel:

тRectan9ie в соответствующий цвет. Теперь одновременно выделите все четыре

только что создаппых пупкта меню и в Инспекторе объектов найдите свойство

propert.y Groupindex: Byte;

 

 

 



 

 

 

 

Красный

шj!ныi\

С11н11А

,/        Бсп111А

 

 

Рис. 6.4. Группа выбора из элементов меню

 

Это свойство предназначено для создания логических групп пунктов меню. По умолчанию, каждый вновь создаваемый элемент тмenuitem не входит ни в одну группу (Groupindex=0), но если атрибуту Groupindex двух (или более) пунктов меню присвоить отличное от нуля значение, то мы получим возможность объединять элементы в некоторые товарищества, что сейчас собственно и сделаем. Введите в это свойство любое положительное число, например 1.

Следующим шагом по превращению четырех пунктов меню в группу выбора будет установка в true свойства:

property Radioitem: Boolean;

Перевод свойства Radiortem в значение true приведет к тому, что пункт меню ста­ нет вести себя аналогично компоненту TRadioвutton. Для того чтобы выяснить, не отмечен ли элемент меню флажком, надо обратиться к уже знакомому нам свойст­ ву IsChecked, а Delphi позаботится о том, чтобы в одной группе не могло быть по­ мечено более одного элемента тмenuitem одновременно. И в завершении убедитесь, что свойство AutoCheck всех четырех элементов установлено в true.

Нам осталось описать щелчок по любому из пунктов меню, например по miWhite (листинг 6.2), и сделать это событие общим для всех оставшихся пунктов меню, входящих в группу выбора.


procedure TForml.miWhiteClick(Sender: TObject);

Ьegin

case TMenuitem(Sender) .Tag of 0:Rectanglel.Fill.Color:=TAlphaColorRec.White; //белый 1:RectangleJ.Fill.Color:=TAlphaColorRec.Red;         //красный 2:Rectanglel.Fill.Color:=TAlphaColorRec.Green; //зеленый 3:Rectanglel.Fill.Color:=TAlphaColorRec.Blue;               //синий

end; end;


Доступ к дочерним элем нтам м'еню

Любой элемент меню имеет возможность обладать дочерними элементами. Для программного доступа к этим элементам следует восподьзоваться свойством

property Children[Index: Integer]: TFrnxObject;  //только для чтения

единственный параметр метода определяет индекс интересующего нас пункта меню диапазоне значений от О до ChildrenCount-1).

 

ВНИМАНИЕ!

Все рассматриваемые в главе компоненты также способны обратиться к дочерним элементам меню с помощью свойства Children.

 

 

Главное меню TMainMenu

Главное меню приложения класса тмainМenu специализируется на хранении и пре­ доставлении доступа к элементарным пунктам меню (реализуемым из класса

TMenuitern).

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

В последнем случае из кода программы следует вьIЗвать метод

procedure AddObject(AObject: TFrnxObject);

 

Планка меню TMenuBar

В библиотеке FireMonkey планка меню тмеnuваr выступает своего рода усиленной версией главного не только главного) меню приложения. Кточевое отличие планки меню тмеnuВаr от его коллеги -                                                                   компонента TMainМenu в том, что планка может быть размещена в любом месте формы, она может перемещаться и менять размеры. Главное меню TMainМenu такими возможностями похвастаться не в со­ стоянии.

 

ЗАМЕЧАНИЕ

В Delphi ХЕ2/ХЕЗ планка меню была способна легко принять "обличие" главного меню приложения с учетом особенностей поведения и отображения меню текущей опера­ ционной системы, для этого требовалось передать значение true в свойство UseOSMenu. Начиная с Delphi ХЕ4, у TMenuBar свойство UseOSMenu исчезло...

 

Контекстное меню ТРорирМепц

Существенная часть построенных на основе класса тcontrol визуальных элементов управления обладает правом показа контекстного (всплывающего) меню. Для этого элементы оснащены свойством, предназначенным для подключения к ним компо­ нента TPopupMQnu

property  PopupMenu:  TCustornPopupMenu;


 

После подключения контекстное меню автомати'Iески вызываете» на :жр11u в МО•

менr щелчка правой кнопкой мыши по элементу управления.

Если у элемента управления не предусмотрено свойство PopupMenu, то это не явля­ ется препятствием для работы с компонентом TPopupмenu. В данном случае про­ граммисту следует взять управление процессом вызова меню в свои руки и вос­ пользоваться методом

procedure Popup(X,  У: Single);

Единственное, о чем следует позаботиться -  это указать экранные координаты (х, У) вывода меню (листинг 6.3).


procedure TFoпnl.FoШIМouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; Х, У: Single);

var P:TPointF;

Ьegin

if  Button=TMouseButton.mЬRight then

Ьegin

P:=Foпnl.ClientToScreen(PointF(X,Y)); PopupМenul.Popup(P.X,P.Y);

end;

end;

 

Наличие метода, отображающего контекстное меню, предполагает и существование метода-антипода

procedure CloseMenu;

закрывающего меню.


 

 

 

 

 

 


ГЛАВА 7

 

 

Командный интерфейс

 

 

 

 

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

свой выбор, по крайней мере, на одном из трех возможных вариантов действий: выбрать пункт главного меню File I Save АН; щелкнуть по соответствующей кнопке на панели инструментов; нажать сопоставленную операции сохранения проекта комбинацию быстрых клавиш <Shift>+<Ctrl>+<Alt>. Наличие трех счособов со­ хранения проекта не означает, что создатели ШЕ Delphi во время написания среды проектирования трижды повторили одни и те же строки кода. Такое решение было

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

Обратите внимание на еще одну особенность поведения элементов управления ви­ зуальной среды проектирования Delphi. После полного сохранения проекта отве­ ЧilЮЩИ:Й :пу_операциюэлемент меню и кноuка элементов управления псрсспuот быть активными - это свидетельствует о том, что все изменения в исходном коде успешно отправлены на жесткий диск компьютера. Но как только программист внесет малейшее исправление в листинг программы, соответствующие элементы управления вновь перейдут в режим готовности выполнить свою задачу - сохра­ нить изменения в коде.

Элементы пользовательского интерфейса современного приложения должны не просто обладать способностью вызывать ту или иную процедуру, но и уметь отра­ жать текущую обстановку. Для достижения поставленной цели в состав FireMonkey были введены командные объекты. В настоящей главе мы познакомимся с наибо­ лее распространенной реализацией команды -  классом TAction.

Командный объект TAction создается и хранится в специализированном контейне­ ре, в роли которого имеет право выступать список команд TActionList. Список команд TActionList обычно задействуется в приложениях, интерфейс которых

 

 



 

строится на основе обычных компонентов меню (тмainМenu и TPopupMenu) и стан­ дартных элементах управления (кнопок твutton).

 

Команда TAction

Наиболее востребованный тип командного объекта создается на основе класса TAction. Кроме решения основной задачи, поставленной перед командой (открытие файла, сохранение данных, вызов метода и т. п.), команда выполняет ряд дополни­ тельных заданий:

  централизация управляющего кода в рамках события OnExecute ();

  предоставление связанным с командой элементам управления права вызова управляющего кода;

   установка подключенных к команде элементов управления в актуальное состоя- ние.

Для того чтобы сразу ощутить все преимущества, которые приобретает приложе­ ние, разработанное на основе командных объектов, рассмотрим небольшой пример НD. Создайте новый проект и на его главной форме расположите следующие эле­ менты управления:

  список команд TActionList;

  главное меню TMainМenu;

  контекстное меню TPopupMenu;

  три кнопки любого типа, например твutton;

  многострочный редактор тмеmо.

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

Дважды щелкните по менеджеру команд и трижды нажмите кнопку New Action

в появившемся редакторе. Настройте свойства тройки новых команд:

   Name=' асСору'; Caption=' Копировать'; ShortCut=' Ctrl+С';

   Name='acCut'; Сарtiоn='Вырезать'; ShortCut='Ctrl+x•;

   Name,=' acPaste'; Caption= 'Вставить'; ShortCut=' Ctrl+V'.

Мы подошли к самому главному -     опишем управляющий  код в событиях

OnExecute ()  каждой из команд (листинг 7.1).

......."'  ................,,..................,.. ...............· ....... ........... ··". ,i.

  .................--...............................,................... ,

procedure TForml.acCopyExecute(Sender: TObject);

Ьegin

Memol.CopyТoClipboard; //копируем текст в буфер end;


procedure TForml.acCutExecute(Sender: TObject);

Ьegin

Memol.CutToClipboard;  //вырезаем текст в буфер

end;

 

procedure TForml.acPasteExecute(Sender: TObject);

Ьegin

Memol.PasteFromClipboard; //забираем текст из буфера

end;

 

Создайте три пустых элемента в главном и всплывающем меню проекта и, восполь­

зовавшись свойством Action, подключите пункты меню к командам. То же самое сделайте и с кнопками. Сразу после установления связи между командой и пунктом

меню с последним происходят чудесные· метаморфозы. Заголовок и всплывающая подсказка переключаются на показ соответствующих свойств ассоциированного командного объекта (рис. 7.1). Но самое главное в том, что с этого момента щелчок по пункту меню и кнопке станет вызывать метод OnExecute ( ) связанной с ними команды. Таким образом, вместо "размазывания" управляющего кода по многочис­ ленным элементам управления пользовательского интерфейса нашего приложения он сосредотачивается в руках командного объекта, а все подключенные к нему элементы управления получают право на вызов этого кода.

 

ЗАМЕЧАНИЕ

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


Рис. 7.1. Список команд TActionList с командами


Нам осталось сделать цоследний важный штрих - научить наши команды оцени­ вать окружающую обстановку. Например, команды копирования и вырезки текста в буфер обмена не могут быть активны до .тех пор, пока в текстовом редакторе не выделено ни одного символа. Команда вставки текста из буфера обмена в поле ре­ дактирования должна активироваться только в том случае, если в буфере находится текстовая строка. Для установки командных объектов в актуал ное· состояние предназначены их события OnUpdate (листинг 7.2).


uses FМX.Platfoпn;  //модуль  для работы интерфейса IFМXClipboardService

/ / ...

procedure TFoпnl.acCopyUpdate(Sender: TObject);

Ьegin acCopy.EnaЫed:=(Memol.SelLength>0); end;

 

procedure TFoпnl.acCutUpdate(Sender: TObject);

Ьegin acCut.EnaЬled:=(Memol.SelLength>0); end;

 

procedure TFoпnl.acPasteUpdate(Sender: TObject); var ClipService: IFМXClipboardService;

s: string;

Ьegin

if  TPlatfoпnServices.Current.SupportsPlatfoпnService(

IFМXClipboardService, IInterface(ClipService)) then

Ьegin s:=ClipService.GetClipboard.ToString; acPaste.EnaЬled:=s.Length>0;

end else acPaste.EnaЫed:=false;

end;

 

 

Демонстрационный проект готов. Запустите приложение и проверьте поведение пунктов меню и кнопок.

 

Связь с элементом управления

Теперь мы знаем, что любой командный объект умеет взаимодействовать со стан­ дартными элементами управления, берущими свое начало от класса тcontrol. В первую очередь к таким элементам относятся пункты меню тмenuitem и различ­ ного рода кнопки.

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

property Action: TBasicAction;


Свойство доступно в Инспекторе объектов, так что для подключения элемента управления к команде программисту достаточно пару раз щелкнуть кнопкой мыши. Визуально появление связи отразится на свойствах техt, Checked, EnaЫed, нelpContext, Hint, Shortcut и VisiЫe нашего элемента управления. Содержимое перечисленных полей обновится в соответствии со значениями в аналогичных свойствах командного объекта TAction. Но самое главное, что с этого момента эле­ мент управления получит право на вызов метода Execute () команды. Как правило, для этого автоматически переопределяется обработчик события onclick () . Так что теперь щелчок по кнопке или пункту меню вызовет <.0бытие OnExecute () командно­ го объекта..

 

Выполнение команды

Ключевым методом любого командного объекта выступает функция

function Execute : Boolean;

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

 

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

 

Источник

Событие

Описание

TActionList

property OnExecute :

TActionEvent;

Событие генерируется у списка ИЛL/! менед- жера команд в зависимости от того, кому из

 

type TActionEvent = procedure(Action: TBasicAction;

них принадлежит команда.

В первом параметре Action окажется ссыл- ка на команду, чей метод Execute () сейчас

 

var Handled: Boolean) of object;

вызывается. Манипулируя параметром

Handled, мы сможем разрешить (false) или запретить (true) дальнейшее выполнение

 

 

команды

TAction

property OnExecute TNotifyEvent;

:

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

 

 

Установка команды в актуальное состояние

В перечне событий команды второе по значимости место занимает событие обнов­ ления статуса команды

property OnUpdate: TNotifyEvent;

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


команда способна контролировать текущую обстановку с минимальным расходом системных ресурсов, такое решение называется обновлением по требованию (update-on-demand approach).

 

Связь команды с контейнером

Командный объект должен храниться в специальном компоненте-контейнере TActionLisiё. Для идентификации контейнера, которому принадлежит команда, предназначено свойство

property ActionList: TCustomActionList;

Индекс команды в списке контейнера находится в свойстве

property Index : Integer;

Исключительно для удобства программиста во время визуального проектирования команды могут группироваться по категориям. Название категории присваивается свойству

property Category: String;

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

 

Предопределенные команды

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

Создайте мобильное приложение для iPhone и разместите на рабочей форме сле­ дующие элементы управления:

  список команд TActionList;

  контейнер для изображений тrmage;

  кнопку TBu ton.

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

Выберите кнопку и в Инспекторе объектов найдите свойство Action. Р!13верните комбинированный список и, пройдя по цепочке New Standard Action 1 Media Library, выберите предопределенную команду TTakePhotoFromCameraAction (рис. 7.2).

В окне Инспектора объектов перейдите на вкладку Events и найдите событие

OnDidFinishTaking () для только что созданного командного объекта (рис. 7.3).


Рис. 7.2. Использование предопределенной команды TTakePhotoFrornCameraAction

 

Object lnspector

 

 


§(_Action

Uve!Jrdr,gs Oc:Ca!1AdionExec a.odeance!Тalo:,ng

» OrOIA.t,T

te


iтшPhotofromC.t.-.Adiont        "

UvelJrmgs


 


u

OnApplySt)'lei.ookц)

OnCмFows

Q1CSd(

OnDblCldc

0,0-фор

OIO'.gEt,d

-v.uly-...-


Рис. 7.3. Выбор события OnDidFinishTaking()


 


Tilldng


Alshown


 

Двойной щелчок по строке события перенесет нас в редактор кода, в котором нам придется написать всего одну строку (листинг 7.3).


prooedure TForrnl.TakePhotoFrornCameraActionlDidFinishTaking(Irnage: TBitrnap);

Ьegin

Irnagel.Bitrnap.Assign(Irnage);

end;


Как видите, за счет предопределенных командных объектов можно весьма уско­ рить процесс разработки приложений в Delphi.

 

Список команд TActionList

Список команд TActionList - невизуальный элемент управления, который вы най­ дете на страничке Standard палитры компонентов. Компонент обычно используют при проектировании приложений малой степени сложности с незначительным чис­ лом командных объектов и построенных на основе обычных компонентов меню и стандартных кнопок. Все свойства, методы и обработчики событий списка команд унаследованы от родительского класса тcustomActionLis� и поэтому нам уже хоро­ шо знакомы.

По умолчанию список команд Actions пуст. Для заполнения списка командами дважды щелкните по компоненту левой кнопкой мыши. В результате такого дейст­ вия на экран компьютера выводится окно специализированного редактора (см. рис. 7.1). Для добавления команды TAction следует вызвать пункт New Action, а для добавления стандартных команд из заранее подготовленного перечня выбираем пункт New Standard Action.... КомандьJ могут группироваться по щ1ределяемым программистом категориям. Выбор в левом списке редактора той или иной катего­ рии отфильтрует только команды указанной группы. При желании можно отказать­ ся от фильтрации команд, для этого в списке категорий Categories надо выбрать строку АН Actions.

ЗАМЕЧАНИЕ

Список команд TActionList предназначен для совместной работы с обычными меню (тмainМenu, TMenuBar и TPopupMenu) и со стандартными элементами управления, на­ пример кнопками TButton.

Число хранимых в списке команд выясняется благодаря свойству

property ActionCount: Integer;

Доступ к коллекции команд обеспечивает свойство

property Actions[Index: Integer]: TContainedAction;

Обращение к отдельной команде производится по ее индивидуальному индексу (начиная от О и заканчивая ActionCount-1).

Список обладает некоторым перечнем прав по отношению к своим подопечным. Например, он способен отдавать приказ на выполнение принадлежащей ему команды.

function ExecuteAction(Action: TBasicAction): Boolean;

Выполнение метода приведет к генерации события OnExecute () . Функция вернет true в случае, если в рамках вызванного обработчика события команда была успешно отработана.

Ко всему прочему, список способен запретить всем принадлежащим ему командам реагировать на запрось\ пользователя. Для этого предназначено свойство


 

property State : TActionListState;//no умолчанию asNormal

type TActionListState =  (asNormal,  asSuspended,  asSuspendec!EnaЫed);

В момент создания свойству присваивается значение asNormal. Это нормальное со­ стояние, разрешающее содержащимся в контейнере командам реагировать на дей­ ствия пользователя. В состоянии asSuspended и asSuspendedEnaЫed все принадле­ жащие элементу управления команды перестают выполнять запросы пользователя. Разница между последними двумя состояниями заключается в оказываемом влия­ нии на свойство EnaЫed компонентов TAction. Так перевод TActionList в состояние asSuspended или asSuspendec!EnaЫed просто запрещает компонентам TAction выпол­ нять запросы пользователя. В дополнение к этому состояние assuspendedEnaЫed устанавливает все свойства команд EnaЫed в true.

Ключевые события TActionList перечислены в табл. 7.2.

 

Таблица 7.2. Основные события TActionList

 

Событие

Описание

property OnExecute: TActionEvent;

        TActionEvent = prooedure (Action: TBasicAction; var Handled: Вoolean) of object;

Событие генерируется у сnиска (или менеджера) команд nри обращении к команде. Параметр Action хранит ссылку на команду. Параметр Handled позво- ляет прервать выполнение команды

property OnChange: TNotifyEvent;

Генерируется nри любом изменении в списке команд

property OnStateChange: TNotifyEvent;

Вызывается сразу за событием OnChange () , уведом- ляет об изменении состояния менеджера

property OnUpdate: TActionEvent;

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


 

 

ГЛАВА 8

 

 

 

 

Управление папками и файлами

 

 

Одна из самых распространенных задач, с которой рано или поздно сталкивается разработчик программного обеспечения, -                      это управление файлами и каталогами компьютера. Основная трудность этой области программирования связана с высо­ кой вероятностью возникновения различного рода исключительных ситуаций (вы­ званных некорректным совместным использованием файлов и папок, ограничением свободного пространства, некорректным доступом к ресурсу, ошибками операций· чтения и записи и т. п.). Понимая, что для начинающего программиста задача рабо-. ты с файлами и папками может оказаться неразрешимой, создатели Delphi реализо­ вали очень удобный модуль System. routils. В нем расположились кроссплатфор­ менные классы TDirectory, TFile и TPath, специализирующиеся на управлении пап­ ками и файлами.

 

ЗАМЕЧАНИЕ

Основные функции кроссплатформенной среды разработки Delphi, связанные с управ­ лением файлами, каталогами и дисками, сосредоточены в модуле System. IOUtils (см. приложение 3).

 

 

Работа с дисками

При изучении файловой системы компьютера с Windows первое, что нас может за­ интересовать, - это состав логических дисков на компьютере. Предусмотрено не­ сколько способов сбора этих сведений. Наиболее простой заключается в задейство­ вании возможностей интеллектуальной записи TDirectory. Среди многочисленных методов записи имеется функция класса

сlавв function GetLogicalDrives: TStringDynArray;

создающая динамический массив строк, заполненный именами дисков в формате:

<буква_диска;•: <симвс1л_разделитель>. Меньше десяти строк кода (листинг 8.1) по­ зволят передать названия дисков в комбинированный список.



uses System.IOUtils,  System.Types;

 

prooedure TForml.ButtonlClick(Sender: TObject);

var SDA:TStringDynArray; i: integer;

Ьegin

ComЬoBoxl.Items.Clear; SDA:=TDirectory.GetLogicalDrives;

for i:=0 to High(SDA) do ComЬoBoxl.Items.Add(SDA[i]);

end;

 

 

Полезные сведения о размере диска и его свободном пространстве возвращают функции

function DiskSize(Drive: Byte): Int64; function DiskFree(Drive: Byte): Int64;

Параметр Dri ve требует передачи номера накопителя. Например: О -   текущий, 1 - А:, 2 - В:, 3 -   С: и т. д.'Если метод не в состоянии выяснить размер диска, то он возвратит -1.

 

Сбор сведений о каталогах и файлах

У описанной в модуле routils записи TDirectory предусмотрен ряд методов, суще­ ственно упрощающих процесс сбора сведений об имеющихся на компьютере фай­ дах и каталогах. Наиболее показательным из них является перегружаемая функция GetDirectories (), способная построить список подкаталогов, принадлежащих ката­ логу Path.

class function GetDirectories(const Path, SearchPattern: string;

const SearchOption: TSearchOption): TStringDynArray;

Перегружаемая функция

class function GetFiles(const Path, SearchPattern: string;

const SearchOption: TSearchOption): TStringDynArray;

нацелена на поиск файлов.

Универсальная функция

class function GetFileSystemEntries(const Path,

SearchPattern: string): TStringDynArray;

соберет сведения как о каталогах, так и о файлах. Все функции обладают идентич­ ным перечнем параметров: Path указывает путь к родительскому каталогу; searchPattern назначает маску поиска; SearchOption определяет опции поиска (soTopDirectoryOnly- сбор сведений только в текущем каталоге, soAllDirectories -


 

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


var SDA:TStringDynArray; i: integer;

Ьegin

SDA:=TDirectory.GetFileSystemEntries('C:\', '*.*'); fori:=0 to High(SDA) do ListBoxl.Items.Add(SDA[i]);

end;

 

 

Проверка существования файла и каталога

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

class function Exists(const Path: string;

FollowLink: Boolean = True): Boolean;

принадлежащий TFile.

Для проверки наличия папки вспоминаем о записи TDirectory и имеющейся в ее арсенале одноименной функции

class function Exists(const Path: string;

FollowLink: Boolean = True): Boolean;

Функции возвращают true, если файл (каталог) действительно существует. Для полноты картины упомянем имеющийся в распоряжении TPath метод class  function DriveExists(const Path:  string):  Boolean;

проверяющий факт существования диска с именем Path.

 

Расположение системных каталогов

Дополнительные сведения о размещении каталогов сможет предоставить специали­ зирующая на обслуживании имен файлов и папок запись TPath (модуль routils).

В арсенале TPath имеется метод

class function GetHomePath: string;

готовый поделиться сведениями о папке с данными приложений.

Еще одна тайна, которой владеет TPath, связана с расположением папки для вре­ менных файлов

class function GetTempPath: string;


 

Путь к каталогу с документами откроет метод

class function GetDocumentsPath: string;

Упомянем важное понятие "текущий каталог" - это каталог, в котором произво­ дятся текущие операции ввода/вывода. Для работы с текущим каталогом предна­ значены методы TDirectory:

class function GetCurrentDir: string;

class function SetCurrentDir  (const Dir: string): Boolean;

позволяющие выяснить, какой из каталогов является текущим, и назначить новый текущий каталог.

 

Создание,удаление,копирование и перемещение

В FireMonkey основным специалистом по созданию и удалению папок считается

TDirectory. Так, для создания новой папки следует вызвать метод

class prooedure CreateDirectory(Path: string);

За процесс копирования папки отвечает процедура

class prooedure  Copy(conвt  SourceDirName,  DestDirName:  string);

Здесь SourceDirName - путь к копируемой папке, DestDirName местоположение по­ лучателя.

Близкий по духу метод

class prooedure Move(const SourceDirName, DestDirName: string);

осуществит перенос папки в новое место.

Класс TDirectory обладает методом

class prooedure Delete(const Path: string;

const Recursive: Boolean);

решающим задачу удаления папки со всеми вложенными элементами. Для активи­ зации рекурсивного удаления надо не забыть установить в состояние true параметр

Recursive.

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

class proaedure Copy(const SourceFileName, DestFileName: string);

Перенос файла на новое место выполнит метод

class prooedure'Move(SourceFileName,  DestFileName:  string);

Для замены одного файла другим с одновременным созданием резервной копии задействуйте метод

class prooedure Replace(const SourceFileName,

DestinationFileName, DestinationВackupFileName:  string);


 

Наконец, самое разрушительное оружие класса TFile class procedure Delete(const Path: string);

удалит файл.

 

Запись в файл и чтение из файла

Создание нового файла средствами TFile - это уже многоходовая комбинация, которая может стартовать с вызова конструктора

class function Create(const Path: string): TFileStream;

создающего доступный для записи файловый поток TFileStream. После этого все остальные действия над файлом возлагаются на экземпляр TFileStream. Например, представленный в листинге 8.3 код демонстрирует процесс сохранения в файл с именем FileName координат левого верхнего угла главной формы приложения.


procedure TFoпnl.SaveToFile(FileName: String);

var fs:TFileStream;

Buf: integer;

Ьegin

if     TFile.Exists(FileName)  then TFile.Delete(FileName); f s:=TFile . Create (FileName) ;

try

Buf:=Application.MainFoпn.Left; fs.Write(Buf,SizeOf(Buf)); Buf:=Application.MainFoпn.Top; fs.Write(Buf,SizeOf(Buf)); finally

fs.Free;

end; end;

 


Обратная задача, связанная с чтением данных из файла, также может быть решена непосредственно силами класса TFile и файлового потока TFileStream так, как это предложено в листинге 8.4.

procedure TFoпnl.LoadFromFile(FileName: String);

var fs:TFileStream;

Buf:integer;

Ьegin

if     TFile.Exists(FileName) then Ьegin

fs:=TFile.Open(FileName,TFileMode.fmOpen);


 

try

fs.Read(Buf,SizeOf(Buf));


 

 

 

 

 

 

 

 

end;


Application.MainForm.Left:=Buf; fs.Read(Buf,SizeOf(Buf)); Application.Ma·inForm.Top:=Buf;

finally

fs.Free;

end; end;


 


На этот раз нам помог метод класса

class function Open(const Path: string;

const Mode: TFileMode}: TFile&tream;

позволяющий загружать файл с именем Path в поток TFil�St:t:eam.

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

class function ReadAllLines(const Path: string): TStringDynArray;

Результат  будет  возвращен    в  динамический    строковый  массив  в  формате

TStringDynArray.

Наиболее универсальный метод чтения

class function ReadAllBytes(const Path: string): TBytes;

загрузит любой файл в массив байт твуtеs.

Для записи данных в файл проще всего воспользоваться методами

class prooedure WriteAllLines(const Path: string;

const Contents: TStringDynArray);

class procedure WriteAllBytes(const Path: string; const Bytes: TBytes};

первый метод нацелен на работу исключительно с текстовыми данными, второй всеяден и в качестве источника данных воспринимает массив байтов.

 

Атрибуты файла и каталога

В современном языке программирования Delphi основными специалистами по ра­ боте с атрибутами файла и папки считаются классы TFile, тoirectory и TPath.

У всех перечисленных классов предусмотрены одинаковые методы

class function GetAttributes(const Path: string;

FollowLink: Boolean = True): TFileAttributes; class procedure SetAttributes(const Path: string;

const Attributes: TFileAttributes);


 

 

 

Управление папками и файлами                                                                                                              141

 

Стоит особо отметить, что перечень атрибутов TFileAttributes определяется опе­ рационной системой и различается у Windows и OS Х.

 

Дата и время создания файла и каталога

Самый простой способ для знакомства с возрастом файла заключается в использо­ вании возможностей класса TFile, а если речь идет о каталогах, то нашим помощ­ ником станет TDirectory. Оба класса располагают набором одноименных метqдов. В частности, за выяснение даты и времени создания файла (папки) отвечает метод

class function GetCreationTime(const Path: string): TDateTime Дату/время последнего обращения к файлу (папке) знает метод class function GetLastAccessTime(const Path: string): TDateTime; О том, когда произошли последние изменения, все известно методу class function GetLastWriteTime(const Path: string): TDateTime;

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

class prooedure SetCreationTime(const Path: string;

const CreationTime:  TDateTime);

class prooedure SetLastAccessTime(const Path: string;

const LastAccessTime: TDateTime);

class procedure SetLastWriteTime(const Path: string;

const LastWriteTime: TDateTime);

На этот раз, кроме параметра Path который следует направить пуrь и имя файла), в процедурах имеется второй параметр типа TDateTime, в который в зависимости от назначения функции следует направить дату/время создания, дату/время последне­ го доступа или дату/время последнего изменения.


 

 


ГЛАВА 9

 

 

Компоненты

для работы с текстом

 

 

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

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

ка TLabel. Список предназначенных для редактирования текста компонентов воз­ главляют строка ввода TEdi t и строка с возможностью быстрой очистки текста тclearingEdi t. Эти элементы управления позволят пользователю ввести простей­ шую строку текста. Элементы управления TNшnЬerBox, TSpinВox и тcornЬoTrackBar представляют различного рода вариации на тему ввода числовых значений. Еще один герой этой главы - многострочный текстовый редактор тмеmо, компонент способен хранить и обрабатывать неформатированные текстовые строки.

Иерархия классов, на основе которых созданы рассматриваемые в главе компонен­ ты, предложена на рис. 9.1.

 

ЗАМЕЧАНИЕ

У всех компонентов (за исключением TLabel) предусмотрена возможность обращения к интерфейсам ITextServiceControl и IVirtualKeyboardControl. Первый из ин­ терфейсов осуществляет подцержку всех базовых операций с текстовой областью данных, а второй интерфейс разрешает элементу управления взаимодействовать с сенсорной клавиатурой, применяемой в IOS.

 

ЗАМЕЧАНИЕ

Концептуально компонент тcornЬoEdit ближе к комбинированным спискам, чем к стро­ кам ввода, поэтому он будет рассмотрен в главе 10.


 

 

Cuadro de texto: TControl (FMX.Controls)

Cuadro de texto: TStyledControl (FMX.Controls)

 

 


 

TScrollBox (FMX.Layouts)

 
TCustomEdit (FMX.StdCtrls)


ТТextControl

-,

 
(FMX.StdCtrls)


......:;;._......._..-:а....._  \     /------------------ +


◄----


------'-----... \


\ (                      TEdit

11        (FMX.Edit)


TClearingEdit           \                                         1

(FMX.Edit)           1                                         1


------1/  \,              ,,,                     ,,,


\     ._                      ,,,  I


'------                                                 ,                                                                           _


.... --метка--/


Многострочнь1й редактор


Строки ввода


TComboEditBase (FMX.Edit)

 
.----+

/


+-----


:"\


TCustom EditBox (FMX.Edit)

\

 
.----+                         ,

/


f         TComboEdit

------

 
:          (fMX.Edit)

\


TComboTrack-  \

Ваг               1

------------ /

 
(FMXEdit)           1

/


1      Tl'-lumberBox                      TSpinBox        1

(FMX.Edit)                      (FMX.Edit)        )

'                               /


Комбинированные строки ввода


..._         Строки выбора значения       -


Рис. 9.1. Иерархия текстовых компонентов

 

Класс TTextControl

Класс ттextC.ontrol выступает системообразующим классом по отношению к суще­ ственному количеству элементов управления, отвечающих за вывод текстовых надписей. Особо отметим, что, как правило, потомки ттextcontrol не наделяют полl\зователя правом редактирования текста. Поэтому такие элементы управления (в частности метка TLabel) специализируются на выводе пояснительных надписей.

Для работы с потомками ттextcontrol достаточно запомнить менее десятка свойств, в первую очередь это свойство

property Text: string;

определяющее содержание текстовой надписи. Гарнитуру, начертание и шрифт определит свойство property Font: TFont;



144                                                                                                                                                            Гпава 9

 

Цвет шрифта и особенности вывода текстовой надписи назначаются кистью, дос­ туп к которой предоставит свойство

property FontColor:  TAlphaColor;

Порядок выравнивания текстовой надписи в клиентской области компонента зави­ сит от состояния свойств

property VertTextAlign: TTextAlign; //тто умолчанию TTextAlign.taCenter; property TextAlign: TTextAlign;                                                           //по умолчанию TTextAlign.taLeading;

TTextAlign = (taCenter, taLeading, taTrailing);

За вертикальное выравнивание отвечает свойство vertтextAlign, за горизонталь­ ное - TextAlign.

Если текстовая строка очень велика и физически не может быть отображена цели­ ком, то установив в состояние true свойство

property WordWrap: Boolean; //по умолчанию False;

можно обеспечить многострочный вывод.

В ряде наследников ттextcontrol для управления атрибутами (влияющими на осо­ бенности отображения текстовых данных) можно обратиться к свойству

property TextSettings: TTextSettings;

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

Некоторые особенности стилевого оформления потомков ттextcontrol, связанные с шрифтом, настраиваются свойством

property StyledSettings: TStyledSettings;

Свойство представляет собой множество тstyledSetting. Включая (или отключая) элементы множества мы указываем - стоит ли при прорисовке элемента управле­ ния учитывать гарнитуру (ssFamily), размер (ssSize), начертание (ssStyle), цвет (ssFontColor) и другие параметры (ssOther) шрифта.

 

Метка TLabel

Из всех рассматриваемых в этой главе компонентов метка TLabel является единст­ венным элементом управления, не способным получить фокус ввода в результате нажатия клавиши <ТаЬ> и не позволяющим редактировать свой текст пользовате­ лю. Дело в том, что задача метки существенно проще, она заключается в выводе пояснительной надписи.

Все базовые свойства и методы метки (в том числе и ключевое свойство техt) унас­ ледованы от уже знакомого нам родительского класса ттextControl.

rвоих CQQ '!'!l Ш"1-!J?.!X "фирменных" СВОЙСТВ И методов у метки практич<::ски пет. Вы­

делим свойство

property AutoSize: Boolean; //по умолчанию False;


 

позволяющее компоненту самостоятельно подогнать свой размер под размер под­ лежащего выводу текста.

 

3АМЕЧАНnЕ

Если логика программы не предполагает задействование методов или обработчиков событий метки TLabel и вам особо не интересно стилевое оформление формы, то вместо метки стоит задействовать менее ресурсоемкий компонент ттехt.

 

Чтобы вы совсем не заскучали, предлагаем научиться задействовать ме1'ку в каче­ стве гиперссьmки. Для этих целей мы сохраним в метке URL-aдpec интересующего нас сайта (листинг 9.1).

 


 

 

prooedure TFoпnl.FoпnCreate(Sender: TObject);

Ьegin

Labell.Text:='Caйт компании EmЬarcadero'; Labell.TagString:='http://www.emЬarcadero.ru';

end;


W"Jl!'!""""''U

 

...... !


 


При обработке события-щелчка по метке (листинг 9.2) обязательно учтем, что для вызова интернет-браузера в Windows. и OS Х потребуется задействовать разный исходный код.


prooedure TFoпnl.LabellClick(Sender: TObject);

var Url:string;

{$IFDEF МАСОS}

Workspace: NSWorkspace; Url: NSURL;

{$ENDIF}                                                                                                                    '

Ьegin

Url:=TLabel(Sender) .TagString;

{$IFDEF MSWINDO�S}

//  uses Winapi.ShellApi,  Windows;

ShellExecute(0,  'open',  PChar(url),  nil, nil, SW_SHOWNORМAL);

{$ELSE}

{$IFDEF МАСОS}

Workspace := ТNSWorkspace.Wrap(TNSWorkspace.OCClass.sharedWorkspace);

_Url := TNSUrl.Wrap(TNSUrl.OCClass.URLWit�String(NSStr(Url))); Workspace.openURL(_Url);

{$ENDIF}

{$ENDIF}

end;


 

ЗАМЕЧАНИЕ

Честно говоря, у метки TLabel есть некоторые проблемы с реакцией на событие OnClick () (речь о RAD Studio ХЕ4 Version 18.0.4854.59655 и RAD Studio ХЕ5 Version 19.0.13476.4176). Если вы столкнетесь с аналогичной ситуацией, то можете заменить в примере метку другим компонентом, например ттехt.

 

Интерфейс /Virtua/KeyboardControl

У всех рассматриваемых в главе текстовых компонентов (за исключением метки TLabel) предусмотрен доступ к интерфейсу IVirtualKeyboardControl. Указанный интерфейс позв.оляет элементу управления взаимодействовать с виртуальной кла­ виатурой операционной системы IOS, устанавливаемой на смартфонах и планшетах от Apple (рис. 9.2). Таким образом, если вашими целевыми устройствами являются iPhone и iPad, то благодаря свойству

property KeyboardType: ТVirtualKeyboardType; TVirtualKeyboardType = (vktDefault, vktNurnЬersAndPunctuation,

vktNurnЬerPad, vktPhonePad, vktAlphabet, vktURL,

vktNamePhonePad, vktEmailAddress);

вы сможете не только определить, какая именно виртуальная клавиатура подклю­ чена к компоненту) но сменить клавиатуру.


Рис. 9.2. Смена виртуальной клавиатуры

 

 

Основа строк ввода, класс TCustomEdit


 

R нем инюшсулированы свойства и методы, обеспечивающие:

    хранение и редактирование текста;

    выделение части текста с возможностью редактирования только этой части;

   реагирование на любые изменения в содержании текста.

С основным свойством всех текстовых компонентов вас вряд ли н Q       ;)Ш\КQМПТII,

Это свойство                                  ·

property Text: tring;

Именно со свойством техt прямо или косвенно связана деятельность подавляющего большинства свойств и методов тcustomEdi t.

 

nРНМЕЧАННЕ

В текстовые компоненты FireMonkey инкапсулирован класс ттextservice, именно он содержит ниэкоуровн1;1вые методь, обработки текстовь,х даннь,х. На долю классов бо­ лее вь,сокого уровня (например, строк ввода) остается лишь ставить классу ттextService задачи по вводу/выводу данных.

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

property OnChange:  ТNotifyEvent;

Это событие генерируется в тот момент, когда пользователь (например, завершив ввод текста) нажмет клавишу <Enter>.

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

property OnChangeTracking: ТNotifyEvent;

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


procedure TForrnl.ForrnCreate(Sender: TObject);

Ьegin

Editl.MaxLength:=10;

end;

 

procedure TForrnl.EditlChangeTracking(Sender:  TObject);

Ьegin

if Editl.Text.Length>=Editl.MaxLength then ShоwМеssаgе('Достигнут предел длины стро:ки! ');;

end;


Предложенный код контролирует длину текстовой строки. Как только длина дос­ тигнет 1О символов, на экран выводится предупредительное сообщение.

 

Ограничения на ввод

Класс тcustomEdit позволяет наложить некоторые ограничения на вводимые тек­ стовые данные. Самое серьезное ограничение (абсолютный запрет на редактирова­ ние текста) накладывает свойство

property ReadOnly: Boolean;          //по умолчанию false

при установке его в true.

Ограничение на максимальное количество символов, хранящихся в свойстве техt,

поможет назначить свойство

property MaxLength:  Integer;          //по умолчанию О -  ограничений нет

Нулевое значение говорит о том, что длина строки не ограничена.

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

property Password: Boolean; //по умолчанию false

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

Если строка ввода должна допускать ввод только определенное подмножество сим­ волов, то это подмножество следует передать в свойство

property FilterChar: string;

Всего одна строка кода из листинга 9.4 ограничит права пользователя на ввод тек­ ста, теперь ему разрешается использовать только цифровые символы.

'                                                                                                                                                                                                                                                                                                                                                       •• ·-;г···•·;;,, -"· ·,1,1,-.1· ·•·•·

f       .  .  .     

· .................................................................·-···········..••·••••...........,..........   ········"'····· ···········•....................................,••_,..............._,,,.,i'I......... .....................-.................................................... .._,

 

Editl.FilterChar:=  '0123456789';

 

Чтобы отключить фильтр ввода, очистите свойство FilterChar.

 

Выделение части текста

У потомков класса тcustomEdi t имеется возможность выделять часть текста и ре­ дактировать эту часть. Так, благодаря свойству

property seiText: string;

возможно обратиться только к выделенному фрагменту текста.

 

 



 

Прt>с 111,.-слйпии выделение текста производится не только мышью или клавишами управления курсором, но и программным способом. Для этого объявлены два свойства:

property SelStart: Integer; property SelLength: Integer;

Они определяют порядковь й номер первого выделяемого символа и всю длину вы­ деляемого текста соответственно.

Для выделения целого слова в текущей позиции вызывают процедуру

prooedure SelectWord;

Для вьщеления всего текста воспользуйтесь методом

prooedure SelectAll;

Для удаления выделенного текста пригодится процедура:

prooedure ClearSelection;

Для того чrобы при получении элементом управления фокуса ввода автоматически выделялся его текст, убедитесь, чrо свойство

property AutoSelect: Boolean; //по умолчанию true;

установлено в состояние true.,

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

 

 


·Н·ор&еrия


---              ,          


_j      88ОД8 ТIЖСПI

ооущ1СП111А8ТСА ПОМ!Ж

М181более noдxo,QflЩ8til


Объедкнвнмые Арабскмв Эмираты Оман

Пакж:та1t Палау

Панама

Паnуа Новая Г8МН8А Параrвай

Перу Поnьwа Портуrалия

Россмя


строки ПОДСТ8НО111G1

 

Строkа: 130


 


Рис. 9.3. Применение выделения части текста при поиске и подстановке

 

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


procedure TFoпnl.EditlKeyUp(Sender: TObject; var Кеу: Word;

var KeyChar: Char; Shift: TShiftState);

var х, i  : word;

s:string;

Ьegin

if DWord(KeyChar)>47 then {контролируем только символы с кодом больше 47}

Ьegin


Editl.ClearSelection; x:=Length(Editl.Text);


//снимаем выделение

//запоминаем число введенных знаков


for i:=0 to Memol.Lines.Count-1 do //перебираем строки в Memol

Ьegin

s:=Copy(Memol.Lines.Strings[i],1,x);

//сравниваем

if  (LowerCase(Editl.Text,loUserLocale)=LowerCase(s,loUserLocale))  then

Ьegin //если нашли совпадение - помога� пользователю Editl.Text:=Memol.Lines.Strings[i];//пepeнocим строку в Editl Editl.SelStart:=x; //выделяем добавленную часть строки Editl.Se1Length:=Editl.Tex4.Length-x;

break;

end; end; end;

end;

 

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

 

Взаимодействие с буфером обмена

Взаимодействие с буфером обмена производится с помощью процедур:


proaedure CopyToClipboard;

procedure CutToClipboard;


//копировать в буфер

//вырезать в буфер


procedure PasteFromClipboard;  //вставить  из буфера

 

Управляющие символы

ПQ УМQЛчанию строки ввода не воспринимают управляющие символы (символ табуляции, символ перевода строки и т. п.). Чтобы изменить ситуацию, воспользуй­ тесь свойством

property Typing: Boolean; //по умолчанию false

установив его в состояние true. Дополнительный контроль за процессом ввода не­

!! '!!!'!' !_ f!!М!ШЛ9 99 спечит событие

property OnTyping: ТNotifyEvent;

 

 



 


Например, это событие поможет описать реакцию компонента на нажатие пользо­ вателя управляющих клавиш <Backspace>, <Del> и т. п. во время редактирования текста в строке.

 

Особенности оформления

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

Свойства

property FontColor: TAlphaColor;

и

property SelectionFill: TBrush;

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

Координаты прямоугольника, в рамках которого выводится текст, поможет узнать метод

function ContentRect: TRectF;

Метод

function GetCharX(a: Integer): Single;

возвратит горизонтальную позицию символа с индексом а. Эти сведения могут пригодиться, например, при визуализации процедуры поиска подстроки в строке. Разместите на форме две строки ввода:

   строка Editl:TEdit будет содержать какой-то произвольный текст;

    в строке Edi t2: TEdit пользователь может ввести символ, сочетание символов

или слово, которое он хочет обнаружить в компоненте Editl.

Собственно с поиском подстроки никакой проблемы нет, в Delphi уже давно суще­ ствует функция Pos (), возвращающая индекс найденного символа. Но в VCL было проблематично дать пользователю визуальную подсказку, например, подчеркнуть найденное слово. В FМХ такой проблемы не существует. Для этого достаточно воспользоваться событием DnPaint () строки ввода Editl (листинг 9.6).


procedure TForml.EditlPaint(Sender: TObject;  Canvas:  TCanvas;

const ARect: TRectF);

var a:integer;

APtl, APt2: TPointF;


 

Ьegin

a:=Pos(Edit2.Text,Editl.Text); //индекс первого найденного символа if    а=О then exit; //если О, значит, подстрока не найдена

//в GetCharX() отсчет начинается с О, поэтому вычтем 1 APtl.X:=Editl.GetCharX(a-1); //координаты первого символа APtl.Y:=ARect.Bottom-3;

 

//рассчитываем координаты последнего символа APt2.X:=APtl.X+Canvas;TextWidth(Edit2.Text); APt2.Y:=ARect.Bottom-3;

 

with Canvas do //подчеркиваем найденное слово if BeginScene(nil) then

Ьegin

Stroke.Color:=TAlphaColorRec.Crimson; DrawLine(APtl, APt2, 1);

EndScene;

end;

end;

 

 

Для того чтобы подчеркнуть найденное слово, нам потребуются две координаты - начала и окончания подстроки в строке. Первая координата выясняется благодаря методу GetCharx (), для расчета второй координаты мы прибавляем к первой результат выполнения метода тextWidth () . Дело сделано -         нам осталось провести линию (рис. 9.4).


Рис. 9.4. Подчеркивание найденного слова

 

 

Строки ввода TEdit и TC/earingEdit

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

Начиная с Delphi ХЕ4, у компонента TEdit появилась возможность подключения дополнительных элементов - кнопок, для этого следует вызвать контекстное меню компонента и выбрать элемент с названием подходящего класса. Подключаемая кнопка представляет собой самостоятельный элемент управления (наследник це-

 

 


 


 

почки классов "тcustomВutton- TEditButton"), который располагается в правой части строки ввода и снабжается соответствующей картинкой (рис. 9.5).

 

3АМЕЧАННЕ

Набор свойств и методов TEdit, TClearingEdi t й ввода ничем не отличается от пе­ речня свойств и методов опорного класса тc;:ustomEdi t.


Рис. 9.5. Подключение управляющих кнопок к компоненту TEdit

 

 

Многострочный редактор ТМето

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

Ядром компонента выступает свойство

property Lines: TStrings;

предоставляющее построчный доступ к тексту.

Предусмотрен и альтернативный способ обращения к обслуживаемому тексту, его предоставляет свойство

property Text: string;

На этот раз мы сможем работать не построчно, а со всем текстом одновременно.

Основными обработчиками событий элемента управления являются

property OnChange:  TNotifyEvent; 

property OnChangeTracking: ТNotifyEvent;

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


 

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


proaedure SelectAll; property SelStart: Integer; property SelLength: Integer; property SelText: string; proaedure ClearSelection;


//выделить весь текст

//индекс первого выделенного символа

//длина выделения

//доступ к выделенному тексту

//очистка выделенной области


Взаимодействие с буфером обмена производится с помощью методов:


prooedure CopyТoClipboard;

prooedure CutToClipboard;


//копировать в буфер

//вырезать в буфер


prooedure PasteFrornClipboard; //вставить из буфера

 

Позиция каретки

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

property CaretPosition: TCaretPosition;

Свойство возвращает запись из двух полей, идентифицирующих номер строки и позицию каретки в строке.

TCaretPosition = record

Line, Pos: Integer; //номер строки, позиция каретки в строке

end;

Номер строки и позиция символа в строке применяются в ряде методов много­ строчного редактора. В частности, метод

funotion PosToTextPos(APostion:  TCaretPosition):  Integer;

nо.тrучив координаты APost:i.on, подсчитает число символов от начала документа до обозначенной позиции. Обратная задача - преобразование числа символов в коор­ динаты тcaretPosition - решается методом

funotion TextPosToPos(APos: Integer): TCaretPosition;

Еще один метод

function GetPositionPoint(ACaretPos: TCaretPosition): TPointF;

произведет пересчет координат символа в экранные координаты.

 

Редактирование текста

Основной сервис по редактированию текста в компоненте тмеmо, безусловно, пре­ доставляет интегрированный в компонент класс тstings. Но кроме методов тstings нам разрешено пользоваться и методами, встроенными в тмеmо.

 

 



 

IIa nставкс блока тс:ксга в многострочный редактор специализируется метод

procedure InsertAfter (Position: TCaretPosition;·

const S: string; Options: TinsertOptions);

В результате в позицию, указанную в аргументе Position, будет вставлен текст s. Особенности вставки определяют в опциях Options (табл. 9.1).

 

Таблица 9.1. Опции вставки текста в тмето

 

Tinsertoption

Описание

ioSelected

К вставленному тексту применяется эффект выделения

ioMoveCaret

Перемещает курсор в позицию после последнего вставленного символа

ioCanUndo

Допускает восстановление удаленного текста

ioUndoPairedWithPrev

Определяет, что данная операция вставки является продолже- нием предыдущей и должна рассматриваться как единое целое

 

Обратную задачу по удалению фрагмента текста решает метод

procedure DeleteFrorn(Position:  TCaretPosition;

ALength: Integer; Options: TDeleteOptions);

Начало области удаления определяется в первом параметре функции Position, длина удаляемой строки назначается в аргументе ALength. Опции предложены в табл. 9.2.

 

Таблица 9.2. Опции удаления текста в тмето

 

ТDeletaOption

Описание

ioMoveCaret

Перемещает курсор в позицию после последнего вставленного символа

doCanUndo

Допускает восстановление удаленного текста

 

Весьма полезное качество компонента тмеmо заключается в возможности отмены последних изменений в тексте, Для этого следует вызвать метод

procedure UnDo;

Метод анализирует состояние специальной структуры TEditActionStack, которую можно сравнить со стеком, в котором сохраняются все правки в тексте. И если от­ кат изменений возможен, то он осуществляется.

 

Быстрое перемещение по тексту

Для организации быстрого перемещения по тексту в состав класса введены четыре метода. Процедуры

procedure GoToTextBegin;

procedure GoToTextEnd;

соответственно осуществят переход к началу или к концу текста.


 

У методов

prooedure GoToLineBegin;

prooвdure GotoLineEnd;

расстояния для " рыжка" поменьше, они переведут каретку соответственно к нача­ лу или к окончанию строки.

 

Ввод чисел TNumberBox, TSpinBox

и ТСотЬоTrackBar

Среди текстовых компонентов есть специалисты, оказывающие пользователю по­ мощь при вводе целочисленных или вещественных числовых значений. Это компо­ ненты TNшnЬerBox, TSpinВox и TComЬoTrackBar. По своему функционалу компоненты очень похожи (табл. 9.3) и отличаются лишь внешним видом и способом ввода зна­ чения (рис. 9.6).


Рис. 9.6. Текстовые компоненты для ввода числового значения

 

 

Таблица 9.3. Общие свойства компонентов ТNитЬеrВох, TSpinBox и TComЬoTrackBar

 

Свойство

Описание

property ValueType:  TNurnValueType;

type TNшnValueType = (vtinteger, vtFloat);

Тип вводимых данных может быть как целочисленным, так и вещественным

property Value: Single;

Числовое значение

property DecimalDigits: Integer;

Число знаков, отображаемых после запятой

property Min:'Single;

Диапазон допустимых значений

property Мах: Single;

 

Конечно же, у каждого из рассматриваемых элементов управления имеются и спе­ цифичные черты. У компонентов ТNшnЬеrвох и тspinBox шаг приращения величины Y.!:l }!!!Шf !' 9!'99Н9ШШЯ С.ВQЙСТВ

 

 


 


 

property Horzincrement: Single; property Vertincrement: Single;

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

СВОЙСТВО Vertincrement.

При желании программист сможет получить доступ к ползунку компонента

тcomЬoTrackBar, для этого в коде программы стоит упомянуть свойство

property TrackBar: TTrackBar;

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

property OnChangeTracking: ТNotifyEvent;

генерируемое при изменении положения ползунка, и событие

property OnChange: ТNotifyEvent;

вызываемое при редактировании значения value.


 

 

 

 

 

 

 


ГЛАВА 10

 

 

Компоненты-списки

 

 

 

 

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

Список выбора строится на основе класса TListBox. Кроме того, существуют два типа комбинированных списков - нередактируемый тсоmЬовох и с редактируемой строкой ввода тcomЬoEdit. Самое существенное различие между рассматриваемыми элементами управления в том, что обычный список выбора представляет собой ок­ но, в котором одновременно отображаются все его элементы. Если из-за ограни­ ченного размера окна текстовые элементы не могут быть выведены на экран в пол­ ном составе, то окно списка выбора снабжается полосой прокрутки. Комбиниро­ ванные списки по умолчанию отображают всего один свой элемент, щелчок по кнопке в правой части компонента заставляет его вывести на экран список с переч­ нем элементов, один из которых выберет пользователь (рис. 10.1). После выбора выпадающий список вновь свернется. Редактируемый список тcomЬoEdit обладает еще одной сервисной возможностью. В отличие от своего статического собрата тсоmЬоВох, список тcomЬoEdi t позволяет пользователю вводить текст в строку ввода.

Основными родовыми классами для списка выбора выступают FМX.Layouts. тscrollBox и FМХ. ListBox. TCustomListBox. Благодаря первому список TListBox при­ обретает способность выступать в роли контейнера, обладающего полосами про­ крутки, а благодаря второму - список выбора получает возможность управлять своими элементами.

Нередактируемый комбинированный список тсоmЬовох создается на основе комби­ нации двух базовых классов (именно поэтому его и называют комбинированным). Непосредственный предок, класс TCustomComЬoBox, определяет всю программную, логику элемента управления, кроме того, здесь описана верхняя часть элемента управления, соответствующая его свернутому состоянию. При развертывании ком­

DИИИ{)ОIШlШОrО списка И3 него "выпадает" список выбора, который создастся па

основе TComЬoListBox.

 

 

 


 


 

О Комnоне"'1,1-сnис1ем FnМonlte),

 

 

С,ране<                                                     Соеродs><нс                                                      Н&ХОДIСЪ

Сарюул                                                      · Ct&tP<                                                                ' Нe:SHHtiOMЫCC

Саръ,о,                                                     ,C,eprм<!II Посц                                                     Нефтеимск

Сеееродаи ,СJ<                          1Cepnyxo1                                                                  Нефrеюrана

Сн;ерос                                    1 CмoJ>t>!C<                                                                Ниж.нееартоеск

СерrиеаГlси;\                                           .сочи                                              НЮЮ1"а:1ыск

Cepr,yxoe

С!ЮЛ <                                                                                                        Н1<ж,;ийТаrм

 

 

 

 

Рис. 10.1. Внешний вид списка выбора и комбинированного списка в проекте для Windows

 

 

ВНИМАНИЕ/

Важной объединяющей чертой списков TListBox и ТСоmЬоВох выступает тот факт, что хранимые в них элементы строятся на основе класса TListBoxitem.

Редактируемый комбинированный список тcomЬoEdi t также объединяет в себе два класса: строку ввода (созданную на фундаменте класса тcustomEdit) и список выбо­ ра TComЬoEdi tListBox.

 

Базовый элемент списка TL/stBoxltem

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

Основной информационный элемент списка (как обычnого, так и комбинированно­ го) строится на основе экземпляра класса TListBoxitem.

 

ЗАМЕЧАНИЕ

TListBoxitem - не единственный класс, представленный в списках. Кроме него спи­ ски способны работать с элементами: TMetropolisUIListBoxitem, TListBoxHeader, TSearchВox, TListBoxGroupHeader и TListBoxGroupFooter.

 

Ключевое свойство элемента

property Text: string;

хранит текстовое описание элемента. Кроме текста с элементом списка разрешено сопоставить любой другой объект, ссьmку на этот объект следуе! передать в свой­ ство

property Data: TObject;


 

В качестве идентификатора отдельного элемента списка может выступать его ин­ декс

property Index: Integer;

Заметим, что индекс элемента не статичен и_может измениться в результате удале­ ния или переупорядочивания принадлежащих списку элементов.

Элемент списка может быть выбран пользователем или программным образом, в этом случае в его свойстве

property IsSelected:  Вoolean;

окажется значение true.

Кроме того, элемент может быть отмечен "галочкой" благодаря свойству

property IsChecked: Щlean;

 

ВНИМАНИЕ!

Элемент списка TListBoxitem может выступать в роли контейнера для других эле­ ментов управления.

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

property ItemData: TListBoxitemData;

Данные детализации описываются четверкой свойств, которые представлены в табл. 10.1.

 

Таблица 10.1. Основные свойства ТListBoxitemData

 

Свойство

Описание

property Text: string;

Основное текстовое описание элемента

property Detail: string;

Дополнительное текстовое описание

.

property Accessory: TAccessory;

//по умолчанию aNone

.

aNone - нет значка;

'

. аМоrе - значок продолжеt-lия;

aDetail -  значок детализации;

    aCheckmark - значок "галочки"

property Bitmap: TBitmap;

Дополнительная картинка

 

Чтобы проиллюстрировать возможности свойства детализации, лучше всего вос­ пользоваться проектом для мобильной платформы File I New I FireMonkey Moblle

Application, разместить на пустой форме компонент TListBox и поэкспериментиро­ вать со свойствами ItemData и StyleLookup элементов списка (рис. 10.2).

ВНИМАНИЕ!

Предусмотрено несколько способов отображения информации детализации элемен­ ТОМ TListHoxitmn. Для того чтобы выбрать наиболее подходящий опоооб дrrn D(Щ следует поэкспериментировать со свойством StyleLookup элемента списка. В проек-

 

 


 

 

              


 


тах для мобильной платформы iOS и Android наиболее востребованными стилями окажутся:  listboxitemnodetail, listboxitemЬottomdetail, li�tboxitemrigh�d���i� И listboxitemleftdetail.

 

 

 

 

 

 

Рис. 10.2. Элементы списка в состоянии детализации

 

 

 

 

 

 

 

 

 

Список выбора TListBox

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

property Items: TStrings;

Манипуляции текстовыми строками осуществляются с помощью стандартных ме-

     тодов, предоставляемых в наше распоряжение классом тstrings (листинг 10.1). В момент появления новой текстовой строки для ее визуализации автоматически создается очередной экземпляр класса TListBoxitem.


,

ListBoxl.Clear;

ListBoxl.Items.LoadFromFile('c:\city.txt'); ListBoxl.Sorted:=true;

 

О числе элементов в списке проинформирует доступное только для чтения свойство

property Count: Integer;

Индекс текущего элемента списка (последнего элемента, выбранного пользовате­ лем) доступен благодаря свойству

property Itemindex: Integer;

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


Редактирование элементов

Если задействованный в проекте компонент-список должен обладать заранее из­ вестным статическим набором элементов, то для построения перечня элементов достаточно воспользоваться контекстным меню компонента и обратиться к пункту меню Items Editor (Редактор элементов). В результате на экране отобразится ре­ дактор, позволяющий добавить/удалить элемент из списка.

В ситуации, когда состав списка динамичен и может подвергаться изменениям во время выполнения приложения, можно пойти по одному из двух альтернативных путей. Наиболее распространенный случай основан на возможностях свойства Items. Свойс1во инкапсулирует набор строк тstrings, что позволяет добавлять (листинг 10.2) и удалять текстовые элементы методами класса тstrings.


ListBoxl.Items.Clear; ListBoxl.Items .Add ('Москва');

ListBoxl.Items.Add('Caнкт-Пeтepбypг');

 

Традиционный способ редактирования состава элементов не раскрывает все воз­ можности компонента-списка. Дело в том, что элемент списка TListBoxitem не ог­ раничивается обработкой текстовых данных и обладает весьма серьезными воз­ можностями, в том числе он может выступать в роли .контейнера для других эле­ ментов управления. Элементы управления (кнопки, графические фигуры, изображения и т. п.) могут размещаться на поверхности элементов списка как во время визуального проектирования, так и программным способом. Однако в по­ следнем случае от разработчика приложения потребуется больше усилий - ему придется самостоятельно в коде программы создавать элементы списка и добавлять их в контейнер.

Для добавления объекта в список следует воспользоваться методом

procedure AddObject(AObject: TFmxObject); override;

или

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

Разница между процедурами заключается лишь в том, что AddObj ect () помещает новый элемент в конец списка, а InsertObj ect () вставляет в позицию Index.

Для удаления ненужного элемента следует обратиться к методу

procedure RemoveObject(AObject: TFmxObject); override;

Кардинальная очистка списка осуществляется методом

procedure Clear;  virtual;

!"!V Ш!9 !!!JЫЙ     !! 9Ш!!Г 10.3 код демонстрирует порядок сбора в спиооr оподс

ний о файлах-картинках в формате JPEG.


лТТSI nn"Rтnpe,яи.v примера вам потребуются три компонента:

   СПИСОК ListBoxl:TListBox;

    строка ввода Edi tl: TEdit, сюда пользователь внесет пугь к каталогу с карт ми:

    кнопка Buttonl:твutton, щелчок по которой инициирует процесс построения

списка.

 

 

•-'

"1                 rJ:::.:,,,,,··-        ,,,     J

 

\     .. ;

 

,,,;

 

•'

 

 

 

•••

1  1''"t''

 

.,,,1/ i  ; 7/:

" ;,..v'

,,,

_,,.

 

 

 

         ·'

,-  ._

._

,

w1,.,1;i,"'•,....

 

;J,

•i:•"- },"'{,1!,;i

prooedure TForml.ButtonlClick(Sender: TObject);

var SDA:TStringDynArray; Item:TListBoxitem; Image:Timage; LЫ:TLabel;  i:integer;

Ьegin ListBoxl.BeginUpdate; ListBoxl.Clear;

 

if DirectoryExists(Editl.Text) then Ьegin

SDA:=TDirectory.GetFiles(Editl.Text, '*.jpg');

for i :=О to High(SDA) do Ьegin

Itern:=TListBoxitem.Create(ListBoxl);

Item.TagString:=SDA[i]; Itern.Height:=40;

 

Irnage:=Tirnage.Create(Item); //миниатюра изображения

Irnage.Parent:=Itern; Irnage.Position.X:=0; Irnage.Position.Y:=0;

Irnage.Width:=40; Irnage.Height:=40;                   . Irnage.Bitrnap.LoadThurnЬnailFrornFile(SDA[i],Width,Height);

 

-LЫ:=TLabel.Create(Item); //метка с именем файла

LЫ.Parent:=Itern; LЫ.Position.X:=50; LЬl.Position.Y:=0;

LЬl.Text:=System.IOUtils.TPath.GetFileNarne(SDA[i]);

 

ListBoxl.AddObject(Item);

end;

end;

ListBoxl.EndUpdate;

end;


Если вы повторите код без ошибок, то увидите (рис. 10.3), что каждый из элемен­ тов списка станет обладателем миниатюры изображения (об этом позаботится экземпляр класса Timage) и названия файла (оно отобразится в метке TLabel).


Рис. 10.3. Элементы TListBoxitem списка с дополнительными объектами

 

 

Доступ к выделенному элементу списка

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

property Selected: TListBoxitem;

 

Если в списке выбора не выделен ни один из элементов, то в свойстве окажется не­ определенное значение nil.

Порядковый номер выбранного элемента цоступен благодаря свойству

property Itemindex: Integer;

 

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

 

ЗАМЕЧАНИЕ

Свойства Selected и Itemindex моrут применяться для выделения элемента списка из кода программы.

 

 

 



 

Доступ к произвольному элементу списка

Для доступа к любому (необязательно выделенному пользователем) элементу спи­ ска из кода программы проще всего воспользоваться методом

function IternВyindex(const·Idx: Integer): TListBoxitem;

Функции необходим всего один аргумент - индекс интересующего нас элемента списка.

Точно такого же результата можно добиться благодаря свойству

property Listitems[Index: Integer]: TListBoxitem;

К числу альтернативных способов доступа к элемеmу TListBoxitem можно ОТН СТИ

метод

function IternВyPoint(const Х, У:  Single): TListBoxitem;

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


procedure TForml.ListBoxlMouseMove(Sender: TObject; Shift: TShiftState;

Х, У:  Single);

var Item:TListBoxitern;

Ьegin

Itern:=ListBoxl.IternВyPoint(X,Y);

if Itern<>nil then Labell.Text:=Itern.Text else

Labell.Text:=' ';

end;

 

 

Выбор нескольких элементов

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

property MultiSelect: Boolean; //по умолчанию false

 

ЗАМЕЧАНИЕ

Для одновременного выбора нескольких элементов списка пользователь. должен удерживать в нажатом состоянии клавишу <Ctrl> или <Shift>.

 

Листинг 10.5 демонстрирует порядок сбора названий выделенных элементов спи­ ска, для повторения примера не забудьте установить у списка свойство MultiSelect в состояние true.


procedure TForml.ListBoxlClick(Sender: TObject); var i:integer;

Ьegin

Labell.Text:='';

for i  :=О to  ListBoxl.Count-1 do

if  ListBoxl.IternВylndex(i) .IsSelected=true then

Labell.Text:=Labell.Text+#0#lЗ+ListBoxl.IternВyindex(i) .Text;

end;

 

 

Для выбора диапазона элементов предназначен метод

procedure SelectRange(Iternl, Itern2: TListBoxitern);

Для одновременного выбора сразу всех элементов стоит обратиться за помощью к методу

prooedure SelectAll;

Обратную задачу -    снятие выделения легко решит процедура

procedure ClearSelection;

 

Представление элементов в виде кнопки выбора

В той ситуации, когда пользователю следует отметить в списке несколько .элемен­ тов, на помощь приходит свойство

property ShowCheckboxes:boolean;      //по умолчанию false

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

 

Перестановка элементов

Для перемещения элементов внутри списка следует обращаться к методу

proc;,ec:i'IЦ'$ Exchange(Iteml,  Itern2:  TListBoxitern);

Процедура поменяет местами элементы Iternl и Itern2.

 

Сортировка элементов

Наиболее востребованная сортировка заключается в упорядочении элементов спи­ ска по текстовым значениям. Такая сортировка производится при посредничестве свойства

property Sorted:boolean; //по умолчанию false

По умолчанию, при переводе свойства в состоящ1е true осуществляется алфавитная сортировка элементов списка по их свойству техt. Однако повеление списю1 нР.-

 

 



С!Т()ЖН() и И МР.НИП,, д.шr этого следует вспомнить о сущtспюшшии GЩШ66flfifif.a ёо:

бытия

property OnCompare: TOnCompareListBoxitemEvent; TOnCompareListBoxitemEvent = procedure(Iteml, Item2: TListBoxitem;

var Result: Integer)· of  object;

Событие точнее череда событий) oncompare () генерируется в момент попарного сравнения элементов Iteml и Item2 списка. Результат сравнения, от которого зави­ сит очередность элементов списка, нам следует определить самостоятельно в пара- метре Result.                                                 '

Допустим, что в нашем распоряжении имеется список Listвoxl и пара кнопок вы­ бора -  RadioButtonl и RadioButton2, определяющих порядок сортировки элементов ts \;Нискt: (нu tsuзрастанию и по убыванию). В этом случае для решения задачи сор­ тировки нам подойдет пример, предложенный в листинге 10.6.


procedure TForml.RadioButtonlClick(Sender: TObject);

Ьegin

//общее событие для кoмпoнeнтoв·RadioButtonl и RadioButton2 ListBoxl.Sorted:=false;

ListBoxl.Sorted:=true;

end;

 

procedure TForml.ListBoxlCompare(Iteml, Item2: TListBoxitem;

var Result: Integer);

Ьegin

if RadioButton2.IsChecked then

if Iteml.Text>Item2.Text then Result:=l else if Iteml.Text<Item2.Text then Result:=-1

else Result:=0

else

if RadioButtonl.IsChecked then

if Iteml.Text<Item2.Text then Result:=l el

if Iteml.Text>Item2.Text then Result:=-1

else Result: =О;

end;

 

 

ЗАМЕЧАНИЕ

Благодаря событию OnCompare () элементы списка могут быть упорядочены не только по текстовым, но и по любым другим типам данных.

 

Текстовый поиск, элемент TSearchBox

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


вания вообще отучат нас от придумывания исходного кода. Не станем полемизиро­ вать на тему хорошо это или плохо. Просто предлагаем щелкнуть по компонеmу TListBox правой кнопкой мыши и добавить к содержащему произвольные тексто­ вые элементы списку элемент поиска тsearchBox (рис. 10.4).


Рис. 10.4. Добавление элемента поиска TSearchВox

 

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

 

Особенности оформления списка

Список позволяет изменить размеры принадлежащих элементов и назначить цен­ трализованно при посредничестве свойств

property ItemWidth: Single; //ширина элемента

property ItemНeight:  Single;//выcoтa элемента

 

ЗАМЕЧАНИЕ

При желании не сложно присвоить индивидуальньiе размеры для каждого из элемен­ тов списка, в таком случае в свойствах ItemWidth и ItemНeight списка отобразятся нули.

Расположение элементов списка определяется состоянием свойства

property ListStyle: TLi;tStyle; //по умолчанию TListStyle.lsVertical;

По умолчанию оно предполагает классическое размещение элементов списка один над другим. Изменив состояние свойства в lsHorizontal, мы добьемся того, что элементы развернутся на 90° против часовой стрелки и расположатся слева направо.

Сво ство

property Colшnns: Integer; //по умолчанию 1 колонка

позволит создать много.колоночный список.


 


Основные события списка

Наряду с классическим набором событий, которыми обладают все элементы управ­ ления FireMonkey (потомки класса тcontrol), компонент-список вооружен рядом эксклюзивных событий, основные из которых предпожены в табл. 10.2.

 

Таблица 10.2. События списка ТListBox

 

Событие

Описание

property OnCh�nge: ТNotifyEvent;

Генерируется в момент выбора элемента списка

property OnChangeCheck:  TNotifyEvent;

Генерируется в момент щелчка по кнопке выбора

property OnCompare: TOnCompareListBoxitemEvent;

Событие, сравнивающее  леменп,1 списка

в момент сортировки

 

 

Нередактируемый комбинированный список

ТСотЬоВох

В сравнении с обычным списком выбора у нередактируемого комбинированного списка тсоmЬоВох есть два важных отличия. Первое отличие - визуальное: по умолчанию комбинированный список свернут и практически не занимает места на форме. Это весьма важное преимущество, особенно в ситуации, когда пользова­ тельский интерфейс разрабатываемого приложения перенасыщен элементами управления. Второе отличие: компонент тсоmЬоВох не предназначен дпя одновре­ менного выбора нескольких значений, это бы противоречило самой логике работы

' комбинированного списка, ведь в свернутом состоянии список-компонент отобра­ жает всего один элемент. В остальном компоненты очень похожи, это подтвержда­ ет табл. 10.3, в которой представлены наиболее важные свойства и методы комби­ нированного списка.

 

Таблица 10.3. Основные свойства и методы тсотЬоВох

 

Свойства и методы

Описание

property Items.: TStrings;

Набор текстовых строк, отображаемых в списке. Отметим, что компонент не умеет хранить свои элементы, поэтому их следует загружать/сохранять в файле методами LoadFromFile () и

SaveToFile ( )

property Count: Integer;

Число элементов в списке

property Selected: TListBoxitem;

Выбранный элемент

property Itemlndex: Integer;

Индекс выбранного элемента

property Listltems[Index: Integer]: TListBoxitem;

Доступ к любому элементу списка по его индексу

procedure Clear;

Очистка списка


Из наиболее интересных черт комбинированного списка отметим щюйство

property DropDownCount: Integer;

определяющее число элементов, отображаемых в выпадающем списке, однако сра­ зу заметим, что в проектах для iOS и Android вместо списка вы увидите вращаю­ щийся "барабан", ко:горый появляется внизу экрана в момент выбора пользователем интересующего его элемента (рис. 10.5).

Допускается изменить место вывода списка на экран. В простейшем случае для этого следует "поколдовать" со свойством

property Placernent: TPlacernent;

TPlacernent = (plBottorn, plTop, plLeft, plRight, plCenter, plBottornCenter, plTopCenter, plLeftCenter, plRightCenter, plAЬsolute, plMouse, plMouseCenter);

По умолчанию список выпадает под элементом управления (plBottorn). Проявив еще немного настойчивости и воспользовавшись свойством property PlacernentRectangle: TBounds;

можно более точно настроить область вывода списка.


Рис. 10.5. Поведение комбинированного списка ТСоmЬоВох в проектах для мобильной платформы

 

 



 


Чтобы заставить компонент показать выпадающий список в ко.де ваl!! .!! !!}?О!'J? !:-!­ мы, обратитесь к методу

prooedure DropDown;

В завершение недолгой истории комбинированного списка напомним ключевое событие компонента

property OnChange:  ТNotifyEvent;

Событие вызывается в момент смены элемента списка.

В составе компонентов FMX имеется комбинированный список тcolorCornЬoBox, позволяющий пользователю выбрать один из заранее предопределенных цветов. Интересно, а сможет ли с подобной задачей справиться классический тсоmЬоВох? Листинг 10.7 доказывает, что никаких проблем в этом нет.

 

.' ,        "' '1" · "  , . ·· ()V                       , 1..

'               ',                       ,,,..,.,)'!!f, i!i,,/·'f,i:,"11,,J;!"''

prooedure TForml.FormCreate(Sender: TObject);

var Item:TListBoxitem;

Prooedure  CreateColorRect(Parentitem:TListBoxitem);

var R:TRectangle;

Ьegin

R:=TRectangle.Create(Parentitem); R.Parent:=Parentitem; R.Fill.Color:=Parentitem.Tag; R.Position.X:=l; R.Position.Y:=l;

R.Height:=Parentitem.Height-2; R.Width:=R.Height;

end;

 

Ьegin

Item:=TListBoxitem.Create(CornЬoBoxl); Item.Text:='Kpacный'; Item.Tag:=claRed; CreateColorRect (Item);

Item.TextAlign:=ТTextAlign.taCenter; CornЬoBoxl.AddObject(Item);

 

// и т. д.

end;

 

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

Для того чтобы воспользоваться услугами нашего комбинированного списка, на­ пример, чтобы перекрасить поверхность формы, выбираем собьпие onchange (лис­ тинг 10.8).


procedure  TFoпnl.ComЬoBoxlChange(Sender:  TObject);

Ьegin

if     ComЬoBoxl.Itemindex<>-1 then

Foпnl.Fill.Color:=ComЬoBoxl.Selected.Tag;

end;

 

 

Редактируемый комбинированный список

TComboEdit

В отличие от обычного комбинированного списка тсоmЬоВох его собрат тcomЬoEdi t в качестве своего опорного класса избрал специалиста по обработке текста - класс тcustomEdit. Благодаря этому поступку тcomЬoEdit приобрел полезную способность редактировать текст в строке ввода.

Мы не станем повторяться и вновь перечислять свойства и методы класса

.                                                                                                '

тcustomEdit (вы их найдете в главе 9), отметим лишь то, что основным свойством,

унаследованным редактируемым комбинированным списком от строки ввода, вы­ ступает свойство техt.

Если вы внимательно прочитали страницы главы, посвященные списку TListвox и

_ комбинированному списку тсоmЬоВох, то уже готовы работать с компонентом тcomЬoEdi t. Несмотря на то, что тcomЬoEdi t имеет несколько иную цепочку пред­ ков, создатели компонента оснастили его набором свойств и методов как по назва­ нию, так функционально повторяющих свойства и методы уже рассмотренных спи­ сков. Единственное, о чем стоит напомнить, так это свойство

property Items: TStrings;

в котором хранится список строк компонента.

Полагаем, что уже настало время рассмотреть пару примеров, раскрывающих осо­ б ННQСТИ компонента TComЬoEdi t. Листинг 10.9 демонстрирует один из способов заполнения списка во время выполнения приложения

 

··········•,. .., ···-······· ,··,i

i

•·· .i.

procedur� TFoпnl. ComЬoEdi tlKeyDown ( Sender:  TObj ect; var Кеу Word;

var        KeyChar: Char; Shift: TShiftState); var s:string;

щin.

1f (Кеу=lЗ) then //код клавиши <Enter>

Ьeg1n

s:=Trim(ComЬoEditl.Text); //введенная пользователем строка

1f s<>'' then         //если строка не пуста, Ьegin               //добавим строку в список ComЬoEditl.Items.Add{s);

 

 



 


 

 

 

end;


ComЬoEditl.Text:='';

end;


 


Если вы пробовали работать со списком выбора или с обычным комбинированным списком, содержащим большое число элементов, то уже наверняка уоедились в том, что на поиск требуемого элемента может уйти много времени. Несколько строк кода из листинга 10.1 О значительно упростят жизнь пользователя. Теперь для выбора текстового элемента из отсортированного по алфавиту списка достаточно набрать первые символы искомого текста. В ответ на это список самостоятельно отыщет нужный элемент и подставит недостающие символы.


prooedure TFonnl.ComЬoEditlKeyUp(Sender: TObject; var Кеу: Word;

var KeyChar: Char; Shift: TShiftState);

var i,x:integer;

s:string;

Ьegin

if Кеу<>lЗ then Ьegin

//пользователь вводит первые символы в строку ввода

x:=Length(ComЬoEditl.Text); //число введенных символов

if х>О then

for i := О to ComЬoEditl.Count-1 do Ьegin s:=Copy(ComЬoEditl.Items[i],1,x);

if UpperCase(s,loUserLocale)=

UpperCase(ComboEditl.Text,loUserLocale) then //начало слова совпало

Ьegin

//копируем недостающую часть слова s:=Copy(ComЬoEditl.Items[i],x+l,Length(ComЬoEditl.Items[i])-x);    ComЬoEditl.Text:=ComЬoEditl.Text+s;             //в строку ввода ComЬoEditl.SelStart:=x;    //выделяем скопированную часть ComЬoEditl.SelLength:=Length(ComЬoEditl.Text)-x;

ComЬoEditl.CaretPosition:=x; //сохраним позицию каретки break;

end; end;

end else

Ьegin

//пользователь нажал <Enter> и подтвердил свой выбор s:=UpperCase(ComЬoEditl.Text,loUserLocale);

//еще раз убедимся, что такое слово есть

for i:=  О to ComЬoEditl.Count-1 do

if s=UpperCase(ComЬoEditl.Items[i],loUserLocale)  then


 

 

 

 

 

end; end;


Ьegin

ComЬoEditl.Itemindex:=i; //выбираем элемент

break;

end;


 

Компонент выбора значения ТРорирВох

Основу компонента выбора значения TPopupBox составляет свойство

property Items: TStrings;

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

property Text:String;

Порядковый номер вь1бранной строки отобразится в свойстве

property Itemindex: Integer;

В момент смены элемента генерируется событие

property OnChange: TNotifyEvent;

Как видите, элемент управления TPopupBox по своему духу очень близок к класси­ ческому комбинированному списку. Разница заключается лишь в том, что на этот раз список текстовых элементов не выпадает из компонента, а всплывает над ним (рис. 10.6).


 

 

 

о Дl!МОН ЦИR 11'.ОМ Т

 

 

              ,- TPopupВo>t

 

 

 

 

 

Рис. 10.6. Внешний вид компонента TPopupBox

 

 

 



 

 

 

 

 

 


ГЛАВА 11

 

 

Иерархическая  труктура

 

 

 

 

В окружающем нас мире иерархические структуры распространены весьма широ­ ко: это и структура предприятия, и дерево каталогов на жестком диске компьютера, и иерархия наследования классов FireMonkey. Можно продолжать приводить мно­ гочисленные примеры иерархически организованных данных, но лучше сразу за­ острить свое внимание на их объединяющей черте - наличии между элементами данных отношения "главный - подчиненный" (родительский - дочерний). Еще один важный отличительный признак иерархических структур заключается в том, что родительский узел дерева способен обладать неограниченным числом дочерних узлов. В свою очередь любой дочерний узел имеет право выступать владельцем целой ветви подчиненных узлов. Тот факт, что структура дерева заранее неизвест­ на, значительно затрудняет хранение иерархии в памяти и осуществлеflие операций с узлами дерева. К счастью, в составе FireMonkey имеется элемент управления, с легкостью решающий задачу обслуживания иерархических данных: главный и единственный герой этой главы - компонент ттreeView.

Компонент ттreeview (если следовать дословному переводу - дерево просмотра) представляет собой логическое объединение двух классов и интерфейса:

    компонент-дерево ттreeView, обеспечивающий визуализацию иерархической структуры и пользовательский интерфейс;

    класс ттreeViewitem, на котором строится узел иерархии;

   интерфейс Пtemscontainer позволяет как дереву, так и узлу дерева выступать в роли контейнера для других элементов. Дерево ттreeView является прямым вла­ дельцем узлов, описывающим самый верхний уровень иерархии. Узлы верхнего уровня способны выступать в роли главного узла по отноше ию к подчиненным узлам, которые, в свою очередь, владеют узлами следующей ступени.

Если дерево изначально статично и состав его узлов не должен изменяться в ходе выполнения приложения, то для создания иерархии элементов проще всего вос­ пользоваться встроенным редактором Items Designer, вызываемым из контекстного меню размещенного на форме компонента ттreeView (рис. 11.1). Однако мы с вами не станем искать простых путей и в этой главе напишем код для дерева, способного динамически создавать свои узлы.



Рис. 11.1. Редактор элементов ltems Designer

 

 

Узел дерева TTreeViewltem

Все элементы иерархической структуры создаются на основе класса ттreeViewitern, который в свою очередь является потомком текстового класса ттextcontrol. От своего текстового предка элемент иерархическо·й структуры унаследовал базовое свойство

property Text: string;

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

 

Управление дочерними узлами

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

property Items[Index: Integer]: TTreeViewitem;

Точно так.ой же результ т можно получить, передав индекс в метод

function IternВyiвdex(const Idx: Integer): TTreeViewitem;

В ответ функция возвратит ссьmку на запрошенный узел. Сведения о количестве подчиненных узлов хранит свойство

property Count: Inte�er; //r9П�К9 для чтения

 

 



 

 

Иерархическая структура                                                                                         177

l:SHHMAHHE!

Свойство Items и метод ItemВyindex () отвечают за предоставление доступа толькок непосредственно подчиненным узлам (находящимся на следующем уровне nосле ро­ дительского узла). Это же замечание справедливо и для свойства count.

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

function ItemВyPoint(const Х, У:  Single): TTreeViewitern;

Дочерние узлы владеют информацией о своем родительском узле, она доступна благодаря методу

function Parentitem: TTreeViewitem;

Благодаря свойству Parentitern несложно проконтролировать всю цепочку РQАИ­ тельских узлов для текущего узла. Например, предложенная в листинге 11.1 функ­ ция GatPath () может применяться в приложении, которое использует компонент ттreeView в качестве дерева каталогов (р с. 11.2). Получив в качестве параметра ссылку ·на выбранный пользователем узел Item, функция (перебирая последова­ тельность предков узла) построит полный файловый путь.


Рис. 11.2. Компонент TTreeView в качестве дерева каталогов

 


function TForml.GetPath(const Item: TTreeViewitern): string;

var Temp:TTreeViewitem;

Ьegin

Ternp: =Itern; Result:=' '; repeat


 

if temp.Parentitem<>nil then //пока есть родительский узел Result:=temp.Text+TPath.DirectorySeparatorChar+Result //уровень папок

else Result:=temp.Text+Result; //уровень дисков Temp:=Temp.Parentitem;

until Temp=nil;

end;

 

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

function TreeView:  TCustomTreeView;

Для добавления нового узла самого верхнего (первого) уровня следует задейство­ вать метод

prooedure AddObject(AObject: TFmxObject); override;

Изъятие дочернего узла осуществит метод

prooedure RemoveObject(AObject: TFmxObject); override;

 

Положение узла в дереве

Существует несколько взаимодополняющих способов определения положения узла

в дереве. Во-первых, узел характеризуется уровнем, о котором нам расскажет метод

function Level: Integer;

При обращении к узлу самого верхнего уровня функция возвратит 1, при обраще­ нии к узлу первого вложения  2, и т. д.

Все дочерние узлы знают свой порядковый номер в списке узла-владельца. Об этом заботится свойство

property Index: Integer;

Свойство Index сохраняет работоспособность и для узлов самого верхнего уровня

(их владельцем выступает компонент TTreeView).

l<:роме локального индекса узел обладает и глобальным номером, определяемым

свойством

property Globalindex: Integer;

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

видимого узла, если же уз л невидим (например, свернут в составе родительского узла), то он исключается из нумерации.

 

ВННМАННЕ!

После изменения положения узла (например, после процедуры сортировки) его ин­ дексы изменяются!


 


 

 

Иерархическая структура                                                                                                                         179

Состояние узла

Узел дерева обладает рядом важных характеристик, описывающих его текущее 1,о­ стояние. В первую очередь, это сведения о том, развернут узел или свернут:

property IsExpanded: Boolean;

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

Если в компоненте TTreeView активировано свойство ShowCheckЬoxes (заставляющее каждый узел дерева отображать кнопку-переключатель), то стоит обратить внима­ ние на свойство

property IsChecked: Boolean;

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

Если компонент ттreeView позволяет пользователю одновременный выбор несколь­ ких узлов (см. свойство MultiSelect), то проверку факта выбора узла позволит осу­ ществить свойство

property IsSelected: Boolean;

 

 

Дерево TTreeView

Хотя на основе класса ттreeViewitem можно создать вполне самостоятельный объ­ ект, он не сможет в полной мере выполнять возложенные на него задачи до тех пор, пока не попадет в распоряжение компонента ттreeView. Благодаря дереву ттreeview экземпляр класса ттreeViewitem визуализируется и превращается в узел, к которому сможет "прикоснуться" пользователь.

Дерево способно централизованно назначить вертикальный размер всем своим элементам, для этого предназначено свойство

property ItemНeight:  Single;

 

Выделение узла

По умолчанию дерево позволяет пользователю вьщелить только один узел, если вас это не устраивает, то переведите в состояние true свойство

property MultiSelect: Boolean; //по умолчанию false

Для выяснения, какой из узлов выбран, следует проконтролировать свойство

property Selected:  TTreeViewitem;

Оно возвратит ссылку на выделенный узел. Если дерево допускает одновременное выделение нескольких узла , то для определения, выделен ли узел, следует контро­ лировать состояние свойства IsSelected у каждого из узлов дерева.


 


 

Если вы хотите, чтобы при утрате фокуса ввода компонент ттreeView снимал визу­ альное выделение узла, то установите в true свойство

property HideSelectionUnfocused: Boolean; //по 1,1Молчанию false

В момент выбора узла (как вручную с помощью мыши и клавиатуры, так и про­ граммным способом) генерируется самое главное событие компонента

property OnChange: TNotifyEvent;

 

Доступ к узлу

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

property Items[Index: Integer]: TTreeViewitem;

или метод

function ItemВyindex(const Idx: Integer): TTreeViewitem;

Как свойство, так и метод предоставят программисту ссылку на узел первого уров­ ня по его индексу. Если число узлов верхнего уровня неизвестно, то справку по этому вопросу предоставит свойство

property Count: Integer;//тoлькo для чтения

Если нам следует работать с узлами более глубоких уровней вложения, то следует воспользоваться методом

function ItemВyGlobalindex(const Idx: Inte�er): TTreeViewitem;

Функция возвратит экземпляр узла по его глобальному индексу, но с одной суще­ ственной оговоркой - этот узел должен быть видимым. Число видимых; элементов в дереве известно свойству

property GlobalCount: Integer;

Кроме того, доступ к видимому узлу по его экранным координатам позволит осу­ ществить метод

 

 
functio.n ItemВyPoint(const Х, У: Si.ngle): TTreeViewitem;

Перечень способов доступа к узлу завершает метод

function ItemВyText(const AText: string): TTreeViewitem;

На этот раз поиск узла производится по содержимому его свойства техt.

Управление составом узлов

Если логика создаваемого приложения предполагает необходимость динамического

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

procedure AddObject(AObject: TFmxObject); override;

f�o�uro f§ill9Y§9Dj@Gt(д0bj@ct: TFmxObjectJ; override;

 

 



 

 


Иепапхuчеr.кая структура

 

Первая процедура добавит узел AObj ect в самый верхний уровень иерархии, а вто- рая - удалит указанный узел.                                                                                                                      '

 

ВННМАННЕ!

Методы AddObject () и RemoveObject () универсальны и способны работать не только

с узлами TTreeViewitern, но и с любыми другими объектами из библиотеки FMX.

Наиболее кардинальными возможностями обладает метод

procedure Clear;

Он позволяет полностью освободить дерево от узлов.

P1;;шt:нut: uднuй из задач, демонстрирующей процесс динамического заполнения узлами дерева TreeViewl, предложено в листинге 11.2. В данном примере мы соби­ раем сведения о доступных логических дисках компьютера с Windows или папках

верхнего уровня для станции с OS Х и передаем сведения в иерархическую струк­

туру.


procedure TfrmМainDuЫicate.UpdateTopLevelFolders;

var SDA:TStringDynArray; Item:TTreeViewitem; i:integer;

Ьegin. try

TreeViewl.BeginUpdate;

TreeViewl.Clear;

{$IFDEF MSWINDOWS}

{/                                                           ДИСКИ WINDOWS

SDA:=TDirectory.GetLogicalDrives;

{$ELSE}

{$IFDEF МАСОS}

//----------------- КОРНЕВЫЕ ПОПЬЗОВАТЕЛЬСКИЕ ПАПКИ OS Х------------------------------ ­

SDA:=TDirectory.GetDirectories(TPath.GetHomePath, '*.*',

TSearchOption.soTopDirectoryOnly);

{$ENDIF}

{$ENDIF}

for i :=О to High(SDA) do Ьegin

Item:=TTreeViewitem.Create(TreeViewl);

Item,Text:=IncludeTrailingPathDelimiter(SDA[i]); Item.Parent:=TreeViewl;

TreeViewl .AddObject (Item);

end;

 

finally

TreeViewl.EndUpdate;

end; end;


ВНИМАНИЕ!

          Чтобы исключить лишние операции перерисовки дерева, операции добавления и уда­ ления узлов целесообразно заключать в программный блок, начинающийся с вызова метода BeginUpdate () и заканчивающийся обращением к методу EndUpdate ().

 

Воспользовавшись функцией GetPath () из листинга 11.1, мы сможем развить успех и научить дерево не только строить перечень дисков (или папок верхнего уровня OS Х), но и собрать сведения о каталогах, размещенных на этих дисках. В листин­ ге 11.3 предложен пример функции, на вход которой поступает ссылка на ассоции­ рованный с диском или папкой узел Parentitem, в ответ на это функция GetChildFolders () собирает сведения о подчиненных каталогах и заносит их в де­ рево.


procedure TForml.GetChildFolders(Parentitem: TTreeViewitem);

var SDA:TStringDynArray; Path:String; Item:TTreeViewitern; i: integer;

Ьeqin

TreeViewl.BeginUpdate;

while Parentitern.Count>0 do //удалим все дочерние узлы Parentitem.Iterns[0] .Destroy;

 

Path:=GetPath(Parentitern); //узнаем файловый путь (см. листинг 11.1)

if TDirectory.Exists(Path) then Ьeqin

SDA:=TDirectory.GetDirectories(Path); //собираем сведения for i:=0 to High(SDA) do //создаем дочерние узлы-каталоги

Ьeqin

Item:=TTreeViewitem.Create(Parentitem); Itern.Parent:=Parentitern;

Item.Text:= TPath.GetFileNarne(SDA[i]); Parentitem.AddObject(Item);

end;

end;

TreeViewl.EndUpdate;

end;

 

 

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


тингов 11.1-11.3· tтcтt:т!.т.я-


это-го_ -п.n,я,я_тт.,nбится описать несколько об


раб oгr,r.1\.-oD ,;;0601-


тий формы и компонента-дерева (листинг 11.4).


 

 


Иерархическая структура                                                                                           183


//создание формы-владельца компонента TreeViewl

prooedure TForml.FormCreate(Sender:  TObject);

Ьegin

UpdateTopLevelFolders; //сбор узлов верхнего уРовня (см. листинг 11.2) end;

 

//двойной щелчок по узлу дерева

prooedure TForml.TreeViewlDЫClick(Sender: TObject);

var Item.:TTreeViewitem;

.r.-.,,.Ln

Item:=TreeViewl.Selected;

if Item<>nil then //если есть выделенный узел

Ьegin

Item.IsExpanded:=NOТ Item.IsExpanded;

if TreeViewl.Selected.IsExpanded=true then

GetChildFolders(TreeViewl.Selected); (см. листинг 11.3)

end;

end;

 

 

Узел в роли флажка

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

property ShowCheckЬoxes: Boolean;  //по умолчанию false

 

ЗАМЕЧАНИЕ

Для проверки состояния переключателя следует обратиться к свойству IsChecked

узла дерева TTreeViewNode.

В момент включения/отключения переключателя у дерева вызывается событие

property OпChangeCheck: TNotifyEvent;

в коде которого можно описать реакцию компонента ттreeView на обращение поль­ зователя к переключателю.

 

Свертывание и развертывание узлов

В арсенале методов компонента ттreeView предусмотрены две процедуры, способ­ ные развернуть и свернуть всю иерархию узлов.

prooedure ExpandAll;

prooedure CollapseAll;


Картину дополняет свойство дерева

property CountExpanded: integer;

позволяющее программисту узнать число развернутых узлов.

 

Упорядочивание узлов дерева

Компонент ттreeView обладает способностью упорядочивать свои узлы. В про­ стейшем случае элементы дерева размещаются в алфавитном порядке своих тек­ стовых заголовков. Для этого следует установить в состояние true свойство

property Sorted: Boolean;//пo умолчанию false

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

property OnCompare: TOnCompareTreeViewitemEvent; TOnCompareTreeViewitemEvent =                                                                                  .

function(Iteml, Item2: TTreeViewitem): inteqer of object;

В рамках события следует сравнить параметры двух узлов, Iteml и Item2, и сделать вывод об их порядке следования по возвращаемому функцией значению.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

 

 


 

 

 

 

 

 

 

ГЛАВА 12

 

 

Сетки

 

 

 

 

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

В составе элементов управления FireMonkey имеются два компонента, специализи­ рующихся на представлении данных в табличном виде. Это компоненты TGrid и тstringGrid. Оба элемента управления объявлены в модуле FМX.Grid и являются

, наследниками одного и того же опорного класса сеток

type TCustomGrid = class(TScrollBox,  IItemsContainer)

Основная разница между компонентами в том, что сетка тstringGrid нацелена на обслуживание исключительно текстовых данных, а сетка TGrid - более универ­ сальна и позволяет работать не только с текстом, но и булевыми значениями, гра­ фическими объектами, списками строк. Такое функциональное разделение не­ сколько напоминает ситуацию с сетками тstringGrid и тorawGrid из библиотеки VCL, однако принцип построения сеток TGrid и тstringGrid абсолютно не похож на архитектуру их коллег из состава VCL.

 

ВНИМАНИЕ!

Компоненты-сетки FireMonkey предназначены для визуализации и редактирования данных, но они не способны обеспечить их хранение. Эта проблема ложится на плечи разработчика программного обеспечения.

 

 

Колонки сетки

Основной строительный элемент сеток FМХ - колонка. Колонка - это экземпляр класса тcolumn или экземпляр одного из потомков этого класса. Например, сетка строк тstringGrid специализируется исключительно на текстовых колонках тstringColumn, а сетка TGrid помимо колонок строк умеет обслуживать весьма экзо­ тические колонки (табл. 12.1).


 

Таблица 12.1. Колонки и ячейки сеток

 

Тип колонки

Тип ячейки                                    .

Описание

TStringColumn

TTextCell = class(TEdit)

Текст

TCheckColumn

TCheckCell = class(TCheckBox)

Флажок

TProgressColumn

TProgressCell = class(TProgressBar)

Шкала

TPopupColurnn

TPopupCell = class(TPopupBox)

Всплывающая панель

TimageColurnn

TimageCell = class(TimageControl)

Изображение

 

Для создания колонок во время визуального проектирования проще всего восполь­ зоваться контекстным меню компонента-сетки и выбрать там пункт Items Editor. В ответ на это действие Delphi отобразит на экране окно дизайнера Items Designer (рис. 12.1). Дальше программист выбирает тип колонки и добавляет колонку в сет­ ку. Колонка является вполне самостоятельным объектом, обладающим свойствами, методами и обработчиками событий.


Рис. 12.1. Редактор колонок компонента-сетки TGrid

 

Каждая из ячеек колонки во время редактирования данных интерпретируется как элемент управления определенного класса (см. табл. 12.1). Например, в колонке TStringColumn, специализирующейся на обслуживании текста, каждая из ячеек фак­ тически является строкой ввода TEdit, а колонка TirnageColurnn предоставляет дос­ тупк ячейкам TimageCell, способным обслуживать графические данные.

ВНИМАНИЕ/

Жи ненный цикл соответствующих ячейкам элементов управления находится в nря­ мои зависимости от в димости ячейки. Если ячейка исчезает с экрана (например,

скрывается за раницеи сетки после скроллинга), элемент управления удаляется, при появлении яч ики.Л .1\1! .1-!Т оссоэдается. Надо лонимап,, что вместе с нсчоа11000,,.,о,.,

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


 

Доа  мс1u.ц11v 1юз ол!ют обратиться к элементу управления, ассоциированному с конкретнои ячеикои колонки.                                                                            .

function CellControlByPoint(X, У: Single): TStyledControl; function CellControlByRow(Row: Integer): TStyledControl;

Какv видите, для идентификации достаточно передать экранные координаты (х, У)

ячеики или номер строки Row в колонке.

Опорный класс всех колонок TColuпm предоставляет своим наследникам минималь­ ный базовый набор свойств и методов. В первую очередь это свойство, описываю­ щее заголовок колонки

property Header: string;

колонка может быть переведена в режим "только Д/IЯ чтения" с помощью сво ства

property ReadOnly: Boolean;  //по умолчанию false

и скрыта с экрана с помощью свойства

property VisiЫe: Boolean;       //по умолчанию true

 

Сетка TGrid

Размер сетки можно уточнить благодаря свойствам

property RowCount: Integer;

property ColuпmCount: Integer; //только дпя чтения

Обратите внимание на то, что свойство RowCount позволяет изменять число строк в сетке. А свойство coluпmCount доступно только для чтения, т. к. количество коло­ нок в сетке определяется числом объектов тcolumn.

Доступ к экземпляру колонки проще всего производить по ее индексу

_property Columns(Index: Integer]: TColumn;

Кроме того, существует метод, идентифицирующий колонку по экранным коорди­ натам

function ColumnByPoint(conet Х, У: Single): TColuпm;

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

function CellControlByRow(Row: Integer): TStyledControl;

Заметьте, что в данном случае возвраща тся обезличенный тип данных тstyledControl, который следует привести к требуемому виду при посредничестве классов TTextCell, TCheckCell, TProgressCell, TPopupCell или TimageCell.

Во время создания пользовательского хранилища данных окажутся полезными со­ бытия

property OnSetValue: TOnSetValue;

type TOnSetValue = prooedure(Sender: TObject; const Col, Row: Integer;

const Value: TValue)  of  oЬject;


 

property OnGetValue: TOnGetValue;

type TOnGetValue = prooedure(Sender: TObject; const Col, Row: Jnteger;

var Value: ТValue) of oЬject;

Событие OnSetValue () генерируется в момент получения ячейкой с координатами Col и Row значения Value. Обратное событие onGetValue () вызывается во время чте- ния значения Value из ячейки.

 

3АМЕЧАННЕ

Используемый в обработчиках событий OnSetValue () и OnGetValue () тип данных system.Rtti.тvalue можно рассматривать как усовершенствованный аналог типа данных Variant, обладающий большим набором сервисных методов.

Упомянем еще одно событие сетки, связанное с обслуживанием данных

property OnEdititingDone: TOnEdititingDone;

type TOnEdititingDone = prooedure(Sender: TObject;

const Col, Row: Integer) of object;

На этот раз речь идет о реакции сетки на любое изменение содержимого ячейки.

 

ВННМАННЕ!

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

 

Сетка TStringGrid

Полезное преимущество сетки строк тstringGrid над обычной сеткой TGrid за­ ключается в том, что разработчики компонента сохранили в нем традиционное (для сетки тstringGrid из состава VCL) свойство

property Cells[ACol, ARow: Integer]: string;

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

 

Пример обслуживания текстовых данных

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


type TArea=packed. record

num:byte;                                //номер записи

country:string[40];           //название страны


aiea:s.ingie;

end;


//площадь,  занимаемая страной


 


Попробуем научить сетку TGrid отображать и редактировать содержимое файла. Для этого (воспользовавшись редактором колонок Items Editor) создайте три ко­

лонки тstringCol=, специализирующиеся на обслуживании текстовых данных (рис. 12.2).

 

 

 


 

 

 

 

 

 

4

Соеди11ен11ые Штаты Амери

9372610

s

Бi>а.,-ю,н,,:

'8547000

6

Аlsстралия

7Ш850

7

Индия

3287590

а

Арrенти11а

2760990

9

 

l<a311 CТ/lti

2717300

10

Судан

2505810

u

Дл)Кl(р

2381740

12

Конго, демо ратическая РС!<

2345410

13

CayдOBCIGl!A Аравия

2218000

14

Мексиu

1972550

15

 

ИНДОl<е3ИА

1919440

1"

1 n-u•

17 a-4n

 

 

 

 

 
Рис. 12.2. Сетка TGrid с тремя колонками TStringColШNl

 

 

 

 

 

 

 

 

 

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


var        Forml: TForml;

FS: TFileStream;               //файловый поток const filename='area.dat'; //vrмя файла

 

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


procedure TForml.FormCreate(Sender: TObject);

Ьegin

if FileExists(filename) then Ьegin

FS:=TFileStream.Create(filename,fmOpenReadWrite); Gridl.RowCount:=FS.Size div SizeOf(TArea);

end else raise Exception.Create('Фaйл '+filename+' не обнаружен!');

end;


 

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

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


prooedure TFoпnl.FormDestroy(Sender: TObject);

Ьeqin

FS.Destroy;

end;

 

После того как мы научили файловый поток загружать данные, перейдем к работе с сеткой. Нам предстоит решить две небольших задачи. В первую очередь, подго­ товим сетку к отображению данных из файлового потока. Для этой цели нам при­ годится событие сетки OnGetValue () (листинг 12.5). Обратите внимание, что для поиска требуемой строки в файловом потоке нам понадобился параметр Row, имен­ но благодаря ему мы позиционируем курсор в необходимом месте потока.


procedure TFoпnl.GridlGetValue(Sender: TObject;

const Col, Row: Integer; var Value: TValue);

var A:TArea;

Ьegin

FS.Seek(Row*SizeOf(TArea),soBeginning);//noзициoниpyeм курсор FS.Read(A,SizeOf(TArea));                                   //читаем данные из файла

case Col of

О:    Value:=TValue.FromVariant(A.nurn);

1:    Vslu : ТV3lU@.FromVariant(A.country);

2:     Value:=TValue.FromVariant(A.area);

end;

end;

 

Вторая задача связана с обеспечением возможности редактирования данных в сетке

и     сохранения их в файл, на э1:от раз воспользуемся событием onsetValue () (лис­ тинг 12.6).


prooedure TFoпnl.GridlSetValue(Sender: TObject;

Вба•t C6l, Row: Integer; const Value: TValue);

var A:TArea;

 

 

 



 

Ьeqin

FS.Seek(Row*SizeOf(TArea),soBeginning); //переходим в позицию записи FS.Read(A,SizeOf(TArea));                                       //считываем старые данные строки FS.Seek(Row*SizeOf(TArea),soBeginning); //переходим в позицию записи case Col of                                                                                         //считываем исправления

О: A.num:=Byte(Value.Asinteger); 1: A.country:=Value.AsString;

2: A.area:=Value.AsVariant;

end;

FS.Write(A,SizeOf(TArea)); //записываем новое значение

end;

 

 

Программирование завершено. Мы научили сетку МJУМШИ}J9D11Тв П PЭJIIJfПtpOJШrt

данные, хранящиеся в типизированном файле.

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 



 

 

 

 

 

 


ГЛАВА 13

 

 

Окна сообщений и диалоги

 

 

 

 

Любой интегративный программный продукт нуждается и в обратной связ.ис поль­ зователем. В первую очередь этот контакт необходим для уведомления пользовате­ ля о завершении определенной операции или для подтверждения какой-либо команды.

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

В этой главе мы обсудим несколько категорий диалоговых о'Кон, от простейших окон сообщений и окон ввода текстовой информации до сложных диалогов доступа к файлам.

 

3АМЕЧАННЕ

BUBRUВfBl(B Fir@MBrtk@y n rраммный код окон сообщений и диалогов сосредоточен в модуле ЕМХ. Dialogs.

 

 

Окна сообщений

Самое простое, что мож сделать программа - оповестить пользователя O каком­ либо событии текстовой строкой. Окно с текстовым сообщением выводится про­ цедурой);

prooedure ShowMessage(oonst Msg: stri.ng

 

В результате, если речь идет о Windows, то ровно в центре рабочего стола появится оюю с текстом, квторый вы ранее передали в параметр Мsg, в случае OS Х окно со­ общения отобразится под заголовком окна приложения.

 

 

 



 

Окна сообщений и диалоги                                                                                                                         193

 

Если вы пишете приложение для Windows, то, возможно, вместо ShowМessage () вам больше понравится процедура

procedure ShowМessagePos(const Msg: string; Х, У: Integer);

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

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

prooedure ShowМessageFmt(const Msg: string; Params: array of const);

Форматирование осуществляется в соответствии с правилами форматирования строк, применяемыми в функции Format () : в первый параметр передается шаблон сообщения с форматирующими символами, во второй парамеlJ> направляется мас­ сив с данными. Пример работы с процедурой предложен в листинге.13 .1.


resourcestring Мsg='Сложное сообщение содержит:'+

#lЗ+'текст - %s'+#13+'целое - %d'+#13+'вещественное - %f';

var s:string;

i:integer; r:real;

Ьegin

s:='строка текста'; i:=555;

r:=111/13;

ShowMessageFmt(Msg, [s,i,r]);

end;

 

 

В результате выполнения листинга на экране компьютера появится диалоговое окно, представленное на рис. 13.1 (слева для OS Х, справа для Windows).

 


Рис. 13.1. Пример окон сообщений, полученных с помощью процедуры ShowMessageFmt ()


Окна вь1бора действия

Все рассмотренные ранее процедуры ShowMessage (), ShowMessagePos (), ShowMessageFmt () предназначены лишь для уведомления пользователя о том или ином событии и никак не влияют на дальнейшую логику выполнения приложения. В том случае, когда операция требует, чтобы пользователь подтвердил ее выполне­ ние, следует искать помощи у функции MessageDlg () В Delphi предусмотрены две перегружаемые версии этой функции, мы рассмотрим самую сложную:

funation MessageDlg(aont 'Мsg: trinq; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; HelpCtx: Longint; DefaultButton: TMsgDlgBtn): Integer; OV8rload;

В результате обращения к функции в системе Windows на экран выводится диало­ rовое окно, представленное на рис. 13.2.

 

Параметр Msg содержит текст сообщения

 


Параметр - а.

DlgType определяет значок и текст заголоека окна


 

с»о1

rнт  •h16ос)и

MIJUT• cocnиrc,yioщy,o _.11.i


 

,                          .               ..


Параметр Buttons назначает кнопки

окна

"


Параметр DefaultBut ton указывает на кнопку по умолчанию

Рис. 13.2. Окно выбора действия, вызванное функцией MessageDlg () в Windows

 

В OS Х внешний вид окна несколько отличается (рис. 13.3), однако функциональ­ ная нагрузка остается прежней.

В параметре Msg задается текст сообщения. Параметр DlgType определяет внешний вид диалогового окна в соответствии с типом TMsgDlgType (табл. 13.1).


Рис. 13.3. Окно выбора действия, вызванное функцией MessageDlg () в OS Х


 

 


 Окн1з сообщений и диалоги                                                                                        

 

Таблица 13.1. Оформление окнв вь1борв тмsgDlgТype

 

Значение

Особенности оформлении окна

mtWarning

Тревожное оповещение

mtError

Оповещение об ошибке

mtinformation

Информационное сообщение

mtConfirmation

Запрос подтверждения

mtCustom

Пользовательский вид

 

Любое диалоговое окно как минимум содержит хотя бы одну кнопку, нажав кото­ рую пользователь известит программу, что он ознакомился с сообщением. Еще ча­ ще в окне диалога располагается несколько кнопок, нажатие которых определяет дальнейшее поведение программы. Например, окно, спрашивающее у пользователя подтверждение на удаление файла, должно содержать кнопки Да (Yes) и Нет (No). Нажатие кнопки Да подтверждает операцию, кнопка Нет отвергнет. В код про­ граммы диалоговое окно возвращает модальный результат, соответствующий на­ жатой пользователем кнопке: кнопка Да-  константа rnrYes, кнопка Нет- rnrNo, и т. д. Задачей программиста является обработка этого результата. Какие имен­ но кнопки будут  размещены  в нижней части окна, определяет  параметр

Buttons:TMsgDlgButtons.

type TMsgDlgBtn = (mЬYes, rnЬNo, mЬОК, mЬCancel, mЬAЬort, mЬRetry, mЬignore, mЬAll, mnNoToAll, mЬYesToAll, mЬHelp);

TMsgDlgButtons = set     of TMsgDlgBtn;

Параметр функции HelpCtx предназначен для взаимодействия со справкой прило­ жения, в нем задается индекс соответствующей диалоговому окну страницы справ­ ки. Если справка отсугствует, то в параметр передается -1. Параметр DefaultButton определяет, какая из кнопок окна будет находиться в фокусе ввода в момент выво­ да окна на экран.

В листинге 13.2 приведен пример процедуры, удаляющей файл с именем FileName. Файл исчезнет с жесткого диска только в том случае, если пользователь подтвердит удаление нажатием кнопки Да.


prooedure FileDelete(FileName : string);

Ьegin

if MessageDlg('Yдaлить файл '+TPath.GetFileName(FileName)+' ?. ' ,

TMsgDlgType.mtConfiпnation,

[ТМsgDlgBtn.mЬYes] + [TMsgDlgBtn.mЬNo], 0) = rnrYes then

TFile.DeleteFile(FileName);

end;


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

function MessageDlgPos(const Msg: string; DlgType: TMsgDlgType;

Buttons: TMsgDlgButtons; HelpCtx: Longint; Х, У: Integer): Word;

Все параметры метода нам уже знакомы, единственная новость - координаты ле­ вого верхнего угла окна определяются аргументами х и У.

Еще одна похожая функция

functiori MessageDlgPosHelp(const Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; HelpCtx: Longint; Х, У: Integer;

const HelpFileName: string; DefaultButton: TMsgDlgBtn): Integer;

 

позволяет динамически подключать файл со справкой прямо к окну диалога в мо­ мент его создания.

 

Окна ввода данных

-             ,

Очень часто для работы приложения недостаточно получать односложные ответы

пользователя: "Да", "Нет" или "Отмена". Программе могут потребоваться фамилия пользователя, адрес электронной почты, пароль или другие данные. В таком случае окажется полезной функция

function InputBox(const ACaption, APrompt, ADefault: string): string;


В результате вызова Inputвox () создается форма, содержащая строку ввода и две кнопки: ОК и Отмена (Cancel). В параметре ACaption определяется заголовок диалогового окна, APrompt содержит надпись над строкой ввода, в ADefault пере­ дается значение по умолчанию. Пример работы с функцией предложен в листин­ ге 13.3.

var s: string;

Ьegin

s:= InрutВох('Создание файла',  'Имя файла',  'Новый файл.tхt');

// ...

 

Еще более удобная, на взгляд автора, функция

function InputQuery(const ACaption, APrompt: string;

.      var Value:  string): Boolean;

возвращает не только содержимое текстовой строки (для этого предназначен пара- метр Value), а еще и логическое значение, соответствующее нажатой пользователем кнопки: ОК- true, Отмена (Cancel) - false. Эта особенность функции позволяет улучшать код наших программ (листинг 13.4).

 

 

 



var s: string;

Ьegin

s := 'Новый пользователь';

if     InputQuery('Peгиcтpaция', 'Фамилия', s} = true then

{действия,  если нажата кнопка ОК}

else {действия, если нажата кнопка Cancel}; end;

 

Компоненты-диалоги

Открыв страницу Dialogs палитры компонентов FireMonkey, вы обнаружите пять компонентов-диалогов. Два компонента, TOpenDialog и тsaveDialog, вызывают диа­ логовые окна открытия/сохранения файлов. Диалоги TPrinterSetupDialog_ и TPrintDialog соответственно помогут подготовить принтер к печати и отправить задание на печать. Настройку размера, полей и ориентации страницы документа осуществит компонент TPageSetupDialog.

Все компоненты-диалоги построены на основе класса тcommonDialog, важнейшим методом которого считается

function Execute: Boolean;

Вызов потомками тcommonDialog функции Execute (} приводит к выводу на экран стандартного диалогового окна, работая с которым пользователь взаимодействует с операционной системой. При нажатии кнопки ОК в окне диалога функция воз­ вращает значение true, в противном случае -  false.

В классе описаны два базовых обработчика событий

property OnShow: TNotifyEvent; property OnClose: TNotifyEvent;

вызываемых соответственно в момент показа и закрытия диалогового окна.

 

Открытие и сохранение файлов

TOpenDia/og и TSaveDia/og

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

 

ЗАМЕЧАНИЕ

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



198                                                                                                                                                          Главв 13

 

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

property Filter: string;

произведет отбор только необходимь х пользователю файлов. Фильтр может быть настроен как в Инспекторе объектов, так и во время выполнения программы. До­ пустим, для приложения, повторяющего функционал Блокнота, фильтр диалогов открытия и сохранения файлов можно подготовить в момент создания главной формы приложения (листинг 13.5).


prooedure TFoпnl.FoпnCreate(Sender: TObject);

Ьegin

OpenDialogl.Filter:='Teкcтoвыe файлыl*.tхt) '; OpenDialogl.DefaultExt:='txt'; SaveDialogl.Filter:=OpenDialogl.Filter; SayeDialogl.DefaultExt:= OpenDialogl.DefaultExt;

end;

 

Обратите внимание на то, что текстовая строка фильтра включает два раздела: тек­ стовое описание фильтра и допустимое расширение в имени файла, а между разде­ лами устанавливается специальный символ 1 .

Допустимо определение сразу нескольких типов файлов в одной строке фильтра:

OpenDialogl. Filter := 'Графические файлы1 *. jpg; *. j peg; *. prig' ;

Если требуется программным образом описать несколько строк фильтра, то отде­ ляйте строку от строки все тем же символом-разделителем - вертикальной чер- той ( 1): .

OpenDialogl.Filter:='Teкcтoвыe файлыl*.tхtlВсе файлыl*.*) ';

В листинге 13.5 упоминается еще одно свойство диалога:

property DefaultExt: string;

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

 

ВНИМАНИЕ!

При вводе расширени_я имени по умолчанию помните, что оно включает только сим­ волы расширения, точку-разделитель указывать не следует.

Если используемый фильтр - многострочный, то следует определить, какая из строк фильтра станет использоваться по умолчанию при появлении диалогового окна. Индекс этой строки передается в свойство

property Filterindex: Integer;


Для определения имени папки, открываемой в момент первого запуска диалога, воспользуйтесь свойством

property InitialDir: strirщ;

При повторных запусках выбор каталога зависит от флага ofNoChangeDir в свойстве

Options диалогового окна (табл. 13.2).

Свойство

property HistoryList: TStrings;

позволяет хранить историю о предыдущих прочитанных/записанных файлах. Текст, подлежащий выводу в заголовке диалога, определяется в свойстве property Title: string;

Наиболее широкий спектр по настройке внешнего вида диалогового окна предос­ тавляет свойство

property Options: TOpenOptions;

выступающее множеством опций TOpenOption, представленных в табл. 13.2.

 

Таблица 13.2. Основные опции диалогов открытия и сохранения файлов

 

Значение ТOpenOption

Описание

ofReadOnly

Открывает окно в режиме ''только для чтения"

ofOverwritePrompt

Играет роль в диалогах записи файлов, запрашивает разре- шение на перезапись при совпадении имени сохраняемого и существующего файлов

ofHideReadOnly

Скрывает флажок ReadOnly

ofNoChangeDir     .

Если флаг установлен, то при повторных запусках диалога он открывает папку, определенную в свойстве InitialDir.

Иначе будет открыт каталог, с которым велась работа

в последний раз

ofShowHelp

Дополняет диалог кнопкой помощи

ofNoValidate

Отключает контроль за вводом недопустимого символа в имя

файла

ofAllowMultiSelect

Допускает одновременный выбор нескольких файлов

ofExtensionDifferent

Наличие флага обычно контролируется программистом во время выполнения приложения. Он автоматически устанавли- вается приложением в случае, когда расширение выбранного

файла,отличается от заданного в свойстве DefaultExt

ofPathМustExist

Вызывает сообщение об ошибке, если пользователь указал неверный путь к файлу

ofFileMustExist

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

ofCreatePrompt

Работает совместно с ofFileMustExist, запросит подтвер- ждение на создание несуществующего файла


 

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

 

Значение 'l'CpenCption

Описание

ofShareAware

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

ofNoReadOnlyReturn

Инициирует сообщение об ошибке при попытке обратиться к файлу с атрибутом ''только для чтения"

ofNoTestFileCreate

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

ofNoNetworkButton

Скрывает кнопку доступа к сетевому ресурсу (используется только совместно с флагом ofOldStyleDialog)

ofNoLongNames

Показывает файлы с форматом имени 8.3 (используется только совместно с флагом ofOldStyleDialog)

ofOldStyleDialog

Флаг обратной совместимости, создает диалоговое окно старого стиля

ofNoDereferenceLinks

Определяет порядок взаимодействия с ярлыками файлов. Если флаг отключен, то диалог возвращает реальное имя файла, определенное в ярлыке. Иначе диалог возвращает имя ярлыка

ofEnaЫeinclu�Notify

Используется для активизации обработчика события

Onincludeitem ()

ofEnaЫeSizing

Разрешает изменять размеры диалогового окна

ofDontAddToRecent

Пока не используется, в будущих версиях Delphi будет управ- лять добавлением ссылки на файл в список недавно исполь- зованных документов

ofForceShowHidden

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

 

Стоит напомнить, что фундаментальным методом диалогов открытия и сохранения файла является функция Execute ( ) . Она осуществляет вызов диалогового окна, в котором пользователь производит выбор файла для открытия или указывает имя сохраняемого файла. Если после работы с диалогом была нажата кнопка ОК, метод вернет true. После успешного вызова диалогового окна имя выбранного файла окажется в свойстве

property FileName:  TFileName;

Допустим, что мы разрабатываем проект "1'екстового редактора. На главной форме проекта Forml расположены диалоги открытия и сохранения файлов, редактор мно­ гострочного текста Memol: тмеmо. За вызов диалога открытия файла отвечает собы­ тие-щелчок по пункту меню miOpen, диалог сохранения вызывается щелчком по кнопке miSave (листинг 13.6).



var  FileName:string; //переменная для хранения имени файла

 

prooedure TFoпnl.miOpenClick(Sender: TObject); //открытие файла

Ьegin

if     OpenDialogl.Execute then

Ьegin

Memol.Lines.Clear; Memol.Lines.LoadFromFile(OpenDialogl.FileName); FileName:=OpenDialogl.FileName; Техtl.Техt:='Документ: '+ExtractFileName(FileName);

end; end;

 

prooedure TFoпnl.miSaveClick(Sender: TObject);

Ьegin

if SaveDialogl.Execute then Ьegin

FileName:=SaveDialogl.FileName;

if FileName<>'' then Memol.Lines.SaveToFile(FileName);

end;

end;

 

Щелчок по меню miSave вызывает метод Execute () диалога открытия файла, в ре­ зультате на экране вашего компьютера отобразится окно, позволяющее пользовате­ лю выбрать текстовый файл.

Теперь уделим немного времени обработке событий в компонентах открытия и со­ хранения файла. Оба компонента унаследовали события OnShow () и OnClose ( ) от своего предка- класса тcomonDialog. Помимо этого во всех диалогах имеется ряд вспомогательных событий (табл. 13.3).

 

Таблица 13.3. События диалогов открытия и сохранения файлов

Событие

Описание

property OnFolderChange: TNotifyEvent;

Происходит при открытии или закрытии папки в диалоговом окне

property OnSelectionChange:  TNotifyEvent;

.

Возникает при выборе пользователем но- воrо файла в списке файлов, применении нового фильтра, создании новой папки

property OnTypeChange: TNotifyEvent;

Вызывается при усrановке пользователем нового фильтра

property OnCanClose: TCloseQueryEvent;

type TCloseQueryEvent = prooedure(Sender: TObject;

var CanClose: Boolean) of aЬject;

Происходит при попытке закрыть диалога- вое окно (без отмены, т. е. без нажатия кнопки Отмена (Cancel)), параметр CanClose разрешает (true) или З'1прещает (false) закрытие окна

 

 
.


 

 

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

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

Работу с параметрами страницы следует начинать с определения размеров страни­ цы. Для удобства пользователя диалоговое окно обладает раскрывающимся спи­ ском Размер, в котором хранятся элементы с заранее предустановленными разме­ рами (АЗ, А4, А5 и т. п.), ориентация страницы выбирается в группе Ориентация. Настроенные пользователем высота и ширина страницы передаются в пару свойств:

property PageHeight: Single;

property PageWidth: Single;

Используемые в диалоге единицы измерения определяются свойством

property Units: TPageMeasureUnits;


type TPageMeasureUnits = (pmDefault,

prnМillimeters, pminches);


//локальные установки системы

//миллиметры

//дюймы


Кроме размеровстраницы диалог позволяет пользователю определять поля. Для этого предназначена четверка свойств:

property MarginLeft: Integer;          //левый отступ property MarginRight: Integer;                                                         //правый отступ property MarginTop: Integer;                                                         //верхний отступ property MarginBottom: Integer; //нижний отступ

Минимально допустимые значения полей ограничиваются соответствующими свойствами:

property MinМarginLeft: Integer; property MinМarginRight: Integer; property MinМarginTop: Integer; property MinМarginBottom: Integer;

Особенности диало,гового окна определяет классическое свойство property Options: TPageSetupDialogOptions;// [psoDefaultMinМargins] Опции представлены в табл. 13.4.

Таблица 13.4. Опции диалога  TPageSetupDialogOptions

 

Опция        .

Описание

psoDefaultMinмargins

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


 

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

 

Опция

Описание

psoDisaЫeMargins

Запрещает пользователю настраивать поля страницы

psoDisaЬleOrientation

Запрещает изменять ориентацию страницы

psoDisaЬlePagePainting

Отменяет режим прорисовки диалога по умолчанию

psoDisaЬlePaper

.

Запрещает изменять размер бумаги и настраивать особен- ности ее подачи

psoDisaЬlePrinter

Отключает кнопку вызова дополнительного диалога настройки принтера

psoMargins

Устанавливает все поля страницы в 1 дюйм

psoмinмargins

Устанавливает минимальные поля из свойств MinмarginLeft, MinмarginRight, MinмarginBottom и MinмarginTop, в против- ном случае эти поля определяются параметрами принтера

psoShowHelp

Показывает кнопку помощи

psoWarning

Отключает сообщение об ошибке при отсутствии установлен-

наго принтера

psoNoNetworkButton

Скрывает и отключает кнопку сетевых устройств

 

 

Настройка печати TPrinterSetupDia/og

Класс TPrintersetupDialog вызывает стандартное окно настройки параметров прин­ тера. Из всех существующих в Delphi компонентов-диалогов диалог настройки принтера самый неприхотливый в программировании. После выбора компонента

       в Инспекторе объектов мы обнаружим 5 опубликованных свойств (Name, таg, HelpContext, BindingName и StyleName) и 2 события (onclose и OnShow). Столь скром­ ный список свойств объясняется тем, что все сделанные пользователем настройки принтера не возвращаются в наше приложение, а автоматически обрабатываются системой и учитываются при отправке задания на печать.

 

Отправка задания на печать TPrintDialog

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

 

ВНИМАНИЕ/

Диалог печати только формирует параметры задания для. печати, сам процесс печати должен быть реализован силами программиста. Помощь в этом ему сможет оказать программный модуль FМХ. Printer.


 

Особенности поведения диалогового окна определяются опциями, устанавливае­ мыми в свойстве

property Options: TPrintDialogOptions;

Значения флагов свойства приведены в табл. 13.5.

 

Таблица 13.5. Опции диалога печати  TPrintDialogOptions

 

Значение

Описание

poPrintToFile

Выводит флажок перенаправления задания печати в файл

poDisaЬlePrintToFile

Запрещает печать в файл (делая флажок Печать в файл неак- тивным)

poHelp

-

Отображает кнопку помощи в окне диалога

 

poPageNшns

Разрешает пользователю выбирать диапазон страниц, отправ- ляемых на печать. В противном случае будет отправлен весь перечень страниц

poSelection

Разрешает пользователю печатать только выделенный фрагмент текста

poWarning

Генерирует сообщение об ошибке при попытке отправить зада- ние на неустановленный принтер

 

Большинство свойств диалога тем или иным образом взаимодействует с опциями окна печати. При использовании флагов poPageNums и poSelection пользователь по­ лучает право отправлять на печать несколько страниц или фрагмент задания. Ре­ зультат выбора пользователя отразится в свойстве

property PrintRange: TPrintRange;

При выборе для печати только фрагмента текста свойство PrintRange примет значе- · ние prSelection, при выборе нескольких страниц -                                    prPageNums. Если на печать от­ правляется весь док ент, то результат будет соответствовать prAllPages.

Совместно с опцией диалога печати poPageNшns трудятся свойства

property FrornPage: Integer;

property ToPage: Integer;

Значения свойств определяют начальную и конечную страницы диапазона печати. В случае если оба свойства установлены в О, то на печать направляется весь доку­ мент.

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

property MinPage: Integer;

property MaxPage: Integer;

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


 

Если допустимо перенаправление 1адания печати в файл и пользователь воспользо­

вался такой возможностью, то свойство

property PrintToFile: Boolean;

примет значение tru�.

Количество копий предназначенного для печати документа вы обнаружите в свой­ стве

property Copies: Int�ger;

Если число соответствует О или 1; то будет отпечатан только один экземпляр.

При печати нескольких копий имеет значение свойство

property Coliate: Boolean;

Благодаря ему вы укажете принтеру порядок подбора листов в копии документа.


 

 

 

 

 

 

 


ГЛАВА 14

 

 

Дата и время

 

 

 

 

Сейчас нам предстоит изучить компоненты FireMonkey, связанные с обработкой таких специфичных данных, как дата и время. Но прежде чем мы приступим к рас­ смотрению героев этой главы, на палитре компонентов нам предстоит получить ответ на самый важный вопрос: каким образом Delphi хранит значения даты и вре­ мени?

 

Дата и время TDateTime

Разработчики Delphi для работы с датой и временем стали использовать обычный вещественный тип данных DouЬle. Правда, для того чтобы акцентировать внимание программиста на то, что все-таки речь идет о именно дате и времени, а не о рядо­ вом действительном числе, в модуле System бьm qб явлен базовый тип данных

type TDateTirne = type DouЬle;

Идея хранения значений даты и времени в TDateTirne заключается в следующем: целая часть числа предназначена для запоминания даты, а значение после запя­ той - времени. Нулевому значению соответствует время: 30.12.1899 г. 00 ч. 00 м. 00 с. ООО мс. Для того чтобы "сдвинуть" дату ровно на сутки вперед и получить значение 31.12.1899 г. 00:00:00:000, достаточно прибавить к переменной типа TDateTirne единицу, вычитание единицы повернет дату вспять, и мы окажемся в 29.12.1899 г. Значению 36526,0417 соответствует 01.01.2000 г. 01:00:00.

 

ЗАМЕЧАНИЕ

Верхний. предел типа данных TDateTirne соответствует значению 31.12.9999 г. 23:59:59:999. Кроме того, TDateTirne допускает работу и с отрицательными значениями.

 

В дополнение к классу тоаtетi�е в Delphi прилагаются производные типы данных:

type TDate       type TDateTirne; //целая часть, только для хранения даты

type TTirne = type TDateTirne; //дробная часть, только для хранения времени


 

 


Д1зт1з и время                                                                                                              207

 

Тип данных тoateTime является основным, но далеко не единственным способом описания даты и времени. Например, для представления времени с точностью больше чем тoateTime (до миллисекунд) предназначена запись

type TTimeStamp = record.

Time: �nteger;  //миллисекунды от полуночи

Date: Integer; //количество дней, начиная с 01.01.0001 г.

end;

При работе с интервалом времени пригодится еще более сложная структура

TTimeSpan.

 

Интервал времени TTimeSpan

В той ситуации, когда программная логика предполагает работу не с текущим вре­ менем, а с временным интервалом (например, при оценке текущей позиции вос­ производимого трека в медиаплеере тмediaPlayer из состава компонентов FMX), то стоит воспользоваться услугами структуры TTimespan.

Структура TTimeSpan определена в модуле System. Timespan и представляет собой не просто хранилище полей, а интеллектуальный класс, с помощью которого про­ граммист с легкостью осуществит все основные преобразования данных временно­ го интервала.

Для инициализации структуры допускается воспользоваться конструктором. В про­ стейшем случае следует обратиться

constructor Create(ATicks: Int64); overload;

 

Единственный параметр конструктора -  значение временного интервала, едини­

цей измерения которого служит 100 наносекунд. Кроме того, предусмотрено не­ сколько перегружаемых версий конструкторов, в которых протяженность интерва­ ла можно задавать в часах, минутах, секундах и миллисекундах:

constructor Create(Hours, Minutes, Seconds: Integer); overload; constructor Create(Days, Hours, Minutes, Seconds: Integer); overload;

constructor Create(Days, Hours, Minutes, Seconds, Milliseconds: Integer); overload;

В состав класса входит несколько десятков операторов, свойств и методов, позво­ ляющих осуществлять манипуляции с интервалом времени. Например, для увели­ чения интервала следует просто воспользоваться операцией сложения (+).

Ряд доступных только для чтения свойств позволят нам декомпозировать и возвра­ тить хранящееся в структуре значение интервала:

property Ticks: Int64;             //значение в тактах property Days: Integer; .                                                             //значение в сутках property Hours: Integer;                                                             //значение в часах property Minutes: Integer; //значение в минутах property Seconds: Integer; //значение в секундах

property Milliseconds:  Integer;  //значение в миллисекундах


208                                                                                                            Глава 14

Отсчет времени, таймер TTimer

Невиз)'ЗЛьный компонент ттirner мы обнаружим на странице System палитры ком­ понентов, компонент описан в модуле ЕМХ. Types и построен на базе класса TFrnxObject. Таймер предназначен для генерации особых данных- сообщений об изменении времени. Дпя этой цели таймер с заданной периодичностью вызывает с0бытие

property OnTirner: TNotifyEvent;

в котором следует описать реакцию нашей программы на изменение времени.

Ключевое свойство таймера

property Interval: cardinal; //по умолчанию 1000 миллисекунд

определяет периодичность срабатывания таймера. Таймер управляется ещшственным свойством property EnaЫed: Boolean;

Установив EnaЫed в true, мы заставим компонент отсчитывать миллисекунды, пе­ ревод свойства в состояние false остановит таймер.

Пример из листинга 14.1 демонстрирует процесс создания секундомера. На этот раз, кроме метки и таймера нам понадобится еще пара кнопок твutton. Кнопка btnStart возьмет на себя ответственность за старт, а кнопка btnStop - за останов секундомера (рис. 14.1).

На этот раз для реализации секундомера предлагаю задействовать структуру­ интервал TTirneSpaп, для это,го не забудьте подключить в строку uses модуль


Systern. TirneSpan.


......-..,._..........


_,.. . ...........,.....,_ ....."' ...·--•-"'""······ ·..


 

  -······"'·"' --···---······..,.. ............. ................... '"--········••«...........-........_........ ..,,........ ..-

 

uses ...  ,  Systern.TirneSpan;

 


var      Forrnl:  TForrnl;

TS:TTirneSpan;  //интервал времени

implementation

{$R *. frnx}

prooedure TForrnl.ForrnCreate(Sender:  TObject);

Ьegin

Tirnerl.EnaЫed:=false;            //таймер отключен


 

 

 

 

//инициализация


Tirnerl.Interval:=50;                   //период генерации события - 50 мс Labell.Text:='00:00:00.000';

end;

 

prooedure TForrnl.btnStartClick(Sender: TObject); //Старт

Ьegin

TS:=TTirneSpan.FromМilliseconds(0); //сброс значения интервала


 

 

Дата и время                                                                                                                                               209

Labell.Text:='00:00:00.000';  //сброс  текста метки


Tirnerl.EnaЬled:=True;

end;


//старт  таймера


 


procedure TForml.TimerlTimer(Sender: TObject);  //Такт таймера

Ьegin

TS:=TS+TTirneSpan.FromМilliseconds(Timerl.Interval); //приращение Labell.Text:=Format('%.2u:%.2u:%.2u.%.3u',

[TS.Hours,TS.Minutes,TS.Seconds,TS.Milliseconds]);

end;

 

procedure TForml.btnStopClick(Sender: TObject); //Стоп

Ьegin

Tirnerl.Enabl'ed:=false;  //стоп таймера

end;


Рис.14.1. Секундомер для мобильной платформы IOS

 

 

Календари TCalendar и TCalendarEdit

На странице Additional палитры компонентов FireMonkey обосновались два ком­ понента, способные исполнять роль календарей. Это простой календарь тcalendar и элемент управления тcalendarEdit, представляющий собой симбиоз комбиниро­ ванного списка и выпадающего из него календаря.



210                                                                                                                                                         Глава 14

 

Оба наших компонента  построены  на фундаменте  общего предка -  классе

тstyledControl, отвечающего за стилевое оформление визуальных компонентов.

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

property Date: TDate;

Свойство oate может применяться и для установки текущей даты календаря из кода программы.

Два базовых события, связанных с деятельностью календаря, обеспечивают реак­ цию элемента управления на выбор пользователем даты

property OnDateSelected: TNotifyEvent;

и на изменение текущей даты

property OnChange: TNotifyEvent;

Под выбором даты следует понимать выделение пользователем даты на календаре с помощью указателя мыши или выбор даты на сенсорном экране.

В России трудовая неделя стартует в понедельник, в США и Англии - в воскре­ сенье. Для того чтобы исключить путаницу, программисты Embarcadero предложи­ ли нам самостоятельно определиться, с какого дня должна начинаться неделя в на­ ших программах. Для этого в праотце всех календарей объявлено свойство

property FirstDayOfWeek: TCalDayOfWeek;

type TCalDayOfWeek = (dowMonday, dowTuesday, dowWednesday, dowТhursday, dowFriday, dowSaturday, dowSunday, dowLocaleDefault);

Здесь: dowMonday -      понедельник, ..., dowsunday- воскресенье,  dowLocaleDefault -

день недели определяется автоматически локализацией версии Windows.

 


Рис. 14.2. Поведение календаря TCalendarEdit в IOS


 

 

Двтв иврвмя                                                                                                                                                 211

 

Отметим метаморфозу, на которую способен компонент тcalendarEdi t, когда он задействуется в приложениях для IOS. Здесь, в момент редактирования даты вызы­ вается фирменный календарь мобильных устройств Apple (рис. 14.2).

К дополнительным особенностям календаря тcalendarEdi t стоит отнести наличие специфичных событий, связанных с "выпадением" календаря из комбинированного списка

propert;y OnPopup:  TNotifyEvent;

и свертыванием календаря

propert;y OnClosePopup: ТNotifyEvent;

 

 

 

 

 

 

 

 

 

 

 

 

 



 

 

 

 

 


ГЛАВА 15

 

 

-Управление цветом

 

 

 

 

Любой из цветов радуги, который вы увидите на экране монитора, получается в результате сложения (в той или иной пропорции) всего-навсего трех базовых цве­ товых составляющих: красного, синего и зеленого цветов. Такую цветовую модель называют RGB, по первым буквам английских названий цветов (Red, Green и Вlue).

Почему выбраны именно эти цвета? Все объясняется физиологическими особенно­ стями строения человеческого глаза, он воспринимает цвета в диапазоне длин волн примерно 400 + 700 нанометров. Излучения с длинами волн от 380 + 470 нм имеют фиолетовый и синий цвета, от 480 + 500 нм -               сине-зеленый, от 51О + 560 нм - зеленый, от 570 + 590 нм - желто-оранжевый, от 600 + 760 нм -        красный. Вос­ приятие цвета глазом человека осуществляется за счет трех типов цветочувстви­ тельных фоторецепторов (колбочек). Существуют всего три типа этих фоторецеп­ торов -               колбочки, реагирующие на красный, зеленый или синий цвет. В зависимо­ сти от интенсивности той или иной цветовой составляющей соответствующий фоторецептор отправляет сигнал в наш головной мозг, где и формируется резуль­ тирующая картинка.

 

Представление цвета ARGB

Представление цвета в FireMonkey изначально ориентировано на полноцветную 32-битную модель, сочетающую в себе три классических 8-битньrх канала цветно­ сти RGB и альфа-канал А, отвечающий за прозрачность закрашиваемого пиксела. Таким образом, информация о цвете хранится в формате ARGB и описывается типом данных:

TAlphaColor.= type Cardinal;

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

 

 

 



 

Дпя назначения цвета можно сразу воспользоваться записью в шестнадцатеричном формате, например, значение $FF0000FF соответствует ·непрозрачному чистому си­ нему цвету, $FFFF0000 - непрозрачному красному, значение $7F00FF00 - наполо­ вину прозрачному зеленому. Однако это не всегда удобно, особенно когда следует получить тот или иной оттенок. Поэтому, чтобы сразу усвоить правила формирова­ ния цвета в формате ARGB, стоит познакомиться с небольшим примером. Дпя это­ го нам понадобятся:

   четыре компонента-ползунка ттrасkваr, которые будут отвечать за регулировку

интенсивности того или иного цвета и уровня прозрачности. Переименуйте ком­ поненты в tbRed, tbGreen, tbВlue и tbAlpha. Присвойте свойству Мах всей четвер­ ки значение 255;

  фигура Circlel: TCircle позволит увидеть изменения в цвете;

  текст тextl:ттехt отобразит числовое значение выбранного цвета.

·-· ---··--·-·

 
. Выберите событие OnChange ·() улюбого из компонентов TTrackBar и внесите в него строки кода, предложенные в листинге 15.1.


 

•••·•·•.. f'  ..,,...............':'lo.-...                                                                                     ••···•·"'r.,.,,.,.••. •.,1..........----..-.,A,,,...••.•.....           .....•....-.,.,- ,..............,.........,..t'.f........................ ••,.,,.,.,M.. .-t

function ARGB(const A,R,G,B:byte) :TAlphaColor;                                                                             '

Ьegin

Result:=B + G SНL 8 + R SНL 16+ А SНL 24;

end;

 

procedure TFoпnl.tbRedChange(Sender:  TObject);

var R,G,B,A:byte;

AC:TAlphaColor;

Ьegin


R:=Round(tbRed.Value); G:=Round(tbGreen.Value); B:=Round(tbBlue.Value); A:=Round(tbAlpha.Value);


//значение R

//значение  G

//значение в

//значение А


 


AC:=ARGB(A,R,G,B);              //формирование значения цвета ARGB Circlel.Fill.Color:=AC;                                                        //nерекраска фигуры Textl.Text:='h'+IntToHex(AC,8);

end;

 

 

Если вам не понравилась придуманная нами функция ARGB ( ) из листинга 15.1, то для формирования значения цвета в формате TAlphaColor можете восп,ользоваться библиотечной функцией

function MakeColor(R, G, В: Byte; А: Byte = $FF): TAlphaColor;

Дпя завершения задуманного осталось сделать событие onchange () общим для всех компонентов-ползунков и нажать клавишу <F9>. Результат нашей работы пред-



Рис. 15.1. Интерфейс приложения ARGB

 

ставлен на рис. 15.1. К сожалению, черно-белая печать книги не способна передать полученную комбинацию цвета hFF84CA47 ...

 

Стандартные цветовые комбинации

Создавая собственные цветовые оттенки, мы сможем раскрасить приложение во все цвета радуги, но далеко не всегда стоит так делать. В бизнес-проектах более полез­ ным окажется не вычурный, а строгий деловой стиль с цветовой гаммой, подобран­ ной специалистами. Существенное подспорье в этом сможет оказать объявленная в модуле System. UITypes запись TAlphaColorRec.

В составе записи TAlphaColorRec определено несколько десятков констант (лис­ тинг 15.2), обращаясь к которым программист получит значение заранее предопре­ деленных цветов по их символьным именам.


TAlphaColorRec = record const

Alpha = TAlphaColor($FF000000);

AliceЬlue = Alpha or TAlphaColor($FOF8FF); Antiquewhite = Alpha or TAlphaColor($FAEBD7); Aqua = Alpha or TAlphaColor($00FFFF); Aquamarine = Alpha or TAlphaColor($7FFFD4);


 

Компоненты цветовой модели ARGB

Библиотека визуальных компонентов VCL и не могла мечтать о таком разнообра­ зии компонентов, нацеленных на предоставление пользователю возможности выбора цветового значения, как в библиотеке FireMonkey. Чтобы проверить это утверждение, предлагаем открыть страницу компонентов Colors, здесь вы обнару­ жите более десятка элементов, способных удовлетворить самые притязательные запросы разработчиков. Среди них 4 компонента специализируются на представле­ нии цвета в формате ARGB (рис. 15.2):

1.    Цветовая панель тcolorPanel наиболее удобна с точки зрения быстрого форми­ рования цвета любого оттенка и прозрачности. Панель представляет собой ком­ бинацию двух компонентов-ползунков (управляющих оттенком цвета и альфа­ каналом).и собственно цветовой панели.

2.    Комбинированная цветовая панель тсоmЬоСоlоrвох. В свернутом виде элемент управления отображает выбранный цвет, в развернутом выводит на экран па­ нель, аналогичную тcolorPanel.

3.    Комбинированный цветовой список тсоlоrСоmЬовох.

4.    Цветовой список TColorListBox.

Cuadro de texto: rn	...
Qд1iceblue
■О Antlquewtlile
Aquг	...
>·:,
J
1
оQ.ь�,re
.Оl!щВllscque	
...

Рис. 15.2. Компоненты цветовой модели ARGB

 

Перечисленные компоненты обладают схожим перечнем свойств и методов, основ­ ные из которых представлены в табл. 15.1.

 

ЗАМЕЧАНИЕ

Компоненты-списки TColorListBox и TColorComЬoBox можно считать нашими давни­ ми знакомыми, в главе 10 мы подробно изучили все их родовые черты.


 

Таблица 15.1. Основные свойства и события TColorPanel, ТСотЬоСоlоrВох,

TColorListBox и ТСоlоrСотЬоВох

 

Свойства/события

Описание

property Color: TAlphaColor;

Выбранный цвет

property OnChange: TNotifyEvent;

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

property UseAlpha: Boolean;

Использование канала прозрачности

(только для TColorPanel и TComЬoColorBox)

 

 

Компоненты цветовой модели HSL

Модель воспроизведения цвета на основе красного, зеленого и синего цветов весь­ ма распространена, но далеко не единственна. Кроме RGB существует еще не­ сколько часто используемых цветовых моделей. Одна из них - модель HSL (Hue, Saturation, Lightness (lntensity)). Если вы хорошо знакомы с английским, то уже до­ гадались, что в цветовой модели HSL главными действующими лицами выступают не значения цветов (как в RGB), а тон, насыщенность и интенсивность.

 

Компоненты TColorPicker и TColorQuad

В составе FireMonkey имеется ряд элементов управления, специализирующихся на работе с цветом в формате HSL. В первую очередь это TColorPicker и тcolorQuad. Представленные компоненты-союзники обычно эксплуатируются совместно (рис. 15.3).

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

property Hue: Single;  //диапазон от О до 1

способное изменять цветовой оттенок в диапазоне от О до 1.


Н5<.

 

1-\;.;t 0.2 1i-73-:!7437 f

S№r1tiМJ,to9S2.s&.."087707S

O..S1141.?S5%31%


 

Рис. 15.3. Выбор цвета в формате HSL с помощью TColorPicker и TColorQuad


 

При необходимости текущее значение цвета можно считать (или записать) в более привычном для программиста Delphi формате ARGB. Для этого предназначено свойство

property Color: TAlphaColor;

Для установления связи между TColorPicker и компQНентом TColorQuad, отвечаю­

щим за управление насыщенностью и интенсивностью, воспользуемся свойством

property ColorQuad: TColorQuad;

В свою очередь компонент тcolorQuad, получив от своего коллеги значение цвето­ вого тона, помещает его в свое свойство

property Hue: Single;

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

property Sat: Single; //диапазон значений от О до 1

и интенсивность

property Lum: Single; //диапазон значений от О до 1

исходного цвета.

В момент изменения параметров цвета у компонента тcolorQuad генерируется со­ бытие

property OnChange: TNotifyEvent;

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

property ColorBox: TColorBox;

На тсоlоrвох возложена самая простая задача -  пересчитать цвет из формата HSL в формат ARGB и возвратить полученное значение в свойстве

property Color: TAlphaColor;

Впрочем, для конвертации цвета из формата HSL в формат ARGB необязательно задействовать элемент управления тcolorBox, вместо этого можно воспользоваться библиотечными функциями, обеспечивающими взаимное преобразование цвета из модели HSL в RGB, и наоборот

function HSLtoRGB(H, S, L: Single): TAlphaColor;

prooedure RGBtoHSL(RGB: TAlphaColor; out Н, S, L: Single);

 

Цветовые полосы THueTrackBar, TAlphaTrackBar и TBWТrackBar

Объединив три элемента компонента в категорию "цветовые полосы", мы несколь­ ко преувеличили. По большому счету из анонсированной тройки только один ком-


понент имеет прямое отношение к цвету- это полоса цветового тона тнueTrackBar. Оставшиеся два компонента отвечают за прозрачность (тAlphaTrackВar) и градации серого (твwтrackBar). Однако перечисленные компоненты функционируют по еди­ ному принципу (они являются наследниками цепочки классов "ттrackBar - твi tmapTrackBar"), посему логически правильно рассмотреть их совместно.

Строго говоря, отличие между THueTrackBar, TAlphaTrackBar и TBWTrackBar чисто косметическое. Фактически это один и тот же элемент управления, отличающийся от своих коллег лишь внешним видом.(р:ие. 15.4).


Рис. 15.4. Приложение с компонентами TAlphaTrackВar, THueTrackBar и TBWTrackВar

 

Все рассматриваемые компоненты обладают идентичным перечнем свойств и ме­ тодов. Наиболее важные из них

property Min: Single;           //по умолчанию О property Мах: Single;                                                    //по умолчанию 1

определяют границы интервала и информируют

property Value: Single;

о . текущем положении ползунка. В момент перемещения ползунка генерируется событие

property OnChange: TNotifyEvent;

Все остальное в руках программиста. Например, листинг 15.3 демонстрирует поря­ док назначения градации серого цвета с помощью компонента твwтrасkваr.

.............................. ...............,.,.,.                        ...,.·•········..., .....,..,_ ·······•······•·•·•······•·•···.., . ,:. ,................. ................,,.,,................. .......,.,_,.,.,.,.,,........-,.,.........................

i в.....              ---       -

proaedure TForml. BWTrackВarlChange (Sender: TObject); ' var GRAY:byte;

Ьegin

GRAY:=Round($FF*BWTrackBarl.Value);

Rectanglel.Fill.Color:=GRAY + GRAY SНL 8 + GRAY SНL 16+ $FF SНL 24;

end;


 

Градиентная заливка TGradientEdit

В составе компонентов FireMonkey предусмотрен элемент управления TGradientEdi t позволяющий устанавливать правила градиентной заливки областей. Ключевое свойство компонента

property Gradient: TGradient;

представляет ссылку на объект TGradient, отвечающий за описание правил по­ строения градиента (табл. 15.2).

 

Таблица 15.2. Класс TGradient

 

Свойства и методы

Описание

property Color: TAlphaColor;

Начальный цвет градиентной заливки

property Colorl:  TAlphaColor;

Конечный цвет градиентной заливки

function InterpolateColor(Offset: Single) : TAlphaColor;

Функция интерполирует цвет градиента. Пара- метр Offset должен находиться в диапазоне значений от О до 1

property Points: TGradientPoints;

Коллекция точек, которые используются для доступа к цвету и отступу области градиентной заливки

property Style: TGradientStyle;

Стиль заливки: линейный gsLinear или ради- альный gsRadial

property StartPosition: TPosition;

Отправная точка для линейной градиентной заливки назначается в виде прямоугольной области с размерами, нормированными к еди- нице (координаты левого верхнего угла -

(О, О), координаты правого нижнего угла - (1, 1))

property StopPosition: TPosition;

Конечная точка линейной градиентной заливки (от (О, О) до (1, 1))

property RadialTransform: TTransform;

Правила преобразований, применяемых для радиальной заливки. В данном случае среди свойств класса TTransform самое важное ме- сто занимает RotationCenter. Именно оно определяет центр заливки

 

 

ВНИМАНИЕ!

Для настройки направления линейной градиентной заливки и шага интерполяции цве­ та (плавности заливки) следует воспользоваться свойствами StartPosition и StopPosition (см. табл. 15.2). По умолчанию эти свойства установлены в состояние (О, О) и (О, 1) соответственно и указывают на то, что градиент направлен сверху вниз.

Компонент TGradientEdi t представляет собой полосу с двумя точками-ползунками

(рис. 15.5), при выборе любого из ползунков генерируется событие

property OnSelectPoint: TNotifyEvent;


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

property CurrentPoint: Integer;

Если выбран левый ползунок, то свойство возвратит нулевое значение. О выборе правого ползунка будет свидетельствовать единица.

В момент изменения любых параметров градиентной заливки компонент Fенериру­ ет событие

property OnChange: TNotifyEvent;

Для того чтобы оценигь возможности компонента TGradientEdi t, предлагаем по­ вторигь небольшой пример. Для этою · разместиге на форме компонент GradientEditl:TGradientEdit и два комбинированных списка выбора цвета TComЬoColorBox (рис. 15.5).-

 

 

TComЬoColorBox

 

 

 

 

TGradientEdit  ..--

 

 

 

 

 

 

 

Рис. 15.5. Демонстрационное приложение управления градиентной заливкой с помощью TGradientEdit

 

Комбинированные списки тсоmЬоСоlоrвох выбора цвета предназначены для управ­ ления цветом заливки. Для назначения цвета мы воспользуемся событиями onchange () этих компонентов (листинг 15.4).


procedure TFoпnl.ComЬoColorBoxlChange(Sender: TObject);

Ьegin

GradientEditl.Gradient.Color:=ComЬoColorBoxl.Color;

end;

 

procedure TFoпnl.ComЬoColorBox2Change(Sender: TObject);

Ьegin

GradientEditl.Gradient.Colorl:=ComЬoColorBox2.Color;

end;


 

· Процесс заливки лучше всего осуществить в рамках события OnPaint (), генерируе­ мого в момент перерисовки единственной формы проекта (листинг 15.5).


prooedure TForml.FormPaint(Sender: TObject; Canvas: TCanvas;

const ARect: TRectF);

Ьegin

with Canvas do

if BeginScene then

Ьegin

Fill.Kind:=TBrushКind.bkGradient; Fill.Gradient:=GradientEditl.Gradient; FillRect(ARect,0,0, [],1);

EndScene;

end; end;

 

Для того чтобы форма немедленно откликалась на все манипуляции пользователя, воспользуемся событием OnChange () компонента GradientEditl (листинг 15.6).


prooedure TForml.GradientEditlChange(Sender: TObject);

Ьegin

Forml.OnPaint(Sender,Forml.Canvas,Forml.ClientRect); Expanderl.Repaint;

end;


 

 


ГЛАВА 16

 

 

Двухмерная графика

 

 

 

 

 

В классических приложениях VCL для Windows весь механизм графического вы­ вода построен на фундаменте GDI (Graphics Device Interface, интерфейс графиче­ ских устройств). Для того чтобы нарисовать линию или вывести строку текста, программисту достаточно получить доступ к контексту нужного графического уст­ ройства и воспользоваться услугами стандартных функций двухмерной графики Windows. Несмотря на относительную "древность", GDI превосходно справляется с большинством задач, которые решают деловые приложения. А если программист ставит перед собой более амбициозные цели, то в качестве инструментария Microsoft предлагает воспользоваться GDI+, Direct2D и DirectЗD.

Скажем несколько слов о графическом механизме OS Х. Не стоит доказывать, что эта ОС ничего не знает ни о GDI, ни о DirectX. В OS Х работа с двухмерной графи­ кой осуществляется силами QuickDraw и Quartz 2D, а трехмерные сцены создаются на основе графической библиотеки OpenGL.

В проектах VCL объектно-ориентированным воплощением контекста графического устройства GDI выступал, выступает и наверняка еще очень долго будет выступать хорошо нам знакомый класс тcanvas. В двухмерных проектах FireMonkey на по­ прище графики трудится одноименный тcanvas (модуль FМX.Types), но для того чтобы одновременно стать полезным OS Х, Windows и Android, он организован по­ другому.

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

type  TCanvas  =  class aЬstract  (TinterfacedPersistent)

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

Изменились и приемы программирования с холстом. Так листинг 16.1 демонстри­ рует порядок вывода обычной линии на поверхности формы.


 

pro08dur8 TForml,FormPaint(Sender: TObject; Canvas: TCanvas;

oont ARect: TRectF); var APtl, APt2: TPointF; Ь8qin

APtl:=Forml,ClientRect.TopLeft; APt2:=Forml.ClientRect.BottomRight;

if Canvas.BeginScene thlln //создание сцены

try

Canvas.DrawLine(APtl, APt2, 1);  //рисование линии

finally

Canvas.EndScene; //завершение сцены и вывод изображения на экран

end;

end;

 

 

ВНИМАНИЕ/

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

 

 

Управление холстом

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

function BeginScene  (AClipRects: PClipRects = nil;

AContextHandle:  THandle =О): Boolean;

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

Одновременно на холсте может сосуществовать несколько открытых графических сцен, об их количестве проинформирует свойство

property BeginSceneCount: integer; Команда на закрытие сцены procedur8 EndScene;

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


 

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

prooedure Clear(const Color: TAlphaColor);

Единственный параметр метода Color содержит новый цвет заливки холста. Для очистки только заданной прямоугольной области воспользуйтесь методом procedure ClearRect(const ARect: TRectF; const AColor: TAlphaColor = О);

границы области, подлежащей перерисовке, ARect.

Ширину и высоту холста возвращают свойства

property Width: Integer;          //только для чтения

property Height: Integer; //только для чтения

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

property Buffered: Boolean; //только для чтения

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

property BufferBits: Pointer; property BufferHandle: THandle;

Для управления особенностями прорисовки графических примитивов пр работе с холстом VCL мы пользовались услугами кисти, пера и шрифта. У холста FMX помощники те же самые, однако работать с ними существенно интереснее (табл. 16.1).

 

Таблица 16.1. Атрибуты холста FМХ. Types. TCanvas

 

Свойство

Описание

property Fill: TBrush;

Кисть, используемая для заливки замкнутых областей

property Stroke: TStrokeBrush;

Кисть, используемая для рисования линий

property Font: TFont;

Шрифт

 

 

 

Кисть TBrush

Первой и едва ли не основной помощницей кроссплатформ.енной версии холста можно считать кисть твrush. Вас не должно вводить в заблуждение знакомое по VCL название класса кисти, кисть rМX.Types.TBrush на голову превосходит своего коллегу 1:JЗ VCL. Это.подтверждают свойства новой кисти.


 

Класс твrush позволяет создать несколько инструментов для закраски поверхности:

property Kind: TBrushКind;

type TBrushKind = (bkNone,                 //пустая  кисть


bkSolid, bkGradient, bkBitmap, bkResource) ;


//сплошная кисть

//кисть с градиентной заливкой

//кисть на основе растрового образа

//кисть из ресурса


В зависимости от вида кисти задействуется то или другое свойство класса твrush.

При определении цвета сплошной кисти обращаемся к свойству

property Color: TAlphaColor;

Кисть bkGradient умеет хранить описание градиентной заливки

property Gradient: TGradient;

Кисть bkBitmap способна интегрировать в себя растровый образ

property Bitmap: TBrushВitmap;

или произвольный ресурс, как кисть bkResource

property Resource: TBrushResource;

,....

 
Порядок заливки эллиптической области сплошной кистью демонстрирует лис­ тинг 16.2.

 

...................... ! .,,_

 

 

 

 

 

 

 

prooedure TFonnl.FonnPaint(Sender:  TObject;  Canvas:  TCanvas;

const ARect: TRectF);

Ьegin

if Canvas.BeginScene() then //создание графической сцены

try

Canvas.Fill.Kind:=TBrushКind.bkSolid; Canvas.Fill.Color:=TAlphaColors.AliceBlue; Canvas.FillEllipse(ARect,1);

finally

Canvas.EndScene(); //завершение сцены и вывод изображения на экран

end; end;

 

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

property OnChanged: TNotifyEvent;

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

property OnGradientChanged: TNotifyEvent;


Внешний вид линий

Читатель, изучивший табл. 16.1, наверняка заметил, что в графике FireMonkey роль пера также отводится кисти. Однако на этот раз экземпляр кисти-пера построен не на твrush, а на классе-наследнике

TStrokeBrush = class(TBrush)

и передается в свойство Stroke (см. листинг 16.3). Если вы уже попробовали пора­ ботать с кистью FМХ. Types. StrokeBrush, то поняли, что возможности FireMonkey по выводу линий поистине безграничны. Чего только tтоит градиентная линия... Кро­ ме того, вне_шний вид линий зависит от состояния квартета свойств.

Толщина линии определяется свойством

property Thickness: Single;

Особенности начертания определяет свойство

property Dash: TStrokeDash;

type TStrokeDash = (sdSolid, sdDash, sdDot,

sdDashDot, sdDashDotDot, sdCustom);

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

procedure SetCustomDash(const Dash: array of  Single; Offset: Single);

Наконечники линий могут быть плоскими и скругленными

property Сар: TStrokeCap;

type TStrokeCap = (scFlat, scRound);

Наконец, места соединения нескольких линий также могут настраиваться свойст­

вом

property Join: TStrokeJoin;

type TStrokeJoin = (sjMiter, sjRound, sjBevel);

Соединения могут быть ограничены, скруглены или отсечены. Порядок работы с пером отражен в листинге 16.3.


procedure TForml.FormPaint(Sender: TObject; Canvas: TCanvas;

const ARect: TRectF);

VAr R:TRectF;

Ьegin

R:=RectF(l0,10,200,200);

if Canvas.BeginScene then

try

��nv��.Stroke,Color;=TAlphaColorRec.Crirn.son; Canvas.Stroke.Thickness:=0.5;


Двухмернвя грвфика                                                                                                                                   227

 

Canvas.DrawRect(R,10,10,

[TCorner.crTopLeft, TCorner.crBottomRight], 1);

 

Canvas.Stroke.Color:=TAlphaColorRec.Blueviolet; Canvas.Stroke.Dash:=TStrokeDash.sdDashDot; Canvas.Stroke.Thickness:=l;

 

Canvas.DrawEllipse(aRect,1);

f'inally

Canvas.EndScene;

end;

end;

 

 

Шрифт TFont

С точки зрения конечного программиста, нововведения FMX не сильно затронули устоявшийся со времен VCL подход к представлению шрифта. В проектах FireMonkey класс шрифта сохранил традиционное имя TFont, но на этот раз объяв­ ление класс перенесено в модуль FМХ. Types.

Три базовых свойства шрифта обеспечивают управление гарнитурой, размером и стилем:

property Family: TFontName;    //гарнитура шрифта property Size: Single;                                                                 //размер шрифта property Style: TFontStyles; //стиль шрифта

type TFontStyle = (fsBold, fsitalic, fsUnderline, fsStrikeOut);

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


procedure TForrnl.FormPaint(Sender: TObject; Canvas: TCanvas;

const ARect: TRectF);

var  R:TRectF;

Ьegin

R:=RectF(l0,10,200,40);

if' Canvas.BeginScene then

try

Canvas.Fill.Color:=TAlphaColors.Red; Canvas.Font.Style:=[TFontStyle.fsitalic]; Canvas.Font.Family:='Arial'; Canvas.Font.Size:=20;

Canvas.FillText(R, 'Привет, Мир! ',True,1, [],TTextAlign.taCenter);


finally

Canvas.EndScene;

end; end;

 

Заливка замкнутых-областей

Все методы графического вывода содержат параметр AOpacity: Single, опреде­ ляющий степень прозрачности рисуемого примитива. Параметр должен принимать значения в пределах от О до 1, где О соответствует абсолютно прозрачному изобра­ жению, а 1 - непрозрачному.

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

 

Таблица 16.2. Методы заливки областей

 

Метод

Описание

procedure FillRect(const ARect: TRectF; const XRadius, YRadius: Single; const ACorners: TCorners;     const AOpacity: Single; const ACornerType:

TCornerType = TCornerType.ctRound);

Заливка прямоугольной области ARect. Допус- кается вывод скругленных углов, в этом слу- чае следует определить радиус - XRadius и YRadius. Параметр ACorners уточняет, какой именно угол (или углы) следует нарисовать скругленным. Особенности вывода назнача- ются параметром ACornerType

procedure FillEllipse(const ARect: TRectF; const AOpacity: Single);

Заливка эллиптической области в границах прямоугольника ARect

procedure FillArc(const Center, Radius: TPointF; StartAngle, SweepAngle: Single; const AOpacity: Single);

Заливка сектора окружности с центром Center и радиусом Radius. Раскрытие сектора опре- деляется двумя лучами, проведенными из центра в точке StartAngle и SweepAngle

procedure FillPath(const APath: TPathData; const AOpacity: Single);

Заливка траектории, описанной в структуре

APath

procedure FillPolygon(const Points: TPolygon; const AOpacity: Single);

Заливка произвольного многоугольника

с вершинами, заданными в массиве Points

 

Листинг 16.5 предлагает простой пример заливки прямоугольной области гради­ ентной кистью. Обратите внимание, что точки startPosition и StopPosition опре­ делят координаты вектора заливки: Значения координат вектора нормируются к 1. Например, если бы мы захотели направить градиент из левого верхнего угла формы в правый нижний, то следовало передать координаты (О, О) и (1, 1).


proced.ure TForml.FoпnPaint(Sender: TObject; Canvas: TCanvas;

const ARect; TRectF);

Ьegin


 

if Canvas.ВeginScene then

try

Canvas.Fill.Kind:=TBrushKind.bkGradient;

With Canvas.Fill.Gradient do

hegin

StartPosition.X:=0.5; StartPosition.Y:=O; StopPosition.X:=0.5;                                                 StopPosition.Y:=l; Style:=TGradientStyle.gsLinear; Color:=TAlphaColors.White; Colorl:=TAlphaColors.Red;

end;

Canvas.FillRect(Foпnl.ClientRect,0,0, [TCorner.crTopLeft, TCorner.crTopRight, TCorner.crBottomLeft, TCorner.crBottornRight],1);

finally

Canvas.EndScene;

end;

end;

 

 

Вывод простейших фигур

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

 

Таблица 16.З. Методы черчения простейших фигур

 

Метод

Описание

prooedure DrawLine(const APtl, APt2: TPointF;

const AOpacity: Single);

Чертит отрезок из точки APtl в точку

APt2

prooedure DrawRect(const ARect: TRectF; const XRadius, YRadius: Single; const ACorners: TCorners; const AOpacity: Single; const ACornerType: TCornerType =

1cornerType.ctRound);

Выводит прямоуг льник. Параметры метода идентичны параметрам FillEllipse (см. табл. 16.2)

procedure DrawArc(const Center, Radius: TPointF; StartAngle, SweepAngle: Single; const AOpacity: Single);

Выводит сектор. Параметры метода идентичны параметрам FillArc

(см. табл. 16.2)                              '

procedure DrawRectSides(const ARect: TRectF; const XRadius, YRadius: Single; const ACorners: TCorners; const AOpacity: Single; const ASides: TSides; const ACornerТype: TCornerType = TCornerType.ctRound);

Чертит стороны прямоугольника

ASides

prooedure DrawEllipse(const ARect: TRectF;

const AOpacity: Single);

Чертит эллипс в границах прямоуголь- ной области

procedure DrawPath(const APath: TPathData;

const AOpacity: Single);

Чертит траекторию APath

с прозрачностью AOpacity


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

 

Метод

Описание

procedure DrawPolygon(const Points: TPolygon; const AOpacity: Single);

Выводит многоугольник с вершинами

Points

 

 

Траектория TPathData

Из предложенных в табл. 16.3 методов вывода графических примитивов наиболь­ ший интерес представляет метод DrawPath (), позволяющий осуществлять графиче­ ский вывод весьма неординарных объектов-траекторий, реализуемых классом FМX.Types.TPathData.

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

Если траектория уже содержит информацию, то свойство

function IsEmpty: Boolean;

окажется в состоянии false.

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

property Points[Aindex: Integer]: TPathPoint; //только мя чтения

Общее число точек в траектории известно свойству

property Count: Integer; //только мя чтения

Для очистки траектории проще всего воспользоваться свойством

procedure Clear;

Для определения начальной точки траектории следует вызвать метод

procedure MoveTo(const Р:  TPointF);

Кроме того, метод моvето ( потребуется в том случае, когда необходимо оторвать виртуальное перо от холста и переместить его в другую точку.

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

procedure LineTo(const Р: TPointF);

 

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


var Path:TPathData; //глобальная переменная

 

prooedure TForml.FormCreate(Sender:  TObject);

Ьegin

Path:=TPathData.Create; //создаем траекторию

end;

 

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


prooedure TForml.FoпnМouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; Х, У: Single);

Ьegin

Path.Clear;                                      //очистка предыдущей траектории Path.MoveTo(PointF(X,Y)); //начальная точка траектории

end;

 

prooedure TForml.FoпnМouseMove(Sender: TObject; Shift: TShiftState;

Х, У:  Single);


Ьegin

if ssLeft in Shift then Ьegin


//строим траекторию


Path.LineTo(PointF(X,Y));

//! ! ! событие перерисовки FormPaint() пока отсутствует FormPaint(Sender,Forml.Canvas,Forml.ClientRect);

//! ! ! см. листинг 16.8

end;

end;

 

prooedure TForml.FoпnМouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; Х, У:  Single);

Ьegin

Path.ClosePath; //пользователь отпустил кнопку МЬШJИ -  закрыли траекторию

end;

 

Для визуализации жеста пользователя нам осталось воспользоваться событием перерисовки главной формы проекта (листинг 16.8).


prooedure TForml.FormPaint(Sender: TObject; Canvas:  TCanvas;

const ARect: TRectF);


Ьegin

if     (Path.Count>0) and (Canvas.BeginScene) then

try

Canvas.Clear(TAlphaColorRec.White); Canvas.Stroke.Color:=TAlphaColorRec.Black; Canvas.StrokeThickness:=0.5; Canvas.DrawPath(Path,1);

finally

Canvas.EndScene;

end;

end;

 

 

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

 

Вывод текста

Одна из сложнейших графических задач вывода текстовых данных в FireMonkey превращается в пару пустяков. Гарнитура, стиль и размер шрифта назначаются в свойстве Font холста, цвет шрифта определяется цветом текущей кисти. Методы холста тcanvas, отвечающие за работу с текстом, представлены в табл. 16.4.

 

Таблица 16.4. Вывод текстовых данных

 

Метод

Описание

function LoadFontFromStream(AStream: TStream): Boolean;

Загрузка шрифта из потока AStream

function TextWidth(const AText: string): Single;

Расчет ширины текстовой строки

function TextHeight(const AText: string): Single;

Расчет высоты текста

procedure FillText(const ARect: TRectF; const AText: string; const WordWrap: B"Oolean; const AOpacity: Single; const Flags: TFillTextFlags; const ATextAlign: TTextAlign; oonst AVTextAlign: TTextAlign = TTextAlign.taCenter);

Вывод текста AText в области ARect. Перенос текста на новую строку опре- деляется параметром WordWrap. На- правление вывода (по умолчанию еле- ва направо) устанавливается в Flags. Горизонтальное выравнивание опре- деляется параметром ATextAlign, вертикальное выравнивание - пара- метром AVТextAlign

function TextToPath(Path; TPathData; const ARect: TRectF; const AText: string; const WordWrap: Boolean; const ATextAlign: TTextAlign; const AVТextAlign: TTextAlign =

TTextAlign.taCenter):  Boolean;

Вывод текста по траектории Path


Отображение рисунков

Для прорисовки на поверхности холста графического образа следует применять метод

prooedure DrawBitmap(const AВitmap: TBitmap;

const SrcRect, DstRect: TRectF; const AOpacity: Single;

const HighSpeed: Boolean = False);

 

Процедура выводит изображение AВitmap полностью или его часть. Подлежащая выводу область изображения определяется параметром SrcRect. Место вывода и размеры результирующего изображения назначаются в параметре DstRect. Пара­ метр HighSpeed позволяет программисту отдать предпочтение скорости (true) или качеству (false) вывода.

Листинг 16.9 демонстрирует порядок работы с методом DrawBitmap () . В представ­ ленном примере мы загружаем графический образ из файла и выводим его на по­ верхности формы. Если нам потребуется отобразить только фрагмент исходного

рисунка, то следует определить границы интересующего нас фрагмента в парамет­ ре srcRect. Управляя размерами области вывода DstRect, мы сможем увеличить или уменьшить рисунок.


var AВitmap:TBitrnap; aWidth,aHeight:integer; SrcRect, DstReGt: TRectF;

Ьegin

if  OpenDialogl.Execute then

with Forml.Canvas do Ьegin

AВitmap:=TBitmap.CreateFromFile(OpenDialogl.FileName); aWidth:=AВitrnap.Width;

aHeight:=AВitmap.Height; SrcRect:=RectF(0,0,aWidth,aHeight); DstRect:=SrcRect;

if BeginScene then Ьegin

DrawBitmap(AВitmap,SrcRect,DstRect,1,true); EndScene;

end;

end;

end;

 

Для организации быстрого просмотра изображения АВitmap стоит воспользоваться методом


prooedure DrawТhumЬnail(const AВitmap: TBitmap;

const Width, Height: Single);

создающим графическую миниатюру заданного размера.

 

Отсечение

Как в Windows, так и в OS Х в качестве основного инструмента, позволяющего на­ строить границы области, в которых будет осуществляться вывод, выступает реги­ он отсечения. Библиотека FireMonkey порадовала возможностью простого управ­ ления регионами отсечения. В ХЕ4/ХЕ5 в распоряжении программиста имеются два метода.

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

procedure IntersectClipRect(const ARect: TRectF);

Метод

prooedure ExcludeClipRect(const ARect: TRectF);

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

 

Сохранение и восстановление

.состояния холста

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

function SaveState:  TCanvasSaveState;

Сведения о состоянии холста представляются в формате объекта тcanvasSaveState.

Для восстановления состояния холста вызываем метод

prooedure RestoreState(State:  TCanvasSaveState);


Методы сохранения/восстановления должны работать вместе так, как продемонст­ рировано в листинге 16.10.

var Canvas: TCanvas;

State:  TCanvasSaveState;

Ьeiin

State := Canvas.SaveState;

//ROQДQЙC�BИQ на свойства холста

//другие операции

Canvas.RestoreState(State);

encl;


Работа с растровой графикой, класс TBitmap

Если вы имеете опыт работы с растровой графикой в VCL, то, вне всякого сомне­ ния, вам приходилось сталкиваться с классом твi tmap, выступающим интеллекту­ альным контейнером для битовых образов для широко распространенных в Windows файлов с расширением имени bmp.

Разработчики библиотеки FMX решили не ломать сложившихся традиций и для обслуживания растровой графики в FireMonkey создали одноименный класс TBitmap (модуль FМX.Graphics). Несмотря на то, что классы VCL и FMX являются тёзками, возможности обновленной версии TBitmap в корне отличаются от его кол­ леги из VCL, превосходя его практически по всем параметрам. Так, обновленная версия FМХ. Т:уреs. твi tmap способна:

   обслуживать графические файлы не только в простейшем формате ВМР, но и

в форматах JPG, JPEG, PNG, GIF, ТIF, ICO приложениях Windows) и JP2, PSD, TGA, ICNS (в приложениях OS Х и IOS);

    изменять геометрию изображения (размеры и вращение);

    осуществлять графический вывод на своей рабочей поверхности благодаря ин- капсулированному холсту тcanvas.

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

constructor Create(const AWidth, AНeight: Integer);

создать объект, загрузив его данные из потока или файла

constructor CreateFromStream(const AStream: TStream); constructor CreateFromFile(const AFileName: string);

и, наконец, сформировать новый растровый объект на основе другого рисунка и его монохромной маски

constructor CreateFromВitmapAndМask(const Bitmap, Mask: TBitmap);

 

Загрузка и сохранение изображения

Традиционно растровый объект обладает возможностями загрузки данных из файла и потока

procedure LoadFromFile(const ДFileName: string;

const Rotate: Single           О); procedure LoadFromStream(Stream: TStream);

и сохранения картинки в файл и поток.

procedure SaveToFile(const AFileName: string;

const SaveParams: PBitmapCodecSaveParams = nil);

procedure SaveToStream(Stream: TStream);


 

Если загрузка изображения осуществилась успешно, то метод

function IsEmpty: Boolean;

возвратит значение false.

 

ЗАМЕЧАНИЕ

В момент сохранения изображения допускается его преобразование в другой графи­ ческий формат. Помощь в этом окажет определенный в модуле FМХ. Types класс TBi tmapCodecManager.

 

Кодирование и декодирование графических форматов

Класс твi tmap далеко не всемогущ, и при обработке поддерживаемых им графиче­ ских форматов зачастую нуждается в услугах сторонних "лиц", в частности класса твi tmapCodecManager, обеспечивающего кодирование и декодирование хранящихся в графическом контейнере данных.

Перечень расширений имен файлов, поддерживаемых классом TBitmapCodecManager,

возвратит метод

class function GetFileTypes: string;

Если вы работаете под управлением Windows, то получите текстовую строку при­ мерно следующего содержания:

*.bmp;*.jpg;*.jpeg;*.png;*.gif;*.tif;*.ico;*.wmp

Если приложение выполняется под управлением OS Х, то менеджер возвратит

*.bmp;*.icns;*.jpg;*.jp2;*.jpeg;*.png;*.gif;*.tif;*.tga

Заметьте, что в возвращаемой строке расширения имен файлов разделяются точкой с запятой.

Для упрощения работы программиста, использующего в своем проекте диалоги открытия и сохранения файлов, в твi tmapCodecManager предусмотрен метод

class function GetFilterString: string;

передающий в программу перечень поддерживаемых форматов в виде, приемлемом для свойства Filter компонентов (т0penDialog и тsaveDialog).

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

class function IsCodedExists(const AFileNarne: string): Boolean;

 

Получение миниатюры изображения

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


 

С такой задачей справится процедура

proaedure LoadThurnЬnailFromFile(const AFileName: string;

const AFitWidth, AFitHeight: Single; const UseEmЬedded: Boolean = True);

Кроме обязательного имени файла с растровой картинкой, метод потребует от вас явным образом указать геометрические размеры (параметры AFitwictth и AFitHeight) миниатюры ..

Еще одну операцию, связанную с созданием миниатюры, осуществляет функция

function CreateThurnЬnail(const Width, Height: Integer): TBitmap;

Она также формирует миниатюру заданного размера, но на этот раз не из файла, а из уже загруженного в контейнер рисунка.

 

Свойства изображения

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

 

Таблица 16.5. Основные свойства FМХ. Types. TBi tmap

 

Свойство

Описание

property Handle: TBitrnapHandle;

//только чтение

Дескриптор изображения

property Width: Integer;

Ширина картинки

property Height: Integer;

Высота картинки

property PixelForrnat: TPixelForrnat;

Глубина цвета

property BitrnapScale: Single;

//только чтение

Масштаб

property CanvasClass: TCanvasClass;

//только чтение

Класс холста

 

 

Простые манипуляции графическим образом

В классе твi tmap максимально упрощены все наиболее востребованные операции с графическим образом. Дпя изменения геометрического размера достаточно обра­ титься к методу

proaedure Resize(const AWidth, AНeight: Integer);

Дпя поворота рисунка на заданный градус вызываем процедуру

proaedure Rotate(const Angle: Single);

Дпя вращения вокруг горизонтальной и вертикальной оси заде_йствуются методы

proaedure FlipHorizontal; proaedure FlipVertical;


 

Инвертирование альфа-канала осуществит метод

prooedure InvertAlpha;

Замена прозрачного цвета выполняется процедурой

prooedure ReplaceOpaqueColor(const Color: TAlphaColor);

 

 

Редактирование битового образа

Одно из неоспоримых преимуществ класса FМX.Types.TBitmap над его тёзкой из VCL з'аключается в том, что он позволяет программисту рисовать на поверхности изображения, используя удобный интерфейс холста

property Canvas: TCanvas;

Внесенные изменения могут быть сохранены в файл или в поток с помощью уже анонсированных ранее методов saveToFile () и saveтostream () .

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

prooedure Clear(oonst AColor: TAlphaColor);

prooedure ClearRect(const ARect: TRectF; co st AColor: TAlphaColor = О);

К процессу изменения картинки имеет отношение еще одна пара перегружаемых методов:

prooedure CopyFromВitmap(const Source: TBitmap); overload; prooedure CopyFromВ tmap(const Source: TBitmap;

SrcRect: TRect; DstX, DstY: Integer); overload;

Перечисленные процедуры копируют рисунок из образа source. Единственное ус­ ловие корректной работы методов заключается в равенстве геометрических разме­ ров образа-источника и получателя.

Для любителей экстремальных способов редактирования рисунка упомянем о функции

function Map(const Access: ТМарАссеss; var Data: TBitmapData): Boolean;

предоставляющей низкоуровневый доступ к битовой карте изображения через параметр Data. Особенности доступа (maRead - только чтение; maWrite - только запись, maReadWrite-чтeниe и запись) определяются параметром Access.

Завершив работу с битовой картой, следует закрыть сессию, воспользовавшись процедурой

prooedure Unmap(var Data: TBitmapData);


 

Управление

графической производительностью

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

Для того чтобы пользователь смог оптимизировать распределение задач между графическим и центральным процессорами, программисту стоит позволить ему управлять состоянием ряда глобальных переменных, объявленных в модуле FМX.Types (табл. 16.6).

 

Таблица 16.б. Настройка графической производительности Windows

 

Переменная

Умолчание

Описание

GlobalDisaЫeFocusEffect:  Boolean;

false

Эффект фокуса ввода. Для устройств с низкой производительностью уста- навить в true

GlobalUseDirect2D: Boolean;

true

Использовать аппаратный Direct2D

GlobalUseDirect2DSoftware: Boolean;

false

Установить программную эмуляцию Direct2D

GlobalUseНWEffects: Boolean;

true

Использовать аппаратное ускорение там, где это возможно

GlobalUseGDIPlusClearType: Boolean;

true

Использовать технологию СlеагТуре для улучшенного отображения шрифтов в Windows


 

 

 

 

 

 


ГЛАВА 17

 

 

Графические эффекты

 

 

 

 

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

Более шести десятков невизуальных компонентов со страницы Effects построены на фундаменте цепочки классов 11TEffect - TFilterEffect- TimageFXEffect". Вне зависимости от "классовой" принадлежности, все графические эффекты можно раз­ делить на пять категорий:

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

2.    Эффекты, в которых для расчета итогового цвета функция преобразования ана­ лизирует не только целевой пиксел, но и пикселы, входящие в его ближайшее, а иногда и дальнее окружение. К таким эффектам относят эффекты затуманива­ ния (Ьlur) и эффекты искажения (distortions) изображения (например, свертка в спираль).

3.    Аддитивные эффекты (additive effects), привносящие в изображение новые эле­ менты (например, зеркальное отражение).

4.    Геометрические эффекты, изменяющие форму и пропорции изображения.

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

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


 

 

 


 

 

Рис. 17.1. Подключение компонента-эффекта InvertEffectl к целевому объекту Imagel

 

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

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


var i:integer;

Ьeqin

//находим и отключаем старый эффект

for    i  :=О to  Imagel.ChildrenCount-1 do

if    Imagel.Children(i] is TimageFXEffect then

Ьeqin

Imagel.RemoveObject(Imagel.Children[i]);

break;

end;

//подключаем новый эффект Imagel.AddObject(BlurEffectl);

end;

 

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


 

Применение эффекта к файлам изображений

Даже беглое знакомство с набором компонентов FireMonkey, реализующих алго­ ритмы разнообразных визуальных фильтров, впечатлит любого программиста, ко­ торый сталкивался с разработкой графических редакторов. Сразу возникает непре­ одолимое желание засучить рукава и написать приложение, способное составить конкуренцию таким грандам 1Т-индустрии, как Adobe Photoshop и Corel Photo­ Paint. Нет ничего невозможного, однако на первых порах придется решить одну непростую задачу -                                           научиться применять эффекты не просто к элементам управ-· ления, а к хранящимся в файлах картинкам и сохранять полученные результаты в файл.                                                                  '

Так в чем проблема? Если мы попробуем вызвать метод сохранения картинки (к которой применен тот или иной графический фильтр) в файл, например

Irnagel.Bitrnap.SaveToFile(<имя  файла>);

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

 

ЗАМЕЧАНИЕ

Компоненты-эффекты никак не воздействуют на физическую структуру рисунка, а лишь управляют процессом вывода картинки на экран. Отключение эффекта (напри­ мер, с помощью свойства EnaЫed) возвращает результирующее изображение в ис­ ходное состояние.

Одним из решений задачи сохранения эффекта в файл может стать небольшой об­ ман. Суть идеи заключается в том, что после применения графического фильтра (например, коррекции контрастности ContrastEffectl: тcontrastEffect) к картинке (хранящейся в компоненте Irnagel: Tirnage) мы скопируем видимую область изо­ бражения в другой объект - растровый рисунок твi tmap. И только затем отправим результаты, полученные таким "мошенническим" путем, в файл (листинг 17.2).


var TempFoпn: TFoпn; Templmage:Tlrnage; TempBitrnap:TBitmap;

Ь&gin

if    SaveDialogl.Execute then

Ьeqin

//создаем временную невидимую форму TempForm:=TForm.CreateNew(Application);

TempForm.ClientWidth:= Imagel.Bitrnap.Width; TempForm.ClientHeight:=Imagel.Bitmap.Height;

//на форме размещаем временный контейнер Timage Tempimage:=Timage.Create(TempFoпn);


 

Ternpimage.Parent:=TernpFoпn; Ternplrnage.Width:= Imagel.Bitmap.Width; Ternplmage.Height:=Imagel.Bitmap.Height;

//переносим в контейнер рисунок, хранящийся в Imagel:Tlmage Ternplmage.Bitmap.Assign(Imagel.Bitmap);

//применяем к контейнеру эффект ContrastEffectl:TContrastEffectl Ternpimage.AddObject(ContrastEffectl);

//теперь в памяти есть невидимая форма с невидимым пользоватето

//рисунком, к которому применен графический фильтр

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

end;


//создаем пустой битовый образ TempBitmap:=TBitmap.Create(bWidth,bHeight);

//переносим в образ изображение из невидимой формы ' TernpBitmap. Canvas. BeginScene () ;

TernpFoпn.PaintTo(TempBitmap.Canvas);//!! ! рисуем именно так!!! TernpBitmap.Canvas.EndScene;

//сохраняем результат в файл TernpBitmap.SaveToFile(SaveDialogl.FileName);

//освобождаем ресурсы TernpBitmap.Free; TernpFoпn.Release;

end;


 


Листинг достаточно подробно прокомментирован, но сделаю еще одно очень важ­ ное замечание. Картинка с эффектом должна быть нарисована на холсте целевого битового образа тempBitmap именно методом Paintтo () формы, а не каким-либо другим способом!

 

Применение нескольких эффектов к файлам изображений

Предлагаю провести один простой, но очень поучительный эксперимент. Попро­ буйте одновременно подключить к компоненту Timage по крайней мере пару разно­ типных графических фильтров, например тwaveEffect и TPixelateEffect. Каков ре­ зультат? Идеалисты наверняка надеялись, что получат картинку со сложением эф­ фектов водной глади и пикселизации. В свою очередь скептики отошли подальше от компьютера, предполагая, что он расплавится при решении непомерно сложной вычислительной задачи. Как всегда истина находится посредине - компьютер ос­ тался цел, а к изображению применился всего один, первый по счету графический фильтр. Разочарованы?

 

ЗАМЕЧАНИЕ

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


 

В том, что визуальный элемент управления готов сотрудничать всего с одним гра­ фическим фильтром, а на остальные не обращает внимания, нет ничего удивитель­ ного. Это разумное ограничение, не позволяющее превратить проект FireMonkey в неработающую новогоднюю елку, ведь наверняка найдется "программист", кото­ рый подключит ко всем визуальным компонентам все компоненты-эффекты. В ито­ ге процессоры компьютера посвятят все свое время перерасчету фильтров.

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

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

    фильтр контрастности/яркости ContrastEffectl: TContrastEffect;

    фильтр цветового тона HueAdjustEffectl: THueAdjustEffect;

    ряд других компонентов, потомков класса TimageFXEffect.

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

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

 

 

;••••••••••••«,,ч,••••••••.. •••••••••,•••••••-•·•••··.,,.,, •••••<:               •.i•••i••••••-r•••·•'•.\/'"' "•..•,..••••м• ч.. v,••••.. -•-•._....•••....••q,.,..11'•••....••-.,••••••••••••••••-•..,"'"'"'""' ,..,,. .,,.,, •---,.. ,.,,,._,   11;

ЯМ-тинr 17.З. Cnиs• alwкro8                                                                           ,---  ·:

uses ... , System.Generics.Collections;

/ / ...

var Forml: TForml;

EffectsList: TObjectList<TimageFXEffect>;      //список TimageFXEffect i.nplementation

{$R *.fmx}

 

proaedure TForml.FormCreate(Sender: TObject); Ьegin

EffectsList:=TObjectList<TimageFXEffect>.Create(false); EffectsList.Add(ContrastEffectl); EffectsList.Add(HueAdjustEffectl);

EffectsList.Add(<дpyгиe графические фиnьтры>);

end;

 

 

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


 

 


Графические эффекты                                                                                                                            245

 

А теперь разработаем функцию ApplayEffects (), на вход которой будет направлено

tmap и перечень эффектов ции должно оказаться изме­

 

................. .,.....,............ ', .................· .....' ....,....,.,...·•···..:.

...............,............... ..... ··········"" ..  •.........;i .,,,..........r.o.. - ... .

Function ApplayEffects(const Source:TBitmap;

const List: TObjectList<TimageFXEffect>) :TBitmap;

var TernpFoпn:TFoпn; Tempimage:Timage; TempBitmap:TBitmap; i:integer;

Ьegin

//------ невидимая форма с временным изображением TempFoпn:=TFoпn.CreateNew(Application);

TempFoпn:c1ientWidth:=Source.Width; TempFoпn.ClientHeight:=Source.Height;

 

Ternpimage:=Timage.Create(TempFoпn); Tempimage.Parent:=TempFoпn; Tempimage.Width:=Source.Width; Tempimage.Height:=Source.Height; Ternpimage.Bitmap.Assign(Source);

/1--------------------------------------------------------

TempBitmap:=TBitmap.Create(Source.Width,Source.Height);

//---------последовательно применяем все фильтры-------------- ­

for i:=0 to List.Count-1 do

Ьegin

Tempimage.AddObject(List.Items[i]);//пpимeняeм фильтр TempBitmap.Canvas.BeginScene(); TernpFoпn.PaintTo(TempBitmap.Canvas); //перерисовка TempBitmap.CaRvas.EndScene;

Tempimage.RemoveObject(List.Items[i]); //снимаем фильтр Tempimage.Bitmap.Assign(TempBitmap);

end;

// Result:=Ternpimage.Bitmap; TempBitmap.Free; TempFoпn.Release;

end;

 

 

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


 

Простейшие корректирующие эффекты

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

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

    компоненту TinvertEffect, инвертирующему цвет рисунка;

    компоненту тмaskToAlphaEffect, превращающему в белые (путем добавления альфа-канала) все серые пикселы изображения;

   компоненту тмonochromeEffect, конвертирующему цветной рисунок в моно­ хромный.

Подключив перечисленные компоненты к целевому объекту с текстурой, мы сразу, без каких-либо дополнительных настроек, получим требуемый результат.

 

Заливка цветом TFil/Effect и TFil/RGBEffec t

Простейшую задачу по заливке пикселов изображения определенным цветом ре­ шают компоненты TFillEffect и TFillRGBEffect. Разница между классами заключа­ ется в том, что первый из них закрасит заданным цветом всю поверхность изуаль­ ного объекта. Второй компонент немного хитрее и применит заданный цвет только к непрозрачным пикселам. Не трудно догадаться, что наиболее важное свойство обоих компонентов

property Color: TAlphaColor;

устанавливает цвет заливки.

 

Яркость и контрастность TContrastEffect

Наиболее востребованная корректировка изображения, связанная с управлением яркостью и контрастностью, производится с помощью компонента тcontrastEffect.

За установку яркости отвечает свойство

proper� Brightness : Single; //диапазон значений от -1  до 1

Для повышения яркости следует выбрать значения выше нуля, отрицательная часть диапазона соответствует пониженной яркости.

Управление контрастностью осуществляет свойство

property Contrast: Single;  //диапазон значений от  О до 2

Значение, превышающее 1,5, увеличивает контрастность, значение меньше 1,5 - уменьшает.


 

 


Графи•,ооиио  оффо1<mы                                                                                                                                '147

Регулировка опенка цвета THueAdjustEffect

Компонент тнueAdjustEffect позволяет управлять оттенком цвета изображения, для этих целей класс снабжен свойством

property Hue: Single; //диапазон от -1 до 1

 

Ясная TB/oomEffect

и пасмурная TG/oomEffect погода

Два родственных компонента TВloomEffect и TGloomEffect нацелены на придание изображению двух противоположных качеств - имитации яркого солнечного или хмурого пасмурного дня.

Для изменения параметров рисунка каждый из компонентов обладает четверкой свойств. Так, компонент твloomEffect для настройки интенсивности свечения за­ действует сво ства

property Bloomintensity: Single;        //диапазон значений от О до 1 property  Baseintensity:  Single;                             //диапазон значений от О до 1

Цветовое насыщение TBloomEffect зависит от состояния свойств

property BloomSaturation: Single; //диапазон значений от О до 1

property BaseSaturation: Single;         //диапазон значений от О до 1

Свойства TGloomEffect практически повторяют свойства его коллеги, интенсив­ ность свечения определяется состоянием свойств

property Gloomintensity: Single; //диапазон значений от О до 1

property Baseintensity:  Single;         //диапазон значений от О до 1

Насыщенность цветом определяется свойствами

property GloomSaturation: Single; //диапазон значений от О до 1

property BaseSaturation: Single;         //диапазон значений от О до. 1

 

Прозрачность TColorKeyA/phaEffect

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

property ColorKey: Single; //диапазон значений от -1 до 1

Допустимая степень отклонения цвета от значения заданного в свойстве ColorKey

определяет свойство

property Tolerance : Single; f/диапазон значений от О до 1

Если свойство принимает нулевое значение, то эффект прозрачности отключается, с возрастанием значения число цветов, попадающих под критерий прозрачности, возрастает.


 


 

Эффекты размытия и искажения

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

 

Размытие

Эффекты размытия (затуманивания) достигаются при активном участии компо­ нентов: TBlurEffect, TDirectionalBlurEffect, TBoxBlurEffect, TGaussianBlurEffect и TRadialВlurEffect. Суть эффекта размытия заключается в том, что все изображение становится расфокусированным без выделения какой-либо области. Каждый из компонентов решает задачу по-своему. Так, самый непритязательный из них (ком­ понент TВlurEffect) просто затуманивает изображение за счет смещения основных контуров рисунка. Степень размытия определяет свойство

property Softness : Single; //по умолчанию 0,3

способное принимать значение из диапазона от О до  9.

Более сложный класс TGaussianBlurEffect создает эффект дымки путем расфокуси­ ровки изображения в соответствии с законом распределения Гаусса. В результате работы алгоритма, заложенного в компонент TGaussianBlurEffect, сведения о пик­ селах распределяются от центра наружу по колоколообразным кривым.

У всех рассматриваемых компонентов (за исключением TВlurEffect с его свойст­ вом Softness) базовым свойством, управляющим степенью размытия изображения,

является

property BlurAmount : Single;

Кроме того, у компонентов предусмотрен ряд вспомогательных свойств, оказы­ вающих дополнительное воздействие на текстуру объекта. В частности у компо­ нента TDirectionalBlurEffect, позволяющего конкретизировать, в каком направле­ нии будет смещен рисунок для получения эффекта затуманивания, предусмотрено свойство

property Angle ; Single;

определяющее угол смещения.

В свою очередь решающий схожую задачу компонент TRadialВlurEffect предлага­ ет программисту свойство

property Center: TPointF;

позволяющее определиться с координатами центра эффекта.


 

Искажения

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

 

Вертикальные полосы-TBandsEffect

Компонент твandsEffect позволяет покрыть изображение вертикальными полоса­ ми, в результате получится эффект, в какой-то степени напоминающий жалюзи (рис. 17.2). Управление эффектом осуществляется с помощью свойства

property BandDensity: Single; //диапазон от О до 150 определяющего число вертикальных полос, и свойства property Bandintensity: Single; //диапазон от О до 1 управляющего интенсивностью засветки границ полос.


Рис. 17.2. Пример эффекта вертикальных полqс, компонент TBandsEffect


 

Водоворот TSwir/Effect и TBandedSwir/Effect

Если вы поклонник неординарных решений, то эфф кт водоворота, создаваемый компонентами TSwirlE"ffect и твandedSwirlEffect, подойдет вам как нельзя лучше (рис. 17.3).

Центр водоворота определяется координатами точки

f property Center : TPointF;

У тswirlEffect интенсивность скручивания определяется свойст ом

property SpiralStrength: Single;

У твandedSwirlEffect название свойства, решающего аналогичную задачу, немного другое:

property Strength: Single;

От знака значения, хранимого в свойстве, зависит направление вращения водово­ рота.

Если вы работаете с более "продвинутым" компонентом твandedSwirlEffect, то по­ лучаете возможность управлять дополнительным параметром - числом полос (кругов от водоворота) вокруг центральной точки эффекта

property Bands : Singlе;//диапазон значений от О до  20

 


Рис. 17.3. Пример  эффекта водоворота, компонент TBandedSwirlEffect


 

Еще одна особенность водоворота зависит от состояния свойства

property AspectRatio: Single; //диапазон значений от 0.5  до 2

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

 

Увеличительное стекло TMagnifyEffect и TSmoothMagnifyEffect

Компоненты TMagnifyEffect И TSmoothМagnifyEffect ПОЗВОЛЯЮТ Программисту "под­ нести" к рисунку увеличительное стекло. Разница между компонентами заклю­ чается в том, что TMagnifyEffect просто увеличивает заданную область, а тsmoothМagnifyEffect, кроме трго, вносит искажения, имитируя реальную стеклян­ ную линзу (рис. 17.4).

 

 

 


Рис. 17.4. Увеличение области без искажения (TMagnifyEffect) и с искажением (TSmoothМagnifyEffect)

 

Коэффициент увеличения задается свойством

property Мagnification: Single;  //диапазон от 1 до 5

Увеличиваемая область представляет собой окружность с центром, определяемым свойством

property Center·: TPointF;

Если вы работаете с тмagnifyEffect, то радиус увеличиваемой области зависит от состояния единственного свойства

property Radius : Single;  //диапазон от О до 1


 

Второй компонент, тsmoothMagnifyEffect - это размер увеличиваемой области, он назначается исходя из состояния пары свойств:

property InnerRadius            Single; //диапазон значений от О до 1 property OuterRadius  : Single;  //диапазон значений от О до 1

соответственно определяющих радиусы внутренней и внешней границ эффекта.

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

property AspectRatio: Single; //диапазон от 0.5 до 2.00

 

Стягивание области TPinchEffect

Компонент TPinchEffect реализует еще один эффект, искажающий не всю рабочую поверхность текстуры, а лишь область (рис. 17.5) в форме окружности с центром в точке с координатами

property Center: TPointF;

Радиус искажаемой области

property Radius : Single; //диапазон от О до 1

Степень искажения области зависит от состояния свойства

property Strength: Single; //диапазон от О до 20


Рис. 17.5. Пример эффекта сдавливания области, компонент TPinchEffect


 

Для восстановления пропорций окружности следует задействовать свойство

property AspectRatio  : Single; //диапазон от 0.5 до 2.00

 

Рябь на воде TRippleEffect

Для создания эффекта ряби, которая появляется на воде после падения в нее ка­ кого-то предмета (рис. 17.6), стоит воспользоваться услугами компонента TRippleEffect. Основные свойства класса позволяют управлять координатами цен­ тра эффекта

property Center   TPointF;

амплитудой зыби


property Amplitude

частотой волн

property Frequency

и фазой


Single; //диапазон значений от О до 1

 

 

Single; //диапазон значений от О до 100


property Phase : Single;

Кроме того, в распоряжении программиста имеется свойство

property AspectRatio: Single;

позволяющее управлять пропорциями эффекта.


Рис. 17.6. Пример эффекта ряби на воде, компонент TRippleEffect


 

Волны ТWaveEffect

Еще одним специалистом по "водным" эффектам можно считать класс тwaveEffect,

его задача заключается в создании иллюзии водной глади, покрытой волнами. Расстояние между волнами на воде определяется свойством.

property WaveSize : Single; //диапазон значений от 32 до 256

Управляя параметром

property Tirne : Single; //диапазон от О до 2048

мы сможем моделировать перемещение волн по поверхности.

 

Горизонтальная деформация краев текстуры ТWrapEffect

Компонент тwrapEffect позволяет применить к левому и правому краям изображе­ ния эффект горизонтальной деформации. Управление эффектом осуществляется отдельно для каждого из края. Так, состоянием левой грани ведают свойства:

property LeftStart : Single;                //верхняя опорная точка левой грани property LeftControll : Single; //первая отклоняющая точка левой грани property LeftControl2 : Single; //вторая отклоняющая точка левой грани property LeftEnd: Single;  //нижняя опорная точка левой грани

Свойства определяют координаты точек невидимой кривой Безье, проходящей по левой границе рисунка. Координаты кривой представлены не в виде физических значений (.Х, У), а в виде вещественного числа из диапазона значений от О до 1. Если вы заполните все свойства Left ... нулями, то эффект горизонтальной дефор­ мации для левой грани отключается и она выпрямляется. Увеличение значений приводит к смещению левой границы изображения вправо.

Степенью деформации правой грани ведает квартет аналогичных свойств:

property RightStart : Single;                //верхняя опорная точка правой грани property RightControll : Single; //первая отклонтощая точка правой грани property RightControl2 : Single; //вторая отклонтощая точка правой грани property RightEnd: Single;                                                                          //нижняя опорная точка правой грани

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

 

Аддитивные эффекты

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

 

Отражение TReflectionEffect

Эффект отражения выводимого в нижней части изображения достигается с по­ мощью компонента TReflectionEffect. Размер отражения определяется свойством

propsrty Length: Single;


 

Свойство воспринимает значения из диапазона от О до 1 (максимальный размер от­

ражения).

 

Эффекты свечения TGlowEffect и TlnnerG/owEffect

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

Цвет свечения определяется свойством

property GlowColor: TAlphaColor;

Плавность полутоновых переходов и размер области свечения зависят от состояния свойства

property Softness: Single; //диапазон значений от О до 9

 

 

Тень TShadowEffect

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

property ShadowColor: TAlphaColor;

определяющее цвет тени.

Протяженность и направление тени зависит от состояния свойств

property Distance: Single;

property Direction: Single; //от О до 359 градусов

По умолчанию расстояние, на которое отбрасывается тень, равна трем единицам, а угол падения тени соответствует 45 градусам.

Плавность цветовых переходов настраивается с помощью свойства

property Softness: Single;

 

 

Эффект тиснения TEmbossEffect

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

Глубина тиснения определяется состоянием свойства

property Amount: Single;//пo умолчанию 0,5

Свойство воспринимает значения из диапазона от О до 1.


 

Также можно управлять расстоянием между линиями контура и дубликатами этих линий, для этого предназначено свойство

property Width:  Single;  //диапазон от О  до 10

 

Набросок на бумаге TPaperSketchEffect

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

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

property BrushSize: Single;  //по умоfчанию 3

 

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


Рис. 17.7. Пример эффекта наброска на бумаге, компонент TPaperSketchEffect

 

 

Карандашный набросок TPen i/StrokeEffect

Компонент TPencilStrokeEffect придает изображению вид нарисованного вручную. Алгоритм осуществляет обработку изображения так, что в результате вместо цвет­ ной картинки получается эффект черно-белого карандашного наброска (рис. 17.8).


 

Базовое свойство компонента

property BrushSize:  Single;//пo умолчанию 5

отвечает за грубость рисунка. Свойство способно принимать значения из диапазона

ОТ 1 ДО 19.


Рис. 17.8. Пример эффекта карандашного наброска на бумаге, компонент TPencilStrokeEffect

 

 

Пикселизация TPixelateEffect

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

Управление эффектом осуществляется с помощью свойства

property BlockCount: Single; //по умолчанию 25

По мере увеличения значения свойства степень пикселизации снижается.

 

Старая фотография TSepiaEffect

Компонент тsepiaEffect придает изображению эффект пожелтевшей от старости фотографии. Степень старения зависит от состояния свойства

property Amount: Single; //по умолчанию 0,5


 

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

 

Управление резкостью TSharpenEffect

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

Управление резкостью осуществляется с помощью свойства

property Amount: Single;//пo УМQлчанию 1

Диапазон допустимых значений заключен в пределы от О до 1.

 

Глубина цвета TToonEffect

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

property Levels: Single;//пo умолчанию 5

Свойство принимает значения из диапазона от 3 (минимум) до 15 (максимум).

 

Геометрические эффекты

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

 

Аффинные преобразования TAffineTransformEffect

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

property Center:  TPointF;        //координаты центра вращения

property Rotation: Single; //угол поворота

За уропе :п, Шlкселu3ации текстуры отвечает свойство

property Scale: Single;             //диапазон от 0,05 до 4

 

Обрезка TCropEffect

Компонент TCropEffect позволяет осуществить обрезку части изображения. Для этого надо указать координаты левой верхней

preperty LeftTop; TPointF;


 

 

Графические эффекты                                                                                                                                           

 

и правой нижней точек отсечения

property RightBottom: TPointF;

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

 

BHHMAHHEI

Для корректной реализации эффекта обрезки изображения последнее должно выво­ диться без искажения пропорций. Например, если в качестве целевого компонента ис­ пользуется Timage, то его свойство WrapMode желательно установить в состояние TIMageWrapМode. iwOriginal.

 

 

Перспектива TPerspective TransfonrrirEffect

Компонент TPerspectiveTransformEffect позволяет вносить геометрические иска­ жения в форму изображения, придавая последнему вид прямоугольника, ромба, трапеции или любого другого четырехугольника. В частности, благодаря TPerspectiveTransformEffect можно получить преобразование перспективы (рис. 17.9):


Рис. 17.9. Эффект  перспективы,  компонент  TPerspectiveTransformEffect

 

Форма результирующего четырехугольника формируется за счет квартета точек:


property TopLe'ft: TPointF; property TopRight:. TPointF;


//левый верхний угол

//правый верхний угол



 

property BottomRight: TPointF; //правый нижний угол property BottomLeft: TPointF;                             //левый нижний угол

Заметим, что на результат оказывают влияние не только значения вершин четырех­ угольника, но и параметры объекта, содержащего изображение. Например, приме­ няя эффект к компоненту тrmage, стоит поэкспериментировать с его свойствами Align И WordWrap.

 

Эффект плитки TTilerEffect

Компонент ттilerEffect реализует эффект плитки, размножая исходное изображе­ ние. Количество клонов рисунка определяется свойствами

property HorizontalTileCount: Single;

property VerticalTileCount: Single;

которые соответственно определяют число рядов и колонок.

При желании программист может назначить отступы между рядами и колонками, для этого следует воспользоваться свойствами

property HorizontalOffset: Single; //горизонтальный отступ

property VerticalOffset:  Single;          //вертикальный отступ

 

Наложение изображений TNorma/BlendEffect

Сознаюсь, что отнеся класс TNormalBlendEffect к компонентам, осуществляющим геометрические преобразования, я пошел на поводу способа классификации эффек-


Рис. 17.1О. Пример эффекта наложения, создаваемого компонентом TNormalBlendEffect


Графические эффекты                                                                                                                               261

 

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

property Target:  TBitmap;

предназначено для хранения второго изображения, которое будет наложено на ис­ ходное, т. е. изображение, к которому подключен компонент TNormalВlendEffect. Накладываемое изображение желательно создавать в формате PNG (напомним, что формат PNG поддерживает альфа-канал). Все остальное - дело техники. На рис. 17.1О производится сложение фотографии горного озера и надписи "FireMonkey", сохраненной в формате PNG.

 

Эффекты трансляции

Эффекты трансляции предназначены для создания управляемого плавного перехо­ да от исходного изображения ко второй - целевой картинке. На роль специалистов по трансляции претендуют два десятка компонентов. Мы с вами остановимся на изучении лишь наиболее показательных из них:

   твandedSwirlTransitionEffect   -      эффект, создающий завихрение;

   TFadeTransitionEffect- эффект постепенного исчезновения исходного изобра­ жения с последующим выводом целевого;

   тcircleTransitionEffect- эффект проявления целевого изображения внутри эллиптической области;

  тмagnifyTransitionEffect -              плавное проявление увеличенного целевого изо­ бражения внутри эллиптической области (рис. 17.11);

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

Все наши новые знакомые снабжены парой важных свойств. В первую очередь это свойство

property Target: TBitmap;

в котором хранится целевое изображение.

Второе свойство

property Progress: Single;  //диапазон от О до 100%

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

Компоненты TCircleTransitionEffect, TMagnifyTransitionEffect И TBandedSwirl­ TransitionEffect помимо управления степенью выполнения эффекта разрешают программисту провести дополнительные настройки. В частности, тройка перечис­ ленных компонентов спо_собна управлять позицией центра эффекта.

property Center: TPointF;



Рис. 17.11. Пример эффекта,  создаваемого  компонентом  TMagnifyTransitionEffect

 

Для TCircleTransitionEffect и TMagnifyTransitionEffect это центр круга, из кото­ рого проявляется второе изображение (рис. 17.11 ), а для твandedSwir1тran­ sitionEf fect это центр пульсации.

Кроме того, компонент TCircleTransitionEffect умеет определять коэффициент расплывчивости

property FuzzyAmount: Single; //от О до 1,  по умолчанию 0.1

и размер круга

property Size: Single; //по умолчанию 1

Компонент твandedSwirlTransitionEffect обладает дополнительной парой свойств, управляющих силой скручивания

property Strength: Single;

и частотой скручивания в спираль

property Frequency: Single;


 

 

ГЛАВА 18

 

 

Анимация

 

 

 

 

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

Как сделать интерфейс наглядным и интуитивно понятным? Существует множест­ во рекомендаций по размещению и группировке элементов управления, по эаданию очередности передачи фокуса ввода, по использованию пояснительных надписей, по подбору цветовых схем и по многому другому. Давайте рассмотрим еще одно направление в разработке пользовательского интерфейса - анимироJ1анные эле­ менты управления.

 

ЗАМЕЧАНИЕ

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

 

Простой пример анимации

Механизм анимации настолько прост, что в минимальной нотации не потребует от нас ни одной строки к_ода. Создайте новый проект FireMonkey и разместите на форме круг Circlel:TCircle (страница Shapes палитры компонентов). Теперь обра­ титесь к странице Animations палитры компонентов и перенесите на форму компо­ ненты:

  FloatAnimationl:TFloatAnimation, этот компонент позволяет воздействовать на описываемые вещественными числами свойства элемента управления;


 

   ColorAnimationl:TColorAnimation, этот компонент предназначен для управления цве'Fом компонента.

Подключите компоненты-аниматоры к кругу Circlel. Для этого следует восполь­ зоваться услугами окна Structure, отображающего структуру нашего проекта (рис. 18.1). Убедившись, что отвечающие за анимацию компоненты попали в нуж­ ные "руки", перейдит,ек Инспектору объектов.

 


Рис. 18.1. Подключение объектов анимации к фигуре TCircle

 

Выберите в Инспекторе объектов компонент FloatAnirnationl. Научим его управ­ лять горизонтальным размером круга Circlel. Для этих целей, вооружившись мышью, осуществим 4 операции:

1.    В свойстве PropertyName находим анимируемое свойство scale.x, отвечающее за горизонтальный масштаб компонента.

2.    Настраиваем свойства, задающие диапазон изменяемых значений масштаба

StartValue=l И StopValue=2.

3.    Выбираем событие-триггер, выступающее инициатором анимации. Установив в свойство Trigger в состояние IsMouseOver=true, мы укажем, что анимация на­ чинается в момент появления над компонентом указателя мыши.

4.    Выбираем  в  свойстве  Triggerinverse  событие,  завершающее  анимацию:

Is ouseover=false (указатель мыши покинул анимируемый объект).

На этом настройка анимации для компонента FloatAnimationl завершена. Теперь

выберите компонент ColorAnimationl и в Инспекторе объектов настройте его пове-


 

 


............�-------..,..=---

 
ОЬjю lmpector                                                                             II

FloatAnlm./Jtion1 Tf!oatAn naoon

-JP1rqю,erntieтsw] Evеen--ts:


Object lnipector

·Colorд1llmatic111t "tCtk<A.'lil>,;,!ioo

jProper 1 Events1


 

!Auto11.everse

l()ekiv

Dural.iOn

fnilbled


---Тat1n

iuFl!lse

1 о

i

 
·t 0,2

False


Animal.iOnType AuroReverse Delay

Duralion

Enabled


;atin

; :,2

 
E]False

 

 

[}False


!nterpo/г\ion

lnverse w,liveBirlФ'105 DeSigner

!1.оор

»!Nalne

Propert,/f'lame

StartfromCurrent

Suirtva!ue


.1\1.ne/Jr

·=- ­

 
!rlfa!se

: :eВinDdщi-/nieor s

H:'jF!lse

]Scale.X

 itJFalse  

1


!nierpolalion                 ,iliim,ar

1

 
,Inverse                  ! False

ooju,.,eВoongs Designer  !uveflindiDnegSsigner

»r:;i                      iмва1

 
iloop                                 1[Е'F1alse

opertyt;ame               I fill.Color

St:arlfromCurrent    ;[';]Fl!lse

llstartValue             I  О Yellow


StopV.we                                  2

St,/leName


StopValue StyleNarne


1 Firebrick

1


.Тlt!I

ITrigger

ITtigger!nverse

Visualy..,

 

 

 

 

 

Alshown


·IsНouseOver=

\tsMOUROver=falst!


,T;sg                                    10

1тngger                  'IsНou'JeOver=Ьve

lтrig!jerlnverse         IIsHouseOver=false

.    l!linil y...--  ---


 

Alstюwn


 


Рис. 18.2. Настройка свойств компонентов FloatAnimationl и ColorAnimationl

 

дение в соответствии с рис. 18.2. Анимированный проект готов к старту. Согласи­ тесь, что программисты Embarcadero потрудились на славу, оставив на нашу долю лишь визуальную сторону проектирования.

 

 

Общие черты компонентов-аниматоров,

класс TAnimation

Все компоненты-аниматоры (которые вы обнаружите на странице Animations па­ литры компонентов Delphi) берут начало от класса EМX.Types.TAnimation. Соответ­ ственно большинство свойств и методов наследники "впитывают" от своего предка. Рассмотрим наиболее важные из них.

Длительность анимации определяется значением свойства

property Duration: Single; //единица измерения - секунды

При необходимости перед началом анимации можно выждать небольшую паузу.

Время задержки устанавливается в свойстве

property Delay:  Single;  //по умолчанию О секунд

Закон, в соответствии с которым анимируемый объект станет переходить из одного состояния в другое, определяется свойством


 

property Interpolation: TinterpolationType;

type TinterpolationType = (itLinear, itQuadratic, itCubic, itQuartic, itQuintic, itSinusoidal, itExponential, itCircular,

itElastic,  itBack,  itBounce);

По умолчанию установлен обычный линейный переход itLinear, приводящий к последовательной смене состояний (например, цвета или размера). Вместе с тем тип данных тr�terpolationType предоставляет такой большой выбор допустимых вариантов поведения, что законом интерполяции наверняка стоит поэксперименти­ ровать. На математический аспект интерполяции оказывает влияние вид анимации:

property AnimationType: TAnirnationType;//пo умолчанию atin

type TAnimationType = (atin, atOut, atinOut);

Сторонникам бесконечного цикла смены размеров (местоположения, цвета и т. п.) стоит установить в true свойство

property Loop: Boolean; //по умолчанию false

Управление анимацией обычно осуществляется за счет свойств-триггеров

property Trigger: TTrigger;                      //старт анимации

property Triggerinverse: TTrigger;  //завершение анимации

указывающих, какое именно событие и при каких условиях послужит поводом для начала и завершения анимации. В качестве примера таких событий можно привести получение и утрату элементом фокуса ввода (IsFocused=true и IsFocused=false) или появление над объектом  указателя  мыши (rsмouseover=true) и уход указателя

( IsMouseOver=false).

Если процесс анимации в данный момент активен, то свойство

property Running: Boolean;  //только для чтения

возвратит значение true.

При необходимости старт и остановка анимации осуществляются из кода:

prooedure Start;                         //старт анимации

prooedure Stop;                         //остановка  анимации

prooedure StopAtCurrent; //остановка анимации в текущем состоянии

Временно приостановить процесс позволит свойство

property Pause: Boolean;

        Как и положено полноценным объектам, все компоненты-аниматоры способны генерировать события. Старт анимации сопровождается вызовом события

property OnProcess:  ТNotifyEvent;

О завершении анимации уведомляет событие

property OnFinish: TNotifyEvent;

Полученных знаний вполне достаточно для того, чтобы попробовать анимировать

объект не в период визуальной разработки, а во время выполнения программы. Во


 

фрагменте кода, предложенном в листинге 18.1, динамически создается и подклю­ чается к панели Panell экземпляр класса TFloatAnimation.

  ,......, . .........   -·-··1

 

 

•••••,;.1}................,....................................... ,

 

 

with TFloatAnimation.Create(Panell) do

Ьegin

Parent:=Panell;

PropertyName: = ',RotationAngle'; StartValue:=0;

StopValue:=180; Trigger:='IsMouseOver=true'; Triggerinverse:='IsMouseOver=false';

end;

 

Обратите внимание на то, что для подключения к анимируемому объекту нам по­ требовалось передать ссылку на этот объект в свойство Parent.

Свойство

property Inverse: Boolean; //по умолчанию false

позволяет   инвертировать   анимацию   (например,   заставить   сменяться   кадры

TBitmapAnimation в обратном порядке).

 

Индивидуальные особенности компонентов-аниматоров

На странице Animations палитры компонентов Delphi расположилось 9 компонен­ тов, предоставляющих программисту разноплановые услуги по анимации объектов FMX. Каждый из компонентов обладает индивидуальными чертами, определяю­ щими область применения того или иного потомка класса TAnimation.

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

property PropertyName:  AnsiString;

Надо понимать, что далеко не все свойства могут быть анимированы. Так, если вы пытаетесь управлять заливкой объекта с помощью компонента тcolorAnimation, то в PropertyName могут быть переданы только названия свойств, работающих с цве­ том КИСТИ (Fill И Stroke).

 

ЗАМЕЧАНИЕ

Во время визуального проектирования Инспектор объектов подскажет программисту, какие именно свойства могут быть задействованы в анимации.


 

Для управления правилами анимации у большинства компонентов-аниматоров имеется пара свойств: startValue и StopValue, однако тип обслуживаемых значений у каждого из аниматоров индивидуальный, зависящий от стоящих перед компонен­ том задач.

 

Цветовая анимация, компонент TColorAnimation

Компонент тcolorAnimation позволяет управлять цветовыми характеристиками кис­ ти заливки (свойство Fill) или кисти вывода графических примитивов (свойство Stroke). Анимируются два цвета, значения которых указываются в свойствах:

property StartValue: TAlphaColor; property StopValue: TAlphaColor;

 

Градиентная анимация, компонент TGradientAnimation

Благодаря градиентной анимации, реализуемой с помощью компонента TGradientAnirnation, можно значительно улучшить внешний вид объектов, способ­ ных управлять своими цветовыми характеристиками с помощью свойств Fill и

Stroke.

Параметры анимации назначаются при посредничестве свойств:

property StartValue: TGradient; property StopValue: TGradient;

Но теперь вместо данных о цвете (как это бьmо в тcolorAnimation) мы должны ука­ зать градиентные настройки TGradient.

 

Анимированная картинка, компонент TBitmapAnimation

Компонент TBitrnapAnimation может работать совместно с объектами, способными отображать растровые картинки (обладающие свойством Bitrnap). Эффект анима­ ции достигается путем смены пары картинок, хранимых в свойствах

property StartValue: TBitmap; property StopValue: TBitrnap;

 

Анимированный ряд, компонент TBitmapListAnimation

Возможности компонента TBitmapAnimation меркнут по сравнению с его "коллегой" твitmapListAnimation, ведь последний умеет управлять не парой, а целым списком картинок. Так что при остром желании твi tmapListAnimation способен показать пользователю короткометражный мультфильм. К кадрам анимации не предъявля­ ется особых требований за исключением одного - все они должны быть одинако­ вого размера и склеены друг за другим в одну ленту.

Лента с картинками передается в свойство

property AnimationBitrnap: TBitrnap;


Для того чтобы компоненг-аниматор смог корректно воспроизводить наш "шедевр" мультипликации, следует уведомить его о количестве кадров в ленге:

property AnimationCount: Integer;

 

Анимация числовых свойств, компонент TF/oatAnimation

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

property StartValue: Single; property StopValue: Single;

 

Анимация прямоугольной области, компонент TRectAnimation

Задача компонента TRectAnimation заключается в воздействии на значения свойств мargins или Раdding ·элеменга управления (напомню, что названные свойства опре­ деляют величины отступов краев дочернего объекта от границ клиентской области родительского контейнера). В результате мы получаем эффект изменения размеров анимируемого объекта относительно его контейнера-владельца.

Вновь нашими основными помощниками станут свойства

property StartValue: TBounds; property StopValue: TBounds;

но на этот раз они типизируются структурой твounds.

 

Анимация траектории, компонент TPathAnimation

Проявив немного терпения можно заставить объект двигаться по заданной траекто­ рии. Для анимации этого весьма занятного действа стоит воспользоваться услугами компоненга TPathAnimation. Траектория движения задается с помощью свойства

property Path: TPathData;

Свойство предоставляет доступ к классу TPathData, предоставляющему услуги по определению контрольных точек траектории.

Стоит отметить существование еще одного свойства:

property Rotate: Boolean; //по умолчанию false

которое в состоянии true заставит вращаться анимируемый объект вокруг своей

оси.

Проиллюстрируем работу с анимированной траекторией на небольшом примере. Для этого нам понадобится помощь фигуры Circlel:TCircle, к которой следует подключить компоненг PathAnimationl:TPathAnimation.


 

Настройте компонент PathAnimationl следующим образом:

    триггер Trigger активации анимации переведите в состояние IsVisiЫe=true

(анимация включена, ·если объект виден);

    продолжительность анимации Duration=б;

   активируем автореверс AutoReverse=true;

   выберем  синусоидальную  интерполяцию   Interppolation=itSinusoidal.

Точки траектории заполним в коде программы, для этого подойдет событие созда­ ния формы (листинг 18.2). В качестве контрольных точек мы выберем четыре угла родительского контейнера, которому принадлежит окружность Circlel:тc�rcle.


with PathAnimationl do

Ьegin

Stop; Circlel.Position.Point:=PointF(0,0);  Path.Clear; Path.MoveTo(PointF(0,0));

Path.MoveTo(PointF((TControl(Circlel.Parent) .Width  -

Circlel.Width)/2,0));

?ath.MoveTo(PointF(TControl(Circlel.Parent) .Width  -

Circlel.Width,O)); Path.MoveTo(PointF(TControl(Circlel.Parent) .Width-Circlel.Width,

TControl(Circlel.Parent) .Heig:it-Circlel.Height));

Path.MoveTo(PointF((TControl(Circlel.Parent) .Width -Circlel.Width)/2, TControl ('Circlel. Parent) .Height-Circlel.Height) ) ;.

Path.MoveTo(PointF(O,TControl(Circlel.Parent) .Height-Circlel.Height)); Path.MoveTo(PointF(0,0));

Path.ClosePath;

Start;

end;

 

После старта приложения окружность начнет свое движение вдоль границ контей­ нера.


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

Реализуемую в рамках FМХ. Media работу с мультимедиа можно разделить на два направления. Первое из них нацелено на воспроизведение аудио- и видеофайлов, второе посвящено захвату аудио- и видеопотоков, поступающих от аппаратных устройств компьютера или их программных эмуляторов.

 

Воспроизведение мультимедиа

С целью воспроизведения мультимедийных данных создателями FireMonkey разра­ ботана пара визуальных элементов управления - TMediaPlayer и TMediaPlayerControl. Их вы обнаружите на вкладке Additional палитры компонентов Delphi. Компо­ нент тмectiaPlayer обеспечивает доступ к файлам с данными мультимедиа, а TMediaPlayercontrol отвечает за вывод видеопотока на экран. Кроме того, в составе FMX имеется класс TMediaCodecManager, осуществляющий общее руководство над декодерами мультимедиа.

 

Менеджер кодеков TMediaCodecManager

Менеджер кодеков предназначен для управления имеющимися в системе кодеками мультимедиа. По умолчанию при работе под управлением операционной системы Windows менеджер кодеков поддерживает аудиоформаты WМА, МРЗ, WАV и ви­ деоформаты AVI и WMV. Если приложение предназначено для OS Х, то вы полу­ чите доступ к звуковому кодеку МРЗ и видеокодекам и MOV, M4V, и МР4.


 


 

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

class function IsCodedExists(const AFileName: string): Boolean;

В единственный параметр метода передается имя файла или только расширение имени (например, ".mрЗ"), если файл может быть воспроизведен- метод возвра­ тит true.

 

ВНИМАНИЕ!

Для своей работы компоненты TMediaPlayer и ТМediaPlayerControl используют имеющиеся в вашей операционной системе кодеки. Поэтому качество воспроизведе­ ния мультимедиа в первую очередь определяется предустановленным программным обеспечением.

 

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

class procedure RegisterMediaCodecClass(const Extension, Description: string; MediaType: TMediaType; MediaCodecClass: TCustomМediaCodecClass);

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

Класс TMediaCodecМanager окажется весьма полезным 8 ТОЙ ситуации, когда ВЫ предполагаете предоставлять доступ к зарегистрированным файлам мультимедиа с помощью диалога открытия файлов. В таком случае метод класса

class function GetFilterString: string;

возвратит строку, отформатированную в соответствии с требованиями свойства Filter диалога TOpenDialog. Нам останется только инициализировать названное свойство компонента во время запуска приложения.

Если вам достаточно лишь узнать типы поддерживаемых файлов, то стоит восполь­ зоваться более простым методом

class function GetFileTypes: string;

Метод возвратит текстовую строку с допустимыми расширениями имен файлов, разделенными точкой с запятой.

При желании сведения о приемлемых расширениях имен файлов можно запраши­ вать отдельно для каждого из типа кодеков

ТМеdiаТуре = (Audio, Video);

для этого предназначены методы

class function GetFilterStringByТype(MediaType: TMediaType): string;

class function GetFileTypesByType(MediaType: TMediaType): string;


 

Проигрыватель TMediaP/ayer

и компонент TMediaP/ayerControl

Для построения приложения, способного воспроизводить аудио- и видеофайлы, следует разместить на форме проигрыватель тмectiaPlayer и его помощник - ком­ понент тмectiaPlayerControl, специализирующегося исключительно на отображении видеоряда (рис. 19.1).


Рис. 19.1. Проигрыватель на основе компонентов TMediaPlayer и TMediaPlayerControl

 

Работа мультимедийного приложения начинается с отправки имени подлежащего воспроизведению файла в свойство

property FileNarne: string;

проигрывателя тмectiaP1ayer. Если в системе предустановлен кодек, способный ра­ ботать с содержащимися в файле данными, то в этом случае в недрах компонента создается экземпляр класса тмеdiа, представляющий собой объектно-ориенти­ рованное воплощение мультимедийных данных, загруженных из файла. Доступ к экземпляру тмесtiа обеспечивает свойство

property Media: TMedia;  //только для чтения

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


 

prooedure Play;

procedure Stop;

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

property State: ТМediaState; //только для чтения TMediaState = (UnavailaЫe, Playing, Stopped);

отражающем текущее состояние проигрывателя TMediaPlayer.

Кроме старта и остановки воспроизведения TMediaPlayer предоставляет возмож­ ность изменять громкость звуковой дорожки

property Volume: Single; //диапазон от О до 1

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

property CurrentTime: TMediaTime;

Для того чтобы убедиться, что не достигнуто ли окончание мультимедийного фай­ ла, следует проверять состояние свойства

property Duration: ТМediaTime;

хранящего общую продолжительность файла.

 

ЗАМЕЧАНИЕ

Свойства CurrentTime и ouration ориентированы на тип данных TTimespan, пред­ ставляющий время как интервал, в котором один такт соответствует 100 наносекундам.

 

Если воспроизводится видеоряд, то обратившись к свойству

property VideoSize: TPointF; //только для чтения

мы сможем уточнить размер отображаемой картинки.

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

procedure Clear;

освобождающему задействованные ресурсы и удаляющему экземпляр TMedia.

Второй ингредиент приложения мультимедиа - компонент тмediaPlayercontrol просто отвечает за вывод видеоизображения на экран, для этого он подключается к проигрывателю при посредничестве свойства

property MediaPlayer:  ТМediaPlayer;

на этом настройки компонента заканчиваются - он готов отображать загруженное в медиаплеер видео.


 

Захват аудио- и видеопотока

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

    тcaptureDeviceManager отвечает за доступ к аппаратным устройствам;

    TAudioCaptureDevice предоставляет в распоряжение базовый функционал теку- щего аудиоустройства;

    тvideoCaptureDevice позволяет управлять текущим видеоустройством.

Кроме того, в составе библиотеки предусмотрен очень простой в использовании компонент-камера тcameracomponent, нацеленный на мобильные приложения.

 

Менеджер устройств TCaptureDeviceManager

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

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

property Current: TCaptureDeviceManager;

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

Число доступных мультимедийных устройств уточняется благодаря свойству

property Count integer;

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

property Devices[Index: Integer]: TCaptureDevice;

и в качестве ответа получить доступ к устройству и его описание в формате класса тcaptureDevice. Такая идея реализована в листинге 19.1, в коде которого мы собра­ ли полный набор текущих мультимедийных устройств, установленных в компью­ тере, и передали его в список Listвoxl: TListBoxl.

'       , t.1• ,...,,.," if;/ 1./1 1','.J/!$,;/'l!r  .t ,"''!,••1 ,                 elif:,1'           '.,'ilf.J/  , ;                      1 ' , ,,."'f I 17'-'!/l/t.h /; :,,r:., '?W-i','lrtт,,lii, ;;1,': ," л; ,.;,"

,               ,,,.,, !v,  , t/..,ti, :         ,j,,'  1tt•  ,                                       1 ,,' i1!1 '!                           ,                                     1,                                        r                                    ,'( l1t•,C'l'•l+f1!w/' v. )i,;'l!i!Jiif!'"J,ii/1.) \./ir•. )1J.'f'i //!!;,": 1;/,1,,,"1 .

var CDM      TCaptureDeviceManager;- CD             TCaptureDevice;

LBI     TListBoxitem; i         integer;

Ьegin

CDM:=TCaptureDeviceManager.Current;


 

for i := О to CDM.Count-1 do Ьegin

CD:=CDM.Devices[i];

LBI:=ТListBoxitem.Create(ListBoxl); ListBoxl.AddObject(LBI);

end; end;

 

Еще один способ захвата текущего аудио- и видеоустройства обеспечивают свойства

property DefaultAudioCaptureDevice:  TAudioCaptureDevice;  //ayдJIIO

property DefaultVideoCaptureDevice: ТVideoCaptureDevice; //видео

Если устройства отсутствуют, то свойства возвратят неопределенные указатели NULL, иначе в ваше распоряжение соответственно поступят экземпляры классов TAudioCaptureDevice и TVideoCaptureDevice, способные передавать в приложение мультимедийные данные (листинг 19.2).

!,:"........................,.. .......................················••'f------·"""'••..····..,--..............-...................,_.........,...............-...,....,......_............_.. .,,  _,.,....,,.... ·-··-........ .._,

' л...,.... 18.Z. lull8'f ---           

var     СОМ    TCaptureDeviceManager; АС     TAudioCaptureDevice; VC TVideoCaptureDevice;

Ьegin

СОМ:= TCaptureDeviceManager.Current; АС ·= CDM.DefaultAudioCaptureDevice; VC  ·= CDM.DefaultVideoCaptureDevice;

 

if Assigned(AC) and Assigned(VC) then Ьegin

//работаем с устройствами

end; end;

 

Для программистов, желающих получить доступ к строго определенному устрой­ ству, подходит метод

function GetDevicesByName(Name: string): TCaptureDevice;

В единственный параметр метода следует передать название аудио- или видеоуст­ ройства.

 

Захват потоков мультимедиа

В FireMonkey специалистами по захвату аудио- и видеопотоков считаются классы TAudioCaptureDevice и TVideoCaptureDevice. Оба класса построены на фундаменте абстрактного класса тcaptureDevice и поэтому вооружены идентичным базовым набором свойств и методов (табл. 19.1).


 

Таблица 19.1. Свойства и методы, унаследованные от класса тcaptureDevice

Cuadro de texto: Свойство/метод	Описание
property MediaType :  TMediaType;	Тип устройства (Audio или Video)
property Name : String;	Название устройства
property UniqueID: String;	Уникальный идентификатор устройства
property Description : String;	Дополнительное описание устройства
property State : TCaptureDeviceState;	Текущее состояние (Capturing или
Stopped)
property IsDefault : boolean;	В состоянии true сигнализирует, что это устройство по умолчанию
Procedure StartCapture;	Начать захват потока мультимедиа
Procedure StopCapture;	Завершить захват потока мультимедиа

 

 

Аудиозахват ТAudioCaptureDevice

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

property FilterString: String;

содержит перечень поддерживаемых расширений имен файлов (*.mрЗ; *.wav;

*.wma).

Второе свойство

property FileName : String;

определяет путь и имя к файлу, в который будет отправлен аудиопоток. Установив имя файла, нам остается лишь вызвать метод startcapture (), и файл начнет запол­ няться данными.

 

Видеозахват TVideoCaptureDevice

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

property Position: TDevicePosition;

TDevicePosition = (dpUnspecified, dpFront, dpBack);

Если камера оснащена вспышкой, то свойство

property HasFlash: Boolean;


 

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

property FlashМode: TFlashМode;

Предусмотрены три варианта настроек: автоматический (fmAutoFlash), вспышка отключена (fmFlashOff) или вспышка постоянно включена (fmFlashOn).

Наличие дополнительного освещения (предназначенного для устранения эффекта красных глаз при портретной съемке) проверяется свойством

property HasTorch: Boolean;

При положительном результате программист может установить режим этого осве­ щения

property TorchМode: TTorchМode;

Допустимых вариантов три: отключено (tmModeOff), включено (tmМodeon), автома­ тическое включение (tmМodeAuto).

Способ фокусировки камеры определит свойство

property FocusMode: TFocusMode;

TFocusMode = (fmAutoFocus, fmContinuousAutoFocus, fmLocked);

Качество фото- и видеоматериала, возвращаемого камерой, также подлежит на­ стройке, для этого предназначено свойство

property Quality: TVideoCaptureQuality; TVideoCaptureQuality = (vcPhotoQuality, vcHighQuality,

                                                   vcMediumQuality,  vcLowQuality);

(

В отличие от устройства аудиозахвата, выдающего непрерывный поток данных,

видеозахват осуществляется с некоторой периодичностью - покадрово. В тот мо­ мент времени как устройство видеозахвата (например, веб-камера) сформирует очередной фотоснимок, у нашего программного об'Ьекта ТVideoCaptureDevice гене­ рируется событие

property OnSarnpleBufferReady: TSampleBufferReadyEvent;

типизированное следующим образом

TSampleBufferReadyEvent = prooedure(Sender: TObject;

const ATime: TMediaTime) of oЬject;

Первый параметр события sender содержит ссьmку на камеру, а второй ATime -

номер такта времени.

Для того чтобы мы смогли перехватить полученный камерой снимок, в коде обра­ ботки события onsampleBufferReady () следует вызвать еще один метод устройства видеозахвата

prooedura SarnpleBufferToBitmap(const AВitmap: TBitmap;

const ASetSize: Boolean);

Снимок возвращается методом через параметр АВitmap, во второй параметр следует заносить значение true (это заставит метод самостоятельно настроить размеры снимка).


 

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


prooedure TForml.VideoBufferReady(Sender:  TObject;  const ATime:  int64);

var AВitmap :TBitmap;

VCO: TVideoCaptureOevice;

Ьegin

AВitmap:=TBitmap.Create(0,0); VCO:=Sender as TVideoCaptureOevice; VCO.SampleBufferToBitmap(AВitmap,true);

{дальнейшая обработка полученных растровых картинок

 

при желании можно сохранить файлы с фотографиями на диск} AВitmap.SaveToFile('c:\\photo'+IntToStr(ATime)+'bmp',nil);

end;

 

 

В  рамках  представленного  кода  мы  создаем  объект  растровой  графики твi tmap* АВitmap и передаем в него снимок с видеокамеры. Дальнейшие действия зависят от стоящих перед приложением задач, в простейшем случае снимки можно просто сохранять на диске компьютера.

Для того чтобы устройство видеозахвата смогло самостоятельно вызывать разрабо­ танный нами метод, следует подключить его в качестве обработчика события onsampleBufferReady () . Для этого достаточно добавить всего OДIJY строку кода в рассмотренный ранее листинг 19.4.


var     СОМ    TCaptureOeviceManager; VC TVideoCaptureOevice;

Ьegin

СОМ:= TCaptureOeviceManager.Current; VC := COM.OefaultVideoCaptureOevice; if (VC<>nil) then

VC.OnSampleBufferReady:=VideoBufferReady;

end;

 

 

Теперь, для получения видеопотока от камеры нам остается вызвать метод

Startcapture () устройства.


 

Камера TCameraComponent

Компонент-камера тcameraCornponent описан в модуле FМX.Media и предназначен для использования в мобильных приложениях. Компонент очень прост в обращении, и зачастую используется совместно с компонентом TActionList (см. главу 7), в кото­ ром вы найдете несколько заранее заготовленных команд для управления камерой.

Стоит отметить, что компонент позволяет выбрать фронтальную или основную камеру мобильного устройства

property Kind:  TCameraKind;

TCameraKind =  (ckDefault,  ckFrontCamera, ckBackCamera);

Включение камеры осуществляется свойством

property Active: Boolean;

После осуществления снимка генерируется событие

property OnSampleBufferReady: TSampleBufferReadyEvent; TSampleBufferReadyEvent = procedure(Sender: TObject;

const ATime: TMediaTime) of object;

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

procedure SampleBufferToBitmap(const AВitmap: TBitmap;

const ASetSize: Вoolean);

 

ЗАМЕЧАНИЕ

У камеры TCameraComponent вы обнаружите уже знакомые по классу видеозахвата TVideoCaptureDevice свойства Quality, HasFlash, FlashМode, FocusMode и TorchМode.


 

 

 

 

 

 

 

ГЛАВА 20

 

 

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

 

 

 

 

Если еще три десятилетия назад персональная ЭВМ для большинства из нас была невиданной диковинкой, к которой боялись даже прикоснугься, то сегодня перед компьютером не спасует даже ребенок. Почему так произошло? Неужели нынеш­ ние дети умнее детей, появившихся на свет четверть века назад? Надеюсь, что да. Но скорее всего отсутствие боязни перед электронной техникой объясняется про­ ще - современные компьютеры очень дружелюбны.

В первую очередь дружелюбие компьютера проявляется через развитый пользова­ тельский интерфейс. И с каждым десятилетием этот интерфейс приобретает новое качество. Судите сами. В середине ХХ века основным способом ввода данных бы­ ли перфокарты и перфоленты, полагаю, что об удобстве здесь можно даже не гово­ рить. Позднее появились клавиатуры, еще позднее - манипуляторы-мыши. Пер­ вые монохромные устройства отображения на электронно-лучевых трубках посте­ пенно сменились цветными мониторами, а сегодня они уже почти полностью вытеснены жидкокристаллическими дисплеями. Но прогресс не стоит на месте. Пользователи проявляют все возрастающий интерес к интеллектуальным устройст­ вам, обладающим сенсорными функциями ввода данных. Это в первую очередь многочисленные платежные терминалы, планшетные компьютеры, электронные доски и смартфоны. Как правило, перечисленные устройства не снабжены отдель­ ной клавиатурой или же возможности их клавиатуры сильно ограничены. Но это не беда, программисты научили эти устройства воспринимать естественный ввод или, говоря проще, жесты человека.

 

ЗАМЕЧАНИЕ

Под жестом (gesture) мы станем понимать произвольную геометрическую фигуру, на­ рисованную пользователем на устройстве сенсорного ввода с помощью электронного пера или просто пальцем.

 

Описание жеста

Большинство построенных на базе класса тcontrol элементов управления Delphi способно реагировать на некоторый набор заранее предопределенных жестов. Жест


 

 

282                                                                                                                        Глава 20


 

+--     sgiLeft=1

 

--+     sgiRight=2

1


 

sgiUpDown=13

 

 

sgiDownUp=14


У sgiCurlicue=25

···································

:               sgiDouЫeCurlicue=26

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

 

. . . . . . . . . . . . . . . . . . . . . . . . . ··································1-                    ····························

 
О


sgiUp=З

 

-           sgiRightLeft=16

 
!


+ -►• sgitLeftRigh=15


о) sgiCircle=27


sgiDown=4

 

sgiDouЫeCircle=28

 
1   ; ,Lel\., .....        ;u c . 17            ; m; ,  L 29

1..  .. ; ,.. .......F ;" . ,.... ...   .. ;s ; ;,o .;,hF,.

. . . . . . . . . . . . . . . . . ' . . . . . . . . \ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

 

...            ..  i o  Le 7.....      -         ..sg - n-Le L-on =-1.9 ..               ..    -hev n  -31......

 

< 

 
...L..               V


L

 
i  -  ght= .-. .  -           ..     - -Ri.ghtLong=20

,g;eenup=9            ---: ,g;saatohoШ•21          !


sg:iCсhev'ron"D=own=з32


г      ·6······.·······························································


 

 

 

 
sgiLeftDown=10


s iТriangle=22             :> sgiChevronRight=34


  J sgiRightUp=11

-i sgiRightDown=12


··································!···································

sgiSquare=23

··································,···································

 

sgiCheck=24


 

Рис. 20.1. Стандартные жесты и их константы

 

представляет собой некую геометрическую фигуру, хранимую в памяти в формате структуры тstandardGestureData (листинг 20.1), определение которой вы обнаружи­ те в модуле FМX.Gestures.


type TStandardGestureData = record

Points: TGesturePointArray; //массив точек TPoint, описывающих жест GestureID: TGestureID;                                         //идентификатор жеста

Options: TGestureOptions;          //опции


 

Deviation: Integer;                 //допустимое отклонение жеста от стандартного ErrorMargin: Integer;                                      //максимальное число ошибок

end;

 

 

Ключевое поле структуры - Points. Это динамический массив, хранящий коорди­ наты точек геометрической фигуры жеста. Второе поле содержит идентификатор жеста. В Delphi имеется более трех десятков предустановленных жестов (рис. 20.1). При анализе жеста учитывается не только его соответствие геометрической фигуре, но и ряд других характеристик. В частности, поле опций Options контролирует-на­ правление goDirectional, наклон goSkew и факт совпадения начальной и конечной точек goEndpoint.

 

Реакция на сенсорный ввод

В библиотеке FireMonkey реакция на сенсорный ввод обеспечивается на уровне класса TFrnxObject. Для того чтобы элемент управления приобрел способность реа­ гировать на предопределенные жесты, ему понадобится помощник - менеджер жестов, компонент TGestureмanager. Для подключения менеджера жестов к элемен­ ту управления необходимо сделать несколько шагов. Сначала следует обратиться к свойству

property Touch: TTouchМanager;

Свойство предоставляет доступ к инкапсулированному в элемент управления объ­ екту - менеджеру прикосновений (экземпляру класса ттouchМanager). В свою оче­ редь у менеджера прикосновений имеется свойство

property GestureManager: TGestureManager;

позволяющее ассоциировать с элементо управления интересующий нас менеджер жестов TGestureManager.

Процесс подключения компонента TGestureManager к форме проекта отражает эк­

ранный снимок Инспектора объектов (рис. 20.2). Обратите внимание на то, что воспользовавшись разделом Touch I Gestures I Standard, программист определяет, на какие именно жесты должен реагировать элемент управления. Для этого доста­

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

Для описания реакции на жест программисту следует воспользоваться событием потомков класса FМХ. Types. тcontrol:

property OnGesture: TGestureEvent;

type TGestureEvent  = procedure(Sender: TObject;

const Eventlnfo: TGestureEventlnfo; var Handled: Boolean) of object;

Событие генерируется в тот момент, когда пользователь (воспользовавшись сен­ сорным экраном, электронным пером или просто мышью) нарисует над поверх­ ностью элемента управления какую-то геометрическую фигуру. Основная задача обработчика события заключается в двух вещах:


 

 


284                                                                                                                                                         Глава 20

   провести анализ введенного жеста и найти ему наиболее точное соответствие среди жестов, имеющихся в распоряжении элемента управления (свойство

Touch);

   выполнить соответствующую жесту операцию.


Рис. 20.2. Присоединение менеджера TGestureManager к форме

 

Ключевой параметр собьпия - Eventinfo, именно он уведомляет нас о том, какая фигура бьша нарисована пользователем_. Параметр представляет собой запись TGestureEventinfo, объявление которой вы найдете в листинге 20.2. Установив по­ следний параметр события нandled в состояние true, мы уведомим систему, что жест в обработке более не нуждается.


type TGestureEventinfo = record GestureID: TGestureID; Location: TPointF;

Flags: TinteractiveGestureFlags; Angle: DouЫe;

InertiaVector: TPointF; Distance: Integer; TapLocation: TPointF;

end;

 

Назначение полей структуры TGestureEventinfo предложено в табл. 20.1.


 

Таблица 20.1. Описание полей записи  TGestureEventinfo

 

Поле записи

Описание

GestureID

Идентификатор жеста

Location

Координаты текущей точки на поверхности устройства ввода

Flags

Набор флагов (gfBegin, gfinertia, gfEnd}, доступных только в момент

ввода жеста

Angle

Угол движения электронного пера (курсора мыши, пальца пользователя) относительно координатных осей ус;тройства ввода

InertiaVector

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

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

Distance

Расстояние в пикселах между текущей (Location) и предыдущей точками

TapLocation

Местоположение начальной точки фигуры жеста

 

 

ЗАМЕЧАНИЕ

Стоит заметить, что значения идентификаторов стандартных жестов имеют положи­ тельные значения (от 1 и далее), значения идентификаторов пользовательских жестов всегда отрицательные (от -1 и далее).

 

Интерактивные жесты

Кроме стандартных жестов, предложенных разработчиками FireMonkey, приложе­ ние способно реагировать и на многоточечные касания (multi-touch). Для этой цели стоит в Инспекторе объектов развернуть свойство Touch и изучить вкладку InteractiveGestures. Среда проектирования позволит нам подключить 5 дополни­ тельных жестов (табл. 20.2).

 

+

 
Таблица  20.2.  Интерактивные  жесты  FМХ. Types. TinteractiveGesture

 

 

Значение

 

Описание

Точек касания

 

Win

osx

iOS и Android

igZoom

Увеличение

2

+

+

+

igPan

Прокрутка

1

+

+

+

igRotate

Вращение элементов польза- вательского интерфейса

2

+

+

..

igTwoFingerTap

Касание двумя пальцами

2

+

 

+

igPressAndTap

Касание и нажатие

2

+

 

+

idDouЫeTap

Двойное касание

2

 

 

+

idLongTap

Долгое нажатие

 

 

 

+


 

 



286                                                                                                                                                          Глвт, 20

 

Пример обработки стандартных жестов

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

Для нашего примера понадобятся новый проект и компонент TGestureManager, ко­ торый следует                подключить   к форме    Forml с     помощью   свойств   тouch - GestureМanager. Отметьте те жесты, на которые должна реагировать форма (см. рис. 20.2). Разместите на форме метку Labell, этот компонент проинформирует нас о жесте пользователя.

Собственно обработка жеста будет осуществлена в рамках события OnGesture ()

единственной формы проекта (листинг 20.3).


prooedure TForml.FormGesture(Sender: TObject;

conet Eventinfo: TGestureEventinfo; var Handled: Boolean);

Ьegin

if Eventinfo.GestureID>-1 th8n

LaЬell.Text:=IntToStr(Eventinfo.GestureID) elae Labell.Text:= '?'

end;

 

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

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


Рис. 20.3. Экранный снимок формы распознавания жестов


 

 

 

 

 

 

 


ГЛАВА 21

 

 

lnterBase ToGo

 

 

 

 

Давным-давно, в середине 90-х годов прошлого века при появлении на свет самой первой версии Delphi (в те времена еще компании Borland) о новой среде проекти­ рования в первую очередь говорили как о превосходной платформе для разработки настольных и клиент-серверных приложений для баз данных (БД). Спустя почти 20 лет Delphi не только не угратила своих позиций в этом сегменте компьютерного рынка, но и существенно нарастила свои возможности. Судите сами, Delphi ХЕ5 позиционируется как среда проектирования, поддерживающая самые распростра­ ненные и успешные системы управления базами данных (СУБД), такие как Oracle, Informix, Microsoft SQL Server, DB2, Sybase, MySQL, Fireblrd, PostgreSQL и конеЧt­ но же собственное дeтищe-InterBase Server.                                                           r

Разработка приложений БД в Delphi - весьма интересная и многогранная тема; достойная отдельной книги. К сожалению, сейчас автор вынужден ограничить­ ся всего одной главой, знакомящей читателя с настольной базой данных InterBase ToGo. Излагая материал, будем исходить из предположения, что читатель хорошо знаком с процессом разработки Бд, работал с сервером InterBase и имеет хотя бы начальное представление о проектировании приложений с помощью ком­ понентов dbExpress.

 

ВНИМАНИЕ/

Для работы с базами данных lnterBase в Delphi предусмотрены специализированные компоненты lnterBase Express. Если в вашей поставке Delphi ХЕ4 они отсутствуют, то обратитесь к ссылке http;//cc.embarcadero.com/item/29408, перейдя по которой вы сможете скачать архив с этими компонентами.

 

Механизм управления данными InterBase ToGo предназначен для создания про­ стейших однопользовательских БД, для функционирования которых совсем не тре­ буется программное обеспечение сервера InterBase, в простейшем случае окажется достаточным воспользоваться единственной библиотекой. Это кроссплатформен­ ная система, способная работать под управлением Windows, OS Х, Linux и Solaris и не требующая никакого администрирования!



288                                                                                                                                                         Глава 21

 

ЗАМЕЧАНИЕ

В Delphi ХЕ4/ХЕ5 механизм lnterBase ToGo представлен двумя платформами: полно­ функциональной ToGo и упрощенной IBLite (предназначенной для встраивания в мо­ бильные приложения IOS и Android). Упрощения сводятся к ограничению у IBLite раз­ мера БД (до 100 Мбайт), отсутствию механизма шифрования,·невозможностью под­ держки сервисов API. Во всем остальном и первая, и вторая платформы основаны на идентичной модели разработки, поэтому с точки зрения программиста отличия между ними незначительны.

 

Соединение с БД TSQLConnection

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

 

ЗАМЕЧАНИЕ

Для создания схемы и объектов базы данных lnterBase ToGo проще всего воспользо­ ваться консолью управления СУБД IBConsole, которую вы найдете в папке c:\Program Files (x86)\Embarcadero\RAD Studio\n.n\lnterBaseXEЗ\Ьin\. Благодаря интуи­ тивно понятному интерфейсу утилита окажется полезной даже для начинающего про­ граммиста.

Работа с компонентом начинается с выбора драйвера, для этого предназначено свойство

property DriverName:·string;

Драйвер определит, какие именно библиотеки должны использоваться для обслу­ живания специфи<rной СУБД и для клиентской стороны, в нашем случае (рис. 21.1) мы воспользуемся драйвером IВLite/ToGo.

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

property Params: TStrings;

В результате среда программирования вызовет редактор соединения, позволяющий назначить его параметры и протестировать их корректность. Список параметров зависит от конкретной реализации сервера, но в самом общем случае нам прихо­ дится определиться с именем хоста HostName, на котором развернут сервер, именем базы данных Database, именем UserName и паролем пользователя Password.

 

Управление соединением

Разобравшись с базовыми свойствами компонента, обсудим его главное назначе­ ние - процесс установки соединения. Компонент TSQLConnection обладает парой методов, осуществляющих подключение к БД и отключение от БД

procedure Open; . · procedure Close;


 

lnterВase ToGo                                                                                                                                               289

W Dat.iМodu Unitp11,


 

 

 

: .: .: .: ..S..Q.


 

 

l. .1..:.: ': .: ..:; .: ..


 

 

Object !nspector

'5QLConnectioo1 TSQLConn ction

jProper-lie-s'I=Ev=enl=:s1----------------- ==========-!

_=b

 
· i Comected ji:JFalse ----· Comecliarl'

8 Qrtve          t181..ih!/ToGo

=:::... ·

sysdЬa


J

 
Dl!Jegateti

Delegatee

Кet!peonne<1[:q•тrue

rtJ UVl!Bindln.o Uvi!l!indings Designer

PROCJ'ERFORМERJ,IOAТE:                    1 Loacl>ar  '{CjFalse


o:;i:::::::::::


h.oow,r tr] filfse

n1


1-       !SQtcamюo

!Pari!lmS  !(ТStnnos)


Tl!blesщ,e ([l:sTable,l:sVl

 
PR.OC_Pl:RFaМ'RJ)EU:ТE :


Iтао        ;о             ew]


•••   61: 18        lnlert


·  par-tzrs Reloed mm«llan pвrametzrs

1mgp.-.,,


 

Alsnown

 

Рис. 21.1. Подключение драйвера к компоненту TSQLConnection

 

Тот же результат достигается за счет обращения к свойству

property_Connected: Boolean;

Кроме того, по состоянию connected можно судить о факте соединения с сервером.

.

 
Простейший пример подключения к файлу БД МUSIC.GDB, расположенному в том же каталоге, что и исполняемый ехе-файл, представлен в листинге 21.1. В ключах параметров Params указываются все тонкие настройки сервера и регистрационные данные пользователя.


var FileName:string;

Ьegin

FileName:= GetCurrentDir+TPath.DirectorySeparatorChar+'MUSIC.GDB';

if TFile.Exists(FileName} then Ьegin

SQLConnectionl.LoginPrompt:=false;

SQLConnectionl.Params.Values['HostName'] :='localhost'; SQLConnectionl.Params.Values['DataBase'] :=FileName;


 


290                                                                                                                                                          Глввв 21

 

SQLConnectionl.Connected:=true;

end else raise

Exception.Create('Фaйл '+TPath.GetFileName(FileName)+' не найден!');

end;

 

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

С процессом установки и разрыва соединения связан классический (для всех ком­ понентов Delphi, отвечающих за подключение к Бд) перечень событий (табл. 21.1).

 

Таблица 21.1. События, связанные с соединением и разрывом соединения

 

Событие

Описание

property BeforeConnect: TNotifyEvent;

Генерируется перед установкой соединения

property AfterConnect: TNotifyEvent;

Генерируется после установки соединения

property BeforeDisconnect: TNotifyEvent;

Генерируется перед разрывом соединения

property AfterDisconnect: TNotifyEvent;

Генерируется после разрыва соединения

 

Если программная логика приложения требует постоянного удержания контакта с БД, даже в случае, если нет ни одного активного открытого набора данных, то проследите, чтобы свойство KeepConnection оставалось в состоянии true. Иначе

'  с закрытием последнего набора данных соединение будет разорвано.

 

Регистраци пользователя

Если для работы пользователя с БД необходимо осуществить ввод имени и пароля, то свойство

property LoginPrornpt: Boolean;  //по умолчанию true

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

 

ЗАМЕЧАНИЕ

По умолчанию административный доступ к lnterBase ToGo осуществляется под учет­ ной записью "sysdba" с паролем "masterkey".

 

Кроме того, для передачи в адрес СУБД имени и пароля пользователя также основных параметров соединения) можно задействовать событие

property OnLogin: TSQLConnectionLoginEvent;

type TSQLConnectionLoginEvent = procedure(Database: TSQLConnection;

LoginParams: TStrings) of oЬject;


lnterBвse ToGo                                                                                                          291

 

ЗАМЕЧАНИЕ

В череде событий компонента TSQLConnection событие OnLogin () генерируется сра­ зу после Beforeconnect (). Событие будет вь1звано только при условии, что свойство LoginPrornpt установлено в состояние true.

О текущем состоянии соединения можно судить по свойству

property ConnectionState:  TConnectionState;

Возможные варианты состояний вы найдете в табл. 21.2.

 

Таблица 21.2. Возможные значения TConnectionSta te

 

Состояние

Описание

csStateClosed

Соединение отсутствует

csStateOpen

Компонент соединен с БД

csStateConnecting

Процесс соединения инициирован, но еще не завершен

csStateExecuting

Компонент отправил в адрес СУБД инструкцию SQL, и она выпол-

няется

csStateFetching

Компонент получает информацию от сервера

csStateDisconnecting

Момент разрыва соединения, но оно еще не завершено          .

 

 

Управление подчиненными наборами данных

Компонент TSQLConnection способен оказывать влияние на подключенные к нему компоненты-наборы данных. Весь массив этих элементов управления хранится в свойстве.

property DataSets[Index: Integer]: TCustornSQLDataSet;

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

property DataSetCount: Integer;

Для закрытия всех открытых наборов данных достаточно вызвать процедуру.

procedure CloseDataSets;

 

Управление транзакциями

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

property TransactionsSupported: LongBool;

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

property MultipleTransactionsSupported:  LongBool;


 

Для старта новой транзакции обратитесь к процедуре

prooedure StartTransaction(TransDesc: TTransactionDesc);

Завершение транзакции с сохранением результатов работы обеспечит процедура

procedure Commit(TransDesc: TTransactionDesc);

Откат транзакции

procedure Rollback(TransDesc: TTransactionDesc);

 

В качестве параметра во все три метода передается одна и та же запись ттransactionDesc. Эта структура позволяет идентифицировать транзакцию и опре­ делять ее основные характеристики.

type TTransisolationLevel  =  (xilDIRTYREAD,  xilREADCOММITTED,

xilREPEATAВLEREAD, xilCUSTOM);

TTransactionDesc = packed record


 

 

 

 

 

end;


TransactionID GlobalID IsolationLevel Customisolation


LongWord; //уникальный идентификатор транзакции LongWord; //глобальный идентификатор для ORACLE TTransisolationLevel;//ypoвeнь  изоляции LongWord; //пользовательский уровень изоляции


Поля с идентификаторами транзакций заполняются системой сразу после старта транзакции, поэтому на долю программиста остается только настройка уровня изо­ ляции.

Во время выполнения транзакции свойство

property InTransaction: Boolean;

 

переходит в состояние true.

 

Выполнение SQL-инструкций

При острой необходимости компонент TSQLConnection вполне способен самостоя­ тельно отправить в адрес сервера команду SQL. В простейшем случае (когда команда не содержит параметров) воспользуемся методом

function ExecuteDirect(const SQL: string): Integer;

 

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

function Execute(const SQL: string, Params: TParams): Integer; overload; function Execute(const SQL: string, Para'ms: TParams,

var ResultSet: Object): Integer; overload;


 


Информирование о БД

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

 

Таблица 21.3. Информационные методы компонента TSQLConnection

Cuadro de texto: Метод	Описание
procedure GetTaЫeNames(List: TStrings; SystemTaЬles: Boolean = False);	Метод предоставит список имеющихся в БД таблиц. Перечень таблиц будет передан в параметр List. Если список должен содержать информацию о сие- темных таблицах, то установите в true второй параметр процедуры
prooed:ure GetFieldNames(const TaЫeName: String; List: TStrings);	Процедура построит список полей таб- лицы TaЬleName и направит его 'в па- раметр List
procedure Get�ndexNames(const TaЬleName: string; List: TStrings);	Список индексов таблицы TaЬleName
передается в параметр List
procedure GetProcedureNames(List: TStrings); overload;	Метод построит список хранимых про- цедур
procedure GetProcedureParams( ProcedureName: string; List: TProcParamList); overload;	Получение информации о параметрах хранимой процедуры
prooed:ure GetProcedureParams( ProcedureName: string; PackageName: string; List: TProcParamList); overload;	Получение информации о параметрах хранимой процедуры Огасlе
procedure GetProcedureParams( ProcedureName: string; PackageName: string; SchemaName: string; List: TProcParamList); overload;

 

Совместно с методом GetTaЬleNames () трудится свойство

property ТаЫеSсоре: TTaЬleScopes; //по умолчанию tsTaЬle и tsView

type TaЬleScope = (tsSynonym,

tsSysTaЬle,        {системные таблицы} tsTaЫe,                             {обычные таблицы} tsView);                             {представления}

оно определяет тип таблиц, который может увидеть компонент.

 

Набор данных TSQLDataSet

Компонент TSQLDataset фактически объединяет в себе функциональные возможно­ сти триумвирата компонентов - таблицы, запроса, хранимой процедуры. Благода­ ря такой самодостаточности компонента TSQLDataSet необходимость в использо­ вании в наших проектах TSQLTaЫe, TSQLQuery и TSQLStoredProc при построении любого проекта dbExpress можно поставить под сомнение.


 

ВНИМАНИЕ!

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

Работа с компонентом начинается с подключения его к БД (точнее говоря, к ком­ поненту TSQLConnection) с помощью свойства

property SQLConnection: TSQLConnection;

Наличие свойства

property CornmandType: TSQLComrnandType;

TPSComrnandType = (ctUnknown, ctQuery, сtТаЫе, ctStoredProc,

ctServerMethod,

ctSelect, ctinsert, ctUpdate, c�Delete, ctDDL);

позволяет нам применять TSQLDataSet в роли таблицы, запроса или хранимой про­ цедуры.

Например, установив свойство CornmandType в состояние ctQuery, мы превращаем компонент в запрос, работающий на основе SQL. В этом случае щелчок по свой­ ству

property CornmandText: string;

в Инспекторе объектов вызывает редактор запросов (свойство ComrnandType=ctQuery), который окажет помощь при проектировании простейших инструкций. Возможно­ сти редактора далеко не безграничны, но, по крайней мере, он позволит начинаю­ щему разработчику не перепутать названия таблиц и полей (рис. 21.2).


Рис. 21.2. Встроенный редактор SQL


 

Немного разовьем идею использования TSQLDataset в качестве запроса, возвра­ щающего строки таблицы. В качестве основы возьмем таблицу perfonner из БД, предназначенной для хранения данных "Исполнитель- альбом" (рис. 21.3).

 

ЗАМЕЧАНИЕ

При изучении процесса создания приложения БД вместо этого примера можно вос­ пользоваться демонстрационной базой данных dbdemos.gdb, которую вы найдете в папке C:\Users\PuЫic\Documents\RAD Studio\11.0\Samples\Data\.

Cuadro de texto: 	
РК	concert id


FK1	
concert concertdate performer_id

Рис. 21.3. Концептуальная модель БД "Исполнитель -  альбом"

 

-

 
Присоедините набор данных Perfoпner_select: TSQLDataSet к базе данных (для это­ го понадобится свойство SQLConnection). Воспользовавшись свойством commandText, передайте в компонент текст запроса: Select * from perfoпner 6rder Ьу perfoпner. Убедитесь, что свойство CommandType установлено в состояние ctQuery. Разместите на форме компонент lbxPerfoпners :TListBox, именно в него мы направим результа­ ты запроса. Все остальное вы найдете в листинге 21.2.

 

.                            ,... ..,,.,.                                               - -·-

 

var    LBI:TListBoxltem;

Ьegin

lbxPerfonners.Clear; //очистка списка

with Perfoпner select do

Ьegin

Open; //открываем запрос

while not eof do //пока не прочитаны все строки

Ьegin

//создаем элемент списка с данными LBI:=TListBoxltem.Create(lbxPerfoпners);

LBI.Text:= FieldВyName('perfoпner') .AsString;         //исполнитель LBI,Tag:= FieldВyName('perfoпner_id') .Aslnteger; //кточ

//----- Пока не снимайте комментарий с этих строк!

//                      LBI.OnClick:=PefoпnersitemClick;


//-----                            см. листинг


,21.5


lbxPerfoпners.AddObject(LBI);//дoбaвим элемент в список Next; //переход к очередной строке

end;

Close;  //закрываем запрос

end;

end;


 

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

Если команда SQL основана на инструкции SELECT, то для выполнения запроса используют унаследованный от далекого предка TDataSet метод Open (). Во всех остальных случаях задействуем метод                                                                  '

function ExecSQL(ExecDirect: Boolean        False): Integer;

Передав в параметр ExecDirect значение true, мы просигнализируем СУБД, что наша команда не нуждается в предварительной подготовке.

Большинство событий компонента TSQLDataset получено в наследство (правда, в весьма ограниченном виде) от класса TDataset. Компонент способен реагировать на следующие события: подключение к таблице BeforeOpen () и AfterOpen (); отключение от таблицы BeforeClose () и AfterClose ( J; перемещение по запи­ сям BeforeScroll () и AfterScroll (); обновление данных BeforeRefresh () и AfterRefresh () ; работа с вычисляемым полем OnCalcFields () .

Кроме того, у компонента TSQLDataSet имеются четыре собственных обработчика событий. Все они предназначены для осуществления анализа отправляемых на вы­ полнение инструкций SQL. Выполнение команды INSERT возможно отследить с по­ мощью события:

property ParseinsertSql: TParseinsertSqlEvent; TParseinsertSqlEvent = procedure(var Field.Names: TWideStrings;

SQL: UnicodeString;

var BindAllFields: Boolean;

var TaЬleName: UnicodeString) of object;

Здесь: Field.Names - имена полей, задействованных при выполнении инструкции; SQL - текст инструкции; BindAllFields - признак, надо ли задействовать все поля; TaЬleName -  имя таблицы.

Команды SELECT, UPDATE и DELETE обслуживаются событиями

property ParseSelectSql: TParseSqlEvent; property ParseUpdateSql: TParseSqlEvent; property ParseDeleteSql: TParseSqlEvent;

TParseSqlEvent = procedure(var Field.Names: ТWideStrings;

SQL: UnicodeString; var TaЬleName: UnicodeString) of object;

 

Хранимая процедура TSQLStoredProc

Хранимая процедура TSQLStoredProc специализируется на вызове заранее подготов­ ленных на стороне сервера процедур. При подготовке к работе компонент следует соединить с базой данных, поэтому в процессе создания проекта самым первым шагом программиста должно быть подключение к TSQLConnection с помощью свой­ ства SQLConnection.


 

Имя хранимой процедуры передается в свойство:

property StoredProcName: string;

Если свойство ParamCheck компонента находится в состоянии true, то при измене­ нии имени процедуры компонент самостоятельно обращается к базе данных и со­ бирает сведения о параметрах процедуры и заносит их в свойство Params.

Для принудительной предварительной подготовки процедуры к выполнению на стороне сервера можно вызвать функцию:

function PrepareStatement(

var  RecordsAffected: Integer): TCustomSQLDataSet;

Впрочем, вместо этого метода разработчики Delphi рекомендуют обращаться к свойству Prepared. В этом случае гарантируется, что сервер не станет без всякой на то надобности перекомпилировать уже давно готовую к работе.процедуру.

Входные параметры процедуры передаются в свойство

property Params:  TParams;

Это же свойство служит хранилищем для возвращаемых в результате выполнения процедуры параметров.

Если процедура не основана на инструкции SELECT (не возвращает результирующе­ го набора данных или возвращает результаты в виде выходных параметров), то для ее вызова обращаемся к методу

function ExecProc: Integer;

в противном случае надо использовать метод Open () либо установить в true свойст­ во Active.

Вернемся к нашему примеру "Исполнитель -  альбом". Для вставки новой записи с исполнителем в БД создадим хранимую процедуру proc_performer_insert с един­ ственным входным параметром aPerformer и одним возвращаемым значением new_id - первичным ключом только что вставленной записи (листинг 21.3).


create procedure proc_performer_insert(aPerformer varchar(S0)) returns (new_id int)

as Ьegin

insert into performer (performer)

values

( :aPerformer);

select max(performer_id) from performer into :new id;

end

 

Для того чтобы обратиться к этой процедуре из приложения FireMonkey, можно вновь воспользоваться услугами TSQLDataset или отдать свои предпочтения спе-


 

циализированному компоненту TSQLStoredProc. Для того чтобы разнообразить наш код, пойдем по второму пути.

Перенесите на модуль данных компонент TSQLStoredProc, соедините его с базой данных с помощью свойства SQLConnection и передайте в свойство StoredProcName название хранимой процедуры нашем случае PROC_PERFORМER_INSERT). Убедитесь в корректности подключения, если все сделано верно, то после щелчка по свойству Params перед вами появится окно с параметрами хранимой процедуры.

 

 

 

 

 

'::::::::

=

 
: :' : SQlCannec1icn1 : ; ; : '

 

 

 

 

 

 

 

 

 

 

 

 

 

_D«SOtT

1Тао ,.....,._"-'=    О

Edtor... Exeaite 8lnd Vl!iully••• Add SlndSou-ce

 

 

 

 

 

Рис. 21.4. Подготовка компонента TSQLStoredProc к работе

 

Порядок использования компонента TSQLStoredProc в проектах FireMonkey раскры­ вает листинг 21.4, в нем мы вносим нового исполнителя в БД и создаем соответст­ вующий ему элемент в списке lbxPerfoпners:TListBox.


var id:integer; LBI:TListBoxitem; s:string;

Ьegin

if     InputQuery('Hoвый исполнитель', 'Исполнитель',s)  then

Ьegin

PROC_PERFORМER_INSERT.ParamВyName('aPerfoпner') .Value:=s.Trim; PROC_PERFORМER_INSERT.ExecProc;


 

id:=PROC PERFORМER INSERT.PararnВyName('new_id').Value; LBI:=TListBoxitem.Create(lbxPerfoпners);   LBI.Text:=s.Trim;

LBI.Tag:=id;

r          lbxPerfoпners.AddObj ect ( LBI) ;

end; end;

 

В соответствии с классическими традициями 'наборов данных Delphi доступ к пе­ речню параметров обеспечивает свойство

property Params: TParams;

Для того чтобы компонент самостоятельно регенерировал список параметров (име­ на, типы данных и т. д.) при вводе или любом изменении SQL команды в поле CormnandText,yбeдитecь, чтобы свойство

property ParamCheck: Boolean; было установлено в состояние true.

ВНИМАНИЕ!

При работе с хранимыми процедурами старайтесь объявлять параметры в той же по­ следовательности, в какой они описаны на серверной стороне.

 

Запрос TSQLQuery

Компонент-запрос специализируется на отправке серверу инструкции на языке SQL. Организация работы с компонентом не сильно отличается от работы с одно­ типными компонентами из других технологий доступа к данным. После подключе­ ния к БД с помощью свойства SQLConnection следует определить текст запроса:

property SQL: TStrings;

Запрос может редактироваться как во время проектирования Бд, так и во время вы­ полнения программы. Если запрос содержит параметрьi, то доступ к ним осуществ­ ляется с помощью сtюйства

property Params:TParams;

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

property ParamCheck; установлено в true.

Если запрос работает с инструкциями INSERT, UPDAТE и DELETE, то для отправки SQL­

зaпpoca к серверу применяют метод ExecSQL ( ) , запрос, нацеленный на выборку данных (инструкция SELECT), активируется методом Open ().

Вновь возвратимся к нашему примеру "Исполнитель - альбом" и научим наше приложение отображать список альбомов после выбора пользователем элемента


 

с названием исполнигеля (щелчок по элементу списка lbxPerformers). Для этого разместиге на модуле данных компонент TSQLQuery '(не забыв подключигь его к БД), перенесите,на главную форму проекта еще один список TListBox и переиме­ нуйте его в lbxConcerts. Так как элементы списка lbxPerformers создаются динами­ чески в момент сбора сведений об исполнигелях, нам придется подключать обра­ ботчик события onclick () вручную. Для этого в секции частных объявлений с главной формой проекта объявим процедуру

procedure PeformersitemClick(Sender: TObject);

Код процедуры вы найдете в листинге 21.5.

 

Лмстмнr 21.S. Со6ытие..щеnчок no амменту сnмс• 1.Ьвhl:f'_..

,.,...:. ······:iil .. ........ ..                  ----······"' - ....... ,.t-,_,.,  ·•······ '""'

procedure TfпnМain.PeformersitemClick(Sender: TObject);

var Performer_id :integer; ConcertLBI:TListBoxitem; lb:TLabel;

Ьegin

Performer_id:=TListBoxitem(Sender) .Tag; //узнаем первичный ключ

 

if Performer id>0 then with SQLQueryl do Ьegin

SQL.Clear; //готовим новый текст запроса SQL.Add('SELECT * FROM CONCERT WHERE'); SQL. Add ( ' PERFORМER_ ID=' +

Performer_id.ToString()+'  ORDER ВУ  CONCERTDATE');

Open; //вьmолняем запрос lbxConcerts.Clear; //очищаем список

while not eof do //перебор записей, возвращенных запросом

Ьegin

ConcertLBI:=TListBoxitem.Create(lbxConcerts); lbxConcerts.AddObject(ConcertLBI); ConcertLBI.Text:= {дата}

FormatDateTime ( 'уууу',

FieldВyName ( 'concertdate') .AsDateTime); ConcertLBI.Tag:=FieldByName('concert_id')  .Asinteger;    //ключ

 

lb:=TLabel.Create(ConcertLBI); //метка для названия альбома lb.Parent:=ConcertLBI;

lb.Position.X:=40; · lb.Text:=FieldВyName('concert') .AsString;

Next; //к следующей записи

end;


 


 

 

 

end;


Close;       //закрываем набор данных

end;


 


Для того чтобы код стал работоспособным, следует подключить процедуру к обра­ ботчику события OnClick () элементов списка lbxPerformers. Это нам придется делать в динамическом режиме, поэтому возвратитесь к листингу 25.2 и снимите комментарии со строки

//LBI.OnClick:=PeformersitemClick;

 

Выпуск приложения

Если вы имеете хотя бы небольшой опыт разработки клиентских приложений баз данных, работающих под управлением InterBase, то наверняка знаете, что при пе­ реносе исполняемого файла на компьютер заказчика в дистрибутив надо обяза­ тельно включить динамическую библиотеку gds32.dll. В названной библиотеке со­ средоточен весь необходимый функционал для доступа и обработки данных, об­ служиваемых сервером InterBase. В InterBase ToGo ситуация очень похожая, но на этот раз в папке с исполняемым файлом должна оказаться библиотекц ibtogo.dll (или ibtogo64.dll), если речь идет о Windows, библиотека liЬibtogo.dylib для OS Х и мобильной платформы iOS.

 

ЗАМЕЧАНИЕ

Разработчики клиентской библиотеки для проектов lnterBase ToGo утверждают, что интерфейсная часть iblogo.dll (iblogo64.dll, liblblogo.dylib) на 98% совпадает с интер­ фейсом gds32.dll, что существенно упрощает переносимость настольных и клиент­ серверных приложений.

Для включения необходимых файлов в дистрибутив следует воспользоваться менеджером развертывания (Deployment Manager) приложения, для этого надо обратиться к элементу меню Project I Deployment среды проектирования. В комби­ нированном списке менеджера необходимо выбрать целевую платформу или вы­

брать универсальный элемент АН configurations -  АН Platforms. Затем щелкаем по кнопке Add Featured Files и в появившемся на экране окне с наиболее часто ис­ пользуемыми файлами отметить флажки (рис. 21.5) интересующих нас строк (в нашем случае InterBase ToGo и DBExpress InterBase Driver).

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

 

ЗАМЕЧАНИЕ

Если логика работы программы требует размещения ваших файлов в строго опреде­ ленных папках, то в окне Deployment следует отредактировать ячейку Remote Path. Путь можно указать раздельно для каждой из поддерживаемых платформ, например в IOS файл БД должен оказаться в папке Star1Up\Documents\.


 


 

 

 

 

 

LoalPalh

r:J S(ll!R!D

е

r.'.:j С:\


[)epo'jml!nt-ТoGopl,цlc!  ..                         

.... -.      -          L4J_ - - ;11ons.м!-t1,inм

 

f.11 1ntere.se e1en1

1,;1-!!l]  l  ToGo


OSXЭ2'()@Ьug\

[.! OSX32'()м,ug\

l':'J S(JIIRВ)ISЮIR)._Э,

l':'J $(JIIRВ)l!ifDIR)"'8x3

'         t(IOS) \

, l':'J S(JIIRВ)lstDIR).............. ,S.

1 rJ t{l8RS)ISro]R)-,э.

'                    IR

i"-!!.!l!!!-.,.11!'!"-t


(t)- tiJ Wln32 (!} !!l] т,64

itl·     OSX32

1 !tl /OSSi!uator

: 1& ffj IOSOewJ!

1!),          Anctcid

m fJ'l! Deexpress 1n               Orlver

\Ь- [:J Deflфresc DIIZ Driv« iiJ. [!:! DIJE,q,rm Frdii'd Driver m - l!'.'!J оее-МySQL 0r1v«


!•,..... ------,r-41m       оеехрrен Orllde Oiiver

liJ {f! DВЕ"Р'- Informi• Drlver

°"""'       

 
===:a::z!ПE;:t],ф 1/f:J ов&р,ен МSSQ.. Drivtt

 '  i)  ·  11:Э  Dlll:"l)l"ess MSSQL9

 

 

Рис. 21.5. Подключение к проекту модулей, необходимых для работы lnterBase ToGo

 

 

ЗАМЕЧАНИЕ

Тематика разработки баз данных будет продолжена в главе 22, но на этот раз в кон­ тексте технологии живого связывания LiveBindings.


 

 

 

 

 

 

 

ГЛАВА 22

 

 

LiveBindings

 

 

 

 

Объем принципиально новых возможностей, появившихся в Embarcadero RAD Studio с выходом FireMonkey, без преувеличения можно сравнить с прорывом пер­ вых версий Delphi в середине 1990-х годов. В этой главе нам предстоит обсудить очередную новацию Embarcadero - технологию "живого связывания" LiveBindings, позволяющую программисту организовать передачу данных в рамках приложения между произвольным источником и объектом-получателем данных.

Несмотря на то, что собственно идея живого связывания нашла свое полноценное программное воплощение в FireMonkey, фундамент LiveBindings был заложен до выхода новой платформы - еще 2008 году. Именно тогда вышло в свет принци­ пиально усовершенствованное ядро RTТI (Run-time type information). Не станем перечислять положительные качества нового RTTI, отметим лишь, что с этого мо­ мента появилась возможность создать универсальную технологию, способную обеспечить взаимодействие между объектами приложения, строго говоря, без опо­ ры на конкретную операционную систему. Поэтому технология LiveBindings оди­ наково хорошо функционирует как в классической библиотеке VCL, так и в новой кроссплатформенной FireMonkey.

Что такое LiveBindings? Если кратко, то это технология взаимодействия двух и бо­ лее объектов. Связь может быть как односторонней, так и двусторонней. В качестве участника взаимодействия выступают два типа объектов: управляемые объекты (control objects) и источники данных (source objects). Обмен данными осуществля­ ется между свойствами объектов, а правила обмена определяются с помощью регу­ лярных выражений (expression). Что немаловажно, в выражения разрешается включать ссьmки на другие внешние объекты.

 

ВНИМАНИЕ/

Строго говоря, за связь между двумя объектами LiveBindings отвечает третий объект, например, экземпляр класса TBindExpression.

В LiveBindings предусмотрено более двух десятJ<ов классов, специализирующихся на обслуживании определенных типов связей, их можно разделить на 4 современ­ ных и одну устаревшую на сегодня ветвь:


 

1.    Ветвь Quick Bindings содержит группу специализированных классов, позво­ ляющих создавать живые связи практически без программирования. Например, класс TLinkControlToProperty позволяет связать элемент управления со свойст­ вом управляемого объекта так, что любые изменения в источнике данных не­ медленно отразятся в выбранном свойстве управляемого объекта. Ключевое достоинство классов Quick Bindings в том, что они поддерживаются визуаль­ ным дизайнером живых связей (LiveBindings Designer).

2.    Ветвь Binding Expressions включает классы TBindExpression и TBindExprrterns, контролирующие процесс взаимодействия между управляемым объектом и ис­ точником данных в соответствии с правилами, описанными в форме регулярных выражений. В простейшем случае связь организуется между двумя свойствами разных объектов.

3.    Тип связей Lists объединяет классы TBindList и TBindGridList. Задача указанных классов - обеспечить связь не просто между парой свойств, содержащих по одному атомарному значению, а между списками, хранящими большой объем данных.

4.    Тип связи Links включает классы TBindLink, твindListLink, TBindGridLink и твindPosition. Перечисленные классы позволят нам создавать ссылки между списками, сетками и другими участниками живой связи (обычно полями таб­ лиц). В отличие от классов, входящих в состав DB Links, классы Links требуют от программиста явным образом задать выражения, в которых должны быть оп­ ределены правила взаимодействия между полем и визуальным элементом управления.

 

ЗАМЕЧАНИЕ

В LiveBindings существует и устаревшее направление DB Links. Оно было предназна­ чено для построения клиентских приложений БД в первой версии FireMonkey (Delphi ХЕ2). В состав данного типа связей LiveBindings входят: твindDBEdi tLink, TBindDBTextLink, TBindDBListLink, TBindDBimageLink, TBindDBMemoLink, TBindDBCheckLink и твindDBGridLink. Каждый из перечисленных классов позволяет формировать связь между полем таблицы соответствующего типа и подходящим для обслуживания этого типа данных элементом управления, например твindDBEdi tLink с легкостью соединит текстовое поле и строку ввода TEdi t.

Палитра компонентов FireMonkey насчитывает несколько компонентов, обеспечи­ вающих функционирование технологии LiveBindings, среди них:

  список связей TBindingsList представляет собой контейнер для анонсированных выше экземпляров классов, осуществляющих связь LiveBindings;

   наблюдатель TBindScope, упрощающий организацию взаимодействия между визуальными элементами управления;

   компонент TBindSourceoв используется в проектах баз данных, он обеспечит взаимодействие между набором данных (любым потомком класса тoataset)_и другими компонентами;

   компонент TBindSourceoвx также используется в проектах баз данных, но на этот раз имеет жесткую специализацию - он нацелен исключительно на технологию доступа к базам данных dbExpress (DBX);


  для доступа к неструктурированным в виде БД данным задействуется компонент TAdapterBindSource, например указанный компонент способен работать с кол­ лекциями различных объектов;

,генератор TDataGeneratorAdapter  предназначен  для совместной  работы с

TAdapterBindSource;

   источник произвольных данных TPrototypeBindSource позволит программисту создать некоторый временный случайный набор данных, используемый для от­ ладки приложения, позднее вместо него можно использовать реальные данные;

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

 

Визуальный дизайнер

Продемонстрируем возможности визуального дизайнера LiveBindings на примере приложения FireMonkey с интерфейсом Metropolis. С этой целью воспользуемся элементом меню New I FireMonkey Metropolis UI Application и в окне выбора ти­ па приложения Delphi New FireMonkey Metropolis UI Application отметим шаб­ лон Grid Metropolis UI Application. В результате этих действий среда проектиро­ вания создаст приложение для Windows 8 с пользовательским интерфейсом Metropolis. Указанное приложение будет состоять из двух модулей: главной формы GridViewl.pas и дочерней формы DataViewl.pas. Главная форма обладает списком Listвoxl с некоторым перечнем элементов, щелчок по тому или иному элементу списка послужит командой для вызова формы детализации с более подробной информацией.

Разместите на главной форме проекта тестовый источник данных PrototypeBindSourcel:TPrototypeBindSource, который позволит смоделировать не­ который произвольный набор данных. Для того чтобы определить структуру гене­ рируемых данных, следует воспользоваться контекстным меню компонента и обра­ титься к пункту Add Field (Добавить поле). В ответ на это действие на экране ком­ пьютера окажется одноименный диалог добавления поля к набору данных Add Field, выберите в списке несколько произвольных полей (рис. 22.1) и нажмите кнопку ОК. Вновь созданные поля заполнят коллекцию

property FieldDefs: TGeneratorFieldDefs;

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

Сразу ограничим число записей, создаваемых в компоненте PrototypeBindSourcel, ,

для этого следует передать значение (допустим, число 5) в свойство

property RecordCount:  integer;  //по умолчанию -1

Отметим, что по умолчанию свойство принимает значение равное -1, и это указы­ вает на отсутствие ограничений.


306                                                                                                                                                          Глввв 22

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рис. 22.1. Имитация набора данных с помощью TPrototypeBindSource

 

Теперь нам следует вызвать на сцену визуальный дизайнер связей LiveBindings. Для этого (предварительно убедившись, что главная форма проекта активна) выбе­ рите пункт меню View I LiveBindings Designer. В результате на вашем дисплее

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

Продемонстрируем процесс создания живой связи. Для этого найдите на схеме прототип PrototypeBindSourcel и левой кнопкой мыши выберите в нем поле с име­ нем BitmapNamel. Удерживая кнопку мыши в нажатом состоянии, перетащите поле к списку ListBoxl и отпустите кнопку в тот момент, когда указатель мыши окажется над свойством Item.Text. В результате на схеме появится "стрелка", соединяющая выбранное поле и свойство (рис. 22.2). Данная "стрелка" визуализирует реальный объект-связь LinkFillControlToFieldl, в данном случае созданный на основе класса TLinkFillControlToField. В этом вы сможете убедиться, взглянув на Инспектор объектов. Обращение к Инспектору объектов является не только праздным любо­ пытством, нам еще следует изменить состояние свойства ListitemStyle, переведя его в режим MetropolisUI.

Обрати_те внимание на то, что после установки типа связи в состояние MetropolisUI

у представления  Listвoxl на схеме LiveBindings Designer заметно расширился


OЬjecl ln,pctor

, l.lmlfllConl:nll'fofleldl 11.Wfilleootr!JIТQF\eld

 

Tru11

l'alse

"Trve

·1

Quklcllindln\Js

Ustlloxl

 

 

 

ec:tlan f tnld!idlr«tiOnal

J

 

 

1

 
1lcGr (Т,-.,akGroups) PrototypdlndSouкeJ

 

, IRtmllpNillmel

(Тformatfxpresllloм)

 

,ormalfxpn!нioм)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рис. 22.2. Создание связи между полем данных и свойством компонента

 

 

перечень свойств. Воспользуемся этим и добавим к схеме еще пару связей:

Bi tmapl -  Itern. Icon И ColorsNamel -  .Itern. SubTi tle.

Переходим к дочерней форме проекта. В первую очередь (воспользовавшись пунк­ том меню File I Uses) подключим к дочерней форме модуль GridViewl .pas, что по­ зволит дизайнеру связей LiveBindings увидеть принадлежащий главной форме про­

екта прототип данных PrototypeBindSourcel. После этого (щелкнув правой кнопкой мыши по клиентской области LiveBindings Designer) во всплывающем меню выбе­ рем пункт Refresh Designer (Обновить Дизайнер). В результате к схеме (до сцх пор отражавшей лишь принадлежащие дочерней форме компоненты) добавится PrototypeBindSourcel. Для того чтобы подчеркнуть тот факт, что прототип принад­ лежит другому модулю, LiveBindings Designer окрасит его в оранжевый цвет. Нам осталось связать поля прототипа со свойствами соответствующих компонентов формы детализации так, как предложено на рис. 22.3.

В завершение нашего примера нам предстоит немного потренироваться в програм­ мировании. Возвращаемся к модулю главной формы и находим на ней компонент ListBoxl: TListвox, он нам поможет вызывать форму детализации. С этой целью


 

 

опишем событие onchange ( ) , генерируемое компонентом в момент выбора того или иного элемента (листинг 22.1).


prooedure TGridViewForm.ListBoxlChange(Sender: TObject);

Ьegin

if     (ListBoxl.Itemindex<>-1)      then

Ьegin

PrototypeBindSourcel.Itemindex:=ListBoxl.Itemindex; Application.GetDeviceForm('DetailView').Show();

end; end;

 


Рис. 22.3. Создание связи между прототипом и компонентами дочерней формы

 

Проект закончен. Теперь щелчок по элементу списка Listвoxl вызывает форму детализации с соответствующими данными.

 

LiveBindings в проектах баз данных

Еще с первых версий среда проектирования и язык Delphi предоставляли разработ­ чику ни с чем несравнимые (как по удобству, так и по скорости проектирования) возможности по построению проектов баз данных. Долгое время казалось, что уже достигнут предел совершенства и ничего более успешного в технологии разработки клиентских приложений БД точки зрения RAD сред) создать невозможно. Одна­ ко в FireMonkey компания Embarcadero превзошла саму себя, поставив LiveBindings на службу приложений БД. Чтобы подтвердить это утверждение, предлагаем написать демонстрационный проект БД в FireMonkey.

Чтобы не заставлять вас разворачивать на компьютере сложные СУБД, для пробно­ го проекта LiveBindings для БД предлагаю временно отказаться от Мае и написать


 

приложение FireMonkey исключительно для Windows. Более того, советую остано­ виться на распространенной настольной БД Microsoft Access и создать в ней одно­ табличную базу данных контактов (рис. 22.4). Предлагаемый пример - не догма, при желании вы можете выбрать любые другие альтернативные источники данных, например кроссплатформенную базу данных InterBase ToGo (см. главу 21), но сей­ час исключительно ради быстроты освоения идеи работы LiveBindings с БД реко­ мендуем ненадолго отклониться от генеральной линии.


Рис. 22.4. Определение полей таблицы контактов contacts

 

Создайте приложение FireMonkey НD Application и разместите на главной форме проекта следующие компоненты (рис. 22.5):

    таблицу  ADOTaЫel: ТАDОТаЫе;  воспользовавшись  свойством  ConnectionString,

сформируйте строку соединения с файлом БД, а в свойстве таЫеNаmе укажите

имя обслуживаемой таблицы;

    источник данных BindSourceDBl:TBindSourceDв; обратившись к свойству DataSet,

присоедините компонент к таблице ADOTaЫel;

   навигатор данных BindNavigatorl:твindNavigator; навигатор следует присоеди­ нить к наблюдателю BindSourceDBl, для этого предназначено свойство DataScope;

   контейнер   связей   BindingsListl:TBindingsList;


Рис. 22.5. Интерфейс приложения БД с компонентом TBindSourceDB


   строки ввода TEdit (по числу текстовых полей в таблице) и изображение

ImageControll:TirnageControl.

 

ЗАМЕЧАНИЕ

В современных версиях Delphi в распоряжении программиста появился компонент TBindSourceDB. По своей сути TBindSourceDB -  это два компонента в одном флако­ не: с одной стороны, TBindSourceDB способен работать с технологией LiveBindings, а с другой стороны, это замена источника данных TDataSource,  существовавшего с первых версий Delphi.

Установив свойство Active в состояние true, активируйте таблицу ADOTaЫel. Вызо­ вите контекстное меню компонента BindSourceDBl и найдите в нем пункт Bind Visually. В появившемся на экране визуальном дизайнере связей создайте связи между полями данных и соответствующими им строками ввода (рис. 22.6).


Рис. 22.6. Создание связей между полями данных и элементами управления

 

"Программирование" завершено - смело нажимаем клавишу <F9> и отправ: яем проект на компиляцию.

 

Binding Expressions -

связь с помощью выражений

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


 

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

Запускаем новый проект НD FireMonkey Application и размещаем на главной форме строку ввода TEdit, метку TLabel и список связей TBindingsList. В нашем примере метке отведена роль управляемого объекта - в ее свойство техt станут поступать данные из свойства техt управляющего объекта TEdit.

Все подготовител!>ные операции завершены, теперь нам следует осуществить свя­ зывание объектов. Распишем эту операцию по пунктам.

1.    Дважды щелкните по компоненту BindingsListl, в результате чего на экране компьютера будет отображен редl\КТОР связей.

2.    Дпя создания новой "живой связи" нажмите клавишу <lns> (рис. 22.7).

 


Рис. 22.7. Создание новой "живой связи" для компонента Labell

 

3.    В диалоге New LiveBinding выберите узел TBindExpression (это укажет среде проектирования на то, что мы намерены создать связь на основе выражения) и нажмите кнопку ОК (см. рис. 22.7).

4.    Найдите в Инспекторе объектов связь BindExpressionl: TBindExpression и пе­ рейдите на страницу свойств Properties. Теперь нам предстоит настроить ряд свойств связи (рис. 22.8):


ControlComponent:=Labell; ControlExpression:=Text;


//управляемый объект

//получатель -  заголовок


Direction:=dirSourceToControl;  //направление  связи "источник->элемент"


 


Managed:=true; SourceComponent:=Editl; SourceExpression:=Text;


//выражение активно

//источник  данных

//свойство источника данных


 

Alshown


 

Рис. 22.8. Настройка свойств связи TBindExpression

 

Для завершения задуманного нам осталось немного поработать за клавиатурой. Листинг 22.2 содержит обработчик события onchangeTracking () строки ввода. Это событие генерируется с каждым изменением содержимого компонента Editl.

... '!1"·····.. · ·--,,., ············--··-· ,...Nl"". ··1


-.··


.,..,_..,       


;,.. j


 

implementation

uses System.Bindings.Helper;

{$R*.frnx}

 

procedure TForml.EditlChangeTracking(Sender: TObject);

Ьegin

TBindings.Notify(Editl, 'Text');

end;

 

 

 



 

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

Labell.Text:=Editl.Text!

 

ЗАМЕЧАНИЕ

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

 

Класс TBindExpression

Повторяя предыдущий пример, вы наверняка заметили, что основным действую­ щим лицом первого приложения стал экземпляр класса твindExpression (модуль Data.Bind.Components). Именно TBindExpression взял на себя ответственность за ор­ ганизацию взаимодействия между двумя компонентами: объектом-источником и управляемым объектом. Дпя этого понадобилось лишь внести ряд настроек в свой­ ства объекта.

Ссылка на объект-источник заносится в свойство

property SourceComponent: TComponent;

Сведения об управлении окажутся неполными до тех пор, пока мы не опишем входное управляющее выражение в свойстве

property SourceExpression: string;

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

Ссылка на второго участника взаимодействия LiveBindings заносится в свойство

property ControlComponent: TComponent;

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

property ControlExpression: string;

Вновь заметим, что в простейшей ситуации здесь достаточно упомянуть название свойства, в которое поступят "живые" данные.

Направление потока данных между объектами определяется свойством

property Direction: TExpressionDirection;//dirSourceToControl

По умолчанию поток данных направлен от источника к управляемому объекту, од­ нако существуют еще два варианта направлений:

type TExpressionDirection =  (


dirSourceToControl, dirControlToSource, dirBidirectional);


//источник-> управляемый объект

//управляемый объект-> источник

//двустороннее взаимодействие



 

Для построения примера двустороннего взаимодействия от нас не потребуется осо­ бых трудозатрат. Разместите на форме два компонента ттrackBar. Выбрав любой из компонентов, создайте связь LiveBindings. Данные должны передаваться между свойствами Value (отвечающими за местоположение ползунка), а направление свя­ зи Direction установите в состояние dirBidirectional (рис. 22.9).


Рис. 22.9. Организация двусторонней связи LiveBindings

 

Для завершения примера выберите обработчик события onchange () одного из ком­

понентов и внесите в него.всего одну строку кода (листинг 22.3).


п

 

22.3. Qnlp...     у

 

._

 

о

 

!

 
:! nм с.................... ,., ,


"   , ,   ..  .. t ,. .,.  t


,,               f                  <

 

Cuadro de texto: 	оа1


f                                t  1 >  1 i                             -  · · .


.......········'...............-···.......······-·······........············"...............................- .....................................................- ·- •••·- ...............·-·········••""  •••••••••............'. ,.,.,                ,..,.,д...................- ---·....

 

implementation

uses System.Bindings.Helper;

{$R *. frnx}

 

procedure TForml.TrackBarlChange(Sender: TObject);

Ьegin TBindings.Notify(Sender, ''); end;

 

Заключительный штрих таков -    сделайте событие общим для обоих компонентов.

Продолжим рассмотрение характеристик класса твindExpression. Для деактивации объекта TBindExpression устанавливаем в false свойство

property Active: Boolean;


LivвBindings                                                                                                                                                    315

 

ВНИМАНИЕ!

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

Еще один способ приостановки взаимодействия обеспечивает свойство

property Managed: Boolean; //по умолчанию true

Отказавшись от значения true, мы изымем связь из-под контроля менеджера связей.

Как и положено полноценному объекту, выражение обладает способностью реаги­ ровать на события (табл. 22.1).

 

Таблица 22.1. События  TBindingExpression

 

Событие

Описание

property OnActivating: TNotifyEvent;

Связь активируется

property OnActivated: TNotifyEvent;

,.

Связь активирована

property OnAssigningValueEvent: TBindingAssigningValueEvent;

type TBindingAssigningValueEvent = procedure(AssignValueRec: TBindingAssignValueRec; var Value: TValue; var Handled: Boolean) of oЬject;

В объект поступает новое значе- ние Value. Установив перемен- ную Handled в состояние true, указываем объекту, что обработка события завершена

property OnEvalErrorEvent: TBindingEvalErrorEvent;

type TBindingEvalErrorEvent =

procedure(AException: Exception) of oЬject;

Генерируется в момент возникно- вения исключительной ситуации AException

property OnAssignedValueEvent: TBindingAssignedValueEvent;

type TBindingAssignedValueEvent = prooedure(AssignValueRec: TBindingAssignValueRec; const Value: TValue) of oЬject;

В объект поступило новое значе- ние Value

 

 

Выражение LiveBindings

Взаимодействие между объектами LiveBindings осуществляется в форме регуляр­ ных выражений. Повторив вводные примеры, мы практически не задумывались над порядком создания выражения - за нас все сделала Delphi. Однако пытливого про­ граммиста такое положение вещей вряд ли устроит.

 

ЗАМЕЧАНИЕ

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

 

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


 

выражений, записанных в форме обычной текстовой строки. Для этого нам потре­ буется помощь трех элементов управления:

   строки ввода Edi tl: TEdi t, которая предназначена для ввода выражения;

   кнопки Buttonl:TButton, она даст команду на расчет выражения;

   метки Labell: TLabel, которая ?тобразит результат расчета.

Полагаете, что далее последует сложный пример с элементами синтаксического разбора сложных предложений? Нет! Технология LiveBindings сделает это без посторонней помощи! На нашу долю выпадет только написать две строки кода в событии щелчка по кнопке вuttonl (листинг 22.4).


uses System.Bindings.Helper;

{$R *.dfm}

 

procedure TForml.ButtonlClick(Sender: TObject);

Ьegin

with TBindings.CreateExpression([], Editl.Text) do Labell.Text:=Evaluate.GetValue.ToString;

end;

 

 

Единственная особенность нашего примера в том, что при вводе арифметического выражения пользователю требуется соблюдать знакомые ему по начальной школе правила, например: (10 + 2): 3 + 1.

Кроме знаков основных математических операций в строке выражения могут ис­ пользоваться встроенные методы. На момент написания этой главы, технология живого связывания поддерживала методы: Format (), FormatDateTime (), LowerCase (),

Math_Мах (),  Math_M n (),  Round (),  StrToDateTime (),  ToNotifyEvent (),  ToStr (), тovariant () и Uppercase (). Кроме того, более двух десятков методов отвечают за преобразование типов данных.

 

ЗАМЕЧАНИЕ

Для получения списка зарегистрированных методов LiveBindings во время визуально­ го проектирования приложения следует в Инспекторе объектов обратиться к свойст­ вам Methods и OutputConverters компонента TBindingsList.

Названия методов говорят сами за себя и вряд ли требуют отдельных комментариев для программиста, имеющего опыт работы в Delphi. Например, метод Format () по­ зволяет форматировать текстовую строку, и его синтаксис очень напоминает поря­ док обращения к одноименной фун ции из модуля system. sysUtils. Допустим, что в нашем распоряжении есть связь твindExpression между компонентом­ источником ArcDiall и компонентом-получателем Labell. Наша задача - сформу­ лировать выражение, которое позволит отобразить в метке Labell значение угла поворота ArcDiall. Value. Решение задачи достаточно простое: SourceExpression:=

Format ( 'Угол:  "%f"', Value).

 

 



 

ЗАМЕЧАНИЕ

К сожалению, методы LowerCase () и UpperCase () пока не работают с национальны­ ми алфавитами.

Не исключено, что в дальнейшем перечень методов возрастет, поэтому предлагаем пример, с помощью которого вы сможете самостоятельно опросить главного спе­ циалиста по методам в тех,нологии LiveBindings - класс TBindingMethodsFactory (листинг 22.5).


uses System.Bindings.Methods;

{$R *. fmx}

 

proaedure TForml.FormCreate(Sender:  TObject);

var MD: TMethodDescription;

Ьegin

for  MD in TBindingMethodsFactory.GetRegisteredМethods do

if MD.DefaultEnaЫed then ListBoxl.Items.Add(MD.Name) else ListBox1.Iterns.Add(MD.Name + '  (отключено)');

end;

 

 

Для повторения кода вам понадобится помощь списка TListвox и ссылка на про­ граммный модуль методов System.Bindings.Methods.

 

Класс TBindings

Если логика приложения предполагает создание связи LiveBindings не поверхност­ ным визуальным, а программным образом, то разработчику стоит познакомиться с классом TBindings. Сразу отметим тот факт, что твindings попросту является владельцем некоторого набора методов, специализирующихся на создании живых связей. Все методы объявлены как методы класса (class function) и поэтому не нуждаются в физическом существовании экземпляра TBindings:

 

ЗАМЕЧАНИЕ

Основной код программной реализации технологии живого связывания сосредоточен

в двух модулях: System. Bindings. Expression и System. Bindings.Helper.

Для создания простейшего выражения LiveBindings следует вызвать метод

class function TBindings.CreateExpr BindGridLinklession(

const InputScopes: array of IScope; //массив источников данных const BindExprStr: string                                           //строка выражения

) :  TBindingExpression;  overload;

 

Параметр Inputscopes содержит массив ассоциаций, представляющий собой пары "объект источник данных- псевдоним объекта в выражении". Текст выражения, описывающего поведение источников данных, передается в параметр BindExprStr.


 

ЗАМЕЧАНИЕ

Еще одна особенность твindings в том, что в результате действий класса на свет по­ является нерассмотренный несколькими страницами ранее TBindExpression. Вместо этого TBindings оперирует экземпляром класса - TBindingExpression.

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

class function TBindings.CreateManagedBinding(

const InputScopes: array of IScope;      //массив источников данных

const BindExprStr:-string;           //строка  выражения

const OutputScopes: array of IScope; //массив выходных значений

const OutputExpr: string;            //выходное выражение

const OutputConverter: IValueRefConverter;//выx:oднoe преобразование BindingEventRec: TBindingEventRec; //событие

Manager: TBindingManager = nil;    //ссылка на менеджер Options:  TCreateOptions =  [coNotifyOutput]//oпции

) :  TBindingExpression;  OV8rload;

Параметры Inputscopes и BindExprStr нам уже знакомы. Параметр outputScopes со­ держит массив пар "управляемый объект - псевдоним объекта в выходном выра­ жении". Выходное выражение заносится в параметр outputExpr. Правила преобра­ зования данных определяются параметром outputconverter. Шестой по счету пара­ метр BindingEventRec предоставляет возможность подключения к созданному объекту связи обработчика события. Седьмой по счету параметр мanager требует более подробного рассмотрения.

В результате выполнения функции появившийся на свет экземпляр связи сразу пе-

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

 

ЗАМЕЧАНИЕ

Технически связь может быть создана без зависимости от менеджера взаимодейст­ вия. Для этого применяется метод класса CreateUrunanagedBindin'g().

Для создания ассоциации между реальным объектом Delphi и его псевдонимом в строке выражения задействуется метод

class function TBindings.CreateAssociationScope(

Assocs: array of TBindingAssocia·tion) : IScope;

В самом простейшем случае в единственный параметр метода - массив ассоциа­ ций Assocs - передаются пары "объект - текстовый псевдоним". Эти пары легко формируются сервисной функцией

function Associate(RealObject: TObject;

const ScriptObject:  String): TBindingAssociation;


 

После успещноrо формирования связи между объектами обсудим самый главный (уже знакомый по предыдущим примерам) метод-уведомление:                                  \


class procedure TBindings.Notify(Sender:  TObject;  PropNarne:  string

Manager:  TBindingManager = nil);


''.,


Метод уведомляет об изменении свойства или объекта, чем стимулирует один из взаимодействующих объектов на выполнение выражения. Здесь: sender- объект, инициирующий взаимодействие объектов; PropNarne - участвующее во взаимодей­ ствии свойство; мanager- необязательная ссылка на менеджер взаимодействия.

 

Lists -  связь между списками

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

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

Листинг 22.6 представляет исходный код класса тvalues, предназначенного для хранения тройки разнотипных значений. Повторяя пример, вы можете добавить или удалить поля класса, выбрать свои типы данных - это лишь подчеркнет все­ ядность LiveBindings.


type TValues=class

private

fRValue:Real;            //поле вещественного типа fIValue:Integer; //целочисленное поле

puЫic

constructor Create(aRValue:Real;aIValue:Integer); property RValue:real read fRValue;

property IValue:Integer read fIValue;

end;

 

//конструктор класса TValues

constructor TValues.Create(aRValue: Real; aIValue: Integer);

Ьegin

fRValue:=aRValue; fIValue:=aIValue;

end;


 

 

После описания класса тvalues, который возьмет на себя обязанности элемента списка, перейдем к описанию собственно списка, который станет источником дан­ ных для LiveBindings (листинг 22.7).


uses ... , System.Generics.Collections;

 

var Forrnl: TForrnl;

ValuesList:TList<TValues>;  //объявление  списка источника  данных

 

implementation

{$R *. fmx}

 

prooedure TForrnl.ForrnCreate(Sender:  TObject);

var Values:TValues; i:integer;

Ьegin

Randomize ();

ValuesList:=TList<TValues>.Create;  //создаем исходный  список

//заполняем 10 элементов списка произвольными данными

for i :=О to  9 do

Ьegin

Values:=TValues.Create(Random(),Random(l00)); ValuesList.Add(Values);


 

end;


end;


 


Основная изюминка предложенного в листинге 22.7 кода заключается в том, что на роль списка мы выбрали объявленный в модуле System.Generics.Collections класс TList. Эго класс-шаблон (см. главу 3), создающий список, способный обслуживать практически любой тип данных. Мы воспользовались универсальностью TList и заставили его работать с элементом типа ТValues. После создания списка случай­ ным образом заполним его значениями и на этом ставим точку в подготовительном этапе.

Наконец мы подошли к этапу организации связи между списком valuesList и ком­ понентом Listвoxl. Для налаживания отношений между списками мы воспользуем­ ся элементом управления списком и парой компонентов со страницы LiveBindings, нам понадобятся:

   ListBoxl :TListBox;

  BindingsListl: TBindingsList;

  BindScopel: TBindScope;

Дважды щелкнув левой кнопкой мыши по·компоненту BindingsListl, войдем во встроенный редактор и создадим новую связь - элемент TBindList (рис. 22.1О). Новому объекту по умолчанию будет присвоено имя BindListl.

 

 



 

LiveBindings                                                                                                                                                321


Рис. 22.10. Создание связи между списками - TBindList

 

Найдите связь BindListl в Инспекторе объектов и приведите ее свойства в следую­ щее состояние:

  автоматическая активация AutoActiyate=True;

  автоматическое заполнение целевого списка  AutoFill=False;

   управляемый компонент  ControlComponent=ListBoxl;

  компонент-источник SourceComponent=BindScopel;

   щелкнув по кнопке с многоточием напротив свойства FormatExpressions, вы по­ лучите доступ к редактору выражений (рис. 22.11). Заполните два поля единст­

венного выражения следующим образом:

        ControlExpression=Text;

        SourceExpression=ToStr(Current.IValue) +"       "+     ToStr(Current.RValue).

По большому счету связь уже полностью организована. Чтобы вы не слишком рас­ слаблялись, разбавим процесс визуального проектирования LiveBindings кодирова- нием, нам предстоит написать целых три строки кода (листинг 22.8).                                                                                       ·

...... ·······•·.....................................................................................................

1


...-.·..-.........................2....1.. .8о··.'"·····."'·.··т.,  ....

procedure TForml.ButtonlClick(Sender: TObject);

Ьegin


 

 

·•·•··"""'•,...•··.·········· •··"•·'"••• •••·•·•··"··'·•·•


BindScopel.DataObject .- ValuesList; //подключаем исходный список


BindListl.FillList; BindScopel.DataObject ·= nil;

end;


//передаем данные в целевой список

//удаляем ссылку на объект



Рис. 22.11. Редактор выражения элемента TBindList

 

Щелчок  по кнопке  заставит  отвечающие  за связь  компоненты  BindScopel  и

BindListl заполнить список ListBoxl данными из целевого списка ValuesList.

 

Класс TBindList

Класс TBindList обеспечивает связь между двумя списками данных. В качестве ис­ точника может выступать практически любой набор значений, оформленный в виде списка (именно так мы и поступили в листинге 22.6), ссьmка на список-источник данных передается в свойство

property SourceComponent: TComponent;

Второй участник связи (управляемый объект) должен быть упомянут в свойстве

property ControlComponent: TComponent;

Правила взаимодействия между списками описываются в свойстве

property FormatExpressions: TExpressions;

Экземпляр класса TExpressions представляет собой коллекцию элементов TExpressionitem, специализирующуюся на обслуживании пар управляющих выра­ жений ControlExpression и SourceExpression. Правила формирования выражений зависят от вида обслуживаемых данных.

Для отправки данных от списка-источника к получателю задействуется метод

prooedure FillList;

Впрочем, если свойство

property AutoFill: Boolean; //по умолчанию true

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


 

 

 

 

 

 

 

 

 

 

 

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

К счастью как в Windows, так и в OS Х, кроме обязательного потока управления процесс имеет возможность обладать набором вспомогательных потоков. Напри­ мер, текстовый процессор Microsoft Word (в котором набираются эти строки) одно­ временно с вводом текста разбивает документ на страницы, контролирует знания языка (безжалостно подчеркивая мои ошибки), отправляет задание на печать и ре­ шает ряд других, менее очевидных для обычного пользователя задач. Все это про­ исходит одновременно благодаря тому, что в Word запущено несколько потоков. Каждый из потоков выполняет поставленную перед ним подзадачу и при желании может быть приостан<?влен или запущен вновь.

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

 

Поток TThread

Для программной реализации потока в модуле systern. Classes объявлен абстракт­ ный класс ттhreact, на основе которого программист создаст свой полнофункцио­ нальный поток.


 

ВНИМАНИЕ!

При проектировании многопоточных приложений для OS Х надо учитывать тот факт, что данная ОС поддерживает две технологии потоков: потоки Сосоа и потоки POSIX. Представленный в RAD Studio ХЕ5 класс System. Classes. TThread нацелен на работу с технологией потоков POSIX.

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

вующему приложению добавить специальный программный модуль. Для этого по­ сле создания нового приложения выберите пункт меню File I New I Other и в разде­

ле Delphi Files диалогового окна New Items найдите значок Thread Object. После нажатия кнопки ОК в новом диалоговом окне требуется ввести имя нового клас­ са - потока. Например, TDemoThread, как показано на рис. 23. I.

 

е Newltems

 

Q                     

Ccщ,onent  Dat.МodAe Firl!Мori<.ey FireМ<pey

Form          Мetropols...

[1]

мsa,id          Teedwt  mn.d ·          Lnt

г-- :

 
rмoets File       WiDrd

 

New Тhread OЬject

 

r;;-        

 
ТDеmоl!тем

 

 

 

 

 

ок.    11 c.icel

 

Рис. 23.1. Добавление потока к приложению

 

В результате выполненных действий Delphi создает представленный в листин­ ге 23.1 шаблон, предназначенный для описания потока. Обратите внимание, что в секции Protected среда проектирования уже подготовила процедуру Execute () . В теле этой процедуры программист располагает программный код, который станет исполняться в отдельном потоке. Если подзадача вызывается многократно, то строки кода помещаются внутрь тела цикла, выход из которого завершает работу потока.


unit Unit2;

 

interface

uses System.Classes;


 

 

 

 

 

 

 

 

 

 

 

 


end; end.

 

При старте приложения автоматическое создание потока не произойдет, для этого следует явно обратиться к конструктору потока

constructor Create(CreateSuspended: Boolean);

Единственный параметр конструктора определяет, каким образом производится запуск потока. Если параметр CreateSuspended установлен в false, то поток старту­ ет немедленно, сразу после создания экземпляра класса. Иначе поток создается в спящем режиме. В последнем случае для запуска потока вызывайте метод

procedure Start;

Говоря образно, эта процедура заставит биться сердце потока.

Для временной приостановки выполнения потока используйте процедуру

procedure Suspend;

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

procedure Start;

Возобновление выполнения потока произойдет именно с того места (с той строки кода), где он был приостановлен. Два названных метода дублируются свойством

property Suspended: Boolean;

Если вы установите свойство в true, то поток приостановится, false - вновь акти­ визируется.

 

ЗАМЕЧАНИЕ

В более ранних версиях Delphi вместо метода Start (} применялся метод Resume ().

В FireMonkey применение этого метода не рекомендуется.

 

Для временной приостановки потока программисты Delphi часто пользуются про­ цедурой класса

class procedure Sleep(Timeout: Integer); static;


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

 

ЗАМЕЧАНИЕ

Основное достоинство метода Sleep () в том, что поток, переведенный в состояние сна, не снижает производительность системы, т. к. не требует выделения ему квантов времени.

Признаком того, что поток завершил выполнение своей задачи, выступает значение

true, возвращаемое свойством

property Finished: Boolean;

Для полной остановки потока вызывайте метод

prooedure Terminate;

Этот способ остановки называется мягким. Процедура просто-напросто присваива­ ет значение true свойству

property Terminated: Boolean;

В этом случае Delphi не настаивает на немедленном прекращении работы потока и разрешает ему достичь логического конца.

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


prooedure TDemoThread.Execute;

Ьegin

repeat

{исполняемый код потока} until Terminated;

end;

 

 

Останов потока сопровождается вызовом обработчика события

property OnTerminate:  TNotifyEvent;

Если установить в true свойство

property FreeOnTerminate:  Boolean;

то сразу после останова уничтожится экземпляр потока. Иначе за вызов деструкто­ ра потока отвечает программист.

Если выполнение метода Execute () прервалось в результате ошибки, и ошибка не была обработана в рамках этого метода, то в свойство только для чтения

property FatalException: TObject; //только для чтения

будет записан объект ошибки.


Еще одним способом, косвенно проверяющим корректность завершения работы потока, является использование свойства

property ReturnValue: Integer;

Это служебная переменная, в которую можно записывать данные, например, о ко­ личестве запусков потока. Свойство Returnvalue часто применяется совместно с методом ожидания wai tFor (} . Функция предназначена для организации ожидания, пока какой-нибудь другой поток не выполнит поставленную перед ним задачу.

Существует альтернативный доступ к значению ReturnValue, его обеспечивает метод класса

class prooedure SetReturnValue(Value: Integer}; static;

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

Современные версии Windows и OS Х поддерживают работу с несколькими про­ цессорами, распределяя задачи между ними. Класс TThread способен проинформи­ ровать программиста о процессоре компьютера. Свойство

class property IsSingleProcessor:  Boolean;

в состоянии true уведомляет разработчика, что поток будет запущен на однопро­ цессорной станции, если же свойство возвратит false, то стоит обратиться к свой­ ству

class property ProcessorCount: Integer;

возвращающему число процессоров или ядер процессора.

 

ВНИМАНИЕ!

Разработчику рекомендуется не запускать в приложении более 16 потоков на один процессор, т. к. при превышении этого значения будет существенно снижена произво­ дительность системы.

Еще одна полезная функция класса

class function GetCPUUsage(var PrevSystemTirnes:  TSysternTirnes}:  Integer;

позволит программисту выяснить степень загрузки процессора. Единственная осо­ бенность метода в том, что поступающая в его параметр структура PrevSysternTirnes: тsysternTirnes должна быть объявлена как глобальная переменная. В самом про­ стейшем случае, разместив на форме шкалу TProgressBar, метку TLabel и таймер ттirner, вы сможете получать регулярные сведения о занятости центрального про­ цессора (листинг 23.3).


var Forrnl : TForrnl;

ST: TThread.TSysternTirnes;



prooedure TForml.TirnerlTirner(Sender: TObject);

var Х: Integer;

Ьegin

Х := TThread.GetCPUUsage(ST); ProgressBarl.Value := Х; Labell.Text ·= Format('%d%%', [Х]);

end;

 

 

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

class procedure Yield; static;

Заметим, что окончательное решение на постановку потока на обслуживание всегда останется за операционной системой.

 

Метод ожидания

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

function WaitFor:  LongWord;

Функция возвращает значение, содержащееся в свойстве Returnvalue ожидаемого потока (табл. 23.1).

 

Таблица 23.1. Значения, возвращаемые методом WaitFor()

 

Значение

Описание

WAIT- OBJECT- о

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

WAIT- TIMEOUT

Хотя время тайм-аута истекло, но состояние опрашиваемого объекта син- хронизации несигнальное. Это означает, что ресурс пока захвачен другим потоком

WAIT- AВANOONED

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

WAIT- FAILED

Ошибка выполнения метода

 

ЗАМЕЧАНИЕ

Метод WaitFor ( J применяется для совместной работы не только с потоками, но и с такими объектами синхронизации Windows, как события, мьютексы и семафоры. Ключевая особенность метода ожидания заключается в том, что он не возвращает ре-


 

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

 

Управление приоритетом потока

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

При определении приоритета потока следует учитывать некоторые различия Windows и OS Х. Так в приложениях Windows приоритет потока определяется именованной константой


type TThreadPriority = (tpldle,

tpLowest, tpLower, tpNorrnal, tpHigher, tpHighest, tpTimeCritical);


//фоновый приоритет

//низкий приоритет

//пониженный приоритет

//нормальный приоритет

//повЬШiенный приоритет

//высочайший приоритет

//приоритет реального времени


в свойстве

property Priority: TThreadPriority; //по умолчанию tpNorrnal

В проектах для OS Х при определении приоритета потока POSIX мы воспользуемся целым числом

property Priority: integer;

 

ВНИМАНИЕ!

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

 

ЗАМЕЧАНИЕ                                                                                                  ,

В состав Delphi входит специальная утилита, предназначенная для просмотра потоков приложения. Для обращения к утилите выберите пункт меню View I Debug Windows 1

Threads.


 

 

Синхронный и асинхронный вызовы внешнего метода

Если из тела основного метода потока Execute () необходимо осуществить вызов внешнего метода или обратиться к свойству компонента библиотеки VCL, то не следует это делать напрямую, вместо этого надо использовать специализированные методы Synchronize () или Queue (). Задача методов - исключение взаимных бло­ кировок потоков.

 

ЗАМЕЧАНИЕ

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

 

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

prooedure Synchronize(Method: TThreadМethod); overload; procedure Queue(AМethod: TThreadМethod); overload;

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

 

Пример многопоточного приложения

Для демонстрации возможности многопоточной обработки данных разработаем приложение, позволяющее собирать сведения о находящихся в определенной папке файлах с растровыми изображениями JPG и создающее их миниатюры. Для реали­ зации задуманного нам понад?бится новое приложение HD, на единственной фор­ ме которого следует расположить:

   строку ввода Editl, в ней пользователь введет путь к папке с файлами;

   область с полосами прокрутками ScrollBoxl, внутри этой области мы станем динамически создавать миниатюры изображений;

    кнопку Buttonl, отвечающую за запуск процесс сбора.

В листинге 23.4  представлен  предельно  упрощенный  исходный  код  потока

TThurnЬThread, отвечающего за создание миниатюры.

 

 



 

 

FileName:TFileName; //имя. файла с изображением

Irnage:  Tirnage;           //ссылка на компонент Tirnage

protected

procedure Execute; override; end;

 

procedure TThumЬThread.Execute;

Ьegin

Irnage.Bitrnap.LoadThumЬnailFrornFile(FileName,Irnage.Width,Irnage.Height); Terrninate;

end;

 

 

В секции частных объявлений потока TThumЬThread мы ввели два дополнительных поля. В поле FileName заносится имя файла, для которого следует создать миниатю­ ру, а в поле Irnage окажется ссылка на компонент тrrnage, отображающий картинку.

Процесс сбора миниатюр начинается в момент щелчка пользователя по кнопке (листинг 23.5).


procedure TForrnl.ButtonlClick(Sender: TObject); var SDA:TStringDynArray;

i: integer; ThumЬirnage:Tirnage;

Ьegin

SDA:=TDirectory.GetFiles(Editl.Text,

'*.jpg',  TSearchOption.soTopDirectoryOnly);

 

for i:=0 to High(SDA) do

Ьegin

ThumЬirnage:=Tirnage.Create(ScrollBoxl); ThumЬirnage.Parent:=ScrollBoxl; ThumЬirnage.Width:=100;                                                     ThumЬirnage.Height:=100;

 

with TThumЬThread.Create(true) do

Ьegin

FileName:=SDA[i]; Irnage                  :=ThumЬirnage; FreeOnTerrninate:=true;

{$IFDEF MSWINDOWS} //код для. Windows Priority:=TThreadPriority.tpLower;

{$ELSE}


 

 


 

 

 

 

 

 

 

end; end;


{$IFDEF POSIX}

Priority:=10;

{$ENDIF}

{$ENDIF}

Start;

end;


//код дпя потоков POSIX


 

Воспользовавшись услугами метода GetFiles () класса TDirectory, мы собираем все файлы с расширением имени jpg в динамический массив SDA. Затем, перебирая эле­ менты массива, создаем экземпляры класса тrmage для каждого файла с картинкой и размещаем их на поверхности компонента scrollBoxl. После этого наступает черед формирования отдельного потока - он создается на основе описанного в листин­ ге 23.4 класса TThurnЬThread. Потоку передаются сведения об имени файла и ссылка на объект Timage.

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


Рис. 23.2. Экранный снимок работающего приложения

 

 

Синхронизация потоков в Windows

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


 

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

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

Объект синхронизации обладает двумя устойчивыми состояниями: сигнальным

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

 

ЗАМЕЧАНИЕ

Все рассматриваемые в этой главе объекты синхронизации описаны в модуле SyncObj s.

 

Синхронизация событием TEvent

К числу наиболее простых средств синхронизации потоков относится синхрониза­ ция с помощью описанного в модуле syncoьjs события TEvent. Как и все объекты синхронизации Windows, событие может находиться в одном из двух состояний: сигнальном и несигнальном. Синхронизируемые потоки способны выяснить, в ка­ ком расположении духа находится экземпляр класса TEvent, и приостанавливать или, наоборот, возобновлять свою работу.

Как всегда, создание объекта начинается ,с вызова его конструктора:

constructor Create(EventAttributes: PSecurityAttributes;

ManualReset, InitialState: Boolean; const Narne: string) ;

Первый параметр конструктора EventAttributes - это дескриптор безопасности, применяемый в Windows. Параметр ManualReset разрешает (значение true) перево­ дить событие в несигнальное состояние вручную (это умеет делать метод ResetEvent () ), иначе сброс произойдет только после старта потока, ожидающего этот объект синхронизации. Параметр InitialState определяет, в какое из состоя­ ний переходит событие сразу после создания: в сигнальное (true) или несигнальное (false). Последний параметр назначает 1:fМЯ создаваемому объекту синхронизации. Будьте внимательны, имя должно быть уникальным. Впрочем, имя понадобится, только если этим событием надо воспользоваться извне процесса, создавшего экземпляр TEvent.


 

При обсуждении конструкт ра события TEvent упоминался метод

procedure ResetEvent;

предназначенный для перевода объекта-события в несигнальный режим "вручную".

Обратная операция, включающая событие, обеспечивается процедурой

procedure SetEvent;

В составе класса TEvent предусмотрен метод ожидания:

function WaitFor(Timeout: DWORD):  TWaitResult;

type TWaitResult = (wrSignaled, wrTimeout, wrAЬandoned, wrError);

Время ожидания перехода объекта синхронизации в сигнальное состояние опреде­ ляется единственным аргументом метода Timeout и измеряется в миллисекундах. Если по истечению заданного тайм-аута ничего не произошло, то метод возвратит wrTimeout; если же за это время объек-- :_-событие перейдет в сигнальное состояние, то метод, не дожидаясь истечения тайм-аута, вернет wrsignaled.

Дескриптор объекта синхронизации Event доступен из свойства

property Handle: THandle;

Код последней ошибки, связанной с объектом, доступен из свойства

property LastError: Integer;

Признаком того, что все в порядке, будет нулевое значение.

Как уже отмечалось в начале подраздела, синхронизация событием очень проста. Создайте новый проект и разместите на его главной форме три компонента: шкалу ProgressBarl: TProgressBar, метку Labell: TLabel и кнопку Buttonl: TButton. Добавь­ те к проекту новый поток, присвоив ему имя TProgressThread (листинг 23.6).


uses Classes, SyncObjs, SysUtils,

 

type TProgressThread = class(TThread) private

procedure ChangeProgress;          //процедура управления шкалой

protected

procedure Execute; override;

end;

 

var

Event:TEvent; //событие синхронизации ,

i.mplementation

 

procedure TProgressThread.Execute;

Ьeqin

repeat


 

 

 

 

 

 


until Terminated; Event.Free;

end;

 

prooedure TProgressThread.ChangeProgress;

Ьegin

if    Forml.ProgressBarl.Value+l<Forml.ProgressBarl.Max  then

Ьegin

Forml.ProgressBarl.Value:=Forml.ProgressBarl.Value+l; Forml.Labe'll. Text:=

Format('%.2f%%', [Formi.ProgressBarl.Value]);

end else Forml.ProgressBarl.Value:=0;

end; end.

 

.

 
Изучив листинг 23.6, вы увидите, что в рамках программного модуля объявляется событие синхронизации Event: TEvent. Визуальной стороной работы потока стано­ вится изменение положения шкалы Progressвarl, эта операция производится в ме- тоде ChangePrqgress () .

Теперь переключим наше внимание на главную форму проекта. Нам понадобится описать два события. В событии OnShow (), вызываемом в момент вывода формы на экран, создается низкоприоритетный поток. Щелчок по кнопке формы вызывает диалог редактирования позиции шкалы Progressвarl. Для того чтобы в момент вво­ да нового значения поток приостановил свою работу, мы переводим событие Event в сигнальное состояние. Завершив редактирование, сигнальный режим отключается (листинг 23.7).


prooedure TForml.FormShow(Sender: TObject);

Ьegin

Event:=TEvent.Create(nil,true,false, 'Eventl');//cQздaниe   события

with TProgressThread.Create(True) do //создание потока

Ьegin

Priority:=tpLowest; FreeOnTerminate:=True; Start;

end;

end;


 


 

procedure TFonnl.ButtonlClick(Sender: TObject); var Value: string;

Ьegin

Event.SetEvent; //перевод события Event в сигнальный режим Value:=FloatToStr(ProgressBarl.Value);

 

if InputQuery('Шкaлa', 'Новое знaчeниe',Value)=true .then ProgressBarl.Value:=StrToFloatDef(Value,ProgressBarl.Value);

 

Event.ResetEvent;  //отключение  сигнального режима

end; end.

 

Критическая секция TCritica/Section

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

Создание критической секции начинается с обращения к методу create (). Появив­ шаяся на свет в результате вызова конструктора секция приступит к выполнению своих прямых функциональных обязанностей после обращения к методу

procedure Enter;

С этого момента объект синхронизации встает на защиту отмеченного участка кода и не допустит к нему ни один из потоков до тех пор, пока не будет вызван метод

procedure Leave;

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

Критическая секция может ждать своего выполнения бесконечно долго, поэтому при разработке многопоточного приложения с критической секцией программист отвечает за то, чтобы:

   при аварийном завершении потока критическая секция немедленно освобожда­ лась;

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

Обычно код входа в критическую секцию оформляется в защищенной от ошибок секции try .. finally, что гарантирует освобождение заблокированного кода (лис­ тинг 23.8).



var CriticalSection:TCriticalSection;

 

procedure TDemoThread.Execute;

Ьegin

repeat

//код потока

try

CriticalSection.Enter;  Synchronize(<вызoв внешнего метода>);

finally

CriticalSection.Leave;

end;

until Teпninated;

end;

 

Мьютекс TMutex

Как и критическая секция, объект синхронизации мьютекс (mutex) может принад­ лежать только одному потоку. Если мьютекс свободен, то он устанавливается в сигнальное состояние, если же он кем-то захвачен - переходит в несигнальный режим. При попытке получить доступ к "приватизированному" другим владельцем мьютексу поток получает отказ и переводится в состояние ожидания его освобож­ дения. Таким образом, мьютекс обеспечивает взаимное исключение (mutual exclusion) доступа потоков к разделяемому ресурсу.

 

ЗАМЕЧАНИЕ

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

Мьютекс описывается классом тмutех. Существуют три варианта создания мьютек­ са. Простейший из них обеспечивает конструктор

constructor Create(UseCOМWait: Boolean = False); overload;

В итоге на свет появляется неименованный объект синхронизации, доступный только внутренним потокам одного приложения. Единственный параметр конст­ руктора определяет способность мьютекса взаимодействовать с компонентами STА (single-threaded apartment) СОМ.

Наиболее востребованной версией конструктора считается

constructor Create(MutexAttributes: PSecurityAttributes;

InitialOwner: Boolean; const Name: string; UseCOМWait: Boolean = False); overload;


..


338                                                                                                                                                          Глава 23

 

Первый параметр метода представляет собой указатель на запись атрибутов защи­ ты pSecurityAttributes = лтsecurityAttributes. Параметр определяет атрибуты безопасности, в простейшем случае в качестве этого аргумента допускается пере­ дача неопределенного указателя nil. Параметр InitialOWner указывает, станет ли поток, создающий мьютекс, его владельцем. Для этого параметру передавайте зна­ чение true. Последний аргумент содержит имя мьютекса. Если мьютекс не плани­ руется разделять с внешними процессами, то метод удовлетвориться пустым аргу­ ментом.

Третий конструктор открывает уже существующий мьютекс, обращаясь к нему по имениNаmе.

constructor Create(DesiredAccess: LongWord;  InheritHandle: Boolean;

const Name: string; UseCOМWait: Boolean = False); overload;

Порядок доступа зависит от первого параметра, в который направляется комбина­


ция флагов доступа: МUТЕ-Х


AL-L ACCESS, МUТЕ-Х


MODIF-Y


STATE, SYNCHRONIZE, _DELETE,


READ_CONTROL, WRITE_DAC, WRITE_OWNER.

Для управления мьютексом достаточно пары методов:

prooedure Acquire; override;

prooedure Release; override;

Первая из процедур захватывает мьютекс, вторая- освобождает.

 

ВНИМАНИЕ!

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

 

Семафор TSemaphore

Семафор (semaphore)- логическое развитие мьютекса. Как вы помните, объект взаимного исключения мьютекс позволял владеть собой только одному потоку. В отличие от него семафор может находиться в совместном использовании сразу нескольких потоков, но при этом он умеет управлять количеством этих потоков - выступает в роли ограничителя их "популяции". Для этого он снабжен счетчиком, определяющим максимальное число потоков, которым разрешено выполняться со­ вместно. С запуском каждого нового потока значение счетчика уменьшается на единицу, и когда оно достигнет нуля, семафор перейдет в несиrнальное состояние. Это означает, что последующие потоки приостанавливаются и будут дожидаться момента, пока какой-нибудь из потоков не освободит семафор. Характерным при­ мером области применения семафоров может послужить программное обеспечение какого-нибудь аппаратного устройства, с которым может работать только ограни­ ченное количество потоков, или реализация веб-сервера (сервера баз данных), до­ пускающего одновременный доступ ограниченного числа посетителей.


Семафор обладает сразу тремя перегружаемыми версиями конструктора: constructor Create(UseCOМWait: Boolean = False); overload; constructor  Create(SernaphoreAttributes:  PSecurityAttributes;

AinitialCount, AМaximumCount: Integer;

const Name: string; UseCOМWait: Boolean = False); overload;

constructor Create(DesiredAccess: LongWord; InheritHandle: Boolean; const Name: string; UseCOМWait: Boolean = False); overload;

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

 

Таблица 23.2. Параметры конструктора семафора

 

Параметр

Описание

SemaphoreAttributes

Ссылка на структуру тsecurityAttributes с атрибутами безопас- ности, допускается nil

AinitialCount

Начальное значение числа доступных потоков и максимальное зна- чение сче ика семафора. Когда число свободных потоков подходит к нулю, семафор приостанавливает доступ к ресурсу

AМaximumCount

Name

Чувствительное к регистру символов уникальное имя семафора

UseCOМWait

Параметр устанавливается в true, если блокируемый ресурс дол- жен поддерживать технологию STA СОМ

DesiredAccess

Уточняет особенности доступа к семафору: SEМAPHORE_ALL_ACCESS, SEМAPHORE_MODIFY_STATE, SYNCHRONIZE,_DELETE, READ_CONТROL, WRITE_DAC, WRITE_OWNER

InheritHandle

Разрешает дочерним процессам наследовать дескриптор

 

Захват семафора осуществляется после обращения потока к методу

prooedure Acquire;

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

pro08dure Release; overload; overricle;

function Release(AReleaseCount: Integer): Integer; reintroduoe; overload;

Несмотря на то, что семафор - достаточно сложный объект синхрониз;щии, работа с ним не вызовет особых затруднений. Листинг 23.9 отражает процесс создания экземпляра семафора.


............. '··"'"······,  _.,               .,...,.,.,...,.........,... ...,,.,...............,.."f\'•···············..-···············..................,....................,......,...........................................................,.•••.•.•..•.•.    •••'lt

Лист11мr 23.1. а......•• 1 ре                                                                                                           .

,;.,, -,'"(f'i........••••••'·•"'-""'•"'••••.. •• ,-1, ,,,, ._.., -<........'!';№, ·f"h.••.. •••••'d ....., '""•"'••"'•,<:· -.. .•.. ••••••••.. •• - -'::"-•и••••••••••'·, ·ъ·••••••••.......••••.. •••••••••••........• ••••••••••............ "'""•••••••................. ••»•••••••••••••••"••-•••••

 

uses SyncObjs, ...

var  Semaphore:TSemaphore;  //глобалQная переменная

 

procedure TfnnМain.FoпnCreate(Sender: TObject);

var AМaximurnCount:Integer;

Ьegin

AМaximurnCount:=TThread.ProcessorCount*l0; Semaphore:=TSemaphore.Create(nil, AMaximumCount-1, AМaximurnCount,

'DemoSemaphor');

 

 

 

Обратите внимание на то, как в коде определяется максимальное число потоков, ко,нтролируемое семафором. Мы узнаем число процессоров в компьютере и, исходя из этого, ограничиваем количество потоков. Напомню, чт"о не рекомендуется созда­ вать более 16 потоков на одно ядро процессора.

Листинг 23.1О демонстрирует работу с семафором потока.


procedure TThumЬThread.Execute;

.

 
Ьegin

Semaphore.Acquire; //ограничиваем число работающих потоков

 

repeat

if Semaphore.WaitFor(200)<>wrSignaled then      //ожидание 200 мс Image.Bitmap.LoadThumЬnailFrornFile(FileName,

Image.Width, Irnage.Height);

if NOT Image.Bitmap.IsEmpty then Terminate;

until Teпninated;

 

Semaphore.Release;

end;

 

 

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

 

ВНИМАНИЕ!

Будьте аккуратны при использовании семафоров в зацикленных потоках (потоках, в·методе Execute () которых используются операторы цикла). В таких случаях высока вероятность блокирования потока.

 

 

 

 

 



 

 

 

 

 

 

 

 

 

 

 

 

 

Мультиязычные проекты

 

 

Кто из программистов не тешет себя надеждой написать программу, которая ока­ жется востребованной не только в стенах компании, во славу которой он трудится, но и внешними, а возможно, и зарубежными пользователями? Сегодня, в эпоху Ин­ тернета, не существует никаких проблем проявить себя и представить свой про­ граммный шедевр на всеобщее обозрение. Единственным ограничением может стать сложность с локализацией вашего программного продукта. Впрочем, в этом предложении я допустил ошибку - следовало написать слово "может" в прошед­ шем времени. Ведь в FireMonkey эта проблема снята.

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

 

Компонент языковой поддержки TLang

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

Для того чтобы сразу понять порядок применения компонента, создадим новое приложение FireMonkey НD Application и на главной форме проекта разместим произвольный перечень элементов управления. В их числе (рис. 24.1):

   кнопка выбора RadioButtonl: TRadioButton, с надписью "English language";

   кнопка выбора Radioвutton2:  TRadioButton, с надписью "Russian language";

   кнопка Buttonl: твutton с надписью "Apply" (Применить);

   компонент языковой поддержки Langl: TLang.

ВНИМАНИЕ!

При проектировании многоязычного проекта за основной целесообразно принимать английский язык.


Рис. 24.1. Состав элементов управления для демонстрационного приложения

 

Исходный язык приложения - английский, наша задача - научить проект "изъяс­ няться" на русском языке. Технология обучения весьма незатейлива. Находим на форме компонент Langl:TLang и дважды щелкаем по нему левой кнопкой мыши. В ответ на это действие на экране компьютера отобразится редактор Language Designer, позволяющий локализовать наш программный продукт.

Для создания русскоязычного (или любого другого) перевода следует воспользо­ ваться кнопкой Add Language... (Добавить язык) и ввести текстовый идентифика­ тор нового языка. В нашем случае, для русского языка восiюльзуемся аббревиату­ рой "ru". Затем, нажав кнопку Scan for strings (Сканировать строки), обновим спи­ сок подлежащих переводу текстовых ресурсов проекта. Последним штрихом станет перевод англоязычных терминов на русский язык (рис. 24.2).

 

ЗАМЕЧАНИЕ

При желании можете создать текстовый файл-шаблон, содержащий подлежащие пе­ реводу на другой язык текстовые данные. Строки файла представляют собой пары, например: Button1=Кнопка1. Заполнив файл, его можно подключить к компоненту TLang.

Закончив перевод англоязычных· названий на русский язык, немного ·попрактику­ емся в программировании -  опишем щелчок по кнопке Buttonl (листинг 24.1).


prooedura TFoпnl.ButtonlClick(Sender: TObject);

Ьegin

if RadioButtonl.IsChecked then Langl.Lang := 'en'

еlве Langl.Lang := 'ru';

end;


 


Рис. 24.2. Окно Language Designer с русскоязычным переводом

 

В рамках события, в зависимости от состояния кнопки выбора RadioButtonl выби­ рается англоязычный или русскоязычный режим работы приложения. Дтiя этого текстовая аббревиатура языка передается в свойство

property Lang: string;

компонента TLang. Хотите- верьте, хотите- нет, но все остальное сделает ком­ понент-переводчик TLang (рис. 24.3).


Рис. 24.3. Англо- и русскоязычный интерфейсы приложения

 

 

ЗАМЕЧАНИЕ

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


 

(TMainМenu, TPopupMenu, TMenuBar). Характерным признаком того, что компонент спо­ собен взаимодействовать с переводчиком TLang, является наличие у него свойства AutoTranslate.

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

property StoreinForm: Boolean; //по умолчанию true

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

procedure LoadFromFile(const AFileName: string); procedure SaveToFile(const AFileName: string);

позволяющие соответственно загрузить ресурс с переводом из файла и сохранить ресурс в фащ:.

Кроме того, ресурс можно загрузить с помощью свойства

property FileName: string;

В этом случае целесообразно файл с переводом размещать в папке приложения.

 

ЗАМЕЧАНИЕ

Для файлов с переводами принято использовать расширение имени lng. Файл lng - это обычный текстовый файл в кодировке Unicode, содержащий пары значений (ис­ ходный текст=перевод).

 

Компонент TLang допускает управление языками перевода не только в период визу­ ального проектирования, но и из_кода программы. Метод

procedure AddLang(const AName:  string);

позволит добавить новый язык перевода в компонент. Для этого в параметр AName следует добавить аббревиатуру нового языка. Все доступные аббревиатуры языков перевода будут доступны в свойстве-списке

property Resources: TStrings; //только дпя чтения

Соответствующий текущему языку набор терминов находится в свойстве

property Original: TStrings; //только дпя чтения

Существует способ доступа ко всем возможным вариантам перевода. Для этого следует в свойство

property LangStr[const Index: string]: TStrings;

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


 

Режим автоматического перевода

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

property AutoSelect: Boolean; //по умолчанию true

было установлено в состояние true. Это необходимое, но недостаточное условие для того, чтобы все поддерживающие перевод компоненты немедленно откликну­ лись на требование языкового компонента и перевели свои названия. Для того что­ бы приложение "заговорило"·на заданном языке, надо вспомнить, что на уровне класса FМХ. Types. тstyledControl объявлено свойство

property AutoTranslate: Boolean; //по умолчанию true

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

 

Перевод меню

К сожалению, компонент TLang наотрез отказывался переводить текст, принадле­ жащий элементам меню. Почему элементы меню тмenuitem постигла такая участь, мы гадать не станем. Предположим лишь, что с развитием FireMonkey программи­ сты Embarcadero исправят этот недостаток, а пока предлагаем вам свой вариант перевода меню приложения. Решение не столь простое, однако вполне жизнеспо­ собное.

Разместите на форме меню (например, тмainМenu) и, дважды щелкнув по компонен­ ту, заполните его произвольными элементами. Для перевода меню приложения на альтернативный язык предлагаем вновь воспользоваться услугами компонента­ переводчика. Пусть этот компонент пока игнорирует элементы меню тмenurtem, но ведь с переводом у него все в порядке. Дважды щелкнем по компоненту­ переводчику. В раскрывшемся на вашем экране редакторе Language Designer до­ бавьте (используя кнопку+ в правой части редактора) несколько новых строк, впи­ сав в них названия пунктов меню (мenuiteml, Menuitem2 и т. д.). В строке ввода Select Language выберите язык перевода и введите в редактор соответствующий перевод для пунктов меню. Подготовительные операции завершены, теперь нам предстоит немного потрудиться за клавиатурой (листинг 24.2).

 


 

 


proc:edure TForml.MenuTranslate(lang: string); var i,  j, р: integer;

MI: TMenuitem; Str:, TStrings; Sl, S2: string;


 

 

--··-·-·..


-"-•·······  ...t.'


 

Ьegin

Str:�Langl.LangStr[lang];  //получаем все строки с переводом

 

if Str<>nil then //если есть перевод для языка lang

for i  :=О to Fonnl.ComponentCount-1 do //перебор компонентов формы

Ьegin

if not (Fonnl.Components[i] is TMenuitem) then continue; MI:=TMenuitem(Fonnl.Components[i]);

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

end;


for j  :=О to Str.Count-1 do //поиск перевода

Ьegin

p:=pos('=',Str.Strings[j]);//пoзиция символа-разделителя Sl:=COPY(Str.Strings[j],l,p-1);

if Sl=MI.Text then //совпала строка и имя компонента

Ьegin

MI.Text:=COPY(Str.Strings[j],P+l,Length(Str.Strings[j])-P);

break;

end; end;

end;


 


На'вход функции мenuтranslate () поступает языковая аббревиатура lang, иденти­ фицирующая язык перевода. С помощью метода LangStr () мы осуществляем пере­ вод всех строк том числе и строк для элементов меню) и помещаем перевод в  список  строк  Str:TStrings.  Далее  в  дело  вступает  цикл  for i:=0 to Fonnl. ComponentCount-1 do, в рамках которого мы перебираем все компоненты формы с целью поиска элементов меню тмenurtem. Найдя пункт меню, мы запуска­ ем вложенный цикл, в котором находим соответствие между названием ( свойством Name) пункта меню и строкой перевода (напомним, что они разделены символом=). Вот собственно и все... Вам осталось лишь вызвать функцию, и перевод меню в ваших руках.


 

 


ГЛАВА 25

 

 

Мобильная платформа

 

 

 

 

В самом начале книги, в ее первой главе, мы уже посвятили несколько страниц во­ просу выпуска приложения для платформы IOS и Android. Напомним, что кроме языка Delphi в процессе создания приложения для iPhone и iPad задействуются еще несколько ингредиентов:

  рабочая станция Мае с операционной системой OS Х 10.7 Lion или 10.8 Mountain Lion;

   мобильная ОС IOS 5.1 выше);

   поставляемый совместно с Delphi ассистент Platfonn Assistant (PAServer), кото­ рый следует установить на компьютере Мае;

   программное обеспечение Xcode вместе с Xcode Command Line Tools;

   мобильное устройство iPhone или iPad, подключенное к станции Мае по USВ- кабелю.

Для отладки приложений для смартфонов и планшетных компьютеров среда проек­ тирования активно сотрудничает с симулятором iOS, отвечающим за создание вир­ туального устройства iPhone и iPad. Благодаря симулятору (рис. 25.1) мы с вами сможем не только осуществлять отладку своего проекта, но и имитировать весь


Рис. 25.1. Приложения для iPhone с активным меню симулятора IOS


 

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

В случае подготовки проекта Android следует задействовать симулятор Google, развернутый непосредственно на том же самом компьютере, что и Delphi ХЕ5 (рис. 25.2). Напомним, что для его установки следует запустить приложение Android SDK Manager (Start I АН Programs I Embarcadero RAD Studio I Android Tools) и скачать с сайта Google бесплатные драйверы и библиотеки (см. главу 1).


Рис. 25.2. Приложение Project1 в окне симулятора устройства Android

 

 

Интернет-браузер ТWebBrowser

Изучение компонентов, разработанных специально для мобильной платформы, мы начнем с интернет-браузера FМХ. WebBrowser. ТWebBrowser. Это во всех отношениях уникальный элемент управления, способный в два счета предоставлять пользовате­ лю доступ к интересующей его странице в Интернете.


 

Мобильная платформа                                                                                                                       =(1!1

Для этой цели URL-aдpec страницы заносится в одноименное свойство

property URL: string;

и вызывается ключевой метод компонента

prooedure Navigate; overload;

Вторая перегружаемая версия метода

prooedure Navigate(const URL: string); overload;

на свойство URL внимания не обращает, т: к. эти сведения передаются через одно­ именный параметр.

 

ЗАМЕЧАНИЕ

Кроме НТМL-страницы метод Navigate () способен загрузить и локальный файл, в данном случае в свойстве (или параметре) URL имени файла должна предшество­ вать строка file: //.

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

property OnDidStartLoad: TWebBrowserDidStartLoad;

и событие

property OnDidFinishLoad: TWebBrowserDidFinishLoad;

сигнализирующее об успешном завершении загрузки.

Допустим, что в качестве строки ввода URL-aдpeca в нашем мобильном приложе­ нии задействован редактируемый комбинированный список comЬoEdi tl: тcomЬoEdi t. В таком случае событие OnDidFinishLoad () можно задействовать для построения списка посещенных сайтов (листинг 25.1).


 

1111•-•t..........                  .........                                  !

 
t•••• "" ,.., ., ,.,,.. --• '""•••• •-• •-u-,, 11',11'• •-561                                        ••-.,--•• •••....,_.,.... , ,,..,._. "'"'"""'"""'"'...,.,,..,м,.••••..,,..,.,.,,,,,.


,.,.,.................................. ,.,.,


 

 

---,1\;,NA.••••.. •.. •hW"' ·••••••·•·><•·i -•···••••• i,i\;,>c<i",-,O,,,,;-;·•...,·,,.,,,••,,,,,                                                               ,.,,.,,,-;,•••n,,,-••••••••·••••••••·•·••••••••••••••••••'r .................... ••••t,,•••••••-''"' .....t  •••••••••.. •·•·•·••-  ...........:.

 

procedure TForml.WebBrowserlDidFinishLoad(ASender: TObject); var aURL, s:String;

i:integer; b:boolean;

Ьegin b:=false;

aURL:=LowerCase(ComЬoEditl.Text,TLocaleOptions.loUserLocale); for i:=0 to ComЬoEditl.Items.Count-1 do

Ьegin

s:=LowerCase(ComЬoEditl.Items.Strings[i],

TLocaleOptions.loUserLocale);  if      s=aURL then Ьegin

b:=true;

break;

end;

end;

if Ь=false then ComЬoEditl.Items.Add(aURL); end;


 

Если в моменг загрузки произошла ошибка, то в ответ компонент сгенерирует со­ бытие

property OnDidFailLoadWithError: ТWebBrowserDidFailLoadWithError;

Ошибка может быть связана не столько с неверным URL, сколько со сбоями в вы­ полнении сценариев на сайте, отсутствием файлов по ссьmкам и т. п.

Для быстрого перемещения по недавно посещенным страницам Всемирной паути­ ны следует обратиться к методам

prooedure GoBack;          //вернуться  назад

prooedure GoForward; //перейти вперед

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

property CanGoBack: Boolean;

property CanGoForward: Boolean;

 

Привязка к местности

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

У вашего мобильного .устро' йства имеется несколько источников получения сведе-

ний о своем местоположении. В идеальном случае это спутниковая система гло­ бального позиционирования (Global Positioning System, GPS), дающая весь·ма точ­ ный результат - при максимальном числе доступных спутников погрешность оп­ ределения координат сегодня составляет немногим более 2-3 метров! Однако спутниковые системы не всесильны, например они окажутся бесполезны, когда мо­ бильное устройство находится в железобетонных сооружениях, в метрополитене, в условиях очень плохой погоды и также в ситуации зашумления. При отсутствии GPS мобильное устройство способно извлечь сведения о своих координатах из со­ товой сети или сетей Wi-Fi, однако в последнем случае о высокой точности геоло­ кации говорить не приходится, погрешность может составлять сотни метров.

 

Датчик местоположения TLocationSensor

В модуле FМХ. sensors, хранящем исходный код классов-сенсоров, описан датчик местоположения - класс TLocationSensor (страница Sensors палитры компонен­ тов), способный возвратить в написанную на Delphi программу все необходимые сведения о расположении мобильного устройства.

Для активации датчика устанавливаем свойство

property Active: Boolean;

в состояние true.


 

Ключевое событие компонента

property OnLocationChanged: TLocationChangedEvent; TLocationChangedEvent = prooedure (�ender: TObject;

const OldLocation, NewLocation: TLocationCoord2D) of object;

генерируется в момент определения координат устройства. Параметры Oldtocat:Lon и NewLocation соответственно возвращают старую и новую широту и долготу в формате записи

TLocationCoord2D = record

Latitude: TLocationDegrees;         //широта Longitude: TLocationDegrees; //долгота

// ...

end;

Мудреное название типа данных TLocationDegrees на самом деле соответствует обычному вещественному типу данных с двойной точностью DouЫe. Целая часть значения соответствует градусам, а дробная - долям градуса.

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

  сенсор TLocationSensor;

   метка TLabel;

   интернет-браузер TWebBrowser.

Создайте новое мобильное приложение, а все остальное вы найдете в листинге 25.2.


prooedure TForml.FoпnShow(Sender: TObject);

Ьeqin

LocationSensorl.Active:=True;

end;

 

prooedur• TForml.LocationSensorlLocationChanged(Sender: TObject;

const OldLocation, NewLocation: TLocationCoord2D);

const ТХТ: String ='Широта %s Долгота %s';

URL: String = 'https://rnaps.google.com/rnaps?q=%s,%s&output=emЬed';

Ьegin

Labell.Text := Format(TXT, [NewLocation.Latitude.ToString(),

NewLocation.Longitude.ToString()]);

WebBrowserl.Navigate(Format(URL,  [NewLocation.Latitude.ToString,

NewLocation.Longitude.ToString]));

end;

 

 

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


 


 

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

https://maps.google.com/maps?q=<широта>,<дoлгoma>&output=embed

передав в параметры <широта> и <долгота> полученные от сенсора географиче­ ские координаты. Действо завершает вызов метода интернет-браузера Navigate (), отправляющего в адрес Google· наши координаты. Результат представлен на рис. 25.3 - сервис Google возвратил в браузер карту местности.


Рис. 25.3. Отображение карты местности по заданным географическим координатам

 

 

Прямое и обратное преобразования координат TGeocoder

Подавляющее большинство пользователей, увидев на экране мобильного устройст­ ва свои координаты, в лучшем случае примет их к сведению, в худшем - переста­ нет пользоваться нашими программами. Спросите почему? Полагаем, не сразу от­ ветите, какой объект скрывается за географическими координатами 59°46'18.52" с. ш. 30°19'33.77" в. д. Впрочем, сильно не расстраивайтесь, тем более, далеко не каждый географ помнит, что здесь расположена главная (Пулковская) астрономи­ ческая обсерватория Российской академии наук. Сухие цифры координат важны для специалиста, но малоинтересны для обывателя! Последнему гораздо удобнее получить от смартфона сообщение, что он стоит на углу Невского проспекта и ули­ цы Марата в двух шагах от станции метро Маяковская, чем географические коор­ динаты, пусть даже с точностью до секунды.

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

 

 



Специалистом в этой области высrупает класс TGeocoder, который вы обнаружите

в модуле System. Sensors.

 

ВНИМАНИЕ!

Класс TGeocoder выступает объектно-ориентированной оболочкой для сетевого сер­ виса кодирования/декодирования географических координат и адресных данных (схо­ жих с почтовым адресом). Поэтому для корректной работы класса необходим доступ приложения во Всемирную паутину. Конкретно для мобильных устройств, работающих под управлением IOS и Android, обычно задействуется сервис геокодирования Google.

 

Класс TGeocoder оперирует двумя разновидностями данных. С одной стороны, его интересуют географические координаты, представленные в уже встречавшемся нам формате TLocationCoord2D и используемом для хранения широты и долготы. С дру­ гой сторонь1, для обслуживания почтового адреса класс задействует более "продви­

нутый" форм.ат данных, описанный в классе TCivicAddress (табл. 25.1).

Таблица 25.1. Описание почтовых координат и адреса в классе тcivicAddress

 

Свойство

Описание

property Coord:  TLocationCoord2D;

Географические координаты

property Address: String;

Адрес

property CountryCode: String;

-

Код страны

property CountryName: String;

Название страны

property FeatureName: String;

Подробное название

property AdminArea: String;

Административно-территориальная единица, для США - название штата

property SuЬAdminArea: String;

Уточнение административно-территориальной единицы, для США - округ

property Locale: String;

Город (административный центр)

property Locality: String;

Район города

property SuЬLocality: String;

Дополнительное описание месторасположения, например квартал

property Phone: String;

Телефон

property PostalCode: String;

Почтовый код

property Premises: String;

Помещение                                                      .

property Thoroughfare: String;

Главная улица

property SuЬThoroughfare: String;

Дополнительное описание улицы

property URL: String;

Адрес интернет-ресурса в формате URL

 

Запрос на преобразование координат может быть осуществлен при посредничестве методов

class prooedure Geocode(const Address: TCivicAddress);

class prooedure GeocodeReverse(const Coords: TLocationCoord2D);


Метод Geocode ( ) осуществляет прямое преобразование, отправляя сетевому сервису интересующий нас адрес Address, а обратный метод GeocodeReverse () передает в сеть географические координаты Coords.

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

class property OnGeocode: TGeocodeEvent;

Указанное событие возвратит массив координат с широтой и долготой

TGeocodeEvent = procedw:e(const Coords: TArray<TLocationCoord2D>) of oЬject;

При решении обратной задачи трансляции широты и долготы в адрес генерируется другое событие

class property OnGeocodeReverse: TGeocodeReverseEvent;

Событие обладает единственным параметром

TGeocodeReverseEvent = procedure (const Address: TCivicAddress) of oЬject;

уже хорошо знакомым нам по табл. 25.1.

 

ЗАМЕЧАНИЕ

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

К сожалению, создав класс TGeocoder, разработчики FireMonkey не позаботились о реализации на его основе компонента, который мы смогли бы просто перенести с палитры компонентов на форму своего проекта для iPhone или iPad. Посему, для того чтобы задействовать умения класса-геокодировщика, нам придется написать несколько строк кода.

Допустим, что в нашем приложении мы планируем осуществить преобразование географических координат в адрес. В таком случае програ·ммисты Embarcadero ре­ комендуют в секции частных определений формы объявить объектную перемен­ ную TGeocoder и событие обратного преобразования OnGeocodeReverse (лис­ тинг 25.3).


type

TForml       class (TForm)

/ / ...

private


 

 

 

 

 

 

 

 

 

 

 

procedure TForml,OnGeocodeReverseEvent(const-Address: TCivicAddress);

Ьegin


Labell.Text Label2.Text

// ...

Labell0. Text

end;


:= Address.AdminArea;

:= Address.CountryCode;

 

:= Address.SuЬThoroughfare;


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

class property Current: TGeocoderClass;

Убедившись (например, с помощью функции Assigned) в наличии сервиса, смело вызываем конструктор объекта. Разработчики класса предлагают для создания эк­ земпляра TGeocoder задействовать событие OnLocationChanged () компонента­ сенсора TLocationSensor, как вы помните, генерируемого в момент получения гео­ графических координат мобильного устройства (листинг 25.5).

 

 

'

procedure TForml.LocationSensorlLocationChanged(Sender: TObject;

const OldLocation, NewLocation: TLocationCoord2D);

Ьegin

// ...

//  код листинга 25.2

// ...

if Assigned(fGeocoder)=false then Ьegin

if Asfiigned(TGeocoder.Current)  then

fGeocoder := TGeocoder.Current.Create;

if Assigned(fGeocoder)  then

fGeocoder.OnGeocodeReverse := OnGeocodeReverseEvent;

end;



if Assigned(fGeocoder) and fGeocoder.Geocoding=false then

fGeocoder.GeocodeReverse(NewLocation);

end;

 

 

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

class function Geocoding: Boolean;

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

 

 

Датчик ориентирования TOrientationSensor

Современные мобильные устройства буквально напичканы разнообразными датчи­ ками. В частности, многие смартфоны и планшетные компьютеры способны пре­ доставить пользователю сведения о наклоне устройства относительно земли, прой­ денном расстоянии и даже работать как компас. Для доступа к перечисленным сер­ висам в состав FireMonkey включен компонент TOrientationSensor, его вы обнаружите на странице Sensors палитры компонентов. Исходный код класса рас­ средоточен по двум программным модулям: Systern. Sensors и FМХ. Sensors.

Для активации компонента задействуется метод

prooedure Start;

Если сенсор бьm успешно запущен, то об этом просигнализирует свойство

property Started: Boolean; //только дпя чтения Выключение прибора обеспечит метод prooedure Stop;

Существует несколько типов сенсоров: компас (compass), сенсор наклона (inclinometer) и сенсор расстояния (distance). Об имеющихся в наличии сенсорах (все определяется классом смартфона или планшетного компьютера) можно судить по свойству

property SensorType: TOrientationSensorType; //только дпя чтения TOrientationSensorType = (CornpasslD, Cornpass2D, CornpassЗD, InclinorneterlD, Inclinorneter2D, InclinorneterЗD, DistancelD, Distance2D, DistanceЗD);

В зависимости от типа сенсора мы сможем получать ту или иную информацию об ориентации устройства (табл. 25.2).


 

 

Сенсор

Свойство

Описание

Сенсор наклона

property TiltX: DouЫe; property TiltY: DouЫe;

property TiltZ: DouЬle;

Наклон мобильного устройства

в градусах относительно осей

Сенсор расстояния

property DistanceX: DouЫe; property DistanceY: DouЬle;          1

property DistanceZ:  DouЬle;

Пройденное относительно точки предыдущего измерения расстоя- ние в метрах

property Updateinterval: DouЬle;

Как часто обновляются данные о движении

Компас

property HeadingX: DouЬle;

property HeadingY: DouЬle; property HeadingZ:  DouЬle;

Направление на север в градусах

 

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

property CompMagHeading: DouЬle; property CompTrueHeading: DouЫe;

Некомпенсированные значения окажутся в свойствах

property MagHeading: DouЬle;

property TrueHeading: DouЬle;

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

property AvailaЫeProperties: TProperties; //только для чтения

Проверка факта существования того или иного сенсора выполняется в рамках со­ бьпия

property OnSensorChoosing: TOnSensorChoosing; TOnSensorChoosing = prooedure (Sepder: TObject;

const Sensors: TSensorArray; va ChoseSensorindex: Integer) of object;

Параметр Sensors возвращает массив доступных сенсоров, а с помощью ChoseSensorindex мы можем выбирать индекс интересующего нас устройства. По­ рядок работы с событием раскрывает листинг 25.6. Здесь продемонстрирован спо­ соб поиска в мобильном устройстве встроенного компаса. С этой целью мы прове­ ряем факт вхождения свойства HeadingX в перечень свойств AvailaЫeProperties к_аждого из датчиков.



procedure TForml.OrientationSensorlSensorChoosing(Sender: TObject;

const Sensors: TSensorArray; var ChoseSensorindex: Integer);

var i:     Integer;

Ьegin

ChoseSensorindex:=  -1;

for i  := О to High(Sensors)  do

if (TCustomOrientationSensor.TProperty.HeadingX in

TCustomOrientationSensor(Sensor.s[I]) .AvailaЫeProperties) then Ьegin

ChoseSensorindex:= i;

Break; end;

if ChoseSensorindex < О then ShowMessage('Koмпac отсутствует!');

end;

 

 

ЗАМЕЧАНИЕ

К сожалению, в современных симуляторах IOS эмуляция сенсора ориентирования по­ ка отсутствует. Поэтому проверка работоспособности нашего проекта может осущест­ вляться только на реальном устройстве.

В момент получения сведений от датчиков у сенсора генерируется событие

property OnDataChanged: TNotifyEvent;

Порядок работы с событием рассмотрен в листинге 25.7.


procedure TForml.OrientationSensorlDataChanged(Sender: TObject);

Ьegin

Labell.Text := Format ( 'Компас (Х, У, Z): (%.2f, %.2f, %.2f) ', [OrientationSensorl.Sensor.HeadingX, OrientationSensorl.Sensor.HeadingY, OrientationSensorl.Sensor.HeadingZ]);

end;

 

 

Менеджер датчиков TSensorManager

Рассмотренные ранее датчики местоположения TLocationSensor и ориентирования тorientationSensor представлены на палитре компонентов FireMonkey и включают­ ся в приложение э-лементарным переносом компонента на форму мобильного про­ екта. Однако работоспособность современных мобильных устройств обеспечивает более существенный перечень датчиков, сканеров и сенсоров. Это подтверждает специальный тип данных тsensorcategory, который классифицирует существующие датчики


 


TSensorCategory = (Location,

Environmental, Motion, Orientation, Mechanical, Electrical, Biometric, Light, Scanner);


//датчик  расположения

//датчик данных окружающей среды

//датчик движения

//датчик ориентации в пространстве

//механический датчик

//электрический датчик

//биометрический датчик

//датчик  освещения

//сканер


Как быть с ними? Ответ на заданный вопрос можно получить, изучив определен­ ный в модуле System. sensors менеджер датчиков тsensorManager. Этот абстрактный класс позволяет идентифицировать имеющиеся в мобильном устройстве датчики и передать получаемые от них сведения в распоряжение программиста.

 

ЗАМЕЧАНИЕ

Для работы с датчиками·в состав модуля System.Sensors включен ряд специализи­ рованных классов, представляющих собой программную оболочку для разнообразных устройств. Описание большинства из них представлено в приложении 4.

Для подключения к менеджеру датчиков следует воспользоваться свойством

class property Current: TSensorManager; //только для чтения

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

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

property Active: Boolean;

Если менеджер отключен -    проверяем возможность включения

property CanActivate: Boolean; //�олько для чтения

и передаем в свойство Active значение true или вызываем метод

prooedure Activate;

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

property Sensors[Index: Integer]: TCustomSensor; �/только для чтения

Общее количество датчиков хранится в свойстве

property Count: Integer;

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

function GetSensorsByCategory(Category: TSensorCategory): TSensorArray;

возвращающий массив сенсоров заранее определенной категории, и метод

function GetSensorsByFilter(Filter: TSensorFilter): TSensorArray;


 

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

TSensorFilter = referenae to  function (Sensor: TCustomSensor): Boolean;

отбирающую требуемые датчики.

Работа с менеджером сенсоров продемонстрирована в листинге 25.8, отражающем порядок обращения к сенсору мобильного устройства заданной категории.


var SA: TSensorArray; SM: TSensorManager;

/ / ...

proaedure TForml.FormCreate(Sender: �Object);

Ьegin

SM := TSensorManager.Current;

if SM.CanActivate then SM.Activate

Ьegin

SM.Activate;

SA  :=  TSensorManager.Current.GetSensorsByCategory(

TSensorCategory.Electrical);  if       (Length(SA)>0) and (SA[0] is TCustomElectricalSensor) then Ьegin

if not TCustomElectricalSensor(SA[0J) .Started then Ьegin

TCustomElectricalSensor(SA[0]) .Start;

//дальнейшая работа с датчиком

end;

end;

end;

end;

 

 

Обратите внимание, что переменная SA ( определяющая динамический массив сен­ соров тsensorArray) и объектная переменная sм: тsensorManager объявлены гло­ бально. Такой подход позволит обращаться к менеджеру датчиков и конкретному датчику (или датчикам) из любого места приложения.

 

Увеличительное стекло TMagnifierGlass

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


 


нента тмagnifierGlass, предназначенного исполнять роль увеличительного стекла (рис. 25.4).

Степень увеличения настраивается с помощью свойства

property LoupeScale:  Single;

Центр увеличения устанавливается свойством

property ZoomRegionCenter: TPosition;

По умолчанию он расположен в точке (О, О).

Для управления фоновым цветом следует воспользоваться свойством

property BackgroundColor: TAlphaColor;//пo умолчанию белый

Стекло может принимать традиционную круглую или прямоугольную форму

property LoupeMode: TLoupeMode; default TLoupeMode.lmCircle; TLoupeMode = (lmCircle, lmRectangle);


Рис. 25.4. Компонент TMagnifierGlass

 

ЗАМЕЧАНИЕ

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

 

Подсистема уведомлений

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


 

работающим в фоновом режиме приложением и пользователем строится на основе центра уведомлений (notification center). Центр уведомлений способен "достучать­ ся" ДО владельца мобильного устройства по крайней мере тремя способами:

    выводом на экран сообщения (рис. 25.5);

   отображением информационного баннера (для планшета);

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

 

ЗАМЕЧАНИЕ

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

Settings I Notifications.


Вывод на экран сообщения                                Число на значке приложения

 

Рис. 25.5. Примеры уведомлений в IOS

 

Как и положено, для объектно-ориентированного языка программирования уве­ домление представлено в виде самостоятельного объекта, построенного на основе класса TNotification. Собственно класс TNotification является едва ли не самым элементарным из всех встречавшихся нам на этих страницах книги (листинг 25.9).

-----�-=,--к---

 
r-·····  ....-,,..,,.,., . •·...

\ Allenlиr 25.9. ОбъЯВJ11еиме кnнса Dlot!r

i ••-

TNotification = class puЬlic


Name: string; AlertBody: string;


//уникальный идентификатор уведомления

//текстовое содержание уведомления


AlertAction: string;                   //текст альтернативного действия_ ApplicationiconBadgeNurnЬer: Integer; //число на значке приложения


 


FireDate: TDateTime; EnaЬleSound: Boolean; HasAction: Boolean;

end;


//дата-время вывода уведомления

//наличие звукового сопровождения

//наличие кнопки альтернативного действия


 


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

Для отправки уведомления нам понадобится доступ к функционалу центра уведом­ лений, это вотчина интерфейса IFМXNotificationCenter, объявление которого вы обнаружите в модуле FМX.Notification. Интерфейс весьма аскетичен, в его арсена­ ле меньше десятка методов. Наиболее востребованы методы

procedure PresentNotification(const ANotification: TNotification);

и

procedure ScheduleNotification(const ANotification: TNotification);

Это два·специалиста по отправке уведомлений. Разница между ними только в том, что PresentNotification() выводит уведомление немедленно (невзирая на состоя­ ние поля FireDate), а метод ScheduleNotification(), наоборот, является большим педантом и отобразит сообщение (или баннер) в точно назначенный срок.

Пара методов нацелена на сброс уведомлений. Наиболее кардинален метод

pro�ure.CancelAllNotifications;

он отменит все запланированные в системе уведомления. Для избирательного отка­ за от уведомления лучше задействовать перегружаемый метод

procedure CancelNotification(const AName: string); overload;

procedure CancelNotification(const ANotification: TNotification); overload;

 

Здесь нам потребуется либо текстовый идентификатор уведомления, либо ссылка на объект сбрасываемого уведомления.

Для работы с дополнительными картинками с числовыми значе·ниями, размещае­ мыми над значком приложения, вызывайте процедуры:

procedure SeticonBadgeNurnЬer(const ACount: Integer);

procedure ReseticonBadgeNurnЬer;

Первая процедура устанавливает цифровое значение, вторая -  сбрасывает.

Мы уже почти готовы работать с центром уведомлений, осталось лишь уяснить два нюанса. Во-первых, центр уведомлений может быть отключен пользователем, по­ этому наше приложение стоит научить проверять его состояние. Во-вторых, надо разобраться с вопросом получения заветного интерфейса IFМXNoti_ficationcenter. И в первом, и во втором случае нашим помощником станет класс TPlatformServices, описанный в модуле FМX.Platform.


 

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

Несмотря на наличие у TPlatfoпnServices самостоятельного конструктора, не стоит создавать экземпляр класса с помощью традиционного вызова create (). Вместо этого следует обратиться к свойству

class property Current: TPlatfoпnServices;

выступающему интеллектуальной надстройкой над конструктором.

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

function Supp?rtsPlatformService(const AServiceGUID: TGUID): Boolean;

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

function GetPlatfoпnService(const AServiceGUID: TGUID): IInterface;

Как правило, методы SupportsPlatfoпnService () и GetPlatfoпnService () должны вызываться совместно, порядок обращения к интерфейсу центра уведомлений от­ ражен в листинге 25.10.


uses FМX.Platform, FМX.Notification;

 

function GetNotificationCenter:IFМXNotificationCenter;

Ьegin

if     TPlatfoпnServices.Current.SupportsPlatfoпnService (IFМXNotificationCenter)

then

Result:= TPlatfoпnServices.Current.GetPlatformService (IFМXNotificationCenter) as IFМXNotificationCenter

else Result:=nil;

end;

Пока не забывайте о функции GetNotificationCenter (), она еще не раз·пригодится нам в этой главе.                                                                                                             1

Пример вывода текстового уведомления в назначенное время

Исполняющееся в фоновом режиме приложение имеет полное право отправить пользователю текстовое уведомление. В простейшем случае нам достаточно ука­ зать идентификатор, текст и время вывода уведомления (листинг 25.11).


 

 

 

procedure ScheduleNotification(const NotificationID,

NotificationTxt:  string;

ADateTime: TDateTime);

var Notification: TNotification; NC: IFМXNotificationCenter;

Ьegin

NC :=  GetNotifгeationCenter;

if Assigned(NC) then Ьegin

Notification:=TNotification.Create;

Notification.Name := NotificationID; Notification.AlertBody := NotificationTxt; Notification.FireDate := ADateTime; NC.ScheduleNotification(Notification); Notification.Free;

end;


..... !..

-- --,, {


end;

 

Если по какой-то причине от вывода уведомления на экран можно отказаться, то предлагаем воспользоваться исходным кодом, представленным в листинге 25.12.


Prooedure ResetNotification(const  NotificationID:string);

var NC: IFМXNotificationCenter;

Ьegin

NC  :=  GetNotificationCenter;

if  Assigned(NC)    then  NC.CancelNotification(NotificationIP);

end;

 

На вход процедуры ResetNotification() следует передать единственный аргумент с идентификатором уведомления.

 

Вызов приложения из окна уведомления

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


NC :=  GetNotificationCenter;

if Assigned(NC) then Ьegin

Notification:=TNotification.Create;


 

Notification.Name := 'Notification_id'; Notification.AlertBody := 'Выбор действия'; Notification.AlertAction:='B приложение'; Notification.HasAction:=true;

NC. PresentNotification.(Notification); Notification.Free;

end;

 

 

Воспользовавшись кодом из листинга 25.13, вы получите сообщение с двумя кноп­ ками (рис. 25.6).

Щелчок по кнопке В приложение развернет на экране приложение, до сих пор ра­ ботавшее в фоновом режиме.


Рис. 25.6. Уведомление, позволяющее перейти к приложению

 

 

Пример размещения числа на значке приложения

Как уведомить пользователя, что он пропустил 1 О входящих звонков или получил 20 текстовых сообщений? Очень просто - разместить над значком приложения числовое значение. В этом нам помогут листинги 25 .14 и 25 .15.

!Jhмmuн' •. , •.11с:

-\'1                                                                                      •-v•

 

procedure SetBadgeNurnЬer(Value:  integer);

var NC:  IEМXNotificationCenter;

Ьegin

NC :=  GetNotificationCenter;

if Assign�d(NC) then NC.SeticonBadgeNurnЬer(Value);

end;

 

1,....                         •·•·····..·······•·.. ···•··..·••······..•·•·•··.,...,... ............,... .•........•,.•• ........,,..,1,•·······

8.15. С1рО8 'IМCtlOIIDrO ----


......                                                       ................... ,.


- ........,1 -  ........ \, w....1.. ,..


procedure ResetBadgeNurnЬer;

var NC:  IEМXNotificationCenter;

Ьegin

NC :=  GetNotificationCenter;

if Assigned(NC) then NC.ReseticonBadgeNurnЬer;

end;


 

 


мооильная платформа

 

Процедура setBadgeNшnЬer () позволяет отправить в центр уведомления число, которое мы планируем разместить на значке приложения, а процедура ResetBadgeNшnЬer () осуществляет сброс предыдущего значения.

 

Звонок по телефону

Завершая главу, посвященную исключительно мобильной платформе, не ri,e вспомнить об основном предназначении телефона - осуществление телефонных звонков. И в этом случае FireMonkey вновь оказывается на высоте, предоставляя разработчику искmочительно простой и удобный API, позволяющий достичь две цели:

    получить все необходимые сведения об операторе связи;

   совершить телефонный звонок.

Обе задачи решаются с помощью описанного в модуле FМХ. PhoneDialer интерфейса IFМXPhoneDialerService. Доступ к интерфейсу осуществляется при посредничестве уже встречавшегося в этой главе класса TPlatformServices (листинг 25.16).


var PDService: IFМXPhoneDialerService;

Ьegin

if     TPlatformServices.Current.SupportsPlatformService(  IFМXPhoneDialerService, IInterface(PDService)) then

Ьegin

//работа с интерфейсом IFМXPhoneDialerService

end else raise Exception.Create('Heт доступа к IFМXPhoneDialerService! ');

end;

 

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

function GetCarrier: TCarrier;

интерфейса IFМXPhoneDialerService. В результате мы получим доступ к абстракт­ ному классу тcarrier (табл. 25.3).

 

Таблица 25.3. Возможности класса тcarrier

 

Свойство

Описание

function GetisoCountryCode: string;

 

'

Кодировка страны в соответствии со стандартом ISO 3166-1. Бывает трех видов: 2-символьная,

3-символьная и числовая. Например, для России: "RU", "RUS" и 643

function GetMobileCountryCode:

strinq;

Код страны мес (moЬile country code). Например, для России код МСС=250


 

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

 

Свойство

Описание

function GetMobileNetwork: string;  .

Код оператора MNC (moblle network code). Например, для операторов России: МТС MNC=01, Мегафон MNC=02, Билайн MNC=28 и 99

function GetCarrierName: string;

Несущая (частота), используемая для связи

property OnCarrierChanged: TOnCarrierChanged;

Определяет, что событие генерируется в момент смены оператора

 

Для того чтобы пользователь смог сделать телефонный звонок, достаточно пере­ дать номер телефона в метод

function Call(const APhoneNurnЬer: string): Boolean;

Для получения сведений о входящих звонках следует обратиться к методу

function GetCurrentCalls: TCalls;

Метод возвращает массив, элементами которого выступают отдельные звонки, описанные в рамках абстрактного класса тcall (листинг 25.17).


 


TCall puЫic


class aЬstract


function GetCallState:  TCallState;  virtual;  aЬstract;

function GetCallID: string; virtual; aЬstract; end;

 

Благ щаря тcall мы можем выяснить состояние звонка и номер телефона абонента, звонившего нам.


 

ГЛАВА 26

 

 

Законы трехмерного мира

 

 

 

 

Первые страницы темы о 3D-проектах посвятим не программированию, а геомет­ рии. Конечно же, мы не станем доказывать теоремы, но и без их упоминания не обойдемся, так что если вы в школе не очень дружили с точными науками - сове­ туем проявить совсем немного терпения и познакомиться с основными законами трехмерного мира FireMonkey.

 

Система координат

Человек всегда бьm склонен к систематизации, поэтому уже очень давно были раз­ работаны трехмерные системы координат. Таких систем несколько, и самая рас­ пространенная из них представлена на рис. 26.1. Это так называемая прямоугольная декартова система координат. Как и полагается для 3D-системы, классическая пря­ моугольная система координат содержит три ортогональных (перпендикулярных) координатных оси:

    ось абсцисс х идет слева направо;

   ось ординат у уходит вверх;

   если ось аппликат z направлена из точки начала координат (О, О, О) навстречу наблюдателю (рис. 26.1, а), то это правая система координат, иначе речь идет о левой (рис. 26.1, б) системе.

 

                                                                                                       х

Рис. 26.1. Прямоугольная декартова система координат: а -  правая, б -  левая


 

Предложенная на рис. 26.1 система неоднократно встречалась нам в школьных и университетских учебниках, и поэтому сразу приходит на ум, когда мы вспоминаем о трех измерениях. Однако в Delphi так полюбившаяся нам по школе система при­ меняется в слегка модифицированном виде. И это не потому, что инженеры­ программисты из Embarcadero хотят отучить нас от шаблонного мышления (хотя это было бы неплохим объяснением). Все дело заключается в традициях двухмер­ ных графических систем.

Еще со времен становления первых устройств видеовывода (еще в те времена, ко­ гда не существовало ни Microsoft Windows, ни OS Х) инженеров-разработчиков электроники и программистов устраивал следующий подход:

    начала двухмерных координат находятся в левом верхнем углу экрана;

   ось х традиционно направлена слева направо;

   ось у устремлена вниз экрана.

Почему бьmо принято столь странное на первый взгляд решение? Потому что эта система целиком и полностью соответствовала европейскому стилю чтения книг слева направо сверху вниз. Более того, именно так (слева направо сверху вниз) пробегал по экрану кинескопа "древних'' мониторов электронный луч, рисуя на нем символы первых программ. Одним словом, так 'получилось... Позднее текстовый режим видеовывода вошел в норму, и любой работающий в современных средах проектирования программист четко знает, как устроена двухмерная оконная систе­ ма координат (рис. 26.2).


 

Рис. 26.2. Оконная система координат 2D-приложения

 

Когда перед разработчиками программного обеспечения из компании Embarcadero RCTШI RОП С (1 том, как научить FireMonkey работать в трех измерениях, они при­ няли самое рациональное для сложившейся ситуации решение. Программисты соз­ дали систему координат приложения ЗD, в которой:

     центр координат (О, О, О) расположен в центре клиентской области формы;

    оси абсцисс х и ординат у сохранили традиционные для текстового режима на­ правления;

    ось аппликат z устремилась вглубь формы.


 

 

.:1с11шны тр1:мм11рниги мира                                                                                                                       371

Если вы вернетесь к рисунку 26.1, а, на котором представлена правая система координат, и, не трогая ось х, мысленно развернете плоскость, создаваемую осями у и z, на 180 градусов, то получите систему координат, применяемую в приложении FireMonkey (рис. 26.3).


Рис. 26.3. Система координат ЗD-приложения FireMonkey

 

 

ЗАМЕЧАНИЕ

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

 

 

Единица измерения

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

Благодаря такой значимой роли пиксел выступает базовой физической единицей измерения размеров в обычных двухмерных деловых приложениях. Например, мы создаем окно размерами 640х480 пикселов, размещаем на окне кнопку размерами 80х30 пикселов и т. д.

Однако физическая единица измерения в виде пиксела не подходит для описания объектов из реального мира. Попробуйте измерить в пикселах вашу комнату, др м­ лющую на окне кошку и (для уяснения масштабов) Солнечную систему. Не скажу, что это сделать невозможно, но то, что совсем неудобно - это точно. Кроме того, пиксел - это дискретная единица, не существует понятия 1/2 или 1/3 пиксела. Именно поэтому основной единицей измерения в 3D-проектах выступает не пик­ сел, а некая условная логическая единица.


 

ВНИМАНИЕ!

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

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

 

Точка

Зная правила построения трехмерной системы координат, нам не составит никакого труда задать координаты точки в этом пространстве. Тем более что в FireMonkey для этого есть много помощников, один из них - представленная в листинге 26.1 запись TPointЗD.


type TPointЗD = record

Х: Single; //координата Х У: Single; //координата У Z:  Single;  //координата  Z

private

 

end;

 

 

Запись состоит из трех полей вещественного типа, каждое из которых отвечает за хранение координаты точки по соответствующей оси.

 

Вектор

Разработчики ЗD-моделей в своей работе охотно применяют векторы. Даже, вернее сказать, что без векторов невозможно построение трехмерных алгоритмов. С точки зрения математики вектор представляет собой направленный отрезок от одной точ­ ки к другой. Вектор не только хранит сведения о координатах точки в пространст­ ве, но еще и указывает направление на эту точку (рис. 26.4).

Для описания вектора необходимы две точки. В FireMonkey начальной точкой век­ тора всегда выступает точка начала координат (О, О, О), поэтому при программиро­ вании мы станем определять только координаты его конечной точки.

В Delphi трехмерный вектор описывается структурой тvectorзo. В прwюжении 1 мы достаточно подробно обсудим эту запись и возможности векторов: операции сложения и вычитания, масштабирование вектора, повороты и многое другое.


 

 

. :: ,,-пи треvмерио20 мира                                                                                                                      373


х

 

 

у

Рис. 26.4. Определение точки А с помощью вектора

 

ЗАМЕЧАНИЕ

Для того чтобы однозначно задать положение и ориентацию трехмерного объекта FMX в пространстве, необходимы несколько векторов. Первый вектор определяет по­ ложение объекта. Второй вектор хранит угол поворота объекта. Третий вектор опре­ деляет центр вращения, относительно которого повернут объект. Именно поэтому в базовом для трехмерных объектов классе тcontrolЗD мы найдем три свойства (Position, RotationAngle и RotationCenter), доступ к которым может быть предос­ тавлен в векторной форме.

 

Объект

Какова должна быть последовательность действий программиста, чтобы описать объект в трехмерном пространстве? Для этого следует сделать 4 шага.

В первую очередь надо позаботиться об определении координат точек, которые позднее превратятся в вершины фигуры. Например, для того чтобы мы смогли по­ строить простейший 3D-объект с мудреным названием "тетраэдр", нам понадобятся 4 точки. Однако для отображения на экране простейшего многогранника сведений о координатах его вершин далеко не достаточно. Взгляните на рис. 26.5, а, на кото­ ром обозначены точки А, В, С и D. Полагаем, что не каждый наблюдатель с первой попытки угадает в них заготовку тетраэдра.

На втором этапе описания на основе сведений о вершинах 3D-объекта необходимо определить его грани. Трехмерные объекты состоят из нескольких граней, в случае тетраэдра их всего четыре. Вне зависимости от сложности объекта для компьютера эти грани являются абсолютно плоскими. Для того чтобы механизм графического вывода без проблем превратил вершины в грани, ПР!f объявлении вершин в исход­ ном коде программы (или в файле с данными фигуры) их следует перечислять в определенном порядке. В противном случае мы рискуем увидеть на экране совсем не то, что рассчитывали.

 

ЗАМЕЧАНИЕ

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


 

а)                                            б)                                          в)                                         г)

А

 

z

 

D

х

 

 

 

у                                            у                                          у

Рис. 26.5. Определение тетраэдра в трехмерном пространстве

 

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

 

ВНИМАНИЕ{

При обходе вершин грани графический механизм FireMonkey рассчитывает на то, что они объявлены по часовой стрелке.

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

 

ЗАМЕЧАНИЕ

На самом деле ориентация граней трехмерных объектов FireMonkey описывается еще сложнее - для этого для каждой из вершин грани определяется отдельный вектор нормали.

Заключительным, четвертым по счету действием создателя 3D-объекта становится раскраска объекта в подходящий цвет. В FireMonkey предусмотрено несколько ме­ ханизмов окраски поверхности, в том числе градиентные и текстурные заливки.

 

Фрейм

На первый взгляд может показаться, что набор стандартных ЗD-фигур со страницы 3D Shapes палитры компонентов FireMonkey весьма скромен. Однако постараемся убедить вас, что за счет них просто создавать более сложные структуры.

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

 

 



 

 

законы трехмерного мира                                                                                                                        375

страцией того, как, используя простые подручные ЗD-фигуры, можно создать более

"рп одвинутые" модели. Как видите, "шедевр" автомобилестроения собран из не-

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


Рис. 26.6. Фрейм "грузовик"

 

Заострим внимание читателя еще на одной важной детали рис. 26.6. Изучив окно структуры (Structure), вы заметите, что все задействованные в построении грузо­ вика ЗD-фигуры принадлежат компоненту RectangleЗDl (это рама автомобиля). Та­ кое объединение нескольких объектов обычно называют фреймом. Важнейшая особенность фрейма в том, что все вошедшие в его состав многоугольники полу­ чают описание не в координатах сцены, а в системе координат головного объекта. Как результат, существенно упрощается процесс визуального проектирования (по­ ворот рамы автомобиля приведет к соответствующему перемещению и всех оста:1ьных объектов фрейма) и программное управление объектами (во время дви­ жения грузовика по сцене колеса могут вращаться относительно рамы автомобиля).

 

ЗАМЕЧАНИЕ

В качестве основы фрейма можно использовать специальный компонент TDummy, ко­ торый вы обнаружите на странице 3D Scene палитры компонентов.

 

 

Проекция

При желании трехмерная сцена FireMonkey может оказаться бесконечной или по крайней мере весьма большой (это преимущество достигается благодаря использо-


 

ванию вещественных чисел при определении логической единицы измерения). По­ этому никто не запрещает нам "втиснуть" в ЗD-пространство приложения Delphi все космические тела нашей галактики и по вечерам наслаждаться мерцанием звезд не на небе, а на экране своего компьютера. Человек - существо любознательное, еще в стародавние времена он мечтал быть ближе к звездам и для этого изобрел телескоп. Телескоп не способен перенести астронома на другую планету, но он по­ зволил приблизить картинку выбранного участка неба.

Зачем мы вспомнили о телескопе? А затем, что принцип работы телескопа позво­ лит нам разобраться с идеей построения ЗD проекционной системы. Для того чтобы мы с вами увидели интересующий нас объект, например Марс, нам надо осущест­ вить две важных подготовительных процедуры:

l . Установить нужное направление телескопа.

2.             Подобрать подходящее фокусное расстояние линз.

Если все сделано верно, то перед наблюдателем появится планета, названная в честь бога войны.

В приложениях ЗD для наблюдения за сценой вместо телескопов используют спе­ циальные объекты, называемые камерами. Камера помогает создавать программ­ ную проекцию области, воспроизводимой на экране.

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

 


Невидимая

область


Видимая

область


 

 

 

 

 

 

 

 

отсечения                       Задняя плоскость

отсечения

Рис. 26.7. Принцип построения ЗD проекционной системы

 

Выбранная область просмотра может находиться в любом месте трехмерного про­ странства, для того чтобы она превратилась в полноценную картинку, осуществля­ ется операция переноса в переднюю область отсечения (рис. 26.8).


 

 


.законы трехмерного мира                                                                                                                        377

 

Видимая область                Перенос

 

 

 

 

 

х

 

 

 

у                                                    у

Рис. 26.8. Перенос системы координат в переднюю область отсечения

 

Для того чтобы трехмерный объект смог корректно отображаться на плоском двухмерном экране, к его вершинам применяется преобразование, которое отобра­ жает трехмерные координаты на двухмерные координаты окна. Преобразование ко()рдинат осуществляется с помощью квадратной матрицы тмatrixЗD (см. прwю­ же11ие 1).

 

 

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

 

ГЛАВА 27

 

 

Проектируем 3D-сцены

 

 

 

 

Актеру для полного перевоплощения в образ необходима сцена - тяжело почувст­ вовать себя Отелло (или'Дездемоной) в голом поле... Поэтому еще в старозаветные времена даже бродячие труппы комедиантов перед спектаклем сооружали себе хотя бы примитивную сцену из повозок. А что уж говорить о требованиях профессио­ нальных актеров! Во всех эпохах для них воздвигались куда более серьезные сце­ ны. Вспомните театры Древней Греции, амфитеатры Римской империи и знамени­ тый лондонский театр "Глобус", на подмостках которого давались гениальные пье­ сы Шекспира. Одним словом, для исполнителей всегда требовалась сцена, ведь без нее не может существовать полноценный театр.

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

   источники света TLight, способные освещать сцену;

   компоненты-камеры тcamera, позволяющие установить точки наблюдения за сценой;

   прокси-объекты TProxyObj ect, умеющие взять на себя обязанности по замеще­ нию любых ЗD-объектов;

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

 

Построение сцены

Разработчику,_ не имеющему опыта проектирования ЗD-приложений, на первых по­ рах придется нелегко. Прежде чем третья координата приложений FireMonkey из хулигана превратится в вашего доброго помощника, пройдет небольшой адаптаци­ онный период. Чтобы минимизировать продолжительность "борьбы" с третьим измерением. предлагаем сразу спроектировать трехмерную сцену.

 

 



ПJIOP.l(m11pyeм ЗD-сцеJ.Jы                                                                                                                             37§

ЗАМЕЧАНИЕ

При обсуждении особенностей формы TFonnЗD (см. главу 26) уже упоминались осо­ бенности координатной системы трехмерной формы. Начала координат находятся в центральной части формы, именно в эту точку и попадают все размещаемые на форме компоненты. Ось абсцисс х направлена из точки (О, О, О) вправо, ось ординат у­ вниз, виртуальная ось аппликат z уходит в глубину формы.

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

Предлагаем наш первый опыт проектирования объемных сцен приобрести при соз­ дании фрагмента ЗD-комнаты. Создайте новый проект FireMonkey ЗD Application. На странице 3D Scene палитры компонентов найдите источник освещения TLight и положите его на форму. По умолчанию он окажется в точке с координатами (О, О, О), соответствующей центру клиентск?й области формы.

Теперь наступила очередь стен и пола. Для их создания нам понадобится несколько компонентов TPlane со страницы 3D Shapes. Для того чтобы один из экземпляров класса TPlane превратился в пол сцены, вы можете немного повозиться с ним, пе­ ремещая объект с помощью мыши.

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

 

 

 

Левая панель: Position (-9, О, О) RotationAngle (О, 270,180)


 

Свет:

U

 
Position (О, О, О)


Правая панель:

Position (9, О, О) RotationAngle (О, 90, 180)


 


Нижняя панель: ---i-+--,;-­

Position (О, 7, О) RotationAngle (90, О, О)

 

Рис. 27.1. Модель ЗD-комнаты


Задняя панель: Position (0, О, 8) RotationAngle (0, О, О)


 

 


380                                                                                                                                                         Главв 27

 

Когда вам это наскучи_тс, делайте следующее:

  в свойство Position() компонента-плана передайте значения (х=О, У=7, Z=O);

  в свойство RotationAngle () направьте углы поворота (х=90, У=О, Z=O).

Не успеете оглянуться, а претендующий стать полом компонент уже опустится вниз по оси у на 7 условных единиц и развернется относительно оси х на 90 граду­ сов. Вам осталось задать подходящие значения ширины (wictth) и высоты (нeight) компонента и, при желании, подобрать текстуру для заливки пола (подключив к свойству мaterialSource один из компонентов со страницы Materials). Что надо сделать с остальными панелями, вам подскажет рис. 27.1.

Как видите, для создания простейшей трехмерной сцены в Delphi от нас не потре­ бовалось никаких усилий. Единственное и достаточное условие для успеха - зна­ ния о координатной системе 3D-проекта.

 

Источник света, класс TLight

При построении сцены не обойтись хотя бы без одного источника света. Поэтому нам стоит поговорить о классе TLight, благодаря которому мы сможем создать са­ мые разнообразные световые шоу. Как и все другие 3D-элементы управления, класс TLight построен на фундаменте тcontrolЗD и поэтому обладает всеми стандартными способами позиционирования, основанными на свойствах Position и RotationAngle.

 

ВНИМАНИЕ!

Источник света TLight - важный, но не единственный участник сцены FireMonkey. Результат светового оформления во многом зависит от материала, из которого сде­ лан освещаемый объект. Для подключения материала (см. главу 29) к трехмерному элементу объекта используют свойство MaterialSource.

Особенности формирования светового потока в первую очередь зависят от состоя­ ния свойства

property LightType: TLightType;

TLightType =  (ltDirectional, ltPoint,  ltSpot);

Самый простой из перечисленных типов источников освещения - направленный (ltDirectional) световой поток. В этом случае от источника идет своего рода стена света, не оставляя шанса на появление каких-либо полутонов и теней (рис. 27.2). Точечный источник ltPoint испускает свет равномерно во всех направлениях, чем дальше от точечного источника вы разместите объект, тем меньше он будет осве­ щен. Прожекторный тип ltSpot испускает один направленный'луч света в заданном направлении. Свет идет от центральной точки, перемещается в определенном на­ правлении и создает плавные переходы от яркого к темному по краям.

 

ЗАМЕЧАНИЕ

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

 

 



 Проектируем 30-сцены                                                                                                                            J§1

 

 

 

 

 

 

 

Рис. 27.2. Виды освещения

 

Любой источник света характеризуется цветом испускаемого светового потока, для его настройки предназначено свойство

property Color: TAlphaColor;

Если источник освещения работает в режиме прожектора (1tspot) или точечного источника (ltPoint), то стоит обратить внимание на пару свойств- SpotCutOff и SpotExponent. Свойство

property SpotCutOff: Single;

определяет порядок отсечения внешней границы луча от прожектора. С увеличени­ ем знач ния свойства переход между светом и тенью становится все более плавным и размытым. Так, у точечного источника это значение по умолчанию равно 180 единицам, а у прожекторного- всего 60.

Or состояния свойства

property SpotExponent: Single;

зависит степень фокусировки луча (чем больше значение, тем выше фокусировка). У точечного источника речь о фокусировке не идет, поэтому свойство устанавлива­ ется в О, а у прожекторного значение по умолчанию составляет 4.

 

ЗАМЕЧАНИЕ

Библиотека FiteMonkey ограничивает число источников света на сцене 100 единица­ ми. Информация об этом хранится в константе MaxLightCount, которую вы обнаружи­ те в модуле FМХ. TypesЗD.

 

 

Камера, класс TCamera

Вы бывали в саду камней? Самый знаменитый из японских садов - сад Реан-дзи, состоящий всего из 15 камней, вот уже полтысячелетия предлагает посетителям решить интересную задачу - найти такую позициk), из которой будут видны все камни. Однако с тех пор как существует сад, этого никому не удавалось! Сад спла­ нирован так, что наблюдатель (если он конечно не способен воспарить над пло­ щадкой) будет всегда видеть лишь 14 камней.


 


382                                                                                                                                                         Глава 27

 

Обсуждая применяемую в трехмерных проектах форму TFormЗD, мы уже упоминали тот факт, что 3D-форма имеет встроенную камеру, определяющую позицию на­ блюдателя. Инкапсулированная камера неподвижна, она находится прямо перед сценой и нацелена на точку с координатами (О, О, О). Статичная камера не позволя­ ет изменить ни точку, ни утол просмотра, поэтому для создания настоящих дина­ мичных сцен нам стоит познакомиться еще с одним элементом управления - клас­ сом тcamera. После знакомства вы без труда сможете разрабатывать проекты своих виртуальных садов. Посетители ваших садов смогут легко плавно изменять точку наблюдения или переключаться между несколькими заранее подготовленными точками.

Применение камеры несложно. Расположите компонент тcamera на форме и на­ правьте "объектив" на нужный участок сцены (рис. 27.3). Чтобы камера стала активной, ее следует подключить к свойству camera формы и отключить у формы встроенный экземпляр камеры (свойство UsingDesignCamera). На практике это зай­ мет две строки кода (листинг 27.1).


prooedure TForml.RadioButtonlClick(Sender: TObject);

Ьegin Forml.UsingDesignCamera:=false; Forml.Camera:=Cameral;

end;

 


Рис. 27.3. Пример сцены с двумя камерами

 

 



Проонmируам ЗD-сц0ны                                                                                                                            383

ЗАМЕЧАНИЕ

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

 

ВНИМАНИЕ!

Для того чтобы воспользоваться альтернативной камерой, недостаточно разместить компонент тcamera на форме. Для того чтобы именно эта камера стала активной, ее следует подключить к свойству Camera формы и установить свойство формы UsingDesignCamera в состояние false.

Из опубликованных в классе тcamera свойств интерес для разработчика представля­ ет лишь

property Target: TControlЗD;

упрощающее выбор целевого объекта для объектива камеры.

 

Объект-заместитель, класс TProxyObject

Объект-заместитель способен взять на себя обязанности по графическому выводу любого из трехмерных объектов (потомков класса тcontrolЗD). Дг я этого достаточ­ но подключить целевой объект к свойству

property SourceObject: TControlЗD;


Рис. 27.4. Представление целевых объектов с помощью объекта-заместителя


 

 


384                                                         Глввн 27

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

 

Макет, класс TDummy

Из всех компонентов, которые вы обнаружите на странице 3D Scene, самым не­ приметным (как в прямом, так и в переносном смысле) можно считать тоuппnу. Единственная задача компонента - стать основой для фрейма (объектов, объеди­ ненных в одну логическую группу).

Вошедшие во фрейм ЗD-фиrуры будут описываться не в системе координат сцены, а в локальной системе координат головного объекта (макета). Объединение объек­ тов во фреймы сушественно упростит как процесс проектирования сцены, так и программное управление объектами.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 



 

 

 

 

 

 

 

· ГЛАВА 28

 

 

 

 

Геометрическое описание фигур и mеsh-объекты

 

На странице 3D Shapes палитры компонентов вы обнаружите несколько компонен­ тов, превращающихся в 3D-объекты в приложении FireMonkey. Среди них сфера (тsphere), куб (тсuье), цилиндр (тcylinder), конус (тсоnе) и ряд других объемных геометрических фигур. Спроектированные программистами Embarcadero классы позволят нам создавать собственные композиции небольшой и средней степени сложности. Дr я этого надо лишь научиться комбинировать имеющиеся фигуры. А что делать, если разработчику понадобится свой эксклюзивный объект? В таком случае следует воспользоваться услугами компонента тмеsh.

 

Произвольный объект, классы TMesh и TMeshData

Класс тмеsh (впрочем, как и все остальные трехмерные фигуры) является наследни­ ком нескольких весьма примечательных классов (рис. 28.1), среди них вы обнару­ жите опорный для всех трехмерных элементов управления класс тcontrolЗD. Но сейчас нас в большей степени интересуют не предки тмеsh, а неприметный класс тмeshData, специализирующийся на хранении сведений о геометрии объекта. Экземпляр данного класса инкапсулируется во все объекты, построенные на фун­ даменте TCustomМesh.

Как только для создания сцены нам понадобится объект (цилиндр, сфера, куб и т. п.), мы обращаем свой взор к странице компонентов ЗD Sbapes. Данные о гео­ метрических параметрах, перечисленных выше, и ряда других стандартных 3D-фигур заполнены в стенах Embarcadero, а на нашу долю остается лишь найти интересующую нас фигуру и перенести на форму проекта (рис. 28.2).

Но из правил всегда есть исключение. Разместив на форме компонент тмеsh, един­ ственное, что вы увидите- пустой каркас (см. рис. 28.2). Дело в том, что компо­ нент тмеsh представляет собой заготовку произвольного объекта, и изначально его данные отсутствуют. В своем роде тмеsh - это мастер перевоплощения, который готов услужить программисту и превратиться в любую фигуру. Единственное


 

 

 

 

Cuadro de texto: TControlЗD (FMX.TypesЗD)

 

Cuadro de texto: TShapeЗD (FMX.ObjectsЗD)

 

Cuadro de texto: TCustomMesh (FMX.ObjectsЗD) Cuadro de texto: TMeshData (FMX.TypesЗD)

 

 

 

 

 

 

 

 

 

 

 

;

Рис. 28.1. Иерархия наследования трехмерных фигур

 

 

 

 

TMesh.

 


TCube

 

 

 

TCylinder,

 

 

 

TRoundCube


TSphere

 

 

 

TRectangleЗD

 

 

 

TEllipseЗD


 

 

 

 

TCone

Рис. 28.2. Внешний вид основных ЗD-фигур


 

Гcoмcmpuчt:GtнJt: опиr.;ание фигур и теsh-объекты                                                                        387

условие для успешного превращения - наличие четкого описания характеристик целевого объекта. А за описание, как вы уже поняли, отвечает инкапсулированный класс TCustomМesh.

Доступ к данным с параметрами объекта предоставляет свойство

property Data: TMeshData;

в указанное свойство программист (даже скорее не программист, а ДИ!Шйнеr, абt­

екта) долж н занести исчерпывающие сведения о:

   координатах вершин объекта;

   индексах (порядке следования) вершин;

  направлениях нормалей к вершинам;

   координатах текстуры заливки грани объекта.

Для этих целей у тмeshData объявлены четыре свойства, с говорящими за себя на­ званиями

property Points: String;                        //координаты вершин property Triangleindices: String;//индeкcы вершин property Normals: String;                                                                          / /нормали вершин property TexCoordinates: String; //координаты текстуры

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

 

ЗАМЕЧАНИЕ

Об управлении координатами текстуры объекта с помощью свойства TexCoordinates

мы поговорим в главе ?9.

Несмотря на то, что четыре перечисленных свойства типизированы как string, надо понимать, что сведения о вершинах объекта не хранятся в текстовом виде (такой подход нельзя было бы назвать рациональным). Физически хранение данных осуществляется в специализированной структуре данных, называемой вершинным буфером. Вершинный буфер описывается в виде класса тvertexBuffer и инкапсули­ руется в тмeshData. Доступ к вершинному буферу обеспечивает свойство

property VertexBuffer:  TVertexBuffer;//тoлькo для чтения

Здесь (кроме уже знакомых нам данных с координатами, нормалями вершин и ко­ ординатами текстур) находится информация о цветах и подсветке вершин.

Кроме вершинного буфера в составе компонентов-фигур FireMonkey используется индексный буфер, построенный на основе класса TindexBuffer. Доступ к индексно­ му буферу предоставит свойство

property IndexBuffer: TindexBuffer; //только для чтения

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


 

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

 

Проектируем треугольник

Проверим себя на практике? Предлагаем опробовать свои силы на самом простей­ шем объекте- равностороннем треугольнике. На рис. 28.3 предложен прототип будущей фигуры с координатами ее вершин.

 

 

-  -2

 

 

 

 

 

х

 

 

 

 


С=(-2,1.72,0).     -2

, у


В= (2, 1.72, О)


 

Рис. 28.3. Чертеж равностороннего треугольника

 

Изучив чертеж фигуры, приступим к визуальному проектированию. Для этого раз­ местите на ЗD-форме компонент мeshl: тмеsh. Выделив компонент, переходим к Инспектору объектов и разворачиваем комплексное свойство Data. В первую оче­ редь, нам следует заполнить координаты вершин. Для этого в поле Points последо­ вательно (вершина А, вершина В и вершина С) заносим тройки значений (Х, У, Z). В качестве разделителя между значениями допускается задействовать символ про­ бела или запятую, например: о. о, -1. 72, о. о, 2 .о, 1. 72, о. о, -2. о, 1. 72, о. о.

Заполнив координаты вершин, обращаемся к полю Triangleindices и указываем очередность вершин объекта: о, 1, 2. Это означает, что при формировании изо­ бражения объекта в качестве стартовой вершины графический механизм FireMonkey станет воспринимать первую тройку значений из свойства Data. Points (о, -1. 72,о),в качестве второй - вторую тройку (2, 1. 72,,0) и·т. д.

Мьгзавершили минимальный набор операций и поэтому смело нажимаем клавишу

<Enter>. Для такого простого объекта, как плоский равносторонний треугольник, класс тмeshData способен самостоятельно рассчитать значения нормалей. Для этой цели автоматически вызывается метод

procedure CalcNoпnals;

 

В результате свойство Noпnals компонента Meshl без посторонней помощи запол­

ИИТСJI шюбх.одимtrми шачепиями.

 

 



 

 

Геометрическое описание фигур и теsh-объекты                                                                            389


Рис. 28.4. Создаем равносторонний треугольник в компоненте TMesh

 

Координаты текстур в данном примере нам пока неинтересны - мы удовлетво­ римся любым цветом заливки треугольника (рис. 28.4).

То, что мы сделали во время визуального проектирования, не сложно превратить в код. Например, так, как предложено в листинге 28.1.


procedure TForntl.FormЗDCreate(Sender: TObject);

Ьegin

Meshl.Data.Clear;

Meshl.Data.Points:='0,-1.72,0,2,1.72,0,-2,1.72,0';

Meshl.Data.Triangleindices:='0,1,2';

end;

 

 

Проектируем тетраэдр

Если вы разобрались с порядком проектирования плоской фигуры, то самое время проверить свои силы на более сложном объекте - тетраэдре. Хотя в мире все от­ носительно, среди всех многогранников тетраэдр также считается простейшим геометрическим объектом - у него всего-навсего 4 треугольных грани, 4 вершины и 6 ребер (рис. 28.5).

Если вы уверены в себе, Т? отложите книгу в сторону и попробуйте самостоятельно "засунуть" предложенный на рис. 28.5 тетраэдр в свойство Data компонента тмеsh. И если у вас все получится с первого раза, то примите мои поздравления - вы прирожденный ЗD-дизайнер. Если же что-то пошло не так, то ничего страшного­ продолжим работать·вместе.


 

 

D = (О, -2, О)

 

 

 

 

 

 

 

В= (2, о, -1.72)

 

у

 

Рис. 28.5. Чертеж тетраэдра

 

 

Сначала стоит ответить на вопрос: сколько точек с координатами вершин следует занести в свойство Data. Points компонента тмеsh, чтобы увидеть на экране тетра­ эдр? Если вы полагаете, что 4, то вы сильно ошибаетесь, правильный ответ- 12! Дело в том, что нам следует описывать не вершины, а грани многогранника. Граней 4, и каждая из них задается 3 точками, произведение 4 на 3 дало 12.

Предлагаем в качестве первой грани выбрать основание тетраэдра, описываемое треугольником АСВ. Передайте координаты вершин в соответствующие поля свой­ ства Data

Meshl.Data.Points:='0 О 1.72 -2 О -1.72 2 О -1.72';//координаты А, С, В Meshl.Data.Triangleindices·:='0 1 2';  //обход вершин А, с, В

и не удивляйтесь, если вы ничего не увидите на экране. На самом деле у нас все получилось, но графический механизм FireMonkey сознательно не отображает нижнюю грань тетраэдра, справедливо полагая, что она должна быть видима, толь­ ко если наблюдатель окажется снизу объекта. Это утверждение легко проверить, если повернуть тмеsh на -90° относительно оси х (рис. 28.6).

Обратите внимание, каким образом мы указали компоненту тмеsh порядок вывода нижней грани многоугольника. Мысленно взгляните на тетраэдр снизу и перечис­ лите в ршины грани по часовой стрелке -  у вас получится А, С, В. Поэтому в свойстве Meshl. Data. Triangleindices и оказались значения О 1 2.

 

ВННМАННЕ/

При определении порядка обхода вершин надо помнить, что координаты вершин ВИ­

ДИМОЙ тороны rрани должны перечисляться по часовой стрелке.

С гранью DCA история  повторится -      она по умолчанию  также невидима.

    граньАСВ =     о

1.72

-2 о -1.72

2 о -1.72);

    грань DCA = -2

о

-2    О -1.72

 

О О     1.72);

 

 
С остальными сторонами тетраэдра никаких проблем не возникнет (рис. 28.7). Подытожим нашу работу. В результате тетраэдр будет содержать 4 грани:


 

 

rоо.,,юmричсс«оа описание фигур и теsh-объекты                                                       1У1


    грань DBC = -2 о

    грань DAB = -2  о


2 О -1.72       -2 О -1.72);

00     1.72        20-1.72).


Необходимо последовательно ввести координаты всех перечисленн:мх граней

в свойство Meshl. o·ata. Points и определить порядок следования вершин о 1 2 з 4 5

6 7 8 9 10 11 в свойстве Meshl. Data. Triangleindices.


 

 

Meshl.RotationAngle=(-90,0,0)

 

Рис. 28.6. Поворот TMesh с нижней гранью тетраэдра на -90° относительно оси х


Рис. 28.7. Компонент ТМеsh с данными тетраэдра

 

 

Проектируем четырехугольник

Вновь возвратимся к плоским фигурам и решим еще одну задачу - подготовим данные для обычного квадрата. Если вы не видите в этом никаких проблем, то вновь предлагаем провести конкурс на сообразительность. Берите очередной ком-


 

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

Если вы сразу отказались от самостоятельной попытки конструирования квадрата и продолжили чтение, то наверняка ожидаете подвоха. Отчасти вы правы. Как вы по­ лагаете, сколько вершин следует описать, чтобы увидеть на экране квадрат? Пра­ вильный ответ - как минимум 6! Почему? Попробуем объяснить.

 

 

А=

(-2, -2, О)

В= (2, -2, О)

 

Г'

' ' '

' ' ''

'

1          ' '

 

 

 

-

 

 

 

-1

 

 

 

 

 

 

 

 

 

1

 

 

 

 

 

 

 

 

 

 

 

 

 

:,,,.

 

2          -1          о

' '

1

' '

 

2

r

х

 

 

1  ' ''

2

'

' '

D= (-2, 2, О)

 

 

 

С=

(2, 2, О)

r  у

Рис. 28.8. Чертеж квадрата

 

 

С точки зрения FireMonkey, плоский четырехугольник или любая другая плоская фигура рассматривается как грань объекта. Грани могут иметь любую форму - от простейших треугольников (как у тетраэдра) до сложных многоугольников. А те­ перь самое главное - независимо от заданной формы граней механизм визуализа­ ции полагает, что многоугольные грани состоят из элементарных треугольников. Поэтому для создания фигуры квадрата нам понадобится разбить его на треуголь­ ники, в простейшем случае это будут два треугольника, например АВС и CDA (рис. 28.8). Соответственно нам понадобится описать 6 вершин, вот такая "тре­ угольная" арифметика (листинг 28.2).

..... ..,.                                                                                                                                           ......................... ········••.-•···· .,...........:.

 

..,,,;;,\,

- .......2  --·------- ·-----

With Meshl.Data d.o

Ьegin

Clear;

Point�;=•-2,-2,0, 2,-2,0, 2,2,0, 2,2,0, -2,2,0, -2,-2,0';//A-B-C-C-D-A Normals:='0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0';

Trianglelndices:='0,1,2,3,4,5';

end;


 

Гr::uм,::111µuчt1r.;кut1 описание фигур и теsh-объекты                                                                           393

Управление нормалями вершин

До СИХ пор, проектируя многоугольники, мы не обращали внимания на нормали их вершин, доверяя расчет нормалей системе или присваивая им нулевые значения (О, О, О). Однако когда вы рассчитываете получить более изысканное изображение грани, то стоит поэкспериментировать с нормалями вершин. В качестве подопыт­ ного предлагаем вновь выбрать квадрат, но на этот раз собрать его из 4 треугольни­ ков (рис. 28.9).


 

Рис. 28.9. Чертеж квадрата, состоящего из 4 треугольников

 

Современные методики, отвечающие за трехмерный синтез изображения грани, не просто заливают их текстурой или краской определенного оттенка, а еще учитыва­ ют направления нормалей ее вершин и расположение источников света. Благодаря этому вместо плоского вида мы можем добиться более реалистичного объемного изображения. В подтверждение этих слов предлагаем повторить листинг 28.3, в ко­ тором создается квадрат с рис. 28.9.

 


 

 

with Mesh2.Data do

Ьegin

Clear;


...............                                                                                                                                              ..........................................4.....,....


---


.•..•••••••••••••••••1., .!...


Points:='-2,-2,0,        2,-2,0, 0,0,0,' +  //грань АВЕ


'  2,-2,0,      2, 2,0, о,о,о,' +

' 2, 2,0, -2, 2,0, о,о,о,' +


//грань  ВСЕ

//грань  CDE


'-2, 2,0, -2,-2,0, 0,0,0';             //грань DAE

//определение нормалей к вершинам, для вершины Е(О,0,-1) Noпnals:='0,0,0,  0,0,0,  0,0,-1,             0,0,0,  0,0,0,  0,0,-1,

О, О, О, О, О, О, О, 0,-1,             О, q,О, О, 0,0, О, О, -1';

Triangleindices:='0,1,2,3,4,5,6,7,8,9,10,11';                      //обход вершин

end;



394                                                                                                                                                     ·  Глава 28

 

Самое важное в предложенном коде сосредоточено в строке определения нормалей. Обратите внимание на то, что описывая нормаль к общей для всех 4 треугольников вершине Е, мы направили ее в сторону, противоположную наблюдателю (вдоль оси z). Нам осталось подобрать положение и направление источника освещения и нажать клавишу <F9> для компиляции проекта (рис. 28.10). Как говорится, ком­ ментарии здесь излишни...


 

Рис. 28.10. Внешний вид квадрата с обычными и модифицированными нормалями

 

 

3D-модель, класс TMode/3D

Полагаем, что потратив немало сил на разработку весьма непритязательных гео­ метрических фигур, многие из вас чуточку устали. Кроме того, наверняка некото­ рых читателей начали терзать смутные сомнения, а хватит ли у них терпения для того, чтобы самостоятельно "нарисовать" в тмеsh 3D-объект более сложный, чем тетраэдр? Поэтому спешим к вам с хорошей новостью, точнее новостями. Во­ первых, совсем необязательно собирать геометрические фигуры программным спо­ собом, вместо этого существуют специальные 3D-редакторы. Во-вторых, во Все­ мирной паутине есть множество заготовок, как простых, так и весьма нетривиаль­ ных объектов, подготовленных профессиональными дизайнерами. И наконец, в-третьих (и в-главных), в FireMonkey существует весьма простой и одновременно

gффективный способ управления не отдельными 3D-объектами, а целыми объем­

ными конструкциями. Об этом мы сейчас и поговорим.

Обратившись к странице 3D Sbapes палитры компонентов Delphi, вы найдете ком­ понент тмоdеlЗD -  это сверхмощное оружие 3D-моделирования, стоящее на во-


Геометрическое описание фигур и теsh-объекты                                                                          395

оружении FireMonkey, и в этом нет ни капли преувеличения. Дело в том, что TModelЗD способен работать не только с отдельной геометрической фигурой (как тмеsh), но и с целой коллекцией ЗD-фигур. В подтверждение этих слов предлагаем вам экранный снимок формы, на котором вы обнаружите ни много ни мало, а зна­ менитый Тауэрский мост через реку Темзу в Лондоне (рис. 28.11).


Рис. 28.11. Компонент TЗDModel с моделью Тауэрского моста

 

Модель моста не монолитна, она собрана из сотен более мелких деталей. Каждая из деталей описана в уже знакомом нам формате (координаты вершин граней, обход граней, нормали и координаты текстур). В свою очередь компонент тмоdеlЗD соби­ рает все составные блоки модели в коллекцию

property MeshCollection: TMeshCollection;

где

TMeshCollection  =  TMeshDynArray;

в свою очередь тмeshDynArray оказывается динамическим массивом

TMeshDynArray = array of  TMesh;

Таким образом, тмоdеlЗD выступает интеллектуальным хранилищем простых фи­ гур, которые в совокупности превращаются в объект любой сложности. Самое от-


396                                                         Глава 28

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

function LoadFromFile(const AFileName: string): Boolean; virtual;

так и в период визуального проектирования, для этого следует обратиться к Ин­ спектору объектов и при посредничестве свойства MeshCollection вызвать встроен­ ный редактор Mesh Collection Editor.

 

ВНИМАНИЕ!

Для загрузки файла dae с моделью в формате COLLADA в компонент_TModelЗD во время выполнения программы потребуется помощь классов TDAEModelimporter и TModelimportServices.

 

ЗАМЕЧАНИЕ

Компонент тмосtеlЗd воспринимает ЗD-модели в формате COLLADA, сохраненные в файлах с расширениями имен ase, obj и dae. Среди них наибольшее предпочтение следует отдавать файлам dae.

Предусмотрена возможность обмена данными между компонентами тмосtеlЗD с по­ мощью потоков в памяти, для этих целей предназначены методы чтения и записи:

procedure ReadМodel(Stream: TStream);          //чтение модели из потока procedure WriteModel(Stream: TStream); //запись модели в поток

Ко всему прочему компонент тмосtеlЗD может оказаться полезным для программи­ ста, стремящегося разобрать относительно несложную 3D-модель на составные части, например для того, чтобы использовать ее отдельные элементы в других проектах. Для этого достаточно разложить свойство MeshCollection н:а массив эле­ ментов тмеsh и исследовать основные характеристики интересных для нас фигур (листинг 28.4).


var  i:integer; Ьegin Memol.Lines.Clear;

for i:=O to High(ModelЗDl.MeshCollection) do

Ьegin

Memol.Lines.Add('Mesh  '+  Intтostr(i));

Memol.Lines.Add('Points: ' + ModelЗDl.MeshCollection[i] .Data.Points); Mernol.Lineв.Add('Nonnalв: ' + ModelЗDl.MeshCollection[i].Data.Normals); Memol.Lines.Add('Triangleindices: ' +

ModelЗDl.MeshCollection[i] .Data.Triangleindices); Memol.Lines.Add(' '.);

end; end;

 

 



 

 

t еометрическое описание фигур и теsh-объекты                                                                             397

В результате выполнения предложенного кода в многострочный текстовый редак­ тор Memol будут помещены сведения о составе вершин, нормалях к вершинам и очередность смены координат для каждого отдельного фрагмента компле:r;:сиоrо

ЗD-объекта.

 

Импорт модели во время выполнения программы

При необходимости модель COLLADA может быть импортирована в компонент тмоdеlЗD и во время выполнения программы. Эго многоходовая операция, в кото­ рой задействуется не только экземпляр класса тмоdеlЗD, но и специалисты по кон­ вертации модели из файла dae в формат, приемлемый FireMonkey, - класс TDAEМodelimporter и класс TModelimportServices.

Основная роль по преобразованию модели COLLADA во внутренний формат FireMonkey отводится импортеру TDAEModelimporter. Экземпляр класса последова­ тельно, узел за узлом, разбирает целостную 3D-модель COLLADA на составные элементы, преобразуя их в данные, понятные FireMonkey. Еще один наш помощник TDAEModelimporter обладает функцией класса Registerimporter () , уведомляющей компонент тмоdеlЗD о том, что он будет загружать файл COLLADA.

В листинге 28.5 предложен простейший пример импорта модели COLLADA в при­ ложение FireMonkey.


uses FМX.DAE.Importer, FМX.Import;

{$R *.fmx}

 

procedure TForml.ButtonlClick(Sender: TObject); var DAEModelimporter: TDAEModelimporter;

Ьegin

if OpenDialogl.Execute then Ьegin

DAEModelimporter:=TDAEModelimporter.Create; TModelimportServices.Registerimporter(DAEМodelimporter);  ModelЗЫ.LoadFromFile(OpenDialogl.FileName);

end; end;

 

Щелчок по кнопке вuttonl вызывает диалоговое окно выбора файла с расширением имени dae. Все остальное сделают наши новые знакомые - TDAEModelimporter и ТМodelimportServices.



 

 

ГЛАВА  29

 

 

Материал объекта

 

 

 

 

Для того чтобы JD-объект выглядел реалистично, недостаточно определить конту­ ры его граней. В комплексе с качественным геометрическим описанием вершин и нормалей к вершинам программисту следует проявить заботу о материале объекта. С некоторой степенью абстракции стоит говорить о том, что материал - это то, из чего сделана поверхность граней фигуры. Надо понимать, что информационные технологии еще не дошли до того уровня, когда мы сможем потрогать программ­ ный объект рукой и почувствовать холод мрамора, свежесть воды или шерохова­ тость свежеструганной доски. Поэтому говоря о материале объекта, мы имеем в виду способ "обмана" глаз пользователя - надо постараться сделать так, чтобы виртуальный объект как можно более походил на его прототип из реального мира.

Базовым классом, предназначенным для описания материала, из которого "сделана" поверхность трехмерного объекта, в FireMonkey выступает класс тмatreial. В пер­ вой версии FireMonkey экземпляр класса инкапсулировался в цепочку насле­ дования ЗD-фигур (начиная с класса тshарезо), и доступ к нему обеспечивало свой­ ство Material. Позднее компания Embarcadero несколько пересмотрела подход управления материалом и убрала из 3D-объектов вышеупомянутое свойство. Вместо этого (начиная с Delphi ХЕЗ) в распоряжение программистов были предос­ тавлены три новых компонента- TColorMaterialSource, TTextureМaterialSource и TlightMaterialSource, которые взяли на себя ответственность за заливку трехмер­ ных объектов. А трехмерные фигуры вместо свойства мaterial приобрели свойство

property MaterialSource: TMaterialSource;

к которому и подключаются перечисленные компоненты.

JiИ.'lbI I\ОМПОifйПЫ также построены на основе идеи, заложенной в классе тмaterial, но на этот раз они поделили между собой различные сферы управления заливкой трехмерного объекта. Так, TColorMaterialSource взял на себя наиболее простую задачу по управлению цветом заливки. Компонент ттextureMaterialSource позволяет осуществлять заливку текстурой; которая может быть создана из любого файла с растровой картинкой. Наконец, TlightMaterialSource освоил наиболее

сложный функционал - он позволяет материалу ЗD-фигуры реагировать на осве­ щение.

 

 

 



ll,faт110/JUйJI UU Ьt:11\f f/cl                                                                                                                                                                                                     399

Заливка цветом,

компонент TColorMaterialSource·

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

что ключевым свойством компонента окажется Color, хранящее значение цвета ]а.

ливки.

На первый взгляд может показаться, что разработчики FireMonkey несколько пере­ мудрили, создав отдельный компонент для решения столь тривиальной задачи. Однако это не так. Во-первых, класс тcolorMaterialSource вписывается в принятую концепцию по распределению обязанностей при заливке поверхности объектов. Во­ вторых, компонент окажется полезным в тех случаях, когда он служит источником цвета для нескольких 3D-объектов - в этом случае упрощается процесс централи­ зованной смены цвета одновременно для всех получателей.

 

Текстурирование

Чтобы достичь полной иллюзии подлинности проектируемого нами 3D-мира, стоит задействовать сильнейшее оружие - текстуры. В роли текстур вправе выступать простой растровый рисунок, который вы нарисуете сами или найдете в Интернете, либо обычная фотография, сделанная вами во время отпуска. Благодаря текстуре неуклюжий 3D-пря оугольник легко превратится в кирпичную или деревянную стену, размещенный в верхней части сцены план без проблем станет небом, а сфера замаскируется под футбольный мяч...

 

ЗАМЕЧАНИЕ

На роль текстур рекомендуется назначать рисунки с геометрическими размерами, кратными значению 2. Однако это просто рекомендация, несколько ускоряющая про­ цесс текстурирования. Если скорость рендеринга несущественна, то вы можете выби­ рать и произвольные размеры.

В простейшем случае 3D-объект FireMonkey, получив в свое распоряжение файл с текстурой, самостоятельно нанесет изображение на свою поверхность. Если же вы не намерены ни на секунду отпускать бразды правления текстурами из своих рук, то стоит вспомнить о классе тмeshData  и его свойстве тexCoordinates (см. главу 28), позволяющем программисту конкретизировать координаты вершин текстуры. Получив координаты текстуры, механизм графического вывода рассчи­ тывает соответствие между минимальным элементом текстуры (текселом) и соот­ ветствующим логическим элементом на грани объекта.

Так как текстура задается в формате растрового рисунка, то ее можно рассматри­ вать как квадратную матрицу, состоящую из и строк и v столбцов. Таким образом, каждый тексел текстуры обладает своим адресом (номером строки и столбца).

В FireMonkey размер текстуры, как по горизонтали, так и по вертикали, нормиро­ ван к 1, т. е. значение (О, О) адресует самый верхний левый элемент текстуры, а зна-


 

 

    

 

Рис. 29.1. Система координат для текстурирования

 

чение (1, 1) хранит адрес правого нижнего тексела (рис. 29.1). Несложные расчеты подскажут, что центр текстуры адресуется значением (0.5, 0.5).

Нормирование протяженности текстуры к единичному значению не является дог­ мой, программист имеет возможность выбирать и другие значения. Например, если мы вместо единичных значений (1, 1), характеризующих координату правого ниж­ него угла текстуры, воспользуемся значениями больше 1 (например, (2, 2)), то тек­ стура размножится как по горизонтали, так и по вертикали, а если значения ока­ жутся меньше единицы (допустим, (0.5, 0.5)), то текстура растянется.

 

ЗАМЕЧАНИЕ

Процесс размножения рисунка текстуры на поверхности объекта называется фраг­ ментацией (tiling).

 

Источник текстуры TTextureMateria/Source

За подключение текстуры  к ЗD-объекту отвечают компонент  ттextureMaterialSource

и его свойство

property Texture:  TBitmap;

Если в свойство загружено растровое изображение, то заранее подготовленные ЗD-объекты (сфера TSphere, куб ТСuЬе, цилиндр TCylinder и т. д.) немедленно "на­ тянут" на все свои грани выбранный программистом рисунок. Если же вы рассчи­ тываете получить больше рычагов управления процессом текстурирования, то сле­ дует отдать предпочтение универсальному компоненту тмеsh.

 

Управление координатами текстуры в TMesh

Очередной эксперимент, который предлагаем провести, как раз посвящен заливке текстурой фигуры, собранной в компоненте тмеsh. Если вы повторяли примеры из главы 28, то в вашем распоряжении наверняка остался исходный код создания квадрата в компоненте тмеsh (см. листинг 28.2), именно э;rот квадрат вновь окажет­ ся нашим подопытным.


1

 
Материал объект=а'-------______________________________________________________ 4 8 1

Занесите в свойство Data компонента тмеsh сведения о вершинах квадрата и под­ ключите источник текстуры. Из-за того, что наш квадрат собран из 2 треугольников (см. рис. 28.8), нам потребуется описать 6 вершин текстуры (по 3 вершины на каж­ дый из треугольников). Для начала мы заполним лишь первые три координаты сле­ дующим образом:

Meshl.Data.TexCoordinates:='0   О     1  О     1  1        00        00        00';

 

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

Meshl.Data.TexCoordinates:='0    О     О О     О О       О  1     1  1     1  О';

текстурой покроется второй треугольник. Как вы уже догадались, для получения целостной картины стоит объединить левую и правую части координат:

Meshl.Data.TexCoordinates:='0   О     1  О    1  1        01      11      10';

В результате заливке будет подвергнута вся площадь квадрата.

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

 

 


TextCoordinates=

010010000000


TextCoordinates=

101101000000


 

ТextCoordinates=

001011000000


TextCoordinates=

110100000000


 

,..- -              ·' \-

т"аур, об-

 

 

 

 

 

 

 

TextCoordinates=

001011011110


 

 

 

 

 

 

 

 

 

TextCoordinates=

■-

 
003033033330

 

!

 
r


 

 

 

 

 

 

 

TextCoordinates=

000000011110


11       f i

i

 
TextCoordinates=

000000100001


 


TextCoordinates=

000000000111


TextCoordinates=

000000111000


Рис. 29.2. Управление координатами текстуры


 

Отраженный свет и компонент TLightMateria/Source

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

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

   Amblent light -    окружающий (не исходящий из каких-либо отдельных источни­

ков) свет, подсвечивающий объект одинаково во всех его точках.

   Dif.fuse light - диффузный (рассеянный) свет. Это свет, который попадает на поверхность объекта и отражается одинаково во всех направлениях. Диффузное отражение присутствует в нормальных условиях и придает объекту его естест­ венный цвет. Основная отличительная черта диффузных материалов - незави­ симость их яркости от положения наблюдателя. Классическим примером мате­ риала, чей внешний вид в первую очередь определяется диффузным отражени­ ем, может стать бархатная ткань, сухой асфальт, рыхлый грунт.

    Specular light- отраженный (зеркальный) свет. В отличие от рассеянного света

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

    Emissive light- эмиссия света. Учитывается в поверхностях, способных само-

стоятельно излучать свет (лампа, угли в костре и т. п.).

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

property AmЬient:  TAlphaColor;        //окружающий свет

property Diffuse: TAlphaColor;        //рассеянный свет от материала

property Specular: TAlphaColor; //отраженный свет

property Emissive: TAlphaColor; / /излучаемый свет

С этой целью предлагаем разместить на форме несколько сфер и истоЧJ-IИК освеще- . ния в режиме точечного излучения LightType=ltPoint в центре. Все остальное по­ знается в результате включения и отключения (состояние Null) перечисленных выше свойств (рис. 29.3).

Любителям более строгого подхода к изучению особенностей поведения материала объекта можно предложить математическую формулу, взятую из документации DirectX 9.0 с SDK. Здесь говорится, что для расчета влияния окружающего освеще­ ния и рассеянного света используется следующее математическое выражение:

Dv =laSa +Se +"f,A;(RdiSdLdi +SaLa;).

i


lt,fa,11aµue111 UUЫ:JKffli:I                                                                                                                                                                                                           403


Рис. 29.3. Особенности отражения света материала от свойств Amblent, Diffuse, Specular и Emissive

 

Результирующий цвет Dv, который окрасит поверхность объекта, складывается из следующих компонентов:

    la -      окружающий свет для данной сцены;

    Sa-   окружающий свет материала поверхности объекта (свойство AmЬient);

    Se-    свет, излучаемый материалом (свойство Emissive);

   А; -    коэффициент ослабления для i-го светового потока, который зависит от характеристик источника освещения;

   RJ; -  фактор диффузии для i-го светового потока, он в свою очередь зависит от направления нормали к освещаемой грани и угла падения светового потока;

   Sd -    значение рассеяния, выбранное в свойстве Diffuse материала;

    Ld; -     рассеяние для i-го светового потока;

    La;- свет окружения, генерируемый i-м световым потоком.

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

 

ВНИМАНИЕ/

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


 

· В реальном мире на отражающие способности объекта оказывает влияние его по­ лировка. Программные объекты также можно отполировать с помощью свойства

property Shininess: Integer; //по умолчанию 30

Свойство может принимать значение из диапазона от О до 128.

 

Дополнительная настройка текстур и класс TTexture

Текстурирование- весьма сложный многоуровневый процесс, в котором задейст­ вуются скрытые от обычного программиста классы. Сейчас мы поговорим об одном из "невидимок" - классе ттexture.

Класс ттexture задействуется в тех случаях, когда в основу текстуры положен битовый образ. Образ текстуры загружается в компоненты-источники текстур TTextureMaterialSource  и  TLightMaterialSource  с помощью  инкапсулированных в них:

   специалиста по растрам -    класса твi tmap;

    класса-помощника твi tmapHelper.

Доступ к хранилищу текстуры · обеспечивает уже упомянутое ранее свойство Texture, у которого в свою очередь надо вызвать свойство-"однофамильца" тexture (листинг 29._1).

r......;,aa.,:--    ·           ...........  ....   ·     ··     ·    i


; ...... - =··-·...-·.,·"'  ·=··-····             . .                                                                           ......;.-      ..........;.v...... ,···"'"'·.....,.. .._,....... _..,                        

prooedure TForml.FormЗDCreate(Sender: TObject); Ьegin

TextureMaterialSourcel.Texture.Texture.Style:= [TTextureStyle.tsMipMaps] + [TTextureStyle.tsDynamic];


··-·-""···:


end;

 

 

примере из листинга 29.1 демонстрируется порядок настройки заливки поверхно­ сти объекта текстурой с помощью свойства

property Style: TTextureStyles;

type TTextureStyle = (tsMipMaps, tsDynamic, tsRenderTarget);

Свойство определяет, будет ЛИ текстура построена на базе нескольких текстурных карт (tsMipMaps), является динамической (tsDynamic) или используется для перери­ совки целевого объекта (tsRenderTarget). По умолчанию стиль заливки включает элементы tsM�pMaps и tsDynamic.

Если текстура отсутствует, то свойство

function IsEmpty: Boolean;

возвратит значение true.


 

 

Матеп11ап пбъвкта                                                                                                                                      i/0§

Формат хранимых данных и глубина цвета текстуры доступны благодаря свойствам

property PixelFormat: TPixelFormat;

и

property BytesPerPixel: Integer; //только для чтения

Геометрические размеры растрового образа можно получить, обратившись к свой­ ствам

property Width: Integer;

property Height: Integer;

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

property MinFilter: TTextureFilter; //фильтр вывода уменьшенной текстуры

property MagFilter: TTextureFilter; //фильтр вывода увеличенной текстуры

где

type TTextureFilter = (tfNearest, tfLinear);

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 



 

 

ГЛАВА 30

 

 

ЗD-контекст TContextЗD

 

 

 

 

Вся ответственность за графический вывод трехмерных сцен в проектах FirеМоnkеу·возлагается на ЗD-контекст, описываемый классом тcontextЗD. Класс определен в модуле FМХ. TypesЗD и несет ответственность за:

   освещение сцены;

    работу с шейдерами;

   текстурную заливку;

    вывод простейших графических примитивов;

    управление графическим буфером.

Одним словом, в руках графического ЗD-контекста сосредоточен весь базовый ' функционал рендеринга. Обычно доступ к контексту обеспечивает свойство Context рабочей формы проекта, однако не исключена ситуация, когда вам потре­ буется самостоятельно создать контекст. В последнем случае следует помнить, что, несмотря на наличие у класса тcontextЗD собственного конструктора, экземп­ ляр класса должен создаваться       при посредничестве менеджера            контекста

тcontextManager, описанного в модуле FМХ. TypesЗD.

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

property Height: Integer; //только дпя чтения

property Width: Integer;        //только дпя чтения

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

Если речь заходит о трехмерной системе координат, возможно, нам потребуется убедиться в отсутствии сдвига начала координат от центра рабочей области кон·- текста (например, принадлежащего форме). Для этой цели введено свойство

property CenterOffset:  TPosition;  //только дпя чтения

Если смещение отсутствует, то свойство возвратит значение (О, О, О).



':ln_vnumc.vcm TCc,nfextЗD

 

Для связи с низкоуровневыми функциями прикладного программного интерфейса может пригодиться дескриптор родительского окна

property Parent: TFmxHandle;

которому принадлежит контекст.

 

Управление графической сессией

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

function BeginScene: Boolean;

Если контекст готов к работе, метод возвращает значение true, и внутренний счет­ чик сцен получает приращение на единицу. Все следующие за методом BeginScene () графические операции станут рассматриваться как команды, форми­ рующие внешний вид текущей сцены; Важно понимать, что сцена полностью соз­ дается в памяти компьютера, а на экране она появится лишь после завершения сес­ сии. Команду на это даст метод

procedure EndScene;

После завершения сессии значение внутреннего счетчика сцен уменьшается.

Разработчики FireMonkey рекомендуют при управлении сессией задействовать конструкцию защиты от ошибок try .. finally, примерно так, как предложено в листинге 30.1.


procedure TFonnl.FonnЗDRender(Sender: TObject; Context: TContextЗD);

Ьegin

if     Context.BeginScene then

try

Context.Clear(TAlphaColorRec.White);  //заливка контекста белым

//другие команды

finally

Context.EndScene;

end; end;

 

Вызов метода EndScene () в секции finaliy гарантирует закрытие текущей сессии. В коде примера 30.1 нам встретился еще один новый метод:

procedure Clear(const AColor: TAlphaColor); over�oad;

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


У метода есть более сложная перегружаемая версия

prooedure Clear(const ATarget: TClearТargets; const AColor: TAlphaColor; const ADepth: single; const AStencil: Cardinal); overload;

позволяющая с помощью параметра ATarget конкретизировать очищаемые элементы.

 

Графические примитивы класса TContextHelper

Трехмерный контекст тcontextЗD обладает набором методов, позволяющих осуще­ ствлять вывод простейших графических примитивов (точек, линий и треугольни­ ков). Кроме того, в состав контекста интегрирован класс-помощник тcontextHelper, расширяющий графические возможности контекста и позволяющий рисовать ряд несложных примитивов. Заметим, что с точки зрения написания исходного кода обращение к методам вывода графических примитивов класса тcontextзo или тcontextHelper осуществляется абсолютно одинаково, поэтому программист может даже и не заметить участия класса-помощника. Свидетельством того станет пример вывода отрезков, формирующих плоскость xz (рис. 30.1).


Ри,, 1),1, 6til60Д плоскости xz средствами класса-помощника TContextHelper

 

Для реализации представленной на рис. 30.1 модели требуется лишь помощь имеющегося в распоряжении тcontextHelper метода вывода отрезков

prooedure DrawLine(const StartPoint, EndPoint: ТVectorЗD;

const Opacity: Single; const Color: TAlphaColor);

и несколько строк кода (листинг 30.2).


:JD но.-== TCoпtextЗD                                                                                                                          409

f .."-     .i· -;.... - ..      -_-;;·;;.;;;;;·;;---...        ·   ·              1


••••••....•»l••-•_...,м,,_ -.        .,,,.,ww.,. ..••••-••.-.-,...,.,,..,cr.,., -•,.,,..,..,,. .,,,_,,.......... ,

 

var Vl,V2:TVector3D; A:single;

Ьegin

if Context.BeginScene then

try


,,,,,,,,;,,.,;,,,.,_,,,,,.,.,.,.,., •••••••••.. •A••••.. •••••••••.. •••''"'• •••••••••..-,t,.,-h. •·•••••М••••/4\,о' -


А:=-10;

while A<=l0 do

Ьegin

Vl:=VectorЗD(A,0,-10);     V2:=Vector3D(A,0,10);

Context.DrawLine(Vl,V2,1,TAlphaColorRec.LightЫue);

 


 

 

// ...


 

 

end;


Vl:=VectorЗD(-10,0,A);  V2:=Vector3D(10,0,A);

Context.DrawLine(Vl,V2,1,TAlphaColorRec.LightЫue);


 

Как видите, для вывода простейшего отрезка в первые два параметра метода следу­ ет передать координаты начал_ьной и конечной точек, степень прозрачности линии и значение цвета.

Класс-помощник позволяет нарисовать прямоугольник

procedure DrawRect(const TopLeft, BottomRight: ТVectorЗD;

const Opacity: Single; const Color: TAlphaColor);

 

Координаты левой верхней вершины и нижней правой направляются в параметры

TopLeft И BottomRight.

Весьма удобен метод, способный вывести на экране контур куба

procedure DrawCuЬe(const Center, Size: TVectorЗD; const Opacity: Single;

const Color: TAlphaColor);

 

Для того чтобы увидеть изображение куба, совсем нет необходимости вычислять координаты всех вершин, вместо этого достаточно указать геометрический центр куба center и размер куба с помощью вектора Size. Замечу, что управляя полями вектора size, можно рисовать не только куб (у которого все стороны представляют собой квадраты), но и параллелепипед с прямоугольными сторонами (рис. 30.2).

У метода DrawCube () имеется коллега с однотипными параметрами, позволяющий осуществить заливку сторон куба

procedure FillCuЬe(const Center, Size: TVectorЗD; const Opacity: Single;

const Color: TAlphaColor);

 

Последний из рассматриваемых методов класса тcontextHelper наиболее сложен, его задача -  осуществление заливки одной из граней ЗD-объекта. Другими слова­ ми, за счет нескольких вызовов метода


prooedure FillPolygon(const Center, Size: TVectorЗD; const Rect: TRectF;

oonst Points: TPolygon;       cons� Material: TMaterial; oonst Opacity: Single; Front: Boolean = True;

Васk: Boolean = True; Left: Boolean = True);

мы сможем последовательно создать несколько двухмерных планов с изображени­ ем полноценного ЗD-объекта. Здесь: center определяет центральную точку закра­ шиваемого объекта; Size назначает размер объекта; Rect при необходимости огра­ ничивает прямоугольную область вывода, если в этом нет надобности, допускается задавать пустой прямоугольник; Points - массив с координатами точек, которые в момент выполнения метода будут нормированы к 1; мaterial- материал заливки (см. главу 29). Передавая в параметры Front, васk и Left значения true или false, вы указываете, следует ли закрашивать фронтальную, заднюю и левую стороны объек­ та. Простейший пример использования возможностей метода вы обнаружите в лис­ тинге 30.3.


Рис. 30.2. Рисуем куб с помощью метода Dra1:1CuЬe ()

 


var Vl,V2:TVector3D; Plgn: TPolygon;

Material:TColorMaterial;

Ьegin

SetLength(Plgn, 5); Plgn[O] := PointF(0,0);

Plgn[l] := PointF(l, О);


 

 

ЗD-контекст TContextЗD                                                                                                                           411

 

Plgn[2] ·= PointF(l, 1); Plgn[ЗJ .- PointF(0, 1);

Plgn[4] := PointF(0,0);

 

if Context.BeginScene then

try

Material:=TColorMaterial.Create; Material.Color:=TAlphaColorRec.Bisque;

 

Context.Clear(TAlphaColorRec.White); //заливка контекста белым Vl:=VectorЗD(0,0,0);

V2:=Vector3D(5,-5,0);

Context.FillPolygon(Vl, V2, RectF(0,0,0,0),

Plgn, Material, 1, true, false, false);

 

finally Context.EndScene; Material.Free;

end;

end;

 

Графические примитивы класса TContextЗD

Кроме методов, подаренных классом-помощником тcontextHelper, в распоряжении контекста тcontextЗD имеется тройка собственных методов, предназначенных для вывода соответственно точек, линий и треугольников

_prooedure DrawLines(const Vertices: ТVertexBuffer;

const Indices: TindexBuffer;

const Material: ТМaterial; oonst Opacity: Single);

prooedure DrawТriangles(const Vertices: ТVertexBuffer;

const Indices: TindexBuffer; oonst Material: TMaterial;

const Opacity: Single);

prooedure DrawPoints(const Vertices: TVertexBuffer;

const Indices: TindexBuffer; oonst Material: TMaterial;

const Opacity: Single);

Основными параметрами всех методов выступают вершинные Ver�exBuffer и ин­ дексные буферы IndexBuffer. В первом параметре хранятся сведения о вершинах, нормалях, цветах и т. д., во втором - индексные сведения о порядке обхода вер­ шин в массиве vertexBuffer. Кроме того, методы позволяют конкретизировать материал Material, которым будет "покрыт" объект, и степень прозрачности объек­ та Opacity.

ЗАМЕЧАНИЕ

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


 

Листинг 30.4 демо стрирует порядок работы с простейшей из функций DrawLines () .

ln-11811•.._.   ..._..···.·•·:-·---o-········· ·,.,------, -·-····· ---- ---,--,  ,.

!. : ·········"' ' =-·-·····"  ..................  ···········' ><·•-····-······· .•,:.,; ..............,..,- ••·····••·.•.....-..................,...••...•..............«1..............

varMaterial:TColorMaterial; Vertices: TVertexBuffer; Indices: TindexBuffer;

Ьegin

if Context.BeginScene then

try

Vertices:=TVertexBuffer.Create([TVertexFoпnat.vfVertex],2); Vertices.Vertices[O] :=PointЗD(-10,0,0); Vertices.Vertices[l] :=PointЗD(l0,0,0);

 

Indices:=TindexBuffer.Create(2); Indices.Indices[O] :=О; Indices.Indices[l] :=1;

 

Material:=TColorMaterial.Create; 'Material.Color:=TAlphaColorRec.Red;

 

Context.Clear(TAlphaColorRec.White); Context.DrawLines(Vertices,Indices,Material,1);

finally Context.EndScene; Vertices.Free; Indices.Free; Material.Free;

end; end;

 

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

.

 

'n,.,..,•.

 
!,\-' · .•,...•,• ...,.. ••••.,. ··-·········                                                                                                                                                                                                                                                          ..................,.. .•• .• ·············,-,.,_ ·••············-•· с.··············, -....-  •·t-·•·-

 

Oa,ot.         -•••..•·•""'"·,.'t••••••..-1•••                                                         ,,,.                                                                                                                                                                                                           ,У,"6,,,,.,,,,,-.,, ....,., .,,_,,., ,.,."'1. <,,4-••'""'··•· "1, ,·-- -·,,.,.,_А.,....,..'

var Material:TColorMaterial; Vertices: TVertexBuffer; Indices: TindexBuffer;

Ьegin

 

if Context.BeginScene then try

Material:=TColorMaterial.Create;

Material.Color:=TAlphaColorRec.Gray;


 

 

:}D  щ>,,п,оноn? TContextЗD                                                                                                                            4-1]

Context.Clear(TAlphaColorRec.White);  //заливка контекста белым

 

Vertices:=TVertexBuffer.Create([TVertexFonnat.vfVertex],12);

/ / ======== КООРДИНАТЫ ВЕРШИН ТЕТРАЭДРА

Vertices.Vertices[0] :=PointЗD( О, О,          1.72); //грань АСВ Vertices.Vertices[l] :=PointЗD(-2, О, -1.72); Vertices.Vertices[2] :=PointЗD( 2, О, -1. 72);

 

Vertices.Vertices[З] :=Point3D( о, -2,          О);          //грань ОСА Vertices.Vertices[4] :=PointЗD(-2,             0,-1.72);

Vertices.Vertices[5]  :=PointЗD(  О,      О, 1. 72);

 

Vertices.Vertices(б] :=PointЗD( О, -2,               О);          //грань DBC Vertices.Vertices[7] :=PointЗD( 2,              о, -1. 72); Vertices.Vertices[B] :=PointЗD(-2,                                                        О, -1.72);

 


Vertices.Vertices[9] :=PointЗD( О, -2,               О); Vertices.Vertices[10]:=Point3D( О,   О,            1.72); Vertices.Vertices[ll]:=PointЗD(    2,      О, -1.72);

//       =========                     ИНДЕКСЫ

Indices:=TindexBuffer.Create(12);


//грань  DАВ

 

 



Indices.Indices[0] :=О; Indices.Indices[l] :=1; Indices.Indices[2] :=2;

Indices.Indices[ЗJ :=3; Indices.Indices[4] :=4; Indices.Indices[5] :=5;

Indices.Indices[б] :=6; Indices.Indices[7] :=7; Indices.Indices[B] :=8;

Indices.Indices[9] :=9; Indices.Indices[l0] :=10;

Indices.Indices[ll] :=11;

//

Context.DrawTriangles(Vertices,Indices,Material,1)

finally

Context.EndScene; Vertices.Free; Indices.Free; Material.Free;

end;

end;

 

 

Освещение

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

В первую очередь отметим существование свойства, способного сослужить хоро­

шую службу в ситуации, когда источники света создаются динамически

property Lights: TList<TLight>;


 

предоставляющего доступ ко всем "прожекторам" и "лампочкам" в формате удоб­ ного списка TList.

Кроме того, в арсенале тcontextЗD имеется пара методов

prooedure AddLight(const ALight: TLight);

prooedure DeleteLight(const ALight: TLight);

позволяющих добавлять и удалять источник света к контексту.

 

Матрицы и матричные преобразования.

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

Для осуществления преобразований достаточно сконструировать необходимую матрицу тмatrixЗD (см. прwюжение 2), а затем вызвать метод

prooedure SetMatrix(const М: TMatrixЗD);

применяющий матричное преобразование к контексту.

Как вы думаете, каким образом бьmо получено изображение фигуры с рис. 30.3?


Рис. 30.3. Пример матричного преобразования

 

Все не так и сложно, как может показаться на первый взгляд. Мы просто подгото­ вили прямоугольник, а затем несколько раз повернули его вокруг оси у с помощью матрицы вращения. Исходный пример создания матрицы вращения и применения

к :Н)-к91rпжсту предложен в листинге 30.6.


 

 

 

 

var M:TMatrixЗD; Angle: Single;

Ьegin

if Context.BeginScene then

try

Context.Clear(TAlphaColorRec.White);

Angle:=0; //исходный угол

while Angle<pi do Ьegin

M:=CreateRotationМatrixЗD(VectorЗd(0,1,0),Angle); //вращение

Context.SetMatrix(M);

Context.DrawRect(VectorЗD(-5,-5,0), Vector3D(5,5,0), 1, TAlphaColorRec.Blue);

Angle:=Angle+0.1;

end;

//восстановим состояние контекста Context.SetMatrix(IdentityMatrixЗD); //! ! ! единичная матрица

// ...     другие операции

finally

Context.EndScene;

end;

end;

 

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

Другой вариант восстановления исходного состояния может быть основан на пред­ варительном чтении свойства

property CurrentMatrix: ТМаtriхЗD;//только для чтения

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

С трехмерными матрицами связано еще несколько доступных только для чтения свойств контекста (табл. 30.1).

 

Таблица 30.1. Информация о состоянии контекста, возвращаемая в формате тмatrixЗD

 

Свойство

Описание

property CurrentCarneraMatrix: TMatrixЗD;

Матрица вида. Представляет матрицу текущей камеры на основе пересчета вектора абсолютной позиции (AЬsolutePosition), т. е. позиции относительно данного контекста


 

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

 

Свойство

Описание

property CurrentCarnerainvMatrix: TMatrixЗD;

Инвертирует матрицу вида, возвращен- ную свойством CurrentCarneraМatrix

property CurrentProjectionМatrix: TMatrixЗD;

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

 

 

Текстуры

Доступные контексту текстуры хранятся в специальном массиве, который очища­ ется в момент старта новой сессии (вспоминаем метод BeginScene ()) при условии, что на момент инициализации сцены счетчик сцен равен нулю.

Одновременно в контекст может быть выбрано несколько текстур. Об их количест­ ве можно судить по состоянию свойства

property TextureUnitCount: Integer;  //только дпя чтения

Обычно свойство возвращает число 16.

Текущая (выводимая в данный момент времени) текстура доступна благодаря свой­ ству

property Texture: ТТехturе;//только дпя чтения

, Для размещения в контексте текстуры Texture следует воспользоваться методом

prooedure SetTexture(const AUnit: Integer; const Texture: TTexture);

Н первый параметр метода AUnit следует направить индекс элемента массива, в ко­ торый будет помещена текстура. Индекс не должен превышать размера массива (значения, возвращаемого свойством TextureUnitCount).

Размер воспринимаемой контекстом текстуры также ограничен, эту "тайну" хранит свойство

property MaxTextureSize: Integer; //только дпя чтения

Значение максимального размера зависит от выбранной библиотеки (DirectX или OpenGL) и обычно находится в пределе от 8 до 16 Кбайт.

 

Шейдеры

Являясь слугой двух господ (речь о Windows и OS Х), исповедующих принципи­ ально разный графический механизм одной стороны DirectX, а с другой - OpenGL), трехмерный контекст нуждается в некотором универсальном подходе к решению задач работы с ЗD-графикой. Одним из помощников на этом нелегком поприще выступает класс тcontextShader, объявление которого вы обнаружите в модуле FМX.TypesЗD.


 

 


ЗD-контекст TContextЗD                                                                                                                            417

 

Прочитав название класса тcontextShader, вы уже наверняка догадались, что он от­ вечает за доступ к подпрограммам обращения к шейдерам. Так как FireMonkey является кроссплатформенной библиотекой, то класс сделан всеядным - он спосо­ бе обращаться к шейдерам, представленным в DirectX 9/1О и OpenGL. Особо от­ метим, что поддерживается не только классическая спецификация OpenGL АRВ консорциума Architecture Review Board, но и подмножество OpenGL ES (OpenGL for Embedded Systems) для встраиваемых систем, широко применяемое в мобиль­ ных телефонах и карманных компьютерах.

 

ЗАМЕЧАНИЕ

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

Библиотека FireMonkey различает две разновидности шейдеров:

   вершинные шейдеры (vertex shader);

   пиксельные шейдеры (pixel shader).

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

Доступ к текущим вершинному и пиксельному шейдерам предоставляют свойства

class property CurrentVertexShader: TContextShader;

class property CurrentPixelShader: TContextShader;

Для смены шейдера следует воспользоваться методом

procedure SetShaders(const VertexShader,  PixelShader:  TContextShader);

 

ЗАМЕЧАНИЕ

В графическом конвейере существует еще одна разновидность шейдеров - гммет­ рические (geometry shader). К сожалению, с геометрическими шейдерами FireMonkey пока не работает.


 

 

 

 

 

 

 

 

 

1

                  ,

 

-

   

>•■■■      - ; ' ! " "

/■

..                                           ,

  ... .

. . . -

, ; V.-.

S. ".     Л

:■J‘r;»sэ {л'-;-

 

 

 

. г'т--

 

"      .  /     . .   -   -

.'•

.■

  :

 

 

:f

'flVf'

 

'

 

/

i

 

-,■

'.'sr/

-•! a

 

- ' I

 

 

-     .

..."

. -.  .  .    

 

 

'

 

 

 

 

' '                     ;

 

> ;

 

 

 

■''■

 

*

 

-  ■' '

 

 

 

 

 
\.y . " ■:   ' ■/

         "     ■'          '    .4 . ...:      . .         .

■'                             "’>                                ' -П       :-rV ■kx~~?l-Q,t. ".               •' '-с             / •,. . •'    ; •» ■, и»          . ■ -  .

- '                       '

. .   5      '                     . . . . . . .    '.   -

 

 

i

 

' .

'

'

-л *;

j i

С .

м-

Ц

 

:Vj - .«/V» Д»г.

 

 

 

 

 

.

 


 

-     ПЪ'Фi- г..-.." ry: -yyt         


Ш Q'


 

  -                -      - .                                                                                      .

 

 

 

-                                                                                                                                                           .■                                                              .            . . . .

 

.                  '                       ■‘ - ;                        '       .     '                    

 

^   ^           . ' * ‘w "     ». , > ~


;. '■    -     '     •-


    . ' V      '                      '

-    •'      . *    *. *.


>. . - ,


г ”Г; -


Л .1;-                      ; : "

 

;            -        ц?                         1                     ^  v ^                ^

- f:). : :у .7


 

 

 

 

 

 

 

ПРИЛОЖЕНИЕ  1

 

Вектор ТVectorЗD

 

 

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

 

ЗАМЕЧАНИЕ

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

В первой версии FireMonkey вектор представлял собой обычную запись тvectorЗD с вариантным полем и просто обеспечивал хранение координат конечной точки вектора. Позднее разработчики существенно расширили структуру, добавив в рас­ поряжение трехмерного вектора набор специализированных методов. А на роль обычного хранилища координат стала претендовать запись tagVECTORЗD (лис­ тинг Пl.1). В современных версиях FireMonkey запись тvectorЗD определена в мо­ дуле System. Types. Кроме того, надо знать о наличии в этом же модуле ряда вспо­ могательных функций, предназначенных для работы с векторами.

 

ВНИМАНИЕ!

В записях тvectorЗD и tagVECTORЗD хранятся только координаты конечной точки век­

тора, а в качестве начальной всегда принимается точка с координатами (О, О, О).


tagVECTORЗD = record case Integer of

О: (V: TVectorЗDType;);


 

1:  (Х: s.ingle;

У: Single;

Z: Single;

W: Single;);

end;

 

Как видите, вариантное поле содержит не только сведения о координатах конечной точки в трехмерном пространстве, но и включает поле w, предназначенное для опи­ сания дополнительного параметра вектора. Четвертый компонент показывает, вхо­ дит ли в состав объекта начало отсчета. Строго говоря, в 99% операциях с вектора­ ми компонент w никакого участия не принимает и хранит значение, равное 1. Ис­ ключение составляют операции нормализации этом случае в w оказывается ноль) и преобразование цвета ARGB в вектор (здесь в w помещается значение альфа­ канала). Четвертая координата вектора в основном задействуется в матричных опе­ рациях, но об этом поговорим немного позже.

Раз мы упомянули о связи между цветом и вектором, сразу приведем пару функций из программного модуля FМХ. Types ЗD.

functiod ColorToVectorЗD(AColor: TAlphaColor): ТVectorЗD;

 

Вполне естественно, что предусмотрено и обратное преобразование

function VectorЗDToColor(const AColor:  TVectorЗD):  TAlphaColor;

 

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

constructor Cr�ate(const Х, У, Z: Single; const W: Single=l.0); constructor Create(const APointЗD: TPointЗD; const W: Single=l.0);

или парой самостоятельных функций

function VectorЗD(const Х, У, Z: Single; const W: Single = 1.0): TVectorЗD;

overload;

function VectorЗD(const Р: TPointЗD; const W: Single = 1.0): TVectorЗD;

overload;

 

 

Длина вектора

Для расчета длины вектора (в математической литературе для понятия "длина век­ тора" введен термин "норма') в трехмерном пространстве стоит воспользоваться теоремой Пифагора

llv 11v=.x2+ v.y2 + v.z2                                 (Пl .1) или обратиться к подготовленному в стенах Embarcadero методу

function Length: Single;


 

 

 

 

 

 

Операция нормализации вектора выдает вектор того же направления, но единичной длины. Например, если исходный вектор задан значениями [О, О, 3], то его норма­ лизованный коллега примет значения [О, О, 1]. С точки зрения математики операция нормализации выглядит следующим образом:


 

-Vл =V-·


11V-1.1


(Пl .2)


 

Если описать формулу (Пl .2) более подробно, то мы увидим, что нормализуются все три координаты вектора:

/       2            2           2


vл=


V.X · yV.X +V.y  +V.Z

v.z · ,Jv.x2 + v.y2 + v.z2

v.z•y/v.x2 +v.y2 +v.z2


(Пl.3)


 

ЗАМЕЧАНИЕ

Кому не по душе язык формул, предложим более простое объяснение процесса нор­ мализации: нормализованный вектор - это исходный вектор, деленный на свою длину.

В FireMonkey нормализацию может выполнить функция

function Nonnalize:  TVectorЗD;

говорящая, как станет выглядеть вектор после нормализации.

 

ЗАМЕЧАНИЕ

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

 

 

Проверка равенства двух векторов

Для проверки равенства двух векторов программисту достаточно воспользоваться стандартным оператором=, обратная задача решается с помощью оператора<>.

'

Сложение и вычитание векторов

Мы с вами знакомы с векторами еще по урокам геометрии. В средней школе нам объясняли, что одна из базовых операций с двумя векторами - их сложение, и обязательно иллюстрировали процесс примерно таким р.1;;1сунком (рис. Пl.1).

Как видите, в результате операции суммирования двух векторов результирующий вектор 1\ +v2 окажется в точке С.

Если вам необходимо добавить вектор к текущему, то лучшим помощником станет символ сложения+.


 

 

 

 

 

с

 

(О, О, О)

 

 

(О, О, О)

 

Рис. П1.1. Сложение векторов 171 и 172                       Рис. П1.2. Вычитание векторов ii1 и 172

 

Обратная задача- вычитание векторов выполняется за счет операции - (лис­ тинг Пl.2).

i          },J!,1it•,. ,,  r  -              ,,\ "  ,  ,,,  \  л,  , ,.11•.,1                      , "                                                                                 , \ ,                         " .,,, , , , 'f,):Z ' :,.,, ;. ,w,  :::,"f, ,•;i f

1                                                                                                                                                                                               11


;.;.       ,,11: .•1;:. ,!At.i.,,.,"'Гl;J,tt 'fc"'- 'Jt-." .11::..il'ti "t1fll}i'fit. №.,/ .... ,"                         t  .,,_, ,жt1 , -  ,.,-ill; .;,1 "',1.,,t,           А     1 1 1,;,,: t .1:,t:.,",М,, i/'Jf;,J::;,1,: :11r."' 1141· 'i\l!'f.t

var vl,v2,v3,v4:TVector3D;

Ьegin

vl:=VectorЗD(l,0,0); v2:=Vector3D(-1,1,0)� v3:=vl+v2; //сложение векторов v4:=vl-v2; //вычитание векторов


f/ "


 

Результат операции вычитания диаметрально противоположен операции сложения. Теперь из компонентов (х, у, z) вектора i\ отнимаются значения соответствующих компонентов вектора v2 . Если два вектора представляют положения точек в про­

странстве, то вычитание задает направление и расстояние, которое следует пройти, чтобы попасть из одной точки в другую (рис. Пl .2).

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

function MidVector(const AVectorЗD:  ТVectorЗD):  TVectorЗD;

позволяющий рассчитать усредненный вектор по формуле:

(v1.x + v2.x) / 2]


result = (v1.y + v2 .у)/ 2 .

[


(Пl .4)


( VJ .z + V2 .z) / 2

 

Расстояние между двумя векторами

Дз: я получения расстояния между текущим (хранящимся в полях записи) и внеш­ ним вектором AVectorЗD предназначен метод

function Distance(const AVectorЗD: TVectorЗD): Single;


 

 

Вектор ТVectorЗD                                                                                                                                        423


 

Расчеты осуществляются в соответствии с выражением


r = .Jv1 - self .x + ..Jv1 - self .y + ..Jv1.z - self .z,

здесь self- текущий вектор, v1 -          внешний вектор.

 

Масштабирование вектора


 

 

(Пl.5)


Масштабирование вектора (на математическом языке - умножение вектора на скаляр) заключается в умножении каждого компонента вектора на одно и то же число:


kv =r:::::1.

k·v.z


(Пl.6)


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

 

после масштабирования

 

 

 

 

 

(О, О, О)                                       (0, О, О)

Рис. П1.З. Масштабирование вектора

 

За умножение вектора на скаляр в Delphi отвечает оператор класса Mul tiply, вызы­ ваемый с помощью классического для арифметических операций умножения сим­ вола *. В качестве сомножителей могут выступать вектор и вещественная перемен­ ная или два вектора. Заметим, что если в качестве множителя параметр передать -1, мы инвертируем направление вектора.

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

 

Векторное произведение

Векторное произведение (cross product) двух векторов- это третий вектор, назы­ ваемый вектором нормали (nonnal), который рассчитывается следующим образом:

Vl ·V2.Z-V1.Z·  V2·Y1


n =  V1.Z·V2.X-V1.X·V2.Z  .

[


(Пl.7)


V1 ·V2·Y-V1 ·V2.X


 

У!

1

 

v1t -/ z

v2  х

Рис. П1.4. Векторное произведение векторов V1 и li2

 

Возвращаемый в результате выполнения выражений (Пl.7) вектор нормали п всегда перпендикулярен аргументам произведения -  векторам 1\ и v2 (рис. Пl .4).

Чтобы мы не теряли времени, разрабатывая функцию для расчета векторного про­ изведения, разработчики FireMonkey дарят нам уже готовый метод

function CrossProduct(const AVectorЗD: TVectorЗD): TVectorЗD;

Чтобы убедиться, что результатом операции векторного произведения окажется вектор, перпендикулярный первым двум, можно повторить листинг Пl.З.

r л  -,,1 з:····             ltl81111111•                 . .                    .  . .  . . .             .                                                           '

:1.••••,...........................................                                                                                                                                                          ........................,,•; -.,.,,.. .-•• ............a:- ••,.. ,n..-.................·.. ,.-.,:,.. ..,,..............-- .. ·-,- •J"'...U'\>-;l\. - ·""· --- - -·

 

varvl,v2:TVector3D;

Ьegin

.//------  1 вариант------------- ­

vl:=VectorЗD(l,0,0,0);

v2:=Vector3D(0,1,0,0);

vl:=vl.CrossProduct(v2);  //перпендикуляр  vЗ=(О,О,1,1)

//-------- 2 вариант----------- ­

vl:=VectorЗD(l,0,0,0);

v2:=Vectoт3D(0,1,0,0);

v2:=v2.CrossProduct(vl); //перпендикуляр v3=(0,0,-1,1) end;

 

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

 

ЗАМЕЧАНИЕ

Векторное произведение двух параллельных векторов возвратит нулевое значение.

 

 

Скалярное произведение

Произведение, которое вычисляется как сумма попарных произведений компонен­ тов векторов, называется скалярным произведением (dot product). Оно рассчитывает не вектор, а скаляр (одно значение).

(Пl .8)


На основе выражения (Пl.8) сконструирован метод

function DotProduct(const AVectorЗD: TVectorЗD): Single;

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

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

                                                    (Пl.9)

где а - угол между векторами v1 и v2 . В данном случае в роли множителей вы­ ступают длины векторов и косинус угла между ними. Результатом выражения ока­ жется не вектор, а число (скаляр), не зависящее от системы координат и характери­ зующее длины векторов-сомножителей и угол между ними.

Комбинируя выражения (Пl.6) и (Пl.7), мы можем получить косинус угла между двумя векторами, в свою очередь косинус легко переводится в радианы или граду­ сы. Впрочем, в Embarcadero тоже читали учебники по геометрии, поэтому и разра­ ботали метод

function AngleCosine(const AVector: TVectorЗD): Single;

 

рассчитывающий значение косинуса.

Скалярное произведение векторов - достаточно полезная штука, благодаря ему мы сможем быстро выяснить следующее:

   если результат скалярного произведения меньше О, то угол между векторами больше 90° (тупой);

  если результат равен О, то векторы перпендикулярны друг другу;

  если результат больше О, то угол между векторами меньше 90° (острый).

Чем еще интересно скалярное произведение? Оно поможет рассчитать проекцию одного вектора на другой, например вектора i\ на вектор v2 . Для этого нам доста­ точно рассчитать длину вектора v1 и вычислить косинус угла между векторами -

и проекция у нас в "кармане" (рис. Пl.5).

 

 

 

 

 

 

 

1  IV1I cos(a)  \     Результат

скалярного произведения

 

Рис. П1.5. Скалярное произведение векторов ii1 и ii2


 

Поворот вектора

Операция поворота является наиболее сложной из всех рассмотренных ранее опе­ раций с вектором. Для расчета поворота вектора в FireMonkey задействуется специ­ альная структура - матрица тмatrixЗD, в рамках которой осуществляется трехмер­ ное преобразование. Для упрощения работы обычного программиста в модуле ЕМХ. TypesЗD о?исана простая в использовании функция

function Rotate(Angle: Single; const axis: ТVectorЗD): TVectorЗD;

Она поворачивает вектор vector на угол Angle вокруг оси, заданной вторым векто­ ром axis. Например, axis:=VectorЗD (1, о, о) заставит вектор вращаться вокруг оси .х, axis :=VectorЗD (О, 1, О) -  вокруг у, и т. д.

Для осуществления поворота следует помнить направление вращения относительно координатных осей (рис. Пl .6).

 

у

 

 

 

х

 

Рис. П1.6. Направление вращения относительно осей координат

 

Пример из листинга Пl .4 демонстрирует порядок вращения объекта вокруг коор­ динатной оси у на 1° против часовой стрелки.


var          V, axis: TVectorЗD; const               angle=pi/180;

Ьegin

V:=VectorЗD(Cubel.Position.Vector.X,

CuЬel.Position.Vector.Y, CuЬel.Position.Vector.Z);

Axis:=VectorЗD(0,1,0);                        //выбор оси у дпя вращения объекта


V.Rotate(angle, axis); CuЬel.Position.Vector:=V;

end;


//расчет вектора поворота

//поворот объекта


 

Отражение вектора

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


 

отраженного вектора хорошей подсказкой станет напоминание о том, что угол па­ дения а.1 всегда равен углу отражения а.2 (рис. Пl. 7).

Как всегда, помощь в расчетах может оказать Delphi, а точнее функция

function Reflect(const AVectorЗD: TVectorЗD): TVectorЗD;

Метод возвращает отражение вектора AVectorЗD относительно плоскости, постро­ енной на основе нормализованного вектора N .

 


Рис. П1.7. Направление отражения относительно осей координат


ПРИЛОЖЕНИЕ  2

 

Матрица преобразований

TMatrixЗD

 

Для определения положения ЗD-объекта на плоском экране необходимо применить к его вершинам преобразование, которое отобразит трехмерные координаты объек­ та в пространстве на двухмерные координаты окна. Основную роль в этом преобра­ зовании играет квадратная матрица размера 4х4, которая является суперпозицией отдельных преобразований перспективы, масштабирования и переноса.

В библиотеке FireMonkey за матричное представление отвечает класс тмatrixЗD,

физически матрица представляет собой запись с вариантным полем (листинг П2.1).


TMatrixЗD = record

private

 

�se Integer of

О: (М:  TMatri ЗDType;);

1: (mll, m12, mlЗ, m14: Single; m21, m22, m23, m24: Single; m31, m32, mЗЗ, m34: Single; m41, m42, m43, m44: Single);

end;

 

Благодаря вариантному полю мы получаем возможность рассматривать матрицу преобразований как структуру TMatrixЗDType, храняшую сведения о четырех векто­ рах тvectorЗD, или набор из 16 вещественных чисел.

 

ЗАМЕЧАНИЕ

В математике используются матрицы различного размера. Говоря языком програм­ миста, в роли матрицы вправе выступать любой двухмерный массив размера MxN. Но так как в FireMonkey размер матрицы тмatrixЗD четко определен, далее мы будем предполагать, что все используемые нами матрицы состоят из 4 колонок и 4 рядов.

В ранних версиях FireMonkey (ХЕ2/ХЕ3/ХЕ4) для заполнения полей матрицы пре­ образований проще всего воспользоваться библиотечными функциями


 

function MatrixЗD (const mll, ml2, mlЗ, ml4, m21, m22, m23, m24,

m31, m32, mЗЗ, m34, m41, m42, m43, m44:Single): TMatrixЗD; overload; function MatrixЗD(const LArr: TSingleDynArray) :ТМatrixЗD;                                overload;

Заметим, что перечисленные функции вынесены в программный модуль FМХ. туреsЗD.

Начиная с Delphi ХЕ5, матрица снабжена десятком конструкторов create ... , по­ зволяющих создавать матрицу смещения, вращения просмотра и т. п.

 

Нулевая и единичная матрицы

В матричной математике существуют два краеугольных понятия - нулевой и еди­ ничной матрицы.

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

Особенность единичной матрицы в том, что это квадратная матрица, по главной диагонали которой расположены 1, а все остальные элементы принимают значения, равные О. В ранних версиях FireMonkey (ХЕ2/ХЕЗ/ХЕ4) в модуле FМX.TypesЗD объ­ явлена константа (листинг П2.2), призванная играть роль единичной матрицы.

_._,.,,. ...,...,,.,....... ············--·····••-.•·...·-················,,,_,.,....,,....•,... ..'" _., ............'"''..,.................' ......,,_,..  ·•"\ ,........................,....... ' .....,..,...,,,,........,......,......-...... ....... ,..,.,.,............. .,.,,,,.

ln..  .....u.1............               ..1 ...-....................·•··················-······························.· !

const IdentityMatrixЗD: TMatrixЗD =

(mll: 1.0; ml2: О.О; mlЗ: О.О; ml4: О.О;

m21: О.О; m22: 1.0; m23: О.О; m24: О.О;


m31: О.О; m32: О.О;

m41: О.О; m42: О.О;


mЗЗ: 1.0; m34: О.О; m43: О.О; m4-4: 1.0;);


 

Матрица переноса

Операция переноса (трансляции)- одна из основных и одновременно простейших матричных операций, применяемых в FireMonkey. Задача операции заключается в перемещении точки в ЗD-пространстве в заданном направлении на определенное расстояние. Для осуществления операции переноса создается специальная матри­ ца - матрица трансляции (translation matrix). Основой матрицы трансляции вы­ ступает единичная матрица, но с одной важной модификацией - в ее элементы m41, m42 и m4з помещаются значения смещения, определяющие направление и ха­ рактер переноса по.соответствующим осям координат.

1      о    о   о

о    1     о   о

Мт=   о    о    1     о                                (П2.1)

рх   Ру    pz


 

Программное решение этой задачи предоставлено функции

class function CreateTranslation(const ATranslation: TPointЗD): ТМatrixЗD;

Функция передает значения переноса через параметр ATranslation и возвращает матрицу трансляции.

 

Матрицы вращения

Для осуществления поворота вектора на заданный угол нам следует создать мат­ рицу вращения (rotation matrix). Матрицу вращения MR интересует выраженный в радианах угол поворота вектора и координатная ось, относительно которой осуще­ ствляется поворот. Особо отметим, что заполнение элементов матрицы вращения зависит от того, относительно какой из осей координат осуществляется вращение:

 

 


ось х:   MRX=

 

 

 

 

ось у: MRY=

 

 

 

 

ось z:     MRZ=


(П2.2)

 

 

 

 

 

1

о о о

о

cos0

-sin0

о

о

sin0 cos0

о

о о о

 

cos0

о

 

-sin0

о

о

1

о

о

sin0

о

cos0

о

о

о

о

1

 

cos0     sin0

о

о

-sin0    cos0

о

о

о

о

1

о

о

о

о

1

 

 
(П2.3)

 

 

 

 

 

(П2.4)


 

 

Для создания матрицы вращения предусмотрен ряд методов. В первую очередь это простейшие методы

class function CreateRotationX(const AAngle: Single): TMatrixЗD; class function CreateRotationY(const AAngle: Single): TMatrixЗD; class function CreateRotationZ(const AAngle: Single): TMatrixЗD;

осуществляющие поворот вокруг заданной оси на угол, определяемый в параметре

AAngle.

Во втором эшелоне следует метод

class function CreateRotation(const AAxis: TPointЗD; const AAngle: Single): TMatrixЗD;

Вращение осуществляется относительно оси, исходящей из точки (О, О, О) и прихо­ дящей в точку AAxis.


 

Еще более сложный метод создан специально для поклонников авиасимуляторов:

class function CreateRotationYawPitchRoll(const AYaw, APitch, ARoll: Single): TMatrixЗD; static;

Здесь для создания матрицы вращения методы оперируют такими авиационными понятиями, как: рысканье (yaw), тангаж (pitch) и крен (rol[).

 

ЗАМЕЧАНИЕ

Если вы далеки от аэронавтики и термины "рысканье", "тангаж" и "крен" вам не знако­ мы, то мы это в два счета исправим. Чтобы разобраться с а"Виационной тематикой, представьте, что наблюдатель смотрит на нос самолета, находясь за его хвостовым оперением. В этом случае ось z идет от хвоста к носу самолета. Ось х протянулась от кончика левого крыла к кончику правого. Ось у направлена вверх от земли. В этом случае тангаж определяет вращение вокруг оси х, рысканье - вращение вокруг у и крен - вращение вокруг z.

 

 

Матрица_масштабирования

Для пропорционального увеличения (уменьшения) вектора предусмотрена матри­ ца масштабирования. (П2.5). Значения коэффициентов масштабирования заносятся в элементы главной диагонали: коэффициент Sx масштабирования по оси х -

в ячейку т11; Sy - т22, Sz - тзз- Таким образом, матрица позволяет определять масштаб по отдельности для каждой из осей координат.

sx   о    о    о

о   Sy     о   о

Ms=      о    о   sz о                                (П2.5)

о    о    о   1

Если вам не нравится прямой доступ к матрице, то воспользуйтесь штатной функ­ цией

function CreateScaling(const AScale: TPointЗD): TMatrixЗD;

и передайте соответствующие коэффициенты в параметр AScale.

В FMX предусмотрен еще один способ масштабирования, но на этот раз не отдель­ ного вектора, а всех элементов матрицы

,

prooedure Scale (const AFactor: Single);

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

 

Умножение матриц

Ни у кого не вызывает затруднений освоение операций сложения и масштабирова­ ния матриц, ведь они соответствуют обычным арифметическим действиям. Умно­ жение матриц -  не столь прозрачный процесс. Здесь элементы из строки первой


 

 

матрицы умножаются на элементы в столбце второй матрицы и полученный ре­ зультат складывается. Чтобы проиллюстрировать суть этого правила, вместо ис­ пользуемых в FireMonkey матриц 4х4 воспользуемся матрицами меньшего размера:

 


a11h12+ a12h22

a21h12 +а22Ь22


(П2.6)


 

1  о   о

о

 

0,5     о     о    о

 

0,5     о

о    о

о   1     о

о

 

о    0,5     о    о

 

о   0,5

о    о

о о         о

1  о  -2     1

о     о    0,5  о      о     о    0,5  о

о     о     о    1        0,5     о    -1

 

 
Проиллюстрируем правило умножения на примере. Допустим, нам необходимо пе- ремножить матрицы Mr и Ms. ·

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 
M1s =Мт·Мs =

 

 

Чтобы завершить задуманное, умножим полученный результат на матрицу враще­

ния MRY·


 

Mтs ·MRr =


0,5  о  о о о 0,5  о о о  о  0,5  о

0,5  о  -1  1


О, 707    о :_о, 101 о

о      1        о      о

О, 707    о   0,707     о =

о     о      о      1


0,354

о

о

0,5

0,354

о

о о

-0,354

о

0,354

о

1,06

о

-0,354

1

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

 

ВНИМАНИЕ!

При выполнении операции перемножения матриц помните, что при перестановке мес­ тами сомножителей результат меняется!

 

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

TVectorЗD.

 

ЗАМЕЧАНИЕ

Операция матричного умножения некоммутативна, т. е. МтхМs* МsхМт.

 

 



Матрица преобразований TMatrix3D                                                                                                       433

 

Дополнительные матричные операции

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

 

Таблица П2.1. Дополнительные матричные функции модуля FМХ. TypesЗD

 

Функция

Описание

function Transpose: TMatrixЗD;

Транспонирует исходную матрицу, заменяя строки столбцами

function Determinant(const М: TMatrixЗD) : Single;

Возвращает определитель (детерминант) матрицы

function Inverse: TMatrixЗD;

Соэдание обратной матрицы. Основное свойство обратной матрицы в том, что при умножении ее на исходную матрицу на выходе получается единич- ная матрица


ПРИЛОЖЕНИЕ  3

 

Модуль System.lOUtils

 

 

Модуль System. IOUtils содержит определение трех кроссплатформенных записей TDirectory, TFile и TPath, предназначенных для работы с файловой системой в опе­ рационных системах Windows, OS Х, iOS и Android.

В табл. ПЗ.1 представлены основные методы записи TDirectory. Запись отвечает за управление отдельным каталогом (папкой).

 

Таблица ПЗ.1. Работа с каталогом средствами записи TDirectory

 

Метод

Описание

class procedure Copy(const SourceDirName, DestDirName: stri99");

Копирование каталога SourceDirName в каталог DestDirName

class prooedure CreateDirectory(Path: string);

Создание каталога с путем

Path

class procedure Delete(const Path: string); overload; class procedure Delete(const Path: strinq; const

Recursive:  Boolean);  overload;

Удаление каталога Path. Вто- рая версия метода обеспечи: вает рекурсивный вызов ме- тода при удалении вложенных каталогов (Recursive=true)

class function Exists(const Path: string; FollowLink: Boolean = True): Boolean;

Проверка существования каталога Path

class function GetAttributes(const Path: string;

FollowLink: Boolean = True): TFileAttributes;

Получение атрибутов каталога

class function GetCurrentDirectory: string;

Выяснение текущего каталога

class procedure SetCurrentDirectory(const Path: string);

Установка текущего каталога

class function GetLogicalDrives: TStringDynArray;

Выяснение текущих логических дисков

class function GetCreationTime(const Path: string):

TDateTime; static;

class function GetCreationTimeUtc(const Path:

string): TDateTime;

Время создания каталога


 

Таблица ПЗ.1 (продолжение)

 

Метод

Описание

class f'unction GetLastAccessTime(const Path: string):

TDateTiпie;

class function GetLastAccessTimeUtc(const Path:

string): TDateTime;

Время последнего доступа к каталогу

class function GetLastWriteTime(const Path: string): TDateTime;

class f'unction GetLastWriteTimeUtc(const Path:

stri.ng) : TDateTime;

Время последней записи в каталог

class prooedure SetAttributes(

const Path: string;

const Attributes: TFileAttributes);

Установка атрибутов каталога

class prooedure SetCreationTime(

const Path: string;

const CreationTime: TDat�Time);

class prooedure SetCreationTimeUtc(const Path:

string; const CreationTime: TDateTime);

Установка времени создания каталога

class prooedure SetLastAccessTime(const Path: string; const LastAccessTime: TDateTime);

class prooedure SetLastAccessTimeUtc(const Path:

string; const LastAccessTime: TDateTime);

Установка времени последне- го доступа к каталогу

class prooedure SetLastWriteTime(const Path: string; const LastWriteTime: TDateTime);

class prooedure SetLastWriteTimeUtc(const Path:

string; const LastWriteTime: TDateTime);

Установка времени последней записи в каталог

class f'unction GetParent(const Path: string): string;

Получение имени родитель- ского каталога

class function GetDirectories(const Path: string):

TStringDynArray;  overload;

class function GetDirectories(

const Path: string;

const Predicate: TFilterPredicate): TStringDynArray; overload;

class function GetDirectories(const Path, SearchPattern: string): TStringDynArray; overload; class function GetDirectories(const Path,

SearchPattern:  string;

const Predicate: TFilterPredicate): TStringDynArray;

overload;                               -

class f'unction GetDirectories(const Path,

SearchPattern: string; const SearchOption: TSearchOption) : TStringDynArray; overload; class function GetDirectories(const Path,

SearchPattern: string; const SearchOption: TSearchOption; const Predicate: TFilterPredicate): TStringDynArray; overload;

class f'unction GetDirectories(

oonst Path: string;

const SearchOption: TSearchOption;

const Predicate: TFilterPredicate):                     .

TStringDynArray;  overload;

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

Расширенные версии метода позволяют настраивать критерии выбора

 

 

.


Таблица ПЗ.1 (окончание)

 

Метод

Описание

class function GetDirectoryRoot(const Path: string): string;

Получение корневого каталога

class function GetFiles(const Path: string): TStringDynArray; overload;

class function GetFiles(const Path: string; const Predicate: TFilterPredicate):

TStringDynArray;  overload;

class function GetFiles(const Path,  SearchPattern:

string): TStringDynArray;  overload;

class function GetFiles(const Path, SearchPattern: string; const Predicate: TFilterPredicate): TStringDynArray; overload;

class function GetFiles(const Path, SearchPattern: string; const SearchOption: TSearchOption): TStringDynArray; overload;

class function GetFiles(const Path, SearchPattern: string; const SearchOption: T$earch0ption; const Predicate:  TFilterPredicate): TStringDynArray; overload;

class function GetFiles(const Path: string; const SearchOption: TSearchOption;

const Predicate: TFilterPredicate): TStringDynArray; overload;

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

class function GetFileSystemEntries(const  Path:

string): TStringDynArray;  overload;

class function GetFileSystemEntries(const Path: string; const Predicate: TFilterPredicate): TStringDynArray; overload;

class function GetFileSystemEntries(const Path, SearchPattern: string): TStringDynArray; overload;

class function GetFileSystemEntries(const Path, SearchPattern: string; oonst Predicate: TFilterPredicate): TStringDynArray; overload;

class function GetFileSystemEntries(const Path: string; const SearchOption: TSearchOption; const Predicate:  TFilterPredicate): TStringDynArray; overload;

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

class function IsEmpty(const Path: string): Boolean;

Проверка на пустой каталог

class function IsRelativePath(const Path: string): Boolean;

Проверка относительного пути

class proaedure Move(const SourceDirName, DestDirName: string);

Перемещение каталога

 

Запись TFile предназначена для осуществления основных операций над файлами (создание, удаление, операции ввода/вывода, поиск). Методы записи представлены в табл. ПЗ.2.


 

Таблица ПЗ.2. Работа с файлом средствами TFile

-


Метод

class function Create(const Path: string): TFileStream; overload;

class function Create(const Path: string; const BufferSize: Integer) : TFileStream; overload;

class procedure AppendAllText(const Path, Contents: string); overload;

class procedure AppendAllText(const Path, Contents: string; const Encoding: TEncoding) ; overload;

 

class function AppendText(const Path: string): TStreamWriter;

 

 

class prooedure Copy(const SourceFileName, DestFileName: string);        overload;

class prooedure Copy(const SourceFileNarne, DestFileName: string; const Overwrite: Boolean); overload;

class function CreateSymLink(const Link, Target: string): ВOolean;

class function CreateText(const Path: string): TStreamWriter;

 

class prooedure Decrypt(const Path: string); class prooedure Delete(const Path: string); class prooedure Encrypt(const Path: string);

class function Exists(const Path: string; FollowLink: Boolean = True): ВOolean;

class function GetAttributes(const Path: string; FollowLink: ВOolean = True): TFileAttributes;

class function GetCreationTime(const Path: string): TDateTime;

class function GetCreationTimeUtc(const Pa,th: string): TDateTime;

class function GetLastAccessTime(const Path: string): TDateTime;

class function GetLastAccessTimeUtc(const Path: string): TDateTime;

class function GetLastWriteTime(const Path: string): TDateTime;

class function GetLastWriteTimeUtc(const Path: string): TDateTime;


Описание

 

Создает файл Path, готовый для записи, и возвращает экземпляр потока TFileStream

 

Добавляет к файлу весь текст из параметра Contents.

Расширенная версия функции позволяет обеспечить шифрование данных

Открывает файл для добавле- ния в него текстовых данных, возвращает поток TStreamWriter

Копирует файл SourceFileName в файл DestFileName

 

 

 

Создает ссылку на файл

 

Создает текстовый файл и предоставляет к нему доступ в. виде потока TStreamWri ter

Расшифровывает файл Удаляет файл Шифрует файл

Проверяет существование файла

Возвращает атрибуты файла

 

Возвращает время создания файла

Возвращает время создания файла

Возвращает время последнего доступа к файлу

 

 

 

Возвращает время последнего изменения файла



Таблица ПЗ.2 (продолжение)

 

Метод

Описание

class function GetSymLinkТarget(const FileName:

string; SymLinkRec: TSymLinkRec) : Boolean; overload;

class function GetSymLinkTarget(const FileName: string;

var TargetName: string): Boolean; overload;

Дает ссылку на файл

class proced:ure Move(SourceFileName, DestFileName:

strinq);

Перемещает файл

class function Open(const Path: string;

const Mode: TFileМode) : TFileStream; overload;

class function Open(const Path: strinq;

const Mode: TFileMode; const Access: TFileAccess) : TFileStream; overload;

class function Open(const Path: string;

const Mode: TFileМode; const Access: TFileAccess;

const Share: TFileShare) : TFileStream; overload;

Открывает файл Path и пре- доставляет к нему доступ при посредничестве файлового потока TFileStream

class function OpenRead(const Path: string):

TFileStream;

Открь1вает файл только для чтения

class function OpenText(const Path: string).:

TStreamReader;

Открывает файл для доступа к тексту

class function OpenWrite(const Path: string):

TFileStream;

Открывает файл для записи

class function ReadAllBytes(const Path: string):

TBytes;

Открывает файл и предоставляет к нему доступ в виде массива байтов

class function ReadAllLines(const Path: strinq):

TStringDynArray;       overload;

class function ReadAllLines(

const Path: string;

const Encoding: TEncoding): TStringDynArray;

overload;

Читает строки из файла и воз- вращает их в виде массива строк

class function ReadAllText(const Path: stri.ng):

string;  overload;

class function ReadAllText(const Path: string; const

Encoding: TEncoding) : string; overload;

Читает данные из файла

и возвращает их в формате строки

class procedure Replace(const SourceFileName, DestinationFileName, DestinationBackupFileName: string); overload;

class prooedure Replace(SourceFileName, DestinationFileName, DestinationBackupFileName: string; const IgnoreMetadataErrors: Boolean); overload;

Заменяет файл с именем DestinationFileName файлом SourceFileName

class prooedure SetAttributes(const Path: string; const Attributes: TFileAttributes);

Устанавливает атрибуты файла

class procedure SetCreationTime(const Path: string;

const CreationTime: TDateTime);

class proced:ure SetCreationTimeUtc(const Path:

string; const CreationTime: TDateTime);

Устанавливает время создания файла


 

 

 

 

 


Метод

Описание

class prooedure SetLastAccessTime(const Path:

string; const LastAccessTime: TDateTime);

class procedure SetLastAccessTimeUtc(const Path:

string; const LastAccessTime: TDateTime);

Устанавливает время последнего доступа к файлу

class prooedure SetLastWriteTime(const Path: string; const LastWriteTime: TDateTime);

class prooedure SetLastWriteTimeUtc(const Path:

string; const LastWriteTime: TDateTime);

Устанавливает время последнего изменения файла

class prooedure WriteAllBytes(const Path: string; const Bytes: TBytes);

Записывает в файл данные из массива байтов Bytes

class proaedure WriteAllLines(const Path: string; const Contents: TStringDynArray); overload; static;

class procedure WriteAllLines(const Path: string; const Contents: TStringDynArray; const Encoding: TEncoding) ; overload;

Записывает в файл все строки из массива

 

class prooedure WriteAllText(const Path, Contents:

string); ovarload;

class prooedure WriteAllText(const Path, Contents:

string; const Encoding: TEncoding); overload;

Записывает в файл текст из параметра Contents

 

Запись TPath специализируется на обслуживании путей и имен файлов и папок (табл. ПЗ.3).

 

Таблица ПЗ.3. Путь TPath

 

Методы III свойства

Описание

class function IsValidPathChar(const AChar: Char): Boolean;

Проверка символа на допустимость применения в пути

class function IsValidFileNameChar(const AChar: Char): Boolean;

Проверка допустимости символа для использования в имени файла

class function HasValidPathChars(const Path: string; const UseWildcards: Boolean): Boolean;

Проверка допустимости символов в пути

class function HasValidFileNameChars(const FileName:

string; const UseWildcards: Boolean): Boolean;

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

class function GetExtendedPrefix(const Path: string):

TPathPrefixType;

TPathPrefixType = (pptNoPrefix, pptExtended, pptExtendedUNC);

Возвращает тип пути, если путь начинается с префикса

,     \ \ ?\', будет возвращено

pptExtended;ecли

С '\\?\UNC\' -

 

pptExtendedUNC; иначе -

pptNoPrefix


Таблица nз.з (продолжение)

 

Методы и свойства

Описание

class function IsDriveRooted(const Path: string): Boolean;

Проверяет, находится ли

в корне пути имя логического диска

class function IsExtendedPrefixed(const Path: string): Boolean;

Проверяет, не начинается ли путь с префикса '\\?\UNC\'

class function IsRelativePath(const Path: string): Boolean;

Проверяет, является ли путь относительным

class function IsUNCPath(const Path: string): Вoolean;

Проверяется, является ли путь универсальным (UNC)

class function IsUNCRooted(const Path: string): Boolean;

Проверяется, находится ли в корне пути префикс '\\?\UNC\'

class function GetGUIDFileName(const UseSeparator: Boolean = False): string;

Возвращает глобальный универсальный идентифика- тор файла

class function DriveExists(const Path: string): Boolean;

Проверяет, имеется ли

в составе пути логический

ДИСК

class function MatchesPattern(const FileName, Pattern: string; const CaseSensitive: Boolean): Boolean;

Проверяет соответствие имени FileName шаблону Pattern с учетом (или без учета) регистра CaseSensitive

class function ChangeExtension(const Path, Extension: string) : string;

Изменяет расширение имени файла

class function ComЬine(const Pathl, Path2: string): string;

Проверяет два пути к одно- му и тому же ресурсу и воз- вращает абсолютный путь

class function GetDirectoryName(FileName: string): string;

Возвращает путь к файлу

FileName

class functiorl" GetExtension ( const FileName: string): string;

Возвращает расширение имени файла

class function GetFileName(const FileName: string): string;

Возвращает только имя

файла из полного пути

class function GetFileNameWithoutExtension(const FileName: string): string;

Возвращает имя файла без расширения имени

class function GetFullPath(const Path: string): string;

Возвращает полный путь

class function GetinvalidFileNameChars: TCharArray;

Возвращает массив

с недопустимыми символа-

ми в имени файла

class function GetinvalidPathChars: TCharArray;

Возвращает массив

с недопустимыми символа-

ми в пути


 

 

 

 

 


Методы и свойства

Описание

class function GetPathRoot(const Path: string): string;

Возвращает корневой эле- мент в пути

class function GetRandomFileName: string;

Возвращает случайное имя

файла

class function GetTempFileName: string;

Возвращает полный путь и

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

class function GetTempPath: string;

Возвращает путь к времен- ной папке

class function GetHomePath: string;

Возвращает путь к папке Roaming

class function GetDocшnentsPath: string;

Путь к документам

class function GetSharedDocшnentsPath: string;

Путь к общедоступным документам

class function GetLibraryPath: string;

Путь к папке с библиотеками

class function GetPicturesPath: string;

Путь к папке с рисунками

class function GetSharedPicturesPath: string;

Путь к общедоступной папке с рисунками

class function GetAttributes(const Path: string; FollowLink: Boolean = True): TFileAttributes;

Возвращает атрибуты файла

class proaedure SetAttributes(const Path: string; const Attributes: TFileAttributes);

Устанавливает атрибуты файла

class function HasExtension(const Path: string): Boolean;

Проверяет наличие расши- рения имени

class function IsPathRooted(const Path: string): Boolean;

Проверяет наличие в пути корневого элемента

class property ExtensionSeparatorChar: Char;

Хранит символ-разделитель имени и расширения имени, по умолчанию ' . '

class property AltDirectorySeparatorChar: Char;

Альтернативный символ- разделитель между именами каталогов, по умолчанию

'/'

class property DirectorySeparatorChar: Char;

Символ-разделитель между именами каталогов, по умолчанию ' \ '

class property PathSeparator: Char;

Символ-разделитель между путями, по умолчанию ' ; '

class property VolumeSeparatorChar: Char;

Символ-разделитель имени диска, умолчанию • :


 


ПРИЛОЖЕНИЕ  4

 

Датчики

 

 

Программный модуль system. Sensors содержит определение многочисленных уст­ ройств-датчиков, которые моrут быть задействованы в мобильных устройствах. Доступ к датчикам осуществляется при посредничестве менеджера тsensorмanager. Описание базовых классов датчиков представлено в табл. П4.1-П4.6 и в главе 25.

 

Таблица П4.1. Датчик движения TCustomМotionSensor

 

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

Описание

property Started: Вооlеаn;//только чтения

Проверка активности датчика

property SensorType: TMotionSensorType; ТМotionSensorType = (AccelerometerlD, Accelerometer2D, AccelerometerЗD, MotionDetector,

GyrometerlD, Gyrometer2D, GyrometerЗD, Speedometer);

Тип датчика

property AccelerationX: DouЬle;              '

property AccelerationY: DouЬle;

property AccelerationZ: DouЬle;

Ускорение по осям х, у и z

property AngleAccelX: DouЬle;

property AngleAccelY: DcuЬle;

property AngleAccelZ: DcuЬle;

Угловое ускорение по осям х, у

и z в градусах/секунды2

property Motion: DouЬle;

Состояние движения

property Speed: DouЬle;

Скорость в метрах в секунду

property Updatelnterval: DouЬle;

Частота обновления данных

 

 

Таблица П4.2. Датчик  электрического устройства  тcustomElectricalSensor

 

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

Описание

property SensorType: TElectricalSensorType; TElectricalSensorType = (Voltage, Current, Capacitance, Resistance, Inductance, ElectricalPower, Potentiometer);

Тип электрического датчика


 

 

 


Датчики                                                                                                                                                           443

 

Таблица П4.2 (окончание)

 

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

Описание

property Capacitance:  DouЫe;

Емкость в фарадах

property Resistance: DouЬle;

Сопротивление в омах

property Inductance: DouЫe;

Индуктивность в генри

property Current: DouЬle;

Сила тока в амперах

property Voltage: DouЫe;

Напряжение в вольтах

property Power: DouЬle;

Мощность в ваттах

 

 

Таблица П4.З. Датчик механического устройства  тcustomМechanicalSensor

 

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

Описание

property SensorType: ТМechanicalSensorType;

TMechanicalSensorType = (BooleanSwitch, BooleanSwitchArray, MultiValueSwitch, Force, Scale, Pressure, Strain);

Тип механического датчика

property SwitchState: Boolean;

Состояние: включен/выключен

property SwitchArrayState: Cardinal;

Состояние для многопозицион- наго переключателя

property Force: DouЬle;

Сила в ньютонах

property AЬsPressure: DouЫe;

Абсолютное давление в паскалях

property GaugePressure: DouЬle;

Манометр, возвращает давление в паскалях

property Strain: DouЫe;

Натяжение

property Weight: DouЫe;

Вес в килограммах

 

 

Таблица П4.4. Биометрический датчик  TCustomBiometricSensor

Cuadro de texto: Свойства/методы	Описание
property SensorType: TBiometricSensorType; TBiometricSensorType = (HumanPresence, HumanProximity, Touch);	Тип датчика
property HumanPresense: Boolean;	В состоянии true, если опера­ тор работает с устройством
property HumanProximity: DouЬle;	Дистанция в метрах между устройством и оператором
property Touch: Boolean;	В состоянии true, если опера­ тор касается датчика



444                                                                                                                                                Приложение 4

 

Таблица П4.5. Датчик освещения  TCustomLightSensor

 

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

Описание

property SensorType: TLightSensorType; TLightSensorType = (AmЬientLight,) ;

Тип датчика

property Lux: DouЫe;

Освещенность в люксах

property Temperature: DouЬle;

Цветовая температура

property Chromacity: DouЬle;

Цветность

 

 

 

 

Таблица П4.6. Датчик-сканер  TCustomScannerSensor

 

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

Описание                 .

property SensorType: TScannerSensorType; TScannerSensorType = (RFID, Barcode);

Тип датчика

property RFIDТag: Uint64;

40-битное значение RFID-метки, используемой для радиочастотной идентификации

property BarcodeData:  string;

Отсканированные данные штрихкода


 

 

 

 

 

 

 


ПРИЛОЖЕНИЕ  5

 

Описание электронного архива

 

 

По ссылке ftp://ftp.bhv.ru/9785977532891.zip можно скачать электронный архив с рассмотренными проектами и исходными кодами примеров. Эта ссылка доступна со страницы книги на сайте www.bhv.ru.

Архив следует распаковать в папку c:\examples.

Папка c:\examples содержит каталоги с исходными кодами примеров, названия вложенных папок построены по следующему принципу:

<сh><номер главы>_<номер примера в главе>

Например:

ch0l_0l -    глава 1 пример 1;

ch01_02 -    глава 1 пример 2;

 

сhЗО_Оl-главаЗОпример 1.

Кроме того, в папке ch00 вы найдете общие примеры для всего материала книtи.

 

 

 

 

 

 

 

 

 



 

 

 

 

 

 


Список литературы

 

 

1.    Online Help for DelphiФ XES and C++BuilderФ ХЕ5. [Электронный ресурс] / URL: http://docwiki.embarcadero.com/RADStudio/XE5/en/Мain_Page (дата обращения: 05.11.2013).

2.    RAD Studio IOS Tutorials: Delphi IOS Application Development. Version ХЕ4. - Embarcadero. Product Documentation, April, 2013. -                        142 р.

3.    Иванов В. П., Батраков А. С. Трехмерная компьютерная графика/ Под ред. Г. М. Полищука. -М.: Радио и связь, 1995. -224 с: ил.

4.    MИJUiep Т. DirectX 9 с управляемым кодом. Программирование игр и графи­ ка. -М.: Издательский дом "КомБук", 2005. -                400 с.

5.    Осипов Д. Л. Delphi ХЕ2. -СПб.: БХВ-Петербург, 2012. -912 с.: ил.

6.    ОсипQв Д. Л. Базы данных и Delphi. Теория и практика. -        СПб.: БХВ­ Петербург, 2011. -                      752 с.: ил.

7.    Хилл Ф. OpenGL. Программирование компьютерной графики. Для профессио­ налов. - СПб.: Питер, 2002. -                             1088 с.: ил.

8.    Шикин А. В., Боресков А. В. Компьютерная графика. Полигональные модели. - М.: Диалог-МИФИ, 2001. -                      464 с.


 

 

 

 

 

 

 

 

 

 

 

Предметный указатель

 

 

ТAffineTransformEffect:

    Center 258

IFMXNotificationCenter:                                                    · Rotation 258

    CancelAllNotifications 363                                              Scale 258

   Cance!Notification 363                                                     ТAlphaTrackВar:

   PresentNotification 363                                                    Мах 218

   ResetlconBadgeNumber 363                                            Min218

   ScheduleNotification 363                                                 OnChange 218

   SetlconВadgeNumber 363                                                Value 218

IFMXPhoneDialerService:                                                      TApplication:

    Call 368                                                                             DefaultTitle 90

    GetCarrier 367                                                                   FormFactor 94

    GetCuпentCalls 368                                                         LastKeyPress 94

   LastUserActive 94

L                                                      MainForm 89

   OnActionUpdate 93

LiveBindings 61, 303                                                            OnException 91

   Onldle 92

т                                      Title 89

ТАпау<>:

TAction:                                                                                   BinarySearch 48

    ActionList 131                                                                    Sort 48

   Category 131                                                                     ТAudioCaptureDevice:

   Execute 130                                                                       Description 277

   Index 131                                                                           IsDefault 277

   OnExecute 130                                                                  MediaType 277

   OnUpdate 130                                                                   Name 277

ТActionList:                                                                            StartCapture 277

    ActionCount 133                                                               State 277

    Actions 133                                                                       StopCapture 277

    ExecuteAction 133                                                            UniqueID 277

   OnChange 134                                                                  ТAudioCaptureDevice:

   OnExecute 130, 134                                                         FilterString 277

   OnStateChange 134                                                          FileName 277

   OnUpdate 134                                                                   TBandedSwirlEffect:

   State 134                                                                            AspectRatio 251


 


TBandedSwir\Effect (прод.):

     Bands 250

     Center 250

     Strength 250 TBandedSwir!TransitionEffect:

     Center 261

     Frequency 262

     Progress 261

     Strength 262

     Target 261 TBandsEffect:

     BandDensity 249

     Bandlntensity 249 TBindExpression:

     Active 314

     Contro!Component 313 ·

     Contro\Expression 313

     Direction 313

     Managed 315

     OnActivated 315

     OnActivating 315

     OnAssignedValueEvent 315

     OnAssigningValueEvent 315

     OnEva\ErrorEvent 315

     SourceComponent 313

     SourceExpression 313 TBindings:

     CreateAssociationScope 318

     CreateExpression 317

     CreateManagedBinding 318

     CreateUnmanagedBinding 318 TBindList:

     AutoFill 322

     Contro!Component 322

     FillList 322

     FormatExpressions 322

     SourceComponent 322 TBitmap:

     BitmapScale 237

     CanvasClass 237

     Clear 238

     ClearRect 238

     CopyFromBitmap 238

     Create 235

     CreateFromBitmapAndMask 235

     CreateFromFile 235

     CreateFromStream 235

     CreateThumbnail 237

     FlipHorizontal 237


     FlipVertical 237

     Handle 237

     Height 237

     InvertAlpha 238

     IsEmpty 236

     LoadFromFile 235

     LoadFromStream 235

     LoadThumbnailFromFile 237

     Мар 238

     PixelFormat 237

     ReplaceOpaqueColor 238

     Resize 237

     Rotate 237

     SaveToFile 235

     SaveToStream 235

     Unmap 238

     Width 237 TBitmapAnimation:

     Delay 265

     Duration 265

     Interpolation 266

     Inverse 267

     Loop 266

     OnFinish 266

     OnProcess 266

     Pause 266

     Running 266

     Start 266

     StartValue 268

     StopValue 268 TBitmapCodecManager:

     GetFilesTypes 236

     GetFilterString 236

     IsCodedExists 236 TBitmapListAnimation:

     AnimationBitmap 268

     AnimationCount 269

     Delay 265

     Duration 265

     lnterpolation 266                                                          

     Inverse 267

     Loop 266

     OnFinish 266

     OnProcess 266

     Pause 266

     Running 266

     Start 266

     Stop 266



TBloomEffect:                                                                         DrawPolygon 230

    Baselntensity 247                                                             DrawRect 229

    BaseSaturation 247                                                           DrawRectSides 229

   Bloomlntensity 247                                                          DrawThumbnail 234

   BloomSaturation 247                                                        EndScene 223

TB\urEffect, Softness 248                                                     ExcludeClipRect 234

TBoxB\urEffect, BlurAmount 248                                       Fil\ 224

TBrush:                                                                                   Fil\Arc 228

   Bitmap 225                                                                        Fil\Ellipse 228

   Color225                                                                            Fil\Path 228

   Gradient 225                                                                      FillPolygon 228

   Kind 225                                                                            Fil\Rect 228

   OnChanged 225                                                                Fil\Text 232

   OnGradientChanged 225                                                  Font 224

    Resource 225                                                                     Height 224

TButton, Action 129                                                              lntersectC\ipRect 234

ТВWTrackВar:                                                                        LoadFontFromStream 232

    Мах 218                                                                            RestoreState 234

    Min 218                                                                             SaveState 234

    OnChange 218                                                                  Stroke 224

   Value 218                                                                          TextHeight 232

TCalendar:                                                                            TextToPath 232

   Date 210                                                                            TextWidth 232

    FirstDayOfWeek 210                                                         Width 224

   OnChange 210                                                                  TCaptureDeviceManager:

   OnDateSe\ected 21О                                                          Count 275

TCalendarEdit:                                                                         Current 275

   Date 210                                                                            DefaultAudioCaptureDevice 276

    FirstDayOfWeek 210                                                        DefaultVideoCaptureDevice 276

   OnChange 21О                                                                 Devices 275

    OnClosePopup 211                                                           GetDevicesByName 276

   OnDateSelected 21О                                                        TCarrier:

   OnPopup 211                                                                     GetCarrierName 368

TCamera, Target 383                                                             GetlsoCountryCode 367

TCameraComponent:                                                               GetMoЬileCountryCode 367

    Active 280                                                                         GetMoЬileNetwork 368

    Kind 280                                                                            OnCarrierChanged 368

TCanvas:                                                                                 TCircleTransitionEffect:

    BeginScene 223                                                                Center 261

    BufferBits 224                                                                   FuzzyAmount 262

   Buffered 224                                                                      Progress 261

   BufferHandle 224                                                              Size 262

    Clear 224                                                                           Target 261

   ClearRect 224                                                                    TColorAnimation:

   DrawArc 229                                                                     Delay 265

   DrawBitrnap 233                                                               Duration 265

    DrawEl\ipse 229                                                               Interpolation 266

    DrawLine 229                                                                   Inverse 267

    DrawPath 229                                                                    Loop 266


 

TColorAnimation (прод.):                                                   TComboTrackВar:

     Onfinish 266                                                                     Decima!Digits 156

     OnProcess 266                                                                  Мах 156

     Pause 266                                                                           Min 156

     Running 266                                                                      OnChange 157

     Start 266                                                                             OnChangeTracking 157

     StartValue 268                                                                   TrackВar 157

     Stop 266                                                                             Value 156

     StopValue 268                                                                   ValueType 156

TColorBox, Color 217                                                           TCommonCustomform, ObjectAtPoint 106

TColorComboBox:                                                                  TCommonDialog:

     Color 216                                                                           OnClose 197

     OnChange 216                                                                   OnShow 197

TColorKeyAlphaEffect:                                                          TComponent:

     ColorKey 247                                                                    ComponentCount 39

     Tolerance 247                                                                    Componentlndex 39

TColorListВox:                                                                       Components 39


     Color 216

     OnChange 216


     Create 39


TColorMaterialSource, Color 399                                        DestroyComponents 39

TColorPanel:                                                                           FindComponent 40

     Color 216                                                                           InsertComponent 39

     OnChange 216                                                                  Name38

     UseAlpha 216                                                                     Owner39

TColorPicker:                                                                           RemoveComponent 39

     Color 217                                                                           Tag38

     ColorQuad 217                                                                 TContext3D:

     Hue 216                                                                             AddLight 414

TColorQuad:                                                                           BeginScene 407

     ColorBox 217                                                                    CurrentCameralnvMatrix 416

     Hue 217                                                                             CurrentCameraМatrix 415

     Lum 217                                                                            CurrentPixelShader 417

     OnChange 217                                                                  CurrentProjectionMatrix 416

     Sat 217                                                                               CurrentVertexShader 417

TColumn, Header 187                                                           DeleteLight 414

TComboBox:                                                                           DrawCube 409

     Clear 169                                                                           DrawLiqe 408

     Count 169                                                                          DrawLines 411

     DropDown 171                                                                  DrawPoints 411

     DropDownCount 170                                                        DrawRect 409

     Itemlndex 169                                                                   DrawTriangles 411


     Items 169

     Listltems 169

     OnChange 171

     Placement 170


     EndScene 407

     FillCube 409

     FillPolygon 410


     Selected 169                                                                      Lights 413

TComboColorBox:                                                                   SetMatrix_414

     Color 216                                                                           VertexShader 417

     OnChange 216                                                                  TContextНelper:

     UseAlpha 216                                                                    DrawCube 409

TComboEdit, Items 172                                                        DrawLine 408


 

    DrawRect 409                                                                    TCropEffect:

   FillCube 409                                                                      LeftTop 258

   FillPolygon 410                                                                 RightBottom 259

TControl:                                                                                 TCustomBiometricSensor:

   Align 64                                                                             BarcodeData 443

   DragMode 79                                                                    HumanPresense 443

   Height 63                                                                           HumanProximity 443

   IsDragOver 70                                                                   Touch 443.

   IsFocused 70                                                                     TCustomEdit:

   IsMouseOver 70                                                                ContentRect 151

   IsVisiЫe 70                                                                         CopyToClipboard 150

   MakeScreenshot82                                                             CutТoClipboard 150

   Margins 64                                                                         FilterChar 148

    OnCanFocus 77                                                                FontColor 151

    OnClick 70                                                                        GetCharХ 151

    OnDЫClick 70                                                                    KeyboardType 146

    OnDragDrop 81                                                                OnChangeTracking 147

   OnDragEnd 82                                                                  OnTyping 150

   OnDragEnter 81                                                                Password 148

   OnDragLeave 81                                                               PasteFromClipboard 150

   OnDragOver 80                                                                 ReadOnly 148

   OnEnter 77                                                                        SelectAll 149

   OnExit 77                                                                          SelectionFill 151

   OnGesture 283                                                                  SelectWord 149

   OnKeyDown 73                                                                SelLength 149

   OnKeyUp 73                                                                     SelStart 149

   OnMouseDown 75                                                            SelText 148

    OnMouseEnter 75                                                             Text 147

    OnMouseLeave 75                                                            Text MaxLength 148

    OnMouseMove 75                                                            Typing 150

    OnMouseUp 75                                                                TCustomLightSensor:

    OnMouseWheel 77                                                           BarcodeData 444

    OnPaint 82                                                                         Chromacity 444

    OnPainting 82                                                                    Lux 444

   OnResize 77                                                                      Temperature 444

   Opacity 68                                                                         'rCustomMechanicalSensor:

   Padding 64                                                                         AbsPressure 443

    PaintТo 82                                                                         BarcodeData 443

   PopupMenu 124                                                                Force 443

   Position 63                                                                         GaugePressure 443

   RotationAngle 66                                                              Strain 443

   RotationCenter 66                                                               SwitchAпayState 443

   Scale 66                                                                             SwitchState 443

   Skew 66                                                                             Weight443

   VisiЫe 68                                                                          TCustomMesh:

    Width 63                                                                            Data 387

TCriticalSection:                                                                      IndexВuffer 387

   Enter 336                                                                           Normals 387

   Leave 336                                                                           Points 387


 


TCustomMesh (прод.):

     Trianglelndices 387

     VertexBuffer 387 TCustomMotionSensor:

     AccelerationX 442

     Acceleration У 442

     AccelerationZ 442

     AngleAccelX 442

     AngleAccelY 442

     AngleAccelZ 442

     Motion 442

     SensorType 442

     Speed 442

     Updatelnterval 442 TCustomScannerSensor:

     BarcodeData 444

     RFIDTag 444

     SensorType 444 TDictionary:

     ContainsKey 54

     ContainsValue 54

     ToArray 55

     TryGetYalue 54 TDictionary<>:

     Add 54

     AddOrSetValue 54

     Clear 54

     Create 53

     ExtractPair 54

     Items 54

     Remove 54 TDirectionalВlurEffect:

     Angle 248

     BlurAmount 248

     Center 248 TDirectory:

     Сору 434

     CreateDirectory 434

     Delete 138, 434

     Exists 137,434

     GetAttributes 140, 434·

     GetCreationTime 434

О GetCurrentDir 138

     GetCurrentDirectory 434

     GetDirectories 435

     GetDirectoryRoot 436

     GetFiles 436

     GetFileSystemEntries 436

     GetLastAccessTime 435

     GetLastWriteTime 435


     GetLogicalDrives 135,434

     GetParent 435

     lsEmpty 436

     IsRelativePath 436

     Move436

     SetAttributes 140,435

     SetCreationTime 435

     SetCurrentDir 138

     SetCurrentDirectory 434

     SetLastAccessTime 435

     SetLastWriteTime 435 TEdit:

     CopyToClipboard 150

     CutТoClipboard 150

     KeyboardType 146

     OnChange 147

     PasteFromClipboard 150

     SelectAll 149

     SelectWord 149

     SelLength 149

     SelStart 149

     SelText 148

     Text 147 TElectricalSensorType:

     Capacitance 443

     Current 443

     Inductance 443

     Power 443

     Resistance 443

     SensorType 442

     Voltage 443 TEmbossEffect:

     Amount255

     Width 256 TEvent:

     Create 333

     LastError 334

     ResetEvent 334

     SetEvent 334

     WaitFor 334 TFadeTransitionEffect:

     Progress 261

     Target 261 TFile:

     AppendAIIText 437

     Сору 437

     Create 139,437

     CreateText 437

     Decrypt 437

     Delete 437


 



     Encrypt 437                                                                       Release 56

     Exists 137, 437                                                                  RemoveFreeNotify 57

     GetAttributes 140,437                                                       RemoveObject 59

     GetCreationTime 437                                                       Sort 59

     GetCreationTimeUtc 437                                                 Touch 283

     GetLastAccessTime 437                                                   TFont:

     GetLastWriteTime 437                                                     Family 227

     GetSymLinkTarget 438                                                    Size 227

     Move438                                                                            Style 227

     Open 140, 438                                                                   TForm:

     OpenRead 438                                                                  Caption 102

     OpenText 438                                                                   Close 101, 104

     ReadAllBytes 438                                                             Create 99

     ReadAllLines 438                                                             FormFactor 106

     Replace 438                                                                       FormFamily 106

     SetAttributes 140                                                              FullScreen 100

     SetCreationTime 438                                                        Hide 101

     SetLastAccessТime 439                                                   Moda!Result 1О1

     SetLastWriteTime 439                                                      ObjectAtPoint 106

     WriteAllLines 439                                                            OnActivate 103

     WriteAl!Тext 439                                                                OnClose 104, 105

TFillEffect, Color 246                                                           OnCloseQuery 104             ,

TFillRGBEffect, Color 246                                                   OnDeactivate 104

TFloatAnimation:                                                                    OnDestroy 104, 105

     Delay 265                                                                          Position 102

     Duration 265                                                                      Quality 107

     Interpolation 266                                                               Release 102

     Inverse 267                                                                         Show99

     Loop 266                                                                           ShowFullScreenlcon 100

     OnProcess 266                                                                  ShowModal 100

     Pause 266                                                                          StyleBook 114

     Running 266                                                                      Touch 283

     Start 266                                                                             WindowState 103

     StartValue 269                                                                  TForm3D:

     Stop 266                                                                             Camera 383

     StopValue 269                                                                    Color 110

TFmxObject:                                                                           Context 109

     Action 61                                                                           Multisample 110 ·

     AddObject 58                                                                    OnRender 109

     Children 57                                                                        UsingDesignCamera 110,383

     ChildrenCount 57                                                              TFormFactor:

     Clone 56                                                                            Devices 94

     DeleteChildren 60                                                             Height 94

     Exchange 59                                                                      Orientations 94

     FreeNotification 57                                                           Width 94

     HasParent 58                                                                     TGaussianBlurEffect, BlurAmount 248

     Index 58                                                                             TGeocoder:

     InsertObject 58                                                                 Coord 353

     IsChild 58                                                                          Current 355

     Parent 58                                                                            Geocode 353


 

TGeocoder (прод.):                                                              ТНueTrackВar:

    GeocodeReverse 353                                                        Мах 218

    Geocoding 356                                                                  Min 218

    OnGeocode 354                                                                OnChange 218

    OnGeocodeReverse 354                                                   Value 218

TGloomEffect:                                                                         ТlnnerGlowEffect:

    Baselntensity 247                                                              GlowColor 255

    BaseSaturation 247                                                           Softness 255

    Gloomlntensity 247                                                          TLabel:

    GloomSaturation 247                                                        AutoSize 144

TGlowEffect:                                                                           Font 143

    GlowColor 255                                                                 FontColor 144

   Softness 255                                                                      StyledSettings 144

TGradient:                                                                               Text 143

   Color 219                                                                           TextAlign 144

   InterpolateColor 219                                                         TextSettings 144

   Points 219                                                                          VertTextAlign 144

   Radia!Тransform 219                                                        WordWrap 144

   StartPosition 219                                                               TLang:

   StopPosition 219                                                               AddLang 344

   Style 219                                                                            FileName 344

TGradientAnimation:                                                               Lang 343

   Delay 265                                                                          LangStr 344

   Duration 265                                                                      LoadFromFile 344

    Interpolation 266                                                              Original 344

    Inverse 267                                                                        Resources 344

   Loop 266                                                                           SaveToFile 344

    OnFinish 266                                                                     StorelnForm 344

    OnProcess 266                                                                  TLight:

    Pause 266                                                                          Color 381

    Running266                                                                       LightType 380

    Start 266                                                                            SpotCutOff381

    StartValue 268                                                                  SpotExponent 381

   Stop 266                                                                            TListBox, MultiSelect 165

   StopValue 268                                                                  TListBoxltem:

TGradientEdit:                                                                         AddObject 162

   CuпentPoint 220                                                               Clear 162

   Gradient 219                                                                      ClearSelection 166

·о OnChange 220                                                                   Columns 168

   OnSelectPoint 219                                                             Data 159

TGrid:                                                                                      Index 160

   Cel!ControlByRow. 187                                                     InsertObject 162

   ColumnByPoint 187                                            IsChecked 160

   ColumnCount 187                                                             IsSelected 160

I         Columns 187                                                                     ItemBylndex 165

    OnEdititingDone 188                                                       ItemByPoint 165

    OnGetValue 188                                                               ItemData 160

    OnSetValue 187                                                                ItemHeight 168

   RowCount 187                                                                  Itemlndex 164

THueAdjustEffect, Hue 247                                                   Items 161

 

 



 


   ItemWidth 168

   Listltems 165

   ListStyle 168

   OnChange 169

   OnChangeCheck 169

   OnCompare 167, 169

    RemoveObject 162

    SelectAll 166

    Selected 164

    Sorted 166

    Text 159 TListBoxltemData:

   Accessory 160

   Bitmap 160

   Detail 160

   Text 160 TLocationSensor:

    Active 350

   OnLocationChanged 351 TMagnifierGlass:

   BackgroundColor 361

    LoupeMode 361

    LoupeScale 361

    ZoomRegionCenter 361 TMagnifyEffect:

    AspectRatio 252

    Center 251

    Magnification 251

    Radius 251 TMagnifyTransitionEffect:

   Center 261

    Progress 261

   Target 261 TMatrix3D:

   CreateRotation 430

   CreateRotationX 430

   CreateRotationУ 430

   CreateRotationYawPitchRoll 431

   CreateRotationZ 430

   CreateScaling 431·

   CreateTranslateMatrix3D 430

   Determinant 433

    Inverse 433

   Scale 431

   Transpose 433 TMediaPlayer:

    Clear 274

   CurrentTime 274

    Duration 274


    FileName 273

    Play 274

    State 274

    Stop 274

    VideoSize 274

    Volume 274

TMediaPlayerControl, TMediaPlayer 274 TMemo:

   ClearSelection 154

   CopyToClipboard 154

   CutToClipboard 154

   GoToLineBegin 156

   GotoLineEnd 156

   GoToTextBegin 155

   GoToTextEnd 155

   Lines 153

   PasteFromClipboard 154

    SelectAll 154

    SelLength 154

    SelStart 154

    SelТext 154

   Text 153 TMenultem:

   Action 129

   AutoCheck 122

   Grouplndex 122

   IsChecked 121

   OnClick 121

   Radioltem 123

   ShortCut 121

   Text 120

TMesh, Data 387

TMeshData, CalcNormals 388 TModelЗD:

   MeshCollaction 395

   ReadModel 396 TMutex:

    Acquire 338

   Create 337

   Release 338

ТNormalBlendEffect, Target 261 ТNumberBox:

I     DecimalDigits 156

   Horzlncrement 157

   Мах 156

   Min 156

   Value 156

   ValueType 156

   Vertlncrement 157



 

TObject:                                                                                   DistanceY 357

   ClassName 35                                                                   DistanceZ 357

   ClassNameis 35                                                                HeadingX 357

   ClassParent 35                                                                   HeadingY 357

   ClassType 35                                                                     HeadingZ 357

   Create 32                                                                            MagHeading 357

   Destroy 32                                                                         OnDataChanged 358

   Disposed 35                                                                       OnSensorChoosing 357

   DisposeOf35                                                                      SensorType 356

   FieldAddress 36                                                                Start 356

   Free 33                                                                               Started 356

    InheritsFrom 35                                                                Stop 356

    MethodAddress 36                                                            TiltX 357

    MethodName 36                                                               TiltY 357

    RefCount 35                                                                      TiltZ 357

    UnitName 35                                                                     TrueHeading 357

TObjectList<>:                                                                        Updatelnterval 357

    Add 51                                                                               TPageSetupDialog:

    AddRange 51                                                                    Execute 197

    Count 51                                                                            MarginLeft 202

   Create 49                                                                            MinМarginBottom 202

   Delete 52                                                                            MinМarginLeft 202

   Exchange 53                                                                      MinМarginRight 202

   Extract 53                                                                          MinMarginTop 202

   Insert 51                                                                             PageHeight 202

   InsertRange 51                                                                  PageWidth 202

    Items 51                                                                             ТPaperSketchEffect, BrushSize 256

    Move 53                                                                            TPath:

    OnNotify 53                                                                      AltDirectorySeparatorChar 441

    OwnsObjects 50                                                                ChangeExtension 440

    Sort 53                                                                               Comblne 440

TOpenDialog:                                                                          DirectorySeparatorChar 441

    DefaultExt 198                                                                  DriveExists 440

   Execute 197, 200                                                              ExtensionSeparatorChar 441

   FileName 200                                                                    GetAttributes 140,441

   Filter 198                                                                            GetDirectoryName 440

   Filterlndex 198                                                                   GetDocumentsPath 441

    Initia\Dir 199                                                                     GetExtendedPrefix 439

   OnCanClose 201                                                               GetExtension 440

   OnFolderChange 201                                                        GetFileName 440

   OnSelectionChange 201                                                   GetFileNameWithoutExtension 440

   OnTypeChange 201                                           GetFullPath 440

    Options 199                                                                       GetGUIDFileName 440

   Title 199                                                                            GetНomePath 137,441

TOrientationSensor:                                                                 GetlnvalidFileNameChars 440

    AvailaЬ\eProperties 357                                                    Getlnva\idPathChars 440

    CompMagHeading 357                                                    GetLibraryPath 441

   CompTrueHeading 357                                                    GetPathRoot 441

   DistanceX 357                                                                   GetPicturesPath 441


 


   GetRandomFileName 441

   GetSharedDocumentsPatb 441

   GetSharedPicturesPath 441

   GetТempFileName 441

   GetТempPath 137,441

   HasExtension 441

   HasValidFileNameCbars 439

    HasValidPatbCbars 439

    IsDriveRooted 440

    IsExtendedPrefixed 440

    IsPathRooted 441

    IsRelativePath 440

   IsUNCPath 440

   IsUNCRooted 440

   IsValidFileNameCbar 439

   IsValidPathCbar 439

   MatchesPattem 440

   PathSeparator 441

   SetAttributes 140, 441

    VolumeSeparatorChar 441 TPathAnimation:

    Delay 265

    Duration 265

    Interpolation 266

    Inverse 267

    Loop 266

    OnFinish 266

    OnProcess 266

    Path 269

    Pause 266

    Rotate 269

    Running 266

    Start 266

   Stop 266 TPathData:

   Clear 230

   Count230

   IsEmpty 230

   Points 230 TPinchEffect:

   AspectRatio 253

   Center 252

   Radius 252

   Strength 252 TPixelateEffect:

    BlockCount 257

    BrushSize 257 TPlatforrnServices:

   Current 364


   GetPlatforrnService 364

   SupportsPlatforrnService 364 TPosition:

   DefaultValue 64

   Empty 64

   OnChange 64

   Point 64

   Vector 64

   Х63, 64

    У 63, 64

TPrintDialog:

· Collate 205

    Copies 205

    Execute 197

    FromPage 204

    MaxPage 204

    MinPage 204

    Options 204

    PrintRange 204

   PrintToFile 205

   ToPage 204

TPrinterSetupDialog, Execute 197 TPrototypeBindSource:

   FieldDefs 305

   RecordCount 305

TProxyObject, SourceObject 383

TRadia!BlurEffect, BlurAmount 248 TRectAnimation:

   Delay 265

   Duration 265

    Interpolation 266

   lnverse 267

    Loop 266

    OnFinish 266

    OnProcess 266

    Pause 266

    Running 266

    Start 266

   StartValue 269

   Stop 266

   StopValue 269

ТReflectionEffect, Length 254 TrippleEffect:

   Amplitude 253

   AspectRatio 253

   Center 253

   Frequency 253

   Phase 253



 

 


TSaveDialog:

    DefaultExt 198

    Execute 197, 200

    FileName 200

    Filter 198

    InitialDir 199

    OnCanClose 201

   OnFolderChange 201

   OnSelectionChange 201

   OnTypeChange 201

   Options 199

   Title 199 TSemaphore:

   Acquire 339

   Create 339

   Release 339 TSensorManager:

   Activate 359

   Active 359

   CanActivate 359

   Count 359

   Cuпent 359

   GetSensorsByCategory 359

    GetSensorsByFilter 359

    Sensors 359

TSepiaEffect, Amount 257 TShadowEffect:

    Amount 255

    Direction 255

    Distance 255

    ShadowColor 255

    Softness 255 TShape3D:

    Amblent 402

    Diffuse 402

    Emissive 402

   Shininess 404

   Specular 402

TSharpenEffect, Amount 258 TSmoothMagnifyEffect:

   AspectRatio 252

   Center 251

   InnerRadius 252

   Magnification 251

   OuterRadius 252 TSpinBox:

    DecimalDigits 156

    Horzlncrement 157

    Мах 156

    Min 156


    Value 156

    ValueType 156

    Vertlncrement 157 TSQLConnection:

    AfterConnect 290

   AfterDisconnect 290

   BeforeConnect 290

   BeforeDisconnect 290

   Close 288

   CloseDataSets 291

   Commit 292

   Connected 289

   ConnectionState 291

   DataSetCount 291

   DataSets 291

   DriverName 288

   Execute 292

   ExecuteDirect 292

   GetFieldNames 293

   GetlndexNшnes 293

   GetProcedureNшnes 293

   GetProcedureParams 293

   GetТaЬleNшnes 293

   InTransaction 292

    KeepConnection 290

    MultipleTransactionsSupported 291

   OnLogin 290.

   Open 288

   Parшns 288

   Rollback 292

    StartTransaction 292

    ТаЫеSсоре 293

    TransactionsSupported 291 TSQLDataSet:

    CommandText 294

    CommandType 294

   ExecSQL 296

   ParamCheck 299

   Parшns 299

   ParseDeleteSql 296

   ParselnsertSql 296

   ParseSelectSql 296

   ParseUpdateSql 296

   SQLConnection 294 TSQLQuery:

   ParшnCheck 299

   Parшns 299

   SQL 299

    SQLConnection 294


 

 

 

 



 


TSQLStoredProc:

   ExecProc 297

О ParamCheck 297

О Params 297

   PrepareStatement 297

   SQLConnection 294, 296

    StoredPro ame 297 TSQLТаЫе, SQLConnection 294 TStringGrid:

    CelJControlByRow 187

    Cells 188

    ColumnByPoint 187

   ColumnCount 187

   Columns 187

   OnEdititingDone 188

О OnGetValue 188

О OnSetValue 187

О RowCount 187 TStrokeBrush:

   Сар 226

О Dash 226

   Join 226

   SetCustomDash 226

О Thickness 226 TStyleBook:

    FileName 115

    Resource 114 TSwirlEffect:

   AspectRatio 251

   Center 250

О SpiralStrength 250 ТТextControl:

О Font 143

   FontColor 144

О Text 143

О TextAlign 144

О VertTextAlign 144

   WordWrap 144 TTexture:

О BytesPerPixel 405

О Height 405

О IsEmpty 404

   MagFilter 405

О MinFilter 405

    PixelFormat 405

О Style 404

О Width 405

ТТextureMaterialSource, Texture 400 TThread:

   Create 325


    FatalException 326

   FreeOnTerminate 326

   IsSingleProcessor 327

   OnTerminate 326

   Priority 329

   ProcessorCount 327

   Queue 330

   Resume 325

О RetumValue 327

   SetRetumValue 327

   Sleep 325

   Start 325

О Suspend 325

   Synchronize 330

    Terminate 326

    Terminated 326

    WaitFor 328 TTilerEffect:

    Horizonta!Offset 260

    HorizontalTileCount 260

    VerticalOffset 260

    VerticalТileCount 260 ТТimer:

О EnaЫed 208

О Interval 208

   OnTimer 208 TTimeSpan:

   Create 207

О Days 207

О Hours 207

   Milliseconds 207

О Minutes 207

   Seconds 207

   Ticks 207

ТТoonEffect, Levels 258

ТТouchManager, GestureManager 283

ТТransform, RotationCenter 219 TTreeView:

   AddObject 180

   Clear 181

   CollapseAII 183

   Count 180

   CountExpanded 184

    ExpandAII 183

   GlobalCount 180

    HideSelectionUnfocused 180

О  ItemByGloballndex 180

   ItemBylndex 180

   ItemByPoint 180

   ItemHeight 179


 


 


TTreeView (прод.):                                                              Reflect 427

   Items 180                                                                           Rotate 426

    MultiSelect 179                                                                 TVideoCaptureDevice:

   OnChange 180                                                                  Description 277

    OnChangeCheck 183                                                        IsDefault 277

    OnCompare 184                                                                MediaType 277

    RemoveObject 180                                                            Name277

    Selected 179                                                                      StartCapture 277

    ShowCheckboxes 183                                                        State 277

    Sorted 184                                                                         StopCapture 277

TTreeViewltem:                                                                       UniqueID 277

    AddObject 178                                                                  TVideoCaptureDevice:

    Count 176                                                                          FlashMode 278

    Globallndex 178                                                                FocusMode 278

    Index 178                                                                           HasFlash 277

    IsChecked 179                                                                   HasTorch 278

    IsExpanded 179                                                                 OnSampleBufferReady 278

    IsSelected 179                                                                   Position 277

   ItemBylndex 176                                                              Quality 278

   ItemByPoint 177                                                               SampleBufferToBitmap 278

   Items 176                                                                           TorchMode 278

   Parentltem 177                                                                  TWaterTransitionEffect:

   RemoveObject 178                                                           Progress 261

   Text 176                                                                             Target 261

   TreeView 178                                                                    TWebBrowser:

TVector3D:                                                                             CanGoBack 350

   Add 421                                                                             CanGoForward 350

  

   Create 420

 

   Navigate 349

 
AngleCosine 425                                                               GoBack 350

    OnDidFinishLoad 349


   CrossProduct 424

    Distance 422

    DotProduct 425


   OnDidStartLoad 349

   URL349


   

w

 
GetNormalize 421

    Length 420

    MidVector 422                                                                  Windows API, MoveFile 139

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 



 


А

Анимация 263

Аудиопоток, захват 275, 277

 

Б

База данных 287

    регистрация пользователя 290 О соединение 288

Буфер:

    вершинный 387

    индексный 387

Буфер обмена 150

в

Вектор 372,419

Взаимная блокировка 330

Видеопоток, захват 275, 277

Видимость 68

Визуальный дизайнер 305

Вращение объекта 66

г

Геолокация 350

Градиентная заливка 219 Графика:

О двухмерная 222

    растровая 235

О трехмерная 369

Графический примитив 229

Графический эффект 240

д

Дата и время 206

О интервал времени 207 Датчик 442

О  местоположения 350

О  ориентирования 356

Дерево 175 О узел176

Диалоги и сообщения:

О InputBox 196

О InputQuery 196

   MessageDlg 194

   MessageDlgPos 196

О  MessageDlgPosHelp 196


О ShowMessage 192

О  ShowMessageFmt 193

О ShowMessagePos 193 О настройка печати 203 Диск 135

ж

Жест 281

О интерактивный 285

з

Заливка:

   замкнутой области 228

О трехмерного объекта 399 Запрос 299

Звонок по телефону 367 Значок приложения 89

и

Изображение:

    загрузка 235

О миниатюра 236

О свойства 237

Источник света 380

к

Календарь 209

Камера 280, 376, 381

Каталог 136

О дата/время создания 141

   проверка существования 137

   создание 138

Кисть 224 Класс:

О TAction 127

о Т Animation 265

о TComponent 38

о TMainMenu 124

. TMenultem 120

о TObject 30

о TPersistent 37, 38

о TTextControl 143

Колонка 185 Команда:

О Т Action 127

О выполнение 130



 


Компоненг, TPrinterSetupDialog 203

Конгекст трехмерный 406, 408

Координаты, преобразование 352

Критическая секция 336

л

Линия 226

м

Макет 384

Масштабирование объекта 66

Матричные преобразования 414,428

Менеджер датчиков 358

Меню 118

    главное 124

    группировка элеменгов 122

    конrекстное 124

    планка 124

    элеменr II виде флажка 121 Метка 144

Метод класса 35

Модель трехмерная 394

    импорт 397 Модуль:

   IOUtils 135

   System.IOUtils 434

   System.Sensors 442

Мультимедиа 271

Мультиязычность 341

Мьютекс 337

н

Название приложения 89

Нормаль 374,393

о

Освещение 413

Отсечение области 234

п

Пиксел 371

Подсистема уведомлений 361

Поле текстовое 146, 152

Поток 323

   приоритет 329


Приложение:

   для мобильной платформы 347

    многопоточное 323

Проекция 375

Прозрачность 68

Проигрыватель 273

 

р

Редактор многострочный 153

Рисунок 233

с

Свет отраженный 402

Семафор 338

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

Сетка 185, 187

    строк 188

Система координат 369 Событие:

    OnActionUpdate 93

    OnActivate 103

   OnActivated 315

   OnActivating 315

   OnAssignedValueEvent 315

   OnAssigningValueEvent 315

   OnCanClose 201

   OnCanFocus 77

   OnChange 134, 169, 171

   OnChangeCheck 169

   OnChangeTracking 147

   OnClick 70, 121.

   OnClose 104, 105, 197

   OnCloseQuery 104

    OnCompare 167, 169, 184

   OnDЫClick 70

    OnDeactivate 104

   OnDestroy 104, 105

   OnDidFinishLoad 349

   OnDidStartLoad 349

   OnDragDrop 81

   OnDragEnd 82

   OnDragEnter 81

   OnDragLeave 81

   OnDragOver 80

   OnEnter 77

   OnEvalErrorEvent 315

    OnExecute 130, 134


 


   OnExit 77

   OnFolderChange 201

    OnGeocode 354

    OnGeocodeReverse 354

    OnGesture 283

    OnKeyDown 73

    OnKeyUp 73

    OnLocationChanged 351

    OnМouseDown 75

    OnMouseEnter 75

    OnMouseLeave 75

    OnMouseMove 75

   OnMouseUp 75

   OnMouseWheel 77

   OnPaint 82

   OnPainting 82

   OnResize 77

   OnSampleBufferReady 278

   OnSelectionChange 201

   OnShow 197

   OnStateChange 134

   OnTenninate 326

   OnTimer 208

   OnTypeChange 201

    OnUpdate 130, 134

    обработка 70

Список 159

    выбор нескольких элементов 165

   выбора 161

   доступ к элементу:

а        выделенному 164

а        произвольному 165

   команд 133

   комбинированный:

а        нередактируемый 169

а        редактируемый 172

   компонент выбора значений 174

   оформление 168

   перестановка элементов 166

   события 169

   сортировка элементов 166

   текстовый поиск 167 Сцена 378

 

т

Таймер 208

Тексел 399


Текст:

   вывод на форму 232

   редактирование 154

Текстура 416

Текстурирование 399, 404

Тетраэдр 389

Точка 372

Траектория 230

у

Увеличительное стекло 360

 

ф

Файл:

   атрибуты 140

   дата/время создания 141

   проверка существования 137

   расширение:

а        dfm 96

0               xfm 37

Форма 9

   3D 108

   HD 108

   главная 95

   доступная 99

   модальная 100

   немодальная 99

   стили оформления 114 Функция:

   ColorToVectorЗD 420

   DiskFree 136

   DiskSize 136

   GetCurrentDir 91

   HSLtoRGB 217

   MakeColor 213

   Matrix3D 429

   RGBtoHSL 217

   Vector3D 420

   Vector3DToColor 420

х

Холст 223

   восстановление 234

   сохранение 234


 


ц                                       ш


Цвет 212

   модель:

0               ARGB 215

"  HSL 216

0               RGB 212

Цветовые полосы 217

Центр уведомлений 362

ч

Число, ввод 156


Шейдер417 Шрифт227

э

Эффекты визуальные 69