Книга
Книга
—! М—!用 e.t,l〃时匕
П рограм м и рован ие в Delphi от Адо Я
Динамические библиотеки
Практические рекомендации и примеры
МИХАИЛ ФЛЕНОВ
Создание локальных, клиент-серверных и трехуровневых баз данных
Программирование звука и графики с помощью OpenGL
БИБЛИЯ
шм
*聲行区Ih I
Михаил Фленов
БИБЛИЯБИБЛИЯ
БИБЛИЯ
БИБЛИЯ
http://all-ebooks.com
Санкт-Петербург
<БХВ-Петербург»
2004
УДК 6813.068+800.92Delphi
ББК 32.973.26-018.01
Ф69
Фленов М. Е.
Ф69 Библия Delphi. — СПб.: БХВ-Петербург, 2004. — 880 с.: ил.
ISBN 5-94157-456-8
Цель книги — научить читателя строить логику программы и алгоритмы различных кычислений. Уметь программировать еще не достаточно, надо знать, как применять полученные знания на практике. Для этого подробно описывается логика выполнения каждого участка кода, чтобы читатель смог использовать эти знания при решении собственных задач. Книга содержит большое количество примеров практического программирования; некоторые из них вынесены в качестве дополнительной информации на прилагаемый компакт-диск. Электронная версия книги была размещена в Internet в 2003 году. Автор собрал все замечания и предложения по дополнению книги и написал совершенно новый вариант, который вы сейчас держите в руках. Таким образом, книга прошла массовое тестирование и теперь отражает потребности множества как начинающих, так и опытных программистов.
Дня программистов
УДК 681.3.068+800.92Delphi
ББК 32.973.26-018.01
Группа подготовки издания:
Главный редактор Зам. главного редактора Зав. редакцией Редактор Компьютерная верстка КорректорДизайн обложки Зав. производствомГлавный редактор Зам. главного редактора Зав. редакцией Редактор Компьютерная верстка КорректорДизайн обложки Зав. производствомЕкатерина Кондукова Игорь Шишигин Григорий Добин Владимир Красильников Натальи Смирновой Наталия Першакова Игоря Цырулъникова Николай ТверскихЕкатерина Кондукова Игорь Шишигин Григорий Добин Владимир Красильников Натальи Смирновой Наталия Першакова Игоря Цырулъникова Николай Тверских
Главный редактор Зам. главного редактора Зав. редакцией Редактор Компьютерная верстка Корректор
Дизайн обложки Зав. производством
Главный редактор Зам. главного редактора Зав. редакцией Редактор Компьютерная верстка Корректор
Дизайн обложки Зав. производством
Екатерина Кондукова Игорь Шишигин Григорий Добин Владимир Красильников Натальи Смирновой Наталия Першакова Игоря Цырулъникова Николай Тверских
Екатерина Кондукова Игорь Шишигин Григорий Добин Владимир Красильников Натальи Смирновой Наталия Першакова Игоря Цырулъникова Николай Тверских
Лицензия ИД № 02429 от 24.07.00. Подписано а печать 22.09.04.
Формат 70x1 OOVie- Печать офсетная. Ус л. печ. л. 70,95.
Доп. тираж 3000 экз. Заказ № 942"БХВ-Петербург", 190005, Санкт-Петербург, Измайловский пр., 29.
Гигиеническое заключение на продукцию, товар № 77.99.02.953 Д.001537.03.02от 13.03.2002 г. выдано Департаментом ГСЭН Минздрава России.
Отпечатано с готовых диапозитивовв ОАО "Техническая книга"
190005, Санкт-Петербург, Измайловский пр., 29
ISBN 5-94157-456-8ISBN 5-94157-456-8© Фленов М. Е., 2004
ISBN 5-94157-456-8
ISBN 5-94157-456-8
© Оформление, издательство "БХВ-Петербург", 2004
Содержание
Введение.1
Структура книги3
Глава 1. Основные принципы работы компьютера.7
Основы работы персонального компьютера.7
Двоичная система работы процессора8
Машинный язык:13
История языков программирования14
Исполнение машинных инструкций19
Глава 2. Машинная математика. 23
Основы машинной математики23
Блок-схемьг:25
Машинная логика и цикльг28
Программирование машинной логики30
Глава 3. Начальные сведения о Delphi33
Установка Delphi 7.33
Замечание по установке в Windows 2000..43
Оболочка Delphi 645
Главное меню.47
3・5. Настройка Delphi 649
Настройка редактора кода54
Настройка оболочки 55
Глава 4. Визуальная модель Delphi57
Процедурное программирование57
Объектно-ориентированное программирование..62
Компонентная модель.-.67
Наследственность..68
Глава 5. Основы языка программирования Delphi.71
*'Hello World" или из чего состоит проект.71
Язык программирования Delphi82
Типы данных в Delphi89
Целочисленные типы данных89
Вещественные типы данных91
Р1Р7Р9000203041215161719202323343637393943484951565657585961626567686971737377798084 у • 1 д 1Л 1д -л 1- 1 1д и- 1л 1 1 1 1Л л 工 J J J J 1Л 1 1Л 1 J 1Л 1 J 1Л J J 1 1 工 1 1 1 J 1Р1Р7Р9000203041215161719202323343637393943484951565657585961626567686971737377798084 у • 1 д 1Л 1д -л 1- 1 1д и- 1л 1 1 1 1Л л 工 J J J J 1Л 1 1Л 1 J 1Л 1 J 1Л J J 1 1 工 1 1 1 J 1
Р1Р7Р9000203041215161719202323343637393943484951565657585961626567686971737377798084 у • 1 д 1Л 1д -л 1- 1 1д и- 1л 1 1 1 1Л л 工 J J J J 1Л 1 1Л 1 J 1Л 1 J 1Л J J 1 1 工 1 1 1 J 1
Р1Р7Р9000203041215161719202323343637393943484951565657585961626567686971737377798084 у • 1 д 1Л 1д -л 1- 1 1д и- 1л 1 1 1 1Л л 工 J J J J 1Л 1 1Л 1 J 1Л 1 J 1Л J J 1 1 工 1 1 1 J 1
IV
Символьные типы данных
Булевы типьь
Массивы
Странный PChar
Константьь
Всемогущий Variant.
Процедуры и функции в Delphi.....
Ре^рсивный вызов процедур.
Встроенные процедурьь
Возврат значений через параметры
Перегрузка
Методы объектов.
Наследование объектов.
Глава 6. Работа с компонентами
Основная форма и ее свойства
Событийная модель Windows
События главной формы
Палитра компонентов
Глава 7. Палитра компонентов Standard.
Кнопка (TButton)
Изменение свойств кнопки (логические операции)
Надписи (TLabel)
Строки ввода (TEdit).
Многострочное поле ввода (ТМето).
Объект TStrings.
Свойства объекта TStrings.
Методы объекта TStrings.
Компонент CheckBox
Панели (TPanel),
Кнопки выбора (TRadioButton).
Списки выбора (TListBg
Выпадающие списки (TComboBox)
Полосы прокрутки (TScrollBar).
Группировка объектов (TGroupBox)
Группа компонентов RadioButton (TRadioGroup).....
Ответы на вопросы
Глава 8. Учимся программирсжать.
Циклы^г... to ... do
Циклы while.
Циклы repeat.
Управление циклами.
Логические операторы
6 Работа со строками.188
ФункцияLength.188
ФункцияСору.189
ФункцияDelete.190
ФункцияPos190
ФункцияInsert.191
8.7. Исключительные ситуации191
Глава 9. Создание рабочих приложении195
Создание главного меню программы195
Создание дочерних окон.200
93 Модальные и немодальные окна.204
Обмен данными между формами206
Многодокументные MDI-окна208
Инициализация окон...〔212
Глава 10. Основные приемы программирования.219
Работа с массивами (динамические массивы)219
Многомерные массивы225
Работа с файлами227
Работа с текстовыми файлами.231
Приведение типов..235
Преобразование целых чисел в строку и обратно.235
Пре образование даты в строку и обратно.237
Преобразование вещественных чисел.238
Преобразование совместимых типов (преобразованиестрок).240
Указатели240
Структуры, записи243
Храним структуры в динамической памяти.247
Поиск файлов.249
Работа с системным реестром.252
Множества“259
Потоки 261
Глава 11. Обзор дополнительных компонентов Delphi265
Дополнительные кнопки Delphi (TSpeedButton^ TBitBtri)265
Самостоятельная подготовка картинок для кнопок271
Маскированная строка ввода (TMaskEdit).272
Сеточки (TStringGrid, TDrawGrid).273
Компоненты-украшения {TImage, TShape, TBevel).281
Панель с полосами прокрутки (TScroltBox).285
Маркированный список (TCheckListBox)..285
Полоса разделения (TSplitter)..287
Многострочный текст (TStaticText).289
Редактор параметров (TValueList Editor)..289
Набор вкладок (TTabControl).292
Набор страниц (TPageControl).298
Набор картинок (ТImage List).300
Ползунки (TTrackBar).“300
Индикация состояния процесса (TProgressBar).302
Простейшая анимация (TAnimate).305
Выпадаюший список выбора даты (TDate Time Picker).307
.18. Календарь (TMonth Calendar)308
Дерево элементов (TTreeView).308
Профессиональное использование компонента Tree View.314
,1 .21. Список элементов (TListView).319
.22. Простейший файловый менеджер321
.23. Улучшенный файловый менеджер (с возможностью запуска файлов)333
I .24. Подсказки для чайников (TStatusBar).335
.25. Панель инструментов (TToolBar и TControlBa^338
1 .26. Перемещаемые панели и меню в стиле MS (Docking)341
1 .27. Меню и панели в стиле ХР345
.28. Всплывающее меню в стиле ХР.351
Глава 12. Графические возможностиDelphi353
Графическая система Windows.353
Первый пример работы с графикой355
Свойства карандаша357
Свойства кисти361
Работа с текстом в графическом режиме366
Вывод текста под углом.368
Работа с цветом.373
Методы объекта TCanvas.378
Pixels.378
TextWidth и TextHeight379
Arc.379
CopyRect.379
Dmw.380
Ellipse.:381
FillRect.381
FloodFUl.381
Компонент работы с графическими файлами (TImage).381
Рисование на стандартных компонентах.386
Работа с экраном.391
Режимы рисования393
Глава 13. Печать в DelphiЛ401
Объект TPrinter..401
Получение информации об установленном принтере.405
Текстовая печать..409
Печать содержимого формы411
Вывод на печать изображения416
Еще немного о печати〔420
Глава 14. Delphi и базы данных423
Теория реляционных баз данных.424
Локальные базы данных..425
Delphi и базы данных.427
Создание первой базы данных Access.428
Пример работы с базами данных.432
Свойства компонента TADOTable437
Методы компонентаTADOTable..439
Управление отображением данных.441
Поисковые поля.448
Улучшенный пример с поисковыми полями456
Сортировка459
Фильтрация данных..461
Язык запросов SQL.465
Связанные таблицы.473
Вычисляемые поля.479
Цветные сетки D В Grid:.482
Подключение к базеданных во время выполнения программы.487
Расширения ADO.489
Обработка базы данных.496
Бинарные данные499
События таблицы504
Глава 15. Создание отчетности..507
Создание отчетности в Excel>508
Отчетность в Quick Reports.517
Печать таблиц с помошью Quick Reports.523
Печать связанных таблиц 525
Дополнительные возможности.527
Глава 16. Работа с DBF, Paradox, XML и клиент- серверными базами данных.529
Создание таблицы Paradox.529
162 Русификация таблиц Paradox и DBF.536
Быстрый поиск 537
Создание псевдонимов. 539
Работа с XML-таблицами542
Теория клиент-серверных баз данных544
Пример работы с SQL Server. 546
Многоуровневые приложения для баз данных553
Реализация сервера бизнес-логикп555
Клиент для бизнес-логики559
Глава 17. Потоки565
Теория потоков.565
Простейший поток567
Дополнительные возможности потоков.572
Подробней о синхронизации.574
Глава 18. Динамически компонуемые библиотеки577
Что такое DLL577
Решение № 1577
Проблема № 1578
Проблема №2578
Решение № 2.579
Из чего сделан Windows.581
Графические движбо!581
Простой пример создания DLL583
Замечания по использованию библиотек587
Хранение формы в динамических библиотеках588
Немодальные окна в динамических библиотеках.592
Явная загрузка библиотек595
Точка входа 597
Вызов из библиотек процедур основной программы599
Глава 19. Разработка собственных компонентов603
Пакеты604
Подготовка к созданию компонента611
Создание первого компонента615
Создание иконки компонента626
События в компонентах.628
Глава 20. Мультимедиа631
Простейшие способы проигрывания звука631
Медиа-проигрыватель средствами Delphi635
Звук без компонентов.640
Формат звукового файла WAV647
Пример воспроизведения WAV-файла …五648
Выбор устройства воспроизведения или записи.663
Функции записи звука.668
Преобразование форматов данных672
Пример преобразования форматов данных683
Глава 21. Графика OpenGL.691
Инициализация и отображения 2В-графики691
Третье измерение и тест глубины.700
Реалистичное изображение (Туман)705
Примитивная графика.708
Генерация собственных примитивов, масштабирование, перемещение объектов.716
Примитивы библиотеки GLU.722
Текстуры726
Освещение... .733
Заключение.737
Глава 22. OLE, COM, ActiveX.739
Теория OLE.739
OLE-контейнер742
Создание собственного окна вставки OLE-объекта.747
Элементы управления ActiveX751
Модель СОМ.758
Пример создания ActiveX-форм761
Создание компонентов ActiveX765
Глава 23. Буфер обмена773
Буфер обмена и стандартные компоненты Delphi.773
Объект Clipboard..775
Картинки и буфер обмена.777
Создание собственного формата для работы с буфером782
Глава 24. Дополнительная информация791
Тестирование и отладка..791
Работа с редактором.799
Закладки.799
Копирование строк800
Code Explorer.800
Редаьсгор кода.802
Создание программ инсталляции802
Как писать и распространять Shareware-программы816
Еиава 25. Сплопшая практика.821
Создание Screen Saver821
Компоненты в runtime.. 827
Тест на прочность.833
Сохранение и загрузка теста.848
Тестер852
Приложение. Описание компакт-диска860
Предметный указатель..861
Введение
Данная книга посвящена одному из наиболее популярных в нашей стране и перспективному во всем мире языку программирования Delphi. Она предназначена для программистов всех уровней, от начинающего до опытного. Как показывает практика, большинство людей научились программированию по книгам. Однако далеко не все из этих книг объясняют принципиальные основы работы Windows и компьютера в целом. Отсутствие базовых знаний в этой области не позволяет писать эффективные программы.
Я решил восполнить этот пробел. Я постараюсь написать так, чтобы, прочитав мой труд, любой человек смог стать настоящим программистом, а главное, вы должны понимать, что и для чего вы делаете. Несмотря на это, я не гарантирую, что именно вы сможете стать профессионалом.
Как показывает практика, из всех обучающихся программированию только 30% становятся настоящими программистами и только к ним можно применить понятие профессионал. Я обучил достаточно много людей и у меня этот показатель свыше 70%. Оставшиеся 30% смогли научиться писать программы, смогли понять основы, но почему-то не смогли сформировать у себя* способность самостоятельно мыслить в данной области. У них постоянно возникают вопросы, ответы на которые можно получить, затратив всего лишь небольшие усилия. Надо просто немного подумать. Причиной такого положения дел может быть лень, а может просто человеку неинтересно самостоятельно мыслить. Чаще всего тут действует лень, особенно когда рядом есть люди, у которых можно спросить. В этой связи сразу хочу вас предостеречь — только самостоятел ьно найденный ответ на вопрос добавит новые знания.
Эта книга может научить многому. Однако без стремления совершенствоваться в данной области вы не сможете самостоятельно писать “хорошие” программы. На протяжении всей книги будут рассматриваться различные методы, некоторые шаблоны и приемы программирования на языке Delphi, однако описать абсолютно все, как вы понимаете, здесь просто невозможно. Программирование — это такая область, в которой требуется постоянное обучение. В связи с этим нельзя останавливаться на достигнутом, прочитав только одну книгу. Нужно постоянно совершенствоваться и обучаться.
Прежде чем приступить к изучению самой книги, необходимо сделать несколько замечаний. Первое из них касается терминологии. В тексте часто будет использоваться выражение "Язык программирования Delphi**. Многие утверждают, что Delphi — это среда разработки, которая использует язык
программирования Pascal (Паскаль). В принципе, здесь не утверждается, что это ошибка. И все же, в Delphi от старого Паскаля осталось очень мало, поэтому я считаю, что это не просто среда разработки, а самостоятельный язык программирования. Это лично мое мнение как автора, и вы можете с ним соглашаться или нет.
Теперь о содержимом книги. В ней сделана попытка представить изучаемый материал таким образом, чтобы было понятно даже человеку, который только недавно познакомился с компьютером. Возможно, опытным программистам начальную часть книги читать будет скучно. Но даже здесь будут описываться достаточно специфичные веши, среди которых можно найти доя себя довольно много полезного. Поверьте, это действительно так и связано с тем, что большинство книг по данной проблематике упускают из виду некоторые очень важные тонкости, которые желательно знать доя понимания принципа работы программ. Без этого понимания тяжело двигаться дальше и любые новые технологии будут казаться тяжелыми и сложными.
Прежде чем приступить к чтению книги, учтите один совет. Книгу желательно читать полностью, от начала и до конца, потому что материал излагается постепенно и некоторые веши могут быть непонятны, если что-то пропустить вначале. Как только вы почувствуете, что набрали достаточно знаний и способны самостоятельно писать хотя бы простейшие программы, можете сделать единственный скачок на 鄧.24. В ней дается материал, касающийся отладки приложений, потому что при самостоятельном написании программ всегда появляются ошибки или опечатки. Эта глава объясняет, как находить такие ошибки. В ней вы также узнаете некоторые приемы по работе с редактором кода, которые могут пригодиться в будущем при программировании собственных приложений, да и при работе с примерами, которые представлены в этой книге.
После прочтения этой главы можно вернуться к той, на которой вы остановились ранее, и продолжить чтение книги уже без каких-либо скачков. Иначе какой-то важный момент может быть упущен, и нагнать упущенное потом будет очень тяжело, потому что вы можете не заметить, что что-то упустили.
Если вы читали вариант книги, представленный в Internet (www.vr-onlme.ru/ books/index.htm)9 вам также будет полезно прочитать эту книгу, потому что данный вариант переработан полностью от начала и до самого конца.
Внимание
Я не читал ни одной книги по Delphi на русском языке. Единственная книга, которую я видел (я даже не прочитал ее полностью, а просмотрел несколько глав), была по Borland Pascal. Это было в 1994 году, поэтому я даже не помню ее названия. Именно поэтому некоторые мои термины могут отличаться от таких же в другой литературе.
И последнее, некоторые термины, встречающиеся в книге, могут отличаться от аналогичных, которые используются в другой технической литературе, относящейся к данному вопросу. Это связано с особенностями перевода англоязычного текста на русский язык. В любом случае, терминология, которая приводится в книге, делает ее намного проще и понятней как начинающим, так и опытным программистам.
Структура книги
В этом месте принято описывать содержание глав книги. Это поможет вам легко найти интересующую главу или, наоборот, узнать какие главы вы уже хорошо знаете и читать не стоит.
Глава 1 “Основные принципы работы компьютера". Посвящена рассмотрению принципов работы компьютера. В ней рассказывается о том, как компьютер производит расчеты и выполняет различные команды. В этой главе даются основы, без которых невозможно понимание самого принципа программирования. Конечно же, можно обойтись и без нее, потому что и обезьяну можно научить кидать гранату. Но только с помощью знаний, полученных при изучении данной главы, можно понять, что и зачем вы пишете в своей программе.
В принципе, эту главу можно и опустить, потому что научиться программированию можно и без этого. Однако только с пониманием работы "железа" можно стать настоящим программистом.
Глава 2 "Машинная математика". В этой главе рассматриваются основы кодирования данных в компьютере или машинная математика. Машинная математика —это основа программирования. Здесь вы познакомитесь с логикой выполнения программ и сами научитесь формировать логическую структуру будущей программы.
Мы познакомимся с гениальным изобретением всех времен — "блок- схемами" программ. Они очень хорошо помогают начинающим программистам в понимании работы логики компьютера. Конечно, в будущем можно научиться писать программы и без использования блок-схем, но на начальном этапе это очень удобный инструмент, как для определения логики работы программы, так и для обучения программированию в целом.
Глава 3 "Начальные сведения о Delphi**. В этой главе излагается процесс установки Delphi 7, а также рассказывается о входящих в поставку утилитах. После этого мы запустим оболочку Delphi 7 и рассмотрим, из чего она состоит. В главе также будут рассмотрены начальные сведения о Delphi. Ее могут пропустить те, кто уже знаком с этим языком программирования. Хотя в конце главы будет идти речь о настройках оболочки, поэтому желательно ее все же прочесть.
Глава 4 ^Визуальная модель Delphi". В этой главе речь пойдет о визуальной модели Delphi. Это то, на чем построена вся теория программирования в среде этой оболочки. Кроме того, здесь будут затронуты вопросы теории объектно-ориентированного программирования, без понимания которых невозможно движения дальше.
Глава 5 “Основы языка программирования в Delphi". В этой главе мы познакомимся с типами данных, используемыми Delphi, и напишем нашу первую программу. Здесь мы познакомимся со всеми "внутренностями" этой программы и узнаем, из чего состоит ее "скелет".
Глава 6 "Работа с компонентами". Работа в среде Delphi предполагает активное использование компонентов среды. В этой главе рассматриваются основы работы с этими компонентами, описываются основные их свойства, а также дается характеристика событийной модели Endows и основных событий главной формы.
Глава 7 "Палитра компонентов Standard п. В этой главе мы познакомимся с вкладной Standard палитры компонентов Delphi. Здесь будут определены все компоненты данной вкладки, а также рассказано, для чего они предназначены и как их использовать. Изложение материала главы сопровождается рассмотрением большого количества примеров с использованием этих компонентов.
Глава 8 "Учимся пр огр аммир ов атьи. Здесь подробно рассказывается про циклы, логические операции, работу со строками и многое другое. Это последняя глава, которая посвящена изучению основ пр огр аммир ования. Те, кто уже имеет опыт программирования в Delphi, могут эту главу пропустить.
Глава 9 "Создание рабочих приложений". Сейчас уже трудно себе представить программу, состоящую только из одного главного окна. Большинство приложений состоит хотя бы из нескольких окон, а некоторые даже из сотен. В этой главе дается понятие многооконных приложениий. Здесь также рассказывается, как создавать главное меню программы.
Глава 10 "Основные приемы кодингам. В этой главе, на первый взгляд, рассматривается совокупность не с вязаных между собой понятий и объектов программирования. Тут и работа с массивами, файлами, реестром, преобразование данных, структуры и указатели. Все это собрано под одной крышей с одной целью, чтобы читатель мог изучать все последовательно, по мере надобности. Меня просто бесит литература, в которой сначала описывают разные функции и бесполезные примеры (которые в жизни не пригодятся), и только к концу книги находишь что-то действительно полезное. Таким образом, благодаря материалу этой главы полезная информация дается своевременно и изучение пр огр аммир ов ания не становится рутинным и скучным.
Глава И “Обзор до полнительных компонентов Delphi". После рассмотрения основных приемов программирования можно перейти к изучению остальных компонентов Delphi. Данная глава посвящена рассмотрению именно этого вопроса. В ней приводятся достаточно полезные в будущем примеры. Если бы это было сделано раньше, то ничего интересного в качестве примеров указать было бы просто не возможно.
Глава 12 *Трафические возможности Delphi". Здесь рассказывается обо всем, что касается графики. Показывается, как можно рисовать встроенными средствами в Delphi различные фигуры и как работать с изображениями разного формата.
Глава 13 "Печать Delphi**. Эта глава полностью посвящена печати, и только печати. В ней рассказывается, как выводить на принтер текст и графику, как учитывать разрешение принтера и многое другое. 、
Глава 14 “Delphi и базы данных". Многие слышали или знают, на примере более ранних версий, что на Delphi очень легко писать базы данных, потому что в структуру его среды программирования встроены мощные средства, предназначенные именно для этого. Данная глава позволит вам в этом убедиться. Здесь будет показано, как работать с локальными базами MS Access, а также приводится множество полезных примеров.
Глава 15 "Создание отчетности". В этой главе рассматриваются вопросы, касающиеся экспорта данных из таблиц в среду Excel, а также вопросы, связанные с подготовкой к печати документов любой сложности.
Глава 16 "Работа с DBF, Paradox, XML и клиент-серверными базами данных". В главе рассказывается о том, как работать с другими таблицами, отличными от Access. Здесь описывается технология доступа к данным через драйвер BDE, который предоставляет фирма Borland для доступа к таблицам DBF и Paradox.
Глава 17 "Потоки". Windows — многозадачная операционная система, позволяющая выполнять многопоточные приложения, в которых операции выполняются параллельно. В этой главе вы познакомитесь с понятием многопоточности, с реализацией ее в программах, также здесь будут рассмотрены несколько примеров реализации многопоточных приложений.
Глава 18 "Динамические библиотеки". Здесь рассказывается все необходимое о динамических библиотеках. Вы узнаете, как создавать библиотеки с математическими процедурами и функциями, как хранить окна в библиотеках и увидите реальные примеры их использования.
Глава 19 “Разработка собственных компонентов”. В этой главе речь пойдет о том, как создавать свои VCL (Visual Component Library — библиотека визуальных компонентов) компоненты, как устанавливать чужие разработки в Delphi и как работать с пакетами компонентов.
Глава 20 “Мультимедиа". Эта глава полностью посвящена принципам программирования звука и видео. В ней показано, как создавать приложения для работы со звуком, используя встроенные в Delphi компоненты, или без них.
Глава 21 "Графика OpenGL”. Есть две достаточно перспективные разработки для профессиональной работы с компьютер ной графикой — OpenGL и DirectX. В- данной главе книги достаточно подробно описывается только OpenGL. Информацию по DirectX вы сможете найти на компакт-диске, прилагаемом к книге, в папке / Документация.
Глава 22 "LE, COM, ActiveX". В этой главе будут описаны основные принципы реализации технологий OLE, СОМ и ActiveX. Все эти термины взаимосвязаны и должны описываться вместе. Не все пр огр аммисты любят использовать эти технологии, но описать их необходимо, потому что иногда работать с ними все же приходится. Возможно, что и вы когда - ни будь столкнетесь с этой технологией.
Глава 23 "Буфер обмена". Кнопки Копировать и Вставить есть практически в любом полноценном приложении. Можно надеяться, что вы также захотите использовать такую возможность в своей программе. В этой главе дается максимум полезной теоретической и практической информации 'для того, чтобы вы смогли сделать свои программы более привлекательными, добавив возможность переноса данных между приложениями.
Глава 24 "Дополнительная информация". Данная глава единственная, которую можно прочитать вне очереди. Как только вы почувствуете, что полученных знаний достаточно для написания собственных небольших приложений, можете перескочить на эту главу. Здесь описываются некоторые приемы работы с оболочкой Delphi, которые смогут вам помочь при разработке собственных приложений, а также принципы тестирования и отладки программ.
Глава 25 "Сплошная практика". Эта глава является завершающей, поэтому в ней приводится несколько интересных программ. Это делается для того, чтобы вы могли увидеть некоторые приемы программирования, которые могут пригодиться в будущем. Данную главу можно расематривать как дополнительный материал ко всему сказанному выше.
Глава 1
Основные принципы работы компьютера ,
Прежде чем начинать программировать, необходимо понять, как работает компьютер. Как говорил какой — то полководец: "Нужно хорошо изучить своего врага!!!** Возможно, это говорил и не полководец, но это неважно. Программирование — это постоянная борьба с машиной. Нужно заставлять ее делать то, что вам нужно. Поэтому любой программист просто обязан знать структуру компьютера.
Основы работы персонального компьютера
В общем случае компьютер состоит из следующих основных компонентов: процессор, память, видеокарта, винчестер (жесткий диск) и различные разъемы для подключения дополнительных устройств. Все эти компоненты связаны между собой с помощью шлейфов и шин.
Вся информация в компьютере хранится на винчестере. Когда запускается на выполнение какая-либо программа, она сначала загружается во внутреннюю или оперативную память (ОП) и только потом процессор начинает выполнять содержащиеся в ней инструкции. Чем больше программа, тем дольше она загружается, и это желательно учитывать всегда. Если у вас получается слишком большой программный код, то для ускорения выполнения программы можно в основном файле задействовать только основные возможности. Все дополнительные возможности, которые используются не всеша, можно вынести в отдельные файлы, например, динамически подключаемые библиотеки DLL (Dynamic Link Library), о которых будет отдельный разговор.
Результат работы программы выводится на экран через видеокарту. На любой видеокарте есть чип памяти, в котором отображается все содержимое экрана. Когда вам нужно вывести что-то на экран, вы просто копируете эти данные в видеопамять, и видеокарта автоматически выводит их
на монитор. Именно такой способ использовался в среде ОС MS-DOS (Операционная система MS-DOS). В Windows запрещен прямой доступ к видеопамяти (не считая DirectX), и вывод работает здесь немного по- другому. Как именно, мы будем рассматривать, когда придет время знакомиться с графикой в Delphi.
Это пока все, что необходимо знать о работе компьютера. В следующих разделах некоторые, наиболее важные, моменты будут рассмотрены более подробно. Сейчас же нас будет интересовать работа процессора, поэтому ему уделяется здесь особое внимание.
Двоичная система работы процессора
Компьютеры изобрели достаточно давно. В те далекие времена об электронике даже и не упоминалось. Первые компьютеры были ламповыми и занимали очень много места. Для того чтобы управлять такой махиной, нужно было очень много обслуживающего персонала.
Однако именно тогда были заложены основные принципы работы компьютера, которые действует до сих пор. Суть их заключается в следующем. Данные передаются с помощью какого-то сигнала (для нас не имеет значения какого, потому что мы не электронщики) методом "есть сигнал или нет" или, по-другому, "включен или выключен". Так появился "бит” (bit). Бит это единица информации, которая может принимать значение 0 или 1, т. е. "включен или выключен ". Восемь бит объединяются в байт, один байт равен 8 битам. Почему именно 8? Да потому, что первые компьютеры были восьмиразрядными и могли работать одновременно только с 8 битами, например, 010000111.
Все первые нули можно удалять, поэтому число 010000111 можно записать как — 10000111. Это то же самое, что и в привычной для нас десятичной системе счисления, где каждый разряд может принимать значения от 0 до 9. Здесь так же никто не будет писать число 5743 как 0005743.
В один байт можно записать любое число от 0 до 255. Почему? Об этом немного позже. Указанный диапазон чисел довольно мал. Поэтому чаще используют более крупные градации:
□ два байта = слово;
口 два слова = двойное слово.
Итак, компьютер стал работать в двоичной системе счисления. Но как же тогда записать число 135, если у нас единица информации может принимать значения 0 или 1? Это можно сделать в двоичной системе. Давайте разберемся, как это работает.
Для начала вспомним, как действует десятичная система счисления, к которой мы привыкли. Для этого рассмотрим число 519 578 246. Я специально
выбрал такое число, чтобы оно состояло из восьми разрядов (цифр). Теперь запишем его, как показано на рис. 1.1.
Номер разряда 7 6 5 4 3 2 1 О 19578246
Рис. 1.1. Число 519 578 246в десятичной системе исчисления
Как видите, я пронумеров ал разряды, начиная с нуля до восьми, и справа налево. Теперь представьте себе, что это не целое число, а просто набор разрядов. 1. 9, £ 7, 8, 2, 4 и £ Как из этих разрядов получить целое число? Наверно некоторые скажут, что надо просто записать их подряд. А если я спрошу, почему? Вот тут появляется математика. Нужно каждый разряд умножить на 10 (степень счисления, возведенную в степень номера разряда). Непонятно? Попробую оформить сказанное в виде формулы, показанной на рис. 1.2.
а 5 * 明 а Номер разрядаНомер разряда
Разряд 0 (10)+Разряд 1 * (10)+...
Рис. 1.2. Работа с десятичными числами
уже 46). Потом 2 х 10 во второй степени, 2 х 102 — '°° (итого %6). Потом8 х 10 в 3 степени, 8 х 1(Р = 8000(и того 8246) и т. д;. В итоге получитсяуже 46). Потом 2 х 10 во второй степени, 2 х 102 — '°° (итого %6). Потом8 х 10 в 3 степени, 8 х 1(Р = 8000(и того 8246) и т. д;. В итоге получитсяДавайте посчитаем значение числа по этой формуле, начиная с нулевого разряда. Получается, что 6 нужно умножить на 10 в нулевой степени 6 х 10° = 6. Потом прибавить 4 х 10 в 1 степени, или 4 х 101 — ‘°(и того
уже 46). Потом 2 х 10 во второй степени, 2 х 102 — '°° (итого %6). Потом8 х 10 в 3 степени, 8 х 1(Р = 8000(и того 8246) и т. д;. В итоге получится
уже 46). Потом 2 х 10 во второй степени, 2 х 102 — '°° (итого %6). Потом8 х 10 в 3 степени, 8 х 1(Р = 8000(и того 8246) и т. д;. В итоге получится
число 519 578 246.
А теперь рассмотрим двоичную систему счисления. Здесь каждый разряд может принимать значение 0 или 1 (2 состояния). Кстати, в десятичной системе у нас каждый разряд мог принимать значения от 0 до 9, т. е. имел десять состояний. Давайте рассмотрим следующий байт — 010000111. Запишем его на листке бумаги так, как показано на рис. 1.3.
Номер разряда 8765432 1 О
Биты0 1 0 0 0 0 1 1 1
Рис. 1.3. Двоичное число
Здесь действует та же самая формула, только нужно возводить в степень не 10, а число 2. Опять же произведем расчет, начиная с нулевого разряда, т. е. справа налево. Получается, что первую 1 мы должны умножить на 2 в нулевой степени (1x2°= 1). Следующую единицу нужно умнозфть на 2\ получается 2 (итого 2 4- 1 = 3) н т. д. Вот как этр 輙嘤т Bbffe阅彥гв полностью:
(1 х2。)+ (1 х2】)+(1 j 第+ :冲х =轡 25)+ (0x26)+0x27) 28)
Вот так, оказывается, выглядит в двоичной системе счисления число 135. Давайте теперь научимся пересчитывать числа из десятичной системы в двоичную. Для этого нужно число 135 разделить на 2. Получается 67 и остаток 1 (запомним 1, т. к. она определяет первый двоичный разряд искомого числа). Теперь 67 снова делим на 2, получается 33 и остаток 1 (таким образом получено уже две двоичные единицы, т. е. 11). 33 делим на 2, получаем 16 и остаток 1 ( в результате получаем три двоичные единицы, 111). 16 делим на 2, получаем 8 и остаток 0 (результат 0111). И наконец, 8 делим на 2 = 4, остаток от деления при этом будет 0 (получаем 00111); 4 делим на 2 = 2, остаток 0 (получаем 000111). 2 делим на 2 ~ 1, остаток 0 (итого 0000111). Оставшаяся 1 частного на 2 не делится, значит, это последний, самый старший разряд искомого числа. Просто дописываем ее к ранее сформированным разрядам и получаем окончательный ответ — 10000111. Получилось первоначальное число.
Вот так происходит преобразование чисел в двоичную систему счисления. Таким же образом можно перевести число в любую другую систему (двоичную, восьмеричную, шестнадцатеричную). Для более полного закрепления материала в табл. 1.1 показано соответствие десятичных чисел двоичным. Попробуйте сами перевести пару чисел из одной системы счисления в другую.
Таблица 1.1. Таблица соответствия десятичных и двоичных чисел
Десятичное
Двоичное
Десятичное
Двоичное
0
0
6
110
1
1
7
111
2
10
8
1000
3
и
9
1001
4
100
10
1010
5
101
Например, попробуйте перевести число, состоящее из 8 бит (1 байт), у которого все биты состоят из единиц, в десятичную систему. Вы должны по-
лучить 255. Это максимальное число, которое можно записать в одном байте, потому что все его биты равны 1. Вот так и получается, что в 8 битах можно записать числа от 0 до 255. В 16 битах (2 байта или слово) можно записать число от 0 до 65 535. В 32 битах (двойное слово) можно уже записать число от 0 до 4 294 967 295.
В компьютере принято вести расчет в двоичной или шестнадцатеричной системе. Вторая вошла в обиход, когда компьютеры стали 16-разрядными.
Шестнадцатеричная система выглядит немного по-другому. Каждый разряд содержит уже не 2 состояния (как в двоичной системе) или десять (как в десятичной системе), а шестнадцать. Поэтому один разряд может принимать значения: 1, 2, 3, 4, 5, 6, 7, 8, 9, А, В, С, D, Е, F. Буква “А” соответствует цифре 10 в десятичной системе "В” соответствует 11 и т. д. Например, число 1А в шестнадцатеричной системе равно 26 в десятичной. Почему? Да в соответствии все с той же формулой. Только здесь нужно возводить уже 16 в степень номера разряда. "А" — десять, нужно умножить на 16°. В результате получится 10. 1 — первый разряд нужно умножить на 16* получится значение 16. Затем полученные результаты складываются и определяется искомое число — 10 + 16 = 26. В результате, как показано в табл. 1.2, можно установить соответствие между числами, записанными в различных системах счисления.
Таблица 1.2. Таблица соответствия десятичных, двоичных и шестнадцатеричных чисел
Десятичное
Двоичное
Шестнадцатеричное
6
0
0
1
1
1
2
10
2
3
11
3
4
100
4
,5
101
5
6
110
6
7
111
7
8
1000
8
9
1001
9
10
1010
А
11
1011
В
12
1100
С
Таблица 1.2 (окончание)
Десятичное
Двоичное
Шестнадцатерично е
13
1101
D
14
ИЮ
Е
V.
15
1111
F
16
10000
10
17
10001
И
18
10010
12
19
10011
13
20
10100
14
На протяжении всей книги мы будем иногда встречаться с шестнадцатеричной системой счисления (без этого никуда не денешься). В этом случае, когда нужно будет показать, что число шестнадцатеричное, перед ним будет ставиться знак решетки #, например, #13. В других языках, например Assembler или C++, принято ставить в конце числа букву h (13h). Но эта книга о Delphi, поэтому здесь будем писать так, как принято в этой среде разработки, чтобы потом не возникало никаких вопросов.
До сих пор рассматривались целые числа. С числами с плавающей точкой совершенно другая история. Если заранее предусмотрено, что число может быть отрицательным, то его длина сокращается ровно на один бит (этот бит отводится под знак числа). Так, неотрицательное целое число может быть 8битным, тогда как число со знаком будет 7-битным. Первый бит будет означать знак. Если первый бит равен 1, то число отрицательное, иначе положительное.
В дробных числах один байт может быть отведен для целой части и один для дробной. Никогда не смешивают целую и дробную части в одно целое. За счет этого дробные числа всегда будут занимать больше памяти, и операции с ними будут проходить намного дольше.
На первый взгляд перевод чисел очень сложный процесс, но вручную им пользоваться необязательно. Человек уже давно придумал для себя хорошего помощника — калькулятор. С его помощью без проблем можно перевести число в любую систему счисления.
Запустите встроенный в Windows калькулятор (Пуск | Программы | Стандартные I Калькулятор). Теперь выберите из меню Вид пункт Инженерный. На рис. 1.4 показано окно, которое вы должны увидеть.
Рис. 1.4. Внешний вид калькулятора
Для перевода числа в другую систему просто наберите его в окне ввода калькулятора и потом хвыберите нужную систему счисления. На рисунке красным цветом выделены кнопки переключения из одной системы счисления в другую:
О Нех — шестнадцатеричная;
П Dec — десятичная;
口 Oct — восьмеричная;
口 Bin — двоичная.
Возникает вопрос — зачем здесь так долго рассказывалось о преобразованиях чисел, когда так легко можно воспользоваться калькулятором? Ответ прост — это нужно знать. Поверьте мне. Если вы будете понимать, как происходит преобразование, то вам потом легче будет работать с этими числами.
Никогда не полагайтесь только на технику. Всегда полезно знать, как и зачем она что-то делает. Если вы разберетесь с шестнадцатер ич н ы м представлением данных, то сможете простые преобразования делать в уме. Ну, а если вы еще собираетесь стать и хакером, то вам просто необходимо научиться хорошо оперировать разными системами счисления.
Машинный язык
Данные на диске хранятся в двоичном виде. Даже текстовые файлы на диске выглядят в виде нулей и единиц. Точно так же выглядит и любая про-
грамма, только ее называют машинным кодом. Давайте познакомимся с ним немного поближе.
Любая программа представляет собой последовательность команд. Эти команды называются процессорными инструкциями. В точном соответствии этим инструкциям процессор определяет, что и как ему нужно делать. Когда вы запускаете программу, компьютер загружает ее машинный код в оперативную память и начинает выполнять команду за командой. Наша задача как программистов написать эти инструкции таким образом, чтобы компьютер понял, что мы от него хотим.
Реальная программа, которую выполняет компьютер, представляет собой последовательность единиц и нулей. Такую последовательность называют машинным языком. Человек не способен эффективно думать единицами и нулями. Для нас легче воспринимается осмысленный текст, а не сумасшедшие числа в двоичной системе счисления, с которой мы не привыкли работать. Например, команда сложения двух регистров выглядит так: #ОЗСЗ. Это мало о чем говорит, и запомнить такую команду очень тяжело. На* много проще написать 'сложить число 1 и число2 ”.
Первое время программисты писали программы в машинных кодах, пока кому-то не пришла в голову идея: н Почему бы не писать текст программы на понятном языке, а потом заставлять компьютер переводить этот текст в машинный код?н Идея действительно заслуживала внимания. Так появился первый компилятор — программа, которая переводила текст программ в машинный код. Таким образом, пользователи стали писать программы более осмысленно, а всю рутинную работу по переводу текста программы в машинный код возложили на компьютер.
Здесь настало время сделать паузу и рассказать вам небольшую историю языков программирования. Она достаточно интересна и поучительна. Ну, а потом мы продолжим изучение принципов работы компьютера и познакомимся со структурой процессора и его работой при выполнении программы.
История языков программирования
Как мы уже выяснили, компьютер — примитивное существо, которое мыслит нулями и единицами, из которых складываются числа. Основное его устройство — процессор. Все, что может делать процессор, это оперировать двоичными числами. Программы — это тоже числа, которые воспринимаются процессором как некоторая совокупность команд процессору с целью выполнения им определенных действий.
Мы также выяснили, что первые программисты писали программы в машинных кодах. Тогда еще не было ко мпиляторов и приходилось все писать числами. Вы даже представить себе не можете, какой это адский труд. Постоянно держать в памяти таблицу машинных кодов (это не таблица умно-
Основные принципы работы компьютера
жения). Например, вам понятно шестнадцатеричное число 8ВСЗ? Нет? А это обычная команда копирования между двумя ячейками регистров. Это просто пример, потому что тогда регистры были другие и процессоры были намного проще.
Со временем компьютер стал умнеть, но самое главное, он все также оперировал двоичными числами, однако делал это намного быстрее. Программист —это человек, а не железка, и ему очень тяжело создавать логику в числах. Намного легче работать с привычными словами. Например, все ту же команду пересылки удобней записать словами типа 'скопировать ebx в еах”. Но что делать, если компьютер не понимает слов, а только числа? Выход есть — написать такую программу, которая будет превращать текст в машинные коды. Пусть компьютер сам создает байт-кол. Программу, выполняющую эти действия, назвали компилятором, а язык, на котором писался исходный текст программы, назвали языком программирования.
00527F7E E84DO1000000527F7E E84DO10000cut】 TSfctJEDataFon»♦ Ihit.ViewGKid
00527F7E E84DO10000
00527F7E E84DO10000
00S2?r©3 8BC3OQ527F85 ЕвБбСЗОООО00S2?r©3 8BC3OQ527F85 ЕвБбСЗООООMainUh 算七*pas. US2f Initf rogranm;
00S2?r©3 8BC3
OQ527F85 ЕвБбСЗОООО
00S2?r©3 8BC3
OQ527F85 ЕвБбСЗОООО
»k?v eax,ebx .
call TSborDaCaFor»BInitProgrami
K&inUnlt. ^tts. 133: if U>evlceMwk>-0 then
QO527F8A ©3BB1004000000 cwp d3“d ptr (eibx+$00000410] , §00
00527F91 7C26jl +$2€
MainUnlt.pa*.135:OOS27F93 B101 00527Г95 8201MainUnlt.pa*.135:OOS27F93 B101 00527Г95 8201ScanPortThr := TScahPortTkread.Create(true)*ScanPortThr := TScahPortTkread.Create(true)*OOS27F97 JLW4A4S200C0S27F9C £ВЕВ94ЕГГГCO527FM 8BF0005Z7Fk3 89E3HCH口伽。OOS27F97 JLW4A4S200C0S27F9C £ВЕВ94ЕГГГCO527FM 8BF0005Z7Fk3 89E3HCH口伽。mov cl,$01 gv dX, $01mov cl,$01 gv dX, $01gv eax, I $00S2a4d4] call TThread.Create wov esireaxiwjv [etox+$00000414) ^eslgv eax, I $00S2a4d4] call TThread.Create wov esireaxiwjv [etox+$00000414) ^eslMMniUnit •»a・.136: ScahFor tthT. U>ev i ceHwa:«"U>e¥ IceHum;MMniUnit •»a・.136: ScahFor tthT. U>ev i ceHwa:«"U>e¥ IceHum;00527ГЛ9 8B63X0O4000O00527ГЛ9 8B63X0O4000O00527ГЛГ 89464C>f&lnUni*・》a* .137:00527ГЛГ 89464C>f&lnUni*・》a* .137:roovroovею” eax,[ebx>$00000^10] [esi+$4c]yeaxею” eax,[ebx>$00000^10] [esi+$4c]yeaxScanPortThr teatime ;ScanPortThr teatime ;00527FB2 8»C6 00527FB4 E81F9eKFFF MainUnlt.pas.141: try00527FB2 8»C6 00527FB4 E81F9eKFFF MainUnlt.pas.141: try»ov wep^^^icoll TThread. Resuroe»ov wep^^^icoll TThread. ResuroeOOS27FB9 00527FBB 00S27FBCOOS27FB9 00527FBB 00S27FBC33CO556в0В80520033CO556в0В805200О0527ГС1 64FF30О0527ГС1 64FF30xoc eox*00xpush eJoppush ?0052800bpush duord ptr fs: [aax]xoc eox*00xpush eJoppush ?0052800bpush duord ptr fs: [aax]
MainUnlt.pa*.135:
OOS27F93 B101 00527Г95 8201
MainUnlt.pa*.135:
OOS27F93 B101 00527Г95 8201
ScanPortThr := TScahPortTkread.Create(true)*
ScanPortThr := TScahPortTkread.Create(true)*
OOS27F97 JLW4A4S200
C0S27F9C £ВЕВ94ЕГГГ
CO527FM 8BF0
005Z7Fk3 89E3HCH口伽。
OOS27F97 JLW4A4S200
C0S27F9C £ВЕВ94ЕГГГ
CO527FM 8BF0
005Z7Fk3 89E3HCH口伽。
mov cl,$01 gv dX, $01
mov cl,$01 gv dX, $01
gv eax, I $00S2a4d4] call TThread.Create wov esireax
iwjv [etox+$00000414) ^esl
gv eax, I $00S2a4d4] call TThread.Create wov esireax
iwjv [etox+$00000414) ^esl
MMniUnit •»a・.136: ScahFor tthT. U>ev i ceHwa:«"U>e¥ IceHum;
MMniUnit •»a・.136: ScahFor tthT. U>ev i ceHwa:«"U>e¥ IceHum;
00527ГЛ9 8B63X0O4000O
00527ГЛ9 8B63X0O4000O
00527ГЛГ 89464C
>f&lnUni*・》a* .137:
00527ГЛГ 89464C
>f&lnUni*・》a* .137:
roov
roov
ею” eax,[ebx>$00000^10] [esi+$4c]yeax
ею” eax,[ebx>$00000^10] [esi+$4c]yeax
ScanPortThr teatime ;
ScanPortThr teatime ;
00527FB2 8»C6 00527FB4 E81F9eKFFF MainUnlt.pas.141: try
00527FB2 8»C6 00527FB4 E81F9eKFFF MainUnlt.pas.141: try
»ov wep^^^i
coll TThread. Resuroe
»ov wep^^^i
coll TThread. Resuroe
OOS27FB9 00527FBB 00S27FBC
OOS27FB9 00527FBB 00S27FBC
33CO
55
6в0В805200
33CO
55
6в0В805200
О0527ГС1 64FF30
О0527ГС1 64FF30
xoc eox*00x
push eJop
push ?0052800b
push duord ptr fs: [aax]
xoc eox*00x
push eJop
push ?0052800b
push duord ptr fs: [aax]
Рис. 1.5. Программа в машинных кодах и Assembler
И вот был разработан первый компилятор. Эту программу назвали Assembler, что переводится, как "сборщик". Писать на нем практически так же сложно, как и в машинных кодах, однако теперь уже использовались не числа, а по-
нятные, как показано на рис. 1.5, человеку слова. Например, все та же команда копирования регистров теперь выглядела так: mov eax, ebx. То есть цифры заменились на понятные слова.
Вроде все прекрасно и удобно, но почему-то среди программистов возникли споры и разногласия. Кто-то воспринял новый метод с удовольствием. А кто-то говорил, что машинные коды лучше. Любители языка Assembler хвалили компилятор за то, что программировать стало проще и быстрее, а противники утверждали, что программа, написанная в кодах, работает быстрей. Говорят, что эти споры доходили до драк и иногда лучшие друзья становились врагами. В принципе, и те, и другие были правы. На языке Assembler действительно программу писать легче и быстрей, но программа, написанная в машинных кодах, работала быстрее.
Тоща никто не мог себе представить, чем же все может закончиться. Но время показало свое. С помощью Assembler программы писались быстрее, а это один из основных факторов успеха любой программы на рынке. Люди начинают пользоваться тем продуктом, который выходит на рынок первым. Даже если более поздний вариант лучше, человека трудно переубедить перейти на другую версию. Здесь начинает играть большую роль фактор привычки. К тому же, когда программист напишет свою первую версию программы в машинных кодах, программист на языке Assembler выпустит уже пару новых версий.
Вот так и получилось, что те, кто программировал на языке Assembler, превратились в убегающих вперед, а те, кто программировал в машинных кодах превратились в постоянно догоняющих. В конце концов, первые убежали настолько "далеко", что вторые не смогли догнать и вынуждены были или перейти на Assembler, или отойти от программирования совсем.
С этого момента начался бум. Языки программирования стали появляться один за другим. Так появились С, ADA, FoxPro, Fortran, Basic, Pascal и др. Некоторые из них были предназначены только для школьников или обучения, другие были ориентированы на профессиональных программистов. И тут споры перенеслись в другую плоскость — какой язык лучше. Некоторые говорили, что это Pascal, другие утверждали что С, ну, а кое-кто утверждал, что это Msual Basic. Этот спор длится уже около 30 лет, и конца ему не видно. При этом все спорные вопросы разделились на две части:
Какой язык самый лучший?
Что лучше — язык высокого уровня или низкого?
Спор по первому пункту не может закончиться до сих пор. Каждый пытается доказать, что его язык программирования самый мощный, удобный и создает самый быстрый профаммный код. Мне кажется, что этот спор не закончится никогда. В принципе, такое положение дел устраивает всех, потому что это своеобразная конкуренция. Благодаря ей происходит развитие языков профаммирования, и мы быстро продвигаемся вперед.
Так все же, какой язык лучше? На этот вопрос можно дать ответ, но об этом немного позже.
Наиболее интересным был спор: "Что лучше — язык высокого уровня или низкого?** Язык низкого уровня это тот, который ориентиров ан на команды процессора, т. е. Assembler. К языкам высокого уровня относят С, Pascal, Basic и др. Они ориентированы на людей и создают им максимум удобства при написании программ. Этот спор проходил в той же манере, как и спор между любителями Assembler и любителями программирования в машинных кодах. Только теперь приверженцы языка Assembler утверждали, что их код самый быстрый, а любители языков высокого уровня утверждали, что они напишут программу быстрей, чем самый лучший программист на языке Assembler.
Спор продолжался достаточно долгое время. И опять победила скорость разработки и "упобство" языка программирования. Любителям Assembler пришлось отступить, потому что теперь они превратились в ” до гоняющих".
Конечно же, нельзя сказать, что машинные коды и Assembler окончательно ушли из нашей жизни. Они используются до сих пор, но в очень ограниченном количестве. Язык Assembler используется только в качестве вставок для языков высокого уровня, а машинные коды используются для написания того, что не может сделать компилятор (да и для написания самого компилятора они могут потребоваться). Ушедшие технологии живут и будут жить, но рядовой программист очень редко встречается с ними.
Следующей ступенью стало объектно-ориентированное программирование. Язык С превратился в C++, Pascal превратился в Object Pascal и т. д. И снова борьба. И снова скорость разработки против скорости выполнения программного кода. Опять споры, драки и оскорбления.
Война длилась несколько лет. Сколько времени было потрачено в спорах, сколько волос было вырвано на голове в процессе доказательств силы именно его программного кода. А в результате победила скорость и удобство разработки, т. е. объектно-ориентированное программирование (ООП).
Последней крупной революцией, происходящей в программировании, счи- тется переход на визуальное программирование. Этот переход происходит прямо на наших глазах. Визуальность дает нам еще более удобные средства разработки для более быстрого написания кода, но проигрывает ООП по быстроте работы. Вот почему многие начинающие программисты стоят на перекрестке, не зная, какой язык выбрать.
Лидером в разработке визуальных языков программирования является Borland, а приверженцем ООП остается Microsoft. Конечно же, Билл Гейтс пытается встроить в свои языки визуальность, но она примитивна по сравнению с такими гигантами, как Delphi, Kylix или C++ Builder. Это связано с изначальной неправильной разработкой MFC (Microsoft Foundation Classes —
Основные классы Microsoft), которая не может работать визуально. Нужна глобальная переработка кода, которую почему-то не хотят делать. Вот народ и стоит на двух атомных бомбах, ожидая взрыва одной из них. Как вы думаете, какая бомба рванет? Что победит — скорость разработки или скорость кода? Я не буду отвечать на этот вопрос. История говорит сама за себя, а мы подождем подтверждение этому.
Считается, что информационные процессы не будут стоять на одном месте и переход на новые технологии программирования рано или поздно состоится. Поэтому я уже перешел на Delphi. Если вы хотите успеть за прогрессом, то тоже обязаны вступить в партию любителей Borland. Выбирайте любой из его компиляторов, и вы не ошибетесь. Для вас есть все, что угодно: Delphi, JBuilder, Kylix или C++ Builder. Как видите, у корпорации Borland есть визуальные варианты всех языков, и они действительно лучшие.
Как ранее уже говорилось, самая лучшая технология программирования это та, в которой поддерживается визуальность. Ваша среда разработки просто обязана быть визуальной, потому что за этим наше будущее. Если вы хотите получить визуальность плюс мощь разработки — ваша среда от Borland. С помощью языков этой фирмы можно сделать абсолютно все. Так что с этим мы покончили. Вердикт окончательный и обжалованию не подлежит.
Осталось только ответить на вопрос: "Какой язык программирования лучше?" Я уже несколько лет пытаюсь ответить на этот вопрос, но окончательного решения вынести не могу. Даже у того же Visual C++ от Microsoft есть свои плюсы. Как это ни странно, но положительные стороны есть у всех. Вопрос остается только за тем, какие программы будут создаваться? Здесь можно дать примерно такую градацию:
Если вы будете писать базы данных, программы общего значения или утилиты, то ваш язык Delphi или C++ Builder.
Если это игры, то желательно Msual C++ или Watcome С плюс знание Assembler. Но это не значит, что нельзя использовать Delphi или C++ Builder. В этих средах вы потеряете не намного больше в скорости работы, поэтому в большинстве игр можно не обращать внимания на эту потерю. Если правильно использовать свои знания, то и на самом медленном и слабом языке программирования можно создать шедевр.
Если это будут драйверы и работа с "железом", где критичен размер файла, ваш язык — чистый С или Assembler.
И все же большую массу программ занимают утилиты и базы данных. А тут визуальность необходима, если вы хотите оказаться впереди. Визуальные языки будут жить и за ними будущее. На протяжении всей этой книги будет рассматриваться самый лучший (это на мой взгляд, и он может отличаться от других) язык программирования — Delphi.
Корпорация Microsoft тоже движется в сторону визуальное™ и простоты разработки программных продуктов. Об этом говорит их новая разработка С#. Но этот язык я считаю самой неудачной их разработкой и самым наглым маркетинговым ходом. Поэтому хочу вас предостеречь от бесполезной траты времени на него.
(Совет )
Я не могу описывать здесь как сам этот язык программирования, так и его проблемы. Это выходит за рамки данной книги. Однако советую вам прочитать мою статью о технологии .NET, которая опубликована на сайте (или на диске к этой книге) в разделе "Без рамки".
Исполнение машинных инструкций
Прежде чем переходить к дальнейшему изучению материала, необходимо рассмотреть ряд понятий.
Сегмент — это область внутренней (оперативной) памяти компьютера (внешняя память представлена накопителями на магнитных дисках и лентах). Раньше, когда операционные системы были 16-битными, процессор не мог работать с памятью размером более 64 килобайт (это максимальный размер области памяти, который можно адресовать, используя в этих целях адрес длиной в два байта). Поэтому память делилась на сегменты по размеру и по назначению. На данный момент мы используем 32-разрядную ОС, которая может адресовать до 4 Гбайт оперативной памяти (длина адреса — 32 бита или 4 байта). Поэтому можно сказать, что память стала сплошной. Однако деление ее по назначению все-таки осталось. Существуют следующие сегменты памяти:
О сегмент кода — в эту область памяти загружается машинный код, который будет потом выполняться процессором;
О сегмент данных — это область памяти для хранения данных;
О сегмент стека — область памяти для хранения временных (локальных) данных и адресов возврата из процедур.
Каждой запущенной программе отводится свой сегмент кода, данных и стека. Поэтому данные одной программы не могут пересекаться с данными или кодом другой программы, если, конечно же, не произошел сбой с ошибкой доступа краспределенной области оперативной памяти.
Регистр — ячейка памяти 6 процессоре. Размер ячейки зависит от ее разрядности. В 32-разрядных процессорах ячейки 32-битные. Мы будем говорить о 32-разрядных процессорах, а значит и о 32-битных регистрах. Таких регистров у процессора несколько и каждый из них предназначен для определенных целей.
Когда компьютер был 16-битным, все регистры были тоже 16-битными. С появлением 32-разрвдной платформы размер ячейки регистра тоже увеличился до 32 бит. Для совместимости со старыми программами такие регистры как бы делятся на две части (две ячейки по 16 бит). Первая — определяет старый регистр, а вторая — дополнительные 16 бит. На словах не совсем понятно, поэтому давайте посмотрим на рис. 1.6.
ЕАХ
6―-~
16 бит
16 бит
Рис. 1.6. Программа в машинных кодах и Assembler
На этом рисунке показан регистр ЕАХ. Полная его длинна 32 бита, но младшая половина — это регистр АХ (16-битный вариант регистра). То есть если мы "попросим" процессор показать нам содержимое регистра АХ, то мы увидим половину регистра ЕАХ. Иногда это очень удобно, особенно когда требуется прочитать только половину числа из регистра.
Рассмотрим пример. Пусть в регистре ЕАХ находится шестнадцатеричное число #21CD52B, тогда в регистре АХ будут находиться последние 16 бит, а именно число #D52B.
Теперь рассмотрим описание регистров процессора. При этом будем иметь в виду следующее: если имя регистра буцет начинаться с буквы "Е\ это значит, что он 32-битный и для него существует и 16-битный вариант без буквы ” Е". Все регистры можно разделить на несколько групп.
П Сегментные регистры — CS, DS, SS и ES (в данную группу входит еще ряд регистров, но нас пока интересуют только эти).
Регистр CS — регистр сегмента кода, в нем хранится начальный адрес сегмента кода.
Регистр DS — регистр сегмента данных, в нем хранится начальный адрес сегмента данных. В этом сегменте располагаются глобальные переменные.
Регистр SS — регистр сегмента стека, в нем хранится начальный адрес сегмента стека. Здесь располагаются локальные переменные.
Регистр ES — регистр дополнительного сегмента. Применяется при использовании в программе дополнительного сегмента.
Примечание
Разницу между глобальными и локальными переменными мы рассмотрим чуть позже, когда будем изучать сам язык программирования. Сейчас я еще скажу только то, что в сегменте сохраняются и переменные, которые передаются в процедуры, и адрес возврата из процедуры (куда нужно вернуться по окончании выполнения кода процедуры).
Регистры общего назначения EAX, ЕВХ, ЕСХ и EDX, все эти регистры 32битные. В 16-разрядных процессорах они были 16-битными и назывались АХ, ВХ, СХ и£Ж Все регистры могут использоваться в программе по собственному усмотрению программистов, но в некоторых случаях им отведена определенная роль. В регистр ЕАХ в основном записываются результаты арифметических вычислений, а регистр ЕСХ используется в качестве счетчика. Регистр EDX используется для хранения результатов вычисления.
К Прммечанме
Очень часто, прежде чем выполнить какую-то команду, процессор загружает необходимые данные в регистры и только после этого выполняет необходимую инструкцию. Но возможны варианты, коща вычисления идут с использованием прямого обращения в память.
П Регистровые указатели ESP, ЕВР.
ESP — это указатель стека, который обеспечивает его использование в памяти.
ЕВР — указатель, который обеспечивает доступ к данным и указателям на данные, переданные через стек.
П Индексные регистры ESt и EDI. Эти регистры используются при сложении и вычитании, а также для расширенной адресации.
Если вы собираетесь писать простые утилиты или базы данных, то эти знания вы не будете использовать. Однако если вы хотите пойти дальше, то желательно не просто знание, но и понимание принципов организации работы процессора при выполнении программы. В связи с этим попробуем объяснить процедуру обработки процессором данных на примере.
При старте программы исполняемый код загружается в сегмент кода. Регистр CS сразу устанавливается в значение, указывающее на начало этого сегмента. Данные программы загружаются в сегмент данных (это константы и любые другие дополнительные данные). На этом же этапе происходит подготовка (инициализация) к работе сегмента стека. Если программе переданы какие-нибудь значения, то они автоматически заносятся в стек.
Сегмент кода содержит код только одной программы. В один сегмент не может быть загружен код двух абсолютно разных программ. Точно так же используются сегмент данных и сегмент стека. При каждом старте новой
22.Глава 1 программы опер ационная система отводит ей свои собственные сегменты кода, данных и стека.
После этих подготовительных действий ОС готова к выполнению кода. Вспомним, что регистр CS указывает на начало сегмента кода. Есть еще один регистр, о назначении которого в примере еще не говорилось. Это регистр EIP (счетчик адреса команды). Данный регистр указывает на адрес текущей команды в сегменте кода.
Процессор последовательно вьшолняет все команды, находящиеся в сегменте кода, ориентируясь при выборе на занчение EIP. Иногда необходимо изменить естественный (последовательный) порядок следования команд, т. е. перейти к выполнению команды, которая не является очередной. В этом случае такой переход происходит, и команды продолжают последовательно выполнятся, но уже начиная с новой команды, на которую произошел переход.
Любые операции вычисления могут производиться с регистрами и с памятью. Например, к значению регистра ЕАХ можно прибавить значение ЕВХ — это сложение регистров. Можно складывать и значения, находящиеся в оперативной памяти. Но надо помнить, что вычисления с регистрами происходят намного быстрее, потому что регистр — это та же память, только находящаяся в процессоре, и процессор работает с ней намного быстрее. Если вы собираетесь произвести с одним и тем же числом две операции, то намного эффективнее будет располагать его в регистре.
Глава 2
Машинная математика
До начала перестройки в нашей стране довольно мало внимания уделялось подготовке программистов. Большинство их были выходцами с кафедр математики, на которых очень часто изучались определенные учебные дисциплины с уклоном в сторону информатики. На этих курсах учили писать блок- схемы, которые в свою очередь помогали понять логику работы программы.
С fiw/с-схемами знакомятся на начальном этапе обучения программированию. Первое впечатление тех, кто столкнулся с б/то/с-схемами, — абсолютно ненужная ерунда. Однако со временем становятся понятны их достоинства. Возможно, что и вам покажется это слишком просто, но все же желательно прочитать эту главу полностью. Здесь рассматривается теория всего процесса программирования, что позволяет понять логику вьшолнения команд в процессе реализации программы на процессоре. В дальнейшем останется только познакомитюся с практикой.
Основы машинной математики
В любом языке программирования можно выполнять математические операции любой сложности. Delphi в этом вопросе не исключение. Но пока мы не будем рассматривать все возможности этого языка в данной области, а остановимся только на математических операциях, чтобы сразу привыкать к математике Delphi. В табл. 2.1 представлены основные математические операции языка Delphi.
Таблица 2.1. Основные математические операции Delphi
Математическая операция
Описание
Математическая операция
Описание
*
Умножить
+
Сложение
/
Разделить
Вычитание
Sqr
Квадрат
Присвоить значение
Sqrt
Квадратный корень
2 Зак 942
Все эти операции вьшолняются в том же порядке, в котором перечислены. При этом соблюдается приоритет операций (приоритет умножения, деления и т. д. выше приоритета сложения и вычитания). Например, результатом вычисления выражения 2+2*2 будет 6, потому что сначала выполняется операция умножения, а потом сложения. Если вы хотите сначала вьшолнить сложение, а потом вычитание, то, как и в простой математике, нужно использовать скобки (2 +2) *2=8. В этом случае результат будет совершенно другим.
Для изучения компьютерной математики необходимо уяснить ряд понятий, и начнем мы с понятия переменной.
Переменная — это ячейка оперативной памяти, в которую можно записывать различные значения. Чаще всего этой ячейке памяти ставится в соответствие какое-нибудь имя. Например, можно определить переменную с именем F. Ей, в свою очередь, можно присваивать значения, например 5. Для этого достаточно записать выражение F:=5. Последовательность символов — знак двоеточия и равно — означает здесь операцию "присвоить".
Почему для присвоения значения используется именно :=, тогда как мы привыкли к простому знаку равенства? Это необходимо, чтобы отделить операцию присваивания от операции сравнения. Знак равенства в Delphi используется для сравнения чисел, а := для присваивания значения переменным.
Значения переменных можно копировать. Допустим, имеется еще одна переменная G. Ей можно присвоить значение переменной F с помощью простого присваивания G:=F. После этого в переменной G тоже будет значение 5.
Переменной можно присваивать результаты каких-то вычислений, например: F:=10/2. Это достаточно простой пример. А вот уже целое выражение с использованием переменных:
F:=5;
G:=10;
F:=G/2.
Имя переменной может состоять как из одной, так и из несколысих букв. Например, переменная может иметь имя str или МуРегешеп. Единственное ограничение, которое следует здесь учитывать — это то, что имя должно состоять из английских букв и не должно содержать зарезервированные слова (о зарезервированных словах будет сказано немного позже). Вы также можете в имени переменной использовать числа (желательно в конце), например Strl,Str2, Str3 ИТ. Д.
( Совет )
Назначайте переменным осмысленные имена Когда вы начнете писать большие программы, тяжело будет разобраться, что означает переменная, напри- мер,1 илиь.
Tun переменной — это тип значения, которое можно присвоить переменной. Очень часто используют термин тип данных, потому что это действительно тип данных, хранящихся в переменной. Он показывает, какого типа информация может быть присвоена переменной. В Delphi принято обязательно указывать типы переменных, чтобы сразу можно было определить, какую информацию можно туда записать, а какую нет.
Существует несколько основных типов переменных, которые на данный момент времени необходимо четко представлять.
В табл. 2.2 приведены только основные типы данных. Реально их намно1ч) больше. Когда мы перейдем к программированию, вы познакомитесь с большим количеством типов.
Таблица 2.2. Основные типы данных в Delphi
Название типа
Описание
Дополнительная информация
Integer
Целое число
Переменная этого типа может принимать в качестве значения любые целые числа, как положительные, так и отрицательные
Real
Вещественное число
Переменная этого типа может принимать в качестве значения целые и дробные числа со знаком и без
String
Строка
Переменная этого типа может принимать в качестве значения любые символы и наборы символов
Boolean
Булево значение
Переменная может принимать значение true или false (истина или ложь). Этот тип очень часто используется для организации логики
Строки — это любые символы или наборы символов. В языке Delphi они , Строки так же выделяются одинарными кавычками, например, ' привет1.上
можно присваивать переменным, как и любое другое значение. Например:
Str — строковая переменная.
Str:=гПривет!i!'
На этом основные сведения о машинной математике подошли к концу. Пора применить полученные знания на практике.
Блок-схемы
Давайте сразу зададим какой-нибудь простой пример, на котором попробуем определить логику его решения на процессоре. Допустим, нам надо получить произведение двух чисел.
В логике человека мы должны выполнить следующие операции:
Листинг 2Л. Произведение двух чисел
Старт.
Ввести число 1.
Ввести число 2.
Уе.шожить число 1 на число 2.
Вывести результат.
Это простейшая и подробная логика, которой оперирует человек. Но машина так не может мыслить и по ее логике нужно рассуждать немного иначе. Для отображения машинной логики определение вычислительных шагов неудобно, потому что решение может быть извилистым, а не прямолинейным. Поэтому давайте знакомиться с блок-схемами на этой линейной задаче.
Блок-схемы принято чертить различными квадратами, овалами и прямоугольниками .Я особо не буду придерживаться стандартов, потому что это непринпипиально в решении, но некоторых особенностей мы будем придерживаться. Основные типы блоков, которые используются для формирования блок-схемы, можно увидеть на рис. 2.1.
Начало работы
Данные
Процесс
ЛогикаЛогикаВвод данныхВвод данныхЗапись/чтение дискаЗапись/чтение дискаВывод на экранВывод на экранРис. 2.1. Основные фигуры блок-схемыРис. 2.1. Основные фигуры блок-схемы
Логика
Логика
Ввод данных
Ввод данных
Запись/чтение диска
Запись/чтение диска
Вывод на экран
Вывод на экран
Рис. 2.1. Основные фигуры блок-схемы
Рис. 2.1. Основные фигуры блок-схемы
Есть и другие, более изощренные блоки, но ими мы не будем пользоваться. Большинство блоков будут оформляться, как простой прямоугольник, потому что от формы блока суть особо не изменится. Самое главное (на мой взгляд) — выделить отдельным видом блока начало блок-схемы и ее логику. Все остальное можно оформить однообразно, наглядность от этого пострадает, но не сильно.
Итак, наша первая блок-схема умножения двух чисел будет выглядеть так, как это показано на рис. 2.2.
Рис. 2.2. Блок-схема умножения двух чисел
На первый взгляд все слишком сложно. Но это только первый взгляд. Реально здесь ничего сложного нет, просто очень громоздко и простейшая операция перемножения чисел превращается в несколько операций. Но все же надо объяснить происходящее подробнее, чтобы мы могли продвинуться дальше и разобраться с более сложными примерами. Для этого дадим характеристику каждого блока в отдельности.
口 Первый блок — это начало. Если вы собираетесь строить блок-схемы, то обязательно указывай его, чтобы сразу можно было увидеть начало логики.
口 Второй блок — перечисляет переменные, которые нам нужны для вычислений. В примере используются три переменные R, С1 и С2. В переменную R будет помещен результат вычисления. Переменные С1 и С2 используются для хранения введенных данных. Здесь пока не указывается тип этих данных, но подразумевается, что это будут или целые числа, или вещественные.
口 Третий 'блок — определяет ввод исходных значений переменных. Здесь показывается, что надо ввести значения переменных С1 и С2.
О Четвертый блок — указывает на необходимость произвести умножение С1 на С2 и результат записать в переменную R.
П Пятый блок — вывод результата на экран.
Это простая блок-схема, в которой нет ничего особенного. Следующие примеры будут использовать уже логику, поэтому и блок-схемы будут более сложными. Однако на этих примерах вы сможете ощутить всю прелесть машинной математики и понять ее логику.
Машинная логика и циклы
В любом языке программирования есть множество операций сравнения, которые позволяют реализовать машинную логику. Что понимается под логическими операциями? Это операции сравнения на "равенство**, "больше** или "меньше**. Как показано в табл. 2.3, таких операций может быть достаточно много.
Таблица 2.3. Логические операции
Математическая операция
Описание
Математическая операция
Описание
=
Равно
>=
Больше либо равно
>
Больше
<=
Меньше либо равно
<
Меньше
О
Не равно
Давайте рассмотрим простейшую логику компьютера на примере расчета значения факториала. Факториал — это произведение чисел от 1 до какого- то числа. Например, факториал 5 равен 1х2хЗх4х5 = 120. Факториал обозначается как знак восклицания "!".
Давайте напишем алгоритм в виде блок-схемы (рис. 2.3).
2345623456
2
3
4
5
6
2
3
4
5
6
Рис. 2.3. Блок-схема расчета факториала
Представим, что мы не знаем числа, факториал которого мы должны вычислить. Пусть это число будет вводить пользователь. Поэтому расчетная формула выглядит, как число F факториал (F!). В этом случае надо перемножить все числа от 1 до F, т. е. 1 х 2 х 3 х *4 х ** х F. Перемножение можно делать последовательно. Умножить 1 х 1. Затем проверить, не превысили ли мы число F. Если нет, то результат прошлого вычисления надо умножить на 2 и снова сделать проверку. Если не превысили F, то снова умножить результат прошлого вычисления на 3. И т. д.
Теперь постараемся объяснить назначение каждого блока в блок-схеме, чтобы было понятно, как она работает. В данном случае это очень важно. Работа блок-схемы очень похожа на то, как "мыслит” компьютер, выполняя заданную программу. Именно так вы должны будете размышлять, коща начнете писать свои первые программы.
Итак, давайте рассмотрим назначение каждого блока в отдельности:
Это начало.
1
Объявление трех переменных: F, R и index. В переменной F будет раниться число, факториал которого надо вычислить. Переменная R используется для хранения результата вычислений. И наконец, в переменной index — будет содержаться счетчик вычислений.
В этом блоке происходит ввод числа, факториал которого надо вычислить. Если пользователь ввел 5, то нам нужно вычислить 5!.
Здесь задаются начальные значения переменным. На этом этапе устанавливается значение счетчика и начальный результат вычислений равными
Почему именно 1? Да потому, что нам нужно перемножить все числа, начиная от 1, до числа, факториал которого вычисляется.
В этом блоке проверяется счетчик (Endex). Проверка заключется в определении, превысило ли его значение число, которое ввел пользователь, или нет? Если "нет”,надо перейти на блок 6. Допустим, пользователь ввел число 5. Значение счетчика пока равно 1. 1 меньше 5, значит, должен произойти переход на блок 6.
Здесь вычисляеся результат r:=r*index. На этом этапе index = 1 и R = 1. Значит, r:=1*1. Результат будет 1, а значит, в R опять будет единица. Далее, мы увеличиваем index на 1 (index: =index+l), после чего он становится равным 2 (index: =1+1), и снова возвращаемся на блок 5. В нем опять происходит проверка index>F. index сейчас равен 2, а это меньше пяти. Значит, снова идем на блок 6. Здесь опять производится расчет r:=r*index (R:=l*2), после чего R становится равным 2. Опять увеличиваем счетчик (index : =index+l), и index становится равным 3. Снова переход на блок 5.
Я не стал дальше расписывать порядок решения данной задачи. Попробуйте сами пройтись по этой блок-схеме до самого конца. Очень важно понять, как она работает.
В описанной блок-схеме была- задействована очень интересная и удобная форма организации вычислений, а именно организация цикла. Цикл — повторяющееся выполнение какого-либо блока. В данном случае несколько раз выполняется шестой блок. Если бы число факториала было известно заранее, можно было бы упростить блок-схему, написав формулу типа R:=T*2*3*4*5. Но мы не знаем число, которое введет пользователь. Поэтому приходится делать цикл и на каждом его этапе вычислять промежуточный результат.
Попробуйте сами написать блок-схему арифметической профессии. Ее формула достаточно проста. В этом случае можно модифицировать блок- схему, которая показана на рис. 2.3. Разница заключается только в том, что в арифметической прогрессии рассчитывается сумма, а не произведение чисел от 1 до определенного числа F.
Нарисуйте эту блок-схему на бумаге и попробуйте мысленно пройтись по ней, выполняя каждый шаг, как это делала бы машина. Если вы думаете, что блок-схемы слишком просты, попробуйте написать что-нибудь более сложное. Это необходимо сделать с целью научиться мыслить как компьютер. Только так вы сможете объяснить ему, что от него требуется, и что самое главное, он вас сможет понять.
Программирование машинной логики
Подошло время превратить нашу логику, описанную в блок-схеме на рис. 2.3, в настоящую программу. Пока эта программа будет существовать только на бумаге, но со временем ее можно превратить в настоящий исполняемый модуль.
Сначала напишем нашу программу на русском языке.
Листинг 2.2. Пример программы, описанной понятным человеку языком
Начало программы. /
Переменные:
F, R, Index - это целые числа;
Начато кода
F:=5;
R:=l;
Index:=1;
От 1 до 5 выполнять
Начало цикла
R:=R*Index;
Index:=Index+l;
Конец цикла
Вывести на экран переменную R.
Конец кола
Если не обращать внимания на все, что написано русским языком, можно считать, что первая программа уже написана. В принципе, программа на любом языке программирования выглядит приблизительно так, как это показано выше, только при пр огр аммировании действуют жесткие правила описания команд. В данном случае наша программа состоит из следующих блоков:
О начало программы;
описание переменных;
начало кода (учтите, что описание переменных — это не код программы);
О заполнение переменных начальными значениями;
запуск цикла от 1 до 5;
выполнение в цикле расчета;
вывод результата.
В принципе, ничего нового здесь нет. Просто блок-схема описана словами, похожими на программный язык. В дальнейшем, коща вы сами начнете писать свой собственный программный код, действовать будете точно так же. Сначала постройте алгоритм программы в виде блок-схемы или хотя бы мысленно представьте алгоритм работы будущей программы и только потом переносите все это в компьютер. Блок-схемы понадобятся только на начальном этапе, когда вы не можете сразу представить себе действия, которые нужно запрофаммировать. Со временем блок-схемы отойдут сами собой, и уже через месяц практического программирования можно будет писать алгоритмы сразу же на Delphi. Все дело в практике.
При написании программы необходимо мыслить точно так же, как это делает машина. В противном случае получится аналогично тому, как если бы вы разговаривали с англичанином на английском языке, но терминами русского языка.
(Совет j
Мало знать английские слова, нужно еще и мыслить как англичанин Я сразу вспоминаю случай, который приключился со мной в Испании. Как-то раз ко мне
подошла девушка и, узнав, что я из России, стала просить у меня деревянную ложку в качестве сувенира. У меня не было ложек, я просто не брал их с собой. Поэтому пришлось сказать ей "No", т. е. "Нет". Я это воспринимал, как ответ: "У меня НЕТ ложки1'. Она меня спрашивала, почему "'НЕТ", понимая мой ответ так, как будто я отказываюсь дать ей эту ложку. На английском языке "NO" — это отрицание, а по нашему "Нет" — это не только отрицание, но и признак отсутствия (например, у меня нет ложки). Если бы я тогда мыслил, как англичанин, то смог бы ответить: "I don't have". Но этому я научился намного позже, тогда я еще очень слабо владел английским языком.
В большинстве случаев машинный язык схож с человеческим, потому что его создавали люди. Но иноща разница чувствительна, потому что машина не всеща может мыслить, как человек. В связи с этим необходимо научиться объяснять свои мысли понятным для машины языком.
Теперь мы готовы перейти к изучению пр огр аммир ования.
Глава 3Глава 3雁
Глава 3
Глава 3
Начальные сведения о Delphi
Delphi — визуальная среда разработки программ. Она прячет от нас все сложности программирования, превращая его в увлекательный процесс. При создании обычных приложений, утилит н программ работы с базами данных вам не придется задумываться о регистрах, стеке и многом другом. Но это не значит, что время, затраченное на изучение внутреннего мира компьютера и принципов организации выполнения программ, пропало зря. Если вы захотите стать профессиональным программистом, вам пригодится все.
В этой главе рассказывается о том,
口 как установить Delphi 7;
□ что входит в комплект поставки;
口 как пользоваться оболочкой Delphi 7.
Те, кто в той или иной степени уже знаком с этим языком программирования, возможно, знают большую часть из того, о чем здесь будет говориться. Но, возможно, и они найдут здесь что-то новое для себя.
Установка Delphi 7
В этом разделе я постараюсь подробно описать ход установки Delphi 7. Он достаточно прост и у большинства не вызывает никаких проблем, но есть люди, которые недавно решили заняться программированием или только начали осваивать компьютер. Нельзя забывать про них, тем более что все мы коща-то были начинающими. Опытных пользователей прошу не ругать меня за трату времени на описание процесса установки.
Итак, вставьте компакт-диск с Delphi 7 в CD-ROM. Если вы не отбслючили автозафузку, то перед вами откроется окно автозапуска (рис. 3.1). Выберите в этом окне пункт Delphi 7. Если автозапуск отключен, раскройте содержимое диска и в его корневом каталоге запустите профамму install.exe или из папки Install программу setup.exe.
Рис. 3.1. Автозапуск программы установки
Примечание ;
В Delphi 6 установка практически не отличается от 7 версии, даже картинка автозапуска такая же, отличие только в номере версии.
После выполнения указанных действий запустится программа установки Delphi 7. Давайте последовательно рассмотрим каждый шаг процесса установки. Я буду последовательно пояснять каждое окно. Вы должны выполнять все действия, которые буду здесь описываться, после чего необходимо нажать кнопку Next (Далее), чтобы перейти к следующему шагу установки.
Некоторых из описываемых здесь шагов может и не быть. Это связано с тем, что процесс установки рассматривается на примере Enterprise Edition (Промышленная редакция) версии Delphi. В Standard (Стандартная) или Professional (Профессиональная) версиях некоторые компоненты отсутствуют, поэтому при их установке некоторых окон не будет. Итак, начнем рассмотрение оконного интерфейса программы установки.
Окно приветствия программы установки Borland Delphi 7 для Enterprise Edition показано на рис. 3.2. Окно ввода серийного номера будет следующим. Если у вас не пиратская версия (бывают и такие), то вместе с диском должен идти серийный номер (Serial Number) и код авторизации (Authorization Key). Если версия неофициальная, то тут за установку я не могу ничего сказать по двум причинам: она бывает разная и такую установку пиратских программных продуктов описывать в книге запрещено.
Рис. 3.2. Окно приветствия программы установки
zJzJ
zJ
zJ
亡 Sorland Delphi 7 Ent*fprise Edilron ' Inxtallation Wizard
License Agreement
Please read the following license agreement carefully,
BORSND SOFTWARE CORPORATION LICENSE TERMS
DELPHI 7 ARCHITECT or ENTERPRISE
NOTICE: THIS BORLAND SOFTWARE PRODUCT (TOGETHER WITH ITS ACCOMPANYING DOCUMENTATION. THE "PRODUCT") IS THE PROPERTY OF BORLAND SOFTWARE CORPORATION ("BORLAND"). THE PRODUCT IS MADE AVAILABLE TO YOU, THE ORIGINAL PURCHASER, SUBJECT TO THE FOLLOWING LICENSE AGREEMENT ("LICENSE"). PLEASE READ THIS LICENSE CAREFULLY BEFORE 1КЮТА1 I QMC nn “C4ZL ТМС nnnni ICT A CCC" C匚 TUK?
Г 扌 胸家亜 twirs jn 侬側&^ 変受姪目
「I do not accept the terms in the license agreement
< Back I №* >
Cancel
Следующее окно, как показано на рис. 3.3, содержит лицензию на использование Delphi 7. Все лицензии приблизительно одинаковые, поэтому ее можно не читать. Если хотите (и знаете английский язык), можете прочитать. Если не хотите, то просто выделите пункт I accept the terms in the license agreement (Я принимаю условия лицензионного соглашения) и нажмите кнопку Next.
Окно, в котором описаны некоторые замечания по установке Delphi 7. Опять же, ничего особенного здесь не описано. Если вы обладаете хорошими знаниями английского, то можете прочитать. Если нет, то и переводить не стоит.
Следующее окно — выбор типа установки. Здесь доступны три типа (рис. 3.4).
Typical — типичный. Устанавливается то, что посчитали нужным разработчики.
Compact — компактный. Устанавливается необходимый минимум.
Custom — выборочный. Пользователь сам может выбрать, что ему нужно устанавливать на свой компьютер, а что нет.
Рис. 3.4. Окно выбора типа установки
< Совет )
Я предпочитаю настраивать установку необходимого программного обеспечения самостоятельно, поэтому всегда выбираю Custom и вам советую. В этом примере будет описываться именно этот тип установки. Если вы выберете другой, то просто избавитесь ст выбора компонентов.
После нажатия кнопки Next перед вами может появиться такое окно, как показано на рис. 3.5.
Рис. 3.5. Установка системного отладчика
В окне написано предупреждение, что в системе нет или установлен другой отладчик запущенных программ. Вы наверно не раз уже видели окна типа —— Программа вьшолнила недопустимую операцию и будет закрыта. В этом окне бывает три кнопки: Закрыть, Отладка и Помощь. Если вы нажмете кнопку Отладка, то должен будет загрузиться отладчик, в котором можно просмотреть, из-за чего произошла ошибка. Правда, тут без основательных знаний языка Assembler не обойтись.
Так вот, предупреждение, о котором здесь вдет речь, гласит, что Delphi хочет стать отладчиком в системе. После этого, если вы нажмете кнопку Отладка, в окно сообщения об ошибке загрузится Delphi и позволит вам отладить программу, которая вызвала эту ошибку. Можете выбрать кнопку Да, если хотите использовать для отладки Delphi или Нет в противном случае.
В следующем окне производится выбор всех компонентов, которые нужно устанавливать. Для выбора просто щелкните кнопкой мыши около нужного компонента и в появившемся меню выберите один из пунктов.
Install on Local Hard Drive — инсталлировать на локальный жесткий диск.
Install Entire Feature on Local Hard Drive — инсталлировать все возможности на локальный жесткий диск. В принципе, этот пункт не отличается от предыдущего. Просто если вы выберете его у заголовка ветви, будет выбрана вся ветвь компонентов.
Do not Install — не инсталлировать.
По умолчанию выбирается Install Entire Feature on Local Hard Drive.
Рис. 3.6. Окно выбора компонентов
Давайте рассмотрим компоненты, которые становятся здесь доступными. На рис. 3.6 они оформлены в виде дерева, так же, как и представлены в окне. Те компоненты, которые устанавливать необязательно, будут указываться отдельно.
Program Files — это заголовок ветки, в которой находятся необходимые для установки Delphi компоненты.
Main Program Files — основные файлы Delphi, в том числе и сама визуальная оболочка, в которой вы будете писать программы.
Image Editor — программа (графический редактор) для редактирования иконок, курсоров и ресурсов.
SQL Builder — программа для визуального создания SQL-запросов (это запросы к базам данных). Можете ее не устанавливать, толку от этой утилиты мало и поэтому она не будет описываться.
WinSlght32 — программа для наблюдения за системой. Можете ее не устанавливать.
Sample Programs — примеры программ, написанных на Delphi.
VCL Source — исходные коды библиотеки VCL (Visual Component Library — визуальная библиотека компонентов). Полезна только для
опытных пользователей. Начинающий не сможет разобраться в исходных кодах VCL.
Internet Controls — компоненты для работы с Internet.
Sample ActiveX Controls — простые компоненты ActiveX (компоненты, созданные на основе технологии СОМ от Microsoft).
Debug Library Files — файлы библиотеки отладки программ.
ITE — компонент многоязычной поддержки.
DbExpress — компоненты для доступа к базам данных.
Microsoft Office Controls — компоненты доступа к Microsoft Office.
Help Files — файлы помощи Delphi.
Indy — низкоуровневые компоненты для работы с Internet.
Shared Files — разделяемые файлы.
Microsoft SDK Help Files — файлы помощи Microsoft. Здесь в основном помощь по Win А Р/( Windows Application Programming Interface — функпии ОС Windows) и, другим технологиям Microsoft.
Image Files — набор картинок, иконок и курсоров.
Sample Data Files — базы данных, которые используются в примерах, поставляемых с Delphi. Эти базы в описании книги использоваться не будут, поэтому нам они не нужны. Если вы будете самостоятельно разбираться с примерами из Delphi, возможно, они вам понадобятся.
Debugger — отладчик приложений.
BDE — библиотека доступа к базам данных. Она старая и работает нестабильно, поэтому ее использовать не рекомендуется. Вместо этого мы будем работать с ADO и DbExpress. В книге, однако, дается описание этой технологи, потому что при организации доступа к старым базам данных, таким как Paradox и DBF, она работает нормально.
Database Desktop — программа для создания баз данных. Этой программой хорошо создаются только старые базы данных (Paradox, DBF).
Если вы выбрали установку BDE, то следующим окном у вас будет выбор баз данных, поддержку которых вы хотите увидеть (рис. 3.7). Несмотря на то, что в этом списке есть все современные базы данных, я не советую вам организовывать доступ к ним через BDE. Она работает нестабильно, и сама фирма Borland согласна с этим.
(Совет )
Начиная с Delphi 6, фирма Borland включила в поставку очень хорошую и удобную библиотеку dbExpress. Именно ее желательно использовать для доступа к базам данных. Еще можно использовать ADO. В этой книге дается описание всех этих технологий. В любом случае, если вы решили установить на всякий
случай BDE, то выберите драйверы, которыми вы будете пользоваться. В списке в основном показаны редкие вещи, которые у вас могут быть не установлены, поэтому можете оставить неизвестные вам базы данных по умолчанию. Единственное, что надо правильно указать — какая версия DAO будет использоваться. Вам могут быть доступны: DAO 3.0 (используется в MS Office 95), и DAO 3.5 (используется в MS Office 97). Обязательно укажите правильную версию установленного у себя пакета программ MS Office. Если установлен Office 2000, укажите DAO 3.5 (MS Access 97) Driver.
Рис- 3.7. Окно настроек драйверов баз данных
Следующее окно (рис. 3.8) предлагает установить программы CORBA (Технологияраспределенныхвычислений). Этатехнология программирования не будет рассматриваться в данной книге, поэтому программы можете не устанавливать и убрать выделение с Use VisiBroker / CORBA Support. В Delphi 6 в этом окне для отключения CORBA нужно выбрать No VisiBroker / CORBA Support.
Выбор версии компонентов Microsoft Office производится в окне, показанном на рис. 3.9. Если вы выбрали установку Microsoft Office Controls, то здесь необходимо указать версию компонентов.
Следующее окно зависит от версии Delphi. В Delphi 5 это дополнительная лицензия. Можете почитать, что тут написано, но ничего нового не узнаете. Просто выделите пункт I accept the terms in the license agreement (Я принимаю условия лицензионного соглашения) и под лицензионным соглашением нажмите кнопку Next.
Рис. 3.8. Окно выбора CORBA
Рис. 3.9. Выбор версии компонентов Microsoft Office
Очередным окном может быть окно запроса (рис. 3.10) на установку Interbase Client (драйверы, нео бходимые для подключения к серверу баз данных InterBase), а также установку или обновление Microsoft Data Access Components (компоненты доступа к данным от Microsoft). Первую установку можно отключить, если вы не будете работать с базами данных InterBase, а вот вторую желательно включить, потому что эти компоненты используются в ADO.
Рис. 3.10. Установка InterBase Client и установка или обновление компонентов доступа к данным
В этом окне вы можете определить пути, по которым будут установлены различные компоненты Delphi. Если достаточно свободного места на системном диске, желательно все установить на этот диск. Хотя при использовании и других дисков проблем не замечалось.
Здесь имеются четыре варианта задания пути установки:
Program Files — путь куца будут установлены программные файлы;
Shared Files — путь куда будут установлены разделяемые файлы;
BDE and SQL Links — путь куда будет установлен BDE;
Database Desktop — путь куца будет установлен Database Desktop.
Как видите, здесь разделение такое же, как и при выборе необходимых при установке компонентов. Если при выборе компонентов не выбиралась установка BDE, то и соответствующего пути в этом окне (рис. 3.11) не будет.
Рис. 3.11. Окно выбора пути установки
В следующем окне появится запрос: Сохранять результат установки на жестком диске?. Фирма Borland рекомендует делать это. Однако, если у вас проблемы с наличием свободного дискового пространства, можно убрать выделенный здесь флажок и сэкономить около 5 мегабайт памяти на жестком диске.
Данное окно уведомляет о том, что сейчас начнется установка на меткий диск Delphi 7. Для выполнения данного действия просто нажмите кнопку Next. После этого начнется копирование выбранных файлов на ваш компьютер. Подождите немного, установка не такая уж и долгая, все зависит от скорости привода используемого CD-ROM и компьютера.
Замечание по установке в Windows 如00
В Endows 2000 иногда возникают проблемы с установкой. Особенно это проявилось для Delphi 5. При попытке запустить setup.exe программа просто
не запускалась, не выдавая никаких сообщений. Эго связано с неправильной обработкой пути временных каталогов.
Для исправления ошибки нужно щелкнуть правой кнопкой мыши по иконке Мой компьютер и выбрать в появившемся меню пункт Свойства. Перед вами откроется окно Свойства системы. В этом окне перейдите на вкладку Дополнительно и нажмите на кнопку Переменные среды. В результате проделанных действий откроется одноименное окно (рис. 3.12).
ОбщиеОбщиеСетевая идентификация 1 ОборщовамиеСетевая идентификация 1 ОборщовамиеПрофили пользователейПрофили пользователейДополнительноДополнительно
Общие
Общие
Сетевая идентификация 1 Оборщовамие
Сетевая идентификация 1 Оборщовамие
Профили пользователей
Профили пользователей
Дополнительно
Дополнительно
Y ыстродействие;::::
Параметры быстродействия определяют, как приложения используют память, что влияет на скорость работы компьютера
Параметры быстродействия..
П^земенные среды—^
Переменные среды.Переменные среды.По племенным среды определяется, где хранится определенная информация.По племенным среды определяется, где хранится определенная информация.Рис. 3.12. Окно переменных среды WindowsРис. 3.12. Окно переменных среды Windows
Переменные среды.
Переменные среды.
По племенным среды определяется, где хранится определенная информация.
По племенным среды определяется, где хранится определенная информация.
Рис. 3.12. Окно переменных среды Windows
Рис. 3.12. Окно переменных среды Windows
Здесь находятся настройки всех путей в системе. На первый взгляд все нормально, но если дважды щелкнуть по какому-либо пути, появится окно его редактирования, в котором путь выглядит совершенно по-другому, например, %USERPROFILE%\Local Settings'Temp. Первую часть этой записи, а именно — %USERPROFILE%, не может понять инсталлятор в Delphi. Измените эту часть на нормальную запись, т. е. пропишите все пути к Тшр и Тетр. Только обязательно указывайте на существующий каталог, иначе некоторые программы не смогут запускаться.
Эта проблема проявляется не только в Delphi, но и в большинстве программ, собранных инсталлятором InstallShield старой версии.
В Delphi 6 эта проблема пока не замечалась. Пока установка производилась на все компьютеры без проблем.
Оболочка Delphi 6
Пора в первый раз запустить Delphi. Для этого выберите пункт меню Пуск | Про1раммы I Borland Delphi (Delphi. Меню для запуска Delphi 6 показано на рис. 3.13.
j Sodccd Delphi 6j Sodccd Delphi 6ДокументыДокументыНастройкаНастройкаНа 讦тнНа 讦тнСправкаСправкаВьтгмитъ …Вьтгмитъ …^пуск「^пуск「fes^ оfes^ оЗавершение работы...Завершение работы...Microsoft SQL ServerText&GraphСтандартныеPROMT FamilyMicrosoft AccessMicrosoft ExcelMicrosoft Word. УMicrosoft SQL ServerText&GraphСтандартныеPROMT FamilyMicrosoft AccessMicrosoft ExcelMicrosoft Word. У
j Sodccd Delphi 6
j Sodccd Delphi 6
Документы
Документы
Настройка
Настройка
На 讦тн
На 讦тн
Справка
Справка
Вьтгмитъ …
Вьтгмитъ …
^пуск「
^пуск「
fes^ о
fes^ о
Завершение работы...
Завершение работы...
Microsoft SQL Server
Text&Graph
Стандартные
PROMT Family
Microsoft Access
Microsoft Excel
Microsoft Word
. У
Microsoft SQL Server
Text&Graph
Стандартные
PROMT Family
Microsoft Access
Microsoft Excel
Microsoft Word
. У
Рис. 3.13. Запуск Delphi 6
После запуска откроется окно, показанное рис. 3.14.
В главном окне будет сформировано пять основных окон, с которыми вы будете постоянно работать.
Главное окно программы. В нем находится основное меню, панели инструментов и палитра компонентов.
Рис. 3.14. Главное окно Delphi 7
Объектный инспектор. Он предназначен для управления объектами и состоит из двух вкладок.
Properties — свойства. На этой вкладке будут перечислены свойства выделенного объекта. Имя и тип выделенного объекта отображаются в вьшадающем списке, вверху окна.
Events — события. Здесь можно создавать и изменять реакцию объекта на различные события, которые возникают в компьютере.
Форма. Это уже готовая визуальная форма будущей программы. На ней мы будем размещать компоненты пользовательского интерфейса программы.
Редактор кода. В этом окне вы будете писать программу на языке Delphi.
Дерево компонентов. Эго окно появилось в Delphi 7 и, на мой взгляд, оно является лучшим новшеством этой версии. С его помощью легко находить компоненты, потому что они расположены в виде дерева. Если у вас какой-то компонент будет полностью перекрывать другой, то вы можете выделить верхний компонент, а потом, в дереве компонентов, легко найти тот, который находится снизу.
Менеджер проектов. При первом запуске программы вы не увидите этот менеджер. Обратиться к нему можно из меню Project (Проект), выбрав пункт меню Project Manager (Менеджер проектов). Это окно очень полезно, поэтому я решил познакомить вас с ним уже сейчас.
Главное меню
Давайте рассмотрим основное меню Delphi 7. Оно достаточно сильно отличается от предыдущих версий за счет некоторых усовершенствований. Рассмотрение начнем с основных пунктов, которые нам понадобятся в ближайшее время.
В позиции меню File (Файл) можно увидеть, как показано на рис. 3.15, сле
дующие пункты.
j File Edit Search View Eroject Run Component DatQl W Application
百 Open ..IP CLX Application
呷 Open Project...Ctrl+Fll 鱼 Data Module
Reopen> LI Form
S 8 с с h"丑・良導S 8 с с h"丑・良導□ F=ran>e
S 8 с с h"丑・良導
S 8 с с h"丑・良導
卫 Unit
D Other...
^ UseUnit... Alt+Fil
昌Print…
XlExit
Рис. 3.15. Меню Rie оболочки Delphi
О New — создание нового проекта, формы или шаблона. Если вы наведете курсор на этот пункт, перед вами раскроется подменю, в котором можно увидеть следующие варианты его реализации.
О Application — создать новое приложение.
О CLX Application — создать новое CLX-приложение.
О Data Module — модуль данных.
О Form — создать новую форму.
О Frame — создать новый кадр.
О Unit — создать новый модуль.
口 Open — открыть существующий файл, поддерживаемый Delphi.
口 Open Prefect — открыть существующий проект.
口 Reopen — повторно открыть проект, который недавно открывался.
口 Save — сохранить текущий модуль.
口 Save As — сохранить текущий модуль под новым именем.
口 Save Project As — сохранить проект под новым именем.
口 Save All — сохранить все.
О Close — закрыть текущий модуль.
口 Close All — закрыть все.
О Use Unit — использовать модуль.
Print — печатать модуль.
口 Exit — выход.
В тексте несколько раз употреблялся термин модуль. Поэтому необходимо- пояснить, что это такое. Модуль — это файл, содержащий код программы или часть кода. Чаще всего это простой текстовый файл с расширением pas. Сейчас под модулем стали понимать н,файлы, содержащие визуальную часть программы. Дело в том, что хотя код и визуальная часть хранятся в разных файлах, они неразделимы.
Позиция меню Edit (Редактирование) содержит все основные команды редактирования текста, плюс специфичные команды работы с визуальными объектами. Если вы работали с векторной графикой или текстовыми редакторами, то для вас не составит проблем разобраться с ними.
Сейчас не будем рассматривать все команды этого меню, потому что все равно они не запомнятся. Вы смоете их запомнить, только если будете все зубрить. Но это глупо. Когда мы перейдем к практике и начнем писать программы, все само собой отложится в памяти. Поэтому перечислим только некоторые команды редактирования текста.
Undo — отменить последнюю операцию ввода текста.
Redo — повторить последнюю операцию ввода текста.
Cut — вырезать выделенный текст в буфер обмена.
Сору — копировать выделенный текст в буфер обмена.
О Paste — вставить в текущую позицию курсора содержимое буфера обмена.
Delete — удалить выделенный фрагмент.
Select All — выделить все.
Для выделения текста в редакторе надо установить курсор на начало выделяемого фрагмента, нажать клавишу <Shift> и стрелками на клавиатуре перевести курсор в позицию конца выделяемого фрагмента.
Остальные команды рассматривать пока не будем, потому что это только забьет голову лишней информацией. Того, что было сказано, достаточно, а остальное мы рассмотрим по мере надобности.
Настройка Delphi 6
В Delphi достаточно много настроек и все их знать не обязательно. Мы познакомимся только с теми, которые действительно могут на что-то повлиять или что-то улучшить.
Для изменения основных настроек нужно выбрать из меню Tools (Инструменты) пункт Environment Options (Настройки окружения). Перед вами откроется окно, показанное на рис. 3.16.
Рис. 3.16. Окно настроек окружения
На вкладке Preferences (Предпочтения) вы можете задать рад опций.
О Autosave options — опции автосохранения. Здесь имеются два пункта.
Editor files — со^фанение редактируемых файлов (модулей). Если вы поставите галочку радом с этим пунктом, то модули будут сохраняться
автоматически.
Project desktop — со^фанение рабочей среды. Если вы поставите галочку напротив этого пункта, то состояние всех окон будет со?фаняться автоматически.
□ Compiling and running — настройки процесса компилирования и запуска готовой программы. Здесь доступны несколько параметров.
Show compiler progress — во время компиляции показывать окно состояния. В этом окне отображается информация о процессе компиляции и его результате. Окно очень полезно. Единственный его недостаток это то, что компиляция проходит немного дольше. При маленьких проектах это незаметно, но с большими программами задержка может быть ощутимой из-за затрат процессорного времени при выводе информации на экран.
Warn on package rebuild — предупреждать, когда необходима перекомпиляция пакета. Лично я ни разу не встречался с ситуацией, когда это сообщение помогло бы. Поэтому пока его можно отключить.
Mmimize on пт — минимизировать оболочку, когда запущена программа. Параметр действует, когда вы запускаете программу из Delphi. Если вы запустите скомпилированную программу из проводника, то Delphi не будет минимизирован.
Hide designers on пт — прятать окна объектного инспектора и визуальной формы при запуске программы. По умолчанию этот параметр выставлен, но я советую вам его отключить, чтобы вы могли выполнять программу и тут же корректировать какие-то ее элементы визуально. Если этот параметр включен, то все окна визуального редактирования пользовательского интерфейса во время выполнения программы будут спрятаны.
Рис. 3.17- Окно, отображающее процесс компиляции
Остальные параметры не так интересны. Единственное, на чем необходимо остановиться, — это окно хода компиляции программы. Как уже говори-
лось, окно действительно удобно и его желательно включать. Как только вы попросите Delphi скомпилировать программу, перед вами появится окно (рис. 3.17). В нем довольно хорошо отображается состояние компиляции. Интересовать здесь нас должны три значения.
П Hints — сообщения. Это простые сообщения, которые указывают на места, где можно улучшить код. Например, вы объявили переменную, но не пользовались ей. В этом случае появится соответствующее сообщение. Это, конечно же, не ошибка и программа все же будет скомпилирована. Но благодаря этим сообщениям можно увидеть, где была объявлена лишняя переменная или, возможно, просто что-то было забыто.
П Warning — предупреждения. На них нужно обращать более пристальное внимание. Например, вы объявили переменную, затем попытались ее использовать, не присвоив начальное значение. В этом случае появится предупреждение. Это опять же не ошибка и программа будет скомпилирована, но Delphi предупреждает вас о возможной ошибке. Такие предупреждения нужно проверять, потому что вы действительно могли забыть что-то сделать и это уже может привести к фатальной ошибке выполнения программы.
Errors — это уже самые настоящие ошибки. Они указывают на те места, где была допущена грубая ошибка, из-за чего программа не может быть скомпилирована.
Даже если вы откажитесь от показа окна состояния компиляции, вы все равно увидите все сообщения, ошибки и предупреждения. С этим окном просто удобнее проводить отладку программы.
Сразу же можно обсудить еще одно окно настроек, управляющее сообщениями, которые нужно отображать при компиляции. Закройте на время окно настроек Delphi и выберите из меню Project пункт Options. Здесь находятся настройки конкретного проекта и для каждой программы можно указать свои собственные параметры. Пока не будем рассматривать все вкладки, а остановимся только на одной — Compiler Mess^es (Сообщения компилятора), показанной на рис. 3.18.
В разделе General вы можете увидеть несколько параметров.
Show warnings — отображать при компиляции предупреждения.
Show hints — отображать при компиляции сообщения. Я не советую вам что-то из этого отключать, потому что и то и другое бывает полезным. А вот в списке Warnings, который расположен ниже, вы можете указать, какие предупреждения отображать при компиляции текущего проекта. Здесь очень часто отключаются следующие предупреждения.
П Platform Symbol (Зависимый от платформы символ) — это сообщение появляется, когда в программе используется переменная, специфичная для определенной платформы. Программы, написанные на Delphi для
Windows, могут быть перекомпилированы в Borland Kylix. Среда разработки Kylix — это тот же Delphi, только для ОС Linux. Когда вы используете в Delphi какую-нибудь переменную, которая не может быть откомпилирована в Kylix, появляется предупреждение. Я никогда не использую свои программы для компиляции под Kylix, поэтому для меня эти предупреждения не несут никакой информации, а только отнимают лишнее место.
Рис. 3.18. Вкладка Compiler Messages окна настроек проекта
Platform Unit (Зависимый от платформы модуль) — то же самое, что и Platform Symbol, только для модулей.
Unsafe type. Unsafe code и Unsafe typecast — эти предупреждения появились в Delphi 1 и появляются они, когда вы используете незащищенные типы данных, код или объявления, которые могут привести к ошибкам.
(Совет )
Я постараюсь на протяжении книги вас научить правильно пользоваться такими типами данных. Однако имейте в виду, когда вы будете писать большие проекты, подобных предупреждений могут быть сотни. Чтобы не искать среди множества подобных сообщений действительно полезную информацию, их можно отключить.
Теперь вернемся в окно настроек программы и перейдем на вкладку Designer (рис. 3.19). Здесь очень интересными являются параметры:
О Display grid — показать сетку;
Snap to grid — перемещать объекты по сетке.
(Совет )
Я советую вам постоянно использовать сетку. Это позволит улучшить внешний вид программы. По умолчанию сетка состоит из ячеек, 8x8 пикселов каждая. Если вы захотите изменить это значение, то советую вам устанавливать значения, кратные 2. А вообще, если следовать эргономике правильного написания программ, желательно не изменять этого значения, потому что с такой сеткой компоненты располагаются достаточно хорошо.
Рис. 3.19. Вкладка Designer окна настроек
С некоторыми из оставшихся вкладок мы познакомимся немного позже. А пока перейдем к рассмотрению других объектов среды профаммирования Delphi.
Настройка редактора кода
Для настройки редактора кода нужно из позиции меню Tools выбрать пункт Editor Options (Настройки редактора). В этом случае откроется окно Editor Properties (Свойства редактора).
Рис. 3.20. Вкладка General окна настроек редактора
На вкладке (рис. 3.20) General (Общие) необходимо установить флажок, определяющий значение параметра Undo after save (Отмена после сохранения). Это делается для того, чтобы у вас была возможность отменять последние действия с помощью команды Undo (Отмена) даже после сохранения файла. Если флажок не установлен, то после каждого сохранения список последних выполненных операций очищается, и вы не сможете выполнять команду Undo (Отмена).
Если вы опытный пользователь и у вас не так уж много места на диске, то на вкладке Displ^ (Отображение) можно убрать флажок с опции Create backup file (Создавать резервную копию). Однако, если этот параметр включен, то при со?фанении измененного файла создается его резервная копия, чтобы можно было вернуться к состоянию до изменения. На практике ре-
зервные копии исходных файлов перед большими изменениями делаются вручную регулярно, а от резервных копий, созданных Delphi, нет особого преимущества.
Настройка оболочки
Последнее, что нужно сделать перед началом работы, — настроить кнопки быстрого доступа на панели инструментов. Для этого нужно щелкнуть правой кнопкой мыши по панели инструментов и в появившемся меню выбрать пункт Properties (Свойства). В результате откроется окно настройки панелей и кнопок (см. рис. 3.21).
Рис. 3.21. Окно настройки панелей инструментов
На первой вкладке можно включать и отключать различные панели. Оставьте только те, которые вы будете часто использовать. На второй вкладке находятся всевозможные кнопки, которые можно добавить на панель инструментов простым перетаскиванием.
С Совет )
Добавьте на панель инструментов команды Save АП (Сохранить все) и Close АН (Закрыть все) из раздела File (Файл). Сохранить один текущий файл можно нажатием сочетания клавиш <Ctrl>+<S>, поэтому кнопку сохранения можно убрать, а вот сохранить все файлы открытого проекта можно или из меню, или добавив одноименную кнопку на панель инструментов.
3 Зак 942
Закрыть проект можно только из меню, потому что сочетания клавиш для этой команды нет. Если для команды Save All (Сохранить все) есть клавиши быстрого доступа (хотя они и не совсем удобны), тут придется пользоваться меню, что иногда также затруднительно.
Еще одна удобная возможность — сохранение состояния окон под определенным именем. Для этого в Delphi есть панель Desktops, которая показана на рис. 3.22. Для сохранения состояния окон нужно нажать кнопку Save current desktop (Сохранить текущую рабочую поверхность), которая находится справа от выпадающего списка, и ввести имя для сохраняемого состояния окон. Таким образом, вы можете иметь несколько любимых состояний оболочки Delphi для разных ситуаций и разных типов проектов. В дальнейшем остается только выбирать нужное состояние из выпадающего списка.
)/ Delphi 7 - Project 1
File Edit Search View Project Run Component Database Tools Window n|lp i I2i€J^ 1
Standard j Additional I Win321 Svstem] DetaAcce$$| Data Controls | db£xaess| DataSnte I VUt | ДРО ) IWBfBISel WBBSenWs I InlfemetE 田营"用A同置山岡& f§膏З .芸G里I
Рис. 3.22. Панель инструментов Desktops
Остальные настройки пока не нужны. Теперь мы готовы приступить к изучению программирования и остальную информацию уже будем получать в процессе приобретения практических знаний.
Глава 4
Визуальная модель Delphi
Вы уже должны знать, что Delphi — визуальная среда разработки программ. Это значит, что большую часть оформления внешнего вида необходимо делать с использованием мыши, расставляя необходимые объекты на дизайнере форм (дизайнер форм — это форма, на которой расставляются компоненты для будущей программы). Такие действия чем-то похожи на строительство домика из кубиков. Вы просто строите каркас своего будущего приложения, не заботясь о том, как на самом деле происходит создание графического интерфейса пользователя в самой программе.
Среда разработки Delphi максимально упрощает создание приложения и облегчает жизнь программисту, упрощая процесс формирования внешнего вида программы. При этом можно больше внимания уделить логике выполнения программы и непосредственно заниматься созданием математической части приложения. 〔
Прежде чем начать расставлять компоненты в окне нашего первого приложения и знакомиться с ними поближе, рассмотрим немного теории, касающейся объектов программной среды и компонентной модели Delphi. Это основа, которую должен знать и понимать любой программист. Как уже говорилось, создавать простую профамму можно научить даже обезьяну, но для самостоятельного написания настоящих профамм необходимо понимание всех основ профаммирования. В противном случае вы сможете только повторять различные шаблоны и не сможете их кардинально изменить или улучшить.
В этой главе даются необходимые базовые знания, которые впоследствии можно применить на практике. Однако здесь мы пока еще будем использовать абстрактный язык программирования. Но, начиная со следующей главы, начнем знакомиться с языком Delphi непосредственно.
Процедурное программирование
Если вы читаете книгу полностью, то наверно уже знаете историю языков программирования. С их развитием развивались и технологии, используемые при написании программного кода.
Первые программы писались сплошным текстом. Это была простая последовательность команд, записанная в столбец. Все это выглядело приблизительно так:
Листинг 4.1. Текст линейной программы
Команда
Команда
Команда
Команда. N.
Используя такой подход к программированию, можно было сделать очень мало. Единственное, что было доступно программисту для создания логики в данном случае, — это условные переходы. Под условным переходом понимается переход на какую-то команду при определенных условиях, которые сложились в процессе обработки данных на процессоре. Например:
Листинг 4.2. Пример условного перехода
Если выполнено условие, то перейти на команду 1, иначе на команду 3
Команда 1
Команда 2
Команда 3
Команда N.
Это единственная логика, с помощью которой можно было выполнять определенные действия, зависящие от конкретных ситуаций, которые могут сложиться в процессе вычислений. Но программы оставались плоскими и неудобными, потому что написать таким образом логику современных программ невозможно. Линейным программированием можно создать только линейную логику, которая проведет вас по определенным шагам. Например, мастера в программах имеют линейную логику. Она может разветвляться по ходу выполнения шагов мастера, но не может делать что-то более сложное.
Взгляните на программу MS Word. В ней трудно представить себе линейность, потому что здесь как бы присутствует диалог с программой. Вы говорите, что вам надо, а она выполняет ваши действия. При линейном программировании можно создать только такую логику, при которой компьютер запра-
шивает определенные параметры (данные), и вы вводите их, а отступить от линейности, заложенной в такую логику, невозможно.
Следующим шагом стал процедурный подход. При этом подходе какой-то код программы мог объединяться в отдельные блоки (процедуры). После этого такой блок команд можно вызывать из любой части программы. Например:
Листинг 4.3. Процедурное программирование
Начало процедуры 1
Команда 1
Команда 2
Конец процедуры 1
Начало программы
Команда 1
Команда 2
Ест выполнено условие, то вьктолнить код процедуры 1.
Команда 3
Конец программы.
В результате появилась возможность использовать один и тот же код в одной программе неоднократно. Код программ стал более удобным и простым для понимания. Именно таким был отец Delphi — язык программирования Turbo/Borland Pascal, который со временем превратился в Object Pascal.
Но вернемся к процедурам. В процедуры можно передавать различные значения, заставляя их что-то рассчитывать.
Листинг 4.4. Пример процедур с параметрами
Начало процедуры 1 (Переменная 1: строка)
Команда 1
Команда 2
Конец процедуры 1
Начало программы
Команда 1
Команда 2
Ест вьктолнено условие, то вьктолнить код процедуры 1.
Команда 3
Конец цротраымы.
В этом примере после начала процедуры в скобках указан тип передаваемой переменной. Таким образом, в процедуру можно поместить код какой- нибудь математики и определить параметры в виде переменных, которые используются в этом коде, а затем только передавать ей разные значения в качестве этих параметров. Процедуры остались и в наше время. Чуть позже мы познакомимся с ними на практике применительно к Delphi.
Сразу хочу заметить, что использование процедуры часто называют "Вызов процедуры**. Эго действительно так, т. е. процедура как бы вызывается.
Давайте рассмотрим пример процедуры, приближенный к реальности.
Листинг4>5. Пример процедуры, приближенный к реальному
На-чало процедуры 1 (Переменная 1: Целое число)
Посчитать факториал числа, находящегося в Переменная 1.
Вывести результат на экран.
Конец процедуры 二
Начало программы
Процедура 1 (10)
Процедура 1 (5)
Процедура ] (8)
Конец программы.
В этом примере условно используется процедура, которая вычисляет факториал переданного ей значения и потом выводит результат на экран. В самой программе эта процедура используется как процедура 1 (10), где цифра в скобках означает переданное ей значение. Вот так получается универсальная процедура, которая считает факториал и выводит результат на экран. Как это все работает? Давайте рассмотрим алгоритм.
Начало программы.
Вызов процедуры процедура 1 (Ю) и передача ей значения 10.
Процедура вычисляет факториал числа 10 и выводит результат на экран.
Выход из процедуры и продолжение выполнения программы.
Вызов процедуры процедура 1 (5) и передача ей значения 5.
Процедура вычисляет факториал числа 5 и выводит результат на экран.
И так далее. Как видите, код программы очень удобный.
Но процедуры — это мелочи жизни. Более удобным стало использование функций. Функция — это та же процедура, только она умеет возвращать значение, т. е. результат своего выполнения.
Листинг4.6. Пример функции
Начало Функции 1: Тип возвращаемого значения — целое число
Команда 1
Команда 2
Конец Функции 1
Начало программы
Команда 1
Команда 2
Если выполнено условие, то выполнить код процедуры 1.
Команда 3
Конец программы.
Заметьте, что после двоеточия идет описание типа возвращаемого значения.
Теперь рассмотрим ту же ситуацию с факториалом. Допустим, нам надо рассчитать факториал для последуюшего использования, но не для вывода на зкран. Такая задача легко решается с помощью функций.
ЛИСТИНГ4.7. Расчет факториала с помощью функции
Начало Функции 1 (переменная 1: Целое число) : Целое число
Посчитать факториал числа, находящегося в Переменная 1.
Вериуть результат расчета.
Конец Функции 1
Начало програтт ы
Переменная 1: =Функция 1 (10)
Переменная 2: =Функция 1 (5)
Переменная 3:= Переменная 1+Переменная 2
Вывести на экран Переменную 3
Конец программы.
В этом примере в “переменную Г* записывается результат расчета факториала 10! (переменная 1 : =Функция 1 (10)). В "переменную 2” записывается ре-
зультат расчета факториала 5!. После этого значения переменных складываются и результат сложения записывается в "переменную 3”. Последнее действие программы — вывод на зкран "переменной 3”.
Объектно-ориентированное программирование
Следующим шагом в развитии технологий программирования было появление объектно-ориентированного программирования. Здесь программный код перестал быть "плоским" и программист оперирует не просто процедурами и функциями, а целыми объектами.
Объект — совокупность свойств, методов, событий. Что означает "сово- купностьи? Это значит, что объект как бы состоит из методов, свойств и событий, и они обеспечивают его полноценную работу. Представим себе кнопку. Она обладает:
О свойствами (цвет, текст на кнопке, шрифт текста и т. д.);
О событиями (события пользовательского ввода, например, нажатие на кнопку);
О методами (обеспечивающими работу кнопки, например, метод прорисовки текста, прорисовки фокуса и т. д.).
Если все это объединить, то получается автономный объект, который может работать в различных условиях. В этом случае достаточно только установить кнопку на форме, и она уже готова к применению. Как же оформляются свойства, методы и события объекта в программах?
Свойства — это переменные, которые влияют на состояние объекта. Например, ширина, высота.
Методы — это те же процедуры и функции, т. е. это то, что объект умеет делать (вычислять). Например, объект может иметь процедуру для вывода какого-то текста на зкран. Эта пропедура и есть метод, который принадлежит объекту.
События — это те же процедуры и функции, которые вызываются при наступлении определенного события. Например, если изменилось какое- то свойство объекта, может быть сгенерировано соответствующее событие и вызвана процедура, которая инициализирует реакцию приложения на это событие.
Изначально в объекте может и не быть процедуры для какого-то события, а если и есть, то она действует как обработчик по умолчанию. В среде Delphi в качестве обработчиков вы будете назначать собственные процедуры только в строго определенном формате (в зависимости от события). Формат процедуры заранее определен, и в разных случаях объект должен будет передавать
вам различные параметры. Ваши процедуры должны будут соответствовать предопределенному формату, чтобы не было разногласий с объектом, к которому они будут относиться.
Рассмотрим пример. Пусть пользователь нажал на кнопку. В этом случае она, как объект, генерирует соответствующее событие. В результате этого может быть вызвана процедура-обработчик, которую вы назначили кнопке при возникновении данного события. В этом случае не требуется никакая дополнительная информация, поэтому объект не будет пересылать вам никаких параметров, кроме указателя на самого себя. Однако, если нужно обработать событие, связанное с перемещением курсора мыши, дело будет обстоять несколько иначе. В этом случае объект будет генерировать событие и автоматически передавать вам новые координаты X и Y курсора, поэтому процедура должна быть объявлена с соответствующими параметрами, чтобы можно было принять их от объекта.
События — наиболее сложная часть объектов, но на практике работа с ними намного проще и вы сейчас должны понимать только это, В дальнейшем мы рассмотрим все это более подробно и от простых примеров продвинемся к более сложным.
Теперь рассмотрим работу свойств, методов и событий как единого целого. И снова для примера возьмем объект — кнопку. Такой объект должен обладать следующим минимальным набором.
О Свойства:
левая позиция (X);
верхняя позиция (Y);
ширина;
высота;
• заголовок;
□ методы:
создать кнопку;
уничтожить кнопку;
нарисовать кнопку;
П события:
кнопка нажата;
заголовок кнопки изменен.
Объект работает как единое целое. Например, вы изменили заголовок кнопки. Объегсг генерирует событие "заголовок кнопки изменен 二 По этому событию вызывается метод "нарисовать кнопку': Этот метод рисует кнопку в по-
зиции, указанной в свойствах объекта, и выводит на кнопке новый текст, указанный в свойстве 'заголовок".
У каждого объекта обязательно присутствуют два метода: "создать объект^ и Уничтожить объект". Во время создания объекта происходит выделение памяти для хранения необходимых свойств, и заполняются значения по умолчанию. Во время уничтожения объекта происходит освобождение выделенной памяти.
工 Примечание 二
Метод для создания объекта называется конструктором (constructor). Метод для уничтожения объекта называется деструктором (destructor). Сам процесс создания объекта называется инициализацией.
Теперь рассмотрим, использования нашей кнопки. Он выглядит следуюшим образом:
Создание бснопки с помощью вызова метода "создать кнопку".
Изменение необходимых свойств.
Все. Наша кнопка готова к работе.
Объект — сложный тип данных. Это значит, что вы можете объявлять переменные типа "объект" (точно так же, как объявлялись переменные типа "число” или "строка") и обращаться к объекту через эту переменную.
На языке программирования это будет выглядеть немного сложнее:
Объявить переменную типа "кнопка".
В эту переменную проинициализировать объект.
Изменить нужные свойства
Можно использовать объект.
Вот тут нужно собрать все, что вы поняли об объектах, в одно целое. Итак, мы можем объявлять переменные типа ''объект”. Давайте объявим переменную Объект 1 типа кнопка. Теперь можно создать кнопку, для чего есть конструктор (метод для создания объекта), который выделяет свободную память под этот объект. Процесс инициализации объекта-бснопки выглядит так: переменной 0бъект1 нужно присвоить результат работы конструктора объекта Кнопка. Конструктор выделит необходимую объекту память и присвоит свойствам значения по умолчанию. Результат этого действия будет присвоен переменной 0бъект1. Эта переменная будет указывать на область памяти, в которой находится созданная кнопка, и ее свойства. После всех этих действий мы можем получить доступ к созданному объекту через переменную Объект1.
Давайте напишем прототип небольшой программы, которая пояснит весь процесс создания объекта.
Листинг 4.8. Создание объекта
Начало программы.
Переменные:
Объект! — Кнопка;
Начало кода
0бъект1:= Кнопка.Создать объект
0&ьект1.Заголовок:^'Привет * Объект1.Уничтожить объект.
Конец кода
Доступ к свойствам и методам объектов осуществляется при помощи записи ИмяПеременной_ТипаОбъект. Свойство или метод определяются записью вида —имяПеременной_ТипаОбъект. Метод (записывается как имя_объекта —— точка — свойство или метод). Именно таким образом мы изменили в вышеуказанном Примере СВОЙСТВО Заголовок(0бъект1. Заголовок), ПРИСВОИВ ему , Точно так же был организован доступ к конструктору значение 1 Привет *.尸т j
(меТОД иСоздать объект**) И Деструктору (меТОД иУничтожить объект1').
Создание объекта — обязательно. Однако, если вы попробуете использовать код, приведенный на листинге 4.9, то у вас произойдет ошибка.
Листинг 4.9. Неправильное использование объекта
Начало программы.
Переменные:
Объект! — Кнопка;
Начало кода
Объект1.Заголовок:= * Привет
Конец кода
Здесь мы пытаемся изменить заголовок без создания и уничтожения объекта, а это ошибка! Переменная объект 1 ничего не хранит и ни на что не указывает. Ее необходимо сначала проинициализировать и создать для нее объект-кнопку или присвоить адрес расположения в памяти уже существующей
кнопки.
Без инициализации можно использовать только простые переменные, такие как число или строка. Тут Delphi выделяет под них память автоматически, потому что размер этих переменных фиксированный и Delphi уже на этапе компиляции знает, сколько памяти нужно отвести под переменную.
Сложные переменные типа объектов обязательно должны инициализироваться. Это связано еще и с размещением данных. Простые переменные хранятся в стеке (сегмент стека), а сложные переменные типа объектов хранятся в памяти компьютера. Как ранее уже указывалось, при старте программы сегмент стека инициализируется автоматически. Поэтому переменные могут спокойно размещаться в уже подготовленной памяти сегмента стека. Когда вы создаете объект, он создается в нераспределенной памяти компьютера. Так как память еще не распределена, ее нужно сначала подготовить (выделить нужный объем под создаваемый объект). После того как объект не нужен, эту память необходимо освободить, вызвав деструктор объекта. Простые переменные освобождать не надо, потому что стек очищается автоматически.
Мы еще не раз встретимся со стеком и необходимостью инициализации, когда будем говорить про типы переменных и просто на практических примерах.
Объект — очень удобная вещь. Он работает как шаблон, на основе которого создаются переменные типа объектов. Например:
Листинг 4.10. Объект в виде шаблона
Начало гтрогразуЕмы.
Переменные:
0бъект1 - Кнопка;
0бъект2 Кнопка.;
Начало кода
Объект1:= Кнопка.Создать объект
0бъект2:= Кнопка.Создать объект
Объект1.Заголовок:=* Привет*
Объект2 ・ Заголовок:=, Пока*
0бъект1.Уничтожить объект.
0бъект2.Уничтожить объект.
Конец кода.
Инициализацию объекта еще очень часто называют созданием экземпляра объекта. И это тоже правильно. Когда вы вызываете конструктор, в памяти создается новый экземпляр объекта. В данном примере можно сказать, что наши переменные 0бъект1 и Объект? указывают на собственные экземпляры объекта Кнопка, т. е. в памяти получается две независимые копии объекта (кнопки). Любую из них можно независимо менять и она не будет влиять на другую переменную.
Теперь более подробно о самом примере. В нем объявляются две переменных типа "кнопка”. Потом производятся их инициализация и изменение заголовка на нужный. В результате мы получили из одного объекта две кнопки с разными заголовками. Обе кнопки работают автономно и не мешают друг другу, потому что им выделена разная память. Таким образом создаются новые объекты на основе шаблона и потом раздельно используются, изменяя разные свойства шаблона и используя его методы.
Для уничтожения объекта всегда есть метод Free. Если объект вам больше не нужен и вы хотите его уничтожить, просто вызовите этот метод:
Объект1.Free.
Я, конечно же, забегаю вперед, объясняя метод Free. Но все же вам не помешало бы уже познакомиться с ним. Теперь пора приводить реальные строчки кода и постепенно вникать в смысл программирования.
Компонентная модель
Компоненты — это более совершенные объекты. Грубо говоря, компоненты —это объекты, с которыми можно работать визуально, и для этого у них есть необходимые свойства и методы.
Когда создавалась технология объектно-ориентированного программирования (ООП), о визуальное™ еще никто не думал, и она существовала только в мечтах программистов. Фирма Borland тогда создала библиотеку объектов Object Windows Library (OWL — Объектная библиотека Endows). А когда Borland создавала свою первую визуальную оболочку для Endows, пришлось немного доработать концепцию ООП, чтобы с объектами можно было работать визуально.
До появления шестой версии в Delphi существовала только одна компонентная модель — VCL (Visual Component Library — Визуальная библиотека компонентов). В шестой версии появилась новая библиотека CLX (Borland Component Library for Cross Platform — Кросспла тформенная библиотека компонентов).
VCL — библиотека компонентов, разработанная только под Windows. Она очень хорошая и универсальная, но работает только в этой операционной
системе.
В 2000 году фирма Borland решила создать визуальную среду разработки для Linux. В основу этой среды разработки легли Delphi и VCL. Но просто создать новую среду разработки было слишком просто и неэффективно. Было принято решение сделать новую библиотеку компонентов, с помощью которой можно было бы писать код как под Windows, так и под Linux. Это значит, что код, написанный в Delphi под Windows, должен без проблем компилироваться под Linux, и при том без дополнительных изменений.
Так в 2001 году появилась новая среда разработки Kylix, которая смогла компилировать исходные тексты, написанные на Delphi для работы в среде операционной системы Linux. В качестве компонентной модели использовалась новая библиотека CLX. В принципе, это та же самая VCL с небольшими доработками. Даже имена объектов остались те же.
Наследственность
Объекты обладают достаточно большим количеством преимуществ. Наследственность —одно из них. В некоторых книгах данное свойство объектов начинают описывать только к середине книги, но это большая ошибка. Наследственность объектов — основа объектно-ориентированного программирования (ООП). Даже в самой простой программе мы встречаемся с наследственностью, поэтому просто необходимо разобраться с этим уже сейчас.
Одно _из величайших достижений в ООП — наследование. Рассмотрим пример. Вы написали объект — игаражи. Теперь вам нужно написать объект "дом". Гараж — это, грубо говоря, однокомнатный дом. Оба эти здания обладают одинаковыми свойствами — стены, пол, потолок и т. д. Поэтому желательно взять за основу гараж и доделать его до дома. Для этого вы создаете новый объект "дом" и пишете, что он происходит от "гаража". Ваш новый объект сразу примет все свойства гаража. Эго значит, что уже есть стены, пол и потолок, остается добавить только интерьер. Теперь у вас будет уже два объекта: игаражи и "дом". Используя данный прием, можно, например, создать еще будку для собаки. Для этого снова создается объект "будка", который происходит от "гаража** (можете произвести построение от "дома", чтобы в будке у собаки был интерьер, но это мне кажется лишним, собака, этого просто не оценит). Нужно только уменьшить размер гаража и он превратится в будку. В итоге получается древовидная иерархия наследования свойств объектов, которая показана на рис. 4.1.
Тут еше нужно запомнить два понятия: предок и потомок. Предок — объект, от которого происходят другие объекты. Потомок — объект, который происходит или порожден из другого объекта. Например, гараж — это предок для дома, будки и дачи. Дом, будка и дача — потомки от гаража. Один объект может быть и потомком, и предком одновременно. Например, объект "дом” является потомком "гаража" и предком, например, для "многоэтажки".
Потомок наследует свойства предка, поэтому всегда знает, какие у него свойства, а предок не может знать свойства своего потомка, потому что он не знает, какие свойства будут добавлены в новый объект. Это необходимо знать и понимать. В будущем мы будет использовать это для создания некоторых программистских трюков.
Рис. 4.2. Пример иерархии объектов в Delphi.
Точно такой же метод наследования принят и в объектно-ориентированных языках. На рис. 4.2 показан фрагмент иерархии объектов Delphi. Как видно из рисунка, все объекты происходят от TObject. Это базовый объект, который реализует основные свойства и методы. От него происходят TPersistent и TThread. В реальности, от TObject происходит намного больше объектов, и все они знают о его свойствах и методах и наследуют их себе.
Есть еще одна возможность, которая очень удобна в ООП, — полиморфизм. Что это такое? Представим, что у гаража дверь открывается вверх, а у дома должны открываться в сторону. Дом происходит от гаража, поэтому у него дверь будет открываться тоже вверх. Как же тоща быть? Вы просто должны изменить (переписать) у дома процедуру, отвечающую за открытие двери. Тогда дом по-
лучит все свойства гаража, но при открывании двери подставит свою процедуру. Что-то подобное и есть полиморфизм. Другими словами, это когда объекты разных иерархий по-разному реагируют на одно и то же событие.
Для того чтобы можно было изменить процедуру, отвечающую за открывание двери, она должна быть объявлена у гаража, как "виртуальная” (virtual). Виртуальная процедура говорит о том, что в порожденном объекте она может быть заменена.
И это еще не все. Пусть, например, в гараже у нас стены голые, а в доме требуется повесить на них картины. Для реализации этого в ООП есть такая очень удобная возможность, как вызов метода предка. Рассмотрим пример.
Листинг 4.11. Вызов предка
Процедура, отвечающая за создание стен у гаража.
Начало
Создать стены
Конец
Процедура, отвечающая за создание стен у дома. Начало
Вызвать объект предка.
Повесить на стены картины.
Конец
В процедуре, отвечающей за создание стен у гаража, создаются стены. У дома тоже есть такая процедура, поэтому мы ее переопределяем. На первый взгляд процедура гаража должна пропасть, и у дома придется снова создавать стены, но это не так. Нужно просто вызвать объект предка (тогда предок создаст для нас стены), а потом вешать на стены картины.
Данная книга представляется как самоучитель, а значит, читатель должен четко понимать, как что-то формируется, а затем работает. Именно поэтому здесь так много времени затрачено на разъяснение сложных вещей. Однако это необходимо, чтобы в дальнейшем мы больше программировали, и вы могли понимать логику представленных в книге примеров. Программирование требует хорошего логического мышления, и именно это от вас потребуется в большей степени. Показать, как пишутся программы, достаточно просто, а вот понимать их и научиться самому создавать логику работы очень сложно.
Мы уже научились программировать теоретически и можем словами описать всю логику программы, и даже такую, на первый взгляд, довольно сложную вещь, как логику объектов. Теперь предстоит перейти к практике и увидеть все это на реальных программах.
Глава 5
Основы языка программирования Delphi
Мы уже достаточно изучили теории, и пора приступить к изучению самого языка Delphi. До этой главы все программы писались на каком-то абстрактном языке, но сейчас пришла пора познакомиться непосредственно с программированием в среде Delphi. Теперь мы будем писать программы только на нем и дополним наши теоретические знания практическими.
В этой главе вы познакомитесь с основами программирования на языке Delphi. Здесь мы научимся пользоваться компонентами, их свойствами и методами. А также увидим на практике объектную модель языка.
Далее, на протяжении всей книги будут использоваться два термина — объектная модель и компонентная модель. Под этими терминами понимается одно и тоже понятие. Как уже было сказано, компоненты отличаются от объектов только возможностью работы с ними визуально. В остальном они идентичны.
В этой главе мы создадим нашу первую программу и внимательно рассмотрим, из чего она состоит. Эта глава книги также описывает основы программирования и закладывает фундамент знаний будущего программиста. Ее понимание очень важно, как и понимание предыдущих частей книги.
"HelloWorld"
или из чего состоит проект
В большинстве книг по программированию (особенно в C++), описание начинают с программы **Hello World**. Это самая простая программа, которая выводит на экран окно, в заголовке которого написаны эти два заветных слова **Hello World**. В свое время появилось даже несколько анекдотов по этому поводу.
Авторы книг по Delphi упускают этот пример, считая его слишком простым. Они сразу начинают описание компонентов и работу с ними. Это ошибка. Действительно, написать на Delphi программу, подобную “Hello World**,
очень просто с точки зрения программирования. Для этого не надо писать ни одной строчки. Зато на таком примере очень удобно пояснять принцип программирования на Delphi и структуру проекта.
Итак, давайте напишем эту программу и разберем по полочкам, что делает Delphi. Запустите оболочку Delphi. Перед вами откроется окно разработки, которое подробно было рассмотрено в гл. 5. Оболочка Delphi загружается с уже созданным пустым проектом. Окно дизайнера с заголовком Forml, как раз сигнализирует об этом.
Давайте закроем этот проект и создадим новый. Выберите из меню Rie (Файл) пункт Close All (Закрыть все). Перед вами останется только главное окно и "Объектный инспектор", в котором нет доступных пунктов, потому что проект закрыт и нет ничего, чем бы мы могли управлять.
Теперь откройте Менеджер проектов, для этого из меню View (Просмотр) выберите пункт Project Manager (Менеджер проектов). Перед вами откроется окно, показанное на рис. 5.1. Как видите, окно абсолютно пустое, в нем есть только одна надпись <NO Project Group> (Нет группы проектов).
Рис. 5.1. Окно Менеджера проектов
Теперь создадим новый проект и посмотрим, что произойдет. Это можно сделать тремя способами:
Выбрать из меню Rie | New | Application (Файл | Новый | Приложение).
Выбрать из меню File | New | Other (Файл | Новый | Другое). Перед вами откроется окно (рис. 5.2). В этом окне нужно выбрать пункт Application (Приложение) и нажать ОК.
В окне Менеджера проектов нажать кнопку New (Новый), и сразу откроется окно, как показано на рис. 5.2. Дальше действуйте точно так же как и в предыдущем пункте.
Рис. 5.2. Окно создания нового проекта
(Совет )
Чтобы все, что описывается в книге, лучше отложилось в памяти, вы должны ее читать и одновременно отрабатывать изучаемый материал на практике. Только так вы сможете лучше запомнить, потому что человек запоминает определенные вещи по разным признакам. Когда вы не только читаете, но и повторяете, в вашей памяти откладываются все действия, которые производятся вами. Рассмотрим пример — танцор, который должен знать множество танцев. Он не знает их все и не сможет вам станцевать все танцы, начиная с любого момента. Если танец давно не танцевался, то в основном его танцуют с начала и до конца, чтобы вспомнить. Однако когда ноги танцора делают определенные движения, то они "вспоминают" следующие только по тому, что делают сейчас. В такие моменты говорят, что танцор знает танец ногами.
Попробуйте закрыть проект (выберите пункт меню File | Close All), чтобы потом создать его одним из указанных выше способов. Потом снова закройте и создайте, но уже другим способом. Результат будет один и тот же. Однако лучше использовать один из первых двух. Третий немного отличается от двух первых, и его отличие будет пояснено немного позже.
Давайте теперь посмотрим на Менеджер проектов. Он изменился, и достаточно сильно. На рис. 5.3 показано окно, в котором раскрыты все ветки дерева, чтобы вы могли увидеть все составляющие проекта. Чтобы легко раскрыть все ветви, достаточно выбрать первый элемент и нажать клавишу <*> на дополнительной цифровой клавиатуре.
Рис. 5.3. Менеджер проектов гри созданном приложении
В Менеджере проектов появилось целое дерево. Давайте рассмотрим каждый пункт этого дерева.
口 ProjectGroup] (заголовок дерева) — имя группы проектов. В одной группе проектов может быть несколько приложений. В нашем случае мы создали одно новое приложение, поэтому в группе будет только оно. Если вы нажмете кнопку New в окне Менеджера проектов и создадите новое приложение, то оно будет добавлено в существующую группу проектов. На рис. 5.4 представлено окно с двумя проектами Project 1 и Prcyect2.
・ П ProjectLexe — имя проекта (приложения). Когда вы создаете новое приложение, Delphi дает ему имя Project плюс порядковый номер.
□ Unitl — модуль. Проект состоит из модулей. Каждое окно программы хранится в отдельном модуле, а мы видим на экране, что у нашего прм-. ложения есть окно, и именно оно находится в нем. Модуль состоит из двух файлов.
Unitl.pas — с расширением pas показываются файлы, содержащие исходный код модуля. Имя файла такое же, как и у модуля.
Forml — это визуальная форма. Она сохраняется в файле с таким же именем, как у и модуля, но с расширением dfhi. Здесь будет храниться все то, что мы будем устанавливать на главной форме.
Когда у вас в проекте несколько приложений, то только одно из них является активным, и именно его вы можете выполнять и отлаживать в среде Delphi. Имя активного приложения написано полужирным шрифтом (на рис. 5.4 это Projectl). Чтобы изменить активное приложение, достаточно дважды щелкнуть по его имени левой кнопкой мыши.
Рис. 5.4. Менеджер проектов с двумя созданными приложениями
Но сейчас мы будем работать только с одним приложением, поэтому если вы создали два, то второе надо удалить. Для этого выделите имя приложения и нажмите на клавиатуре клавишу <Delete>. Перед вами появится окно с предупреждением — Аге you sure you want to remove Project2.dpr from the project group? (Вы уверены, что хотите удалить приложение Project2.dpr из группы проектов?). Нужно ответить Yes, и приложение будет удалено из группы проектов.
Учтите, что реально файлы с диска не удаляются. Они остаются на месте, но не отображаются в вашем проекте. Поэтому если вы сохраняли проект и файлы вам не нужны, нужно найти эти файлы и удалить вручную. Если вы не успели сохранить, то файлов на диске не будет.
Давайте сохраним наше новое приложение. Для этого выберем из главного меню File | Save All (Файл | Сохранить все). Перед вами откроется стандартное окно сохранения файла. Сначала Delphi запросит ввести имя модуля. По умолчанию указано текущее имя — Unitl.pas. Давайте изменим его на MainModule^ нажмем кнопку Save (Сохранить).
f Совет3
Сразу хочу обратить внимание, что нельзя вводить имена с пробелами или на русском языке. Если вы попытаетесь ввести что-то подобное, то произойдет ошибка, файл не будет сохранен и придется повторять процедуру сохранения. Не забудьте выбрать папку, куда хотите сохранить модуль и проекты. Желательно, чтобы все файлы одного проекта хранилось в одной папке.
Теперь Delphi запросит у вас имя будущего проекта. Давайте введем HelloWorld. Здесь тоже нельзя вводить имена с пробелами или на русском
языке. Теперь нажмите кнопку Save (Сохранить). Проект сохранится в файле под именем HelloWorld.dpr. Когда вы захотите снова открыть пример, вам нужно открыть именно этот файл. Не надо открывать файлы с расширением pas, потому что это всего лишь составляющая часть проекта. Открывать нужно файлы с расширением dpr.
Старайтесь выбирать имена, наиболее точно отображающие содержимое модуля, чтобы потом легче было разобраться с файлами в больших проектах. К тому же желательно помнить, что имя проекта задает имя будущего запускаемого программного файла проекта. Если вы оставите имя Project 1, то и имя запускаемого программного файла будет ProjectLexe.
(Совеу )
При сохранении проектов желательно это делать в отдельных папках. Для каждого проекта лучше отводить отдельную папку, потому что они состоят из множества файлов, и когда у вас будут большие проекты, вы не сможете отделить один от другого, если они находятся в одной папке. Все дополнительные формы или модули, которые вы будете создавать для проекта, желательно также сохранять в папке этого проекта. В этом случае вам проще будет их копировать на другой компьютер, архивировать или переносить их в другую папку.
Давайте теперь посмотрим, как изменился наш Менеджер проектов (рис. 5.5). Как видите, имя проекта изменилось на HelioWorld, а имя модуля на MainModule.
Рис. 5.5. Обновленный Менеджер проектов
Теперь перейдите в папку, куца вы сохранили проект, и посмотрите, какие файлы там присутствуют. На компакт-диске это папка \ Примеры \ Глава 5 \ Hello World. Давайте посмотрим на содержимое этих файлов.
口 HelloWorld.cfg — файлы с расширением cfg, содержат конфигурацию проекта.
П HeltoWorld.dof — файлы с расширением dot содержат опции проекта.
П Hello World. dpr — файлы с расширением dpi*, это сам проект. В этом файле находятся описание используемых в проекте модулей и описание процедуры инициал из ации программы. Его можно использовать и для написания кода. В будущем мы научимся писать код в этом модуле.
口 HelloWorld.res — файлы с расширением res, содержат ресурсы проекта, например такие, как иконки, курсоры и др.
П MainModule.pas — файлы с расширением pas содержат исходный код модулей.
П MainModule.dfm — файлы с расширением dfm, содержат визуальную информацию о форме.
П MainModuIe.ddp — файлы с расширением ddp, определяют вспомогательные файлы модуля.
П MainModule.dcu — файл с расширением dcu, представляет откомпилированный модуль проекта. Когда компилируется программа, все модули проекта собираются в один и получается один программный файл. У вас пока не может быть этого файла, потому что вы еще не производили компиляпию своего проекта.
Файлы исходных кодов (с расширением pas) — это текстовые файлы, которые мы будем редактировать в редакторе кода. Файлы визуальных форм (с расширением dfm) создаются автоматически, и в них будет храниться информация о том, что находится на форме, а также настройки самой формы. Откройте этот файл в текстовом редакторе (например, в Блокноте), и вы увидите примерно следующее:
object Forml: TForml
Left 二 231
Top = 163
Width 二 426
Height 二 300
Caption 二 * Hello world"
Color 二 clBtnFace
Font.Charset 二 DEFAULT_CHARSET
Font.Color 二 clWindowText
Font.Height 二-11
Font .Name 二 *MS Sans Serif
Font. Style 二'[]
OldCreateOrder = False
PixelsPerlnch = 96
TextHeight 二 13
end
Первая строка идентифицирует объект, его имя и тип. Потом идет перечисление свойств объекта и значения, которые им присвоены. Описание свойств заканчивается ключевым словом end.
Если у вас возникла проблема с открытием формы, вы можете открыть файл визуальной формы в текстовом редакторе и подправить это свойство. Это может потребоваться, когда на форме есть компонент, который соединяется с базой данных. Если соединение активно и база недоступна, то при открытии формы Delphi буцет пытаться соединиться с базой данных и при неудачном соединении форма может не отобразиться. В этом случае нужно открыть такой файл в ickctobom редакторе и отредактировать свойства соединения с базой. Молено отключить соединение или подправить путь к базе данных (о базах данных мы будем говорить в гл. 14).
А теперь давайте вернемся к нашей программе Hallo World, которую мы должны написать. Сначала посмотрим, что у нас уже есть. В принципе, мы ничего особого не сделали, но у нас уже есть готовое окно, которое можно превратить в программу. Для этого нужно скомпилировать проект. Для выполнения компиляции выберите из меню пункт Project | Compile HelloWorld (Проект I Компилировать HelloWorld). Если вы выбирали в настройках Delphi показ окна состояния компиляции, то увидите окно, которое показан но на рис. 5.6.
Рис. 5.6. Результат компиляции
Как видите, нет никаких сообщений, предупреждений или ошибок. Еще бы, ведь мы еще ничего не делали и не успели испортить созданную для нас заготовку. Программа скомпилирована. Просто нажмите кнопку ОК, чтобы закрыть это окно.
Теперь перейдите в папку, где вы сохранили проект. Там появится запускной файл HelloWorld.ехе. Запустите его, и вы увидите окно, как показано на рис. 5.7. Как видите, перед нами пустое окно, т. е. программа почти готова, хотя мы еще вообще ничего не делали. Для завершения поставлен-
ной задачи нам нужно только изменить заголовок окна на Hello world и все.
На языке C++ такая простая программа описывалась бы с такими подробностями на протяжении 20-30 страниц. И это не шутка.
Рис. 5.7. Программа в запущенном виде
Итак, осталось изменить заголовок. Для этого вспоминаем ООП. В Delphi все — объекты, а значит, и окно программы тоже объект. Заголовок окна — это скорей всего свойство окна. Для того чтобы изменить это свойство, нужно перейти в объектный инспектор (вюгадка Properties), найти свойство Caption (Заголовок) и ввести в его поле ввода слово Hello world (рис. 5.8). После этого можно нажать клавишу <Enter> или просто перейти на другую строку с другим свойством.
Теперь давайте запустим нашу программу. Для этого можно снова скомпилировать ее и запустить файл. Однако на этот раз мы будем действовать по- другому. Выберите из меню Run (Выполнить) пункт Run (или нажмите на клавиатуре клавишу <F9>). Delphi сразу откомпилирует и запустит готовую программу. Как видите, программирование не настолько страшно, как его расписывают.
f Совет\)
Заведите себе привычку перед каждой компиляцией сохранять весь проект (File I Save All). Обидно будет, если пол-дня тяжелой работы пропадет даром от какой-нибудь внештатной ситуации. А они бывают, особенно если вы установите в среду Delphi компоненты с ошибками. Поэтому лучше лишний раз сохраниться, чем пол-дня восстанавливать проделанную ранее работу.
iject Inspectoriject Inspector|Form1|Form1TForiniTForiniProperties | Events |Properties | Events |AutoScroifj AutoSizeI BiDiModeH BordericonsBorders lylet _ BorderWidthI Caption ClientHeighti CliertWidth । CdoI 曰 Enstmints [....СИЗОi Ctrsori DefaultMonitor DockSiteDragKind1 DragMode{All shownAutoScroifj AutoSizeI BiDiModeH BordericonsBorders lylet _ BorderWidthI Caption ClientHeighti CliertWidth । CdoI 曰 Enstmints [....СИЗОi Ctrsori DefaultMonitor DockSiteDragKind1 DragMode{All shownI Hello worldI Hello world
iject Inspector
iject Inspector
|Form1
|Form1
TForini
TForini
Properties | Events |
Properties | Events |
AutoScroif
j AutoSize
I BiDiMode
H Bordericons
Borders lyle
t _ BorderWidth
I Caption ClientHeight
i CliertWidth । Cdo
I 曰 Enstmints [....СИЗО
i Ctrsor
i DefaultMonitor DockSite
DragKind
1 DragMode
{All shown
AutoScroif
j AutoSize
I BiDiMode
H Bordericons
Borders lyle
t _ BorderWidth
I Caption ClientHeight
i CliertWidth । Cdo
I 曰 Enstmints [....СИЗО
i Ctrsor
i DefaultMonitor DockSite
DragKind
1 DragMode
{All shown
I Hello world
I Hello world
True
False
bdLeftToRight
I [biS^stemMenub Minimize biM 'bsSizeable
:0
J 310
*468
(Л clfi InFace
(TSizeConstrainU)
XuecfD efault
i drrActiveFormFalse .....
• dkDragdmManual▼
17
Рис. 5.8. Изменение свойствав объектном инспекторе
В принципе, на этом можно было бы остановиться и рассмотреть код модуля главной формы. Однако перед этим необходимо рассмотреть свойства проекта и изучить правила, по которым ими можно управлять. Выберите пункт меню Project | Options (Проект | Настройки), и вы увидите окно, как показано на рис. 5.9. Окно разбито на множество вкладок, и с некоторыми из них мы познакомимся сейчас, а остальные оставим на будущее. Слишком много информации ни к чему хорошему не приведет, тем более что нам пока большинство настроек ненужно.
На первой вкладке Forms (Формы) вы можете настраивать формы проекта. Вверху есть выпадающий список Main form (Главная форма). Здесь можно выбрать форму, которая будет являться главной для активного приложения. У нас только одна форма, For ml, которая на данный момент является активной (находится в стадии редактирования), поэтому она будет главной.
Чуть ниже расположены два поля со списком. Слева находится поле Autocreate forms — автоматически создаваемые формы. При запуске программы все описанные здесь формы будут инициализироваться автоматически. Справа находится поле Available forms — доступные формы. В этом списке будут находиться формы, которые не будут создаваться автоматически, но доступны проекту. Такие формы пользователь обязан инициализировать самостоятельно. Между списками расположены кнопки, с помощью которых можно перемешать имена форм из одного списка в другой, и обратно.
Project Options for HelloWorld.eHe
Directories/CondilionalsVersion InfoPackages
Forms I Application | Compter j Compiler Messages j Linker ]
Mab Jo пт I FormlFAuto-create forms:用arable forms:Mab Jo пт I FormlFAuto-create forms:用arable forms:厂'Defauli厂'DefauliHelpHelpРис. 5.9. Окно свойств проектаРис. 5.9. Окно свойств проекта
Mab Jo пт I FormlF
Auto-create forms:用arable forms:
Mab Jo пт I FormlF
Auto-create forms:用arable forms:
厂'Defauli
厂'Defauli
Help
Help
Рис. 5.9. Окно свойств проекта
Рис. 5.9. Окно свойств проекта
Теперь перейдем на вкладку Applications (Приложения). Здесь вы можете настраивать следующие поля.
口 Title — заголовок, который будет отображаться в панели задач.
□ Help file — имя файла помощи.
口 Icon — иконка приложения. По умолчанию используется иконка Delphi, но вы можете ее изменить, нажав на кнопку Load [con (Загрузить нконку).
П Target file extension — расширение результирующего файла. Если здесь ничего не указано, то запускаемый программный файл будет иметь стандартное расширение ехе. Вы можете указать любое другое значение, но файл от этого не изменится. Это будет тот же самый программный файл, только ему присвоится другое расширение. Такое действие равносильно простому переименованию файла после компиляции.
Следующая вкладка — Version Info (Информация о версии). Если установить флажок Include version information in project (Включить информацию о версии в проект), то в запускаемый программный файл будет встроена информация о версии программы. На этой же вкладке вы можете указать номер версии, сборки и выпуска (рис. 5.10).
На этом рассмотрение работы с проектом завершено. В следующем разделе будет рассмотрен исходный код, который сгенерирован Delphi для нашего
проекта. Не смотря на то, что он “пустой”,icoe-какая информация присутствует и с ней нужно разобраться уже сейчас, пока мы не перешли к рассмотрению более сложных программ.
Рис. 5.10. Вкладка Version Info окна настроек проекта
ПримечаниеПримечание
Примечание
Примечание
На компакт-диске в папке \Примеры\Глава 5\Hello World вы можете увидеть пример программы "Hallo World”.
Язык программирования Delphi
Язык профаммирования Delphi достаточно прост для освоения, но очень эффективен и достаточно мощный. Самое первое, с чем надо познакомиться,— это ко ммент арии.
Комментарии — это любой текст, который абсолютно не влияет на код Программы. Он никогда не компилируется и не вставляется в исполняемый файл, 〃 используется только для пояснений кода.
Комментарии могут оформляться двумя способами:
口 все, что идет после двойного слеша, воспринимается как комментарий (так можно оформить только одну строку комментария);
口 все, что заключено в фигурные скобки { и }, тоже является комментарием (в этом случае можно включить в комментарий сколько угодно строк).
Рассмотрим пример.
Листинг 5.1, Пример комментария
//Это комментарий.
Это уже не комментарий
(Это снова комментарий
И это тоже}
В данной книге постоянно будут использоваться комментарии для пояснения кода описываемых программ. Именно поэтому до начала разбора кода мы должны были познакомиться с ними.
Рис. 5.11. Редактор кода
Теперь вы готовы к изучению языка программирования Delphi. Создайте новый проект или откройте программу **Hello World**, разницы нет. После этого перейдите в редактор кода, который показан на рис. 5.11.
Здесь для вас уже написана заготовка будущей программы. Точнее сказать, только для этой формы. Если вы захотите, чтобы в программе было два окна, то в этом случае придется создать еше одну форму, а значит, появится еше один подобный модуль, т. к. каждое окно описывается в отдельном модуле.
Давайте досконально рассмотрим, что тут написано. В листинге 5.2 приведены подробные комментарии для каждой строчки созданного шаблона. Однако здесь кое-что упущено в целях удобства изучения программного кода.
[листинг 5.2. Сгенерированный оболочкой Delphi шаблон *
unit Unitl; //Имя модуля
inteirfcLce
uses //После этого слова идет перечисление подключенных модулей.
Windows, Messages, SysUtils, Variants, Classes,
Graphics, Controls, Forms, Dialogs;
Type //После этого идет объявление типов
TFoml 二 class (TForm) //Начало описания нового объекта TForml
_(
/ /Здесь описываются компоненты и собьтгия
private //После этого слова МОЖНО описывать закрытые данные объекта
{ Private dec 1 arat ions } / /Подсказка, которую с генерировал Delphi
{Здесь можно описывать переменные и методы, доступные только для объекта TForml}
public //После этого слова можно описывать открытые данные объекта {Public declarations}//Подсказка, которую сгенерировал Delphi
{Здесь можно описывать переменные и методы, доступные из любого другого модуля}
end;
var / /Объявление глобальных переменных
Forml : TForml; //Это описана переменная Forml типа объекта TForml
Основыязыка программирования Delphi 85 iinplement at ion
{$R * .dfni} //Подключение . dfm файла (файл с данными о визуальных объектах )
end. // end с точкой - конец модуля
Если что-то осталось еще непонятным, то в процессе программирования реальных программ все встанет на свои места. Не старайтесь запомнить все сразу, потому что это не реально. Слишком много новой информации, не подкрепленной практикой. Старайтесь понять только смысл написанной программы.
ПрактичесБси все строчки заканчиваются знаком ”;" (точка с запятой). Этот знак показывает конец оператора. Он ставится только после операторов и никогда не используется после ключевых слов типа uses, type, begin, implementation, private, public И T. Д. В последствии ВЫ познакомитесь СО всеми ключевыми словами, большинство которых вьвделяется жирным шрифтом. Сразу видно исключение — end, после которого точка с запятой не ставится.
Итак, разберем структуру кода. В самом начале стоит имя модуля. Оно может быть любым, но таким же, как и имя файла (без учета его расширения). Изменять имя модуля вручную нежелательно. Если все же надо изменить, то сохраните сначала модуль со старым именем. Для этого нужно выбрать бсо- манду меню File | Save As (Файл | Со?фанить как).
( Совет )
Желательно давать модулям понятные имена. В этом случае по имени можно определить, что находится внутри одноименного модуля. Трудно догадаться, что находится в файле под именем Unitl.pas. Файлам также желательно давать такие имена, которые связаны с их содержимым. Например, главную форму программы лучше сохранять в файле с именем Mainllnit.pas или MainModule.pas. В этом случае модуль и файл будут иметь имя MainUnit или MainModule, и вы можете сразу определить, что это главная форма.
Далее идет подключение глобальных модулей. Все процедуры, функции, константы описаны в каком-нибудь модуле, и прежде чем эти процедуры использовать, нужно его подключить. Вы можете знать о существовании какой-нибудь функции. Но чтобы об этом узнал компилятор, вы должны указать модуль, где описана эта функция, понятным для компилятора языком. Например, вам надо превратить число в строку. Для этого в Delphi уже есть функция intToStr. Она описана в модуле Sysutils. Если вы хотите воспользоваться этой функцией, вам надо подключить этот модуль к своему модулю формы (напечатать название Sysutils в разделе uses) и использо-
Листинг £6 Раздел private
private
Переменная!: Integer;
Procedure Ргос1;//Это процедура, после нее не может быть переменных
Переменная2: String;//Это ошибка. Уже объявлена одна процедура.
Существуют четыре типа разделов:
П Private — свойства и методы из этого раздела доступны только этому объекту. Другие объекты не могут вызывать эти методы и читать/ записывать свойства;
口 Protected — эти свойства и методы доступны только этому объекту и потомкам (объектам, которые происходят от нашего и наследуют его свойства). Сторонние объекты не могут получить доступ к хранящимся здесь свойствам и методам;
П Public — все, что описано здесь, доступно всем;
П Published — когда будут писываться собственные компоненты приложения, в этом разделе мы будем описывать свойства и события, которые должны быть отображены в объектном инспекторе. Эти свойства доступны всем.
Надо заметить, что если два объекта объявлены в одном и том же модуле, они считаются дружественными и видят абсолютно все методы и свойства, даже если объявление произведено в разделе private.
После объявления объекта и его составляющих идет описание глобальных переменных. Оно начинается после ключевого слова var и идет всегда после объявления типов. Это надо запомнить уже сейчас, потому что сначала идет раздел type, дце описываются типы, а затем var, дце описываются переменные.
Глобальные переменные — это переменные, которые хранятся в стеке, создаются при запусБсе программы и уничтожаются при выходе из программы. Это значит, что они доступны всегда и везде, пока запущена программа.
Листинг 5 7 Пример объявления глобальных переменных
var / /Объявление глобальных переменных
Forinl: TForml; //Это описана переменная Forml тгта объекта TForml
В этом примере объявлена переменна兄 Forml типа объекта TForml. Когда
будет создан объект TForml, то его указатель будет записан в переменную
Forml. Данная переменная глобальная и существует, пока программа выполняется, а значит, к ней всеща можно получить доступ.
Как инициализируется переменная Formi, мы рассмотрим позже. Ключевое слово implementation тоже пока трогать не будем, а оставим на будущее, коща наши знания о Delphi немного расширятся.
Последнее, что осталось рассмотреть в этой главе, — ключ {$r * dfm}. В фигурных скобках могут быть не только комментарии, но и ключи для компилятора. Они отличаются тем, что выглядят как {$Буква Параметр}. Буква — указывает на тип ключа. В нашем случае используется буква R. Этот ключ указывает на то, что надо подключить файл ресурсов, в данном случае . dfm файл (файл с данными о визуальных объектах) с тем же именем, что и имя модуля. Если имя модуля MainUnit, то можно изменить этот КЛЮЧ на {SR MainUnit.dfm}.
Ключ R — это единственный ключ, который пока понадобится. Со временем мы изучим ключи более подробно.
Любой программный код в Delphi заключается между begin и end. begin — означает начало кода, a end — конец. Например, коща вы пишете процедуру, то сначала нужно написать ее имя (как это делать, мы поговорим позже), а потом заключить ее код между операторными скобками — begin и end. Мы уже говорили об этом, когда писали на абстрактном языке программирования, но я решил повториться, чтобы вы не забывали, что это относится и к Delphi.
Типы данных в Delphi
Как уже говорилось, на языке программирования все должно иметь свой тип. Мы уже знаем, что существуют четыре основных типа данных: целые числа, вещественные числа (дробные), строки и булевы значения.
Все переменные должны относиться к какому-то типу. Почему? Чтобы ответить на этот вопрос, надо вспомнить что такое переменная. Переменная — область памяти, ще хранится определенная информация. Чтобы знать, что хранится в этой памяти, мы должны указать, к какому типу относится переменная. Если переменная относится к типу целых чисел, то в памяти, отведенной под переменную, хранится целое число.
Рассмотрим более подробно, каких типов бывают переменные.
Целочисленные типы данных
В переменных целого типа информация представлена в виде чисел, не имеющих дробной части. Они используются для математических вычислений и любых других операций, тде нужна работа с числами.
Существует несколько видов целых типов данных. Они в основном отличаются только размером отводимой переменным памяти для хранения целых чисел.
В табл. 5.5 перечислены все типы целых чисел. В примечании указано, какого типа могут быть эти числа — со знаком или без (т. е. только положительные или могут быть как положительными, так и отрицательными). В зависимости от объема памяти, отводимого переменной для хранения данных, определяется максимальное число, которое можно записать в эту переменную.
Таблица 5.1. Типы целочисленных переменных
Тип
Диапазон возможных значений
Размер памятидля хранения данных
Примечание
Integer
-2147483648 .. 2147483647
4 байта (32 бита)
Знаковое
Cardinal
0 .. 4294967295
4 байта (32 бита)
Без знака
Shortint
—128..127
1 байт (8 бит)
Знаковое
Smallint
-3276832767
2 байта (16 бит)
Знаковое
Longint
-2147483648.. 2147483647
4 байта (32 бита)
Знаковое
Int64
-263 .. 263-1
8 байт (64 бита)
Знаковое
Byte
0 .. 255
1 байт (8 бит)
Без знака
Word
065535
2 байта (16 бит)
Без знака
Longword
0 4294967295
4 байта (32 бита)
Без знака
Целочисленным переменным можно присваивать как десятичные числа, так и шестнадцатеричные. Для этого перед шестнадцатеричным числом нужно поставить знак доллара — $. Сразу же рассмотрим пример.
Листинг 5.В. Запись значения в целочисленную переменную
var
i:Integer;
begin
i:=10;
ifa?
end;
В этом примере сначала переменной i присваивается число 10, а потом $а — шестнадцатеричное А, что также равно десяти. Так что первая и вторая строка делают одно и то же, а значит, и результат будет одинаковый.
Вещественные типы данных
Вещественные или дробные типы данных предназначены для хранения чисел с плавающей точкой. Некоторые считают, что лучше использовать именно такие типы вместо целочисленных. Это заблуждение. Операции с плавающей точкой отнимают у процессора больше времени и требуют больше памяти. Поэтому используйте переменные этого типа только там, где это действительно необходимо. В этой книге вещественные переменные будут использоваться минимально. В табл. 5.2 представлены типы вещественных переменных.
Таблица 5.2. Типы вещественных переменных
Тип
Диапазон возможных значений
Максимально количество цифр в числе
Размер
в байтах
Real48
2.9 хЮ"39.. 1.7 х 1038
11-12
6
Real
5.0x10"324.. 1-7х1О308
15-16
8
Single
1.5x10*..3.4x1038
7-8
4
Double
5.0х10Т24, i.7xio3O8
15-16
8
Extended
3.6 X 107951 1.1 * Ю4932
19-20
10
Comp
—2-63+[ 263
19-20
8
Currency
-922337203685477.5808 ..
922337203685477.5807
1&-20
8
Очень важно помнить, что вещественные числа не равны целым. Например, вещественное число 3.0 не будет равно целому числу 3. Для того чтобы сравнить оба этих числа, нужно округлить вещественное число.
Символьные типы данных
Символьные данные могут хранить текст, например, для вывода на экран или в диалоговое окно. Символьные данные — это простая цепочка из чисел. Каждое число, в свою очередь, определяет порядковый номер символа в таблице символов. Например, если представить наш алфавит в виде такой таблицы, то число 0 будет означать букву А, число 1 будет означать букву Б, и т. д. Это значит, что слово кош в числовом виде будет выглядеть так: 10 14 18
Здесь 10 — это буква к, 14 — это о, 18 — это т. Именно в виде таких последовательностей чисел и выглядят строки в компьютерной памяти.
Самые первые таблицы символов были 7-битными (ASCII). А т. к. в 7 битов можно поместить максимум число 127, то и количество символов в таблице
равнялось 127. Хотя данные хранились в 7 битах, под каждый символ все же отводились 8 бит, т. е. один байт. Это связано с тем, что память в компьютере разбита по ячейкам в 8 бит. Поэтому один бит оставался свободным.
Но тут возникает проблема — помимо букв в таблице должны содержаться еще и цифры от 0 до 9, а также служебные символы типа знака равно, больше, меньше и т. д. Таким образом, получилось, что в такой таблипе не хватило места для букв из языков большинства народов.
В табл. 5.3 вы можете увидеть таблицу ANSI, которая используется в Windows.
Таблица 5.3. ТаблицаАНЗН
! Сим
1 вол
Номер
Символ
Но
мер
Символ
Но
мер
Символ
Номер
Символ
Номер
1
-
21
)
41
=
61
Q
81
2
22
42
>
62
R
82
3
23
43
?
63
s
83 j
4
24
1
44
@
64
T
84
5
25
-
45
А
65
и
85
6
26
46,
В
66
V
86〕
7
27 а
,.—
/
47
с
67
w
87:
i
8
28
0
48
D
68
X
88
9
29
1
49
Е
69
Y
89 j
10
30
2
50
F
70
z
90 ;
11
31
3
51
G
71
91
f
12
32
4
52
Н
二三丿
92
13
33
5
53
I
73
93 j
14
34
6
54
J
-!
二
-5…
#
35
7
55
К
75 ■
95
16
$
36
8
56
L
76
96
17
%
37
9
57
м "
77」
a
97 !
—
18
&
38
58
N
78 I
98
19
39
…59.I
.79
c
99
20
г
40
<
60
:р
80
d
## i
Таблица 5.3 fпродолжение)
i Символ
Номер
Символ
Номер
Символ
Номер
Символ
Номер
Символ
Номер :
;е
##
П
129
п
157
№
185
X
213 ;
f
##
130
D
158
9
186
ц
214
;g
##
131
D
159
187
ч
215
;h
##
132
160
%
188
ш
216 ;
i
##
133
i
161
力
189
щ
217
##
t
134
0
162
%
190
ъ
218 ;
j К
##
t
135
£163
6
191
Ы j 219
##
П
136
о
164
А
192
ь
220
M
##
%o
137
¥
165
Б
193
э
221 ;
i n
##
П
138
166
В
194
ю
222
c
111
(
139
§
167
「
195
Я
223 !
p
112
n
140
Ё
168
д
196
а
224
5 C
113
D
141
©
169
Е
197
6
225 j
i 「
114
D
142
а
170
ж
198
в
226 |
£
115
口
143
171
3
199
「
227
1
116
口
144
—
172
и
200
Д
228
;u
117
1
145
173
й
201
е
229
、
118
146
®
174
к
202
ж
230 ;
V
119
147
175
л
203
3
231 ;
120
148
176
м
204
.и
232
」21
149
+
177
н
205
й
233
122
—
150
2
178
о
206
к
234 ;
123
151
3
179
п
207
л
235
124
п
152
п
180
р
208
м
236 ■
125
та
153
н
181
с
209
н
237 ?
rtrf
126
п
154
и
182
т
210
о
238
i □
127
155
183
у
211
п
239 ;
D
128
D
156
I ё
184
Ф
212
I р
240 ;
Таблица 5.3 (окончание)
j Символ
Но
мер
Символ
Номер
Символ
Номер
Символ
Номер
Символ
Номер
с
241
ф
244
ч
247
ъ
250
э
253
Т
242
X
245
ш
248
ы
251
ю
254
У
243
ц
246
щ
249
.ь
252
я
255
Если посмотреть на табл. 5.3, то можно увидеть, что вместо английской буквы А в памяти будет стоять число 65, а вместо русской буквы П мы увидим 207. Получается, что слово Привет в памяти машины будет выглядеть следующим образом: 207 208 200 194 210
Нулевой символ в таблице использовать не стали и зарезервировали как полный ноль. Чуть ниже вы увидите, как программисты нашли достойное применение этому символу. Первые символы в таблице (те, тде в поле символов пусто) — это служебные символы, такие как символы клавиши <Esc>, <Tab>, <Enter> и др. Как вы понимаете, у этих символов нет графического отображения, но у них должны быть номера, чтобы реагировать на нажатие клавиш.
В Delphi используется 8 -битная расширенная таблица символов, ще задействованы все 8 бит (ANSI-таблица). Эта таблица берется из самой операционной системы Windows. Таким образом, количество символов и их расположение зависит от ОС.
Для того чтобы удовлетворить все национальности, ввели поддержку UNICODE (16-битная таблица символов). В ней первые 8 бит совпадают с таблицей ANSI, а остальные являются специфичными. Начиная с Windows 2000, эта кодировка стала использоваться все шире и шире.
Основные типы строк, которые присутствуют в Delphi, приведены в табл. 5.4.
Таблица5.4. Типы строк
Тип
Максимальная длина строки, символов
Память, отводимая для хранения строки
Примечание
ShortString
255
От 2 до 256
AnsiString
231
От 4 байтов до 2 Гбайт
8-битные
WideString
230
От 4 байтов до 2 Гбайт
UNICODE
Строки в Delphi заключаются в одинарные кавычки. Например, как показано ниже, вы можете объявить переменную str типа "строка" и присвоить ей значение 'Hello World'.
Листинг 5.9. Объявление строковой переменной
var
Str:tosiString;
begin
Str:=,Hello World1 ; //Присваиваем Str значение 1 Hello World1. end;
Так как строка — это массив символов, значит, можно получить доступ к отдельному символу строки. Для этого нужно после имени переменной указать в квадратных скобках номер символа, который нужен, только не забывайте, что буквы в строках нумеруются, начиная от 1. Большинство массивов в языках программирования нумеруются с нуля (строки — это те же массивы, только символов). В этом случае получается исключение, которое надо запомнить.
Примечание
Нулевой символ в строке указывает на ее длину. Его нельзя изменять прямым доступом, поэтому лучше вообще не обращаться напрямую к нулевому симво- * лу. Для этого есть специальные функции, которые будут рассмотрены немного позже.
Теперь посмотрим на примере, как можно работать с отдельными символами в строке.
Листинг 5.10. Работа с отдельными символами
var
Str:tosiString;
begin
Str: = ,Hello World1; //Присваиваем Str значение 1 Hello World*
Str[1] : =,T,;// Первому символу присвоишь значение 'Т* end;
После первого гпоисваивания переменной str в нее будет записана строка 'Н Цо World' Второе присвоение заменит в переменной str первому сим
волу присвоим значение ' Т L
Так как строка — это набор символов, а символ — число, указывающее на конкретный символ в таблице, мы можем создавать строки из чисел. Например, если мы хотим присвоить переменной str строку *Hello World* плюс символы конца строки и перевода каретки (обеспечивают переход на новую строку), нужно воспользоваться числовыми значениями, потому что на югавиатуре нет этих символов, и мы не можем их набрать. Символ конца строки в таблице символов находится в позиции #13 (шестнадцатеричное значение — $D), а символ перевода каретки — в позиции #10 ($А). Обратите внимание, на необходимость указания знака решетки перед числовым значением. Таким образом, наш код будет выглядеть так, как показано в листинге 5.11.
Листинг 5.11. Работа с числовыми представлениями символов
var
Str:AnsiString;
begin
Str: = *Hello World*#13#10;
//Присваиваем str значение * Hello World1 +#13#10.
Str:=#100#123#89; //Присваиваю Str строку из символов //в числовом представлении. end;
В дальнейшем очень часто в примерах можно будет встретить тип char. Этот тип данных определяет только один символ. Мы редко будем использовать его в чистом виде, в основном он будет присутствовать в программах в виде массива символов pchar, о котором будет говориться немного позже.
Чаще всего будет использоваться тип string, потому что он очень удобен и практически ничем не отличается от остальных типов, которые описаны выше. Объявляется этот тип следующим образом:
Листинг 5.12. Работа с типом данных string
var
s : String;
;.- >ring[200];
В этом примере объявляются две строковые переменные s и si. Первая объявлена как простая строка типа string, а у второй после типа в квадратных скобках стоит число 200. Эго число определяет длину строки в символах.
Когда переменная такого типа хранится в памяти, то ее символы Е1умеруют- ся с единицы. Если вы хотите прочитать 2-й символ, нужно обращаться к нему как s [2]. В этом случае тип string схож с уже описанными.
Если нужно узнать длину строки, то для этого можно воспользоваться функцией Length, а чтобы установить длину, используйте SetLength. Хотя с процедурами и функциями мы будем знакомиться немного позже, рассмотрим здесь маленький абстрактный пример.
Листинг 5.13. Определение длины строки
var
s:String;
1:Integer
begin
s: =1Привет I!!*;
1:=Length(s);
SetLength(s, 50); end;
В листинге 5.13 в первой строчке кода строковой переменной s присваивается произвольный текст (его содержимое не имеет значения). Во второй определяется длина строки. Для этого в целочисленную переменную 1 записывается результат выполнения функции Length (s). В последней строчке кода вызывается функция SetLength, чтобы установить новую длину строки. Цифра 50 показывает значение новой длины строки.
Булевы типы
С помощью переменных этого типа очень удобно строить логику. Переменная булева типа может принимать только одно из двух значений — true или false. Вам это ничего не напоминает? Совсем недавно рассказывалось про биты, которые имеют два состояния 1 или 0, включен или выключен. Переменные булева типа занимают только один бит и принимают только два значения (1 или 0). Для удобства в программировании эти значения заменяются понятиями true (истина) или faj s^ (ложь) соответственно.
Лучше J4 понятнее (на мой взгляд) булевы переменные называть логическими переменными. Для объявления логических переменных используется слово Boolean. Рассмотрим пример работы с такими типами переменных.
Пистинг 5.14. Работа с логическими переменными
var
Ь: Boolean; // Объявляю логическую переменную Ь
Str:AnsiString; // Объявляю строковую переменную Str begin
b:=true;
if b=true then
Str: —Истина*
else
Str: = 'Ложь *
end; \
В этом примере объявляются две переменные: ь (логическая) и str (строковая). Потом происходит присваивание переменной ь значения true. Дальше требуются пояснения, потому что идет логическая конструкция if then, которая до этого момента не изучалась.
Мы уже с вами рисовали блок-схемы и в них использовали логику типа несли выполняется какое-то условие, то выполнить какое-то действие**. Конструкция if . . . then действует также. Слово if переводится как меслип. Слово then переводится как "то【В результате получается конструкция несли условие выполнено тон. В программе она выглядит как if условие выполнено then .…
Частным случаем этой конструкции является запись if ... then ... else. Слово else переводится как fиначеп. То есть, если условие выполнено, то выполнится то, что написано после then, иначе выполнится то, что написано после else.
Ранее говорилось, что все операторы в Delphi заканчиваются точкой с запятой. Эго нужно делать, чтобы отделить команды друг от друга, ведь одна команда может быть записана на две строки или две команды в одну строку. Так вот, после оператора, идущего перед else, никогда не ставится точка с запятой. В этом случае в примере, который представлен в листинге 5.14, не стоит точка с запятой после Str: = *Истина*, потому что потом идет else. Эго правило надо запомнить.
В примере проверяется условие, если переменная b равна true, то переменной str присваивается значение "Истина”,иначе — значение "Ложь ”,
В Delphi можно сравнивать логические типы в упрощенном виде. Например, предыдущий код можно написать так:
I:
Листинг 5,15* Упрощенный способ работы алогическими переменными
var
b: Boolean; / / Объявляю логическую переменную Ь
Str:AnsiString; // Объявляю строковую переменную Str
begin
b:=true;
if b then
Str:=•Истина*
else
Str:=*Ложь'
end;
b then. Если не указано, с чем правильное значение. Это зна- истину (равна ли она true), аb then. Если не указано, с чем правильное значение. Это зна- истину (равна ли она true), аВ этом примере просто написан оператор if мы сравниваем, то проверка происходит на чит, что переменная будет проверяться на значит, этот код идентичен предыдущему.
b then. Если не указано, с чем правильное значение. Это зна- истину (равна ли она true), а
b then. Если не указано, с чем правильное значение. Это зна- истину (равна ли она true), а
Массивы
Последний тип данных, с которым мы познакомимся, это массивы. Конечно же, сейчас не будут расписываться все возможные их типы, потому что здесь еще рано делать. Мы познакомимся с остальными типами по мере надобности.
Массив— это просто последовательность переменных одного типа. Например, массив целых чисел будет выглядеть так: 15, 23, 36, 41. Для того чтобы объявить переменную типа "массив”,нужно в разделе var сделать запись:
Имя переменной : array [диапазон значений] of Тип переменных в массиве
Диапазон значений Оформляется В виде иНачальное значение . . Конечное значение”. Между начальным и конечным значением ставятся две точки. Рассмотрим пример объявления массива из 100 целых чисел.
Листинг 516. О1эъявление массива из 100 элементов
var
b:array [0..99] of Integer;
begin
b[0]:=1;
b[l]:=2;
end;
В этом же примере показано, как осуществить доступ к элементам массива. Как видите, это делается так же, как мы получали доступ к отдельным буквам в строках. Строки — это тоже массивы, поэтому доступ к их элементам одинаковый.
Единственная разница в том, что здесь мы обращаемся к элементам с нуля, потому что объявили массив значений от 0 до 99 включительно, что равно 100 числовым значениям. Но никто не мешает нам объявить массив от 1 до 100. Просто в программировании принято вести отсчет с нуля, и мы будем придерживаться этого правила.
工 Примечание 】
Почему все программисты считают начиная с нулевого значения? Вспомните, как компьютер работает с числами. Любое шестнадцатеричное значение (байт, слово, двойное слово) изменяется от 0 до какого-то значения. Именно поэтому ноль и должен использоваться. В противном случае мы сразу сокращаем возможности адресации ровно на единицу.
Странный PChar
Из основных типов, которые нам понадобятся в будущем, мне осталось рассказать только про тип PChar. Этот тип широко используется в WinAPI- функциях (функции ОС Windows), и когда мы будем обращаться к ним напрямую, то для передачи строк придется использовать именно этот тип, потому что старые WinAPI-функции не могут работать с типом string.
Переменная типа Pchar — это указатель на начало строки, т. е. переменная указывает на первый символ строки в памяти машины. Когда программе надо обратиться к этой переменной, она обращается по этому адресу и начинает читать строку. Но как же тотда программа узнает длину строки? Переменная —это только указатель на начало, символы нумеруются с нулевого, и где же тогда хранится длина строки? Попробуйте догадаться. Если ничего не приходит в голову, я открываю секрет — нигде. Действительно, у строк типа PChar нигде не указывается длина строки.
Для того чтобы понять, как программа узнает ее длину, нужно вспомнить, как хранятся символы строк в памяти машины. Как вы помните, каждый символ — это число. Однако у нас есть одно число, которое не используется при кодировании символов, — это ноль. Так вот, когда программа читает
строку pchar, то читаются все коды символов по указанному адресу, пока не встретится нулевой код. Именно нулевой код является признаком конца строки.
Тип pchar нельзя использовать напрямую, потому что это указатель на память. По этому указателю должна .быть выделена какая-то ее область. Эго значит, что следующий пример будет недействителен.
инг 5Л7. Неправильная работа с типом PChar
var
s:Pchar;
begin
s:='Привет1; end;
В примере, показанном в листинге 5.17, объявлена строка s типа pchar и сделана попытка присвоить ей текст. Такая операция невозможна, потому что s — указатель и пока ни на что не указывает. Мы просто его объявили, но не выделили ему память. Если предыдущие типы ограничены в размере и Delphi может автоматически резервировать для них память, то для типа pchar размер не ограничен и нигде не указывается его длина. С типом string в этом отношении просто: если мы не указали его размер, то Delphi может зарезервировать максимальный размер — 255 символов. У pchar нет такого максимального размера и поэтому вся ответственность за выделения памяти под эту переменную ложится на программиста.
Про выделение памяти мы поговорим позже, здесь эту тему затрагивать слишком рано, но один способ объявления такой переменной мы можем рассмотреть.
Листинг 5J8 Выделение памяти для переменной типа rch—r
procedure TForml.ButtonlClick(Sender: TObject); var
s:array[0..200] of char;
si:PChar;
begin
si:=s;
end;
В этом примере объявлена переменная s, как массив из 200 элементов типа char. Тип char — это просто одиночный символ. Получается, что s — мае-
сив из 200 символов или, проще говоря, та же самая строка. Здесь также объявлена переменная si типа PChar.
Между begin и end имеется только одна строчка кода. В ней переменной si присваивается значение переменной s. Теперь si указывает на область памяти, в которой находится массив из 200 символов.
Этот способ мы будем использовать редко, потому что чаще всего мы будем работать с типом String.
Константы
Константы ——это переменные, значение которых нельзя изменить. В отличие от переменных, тут не надо указывать тип переменной. Вы просто пишете имя и значение:
const
Pi 二 3.14;
MessageText = 1 Текст сообщения об ошибке *;
Константы описываются в разделе const. Определение констант идет до определения переменных. После описания вы можете использовать их как простые переменные, но не можете изменять из значения.
const
Pi = 3.14;
var
si:Integer;
begin
sl:=Pi*5;
,4
end;
Совет 3
Я рекомендую вам всегда применять константы, когда необходимо несколько раз использовать определенное число. При проектировании программы вы можете решить, что это число вам подходит, но потом окажется обратное и придется во всем коде изменять выбранное ранее число. При использовании констант вам достаточно будет изменить только ее значение.
Как вы уже поцяли, тип констант определять не надо. Но если нужно указать принадлежность их к определенному типу, то это можно сделать следующим образом:
const
Pi: real = 3.14;
MessageText : String = 'Текст сообщения об ошибке *;
Для простых типов все это не обязательно, но иногда возникает необходимость в явном указании типа.
Всемогущий Variant
Когда на этапе проектирования программы мы не можем четко определить тип данных определенной переменной, можно воспользоваться очень мощным типом Variant. Этот тип вводился для поддержки OLE (Object Link and Embedding, или внедрение и связь объектов, — мы будем рассматривать эту технологию позже), где используется почти на каждом шагу. Но сейчас мы можем найти и другое применение этому типу данных. Переменные этого типа могут принимать любые значения. Вы присваиваете что угодно, а программа сама определяет ее тип на этапе выполнения. Давайте рассмотрим следующий пример.
var
Variant;
begin
V:=5;
= ,Это текст1;
v :=true;
v :=3.14;
end;
В первой строке содержимое переменной v будет восприниматься как целое число. Во второй строке как текст. Далее будет логическая переменная, и в последней строке мы превращаем переменную v в число с плавающей точкой. Таким образом, при выполнении этого кода переменная изменит свой тип четыре раза, и код выполнится корректно.
Когда программа встречает переменную типа variant, она автоматически выделяет для нее память в зависимости от хранимого значения. Если значение не указано, то переменная инициализируется нулевыми значениями.
(Совет )
Старайтесь использовать этот тип только там, где это действительно необходимо. Не надо все переменные объявлять как Variant только потому, что это можно. Отлаживать такие приложения сложно, и когда вы попытаетесь, например, сложить переменную ™па Variant, содержащую число, со строкой, можете получить не тот результат, который ожидали. Поэтому старайтесь указывать реальные типы.
Рассмотрим следующий программный код.
var
V2 / V3 : variant;
begin
=,1,;
vzis1;
V3:=10;
V1:=V1+V2+V3;
end;
Как вы думаете, какой результат будет в переменной VI? На первый взгляд, должно быть число 16, если программа попытается сложить все как числа, или строка "1510”, если сложит как строку. А реально будет 25, потому что сначала будет сложение строк Т и "5", в результате чего получиться 15, а потом произойдет сложение чисел 15 и 10.
Процедуры и функции в Delphi
Мы уже познакомились с процедурным программированием теоретически. Сейчас нам предстоит узнать, как это выглядит на практике.
Процедуры и функции — это некий участок кода, который выделен в отдельный блок. Простая процедура выглядит так, как показано в листинге 5.19.
Листинг 5Л9. Пример процедуры
Procedure Exampl ();
var
Integer; / /Объявление локальной переменной begin
=10 ; / /Присваиваю переменной значение
end;
Любая процедура начинается с ключевого слова Procedure. После этого слова идет ее имя. В нашем примере она называется Exampl. После имени идут скобки, в которых можно указывать параметры, передаваемые в процедуру. Что такое параметры, мы узнаем чуть позже, а пока мы должны знать, что если параметров нет, то нужно ставить пустые скобки или вообще ничего не ставить.
Внутри процедуры после слова var идет объявление локальных переменных. Здесь объявляется одна переменная i типа integer (целое число). Мы уже
знакомы с глобальными переменными, которые появляются при старте программы и уничтожаются после выхода, i — это локальная переменная, и ее можно использовать только внутри процедуры. После выхода из нее переменная автоматически уничтожается.
Код процедуры начинается после слова begin и заканчивается после слова end. Внутри блока begin . .. end процедуры переменной i присваивается значение 10. В принципе, ничего не происходит, потому что после присваивания числа процедура заканчивается и переменная i уничтожается.
Для вызова процедуры достаточно указать ее имя Exainpl.
Если процедура относится к объекту (т. е. является его методом), то нужно написать в объявлении имя объекта, а после точки имя процедуры. Вот пример процедуры, относящейся к объекту формы Forml.
Листинг 5.20 Метод объект: в виде процедуры
Procedure TForml.Examp2;
begin
Exanpi; //Вызываем процед/ру Exarapi, назжсанную ранее.
end;
В процедуре Ехашр2 вызывается написанная ранее Exampi. Все имена процедуры должны начинаться с латинских букв и могут заканчиваться цифрами. Нельзя использовать в имени русские буквы, а также имена процедур не могут начинаться с цифр.
Внимание
Если процедура не относится к объекту, она должна быть описана до начала использования.
Рассмотрим еще один пример использования процедур.
Листинг 5 21- Примеры вызовов процедур
procedure Ехашр2;
begin
Exairipi; //Произойдет ошибка, потому что проце,1^ра Exairipi озжсана ниже end;
procedure Exairpi; var
i:Integer;
begin
i:=10;
end;
procedure Exarap4;
begin
Exampl (); //Здесь ошибки не будет, потому что Exampl оттасана вьтте. end;
В листинге 5.21 дважды вызывается одна и та же процедура. В первом случае произойдет ошибка, потому что вызов процедуры происходит раньше, чем ее описание. Во втором случае ошибки не будет.
При первом вызове мы написали только имя без указания скобок, потому что у процедуры нет параметров. Во втором случае скобки опушены. Оба вызова идентичны. Таким образом, когда у процедуры нет параметров, можно указывать скобки или не указывать. Это остается на ваше усмотрение, но желательно для процедур и функций при вызове всегда указывать скобки. Без скобок вы не поймете, что такое Exampl — процедура или переменная. Вот если скобки указаны, то сразу видно, что это вызов именно процедуры.
Кода вы начнете писать большие проекты, то уследить за всеми именами будет тяжело. Вы должны при первом взгляде^ определять, что перед вами. Компилятор Delphi дает вам небольшую свободу, но пользоваться ей надо аккуратно, чтобы потом не усложнить себе жизнь.
Если процедура относится к объекту (является его методом), то не имеет значения, где она написана и где вызывается, т. к. объекты имеют область описания (которую мы уже рассматривали) и она доступна компилятору.
Листинг 5.22. Описание методов объекта
type
TForml 二 class(TForm)
private:
procedure Exampl;
procedure Examp2;
public:
procedure ЕхапрЗ;
procedure Exam.p4;
end;
По этому описанию компилятор узнает о существовании процедур, поэтому вы можете их реализовывать в любом порядке, ошибок не будет. Мы уже знакомы с такими описаниями, они находятся, как показано в листинге 5.22, в начале модуля.
Теперь разберемся с функциями. Это те же процедуры, только они умеют возвращать значения. Простейшая функция выглядит так, как это показано в листинге 5.23.
| Листинг 5.23. П^им^р функции
function Exampl:Integer;
var
i:Integer;//Объявление локальной переменной begin
i:=10;//Присваиваю переменной значение
Result:=i; // Возвращаю значение i
end;
Я объявил функцию, которая будет возвращать значение типа integer (целое число) function Exampl : integer. Тип возвращаемого функцией значения указывается после имени и двоеточия. Для возврата значения его нужно присвоить переменной Result, как это делается в примере.
Вызов функции осуществляется следующим образом.
Листинг 5.24. Вызов функции
Procedure TForml. Ехапр2;
var
х:Integer;
begin
x: =Exampl; //Вызываем функцию Exairpi, натшсаы^ую ранее. end;
\
В этом примере переменной х присваивается значение, возвращаемое функцией Exampi.
Все остальные правила объявления функций такие же, как и у процедур. Теперь посмотрим, как можно передавать значения внутрь процедур и функций.
Листинг5.25. Передача значения в функции
Function Exampl(index:Integer):Integer;
begin
Result: =index* 2; / / Возвращаю переданное значение index, / / умноженное на 2 end;
Как показано в листинге 5.25, после имени функции б скобках указывается тип переменной, который можно передать внутрь ее или процедуры. В данном случае это переменная index типа integer. После скобок указывается двоеточие и тип возвращаемого значения. Здесь возвращается значение также типа Integer.
Что же будет возвращать наша функция? Результат ее выполнения можно записывать в Result или присваивать самому имени функции. В приведенном примере переменной Result присваивается результат вычисления выражения index*2. Эта переменная нигде не описана, но она зарезервирована как переменная, возвращающая значения из функции.
Результат можно присваивать и имени функции. Вот как это будет выглядеть на примере.
Листинг 5.26. Возврат результата через имя функции
Function Exampl (index: Integer) : Integer;
begin
Exampl : =index* 2; // Возвращаю переданное значение index, // умноженное на 2
end;
Вызов функции, определенной в предыдущем примере, будет осуществляться следующим образом.
Листинг5.27. Вызов функции
procedure ТForml. Examp2 ;
var
х:Integer;
begin
X: =Ехашр1 (20); //Вызываем процедуру Exampl, написанную ранее.
end;
Здесь в функцию Exampl передается значение 20, а она вернет 20, умноженное на 2, т. е. 40 (см. листинг 5.26).
Все рассмотренные примеры оперировали функциями. Однако точно так же можно поступать и с процедурами, передавая им значения,
工 Примечание
Помните, что процедуры и функции практически одно и то же. Разница только в том, что функции умеют возвращать значения. С этим мы уже знакомы из теории, но теперь увидели и на реальных примерах.
Попробуйте сейчас внимательно посмотреть на следующий пример и найти в нем ошибку.
Листинг5.28. Ошибочная процедура
Procedure TForml. Examp2;
var
x:Integer;
begin
Result:=x*20;
end;
В этом примере переменной Result присваивается значение, вычисляемое из выражения — х*20. Вроде все правильно, но это же процедура, а она не может возвращать значение. Значит, компилятор выдаст ошибку на то, что переменная Result не определена.
И последнее, что необходимо здесь рассмотреть, — это досрочный выход из процедур или функций. Когда процедура выполняет заложенные внутри нее операторы и встречает среди них оператор exit, производится моментальный выход из этой процедуры. Рассмотрим эту ситуацию на примере.
Листинг5 29 Прерывание выполнения процедуры
procedure Т Fo rml. Examp2;
var
x:Integer;
begin
x:=2O;
exit;
x:=10; // Этот код никогда не будет выполнен, end;
Здесь переменной х присваивается значение 20. Потом программа встречает оператор exit и производит мгновенный выход из процедуры, поэтому строчка с присваиванием переменной х значения 10 никогда не будет выполнена.
Этот пример не совсем удачный, поэтому давайте рассмотрим функцию, которая будет возвращать результат деления двух переданных значений.
Листинг 5.30. Вызов функции с двумя параметрами
function Exampl(indexl,index2:Integer):Real;
begin
Exampl:=indexl/index2;
end;
ч procedure Examp2;
var
x: Real;
begin
х:=Ехапф1 (20,10) ; //Вь1зываем функцию Exairpl, написанную ранее. end;
В этом примере фуньщии Exampl передаются два значения. Обе переменные, indexl и index2, в которых хранятся эти значения,——целого типа. В качестве результата функцнн возвращается частное от деления indexl на index2. В процедуре Ехатр2 показан пример вызова функции.
Какому-то параметру может быть задано значение по умолчанию. Такие параметры должны быть в конце объявления. Вот как преобразовать в этом случае код, представленный на листинге 5.30.
Листинг 5.31. Использование функции с заданным по умолчанию параметром
Function Exampl(indexl:Integer;index?:Integer=2):Real;
begin
Exampl:=indexl/index?;
end;
procedure Examp2;
var
x:Real;
begin
x:=Exanipl (20); //Вызываем ттроцедуру Exampl, написанную ранее.
x:=Exampl (20, 3); //Вызываем ттроцедуру Exanipl, налисанную ранее, end;
Здесь у нас объявлена функция с двумя параметрами. Второму параметру указывается значение, по умолчанию равны 2. Далее показаны два примера вызова этой функции. В первом случае указан только один параметр. В качестве второго будет значение по умолчанию. Это значит, что число 20 будет разделено на 2. Во втором случае указаны два параметра и здесь число 20 будет разделено на то число, которое мы указали в качестве значения второго параметра.
Теперь допустим, что в index2 было передано значение 0. В этом случае будет произведена попытка деления на 0, что вызовет ошибку. Вполне логичным было бы сделать проверку, если в index2 содержится 0, то выйти из функции. На практике это будет выглядеть следующим образом.
Листинг 5.32. Логика функции с прерыванием выполнения
Function Exampl (indexl, index2: Integer) : Real;
begin
Если index2равен 09 mo exit.
Exampl:=indexl/index2;
end;
procedure Examp2;
var
x:Real;
begin
x: =Exampl (20, 0) ; //Вызываем процедуру Exairpl, написанную ранее.
x:=х*2; // Здесь произойдет ошибка
end;
Здесь логика программы записана обычными словами, потому что более подробно мы будем изучать ее в следующей главе. Сейчас нам достаточно только понимать смысл записанного кода.
В этом примере, программа которого представлена в листинге 5.21, проверяется значение переменной index2 на ноль, и в случае равенства организуется выход из функции.
Если index2 действительно будет равна нулю, то в этом случае функция вернет неопределенное значение, потому что результат вычисляется после
оператора выхода. Если потом попробовать воспользоваться возвращенным неопределенным значением, то произойдет ошибка. В примере специально показана строка с попыткой умножения переменной х на 2. Неопределенное значение нельзя использовать. В переменную х мы пока еше не заносили никакого значения, поэтому не используйте ее.
Чтобы избавиться от таких проблем, можно при входе в функцию сразу задавать значение по умолчанию.
Пистинг 5.33. Задание возвращаемого значения при входе в функцию
Function Exampl(indexl,index2:Integer):Real;
begin
Result:=1;
Если index2равен 0, mo exit.
Result:=indexl/index2;
end;
В этом примере мы в первой же строке присваиваем переменной Result значение 1. Теперь у нас результат определен с самого начала. После этого проверяется второй параметр на ноль, и если равенство верно, то произойдет выход. Однако после такого выхода у нас не будет неопределенных значений, потому что Result уже содержит значение 1.
Если второй параметр не равен нулю, то выполнится деление первого параметра функции на второй и результат запишется в Result.
Этот пример наглядно показывает, что Result работает как простая переменная, хотя она нигде не описана. Она всегда существует в функциях и имеет тип возвращаемого функцией значения.
(Совет)
Если кто-то подумал, что переменной Result надо присваивать значение только в самом конце программного кода или по ней происходит выход из функции, это не так. Result можно изменять где угодно и можно даже использовать как простую переменную. Тип у этой переменной соответствует типу возвращаемого функцией значения. Помните эту особенность, когда будете использовать переменную Result.
Рекурсивный вызов процедур
Вы наверно уже слышали про такое понятие, как рекурсивный вызов. Если не слышали, то сейчас нам предстоит с ними познакомиться. Рекурсивный вы-
зов — это когда процедура вызывает сама себя. Допустим, что внутри процедуры нужно выполнять абсолютно один и тот же код, только с другими параметрами. Писать из-за этого новую процедуру смысла нет, потому что код повторится. А если таких вызовов будет десять? Тогда программа может необоснованно вырасти.
Вспомним нашу классическую задачу — расчет факториала. Мы уже научились ее решать с помощью цикла, а теперь будет показано, как рассчитать факториал с помощью рекурсивного вызова процедур. Хотя этот пример неэффективен и легче (да и быстрее) сделать то же самое с помощью простого цикла, все же рассмотрим его чисто в познавательных целях.
Для расчета нам понадобится функция, назовем ее MulNumber. Ей будет передаваться одно число, а возвращаться будет результат умножения переданного числа на число, меньшее на единицу. •
Листинг 5.34. Функция для расчета факториала ''
Function TForml.MulNumber (index: Integer) : Integer; begin
Result:=Index*Index-1;
end;
Если мы будем пользоваться такой функцией, то потребуется вызывать ее для каждого числа факториала. Это никому ненужно, поэтому давайте добавим сюда рекурсию, как показано в листинге 5.35.
Рекурсивный вызов
function TForml.MulNumber(index: Integer): Integer; begin
Result: =Index*MulNuniber (index-1);
end;
Теперь переменная index умножается на результат вызова функции MulNumber с параметром, на единицу меньшим, чем index. Получается, что прежде чем перемножить index и MulNumber, сначала выполнится процедура MulNumber и потом уже произойдет умножение. Но при расчете MulNumber с новым значением опять будет вызвана эта же функция, но с еще более маленьким значением. В принципе, нас это устраивает, потому что нужно произвести перемножение всех чисел от начального значения index и до 1. Но как программа узнает о том, что нам нужно остановиться на этой единице? Да никак. Она будет продолжать уменьшать index и снова вызывать
саму себя с новым параметром. Так переменная index уменьшиться до отрицательного значения и будет быстро уходить в бесконечность. Такая ситуация плачевна и приводит к ошибке программы, потому что рекурсию невозможно прервать.
Мы сами должны написать код, который будет прерывать рекурсию.
Листинг 5.36. Прерывание рекурсии
Function TForml.MulNumber (index: Integer) : Integer; begin
if Ind.ex=l then
begin
Result:=1;
exit;
end;
Result: = Index*MulNumber (index-1);
end;
Здесь вначале происходит проверка, равенства index 1. Дальше уже рассчитывать не надо и нужно выходить из процедуры. В качестве результата возвращается 1, потому что его перемножение на другое число при расчете факториала не повлияет на общий результат. Таким образом, мы сделали прерывание на 1, и после этого рекурсия заканчивается. При index равной единице не произойдет очередного вызова процедуры MulNumber.
Теперь, чтобы рассчитать пример, мы должны всего лишь из любой точки вызвать эту процедуру и указать в качестве параметра число, факториал которого нам надо рассчитать.
Рассмотрим более полезный алгоритм с использованием рекурсии — поиск файла на диске. Это будет именно алгоритм, потому что показывать сейчас код будет слишком сложным занятием. В дальнейшем этот алгоритм мы реализуем в реальной программе, а пока ограничимся абстрактным его определением.
Листинг 5.37, Алгоритм поиска файлов
Function FindFile (Имя файла. Папка); begin
Получить список солержимого папки;
Проверить Содержимое папки;
Если фе「к файлов нет искомого,
то вызвать функцию FindFile для вложенных папок, чтобы повторить поиск там.
end;
Это чисто абстрактный алгоритм и в реальности код будет работать немного по-другому. Но главная его цель — показать принцип рекурсии и более полезный пример ее использования.
(Совет )
Работать с рекурсивным вызовом нужно осторожно, потому что он может вызвать зависание программы или даже компьютера.
Встроенные процедуры
Есть еще один очень интересный способ использования процедур. Допустим, что у вас есть какой-то часто повторяющийся код, который может вызываться только из одной процедуры. Если вы опишете этот код в виде отдельной, независимой процедуры, то его можно будет вызывать где угодно. Но зачем это нужно? Если вы уверены, что код будет вызываться только из одной процедуры, то имеет смысл написать его внутри именно этой процедуры.
В этом есть определенный смысл. В одном модуле не может быть двух процедур с одним и тем же именем. Поэтому, а когда вы создаете процедуру внутри другой, вы не блокируете имя и можете создать такую же процедуру с таким же именем, но внутри другой, получается, что нам нужно объявить пропедуру внутри другой процедуры. Как это сделать, показано в листинге 5.38.
Листинг 5.3 3. Встроенная процедура~ ]
Procedure Examp1(Sendeг: TObject);
Function Suma (ir j : Integer) : Integer; begin
Result: =i+j;
end;
var
i:Integer;
begin
i:=Suraa(10,20);
end;
Здесь объявлена функция Suma внутри процедуры Exampl, об этом говорит то, что функция описана после объявления процедуры Exampl и до начала ее раздела var. Такую функцию можно без проблем вызывать, но только из кода процедуры Exampl. Если попытаться ее вызвать из другого места, то произойдет ошибка.
Листинг 5.39 Вызов встроенной процедуры
procedure Exampl(Sender: TObject);
function Suma(ir j:Integer):Integer; begin
Result:=i+j ;
end;
var
i:Integer;
begin
i:=Suma(10r 20);
end;
procedure Examp2(Sender: TObject);
var
i:Integer;
begin
i:=Suma (10,20); //Здесь будет ошибка. end;
В последнем примере, приведенном в листинге 5.39, добавлена новая процедура Ехатр2 и сделана попытка из нее вызвать функцию Suma. Но это невозможно, потому что здесь эта функпия недоступна. Ее можно вызывать только из Exampl, где она и описана.
Возврат значений через параметры
Когда говорилось, что функции отличаются от процедур возможностью возврата параметров, имелось в виду, что сама процедура не может возвращать значения. Но и те и другие могут изменять значения передаваемых параметров, а значит, возвращать результаты через параметры. Для этого в процедуре или в функции параметр должен быть объявлен как var (см. листинг 5.40).
Лисп1НГ 5.40. Возврат значений через параметры
procedure Exampl1(var Str:String);
begin
Str:='Тест*;
end;
procedure Exampl2(var Str:String);
var
S:String;
begin
Exempli(S);
//Здесь S содержит значение 1Привет1 end;
Здесь параметр функции Exampll объявлен как var, и мы присваиваем ему строку. В процедуре Ехашр12 вызывается первая процедура, передавая в качестве параметра строковую переменную s. Если бы в процедуре Exampll параметр не был бы объявлен как var, то после выхода из Exampll переменная s не получила бы присвоенное ей значение, а так значение переменной изменилось.
(Совет )
Можем объявить хоть все параметры изменяемыми (поставить перед ними ключевое слово var) и наша процедура или функция сможет изменять их, т. е. возвращать через них свои значения. Иногда это очень полезно, но если нужно вернуть только одно значение, то желательно пользоваться функциями.
Перегрузка
Внутри модуля или объекта имена процедур н функций должны быть уникальными. Если вы попытаетесь создать две процедуры или процедуру и функцию с одним и тем же именем, то компилятор выдаст ошибку. Но что делать, если вам нужно выполнить схожие действия с разным количеством параметров или с разными типами данных? Неужели придется каждый раз выдумывать новое имя? Конечно же нет. Начиная с Delphi 4, появилась новая возможность — перегрузка процедур, функций и методов объектов.
Благодаря перегрузке можно создавать любое количество процедур с одним и тем же именем, главное, чтобы количество или тип передаваемых пара-
метров отличались. Именно по параметрам программа и определит, какая именно процедура вызывается.
Чтобы определить перегруженную процедуру, после ее имени нужно написать, как показано ниже, слово overload.
Листинг 5.41. Определение перегружаемой процедуры г
procedure Sum(i, j : Integer) ; overload;
begin
end;
В следующем листинге показан пример трех функций Sum с разным количеством и типом параметров. При вызове процедур программа правильно определяет, какую из них вызвать.
Листинг 5.42. Определение порядка вызова функций
function Sum(i, j : Integer) : Integer;overload; begin
Result:=i+j;, end;
function Sum(ir j, z: Integer) : Integer;overload; begin
Result:=i+j+z;
end;
function Sum(i『j : String) : String;overload;
begin
Result:=i+j;
end;
procedure test;
var
i:Integer;
begin
i:=Sum(10, 5)+Sum(lr2,2);
Sum (* Привет *, * Кгк 力: 〃знь,); end;
(Совет )
Если есть возможность, то желательно в первое время не использовать перегрузку. Если вы ошибетесь при вызове или забудете указать какой-то параметр, то вызовется не та функция, которую вы ожидаете. Такую ошибку потом определить очень сложно. Компилятор при сборке программы проверяет только синтаксис и некоторые ошибки может предсказать, но не может найти ошибки в логике программы. Вам нужно набраться немного опыта в программировании и тогда вы сможете вылавливать опечатки, приводящие к неверной работе программы.
Методы объектов
Методы — это процедуры и функции, которые принадлежат объекту. Мы уже с ними встречались. Теперь предстоит узнать о них немного больше.
Как вы уже знаете, методы описываются внутри объявления объекта и бывают нескольких типов.
口 static (статические) — это простые процедуры и функции. Если при описании метода вы ничего не указали, то используется именно этот тип. Для компилятора это самый простой тип метода, потому что в потомках такой метод не может быть изменен, и поэтому заранее можно узнать адрес этого метода в памяти.
virtual (виртуальные) — такие методы могут быть переопределены в потомках объекта. Например, если у вас есть объект гараж и метод ворота, то в его потомке этот метод может быть заменен улучшенной версией. Для определения адреса Delphi строит таблицу виртуальных методов, которая позволяет во время выполнения программы определить адрес метода. В такой таблице хранятся все методы текущего объекта и его предка.
П Dynamic (динамические) — эти методы схожи с виртуальными, но для определения адреса используется другой способ. Для каждого объекта строится таблица только из его методов. Каждому методу назначается уникальный индекс. В данном случае экономится память, потому что не надо хранить адреса методов предков, но для поиска любого из них тратится намного больше времени.
Message (сообщения) — такие метода реагируют на события операционной системы. Для большинства сообщений ОС Windows в Delphi уже есть специальные обработчики событий, но если вам нужно, чтобы метод реагировал на определенное событие, которого нет у компонента, необходимо определить его вручную (подробную информацию см. в гл. 23).
口 Abstract (абстравсгный) — такой метод будет только объявлен в объекте, а реализации у него не будет. Если в объекте есть хотя бы один такой метод, то он считается абстрактным. Такой объект нельзя использовать.
5 Зак 942
Реализацию метода должны сделать потомки, и именно потомками можно работать. Таким образом, вы можете в каком-либо объекте зарезервировать имя метода для потомков, чтобы они реализовали в нем какое-то действие.
Если вы объявили метод как virtual или dynamic, то можно переопределить их действия в наследниках.
Наследование объектов
Мы уже знаем, что такое на следование. Теперь предстоит познакомиться с этой возможностью на практике. Допустим, что у нас есть объект, описы- ваюший точку, — TMyPoint. Он может выглядеть следующим образом:
TmyPoint 二 class
protected
PointX, PointY:Integer;
public
constructor Create(X,Y : Integer) ; virtual;
procedure Draw; virtual;
end;
В данном случае у нас объект, который не имеет предков. В таких случаях берется базовый объект TObjecl. Он дает нам базовые функции, необходимые любому объекту. В разделе protected объявлены две переменные PointX и PointY, которые будут являться координатами точки. Это значит, что доступ к ним сможет получить только этот объект или его потомок.
В разделе public объявлены две процедуры, которые будут доступны всем. Первая — это конструктор (constructor) с именем create. Конструктор — это такая же процедура, только вместо ключевого слова procedure нужно написать constructor и дать имя create. Этой процедурой мы будем создавать данный объект во время выполнения программы. Внутри ее можно выполнить какие-то начальные действия (выделить необходимую память, установить значение переменных по умолчанию и т. д.).
В данном случае конструъсгору передаются координаты точки, и в нем мы можем сохранить эти значения, используя переменные Pointx и PointY. Вот как это буцет выглядеть:
constructor TMyPoint.Create(X, Y: Integer);
begin
PointX:=X;
PointY:=Y;
end;
Процедуру Draw мы не будем описывать, просто представим, что она выводит на экран точку.
Конструктор и процедура у нашего объекта объявлены виртуальными. Об этом говорит соответствующее ключевое слово (virtual) в конце объявления. Это сделано для того, чтобы потомки могли создавать свою реализацию конструктора и метода рисования.
Теперь допустим, что требуется создать объект линии. Для этого нам нужно описать свойства четырех координат, конструктор и метод рисования. Можно все это создать с нуля, а можно вывести из объекта точки, который уже имеет половину необходимого. Объявление объекта линии может выглядеть примерно так:
TmyLine 二 class (ТМуPoint)
protected
PointXl, PointYl:Integer;
public
constructor Create (X, Y, XI, Y1 : Integer); reintroduce;
procedure Draw; override;
end;
Этот объект у нас является наследником от TMyPoint, а значит, наследует все его свойства и методы. О том, что у нас объект наследник, говорится в объявлении имени базового объекта (в скобках после ключевого слова class).
В этом объекте объявлены две переменные PointXl и PointYl, которые описывают координаты второй точки. Координаты первой точки наследуются из базового объекта, поэтому нам не надо их описывать.
Метод create уже содержит четыре параметра, которые будут описывать координаты двух точек. После объявления этого метода стоит ключевое слово reintroduce. Зачем оно нужно? У базового объекта тоже есть конструктор, но у него два параметра, а у нашего конструктора четыре. Когда мы переопределяем метод и в новой версии количество параметров изменилось, то мы должны поставить такое ключевое слово, чтобы показать компилятору, что мы изменили параметры корректно.
Реализация конструктора может выглядеть следующим образом:
Constructor TMyLine.Create(X,Y,XI,Y1:Integer);
begin
inherited Create(X,Y);
PointXl:=X1;
PointYl:=Y1;
end;
В самом начале стоит ключевое слово inherited. Этим мы говорим, что нужно вызвать конструктор предка (объекта TMyPoint). После этого ключевого слова мы пишем имя конструктора предка и указываем, какие параметры нужно ему передать. В данном случае передаются координаты первой точки. Предок сохранит эти координаты в переменных Pointx в PointY. Когда количество параметров у предка и наследника одинаковое, то можно ПрОСТО указать КЛЮЧевое СЛОВО inherited;.
После этого мы сохраняем координаты второй точки в переменных PointXl HPointYl.
После объявления метода Draw у нас стоит ключевое слово override. Оно говорит о том, что мы перекрываем такой же виртуальный метод в объекте предка.
Чуть позже мы на практике научимся создавать объекты и работать с ними, а пока с теорией закончим.
Внутри каждого объекта существует переменная Self, которая указывает на него самого. Если вам нужно обратиться к переменной или методу объекта, явно указав принадлежность, то можно написать self .Имя. В принципе, эту переменную мы будем использовать, когда нужно вызвать какую-то процедуру и передать ей объект, из которого мы делали вызов.
CallFunc(Self);
В приведенном выше примере мы вызвали процедуру CallFunc, а в качестве параметра передали текущий объеьсг.
ГлавабГлаваб嚇
Главаб
Главаб
Работа с компонентами
Мы уже создали первую и самую простую программу, а также разобрались, из чего она состоит. Помимо этого, мы рассмотрели достаточно теории и готовы использовать эти знания на практике.
Первое, с чем мы далее начнем знакомиться, — это компоненты языка и примеры с их использованием. Вот это как раз то, с чего начинаются некоторые самоучители или книги по Delphi.
Изучение компонентов будет постепенное. Сначала рассмотрим компоненты в действии и Познакомимся с их основными возможностями. Это необходимо, чтобы дальнейшая теория подкреплялась практическими примерами, а книга читалась легче и проще. Начиная с этого момента, теория будет более запоминающаяся, и все будет лучше усваиваться. Когда будет набран достаточный запас знаний, тогда мы заглянем внутрь компонентов и увидим, как они устроены.
В Delphi уже собрана большая коллекция компонентов, позволяющих визуально создать любой интерфейс программы.
Основная форма и ее свойства
Основная форма — это окно будущей программы. На нем вы можете располагать визуальные компоненты в любом виде и порядке. В Delphi расстановка элементов управления происходит визуально, и все делается одним нажатием кнопки мыши. Это позволяет даже самый сложный пользовательский интерфейс сформировать в считанные минуты.
Как вы уже знаете, если выделить какой-то компонент, то в объектном инспекторе появятся его свойства и события. Сейчас мы рассмотрим основные свойства и события формы. Большинство из них присутствуют и у компонентов. Поэтому в будущем уже не будем повторяться.
工 Примечание
Когда описывается событие, которое может быть не только у формы, но и у компонента, будет присутствовать пояснение "форма/компонент". Когда будут рассматриваться свойства и их назначение, попробуйте создать новый проект в среде Delphi и изменять различные его свойства. Так лучше можно понять, на что эти свойства будут влиять.
Итак, начнем рассмотрение.
ActiveControl — указывает на компонент, который должен быть активным по умолчанию.
Align — выравнивание компонента. Любой компонент может быть выровнен по одной из сторон родительского компонента. Этому свойству можно присвоить следующие значения:
口 AlNone — нет выравнивания, как нарисовал, так и будет;
口 AlBottom — выравнивание по нижнему краю;
О AlLeft — выравнивание по левому краю;
口 AlRight — выравнивание по правому краю;
口 AlTop — выравнивание по верхнему краю.
Компоненты выравниваются относительно формы, а форма выравнивается относительно окна.
AlphaBlend (тип свойства — логический) — свойство формы, которое означает, имеет ли форма прозрачность. Если это свойство равно true, то окно будет прозрачным. На рис. 6.1 показан пример полупрозрачного окна. Степень прозрачности задается через свойство AlphaBlendvalue.
Рис. 6.1- Полупрозрачное окно
Внимание
Прозрачность работает в среде не всех операционных систем. Пример отлично выполнится BWin2000, ХР и старше, но выдаст ошибку в Win9x. ME, NT.
AlphaBlendValue (тип свойства — целое число) — степень прозрачности формы. Здесь можно задавать числовое значение степени прозрачности от О до 255. Если поставить 0, то форма будет абсолютно прозрачной. 255 означает полную непрозрачность. Чтобы сделать форму полупрозрачной, нужно выставить какое-нибудь промежуточное значение (например, 127).
工 Г^имечанме
На компакт-диске в папке \ Примеры \ Глава 6 \ Прозрачное окно вы можете увидеть пример программы. Проверено, что прозрачность работает в Windows 2000 и более поздних системах. В Win9x данный пример может не работать.
Anchors — это свойство есть и у формы, и у компонентов. Оно показывает, как происходит прикрепление к родительскому объекту. Это свойство раскрывающееся .Если щелкнуть по квадрату слева от имени свойства, то раскроется список из четырех дополнительных свойств.
口 AkLeft — прнкреилятв левый край (по умолчанию true).
口 AkTop — прнкрегглятв верхний край (по умолчанию true).
口 AkRight — прнкрегглятв правый край (по умолчанию flase).
О AkBottom — ПрИКреПЛЯТЬ НИЖНИЙ Край (ПО умОЛЧаНИЮ false).
По умолчанию прикрепление происходит по левому и верхнему краю.
Autoscroll (тип свойства — логический) — указывает на то, будет ли форма автоматически производить скроллинг, или нет.
Autosize (тип свойства — логический) — указывает на то, должны ли компоненты на форме автоматически корректировать размеры.
Borde г Icons — свойство, определяющее, какие кнопки должны присутствовать у окна (рис. 6.2). Это свойство раскрывающееся. Если щелкнуть по квадрату слева от'нменн свойства, то раскроется список из четырех свойств. 口 BiSysteniMenu — показать меню (иконка слева в строке заголовка окна) и другие кнопки заголовка окна.
口 BiMinimize — кнопка минимизации окна.
口 BiMaximize — кнопка максимизации окна.
口 BiHelp — кнопка помощи.
biMaximize
biMinimizebiMinimizebiSystemMenubiSystemMenuРис. 6.2. Элемекгы заголовка окнаРис. 6.2. Элемекгы заголовка окна
biMinimize
biMinimize
biSystemMenu
biSystemMenu
Рис. 6.2. Элемекгы заголовка окна
Рис. 6.2. Элемекгы заголовка окна
Borderstyle — свойство формы. Отвечает за вид оборки окна. Это свойство может принимать следующие значения.
О Bs Size able — установлено по умолчанию. Стандартное окно с нормальной оборкой, которое может изменять свои размеры (рис. 6.3).
口 BsDialog — окно выглядит как диалоговое (рис. 6.4).
Рис. 6.3. Окно с оборкой BsSizeable
Рис. 6.4. Окно с оборкой BsDialog
口 BsNone — окно вообще без оборки У такого окна нет оборки и меню, просто (квадрат с цветом фона) рабочая область.
口 BsSingle — на первый взгляд это простое окно, но у него нельзя изменить размеры. Оно имеет фиксированный размер, и изменять его мышкой нельзя.
口 BsSizeToolwin — окно с тонкой оборкой. Особенно это заметно в заголовке окна (рис. 6.5).
口 BsToolwindow — оно ничем не отличается от предыдущего. Единственная разница — у этого окна нельзя изменять размеры окна.
Рис. 6.5. Окно с оборкой bsSizeToolWin
Примечание ?
Все примеры окон тиожно найти на прилагаемом к книге компакт-диске в папке \ Примеры \ Глава 6 \ Окна \. Попробуйте создать приложение и самостоятельно поэкспериментировать с описанными свойствами.
Borderwidth — ширина оборки окна. Пока что все окна, которые здесь рассматривались, имели ширину оборки, равную нулю. На рис. 6.6 показано окно с оборкой, равной 10. В центре окна растянуто на всю площадь поле для ввода текста и, несмотря на это, в окне по краям видны широкие оборки. Для большего эффекта на форму помещена еще и кнопка, которая исчезает при пересечении с оборкой.
caption — это строковое свойство, которое отвечает за заголовок окна. Мы уже использовали его, когда писали программу "Hello World*'.
ClientHeight — это свойство в виде целого числа. Показывает высоту клиентской области окна. Высота берется без учета ширины оборки и системного меню, только рабочая область.
Рис. 6.6. Окно с широкой оборкой
ClientWidth — это свойство в виде целого числа показывает ширину клиентской области окна. Ширина берется без учета ширины оборки и системного меню, только рабочая область.
Color — цвет клиентской области окна. В списке выбора есть все системные цвета, которые вы можете выбрать. Но если вы [Хотите использовать какой- то специфичный цвет, можете дважды щелкнуть по этому параметру, и перед вами откроется стандартное окно выбора цвета. В этом окне можно выбрать любой цвет. Но лучше использовать системные цвета.
К~ Примечание
На компакт-диске в папке \ Примеры \ Глава 6 \ Цвет окна вы можете увидеть пример программы с желтым цветом окна.
constraints — в этом свойстве содержатся максимальные и минимальные значения размеров окна. Таким образом, можно ограничить размеры окна, в которых оно и будет изменяться. Эго свойство раскрывающееся и содержит следующие параметры.
口 MaxHeight — максимальная высота окна.
口 Maxwidth — максимальная ширина окна.
П MinHeight — минимальная высота окна.
口 MinWidth — минимальная ширина окна.
Если вы установите эти значения, то окно нельзя будет растянуть больше максимальногоразмераиуменьшитьменьшеминимального.
工 Примечание ;
На компакт-диске в папке \ Примеры \ Глава 6 \ Размер окна вы можете увидеть пример программы, в которой главное окно нельзя увеличить более чем 400x400 и меньше200x200.
Ctl3D (тип свойства — логический) — указывает показывать окно/компонент в псевдо-ЗО-плоскости или нет. Этот параметр остался еще от Windows 3.1, когда он действительно имел смысл. Сейчас, даже если вы отключите 3D, окно сильно не изменится. Поэтому про это свойство можно забыть.
Cursor — это свойство отвечает за курсор, который будет отображаться при наведении указателя мыши на форму/компонент. Здесь могут быть, как показано в табл. 6.1, доступны сразу несколько форм курсоров.
Таблица 6Л. Виды курсоров
Имя курсора
ВИД
Имя курсора
Вид
CrNone
Нет
CrArrow
CrCross
CrlBeam
CrSizeNESW
CrSizeNS
CrSizeNWSE
CrSizeWE
CrUpArrow
crHourGlass
CrDrag
CrNoDrop
tfx人)
CrHSplLt
CrVSplit
CrMultiDrag
♦飞
crSQLWait
SQL
CrNo
crAppStart
CrHelp
Q? 1
crHandPoint
CrSize
CrSizeAll
DockSite (тип свойства — логический) — указывает, можно ли на форму/ компонент бросать другие компоненты с помощью Drag & Drop. Это свойство создает эффект, который вы могли наблюдать в MS Office, когда панели инструментов можно отрывать от формы и прикреплять обратно. Это свойство как раз и разрешает выполнять эти действия.
Примечание
На компакт-диске в папке \ Примеры \ Глава 6 \ Dock вы можете увидеть пример программы, в которой можно отрывать панель от формы и прикреплять обратно.
DragKind — вид перетаскивания объекта при Drag & Drop. Здесь вам доступны два варианта.
口 DkDrag — стандартный Drag & Drop, при котором объект остается на месте. Этот тип удобен при копировании объектов. Вы как бы перетаскиваете компонент на то место, где должна быть создана копия, но при этом исходный объект остается на месте.
口 DkDock — перетаскивается сам объект. Этот параметр следует выбрать, если нужно, чтобы компонент мог прикрепляться к другим компонентам или форме. В примере к предыдущему свойству панель имеет именно такое свойство, чтобы могла прикрепиться к форме.
DragMode — режим Drag & Drop. Здесь доступны также два варианта.
口 DniManual — ручной режим. При таком режиме вы сами должны запускать перетаскивание объекта.
口 DmAutomatic — режим Drag & Drop. Он будет включаться автоматически, если пользователь начал перетаскивать компонент мышью. При этом не нужно писать дополнительный код, как при ручном режиме.
Enabled (тип свойства — логический) — определяет доступность компонента. Если это свойство равно true, то пользователь может работать с этим компонентом. Иначе компонент недоступен, окрашен серым цветом, и вы, как пользователь, не сможете его использовать (рис. 6.7). Например, если это кнопка, то вы не сможете на нее нажать.
Enabled-trueEnabled-trueРис. 6.7. Доступность компонентаРис. 6.7. Доступность компонента
Enabled-true
Enabled-true
Рис. 6.7. Доступность компонента
Рис. 6.7. Доступность компонента
& Примечание
На компакт-диске в папке \ Примеры \ Глава 6 \ Enable вы можете увидеть пример программы, в которой есть две кнопки на форме. Одна из них доступна, а другая нет.
Font — шрифт, используемый при выводе текста на форме. Если вы дважды щелкнете по этой строке, то перед вами появится стандартное окно Endows выбора шрифта:
FormStyle — стиль формы. Здесь доступны для выбора следующие варианты.
П FsNormal — нормальное окно.
П FsMD工Form — окно является родительским для МЛ/-окон. Если вы помните старый Office, то должны знать, что там внутри основного окна можно было перемещать другие окна. Эти окна относятся к классу MDI — мультидокументные окна. Хотя Microsoft не рекомендует использовать MDI-окна, и вроде как сама отказалась от их использования, в Windows 2000 консоль ММС выполнена именно так (рис. 6.8). В некоторых случаях многодокументная архитектура остается неизменной и будет жить еще долго.
Рис. 6.8. MDl-интерфейс консоли Windows
О FsMDlChild — окно является дочерним MDI-окном. FsMDiForm создает главное окно, a FsMDichild создает дочернее, т. е. то окно, которое будет внутри главного.
ВниманиеВниманиеЕЕ
Внимание
Внимание
Е
Е
Если вы попытаетесь создать главное окно с таким параметром, то программа не запустится и вы увидите ошибку.
О FsstayOnTop — окно с этим параметром будет находиться всегда поверх остальных.
Height (тип свойства — целое число.) — высота окна.
Hint (тип свойства — строка) — текст подсказки, который будет появляться в строке состояния при наведении указателя мыши на форму/компонент.
HorzScrollBar — параметры горизонтальной полосы прокрутки. Этот параметр пока не будет рассматриваться, потому что если его раскрыть, то там будет столько настроек, что это тема отдельного разговора.
Icon — иконка, отображаюшаяся в заголовке окна. Если дважды щелкнуть по этому свойству, то появится окно загрузки иконки (рис. 6.9).
Рис. 6.9. Окно загрузки иконкиРис. 6.9. Окно загрузки иконки
Рис. 6.9. Окно загрузки иконки
Рис. 6.9. Окно загрузки иконки
В этом окне есть ряд кнопок.
П Load — загрузить иконку из файла.
П Save — сохранить иконку в файл.
G Сору — копировать иконку в буфер обмена.
П Paste — вставить иконку из буфера обмена.
О Clear — очистить текущую иконку.
Left (тип свойства — пелое число) — левая позиция окна.
Menu — меню, которое используется в главном окне. Этот параметр стоит рассмотреть отдельно.
Name — имя формы/компонента. Запомните, что имя, которое вы здесь укажете, будет использоваться в качестве названия объекта. Если это форма, то в коде будет создан шаблон объекта с таким же именем, но с добавлением в начале имени буквы т. Мы с вами уже рассмотрели исходный код, который сгенерировал Delphi для пустого проекта. Там объект назывался тForml. Обратите внимание, что здесь, а именно в шаблоне, написано просто Fromi. Если вы попробуете изменить свойство Name, то и имя объекта в исходном коде изменится.
Ссовет)
Старайтесь давать формам/компонентам понятные имена. Так легче будет понять, для чего они предназначены. Я думаю, что удобней будет работать с компонентом, если у него имя будет ExitButton или NewFileButton, а не просто Buttonl uButton2.
parentFont (тип свойства — логический). Если это свойство равно true, то для вывода текста оно будет использовать тот же шрифт, что и у родительского объекта. Иначе используется тот, что укажете вы.
position — позиция окна при старте приложения. Здесь доступны следующие варианты (в старых версиях Delphi могут присутствовать не все значения).
О РоDefault — Wndows сам будет решать, дде расположить окно и какие будут его размеры,
П PoDefaultPosOnly — Endows сам будет решать только, дде расположить окно, а размеры его будут такими, какие установите вы в свойствах.
PoDefaultsizeOnly — Windows будет решать только, какими будут размеры окна, а позиция будет такая, какую вы укажете в свойствах.
П PoDesigned — и размер, и позиция будут такими, какими вы укажете в свойствах.
П PoDesktopCenter — окно будет располагаться по центру рабочего стола.
П poMainFormCenter — окно будет располагаться по центру основной формы.
П poOwne гFormCentег — окно будет располагаться по центру окна владельца. То есть того окна, которое вызвало это.
П PoScreenCenter — окно будет располагаться по центру экрана. ‘
showHint (тип свойства — логический). Оно показывает — нужно ли показывать подсказки.
Tag — это свойство имеет тип — целое число. Оно ни на что не влияет, и вы можете его использовать в своих целях.
Тор (тип свойства — целое число). Верхняя позиция окна.
Тransparentcolor (тип свойства — логический). Определяет, являются ли форма или компонент прозрачными. В отличие от AlphaBlend, эта прозрачность работает всегда. Зато здесь нельзя сделать полупрозрачными формы и компоненты.
TransparentColorValue — значение прозрачного цвета.
VertScrollBar — вертикальная полоса прокрутки. Она имеет те же параметры, что и горизонтальная. Мы рассмотрим ее отдельно.
visible (тип свойства — логический). Если оно равно true, то форма/ком- понент видимые. Иначе форма/компонент невидимы.
width (тип свойства — целое число). Ширина окна.
Windowstate — состояние окна после запуска. Здесь доступны следуюшие параметры.
П WsNormal — окно показывается в нормальном состоянии.
WsMaximized — окно показывается максимизированным.
О WsMinimized — окно показывается минимизированным.
На этом обзор свойств формы считаю законченным.
Событийная модель Windows
Вся работа операционной системы Windows основана на понятии события. Что это значит? Давайте попробуем разобраться.
Внутри ядра M^ndows создается очередь событий. Коща какое-нибудь приложение или устройство изменило свое состояние и хочет сообщить об этом операпионной системе, то оно помещает э эту очередь соответствующее сообщение. ОС Windows обрабатывает его и, если необходимо, реагирует на изменения.
Давайте рассмотрим реальный пример события и реакцию на него. Допустим, что мы передвинули курсор мыши. Она генерирует событие и помещает его в очередь сообщений. Кодда Endows доходит до обработки этого сообщения, то получает новые координаты курсора мыши. Так как положение курсора изменилось, ОС должна перерисовать его в новой позиции на экране. Если какое-то окно тоже желает обработать это событие, то система помешает событие в очередь окна. После этого Windows переходит к обработке следуюшего сообщения.
Если в очереди нет сообщений, то Endows переходит в состояние ожидания. Но такое бывает очень редко. Даже когда вы не работаете за компьютером (он простаивает), в фоне работает очень много процессов, которые отнимают процессорное время и генерируют свои собственные события.
Когда вы нажимаете на кнопку мыши, генерируется событие, извещающее ОС, что кнопка нажата. Любое действие, которое несет в себе какие-либо изменения, может генерировать системное или оконное (относящееся к определенному окну) событие. Это очень эффективная и удобная модель, благодаря которой и реализуется многозадачность Windows.
Давайте взглянем на простую очередь (табл. 6.2 она не является очередью ОС Windows, это просто пример).
Таблица 62 Абстрактная очередь сообщений
Событие
Идентификатор приложения
Дополнительно
Нажата клавиша
261
А
Перерисовать экран
385
(12, 46, 336, 267)
Перемещена мышь
261
(356, 451)
Первая колонка показывает тип события. Вторая колонка показывает идентификатор приложения, которое сгенерировало событие. В третьей показываются дополнительные параметры. Например, при нажатии клавиши на клавиатуре в качестве дополнительного параметра идет буква, которую нажали. Конечно же, в реальной ситуации будет не буква, а код, но у нас же это просто пример.
ОС Endows берет первую строку из очереди и обрабатывает ее. Потом берет вторую строку. Она уже относится к другому приложению. Третья строка опять относится к первому приложению. Таким образом, ОС последовательно обрабатывает события разных приложений, что дает многозадачность.
Конечно же, многозадачность построена не только на сообщениях и здесь много дополнительных факторов. Однако очереди играют достаточно большую роль.
В Delphi все компоненты также работают через события. Вы будете постоянно создавать обработчики событий для разных ситуаций. Например, можно создать обработчик события для нажатия клавиши на клавиатуре и производить в нем какие-то действия. По нажатию определенной клавиши можно, например, выводить окно (действие как у горячих клавиш).
Таким образом, подводя итог сказанному, можно заключить, что в Delphi обработчик события — это простая процедура или функция, которая вызывается по наступлению какого-то вполне определенного события.
События главной формы
Здесь мы рассмотрим описание большинства событий, которые может отлавливать главная форма приложения. Конечно же, вам доступно намного больше и вы можете создать еще и свои обработчики, но здесь будут рассмотрены те, которые наиболее часто используются в Delphi. События можно увидеть на вкладке Events объектного инспектора (табл. 6.3).
Таблица 6・3・ Основные события, генерируемые формой (окном)
Событие
Описание
On Activate
Когда приложение стало активным
OnCanResize
Это событие генерируется перед тем, как изменить размер окна. Здесь можно запретить какие-либо изменения или производить какие-то подготовительные действия
OnClick
Генерируется, когда пользователь щелкнул по форме
OnClose
Генерируется, когда окно закрывается
OnCloseQuery
Генерируется до закрытия окна. В этом обработчике происходит запрос на закрытие, поэтому из этого обработчика можно вывести окно, которое будет запрашивать подтверждение на закрытие. Такие подтверждения можно увидеть в каждом втором приложении типа "Вы уверены, что хотите закрыть окно?"
OnCreate
Генерируется, когда окно создается
OnDblClick
Генерируется, когда пользователь дважды щелкнул по окну
On Deactivate
Генерируется, когда окно деактивируется
On Destroy
Когда окно уничтожается
OnHide
Генерируется, когда окно исчезает из виду. Событие генерируется даже тогда, когда память, выделенная для окна, не уничтожается
OnKeyDown
Генерируется, когда нажата клавиша на клавиатуре
On KeyPress
Генерируется, когда нажата и отпущена клавиша на клавиатуре
OnKeyUp
Генерируется, когда отпущена клавиша на клавиатуре
OnMouseDown
Генерируется, когда нажата кнопка мыши
OnMouseMove
Генерируется, когда двигается мышка
OnMouseUp
Генерируется, когда отпускается кнопка мыши
OnMouseWheel
.Генерируется колесиком мыши
Таблица 6.3(окончание)
Событие
Описание
OnMouseWheelDown
Генерируется, когда колесико мыши прокручено вниз
OnMouseWheelUp
Генерируется, когда колесико мыши прокручено вверх
OnPaint
Генерируется, когда надо перерисовать окно
OnResize
Генерируется, когда надо изменить размеры окна
OnShortCut
Когда нажата горячая клавиша
OnShow
Когда показывается окно, но до фактической прорисовки. В этот момент окно уже создано и готово к отображению, но еще не прорисовалось на экране
Это основные события, которые может генерировать форма. Когда мы будем рассматривать компоненты, то я не буду снова расписывать те события, которые уже перечислены здесь, потому что разницы в них нет. Некоторые компоненты построены на основе окна и будут иметь большинство из этих событий. Поэтому я буду затрагивать только то, что специфично для конкретного компонента.
Палитра компонентов
Начиная со следуюшей главы, мы начнем знакомиться с предоставляемыми средой разработки Delphi компонентами, поэтому сейчас надо познакомиться с палитрой компонентов и узнать, из чего она состоит.
Standard | Additional | Win321 Sustem | Data Access) Data Controls I dbExoress] DalaSnao| BD»_®Г _ A阿圉品国@圓置31匚I三[國
Рис. 6.10. Палитра компонентов
Палитра компонентов состоит из нескольких вкладок.
П Standard — все эти компоненты являются аналогами компонентов Windows.
П Additional — дополнительные компоненты.
П Wn32 — компоненты, которые есть только в семействе ОС Win32. В это семейство входят Windows 9х, Windows ME, Windows 2000, Windows NT, Windows XP. Наверно, легче было сказать, что не входит, потому что это только Endows 3.1.
口 System — системные компоненты, с помошью которых облегчается доступ к системе.
口 Data Access — компоненты доступа к базам данных.
口 Data Controls — компоненты для работы с базами данных.
dbExpress — компоненты доступа к базам данных, которые пришли на смену BDE.
П BDE — старые компоненты доступа к базам данных.
П ADO — это тоже компоненты для доступа к базам данных, только по технологии Active Data Object (ADO). Данные компоненты удобны при работе с базами данных от Microsoft.
О InterBase — компоненты доступа к базе данных InterBase.
WebServices — компоненты доступа к сети Internet.
П JnternetExpress — компоненты доступа к сети Internet.
П FastNet — сетевые компоненты. Мы будем мало их использовать.
П Qreport — компоненты для создания отчетности.
П Dialogs — компоненты, облегчаюшие доступ к стандартным диалогам.
П Win3.11 — компоненты доступа к компонентам Win 3,1.
П Samples — различные примеры компонентов. Некоторые из этих компонентов доступны в исходных кодах и поставляются вместе с Delphi.
На этом пока хватит. В следующей главе мы познакомимся с палитрой компонентов Standard и входящими в нее компонентами. В процессе этого будем учиться работать не только с компонентами, но и изучать язык программирования Delphi на конкретных примерах.
Глава?
Палитра компонентов Standard
В этой главе будут рассматриваться компоненты, которые находятся на вкладке Standard палитры компонентов. Одновременно с этим мы будем писать программы с использованием этих компонентов и изучать язык программирования Delphi.
Здесь не будут просто перечисляться компоненты и их свойства. Мы будем писать вполне работающие приложения. Функциональность их пока будет очень слабая, но все же эти программы будут вполне рабочими и даже полезными не только для обучения, но и при написании реальных программ.
По ходу изложения материала будет рассмотрено большое количество довольно интересных примеров, но это уже зависит от самих компонентов. С некоторыми можно создать что-то полезное без дополнительных знаний, но в большинстве случаев ничего сложного с их использованием сделать невозможно, потому что вы еще не научились хорошо программировать.
Рассмотрение компонентов на данном этапе изучения языка Delphi необходимо для получения практических знаний при написании программ с простым интерфейсом пользователя. В этой связи следует еще раз напомнить о необходимости повторять все действия по ходу изложения материала книги. На диске есть все исходные коды примеров, но это готовые примеры. Вы сможете чему-то научиться, только если сами попробуете программировать.
Итак, переходим к рассмотрению компонента “кнопка”. Хотя он находится в середине вкладки, рассмотрение компонентов целесообразно начать именно с него. Этот компонент самый простой и при рассмотрении других компонентов мы будем постоянно его использовать.
Кнопка (TButton)
Кнопка в Delphi происходит от объекта TButton. Когда вы устанавливаете на форму новую кнопку, то ей дается имя по умолчанию Buttonl. Следующая кнопка получит название Button2 и т. д.
Давайте напишем маленькую программу с использованием кнопки (при наведении указателя мыши на этот компонент должна появляться подсказка Button). Для этого создайте новое приложение. Щелкните мышью по изображению кнопки на палитре компонентов. После этого щелкните мышью в любом месте формы. На форме сразу же появится кнопка с заголовком Button 1 (рис. 7.1). Можно не щелкать по форме и растягивать, как бы прорисовать, квадрат (происходит при нажатой кнопке мыши). В этом случае кнопка примет размеры нарисованного квадрата.
Рис. 7.1. Форма с кнопкой
Есть еще один способ установить кнопку на форме. Для этого дважды щелкните мышью по изображению кнопки на палитре компонентов. Но в этом случае кнопка окажется в центре формы, а не там, вде мы хотим.
Выделите кнопку и перейдите в объектный инспектор. В нем (рис. 7.2) показаны свойства кнопки. Как видите, большинство свойств нам уже знакомо по свойствам формы, поэтому не будем их расематривать. Есть только одно новое свойство — ModaIResuit, но с ним мы познакомимся позже.
Давайте изменим заголовок кнопки. За заголовок формы отвечает свойство Caption. Здесь то же самое. Найдите свойство caption и измените содержащийся в нем текст на "Нажми меня**.
Сразу изменим и свойство Name у кнопки. Ранее говорилось, что любым компонентам и формам лучше давать понятные имена. Поэтому давайте с самого начала будем привыкать к нормальному стилю программирования. Найдите свойство Name и измените его на MyFirstButton. Пускай имя кнопки пока не отражает никакого смысла, ведь она еще ничего не делает.
Теперь изменим имя формы. Для этого снимите выделение с кнопки (щелкните мышью в любом месте формы). Вверху окна объектного инспектора должна загореться надпись Fermi TForml, как это показано на рис. 7.3. Нужный для редактирования компонент можно выбрать и в этом списке.
Рис. 7.2. Объектный инспектор со свойствами кнопки
Рис. 7.3. Объектный инспектор с вьщеленной главной формой
Теперь найдите здесь свойство Name (оно должно быть равно Forml) и измените его значение на Ma inForm (это переводится как — главная форма).
Попробуйте запустить программу (нажать клавишу <F9>). Оболочка Delphi скомпилирует программу и запустит на выполнение. Теперь можно спокойно нажимать на кнопку, только ничего в этом случае происходить не будет.
Давайте усложним пример и введем реакцию на событие, когда нажимается кнопка. Для этого перейдите на вкладку Events в окне объектного инспектора. Когда мы рассматривали события формы, говорилось, что за щелчок кнопки мыши отвечает событие Onclick. Для кнопки есть такое же событие. Найдите
его (для этого надо перейти на вкладку объектного инспектора Events) и щелкните по нему дважды. Delphi должен создать в редакторе кода процедуру —обработчик события Onclick. По умолчанию ей дается имя в виде имени компонента (нашей кнопки) плюс имя события без приставки on. В нашем случае получается, что имя процедуры обработчика будет MyFirstButtonClick.
I Листинг 7.1. Процедура обработчика события Onclick для копки
Procedure ТForml.МуFiгstButtonClick(Sender: TObject);
begin .
end;
В объектном инспекторе напротив строки onclick также должно появиться имя процедуры обработчика. Вы можете изменить его, просто напечатав новое имя, или выбрать уже существующий обработчик из выпадающего списка в этой же строке. В нашем случае выпадающий список пуст, потому что мы больше не делали схожих обработчиков событий, и оно у нас пока единственное.
Давайте вернемся в редактор кода и посмотрим, что там создал для нас Delphi. Это процедура МуFirst ButtonCiick. Ей передается один параметр sender объектного типа TObject. Вначале выполнения процедуры в переменной sender будет находиться указатель на объект, который вызвал этот обработчик. Это очень важно, потому что одна процедура-обработчик может обрабатывать нажатия сразу нескольких кнопок или вообще компоненты разного типа. По содержимому этой переменной можно узнать, какой именно компонент сгенерировал событие. Будем использовать эту возможность, когда это понадобится.
Давайте напишем внутри процедуры (между операторными скобками begin и end) команду close. Эта команда закрывает окно. Точнее сказать, это метод окна, который закрывает его. Теперь наша процедура должна выглядеть так
Листинг 7.2. Измененный обработчик события Onclick для кнопки
Procedure TForml. Му Fir s tBu 11 onCl ick (Sender: TObj ect);
begin
Close;
end;
Попробуйте запустить программу и нажать на кнопку. Программа закроется.
Примечание?
На компакт-диске в папке \ Примеры \ Глава 7 \ Button вы можете увидеть пример программы.
Так как close — это метод формы, мы могли написать и так:
Листинг7.3. Вызов метода Close
Procedure TForml.MyFirstButtonClick(Sender: TObject);
begin
Forml.Close;
end;
Разницы между листингами 7.2 и 7.3 абсолютно никакой нет. Так почему же в первом случае написано просто close без указания имени объекта, метод которого используется? Мы же знаем, что методы нужно вызывать в форме Имя_Объекта. Имя_Метода. Все очень просто. Процедура обработчика события относится к объекту Forml (об этом говорит объявление процедуры TForml. MyFirstButtonClick) и внутри нее можно использовать его свойства и методы без указания владельца. По умолчанию будет браться Forml. Но если мы захотим из этой процедуры закрыть другую форму, например, For m2, то придется указать, что необходим объект именно Form2.
Листинг 7.4. Использование метода другой формы
procedure TForml.MyFirstButtonClick(Sender: TObject);
begin
Form2.Close;
end;
Если мы просто напишем close, то закроется Forml, а не Form2.
Изменениесвойств кнопки (логические операции) '
Этот пример уже описывался в журнале "Хакер”. Он достаточно простой и удобный для обучения, поэтому рассмотрим его здесь. Сейчас будет написана программа, у которой будет только одна кнопка. При наведении на нее указателя мыши кнопка будет убегать.
Воспользуемся предыдущим примером и улучшим его. Для начала выделите форму и измените свойство AutoScroll на false, чтобы на форме автоматически не появлялись полосы прокрутки.
Теперь создайте для кнопки обработчик события OnMouseMove, 口/я этого выделите кнопку и перейдите в объектном инспекторе на вкладку Events. Здесь вы уже создавали обработчик onclick, теперь шелкните дважды напротив строки OnMouseMove, чтобы создать соответствующий обработчик.
Если вы все сделали правильно, то Delphi должен создать процедуру для обработки сообщения — OnMouseMove.
Листинг 7.5. Заготовка обра отчика событий ДЛЯ OnMouseMove
Procedure TForml. МуFiгstButtonMouseMove (Sender: TObj ect ; Shift: TShiftState; X, Y: Integer);
begin
end;
He будем пока вдаваться в подробности передаваемых параметров. Просто напишите здесь код, показанный в листинге 7.6.
П, -Тинг 7.& Обновленный обработчик собьлтия OnMouseMove
Procedure TForml. МуFiгstButtOnMouseMove (Sender: TObj ect ;
Shift : TShiftstate; x, Y: Integer);
var
index:integer;
begin
index: ^random (4);
case index of
0: My Fir st But ton. Left: =MyFir stButton. Left+MyFirstBut ton .Width;
MyFirstButton.Left:=MyFirstButton.Left-MyFirstButton.width;
MyFirstButton.Top:=MyFirstButton.Top+MyFirstButton.Height;
MyFirstButton.Top:=MyFirstButton.Top-MyFirstButton・Height;
end;
if MyFirstButton.Left<0 then
MyFirstButton.Left:=0;
if (MyFirstButton.Left+MyFirstButton.Width) > MainForm. Width then
MyFirstButton. Left: =MainForm. Width-MyFirstButton. Width;
if MyFirstButton.Top<0 then
MyFi rs tBut ton. Top: =0;
if (MyFirstButton.Top+MyFirstButton. Height) > MainForm. Height then
MyFirstButton ・ Top: =MainForm. Height-MyFirstButton. Height;
end;
Пока просто перепишите содержимое этого листинга. Скоро мы подробно рассмотрим, что тут написано. Запустите программу и попробуйте нажать на кнопку. Как только попытаетесь навести на нее указатель мыши, кнопка будет убегать от вас.
工 Пртуючание〉
На компакт-диске в папке \ Примеры \ Глава 7\ Button 1 вы можете увидеть пример этой программы.
Обязательно сначала посмотрите, как работает пример. Когда поймете принцип действия программы, возвращайтесь к чтению книги, и мы рассмотрим исходный код.
В разделе var объявлена одна переменная index типа **целое число**. В первой строчке этой переменной присваивается случайное число с помощью функции random:
index:=random(4);
Функция random возвращает случайное число. В качестве единственного параметра ей нужно передать число, которое будет означать максимально возможное случайное значение. В операторе передается цифра 4. Это значит, что функция вернет число от нуля до четырех (0 < X < 4). Само число 4 в диапазон возможных значений не входит, все случайные числа будут меньше него.
После этого проверяется, какое число сгенерировала функция random с помощью КОНСТРУКЦИИ case:
Листинг 7.7. Принцип работы оператора case
case Переменная of
Значение!: Действие!;
Значение2: Действие2;
end;
Конструкция case сравнивает переменную с перечисленными между ключевыми словами of и end значениями, и если одно из них совпадает, то выполняет соответствующее действие. Например, допустим, что наша переменная равна числу Изначение2". В этом случае будет выполнено “Действие2”. При ЭТОМ “Действие】"И Другие ВЫПОЛНЯТЬСЯ не будут.
Если вам нужно, чтобы при равенстве значений выполнялось несколько действий, то необходимо заключить их в логические кавычки begin ... end, как это показано в листинге 7.8.
Листинг 7 i. Обработка нескольких действий в операторе case
case Переменной of Значение!:
begin
Деиствие1_1;
Действие1_2;
end;
Значение2:
begin
Действие2_1;
Действие2_2;
end;
end;
Теперь вернемся к нашему примеру. В нем используется следующий оператор case: case index of
О: MyFirstButton.Left:=MyFirstButton.Left+MyFirstButton.Width;
MyFirstButton.Left:=MyFirstButton.Left-MyFirstButton.Width;
МуFirstButton.Top:^MyFirstButton.Top+MyFirstButton.Height;
MyFirstButton.Top:=MyFirstButton.Top-MyFirstButton.Height; end;
Если переменная index равна О, то выполнится действие:
MyFirstButton. Left: =MyFirstButton. Left+My Firs tBut ton. Width
Что это? Здесь свойству Left (левая позиция) кнопки MyFirstButton присваивается значение левой позиции этой же кнопки плюс ее ширина. Это значит, если вы попытались навести мышкой на кнопку, и функция Random сгенерировала 0, то левое значение кнопки будет увеличено на ширину кнопки. А это уже значит, что кнопка сдвинется от вас вправо. Если вы уже запускали пример, то поняли это.
Если значение переменной index равно 1, то, наоборот, уменьшается левая позиция кнопки на ее ширину. А это значит, что кнопка убежит влево. Если значение переменной index равно 2, то увеличивается верхняя позиция кнопки на ее высоту — кнопка убежит вниз. Надеюсь, что смысл понятен. Давайте двигаться дальше.
После конструкции case .. of . . end идет проверка: "Не убежала ли кнопка за пределы окна?" Сначала проверяется левая позиция кнопки.
if MyFirstButton.Left<0 then
MyFirstButton.Left ^0;
Здесь вдет проверка, если левая позиция кнопки MyFirstButton. Left меньше нуля, то установить ее в ноль.
В следующей строке проверяется, если левая позиция кнопки плюс ее ширина больше ширины окна, то левой позиции присвоить значение "ширина окна" минус "ширина кнопки":
if (MyFirstButton.Left+MyFirstButton.Width)>Forml.Width then
MyFirstButton.Left:=Forml・ Width-MyFirstButton.Width;
Точно так же проверяем и верхнюю позицию, чтобы она не вышла за пределы окна.
В этой проверке используется конструкция if . . then. О ней уже говорилось в теории, а теперь она используется на практике. Итак, конструкция if . . then выглядит следующим образом:
If Условие then
Выполнить действие;
Если условие выполнено, то будет выполнено и действие. Если вы хотите выполнить два действия, то должны заключить их в логические скобки begin . . . end.
Например:
If Условие then
begin
Действие!;
Действие2;
Действие3;
end;
В этом случае все действия между begin и end будут выполнены, если условие верно. Таким образом, begin . . . end группирует последовательность действий в одно.
Надписи (TLabel)
Этим компонентом мы будем пользоваться практически в каждом примере для вывода информации и надписей для других компонентов.
Создайте новое приложение. Установите на форму один компонент TLabel и измените у него свойство caption на “Это моя первая программа**.
工 Примечание ]
На компакт-диске в папке \ Примеры \ Глава 7 \ Label вы можете увидеть пример этой программы.
Компонент простой, и мы почти всегда будем использовать у него только свойство Caption. Возможно, что иногда понадобится еше свойство Transparent (прозрачность). В следующем примере мы создадим один простой текстовый эффетсг и воспользуемся свойством прозрачности TLabel.
Продолжим программирование. Щелкните дважды по свойству Font (шрифт) компонента Label 1. Перед вами появится окно свойств шрифта. Сделайте шрифт побольше (можно использовать 12 или 14) и измените цвет на белый.
Теперь запомните значения свойств Left и тор (левая и верхняя позиции) компонента. Пусть эти свойства равны 16 и 16 соответственно. Теперь скопируйте Label в буфер обмена, выбрав команду меню Edit | Сору (Редактирование I Копировать). Щелкните по форме, чтобы снять выделение с компонента Labeii. Теперь вставьте копию компонента из буфера обмена с помощью меню Edit | Paste (Редактирование | Вставить). Delphi создаст новый компонент Label2, который будет копией первого. Выделите новую копию и измените свойства Left и тор на 18 и 18, что на пару единиц больше по-
зиции первого компонента. Это сделает второй компонент как бы немного сдвинутым поверх первого. Щелкните дважды по свойству Font и измените цвет шрифта на синий. И последнее — измените свойство Transparent на true.
Рис. 7.4. Текст с тенью
Если вы все сделали правильно, то у вас должно получиться нечто, похожее на рис. 7.4. Получается как бы надпись с тенью.
工 Примечание 】
На компакт-диске в папке \ Примеры \ Глава 7 \ CoolLabel вы можете увидеть пример этой программы.
Строки ввода (TEdit)
С помощью строк ввода мы постоянно будем вводить различную информацию в наши программы. Давайте попробуем написать несколько программ с использованием этого компонента, чтобы понять все тонкости работы с ним.
Создайте новый проект. Установите в окне формы два компонента TEdit с палитры компонентов Standard и одну кнопку. У вас должно получиться нечто, похожее на рис. 7.5.
Давайте очистим у обоих компонентов TEdit свойство Text. Это свойство отвечает за содержимое строки ввода. Мы просто очистим, чтобы после запуска программы обе строки ввода были пустыми.
Теперь создайте для кнопки обработчик события onclick. Мы это уже делали, поэтому у вас не должно быть с этим проблем. Кстати, если дважды щелкнуть по кнопке или по компоненту на форме, то Delphi автоматически создаст этот обработчик события. Ну, а если он уже создан, то просто перенесет вас в то место, где написан код этого обработчика.
Рис. 7.5. Форма будущей программы
Итак, создайте обработчик и напишите в нем содержимое листинга 7.9.
Листинг 7,9 Обработчик события Onclick
procedure TForml. Му Fir stButtonClick (Sender: TObject);
begin
Edit2.Text:=Editl.Text;
end;
По нажатию кнопки мы копируем содержимое свойства Text компонента Edit2 в свойство Text компонента Editl. Попробуйте запустить программу. Теперь введите в первую строку ввода какой-нибудь текст, а потом нажмите кнопку. Во второй строке ввода появится тот же текст.
工 Примечание ]
На компакт-диске в папке \ Примеры \ Глава 7 \ TEdit вы можете увидеть пример этой программы.
Давайте немного улучшим пример. Теперь измените у первой строки ввода свойство PasswordChar на звездочку ”*”. Запустите программу и попробуйте ввести в эту строку текст. Вместо текста будут появляться звездочки, как при вводе пароля в какой-нибудь программе. Именно таким образом делаются строки ввода паролей. Попробуйте нажать на кнопку, и во вторую строку ввода перенесется текст, который вы вводили.
匕 Примечание~
На компакт-диске в папке \ Примеры \ Глава 7 \ PasswordChar вы можете увидеть пример этой программы.
Теперь сделаем проверку на ввод пароля. Найдите в процедуре обработчик события onclick для кнопки и напишите там следующее:
I Листинг 7Л0. Проверка пароля
procedure TForml .MyFirstButtonCLick(Sender: TObject); begin .
if Editl. Text=,password * then
Edit2 . Text: = ' Пароль верный '
else
Edit2 . Text: = ' Неверно ';
end;
Теперь попробуйте запустить программу. Если вы введете в первую строку ввода слово password и нажмете кнопку, то во второй строке появится надпись Пароль верный, иначе будет надпись Пароль неверный.
Прммечанме~^Д
На компакт-диске в папке \ Примеры \ Глава 7 \ PasswordCharl вы можете увидеть пример этой программы.
Этих знаний достаточно для написания практически любой программы, которая использует строку ввода. Мы будем регулярно сталкиваться с этим компонентом, поэтому вы еще успеете познакомиться с ним поближе.
Многострочное поле ввода (TMemo)
Теперь мы познакомимся с многострочными компонентами ввода. Для этого на вкладке Standard есть компонент тмето.
Как всегда, в качестве примера напишем небольшую программу. Для этого создайте новый проект. Установите на форму компонент тмето.
По умолчанию в нем уже присутствует одна строка текста, равная имени, компонента. За содержимое текста отвечает свойство Lines. Это свойство — целый объект типа Tstrings, и имеет свои свойства м методы. Немного позже мы познакомимся с некоторыми из них.
Давайте сначала просто очистим содержимое компонента Memol. Для этого дважды щелкните по свойству Lines. Перед вами откроется окно редактора строк (рис. 7.6). Это окно содержит простой текстовый редактор, в котором можно набрать многострочный текст. Мы не будем этого делать, а просто удалим все его содержимое. Как только сделаете это, нажмите кнопку ОК.
6 Зак 942
Рис. 7.6. Редактор строк в компоненте Мето
Теперь давайте напишем небольшую программу, которая будет выполнять простые функции текстового редактора. Сложные вещи не получатся, потому что тмето — это простой компонент, да и еще рано делать что-то сложное. Для создания более сложных текстовых редакторов есть другой компонент.
Итак, добавьте на форму пока только одну кнопку. Измените ее свойство caption на очистить и имя на ClearButton. Кстати, давайте изменим имя и компонента Memol на Ma inMemo. Создайте для кнопки обработчик события Onclick. В нем напишите следующее:
procedure TForml.ClearButtonClick(Sender: TObject);
begin
MainMemo.Lines.Clear;
end;
В этом программном коде вызывается метод clear объекта Lines, который в свою очередь принадлежит объекту MainMemo. Немного запутано, но со временем вы поймете, что это очень даже удобно. У компонента MainMemo есть свойство Lines, значит, чтобы получить к нему доступ, мы должны написать MainMemo. Lines. Свойство Lines — это целый объект, и у него есть метод clear, который очищает содержимое строк редактора.
Попробуйте запустить программу и написать внутри компонента Мето какой-нибудь текст. Потом нажмите кнопку Очистить, чтобы уничтожить все, что вы ввели.
工 Прммечанме
На компакт-диске в папке \ Примеры \ Глава 7 \ Мето вы можете увидеть пример этой программы.
Давайте теперь услржним наш пример, добавив возможность сохранения введенного текста и загрузки его обратно.
Создайте обработчик события onshow (срабатывает, когда отображается окно) для формы и напишите там следующее: procedure TForml.FormShow(Sender: TObject);
begin
MainMemo. Lines. LoadFromFile ( ' memo . txt1 );
end;
Здесь вызывается метод LoadFromFile объекта Lines. Ему нужно передать только один параметр — имя файла, откуда будет происходить загрузка данных. Есть только один недостаток — если вы сейчас попытаетесь запустить программу, то во время загрузки произойдет ошибка, потому что файла niemo.txt нет. Тут есть два выхода:
заранее создать файл;
при старте программы проверять, есть ли файл, и только если он существует, производить загрузку текста.
С первым способом все понятно. А вот для реализации второго способа нужно воспользоваться функцией FileExists. Ей нужно передать имя файла, существование которого надо проверить, и если такой файл существует, функция вернет true. Давайте изменим обработчик события Onshow следующим образом: procedure TForml.FormShow(Sender: TObject);
begin
if FileExists(*memo.txt1) then
Ma inMemo. Lines. LoadFromFi 1 e (* memo. txt'); end;
Теперь создадим обработчик события OnClose (срабатывает на закрытие окна). В нем напишем процедуру сохранения содержимого MainMemo.
procedure TForml.FormClose(Sender: TObject; var Action: TCLoseAc- tion);
begin
MainMemo.Lines.SaveToFile(,memo.txt1);
end;
Здесь используется метод SaveToFile, который работает так же, как и LoadFromFiie, только сохраняет данные.
Примечание
На компакт-диске в папке \ Примеры \ Глава 7 \ Мето1 вы можете увидеть пример этой программы. Не запускайте пример с привода CD-ROM, потому что при выходе из программы происходит попытка сохранить имеющиеся данные. А поскольку на CD-ROM записать невозможно, произойдет ошибка.
Теперь осталось только научиться программно добавлять, удалять и изменять строки в компоненте Tmemo, и можно считать, что мы досконально изучили этот компонент.
В этом компоненте текст хранится как простая последовательность строк — массив. Используя предыдуший пример, наберите текст и после закрытия программы посмотрите, как он выглядит в файле. Вот содержимое memo.txt: Библия для программиста 6 среде Delphi
Copyright Horrific
www.cydsqft.com/vr-online/
Как видите, в поле Мето четыре строчки (вторая пустая). Точно так же строки расположены и в файле.
Рис. 7.7. Хранение строк в компоненте Мето
Для доступа к каждой строке можно воспользоваться свойством strings объекта Lines. На рис. 7.6 наглядно показано, как получить доступ к строкам. Чтобы ПОЛУЧИТЬ Нулевую строку, НУЖНО Написать Ma inMemo. Lines. Strings [ 0 ]. ДЛЯ первой строки —.MainMemo. Lines. Strings [ 1 ], И Т. Д.
Давайте напишем пример, который будет получать доступ к строкам, чтобы увидеть все это на практике. Добавьте к предыдущему примеру три кнопки: 口 Добавить — дадим этой кнопке имя AddButton.
口 Удалить — дадим этой кнопке имя DelButton.
口 Изменить — дадим этой кнопке имя Change Button.
Можете расположить их как на рис. 7.7. Теперь создадим обработчик события Onclick для кнопки Добавить. Здесь мы будем программно добавлять новую строку в Ma inMemo. Для этого у объекта Lines есть метод Add, у которого только один параметр — текст новой строки: procedure TForml.ButtonlClick(Sender: TObject);
begin
MainMemo.Lines.Add('Новая строка'); end;
Теперь создадим обработчик события Onclick для кнопки Удалить. По этому событию мы должны удалить строку, в которой находится сейчас курсор. Для этого напишите в обработчике следующий текст: procedure TForml.DelButtonClick(Sender: TObject);
begin
if MainMemo. Lines. CountoO then
MainMemo.Lines.Delete(MainMemo.CaretPos,Y); end;
В первой строке кода проверяется, сколько строк в компоненте MainMemo. Для этого есть свойство Count объекта Lines. Если оно не равно нулю (MainMemo. Lines. CountoO), значит, строки есть, и мы можем удалить текущую строку. Знак О означает ине равно**.
Для удаления используется метод Delete объекта Lines. В качестве единственного параметра нужно передать номер строки для удаления. Но как узнать, какая строка сейчас является текущей? Для этого у MainMemo есть свойство CaretPos, которое указывает на текущую позицию курсора.
CaretPos — это переменная типа TPoint. Этот тип определяется как запись. С таким типом мы подробно познакомимся немного позже. Единственное, что сейчас необходимо знать, так это то, что данный тип похож на объект, только у него нет методов, а только свойства. У TPoint есть два свойства х и У. х — указывает на текущую колонку, a Y указывает на текущую строку. Таким Образом, МОЖНО узнать Текущую Строку С ПОМОШЬЮ MainMemo. CaretPos. Y. Итак, MainMemo. Lines . Delete удаляет указанную строку. Указание текущей строки ПРОИСХОДИТ С помощью MainMemo. CaretPos. Y.
Теперь осталось только написать обработчик события onclick для кнопки Изменить. В нем напишите следующий код:
Procedure TForml.ChangeButtonClick(Sender: TObject); begin,-
,MainMemo.Lines.Strings[MainMemo.CaretPos.Y]:='Horrific*;
MainMemo.Lines.Strings[0]: = *Текст изменен *;
end;
В первой строке я изменяю теьсущую строку на мНогпПси. Второй строкой кода я изменяю первую строчку ManMemo на "Текст изменен**.
Здесь вам уже все должно быть знакомым. Так что теперь можно считать, что мы изучили основные возможности компонента тмешо.
工 Примечание—
На компакт-диске в папке \ Примеры \ Глава 7 V Мето2 вы можете увидеть пример этой программы. Не запускайте пример с привода CD-ROM, потому что при выходе из программы происходит попытка сохранить имеющиеся данные. А поскольку на CD-ROM записать невозможно, произойдет ошибка.
Объект TStrings
В предыдущем разделе мы познакомились на практике с объектом тмето. Свойство Lines компонента тмето имеет тил TStrings.女о очень мощный объект, с которым мы будем очень часто встречаться на протяжении всей книги, поэтому необходимо остановиться и рассказать о нем подробнее.
Объект TStrings это набор строк. Везде, где информация поделена на строки, этот объект является мощнейшим средством для хранения и работы с ними. Представьте себе простой текстовый файл. Как хорошо, когда с ним можно работать, именно разбив содержимое на строки, а не со всей сплошной информацией. Когда мы будем рассматривать работу с файлами, то этот объект тоже будет присутствовать. Вы еще не раз будете возвращаться к этой главе, чтобы вспомнить, для чего предназначено то или иное свойство или метод.
Здесь мы рассмотрим только описание свойств и методов объекта и начнем конечно же со свойств.
Свойства объекта TStrings
count — это свойство, которое вы можете только читать. Здесь хранится количество строк, содержащихся в объекте.
strings — здесь хранится набор строк. К любой строке можно получить доступ, написав такую конструкцию:
Переменная:=Имя_Объекта.Strings[Номер строки];
Имя_объекта.Strings[Номер строки]:= Переменная;
Первая строка кода запишет в переменную содержимое указанной строки. Вторая строка, наоборот, запишет содержимое переменной в указанную строку. Запомните, что строки в этом объекте нумеруются с нуля.
Text — в этом свойстве хранятся все строки в виде одной целой строки, разделенные кодами конца строки и перевода каретки.
Методы объекта TStrings
Add (Строка) — метод добавляет строку, указанную в качестве параметра, в конец набора строк объекта. Возвращает номер, под которым добавлена новая строка.
Append (Строка) — этот метод тоже добавляет строку, указанную в качестве параметра, в конец набора строк объекта. Он ничего не возвращает.
Add Strings (Набор строк типа TStrings) — метод добавляет все Строки ИЗ другого объекта типа TStrings.
Assign — метод присваивает вместо своего набора строк новый, указанный в качестве параметра.
clear — метод удаляет все строки из объекта.
Delete (номер строки) — метод позволяет удалить строку под указанным номером.
Equals (Набор строк типа TStrings) — метод допускает сравнение собственного набора строк с указанным в качестве параметра. Если наборы равны, то метод вернет true, иначе false.
Exchange (Номер!, Номер2) — метод меняет местами строки указанных номеров.
Get (номер строки) — метод возвращает строку указанного номера.
Indexof (Строка) — этот метод позволяет найти указанную в качестве параметра строку. Если такая строка существует в наборе, то метод вернет ее индекс, иначе — 1.
insert (Номер, Строка) — метод позволяет вставить в набор новую строку под указанным номером.
LoadFromFile (Имя файла) — данный метод используется, чтобы загрузить набор строк из указанного текстового файла.
SaveToEile (Имя файла) — метод обеспечивает сохранение набора строк в указанном текстовом файле.
Move(Номер 1Г Номер2) — метод перемещает строку под номером I на место с номером 2.
Пока этих свойств и методов больше чем достаточно. В нашей книге будут рассматриваться в основном эти методы, хотя это далеко не все.
Компонент CheckBox
Теперь перейдем к рассмотрению компонента CheckBox. Как всегда, давайте создадим маленькую программу, которая будет использовать этот компонент.
Создайте новое приложение, поместите на главную форму одну кнопку и два компонента TCheckBox. Первому компоненту дадим заголовок (свойство Caption) равным "Разрешить закрытие программы*' И ИМЯ (СВОЙСТВО Name) равным AllowCloseCheckBox. Второму компоненту дадим заголовок (caption)- равным "Отключить кнопку" И ИМЯ (Name) равным EnableButtonCheckBox. Кнопке дадим имя МуFi rs tBu11on.
Создайте обработчик события Onclick для компонента EnableButtonCheckBox (это второй CheckBox). В нем напишем следующее:
Procedure TEorml. EnableButtonCheckBoxClick (Sender: TObject); begin
MyFirstButton. Enabled: =not EnableButtonCheckBox. Checked; end;
Здесь свойству Enabled нашей кнопки присваивается значение not EnableButtonCheckBox. checked. Что это значит? Свойство Checked компонента EnableButtonCheckBox показывает, стоит ли галочка на этом компоненте CheckBox. Если да, то свойство checked будет равно true, иначе false. Оператор not меняет это состояние на противоположное. Это значит, что если СВОЙСТВО Checked было равно true, ТО В MyFirstButton.Enabled будет присвоено противоположное (false).
Можете попробовать запустить пример и посмотреть, что происходит. Когда вы ставите галочку напротив компонента с надписью “Отключить кнопку**, свойство Checked этого компонента меняется на true. Срабатывает событие Onclick и в свойству Enabled кнопки присваивается значение свойства Checked компонента CheckBox, измененное на противоположное, т. е. false. А когда свойство Enabled кнопки равно false, она становится недоступной.
Чтобы окончательно разобраться с работой примера, нажмите на EnableButtonCheckBox при запущенной программе. Потом попробуйте убрать из исходного кода оператор not и снова запустите программу.
Теперь давайте создадим обработчик Onclick для кнопки. В нем напишите следующий код:
procedure TForml.MyFirstButtonClick(Sender: TObject);
begin
if AllowCloseCheckBox.Checked then
Close;
end;
Здесь проверяется, если свойство checked компонента AllowCloseCheckBox (первый checkBox на форме) равно true, то закрыть профамму (выполнить метод close). Иначе ничего не произойдет. Снова запустите пример и попробуйте выйти из программы с помощью установленной кнопки.
Как видите, работа этого компонента очень проста.
工 Примечание
На компакт-диске в папке \ Примеры \ Глава 7 \ CheckBox вы можете увидеть пример этой программы.
Панели (TPanel)
Если идти по порядку, то сейчас мы должны рассматривать компонент TRadioBox. Но мы перескочим сразу на TPanel. Это сделано потому, что он будет использоваться уже в следующих примерах.
TPanel это компонент в виде панели. Он ведет себя почти так же как форма, и между ними есть что-то обшее. Вы можете на нем располдгать компоненты, и если вы передвинете панель, то все компоненты, установленные на ней, тоже передвинутся.
Панель может выглядеть по-разному. За внешний вид отвечают два свойства: Bevel inner и BevelOuter. Рассмотрим маленькую профамму, которая ничего не делает, зато она показывает, как может выглядеть панель с различными вариантами установленных параметров. На панели шрифтом выделены установленные значения Bevel inner и BevelOuter (рис. 7.8).
Рис. 7.8. Различные состояния компонента Panel
工 Прм1У1ечанме
На компакт-диске в папке \ Примеры \ Глава 7 \ Panel вы можете увидеть пример этой программы.
Давайте напишем пример, в котором будем программно менять внешний вид панели. Для этого создайте новое приложение и установите на форму два компонента тPanel с палитры компонентов Standard.
В этом примере не будут меняться имена панелей. Они останутся по умолчанию Panell и Рапе 12. Так будет нагляднее.
Единственное, что мы поменяем — это свойства Caption обеих панелей. У первой я написал Buttonl, а у второй — close.
Теперь создадим обработчик события OnMouse Down (срабатывает, когда нажали кнопку мыши) для первой панели и в нем напишем следующий код: procedure TForml.PanellMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin
Panell.BevelOuter:=bvLowered;
end;
Одна строчка кода меняет вид панели. Создадим еще обработчик события OnMouseUp (срабатывает, когда отпустили нажатую кнопку мыши) для первой панели. По этому событию меняется вид панели на исходный: procedure TForml.PanellMouseUp(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin
Panell.BevelOuter:=bvRaised;
end;
Попробуйте запустить программу и нажать на первую панель. Когда вы нажимаете кнопку мыши, панель изменит внешний вид на вогнутый. При отпускании мыши панель возвращается в исходный вид. Таким образом, панель начинает работать как кнопка.
Раз так, давайте создадим экзотичную кнопку. Для второй панели изменим свойства:
О BevelOuter на bvRaised;
口 BevelInner на bvLowered.
Теперь создадим обработчик события OnMouseDown для второй панели и в нем напишем следующее:
Procedure TForml.Pane12MouseDown(Sender: TObject;
Bunton: TMouseBu11on; Shift: TShiftStL^tse; X, Y: Integer);
begin
Pane12.BevelOuter:=bvLowered;
Pane12.BevelInner:=bvRaised;
end;
Здесь меняется внешний вид панели на вогнутый. Теперь создадим обработчик события OnMouseUp для второй панели. По этому событию мы меняем вид панели на исходный:
procedure TForml.Panel2MouseUp(Sender: TObject;
Button: TNouseButton; Shift: TShiftState; X, Y: Inreger);
begin
Panel2.BevelOuter:=bvRaised;
Panel2.BevelInner:=bvLowered;
Close;
end;
К Пр—ечанме 】
На компакт-диске в папке \ Примеры \ Глава 7 \ Panel 1 вы можете увидеть пример этой программы.
Кнопки выбора (TRadioButton)
Эти кнопки очень похожи на TCheckBox даже по методу работы. У них так же есть свойство checked, которое отображает их состояние. Если RadioButton выделен, то это свойство равно true, иначе равно false. Единственная разница — если у вас на форме есть несколько таких компонентов, то одновременно может быть выделен только один.
Давайте посмотрим работу данного механизма селекции кнопок на примере. Бросьте на форму несколько компонентов RadioButton, запустите программу и попробуйте пощелкать мышью по этим компонентам.
工 Примечание"^
На компакт-диске в папке \ Примеры \ Глава 7 \ RadioButton вы можете увидеть пример этой программы.
Как видите, вы не можете выделить сразу два компонента RadioButton. А как же тогда сделать возможность двойного выбора на форме? Например, вам нужно выбрать с помощью Radio Button пол и семейное положение человека. Если вы расположите на форме компоненты выбора пола и семейного положения, ТО Выбрать СМОЖете ЧТО-ТО ОДНО. ДЛЯ ЭТОГО компоненты RadioButton можно убрать на панели (см. форму на рис. 7.9). Например, на левой панели
будет выбор пола, а на правой можно выбирать семейное положение. Таким образом на форме будут выбраны два компонента RadioButton.
4 Г* Form 14 Г* Form 1див
4 Г* Form 1
4 Г* Form 1
Г RadioButton2Г RadioButton3Г RadioButton2Г RadioButton3<• RadioBuUonl
Г RadioButton2
Г RadioButton3
Г RadioButton2
Г RadioButton3
Г RadioBuUon2
Г RadioBunon3
Рис. 7.9. ГруппированиекомпонентовTRadi©Button
Примечание
На компакт-диске в папке \ Примеры \ Глава 7 \ RadioButton 1 вы можете увидеть пример этой программы.
Больше ничего нового здесь не скажешь. Работа с этим компонентам происходит так же, как и с checkBox.
Списки выбора (TListBox)
Списки выбора хранят в себе какие-то списки (например, параметров) и дают пользователям возможность выбирать из них один или несколько параметров.
Чтобы получить доступ к строкам списка, нужно воспользоваться свойством items объекта TListBox. Это свойство имеет тип TStrings. Оно ничего вам не напоминает? Такой же тип у свойства Lines объекта ТМешо. Значит, работа со строками списка нам уже известна и не вызовет затруднений, потому что все, что мы говорили про эти методы и свойства Lines объекта Tmemo, также относится и к свойству items объекта TListBox.
Давайте напишем маленькое приложение с использованием этого компонента. Создайте новый проект. Установите на форме один компонент TListBox и один компонент TEdit. Теперь дважды щелкните по свойству items компонента ListBoxl, и перед вами откроется уже знакомый редактор строк. Наберем в нем названия всех основных цветов:
口 белый;
口 красный;
口 черный;
口 оранжевый;
□
желтый;
□
фиолетовый;
□
зеленый;
□
бирюзовый;
□ салатневый;
口 голубой;
口 вибирай себе любой :).
После этого нажмите кнопку ОК, чтобы сохранить введенные данные. У вас должна получиться форма, как показано на рис. 7.10.
Рис. 7.10. Форма будущей программы
Давайте теперь создадим обработчик события onclick для списка выбора. В нем напишем следующее:
procedure TForml . ListBoxlClick(Sender: TObject)r begin
Editl. Text: =ListBoxl. Items. Strings [ListBoxl. Itemindex J ;
end;
Свойство itemindex объекта ListBoxl указывает на выделенную строку списка выбора. С помощью ListBoxl. Items. Strings мы можем получить доступ ко всем строкам списка. В результате получается, что мы присваиваем Editl текст вьзделенной строки в списке выбора.
工 Примечание'
На компакт-диске в папке \ Примеры \ Глава 7 \ ListBox вы можете увидеть пример этой программы.
Все очень похоже на работу с TMemo. Для большей ясности давайте добавим к нашему приложению еще две кнопки Добавить и Удалить строку.
Для кнопки Добавить в обработчике события Onclick напишем следующий код:
procedure TForml . AddButtonClick (Sender: TObject);
begin
ListBoxl. Items. Add ( * Новаястрока')
end;
Для кнопки Удалить напишем такой текст программы: procedure TForml.DelButtonClick(Sender: TObject); begin
ListBoxl. Items. Delete (ListBoxl. Itemindex);
end;
Очевидно, что комментарии здесь излишни. Получилась полная аналогия с ТМето.
Бывают ситуации, когда надо выбрать несколько элементов в списке. Для этого у компонента ListBox есть свойство MultiSelect. Если в нем установить значение true, то можно будет выбирать мышью несколько строк. Для этого можно щелкать по нужным элементам, удерживая нажатой клавишу <Ctrl>.
Создадим пример, который проиллюстрирует работу компонента ListBox с возможностью множественного выбора. Для этого на форму поместим один КОМПОНеНТ ListBox (назовем еГО MultiListBox), ОДИН КОМПОНеНТ Мето (назовем его ResultMemo) и одну кнопку. В компонент ListBox нужно ввести несколько значений, чтобы было, что выделять, и установить у него СВОЙСТВО MuitiSeiect В true.
По нажатию кнопки мы будем искать все выделенные строки и переносить их в ResuitMemo. Для события кнопки мыши Onclick напишем программный код, который приведен в листинге 7.11.
Листинг 7.11. Обработчик события Onclick для кнопки
procedure TForml.CheckButtonClick(Sender: TObject); begin
ResuitMemo.Clear; “
if MultiListBox. Selected [ 0 ] then
ResuitMemo. Lines. Add (MultiListBox. Items. Strings [0]);
if MultiListBox. Selected [ 1 ] then
ResuitMemo. Lines. Add (MultiListBox. Items. Strings [1]);
end;
В первой строке очищается содержимое компонента Мето. Это чисто косметическое действие, чтобы наша программа выглядела более эргономично.
У компонента ListBox есть свойство selected. Это свойство имеет тип массива в виде булевых значений. Таких значений ровно столько же, сколько и строк в компоненте ListBox. Чтобы узнать, выделена ли определенная строка, нужно проверить соответствующий элемент в массиве Select.
Для иллюстрации сказанного проверяется, выделена ли нулевая строка MultiListBox.Selected[0]. Если да, то текст этой строки добавляется в компонент Мето. Точно таким же образом проверяется первая строка.
Алгоритм проверки, показанный здесь, далеко не совершенен, потому что мы еще не знаем, что такое циклы, и не увидели их работу на практике. В дальнейшем вы сможете улучшить этот пример и сделать его более универсальным, а пока нам достаточно этого.
Выпадающие списки (TComboBox)
Выпадающие (раскрывающиеся) списки по работе своих свойств и методов похожи на списки выбора. Я бы сказал, что это полная копия, только выглядит по-другому и в них нельзя выбирать сразу несколько элементов.
Давайте создадим приложение, похожее на предыдущее, только вместо ListBox будем использовать ComboBox. На рис. 7.11 можно увидеть форму будущей программы.
Рис. 7.11. Форма буцущей программы
Теперь создадим обработчик события OnChenge для выпадающего списка ComboBox 1. Это событие происходит, когда пользователь выбрал какой- нибудь элемент списка. Для него мы напишем следующий текст программы: procedure TForml.ComboBoxlChange(Sender: TObject);
begin
Editl.Text:=ComboBoxl.Items•Strings[ComboBox1.Itemlndex];
end;
Как видите, это полная копия кода из предыдущего примера, только используется другое имя компонента.
Теперь напишем код для кнопки Добавить:
procedure TForml.AddButtonClick(Sender: TObject);
begin
ComboBoxl. Items. Add ( 1 Новаястрока 1 ) end;
Для кнопки Удалить код будет следующим:
procedure TForml . DelButtonClick (Sender: TObject); begin
ComboBoxl. Items. Delete (ComboBoxl. Itemlndex); end;
屋 Прммечанме~~^
На компакт-диске в папке \ Примеры \Глава 7 \ ComboBox вы можете увидеть пример этой программы.
Как видите, здесь наблюдается полная аналогия со списком выбора и ТМепо. Содержимое списков можно сохранять с помощью ComboBoxl. Items. SaveToFile ( *Имя файла *), а загружать С ПОМОЩЬЮ ComboBoxl, Items. LoadFromFile (' Имя файла').
Существует несколько типов выпадающих списков. За тип списка отвечает свойство style.
工 Примечание
Это свойство можно изучить на примере маленькой программы, которую вы можете найти на компакт-диске в папке \ Примеры \ Глава 7 \ ComboBoxl. Она демонстрирует все типы списков в действии. На рис. 7.12 показано главное окно этой программы.
Рис. 7.12. Программа, отображающая разные типы выпадающих списков
Этот пример не может показать всей разницы стилей, поэтому рассмотрим их небольшое описание.
О CsDropDown — основной стиль. При нем вы можете не только выбирать значения из списка, но и вводить в строку свои.
О CsDropDownList — при этом стиле можно только выбирать из списка.
О CSOwnerDrawFixed — при этом стиле вы можете рисовать элементы сами с помощью графических средств (чуть позже будет показано, как это делается). Высота элементов фик сиро ванная.
口 CsOwnerDrawVariable — при этом стиле вы можете рисовать элементы сами. Отличается от предыдущего тем, что высота элементов нефиксированная.
口 Cssimple — только строка ввода без выпадаюшего списка. Компонент будет вытлядеть, как TEdit.
Позже в этой книге вам будет показано, как рисовать внутри списков выбора и выпадающих списков. Сейчас у нас пока не хватает знаний о графических возможностях Windows.
Полосы прокрутки (TScrollBar)
Полосы прокрутки очень часто используются для прокручивания какого-то действия. Например, когда вы слушаете музыку, вы можете прокрутить ее вперед или назад до любого места с помощью простой полосы прокрутки. Если информация не помещается в окно, ее так же прокручивают с помощью таких полос, но в большинстве случаев это делается автоматически, и тут вы вмешиваться в действия объектного инспектора будете очень редко.
Давайте посмотрим на полосу прокрутки в действии. Создайте новое приложение. Сформируйте на форме один компонент TLabel и одну полосу Прокрутки TScrolBar.
У компонента Label 1 измените свойство caption на 0. Теперь создайте обработчик события OnChange для полосы прокрутки и напишите там следующее:
procedure TForml.ScrollBarlChange(Sender: TObject);
begin
Label1.Caption:=IntToStr(ScrollBarl.Position); end;
В этом коде мы присваиваем свойству Caption компонента Labell значение текущей позиции ползунка полосы прокрутки. Текущее значение ползунка можно получить с помошью свойства Position объекта ScrollBarl. Только тут есть одно НО. Это свойство имеет тип ицелое число**, а свойство caption
компонента Label 1 — это строка. Поэтому нам надо превратить целое число в строку. Для этого есть функция intToStr. Ей нужно передать число, а она нам вернет строку. Поэтому если вызвать эту функпию с параметром текущей позиции ползунка intTostr (ScrollBarl. Position), результат ее работы МОЖНО ПРИСВОИТЬ СВОЙСТВУ Caption компонента Label 1.
Попробуйте запустить программу и подвигать ползунок. Значение позиции будет отображаться в компоненте Label 1.
Примечание
На компакт-диске в папке \ Примеры \ Глава 7 \ ScrollBar вы можете увидеть пример этой программы.
В этой программе мы написали пример горизонтальной полосы прокрутки. Чтобы сделать ее вертикальной, нужно свойство Kind поменять на sbvertical. И еще, значение ползунка изменяется от 0 до 100. Чтобы изменить эти значения, есть свойства Min (по умолчанию равно нулю) и мах (по умолчанию равно 100). Больше ничего особенного в работе полос прокрутки нет.
Группировка объектов (TGroupBox)
Компонент ТGroupBox очень удобно использовать для группировки каких-то компонентов. На вид это простая панель с заголовком наверху (рис. 7.13).
Рис. 7.13. Внешний вид компонента GroupBox
За текст, отображаемый в заголовке, отвечает свойство caption. Больше ничего особенного эта панель делать не умеет. У нее нет никаких особых свойств или методов.
К Примечание [I
Пример программы, использующей TGroupBox, вы можете увидеть На компактдиске в папке \ Примеры \ Глава 7 \ GroupBox.
Рис. 7.14. Пример, отображающий работу компонента TGroupBox
Панель TGroupBox в основном используют для группировки компонентов TRadioButton, и мы будем делать так же.
Группа компонентов
RadioButton (TRadioGroup)
Если установить этот компонент на форму, то на первый взгляд он выглядит, как простой TGroupBox.-Но это только на первый взгляд.
Создайте новый проект и бросьте на него один компонент TRadioBox. Щелкните по свойству items, н перед вами появится уже знакомый редактор строк. Введите туда три строчки:
Все
Выделенные
Текущую
Нажмите ОК и вы увидите GropBox, содержащий в себе три компонента TradioBox (рис. 7.15).
*рПараметрь1 печати""ГВсё
f「Выделенные ・
С Текущую
F—■ ■
Рис. 7.15. Внешний видкомпонента TRadioGroup
Зачем же нужен такой гибрид? Потерпите немного, пока мы не напишем программный код до конца. Тогда вы сможете оценить все прелести этого гибрида и, возможно, полюбите его.
Рис. 7.16. Форма будущей программы
Установите на форму еще одну кнопку, одну строку ввода и один TLabel. Расположите их так, как показано на рис. 7.16. Теперь создадим обработчик события OnClick для компонента RadioGroupl. В нем напишем следующее: Labell.Caption:=IntToStr(RadioGroupl・Itemindex);
Свойство Itemindex компонента RadioGroupi показывает, какой компонент сейчас выделен. Компоненты пронумерованы в таком же порядке, как записаны их имена в списке. Это свойство имеет тип "целое число", поэтому его приходится превращать в строку с помощью функции intToStr.
Для события, вызываемого нажатием кнопки, напишем следующий текст программы:
procedure TForml.ButtonlClick(Sender: TObject);
begin
Editl.Text:=IntToStr(RadioGroupl.Itemindex);
end;
В этом программном коде номер выделенного компонента помещается в Editl.
А теперь посмотрим на преимущества данного компонента. Представим, что у нас просто стоят три компонента TRadioButton. Чтобы узнать, какой из них сейчас выделен, нужно проверить свойство checked всех этих компонентов. А при использовании группы TRadioGroup ничего этого делать не надо. Нужно ТОЛЬКО проверить СВОЙСТВО Itemindex компонента TRadioGroup, и нам уже известен номер выделенного элемента.
Несмотря на это пре имуще ство, пр огр аммисты им очень редко пользуются в своих программах, потому что невозможно точно позиционировать элементы RadioButton Внутри группы.
Ответы на вопросы
Здесь будут ответы на вопросы, которые могут возникнуть у вас при чтении данной главы.
Вопрос. Почему, когда я навожу указатель мыши на компонент, например Tlabel, выскакивает подсказка, в которой написано Label, а не TLabel? Куда исчезает буква ”т”?
Delphi 6
File Edit Search View Project Run Component £atat
Standard j AdditionaU/WkSJf^ustern | Data Access ] Data N mr^AjiAi^ 府薛樓
Рис. 7.17. Подсказка над компонентом
Ответ: Действительно, в подсказках всегда отсутствует буква "т”. Просто компонент называется Label (рис. 7.17), а объект этого компонента называется ТLabel. Так принято, что имена всех объектов всегда начинаются с буквы ”т”. Это не значит, что так обязательно, а означает, что так желательно. Просто достаточно взглянуть на имя и сразу видно, что это имя объекта. В подсказках показывают имя компонента, поэтому там нет никаких букв в начале.
Глава 8Глава 8,甯
Глава 8
Глава 8
Учимся программировать
В этой главе будут рассматриваться основные принципы программирования. Здесь мы познакомимся с циклами на практике, увидим различные приемы программирования, и все это будет сопровождаться большим количеством полезных примеров.
Все знания приходят с практикой. Поэтому только самостоятельная работа позволит закрепить изучаемый материал. Здесь следует учесть тот факт, что все исходные коды, которые идут на диске, приведены только для того случая, когда у вас что-то не получается или если что-то непонятно из рисунков в книге и нужно увидеть реальную форму. Вы можете посмотреть их содержимое, но после этого должны повторить все действия самостоятельно.
Итак, приступим к последней главе, в которой будет описываться теория. После этого мы будем знакомиться уже только с разными приемами и технологиями программирования, ще будет минимум теории и максимум практики. Это последняя глава, в которой закладываются основы программирования.
Циклы for... to... do
Циклы — это основа любого программирования. Мы будем использовать их достаточно часто. Когда ранее строилась логика программы, циклы использовались для решения задачи с факториалом. Тоща мы писали программу в форме блок-схемы. Напомню, как выглядит логика цикла.
Листинг 8Л. ЛогикаЛистинг 8Л. Логикациклацикла
Листинг 8Л. Логика
Листинг 8Л. Логика
цикла
цикла
От 1 до 5 выполнять
Начало цикла
R:=R*INDEX;
INDEX:=INDEX+1;
Конец цикла
Это логика расчета факториала. Давайте перенесем ее на язык программирования Delphi.
Цикл в Delphi оформляется следующим образом:
for переменная : = начальное-Значение to конечное_значение do действие;
После слова for нужно присвоить какой-нибудь переменной начальное значение. Эта переменная будет использоваться в качестве счетчика. После каждого выполнения действия этот счетчик будет увеличиваться на единицу, пока переменная не превысит конечного значения.
В общем виде цикл выглядит так:
for . . . to . . . do
Действие!
Рассмотрим пример:
Листинг 8.2. Организация цикла
var
index:Integer;
sunt: Integer;
EndCount:Integer;
begin
Sum.:=0;
for index:=0 to 5 do
Sunt: =Sum+index;
end;
В этом примере объявляются две переменные index и sum типа "целое число". Сначала переменной sum присваивается значение 0. После этого запускается цикл, в котором переменная index будет изменяться от 0 до 5.
Теперь посмотрим поэтапно, что здесь происходит:
На первом этапе переменная index равна нулю, sum тоже равна нулю, значит, выполнится операция Sum:=o+O. Результат Sum = о.
На втором этапе index увеличена на 1, значит, выполнится действие sum: =0+1. Результат Sum = 1.
Здесь index увеличена на 1 и уже равна 2, a sum = 1. Значит, выполнится действие Sum:=1+2. Результат sum = з.
Здесь index увеличена на 1 и уже равна 3, a Sum = 3. Значит, выполнится действие Sum: =3+3. Результат Sum = 6.
Здесь index увеличена на 1 и уже равна 4, a sum = 6. Значит, выполнится действие sum: =4+6. Результат sum = 10.
Здесь index увеличена на 1 и уже равна 5, a sum = ю. Значит, выполнится действие Sum:^5+10. Результат Sum = 15.
Заметьте, что мы не увеличиваем значение переменной index, используя для этого определенные команды. Значение увеличивается автоматически.
Давайте перенесем рассмотренный выше профаммный код непосредственно в профамму, чтобы мы могли убедиться в этом на реальном примере. Создайте новое приложение. Поместите на форме два компонента TLabel, два компонента TEdit и одну кнопку. Форма будущей программы показана на рис. 8.1.
Рис. 8 1в Форма будущей программы
Компонент Editl переименован в EndEdit, a Edit2 переименован в ResultEdit. Теперь по нажатию кнопки нужно написать код из листинга 8.3.
Листинг 8.3. Обработчик события ОпсНскдля кнопки
procedure TForml.CalculateButtonClick(Sender: TObject);
var
index:Integer;
sum: Integer;
EndCount:Integer;
begin
Sum:=0;
EndCount:=StrToInt(EndEdit.Text);
for index:=0 to EndCount do Sum: =Sum+index;
ResultEdit.Text:=IntToStr(Sum); end;
В принципе, текст тот же самый. Единственная разница заключается в том, что цикл запускается, начиная от 0 до числа, введенного в компонент EndEdit. EndEdit содержит текст, а нам нужно превратить его в число, поэтому мы используем функцию StrToint для преобразования строки в число. Эта функция работает так же, как и IntTostr, которая, наоборот, преобразовывала число в строку.
Результат преобразования сохраняется в переменной Endcount:
EndCount: =StrToInt (EndEdit.Text);
После этого запускается цикл, в котором переменная index будет изменятся от 0 до значения EndCount (в котором находится число, введенное в EndEdit):
for index: =0 to EndCount do
Запустите программу и введите в строку Конечное значение число 5. После этого нажмите на кнопку, и в строке результата должно появиться число 15.
工 Примечание-^
На компакт-диске в папке \ Примеры \ Глава 8 \for..to..do вы можете увидеть пример этой программы.
Зцесь необходимо еще отметить то, что после цикла for будет выполняться только одно действие. Например, если вы захотите выполнить два действия подряд, то вы должны заключить их в скобки begin и end, как это показано в следующем примере.
Листмнг8.4. Выполнени ; неско ьких действий в цикле
for index: =0 to EndCount do begin
Sum: =Sum+Index;
Sum: =Sum+l;
end;
Здесь на каждом шаге цикла sum увеличивается еще на единицу. Если вы попробуете написать так:
for index : =0 to EndCount do
Sum.: =Sum+Index;
Sum.:=Sum.+l;
то выполняться в цикле будет только строчка Sum: =Sum+index. Вторая строка Sum:=Sum+l выполнится только по окончании цикла.
< Совет )
Если вы что-то не поняли в предыдущем примере, вернитесь к главе, где описывались блок-схемы. Там был рассмотрен пример, который работает как цикл for … to … do. Попробуйте точно так же самостоятельно нарисовать блок-схему для данного примера и мысленно пройдите ее по шагам.
Циклы while
Еще одной разновидностью цикла является цикл while. Это слово переводиться как "гюка”. Это значит, что цикл будет выполняться, пока выполняется условие.
В общем виде он выглядит следующим образом:
while условие do действие
Сразу рассмотрим пример.
Листинг 6 S. Пример использования цикла while
var
index:integer;
begin
index:=0;
while index<10 do
index:=index+l;
end;
В этом примере мы объявляем переменную index. В первой строке кода присваиваем ей 0. После этого запускается цикл. В условии записан код — index<10. Это значит, что будет в ыполняться следующее действие (index: =index+l), пока переменная index меньше 10.
В данном случае в качестве счетчика используется переменная index и цикл выполняется, пока верно условие. В отличие от цикла for, этот цикл не увеличивает автоматически счетчик, поэтому мы должны это делать самостоятельно.
Внимание
Если забыть про увеличение счетчика в цикле, он может стать бесконечным и "подвесить" программу.
В цикле while также выполняется только одно действие. Если вы захотите выполнить в цикле сразу два действия, то должны заключить их в операторные скобки begin И end.
Давайте перепишем предыдущий пример, но с использованием цикла while. Измените код по нажатию кнопки на следующий:
Листинг В.6. Пример использования цмх 工寧
procedure TForml. CalculateButtonClick (Sender: TObject); var
index:Integer;
sum: Integer;
EndCount:Integer;
begin
Sum:=0;
index:=0;
EndCount :=StrToInt (EndEdit. Text);
while index<EndCount do
begin
Sum: =Sum+index;
index:=index+1;
end;
ResultEdit. Text: =IntToStr (Sum);
end;
В данном примере надо обнулять не только переменную Sum, но и index, чтобы начальное значение было равно нулю и цикл шел от нуля до введенного значения. Обратите также внимание на то, что здесь нужно самостоятельно увеличивать переменную index (index :=index+l). Для этого данная строчка добавлена в цикл. Она объединена с расчетом суммы при помощи операторных скобок begin И end.
Попробуйте запустить программу и ввести число 5. Результатом расчета будет 10. Если вы помните предыдущий пример, там результат был 15. В чем проблема? Почему разные результаты? В предыдущем примере мы выполняли цикл от 0 до 5 включительно. Здесь будет выполняться цикл от 0 и до того момента, пока выполняется условие index<5. Когда index=5, условие не выполнится и расчет с цифрой 5 не будет производиться.
Для решения этой проблемы можно поменять условие цикла на index<=5 (переменная index меньше или равна EndCount). В этом случае расчет с цифрой 5 также будет произведен. Или можно вводить цифру 6.
工 Примечание 】
На компакт-диске в папке \ Примеры \ Глава 8 \ while вы можете увидеть пример этой программы.
Циклы repeat
Теперь давайте разберем еще один тип циклов repeat . .. until. Этот тип цикла похож на while. Даже смысл цикла похож. Он означает — выполнять действия, пока не выполнится определенное условие. Только тут есть пара отличий.
口 В цикле while действия выполнялись, пока условие верно. В цикле repeat действия будут выполняться, пока условие неверно, и прекращается, когда оно станет верным.
口 В цикле while выполнение условия проверяется перед началом действий. Это значит, что если условие заведомо неверно, то действия цикла не будут выполнены. В цикле repeat сначала выполняется действие, а потом происходит проверка. Это значит, что если условие заведомо неверно, действие все равно будет выполнено один раз, просто на второй проход цикла перехода не будет.
В общем виде цикл repeat выглядит так:
repqat
действия
until Условие;
Обратите внимание, что в этом случае действий может быть несколько. Тут уже не надо объединять несколько действий в begin . . . end, потому что repeat . . . until уже действует как объединение нескольких действий.
Давайте рассмотрим надоевший пример с использованием этого типа цикла:
ЛИСТИНГ 8.7. Пример ИСПОЛЬЗОВаНИЯ ЦИКЛЯ repeat I 一 一——
procedure TForml.CalculateButtonClick(Sender: TObject); var
index:Integer;
sum: Integer;
EndCount:Integer;
begin
Sum:=O;
index:=0;
EndCount:«StrToInt(EndEdit.Text);
repeat
Sum: =Sum+index;>'
index:=index+l;
until index>EndCount;
ResultEdit.Text: =IntToStr (Sum);
end;
Здесь действия будут выполняться в цикле, пока переменная index не станет больше числа, указанного в EndCount.
工 Примечание
На компакт-диске в папке \ Примеры \ Глава 8 \ repeat- вы можете увидеть пример этой программы.
Управление циклами
Работой цикла можно еще и управлять. В Delphi есть два оператора управления —break и continue. Начнем рассмотрение с оператора continue.
Допустим, надо разделить число 10 на числа от -3 до 3 и вывести результат В TListBox.
ан^^™т^а^и^~^~-ур・м^
Листинг S 8 Ццкл деления числа 10
procedure TForml.CalculateButtonClick(Sender: TObject); var
r: Integer;
begin
for i:=-3 to 3 do begin
r:=round(10/i);
ListBoxl.Items.Add(* 10/*+IntToStr(i) +1 =1+IntToStr(r)); end;
end;
В этом примере запускается цикл, в котором переменная i будет изменяться от ~3 до 3. На каждом шаге 10 делится на значение i и сохраняется результат в переменной г.
При делении используется функция round, которая округляет переданное ей значение. В качестве исходного значения мы передаем ей результат деления 10 на переменную i, т. е. round (10/i), Таким образом, в переменную г будет записан округленный до целого результат деления. Этого пока достаточно знать о функции round, потому что с ней мы познакомимся поближе немного позднее.
Следующим действием является добавление результата в List В oxi с одновременным преобразованием переменной г в строку.
Давайте посмотрим, что произойдет, когда переменная i будет равна 0. В этом случае число 10 будет делиться на 0, а значит, произойдет ошибка, потому что на 0 делить нельзя. Как же тоща выйти из этой ситуации? Можно на каждом этапе цикла проверять значение i, и если оно равно 0, то не выполнять никаких действий. В листингах 8.9. и 8.10 приведены два возможных решения.
Листинг 8.9. Первое решение проблемы деления на О
procedure TForml.CalculateButtonClick(Sender: TObject);
var
r: Integer;
begin
for i:=-3 to 3 do
begin // Это начало для оператора for if ioO then
begin // Это начало для оператора if
г: =round(10/i);
ListBoxl.Items.Add(110/*+IntToStr(i) + , = ,+IntToStr(r));
end; // Этот end для оператора if
end; / / Этот end для оператора for end;
В этом примере на каждом этапе проверяется значение i, и если оно не равно 0, то только в этом случае производятся вычисления.
Это очень простое решение для маленьких и простых программ. Но если ваш цикл большой и выполняет много действий, то такое решение будет как минимум неудобно и может потеряться нчитабельностьн кода. В худшем случае, вообще может ничего не получиться. Вот туг на помощь приходит оператор continue.
I Листинг 8.10. Второе решение проблемы деления на О
procedure ТForml.CalculateButtonClick(Sender: TObject); var
r:Integer;
begin
for i:=-3 to 3 do
begin // Это начало для оператора for
if 1=0 then
begin // Это начало для оператора if
ListBoxl. I terns. Add (1 На ноль де1шть нельзя1);
Continue;
end; // Этот end для оператора if
г:round(10/i);
ListBoxl.Items.Add(110/* +IntToStr(i) +』'+工ntToStr (G );
end; // Этот end для оператора for end;
В этом примере мы также проверяем на каждом этапе значение переменной i. Если оно равно нулю, то выводится сообщение в ListBoxl о том, что на ноль делить нельзя, и выполняется оператор continue. Как только программа встречает такой оператор, она сразу прерывает дальнейшее выполнение цикла и переходит на следующий шаг. Это то же самое, что выполнить команду: "Остановить дальнейшее выполнение программы, увеличить значение переменной i и начать выполнение цикла со следующим значением".
Как только программа встречает оператор continue, она перескакивает на конец цикла, где увеличивается значение счетчика (в данном случае переменная i) и продолжается выполнение уже со следующего шага.
На рис. 8.2 показана форма с результатом работы нашего примера.
二 Пр-мечанме :
На компакт-диске в папке \ Примеры \ Глава 8 \ continue вы можете' увидеть пример этой программы.
Теперь давайте разберемся с оператором break. Как только программа встречает такой оператор, цикл прерывается и выполнение передается следующему оператору после оператора цикла. Давайте возьмем наш предыдущий пример и заменим в нем continue на break (листинг 8.11).
Рис. 8.2. Результат работы программы
Листинг В11. Использование оператора break
procedure TEormLCalculateButtonClick(Sender: TObject);
var
r:Integer;
begin
for i:=-3 to 3 do
begin // Это начало для оператсра. for
if 1=0 then
begin // Это, начало для оператора if
ListBoxl. I terns. Add (' На ноль делить нельзя');
break;
end; // Этот end для оператора if
r:=round(10/i);
ListBoxl. Items.Add(' 10/'+工ntToStr (i)+』'+工ntToStr (r));
end^ // Этот end для оператора for
ListBoxl.Items.Add(1 Расчет окончен');
end;
В этом случае, если значение i будет равно нулю, то выводится сообщение о том, что на ноль делить нельзя, и цикл прерывается. После этого управление передастся следующему оператору после цикла.
7 Зак 942
На рис. 8.3 показан результат работы программы.
10/-3«=-310Л2--510Л1=-10На ноль делить нельзяРасчёт окончен10/-3«=-310Л2--510Л1=-10На ноль делить нельзяРасчёт окончен
10/-3«=-3
10Л2--5
10Л1=-10
На ноль делить нельзя
Расчёт окончен
10/-3«=-3
10Л2--5
10Л1=-10
На ноль делить нельзя
Расчёт окончен
i Подсчитать
Рис. 8.3. Результат работы программыс оператором break
Как видите, после встречи с нулем в цикле больше ничего не выполняется и оператор break просто завершает его работу.
Примечание
На компакт-диске в папке \ Примеры \ Глава 8 \ break вы можете увидеть пример этой программы.
5. Логические операторы
С логическими операторами мы уже знакомились и уже достаточно много работали. Но здесь мы рассмотрим более полную информацию по логическим операциям. Точнее сказать, по одной — if.
Как вы уже знаете, она выполняет проверку — **Если какое-то условие в ыполнено, то выполнить следующее за условием действие**. Если нужно выполнить несколько действий, то их нужно объединить с помощью begin .. . end.
В общем виде логика выглядит так:
If Условие выпозшено then
Действие!;
Если нужно выполнить два действия, то нужно написать так:
If Условие выполнено then
begin
Действие!;
Действие2;
end;
При проверке нескольких условий можно использовать несколько способов. Первый из них можно определить следующим образом:
If Условие! вьктолнено then
If Условие2 выполнено then
Действие!;
Если Условие! верно, то выполнится следующее за логикой действие, а это вторая проверка. Если вторая проверка (условие 2) верна, то выполнится действие. Если хотя бы одно из условий не выполнится, то цепочка прерывается и действие не будет выполнено.
Второй способ в большинстве случаев удобнее и нагляднее. Он может быть представлен следующим кодом:
If (Условие! выполнено) and (Условие2 выполнено) then Действие!;
В этом примере объединены две проверки в одну. Если Условие! и Условие! верны, то выполнится действие.
А если вам нужно выполнить действие, если хотя бы одно из условий верно? Не обязательно, чтобы оба сразу, а хотя бы одно. В этом случае можно для объединения использовать не and, a or. Это будет выглядеть так:
If (Условие! выполнено) or (Условие2 выполнено) then
Действие!;
Если вы объединяете два условия в один оператор if, то их обязательно нужно оградить скобками. Если вы их не поставите, то возникнет ошибка. Вот пример неправильного оформления:
If Условие! выполнено or Условие2 выполнено then Действие!;
В этом случае будет не объединение двух проверок, а бинарная операция (бинарные операции требуют отдельного разговора), для которой неправильно записана строка. Поэтому и возникает ошибка.
В качестве условий можно применять операторы, представленные в табл. 8.1.
Таблица 8」.Операторы сравнения
Оператор
Описание
Пример использования
<
Меньше
Index < 10
>
Больше
Index > 10
--
Равно
Index = = 10
< =
Меньше либо равно
Index < = 10
> =
Больше либо равно
Index > = 10
Существует одно исключение, при котором оператор может отсутствовать. Если вы проверяете булеву переменную, то оператор можно опустить. Пример такого случая показан в листинге 8.12.
Листинг 8.12. Пример, в котором опущен оператор сравнения
var
b:Boolean;
begin
b:=true;
if b then
Выполнить действие;
end;
В этом примере происходит проверка булевой переменной Ь. Но с чем ее сравнивают, не указано. Как вы знаете, булевы переменные могут принимать одно из двух значений: true или false (истина или ложь). Так вот, в этом случае происходит проверка на истину. Если булева переменная равна true, то действие будет выполнено. Если Ь равна false, то действие не будет выполнено.
До сих пор мы рассматривали сокращенный вид логики if. В полном виде она выглядит так:
If Условие выполнено then
Действие!
else
Действие2
В этом виде, если условие выполнено, то выполнится Действие!, иначе выполнится Действие!.
Внимание
Вы должны уже знать, что каждый оператор должен заканчиваться точкой с запятой. Запомните, что после любого оператора перед else точка с запятой не ставится. Это как исключение, которое надо помнить.
Давайте рассмотрим пару примеров. В листинге 8.13 собрано несколько операторов сравнения.
Листинг 8J3 Пример работы с логическими операциями
var
i:integer;
begin
if i>0 then
i:=i+10 //Обратите внииание, что точки с запятой нет. else
i:=i+20
if i<0 then
begin
i:^10;
i:=i-2;
end // Обратите внииание, что точки с запятой нет.
else
begin
i:=20;
i:~i-2;
end;
end;
Вот теперь рассказано все необходимое о логических операциях и как с ними работать.
工 Пр-мечанпе ;
На компакт-диске в папке \ Примеры \ Глава 8 \ if вы можете увидеть пример программы, в которой используются различные типы логических операций.
При использовании логичесьсих операций результат сравнения всегда должен быть логическим.
(Совет )
Нельзя написать так—— if i:=o then. Здесь в операторе используется присваивание, а у него нет результата, а значит, произойдет ошибка. Чтобы не делать таких ошибок, просто запомните, что при использовании оператора if обязательно должны применяться только логические операции равенства, больше, меньше и т. д.
Единственный случай, когда можно опускать операторы сравнения, — это когда проверяется логическая переменная. Например: var
ре rem: Boolean;
begin
perem:=true;
if perem then
perem: =false;
end;
В этом примере нет никаких операторов типа равенства, больше или меньше, потому что переменная param — логическая и проверяется она. Если она равна true, то следующее действие выполнится если нет, то пропустится.
6. Работа со строками
В этой части мы рассмотрим, как можно работать со строками, и поговорим об основных функциях обработки строк. Также рассмотрим варианты использования этих функций. Для этого будут написаны несколько полезных примеров, и мы разберем их по командам.
Для начала давайте рассмотрим основные функции для работы со строками. /V
Функция Length
Эта функция возвращает длину строки. У нее есть только один параметр — строка, длину которой надо вернуть. Функция Length выглядит так:
function Length (S) : Integer;
Пример использования функции показан в листинге 8.14.
I Листинг 8Л4. Пример определения длины строки
var
Str: string;
Index:Integer;
begin
Str:=* Привет *;
index:= Length(Str); end;
В этом примере объявлены две переменные str (строка) и index (целое число). В первой строчке кода в переменную str помещается строка нПриветн. После этого переменной index присваивается длина строки str. Результат, записанный в переменную index, будет равен числу 6 — длина строки.
Функция Сору
Эта функция возвращает указанный отрывок строки. Например, вам нужно получить из строки "Меня зовут Михаил** символы с 5-го по 10-й. Это легко сделать с помощью функции Сору. У нее есть три параметра.
口 Строка, из которой нужно получить отрывок текста.
П Начальный символ.
П Количество нужных символов.
function Copy(S; Index, Count: Integer): string;
Пример использования функции показан в листинге 8.15.
Листинг 8.15. Пример использования функции Сору
var
Strl: string;
Str2:Integer;
begin
Strl: = rМеня зовут Михаил';
Str2:= Copy (Strl, 5, 5);
end;
Здесь объявлены две строковых переменных: Strl и str2. В первой строчке кода мы присваиваем переменной strl строку "Меня зовут Михаилн. В следующей строке происходит копирование в переменную str2 пяти символов из переменной strl, начиная с 5-го символа. Получается, что мы копируем строку с пятого символа по первый. Результатом будет в str2 строка: "зовут".
Функция Delete
Эта функция удаляет кусок текста из указанной строки. У нее есть три параметра.
О Строка, из которой нужно удалить отрывок текста.
П Символ, начиная с которого будут удаляться символы.
П Количество символов для удаления.
В общем виде функция выглядит так:
procedure Delete (var S: string; Index, Count: Integer) ; •:
Пример использования функции Delete показан в листинге 8.16.
I Листинг 8Л6 Пример использования функции Delete |
var
Strl:string;
begin
Strl:= 'Меня зовут Михаил';
Delete(Strl, 5, 5);
end;
В этом примере мы удаляем из строки strl символы с 5-го по 10-й (пять символов, начиная с 5-й позиции). В результате в переменной strl останется только строка "Меня Миша*.
Функция Pos
Эта функция ищет указанные символы в строке. Если эти символы найдены, то она вернет порядковый номер, начиная с которого найдена нужная строка. У функции два параметра.
П Строка, которую надо искать.
□ Строка, в которой надо искать.
Если подстрока не найдена, то функция вернет ноль.
function Pos(Substr: string; S: string): Integer;
Пример использования функции Pos показан в листингу 8.17.
Листинге 17 Пример исполе ования функции г: ]^L .
var
Strl:string;
index: integer;
begin
Strl: = 'Меня зовут Миша1;
index: =Pos (* Mijma1, Strl);
end;
В этом примере мы запускаем поиск строки нМиша" в строке strl. В данном случае строка “Миша" есть в строке переменной и начинается с символа 11. Результат — в переменной index будет число 9.
Функция Insert
Эта процедура вставляет одну строку в другую, начиная с указанного символа. У нее есть три параметра.
П Строка, которую надо вставить.
О Строка, в которую надо вставить.
□ Позиция, куда надо вставить.
В общем виде функция выглядит так:
procedure Insert(Source: string; var S: string; Index: Integer)
Пример использования функции insert показан в листинге 8.18.
Листинг 8.18. Пример использования функции Insert
var
Strl:string;
index: integer;
begin '
Strl:='Меня Миша*;
Insert(1 зовут I Strl, 6);
end;
Здесь в строку strl вставляется текст “зовут", начиная с 6-го символа. Результатом будет строка “Меня зовут Миша".
7. Исключительные ситуации
Теперь пора познакомиться с исключительными ситуациями. Для чего они нужны? Допустим, что у вас есть участок кода, ще может произойти ошибка. Как сделать так, чтобы программа не зависла при ее возникновении? Очень просто. Надо заключить этот код в блок проверки исключений, и тогда ваша программа выдержит даже нцунами**.
Итак, простейший блок исключений выглядит следующим образом:
tru
//Здесь ты пишешь код, в котором может произойти ошибка except
//Если ошибка произошла, то выполнится этот код end;
Рассмотрим простейший пример.
tru
х:=у/0;
except
//Здесь можно вывести сообщение об ошибке.
end;
х:=0;
Между tru и except у нас стоит маленькое действие — деление на ноль. Компьютер не умеет делать такие вещи (на ноль делить нельзя), поэтому произойдет ошибка и выполнится код между except и end. Если бы не было блока tru, то после возникновения ошибки процедура закончила бы выполнение, и все остальные операторы не были бы выполнены, как, например, в нашем случае — х:=о. При использовании tru ... except не будут выполнены только операторы между строкой, которая стала причиной ошибки, и except. После оператора except ... end все будет вьшолняться, как будто ничего не произошло.
Если бы мы поменяли 0 на любое другое число, то ошибки бы не было, и код между except и end никогда не выполнился бы.
Такой блок можно использовать не только для вывода сообщения о произошедшей ошибке, он и исправить ситуацию. Например, в нашем случае, если между tru и except произошла ошибка, то между except и end можно присвоить переменной х значение по умолчанию на случай ошибки и продолжить выполнять процедуру с этим значением.
Давайте посмотрим еще пример, который показан в листинге 8.19.
Листинг 8.19. Пример с выделением и освобождением памяти
var
b:Tbitmap begin
b:= TBitmap.Create;
tru
b.Canvas.Rectangle(lr1,100r100);
except
//Здесь можно вывести сообщение об ошибке.
Ь.free;
exit;
end;
b.free;
end;
В этом примере мы создаем объект ь типа TBitmap (картинка). Потом начинаем блок tru. В этом блоке мы пытаемся начать рисование. Если во время рисования произошла ошибка, то можно сообщить об этом пользователю и освободить память b. free. После этого происходит выход из процедуры с помощью оператор exit. Если ошибок не было, то просто освобождается память Ь. free.
Теперь давайте разберемся с еще одним типом исключительных ситуаций — tru . . . finally.
tru
//Здесь пишется код, в котором может произойти ошибка finally
/ /Этот код выполнится в любом случае
end;
Между tru и finally вы пишете свой сомнительный код, в котором может произойти ошибка. А между finally и end пишется код, который должен вы полниться вне зависимости от результата кода между tru и finally. В этом случае мы не можем информировать пользователя об ошибке, потому что в разделе finally мы не знаем, произошла ошибка или нет. Зато работа с памятью упрощается (листинг 8.20).
t
Листинг 8.20. Упрощенный пример работы с выделением и освобождением памяти
var
b:TBitmap
begin
b:= TBitmap.Create;
tru
b.Canvas.Rectangle(1,lr100,100);
finally
b.free;
end;
end;
Подобный пример уже рассматривался. В данном случае мы создаем^ объект ь и пытаемся рисовать. В разделе finally, который выполняется всегда, вне зависимости от того, была ошибка или нет, мы удаляем созданный объект ь. Теперь мы уверены, что ь всегда будет удален корректно и выделенная память будет освобождена. Таким образом, код между finally и end будет выполняться всегда, вне зависимости от произошедших ошибок.
工 Примечание
Если бы все программы в Windows были написаны корректно и сомнительные участки кода заключались бы в блоки исключительных ситуаций, пользователь забыл бы, что такое синий экран смерти. Если вы собираетесь писать коммерческое программное обеспечение, то ошибки в нем непростительны. Никто не будет покупать нестабильные программы, которые будут зависать через каждые пять минут. Это я вам говорю из своего опыта.
Будьте внимательны. Ошибки могут появиться даже в самых неожиданных местах. Пользователь очень часто может нажимать не туда, -куда надо, и сбивать программу с пути правильного выполнения. Конечно же, можно говорить, что пользователь безрукий, но этот безрукий вам платит за то, чтобы программа работала. Поэтому от его действий нужно предохраняться. Исключительные ситуации тут один из лучших способов.
Глава9Глава9嚇
Глава9
Глава9
Создание рабочих приложений
В этой главе рассматриваются вопросы, касающиеся создания самых настоящих приложений. До этого момента мы писали только примеры. Они были очень просты и представляли только суть изучаемого вопроса.
Изучать эту главу мы начнем с создания меню. Это единственное, что было пропущено при рассмотрении палитры компонентов Standard. Сейчас настало время восполнить этот пробел.
После этого будет показано, как создавать панели кнопок, как работать с ними, а также здесь будут приведены примеры реализации всех этих действий. Глава будет насыщена интересными примерами и их описаниями.
Создание главного меню программы
Давайте создадим маленькую программу, которая будет формировать некоторое меню. Какое именно — это не особо важно, лишь бы научиться с ним работать.
Создайте новое приложение. Установите на форму один компонент MainMenu. Теперь посмотрим, какие есть свойства у этого компонента.
П Auto Hotkeys ——свойство, которое определяет, будут ли создаваться автоматически клавиши быстрого вызова. ЕСЛИ Выбрать ma Automatic, ТО Delphi будет автоматически создавать клавиши. При выборе maManual это придется это делать вручную.
Примечание
Не путайте клавиши быстрого вызова с горячими клавишами, которые можно создавать только вручную. Посмотрите на меню какой-нибудь программы. Все названия пунктов содержат в названии подчеркнутую букву. Если вы войдете в меню (например, нажатием клавиши <Alt>), то, нажав подчеркнутую букву, вы переместитесь на этот пункт.
О AutoMegre — свойство, которое определяет автоматическое слияние с меню дочерних окон.
images — используя это свойство, можно подключать списки картинок, которые смогут отображаться на пунктах меню.
items — в этом свойстве описываются пункты меню.
Сразу подключим список картинок. Установите на форму компонент imageList с вкладки Win32. Теперь дважды щелкните по нему левой кнопкой мыши, и перед вами откроется окно работы со списком картинок, как это показано на рис. 9.1.
Рис. 9.1, Окно управления картинками в списке ImageList
Нажмите кнопку Add, чтобы добавить картинку. Откроется стандартное окно выбора файла. Откройте какую-нибудь картинку, и она добавится в список. Желательно, чтобы она была размером 16x16. Именно такие габариты используются по умолчанию.
工 Примечание
На компакт-диске в папке \ Images вы можете найти большое количество картинок для кнопок. В папке \ Примеры \ Глава 9 \ Menu находится исходный код примера и картинки, которые будут использоваться в этом примере.
Можете таким образом добавить несколько картинок. Четыре кнопки, которые были добавлены в этом примере, вы можете увидеть на рис. 9.2.
После того как была добавлена картинка, вы сразу же можете изменить цвет, который будет прозрачным (цвет фона, который не будет выводиться на кнопку). По умолчанию используется цвет крайнего левого нижнего пик-
села. Чтобы изменить цвет, можно ввести код или выбрать системный цвет из выпадающего списка Transparent Color. Если вы точно не знаете код и использовали не системный цвет, то можно щелкнуть по нужному цвету на самой картинке, в поле Selected Image слева вверху.
Рис- 9.2. Окно управления картинками с добавленными изображениями
Теперь подключим наш список картинок к меню, чтобы его пункты могли сопровождаться графическими изображениями. Выделите компонент Ma inMenu 1 и у свойства images, в выпадающем списке, выберите пункт imageListl. Пример будет достаточно долгим, и чтобы не запутаться, не меняйте имена компонентов, данные по умолчанию.
Теперь создадим само меню. Для этого дважды щелкните по свойству items, и перед вами откроется редаьсгор меню, как показано на рис. 9.3.
Рис. 9.3. Редактор меню
Этот же редактор можно вызвать, если дважды щелкнуть левой кнопкой мыши по компонентуМainMenul.
Кругом на рис. 9.3 выделен уже созданный пункт. Перейдите в объектный инспектор и наберите в свойстве caption слово "<1)айл''. Как только вы нажмете клавишу <Enter>, будет создано меню Файл (рис. 9.4).
Рис. 9.4. Меню Файл
Обратите внимание, что слева и под меню Файл созданы два пункта без имени. Это пустые заготовки, с помощью которых вы можете расширять его. Они без имени и не будут отображаться в программе.
Давайте создадим еще и меню Помощь. Щелкните справа от созданного меню (в рамочке, обведенной пунктиром) и снова перейдите в объектный инспектор. Там введите в свойстве Caption слово "помощь".
Теперь создадим подпункт для меню Помощь. Щелкните в рамке чуть ниже меню Помощь. В свойстве caption введите фразу "О программе**. В результате у вас должно получиться что-то похожее на рис. 9.5.
Рис. 9.5. Результат создания меню
Теперь таким же образом заполним меню Файл. Выделите его. Теперь щелкните в рамочке чуть ниже. Здесь мы напишем в свойстве Caption слово "Открыть". Когда вы нажмете клавишу <Enter> или перейдете на другой пункт меню в редакторе, создаст пункт Открыть и тут же немного ниже будет сформирован новый пустой пункт. Щелкните по нему и введите в свойстве Caption слово ℃охранитьп.
Теперь снова щелкните на новом пункте меню и у него в свойстве caption просто введите тире (—). Это заставит Delphi создать сепаратор (разделитель меню), как показано на рис. 9.6.
Рис. 9.6. Пример разделителя
И, наконец, создадим последний пункт — Выход. Попробуйте его сделать самостоятельно в меню Файл.
Теперь назначим каждому пункту меню картинки. Рассмотрим как это делать на примере пункта Открыть, а остальные вы сделаете сами.
Выделите пункт Открыть. Теперь в объектном инспекторе щелкните по выпадающему списку свойства Imageindex. Перед вами откроется список всех картинок, которые вы подключили ранее (рис. 9.7).
Рис. 9.7. Список картинок
Выберите ту, которая вам больше понравится, из состава подключенных. В редакторе меню изображение не будет видно, зато в редакторе форм вы сразу сможете увидеть ее.
R Примечание :
На компакт-диске в папке \ Примеры \ Глава 9 \ menu вы можете увидеть пример этой программы.
Теперь создадим обработчик события, который будет вызываться всякий раз при выборе пункта меню. Для этого выберите в редакторе меню пункт Выход и щелкните по нему дважды или перейдите на вкладку Events объектного инспектора. После этого дважды щелкните по событию onclick. Эти действия заставят Delphi создать обработчик события при выборе пункта меню.
В этом обработчике напишем следующее:
procedure TForml.N7Click(Sender: TObject);
begin
Close;
end;
Здесь используется метод формы close. Этот метод закрывает форму, а если мы закрываем главную форму, то закроется все приложение.
工 Примечание、
На компакт-диске в папке \ Примеры \ Глава 9 \ menul вы можете увидеть пример этой программы.
Напоследок необходимо узнать, как меню подключается к форме. Когда вы поместили новый компонент Ma inMenu на форму, то он автоматически появился в свойстве формы Menu. Если вы удалите название компонента из этого свойства, то меню исчезнет. Если вы укажете другой компонент меню, то в качестве меню для формы будет использоваться указанный компонент.
Создание дочерних окон
До сих пор мы создавали простые приложения, состоящие из одного только главного окна. В этой части главы мы рассмотрим, как создать приложение, состоящее из одного главного окна, которое может вызывать дочернее окно.
Для примера будет использоваться исходный код из предыдущего раздела главы, где было создано меню. Откройте этот проект.
Для начала создадим новую форму. Для этого из меню File выберите пункт New, а затем пункт Form. Это касается только Delphi 6 и 7. В более старых версиях нужно просто выбрать File | New Form.
Delphi должен создать новую чистую форму. Откройте менеджер проектов (View I Project Manager). Посмотрите на его содержимое и убедитесь, что в вашем проекте projectl.exe теперь есть две формы: Unitl и Unit2 (рис. 9.8).
Двойной щелчок по любой из форм в менеджере проектов загрузит форму в редактор. В принципе, новая форма уже открыта, и нам не надо дважды щелкать левой кнопкой мыши. Хотя можете попробовать, и Delphi моментально откроет форму для редактирования. В дальнейшем при редактировании вам это пригодится, чтобы открывать дочерние окна с целью их изменения.
Давайте сразу сохраним новую форму. Для этого при выделенной новой форме нажмите комбинацию клавиш <Ctrl>+<S>. Перед вами появится
стандартное окно для ввода имени формы. Ранее указывалось, что имена нужно задавать осмысленные. В книге иногда происходит отступление от этого правила, чтобы вам легче было читать, но в реальных проектах вы должны делать все осмысленно, иначе потом будет тяжело работать.
Рис. 9.8. Менеджер проектов
Данное окно у нас будет показывать информацию о программе, поэтому на- звем его AboutUllit.pas. Модуль главной формы переименуем в MainUnit.pas.
(Совет )
Нельзя просто так переименовывать имена модулей. Для этого желательно использовать меню File | Save As.
Сразу измените и имя формы с Form2 на About Form. После этого изменим заголовок формы на О программе. Можете еще приукрасить как-нибудь эту форму. Установим несколько компонентов TLabel, чтобы сделать надписи. Но это уже неважно. Для нас главное научиться работать с этими формами (пример формы показан на рис. 9.9).
Рис. 9.9. Форма будущего окна О программе
Теперь нужно показать это окно. Давайте создадим обработчик события Onclick для пункта меню О программе для нашей главной формы. Когда вы создаете обработчик, Delphi даст процедуре непонятное название типа N4Click. Если бы заголовок меню был написан на английском, то имя обработчика было бы хоть немного понятным, потому что Delphi использовало бы имя меню и слово Click. Но у нас все написано на родном языке, а его в коде программы использовать нельзя, поэтому среда разработки изменяет его на букву N с номером. Число у вас может отличаться. Мы договорились, что все будем называть понятными именами, поэтому переименуйте ее в объектном инспекторе в About с lick. Для переименования достаточно напечатать в объектном инспекторе новое имя напротив события Onclick и нажать клавишу <Enter>.
Теперь в получившемся обработчике напишем следующее:
procedure TForml.AboutClick(Sender: TObject);
begin
AboutForm.ShowModal;
end;
В этом коде мы вызываем метод ShowModal окна AboutForm. Этот метод показывает форму в режиме Modal. В этом режиме окно получает полное управление, и пока оно не закроется, главная форма не будет работать.
Рис. 9.10. Окно подключения модуля
Если вы попробуете сейчас откомпилировать код, то получите ошибку. В Delphi 5 это будет просто ошибка, означающая что AboutForm не найдена. Эго потому, что данная форма описана в нашем модуле About Unit, а мы используем ее в Ma inUnit. Чтобы MainUn.it смог увидеть форму, описанную в Aboutunit, нужно ее подключить. Для этого перейдите в модуль MainUnit и из меню File выберите пункт Use Unit. Перед вами откроется окно, как показано на рис. 9.10. В этом окне нужно выбрать модуль, который требуется подключить и нажать кнопку ОК. Что после этого изменится? Посмотрим на следующий фрагмент нашего модуля MainUnit:
var
Forml: TForml;
implementat ion
uses AboutUnit;
{$R *.dfm}
Как видите, здесь появилась новая строчка uses. Точно такая же есть и в начале модуля, но там мы подключали стандартные заголовочные файлы. Здесь же мы подключаем модули, которые написаны нами (не обязательно, но в основном будем делать именно так).
(Совет )
В принципе, можно подключить модуль Aboutunit и в самом начале, но это делать не желательно.
Строку uses можно написать и вручную, в указанном выше месте, и не выполнять никаких действий. Так что выбирайте, какой способ вам удобнее — прописывать вручную или делать это автоматически с помощью меню File I Use Unit.
Теперь перейдем к форме MainUnit. Мы подключили наш модуль Aboutunit и можем смело использовать его содержимое.
Обладатели Delphi 6 и более поздней версии находятся в более удобном положении. Если вы забыли подключить модуль и попытались откомпилировать код, то помимо ошибок вы увидите окно с сообщением, как это показано на рис. 9.11. Здесь написано, что вы из главного модуля ссылаетесь на форму AboutForm, которая объявлена в модуле Aboutunit. Вам также предлагается подключить этот модуль. Если вы нажмете Yes, то Delphi моментально сделает все действия для подключения самостоятельно.
Рис. 9,11, Окно сообщения при не найденном модуле
Вот теперь можно компилировать код еше раз, и программа будет собрана без ошибок. Запустите приложение и попробуйте выбрать пункт меню О программе. Если вы все сделали правильно, то увидите вторую созданную нами форму.
工 Примечание
На компакт-диске в папке \ Примеры \ Глава 9 \ Forms вы можете увидеть пример этой программы.
Модальные и немодальные окна
В предыдущем примере мы создали главное окно, которое вызывает дочернее в виде модального окна. Что значит модальное? Это значит, что управление полностью передается ему. Как только программа встречает код AboutForm. ShowModal, работа главной формы останавливается и управление полностью передается дочерней форме. Пока модальное окно не закроется, главная форма работать не будет.
Рассмотрим простой пример.
procedure TForml.AboutClick(Sender: TObject);
var
Index:Integer;
begin
AboutForm.ShowModal;
Index:=10;
end;
В этом примере показывается модальное окно, и после этого переменной index присваивается значение 10. Так вот, переменная index получит значение 10, но только после того, как модальное окно AboutForm закроется.
Для того чтобы создать немодальное окно, нужно вызвать метод show. В этом случае главная форма создаст дочернее, показав его на экране, и смело продолжит выполняться дальше. Это позволит вам работать с обеими формами одновременно, переключаться между ними, и код обеих форм будет выполняться как бы параллельно. Это еще не многозадачность и если одно окно выполняет какие-то действия, то второе ожидает их завершения, поэтому здесь нет истинной параллельности.
Теперь рассмотрим пример.
procedure TForml.AboutClick(Sender: TObject);
var
Index:Integer;
begin
AboutForm. Show;
Index:=10;
end;
В этом случае создается немодальное окно, и выполнение кода не останавливается на точке AboutForm. Show в ожидании закрытия окна, а спокойно продолжается дальше. То есть будет показано дочернее окно и моментально переменной index будет присвоено значение 10.
Давайте создадим еще одну форму, как мы это уже делали при создании окна AboutForm. Сразу переименуем ее свойство Name в NonModalForm. Можете что-нибудь написать на ней, например, установим одну кнопку, с помощью которой можно будет закрыть это окно.
Сохраните новую форму под именем NonModa lUni t. pa s.
Теперь вернемся в. главную форму и допишем в раздел uses имя модуля NonModalUnit. После этого наша строка uses должна выглядеть так: uses AboutUnit, NonModalUnit;
Если не хотите подключать эту форму вручную, то выберите из меню File пункт Use Unit и выберите в появившемся окне имя модуля для подключения.
Все. Модуль подключен. Теперь можно его использовать. Создадим обработчик события для пункта меню Сохранить и напишем в нем следующее:
procedure TForml.SaveClick(Sender: TObject);
begin
NonModa iForm. Show;
end;
Здесь мы отображаем форму NonModaiForm как немодальное окно. Это значит, что если вы запустите программу и выберете из меню пункт Сохранить,
то увидите окно новой формы и сможете спокойно переключаться между главной формой и NonModa 1 Form без каких-либо проблем.
ПримечаниеПримечание
Примечание
Примечание
На компакт-диске в папке \ Примеры \ Глава 9 \ Forms 1 вы можете увидеть пример этой программы..、
В последствии мы еще очень много будем использовать оба типа окон и вы познакомитесь с их работой более подробно.
Обмен данными между формами
Зачем нужны все эти формы, если нельзя передавать параметры между ними и взаимно использовать процедуры? Когда мы вызываем какое-то диалого- ове окно, мы хотим, чтобы пользователь ввел в него определенные данные. После этого мы должны получить введенные данные в главном окне и как- то их обработать. Помимо этого, в диалоговом окне очень часто располагаются кнопки Да и Нет. Мы просто обязаны знать, какую кнопку нажал пользователь и в зависимости от этого обрабатывать ввод или отметить определенные действия.
В этом разделе главы мы закрепим все то, о чем говорилось в предыдущих разделах про формы и научимся с ними работать. Пока известно только то, как их создавать и выводить на экран, а работа с ними осталась за кадром. Пора это исправить.
В предыдущем разделе мы создали немодальное окно для пункта меню Сохранить. Немного изменим вид окна, добавив на него строку ввода и две кнопки: Закрыть и Отмена (рис. 9.12).
Рис. 9.12. Измененное немодальное окно
Теперь посмотрим на очень интересное свойство кнопок — ModalResult. В этом свойстве можно задавать значение, возвращаемое при закрытии окна. Давайте выберем здесь mrok. Если теперь мы покажем окно как модальное и потом закроем его кнопкой Закрыть, то функция showModal вернет нам значение mrok.
Реально значения mrOk и другие, которые вы можете увидеть в выпадающем списке свойства ModalResult, — это числа, но для того, чтобы с ними удобно было работать, этим числам были поставлены в соответствие имена (константы). Первые две буквы mr это сокращение от слова ModaiResult. Остальное — это символьное представление результата. Получается, что, глядя на эту константу, мы можем сразу сказать, что она отображает нажатие кнопки ОК модального окна.
Мы еще добавили на форму кнопку Отмена. Для нее свойство ModaiResult установим в mrCancel. Кстати, теперь вы должны очистить обработчики событий onclick для кнопок. Когда вы указали в свойстве ModaiResult возвращаемое значение, кнопка уже автоматически умеет закрывать окно и не нужно создавать для нее обработчик onclick для того, чтобы написать в нем метод close. Это событие можно обрабатывать для других целей, и даже можно написать этот метод, но это уже лишнее. Зачем делать то, что работает автоматически.
В связи с этим предлагаю изменить обработчик события onclick для пункта меню Сохранить:
procedure TForml.Savedick(Sender: TObject);
begin
if NonModa 1 Form. ShowModalmrOK then
Application.MessageBox(PChar(NonModalForm. Editl.Text),
Ты ввел: \ MB_OKCANCEL) end;
Теперь построчно рассмотрим код. В первой строке вызывается модальное окно и ①азу цроверяется возвращаемое значение. Если оно равно mrOK, то выполняем следующее действие.
Вторая строка показывает стандартное диалоговое окно. Эго делается с помощью метода MessageBox объекта Application. У метода три параметра:
строка, которая будет показана внутри окна;
строка заголовка окна;
П кнопки, которые будут в окне:
мв ок — кнопка ОК;
MB OKCANCEL — кнопки ОК и Отмена;
MB RETRYCANCEL — КНОПКИ ПОВТОРИТЬ И Отмена;
MB YESNO — кнопки Да и Нет;
mb_yesnocancel — кнопки Да, Нет и Отмена-
В качестве текста сообщения в окне выводится текст, введенный в строку ВВОДа Нашего модального ОКНа (NonModa 1 Form. Editl. Text).
Теперь если пользователь нажмет в модальном окне кнопку Закрыть, то появится окно с введенным текстом. Иначе ничего не произойдет.
Вот и все. В этом примере мы смогли показать окно, получить результат его выполнения и вывели введенный в дочернее окно текст.
工Пр 吧 ечанме ;
На компакт-диске в папке \ Примеры \ Глава 9 \ Forms2 вы можете увидеть пример этой программы.
Многодокументные MDI-окна
Что такое многодокументные MDI-окна? Эго когда главное окно содержит внутри себя несколько подчиненных окон. Дочерние окна чем-то похожи на немодальные. Они также не блокируют главное окно и, работают независимо, только их область видимости ограничивается главным окном. Они находятся как бы внутри главного окна.
Примечание
Хотя Microsoft уже не рекомендует использовать эту технологию, и она убрана ее из MS Word, сама, тем не менее продолжает ее использовать. В Windows 2000 ярким прим叩ом MDI-окон является консоль ММС (рис. 9.13).
Рис. 9.13. Многодокументная консоль ММС
Давайте создадим простейшую МDI-программу. Для этого создайте новое приложение. Сохраните главное окно под именем Ma inModule, а проект под именем mdi. Теперь измените свойство FormStyle у формы на fsMD工Form.
Создайте еше одно окно (дадим ему имя ChildForm) и изменим у него свойство Formstyle на fsMDIChild.
Вот и все, MDI-профамма уже готова. Можете запустить и посмотреть, как она работает.
U П|^мечание \
На компакт-диске в папке \ Примеры \ Глава 9 \ MDI вы можете увидеть пример этой программы.
В нашем случае программа запускается и сразу создается дочернее окно. Как убрать его и создавать в режиме runtime (во время выполнения программы)? Очень просто — выберите меню Options | Project и вы увидите окно, как показано на рис. 9.14.
Рис. 9.14. Окно настроек проекта
В левой части окна перечислены те формы, которые будут создаваться автоматически (Auto-create forms). Вьщелите chiidForm (наше дочернее окно) и переместите его в список Available forms, нажав кнопку между списками в виде одиночной стрелки, направленной вправо. Теперь дочерняя форма не будет создаваться автоматически. Это действие придется делать вручную.
Установите на главную форму панель и растяните ее по верхнему краю окна (свойство Align надо установить в alTop). Теперь на панель установим кнопку и дадим ей заголовок Создать (рис. 9.15).
Рис. 9.15. Вид главного окна нашего многодокументного приложения
По нажатии этой кнопки мы будем вручную создавать окно. Для этого напишем следующий код:
procedure TMainForm.CreateButtonClick(Sender: TObject);
begin
ChildForm:= TChildForm.Create (Owner);
end;
Здесь мы присваиваем переменной ChildForm указатель на новое созданное окно TChildForm. create. Переменная childForm объявлена в модуле дочернего окна в разделе var:
var
ChildForm: TChildForm;
Запустите программу и попробуйте несколько раз нажать на кнопку Создать.
У вас должно создаться сразу несколько дочерних окон, как на рис. 9.16.
Но если вы попытаетесь закрыть любое из них, оно просто свернется. Чтобы окно закрывалось, нужно создать обработчик события OnClose для дочерней формы и в нем написать:
procedure TChildForm. FormClose (Sender: TObject; var
Action: TCloseAction);
begin
Action:=caFree; end;
Рис. 9.16. Работающее многодокументное приложение
Здесь переменной Action (действие) присваивается значение caFree. Эта переменная передается обработчику события в качестве второго параметра. Мы изменяем действие, которое выполняется по умолчанию для клиентских многодокументных окон на caFree, что заставляет форму уничтожиться. Как вы могли убедиться, по умолчанию окно сворачивается.
Вот теперь наше приложение готово.
工П吧меча吗е]
На компакт-диске в папке \ Примеры \ Глава 9 \ MDI1 вы можете увидеть пример этой программы.
Как видите, для разных типов приложений используется один и тот же тип окна TForm, что очень удобно. Но вы должны помнить, что они работают немного по-разному, и учитывать это при написании реальных приложений.
Напоследок рассмотрим несколько полезных свойств и методов формы, примеры которых уже реализованы в приведенных выше программах.
口 ActiveMDlChild — указывает на активное дочернее окно.
О MDlChildCount — целое число, указывающее на количество дочерних окон.
О MDlChildren — через это свойство можно получить доступ к любому дочернему окну. Например, второе окно можно получить с помощью MDlChildren[2].
Например, вам надо изменить заголовок активной дочерней формы. Как узнать, какая из них активная, когда их несколько? Очень просто. Создайте в главной форме кнопку и по ее нажатии напишите:
procedure TMainForm.ButtonlClick(Sender: TObject);
begin
ActiveMDlChild.Caption: = *Активное дочеренее окно*;
end;
В этом коде мы изменяем свойство caption активной формы. Если нет активной дочерней формы (бывает, когда дочерних форм вообще нет), то СВОЙСТВО ActiveMDlChild равно nil.
Давайте попробуем изменить заголовки всех дочерних окон. Для этого запустим цикл от 0 до МDiehiidCount и изменим все заголовки:
for i:=0 to MDIChi IdCount1 do
MDlChildren[i].Caption: = * Новый заголовок*;
Есть еще несколько интересных методов.
О Arrange Icons — выстроить иконки всех дочерних окон.
口 cascade — выстроить каскадом все дочерние окна.
口 Next — следующее дочернее окно.
口 previous — предыдущее дочернее окно
口 Tile — тоже выстроить дочерние окна.
Инициализация окон
Вот теперь мы написали уже достаточно много примеров и готовы узнать, как инициализируются окна. В этом разделе мы рассмотрим: из чего состоит ''сердце'' нашей программы, где инициализируются окна и как управлять этим процессом. До этого момента эти вопросы не рассматривались, чтобы не забивать вам голову, но теперь это необходимо для продолжения разговора о программировании на Delphi.
Создайте новый проект. Сохраните модуль главной формы под именем MainUnit .pas, а проект под именем SplashProject.dpr. Теперь выберите из меню Project пункт View Source (Проект | Просмотр исходника), чтобы увидеть исходный код проекта. Вы должны увидеть следующий код:
'Листинг 9.1 Исходный код проекта
program SplashProj ect;
uses
Forms,
MainUnit in rMainUnit.pas1 {Forml};
{$R *.res}
I
begin
Application.Initialize;
Application. Create Form (TForml z Forml);
Application.Run;
end.
Все это ничто иное, как содержимое файла SpiashProject.dpr. Первой строкой стоит имя программы program SplashProject. В этой строке ничего менять нельзя, потому что имя файла должно совпадать с написанным здесь именем программы. После этого идет уже знакомый раздел uses, в котором можно подключать необходимые модули. У нас подключены модули Forms (позволяет работать с формами) и MainUnit (модуль главного окна). Если в вашей программе несколько окон, то все они автоматически прописываются здесь в раздел uses, потому что в этом файле описана инициализация окон, и он должен знать о существовании всех окон в программе. Если как^р-то окно инициализируется не автоматически, то только в этом случае вы можете убрать модуль этого окна из подключения uses, иначе при компиляции произойдет ошибка.
Между begin и end выполняются три строчки:
Application. Initialize — запускает инициализацию приложения. Убирать не рекомендуется.
. Application.CreateForm (TForml, Forml) — метод CreateForm инициализирует форму. У него два параметра — имя объекта и имя переменной, которая в последствии будет указывать на созданный объект. В нашем случае это имя формы TForml и имя переменной Forml. Переменная
Forml автоматически описывается в модуле объекта TForml (в нашем случае это модуль MainUnit .pas) в разделе var:
var
Forml: TFo rml;
Application. Run— после инициализации всех форм можно запускать выполнение программы с помощью метода Run объекта Application.
Здесь везде используется объект Application. Этот объект всегда существует в ваших программах в единственном экземпляре и создается с помощью строки Application: initialize. С этим объектом мы будем знакомиться постепенно на протяжении всей книги, а сейчас достаточно этих знаний.
Теперь создайте новую форму, выбрав меню File | New | Form, и сохраните ее под именем SplashUnit. pas. Снова посмотрите на исходный код проекта, он должен быть таким, как в листинге 9.2.
I Листинг 9 2 Обнсменный исходный код проекта program SplashProj ect;
uses
Forms,
MainUnit in 'MainUnit.pas * {Forml}r SplashUnit in *SplashUnit.pas1 {Form2};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TForml, Forml); /\pplication. CreateForm (TForm2, Form2); Application.Run;
end.
В раздел uses добавилось объявление нового модуля, а между begin и end появился код создания новой формы.
Теперь войдите в свойства проекта (из меню Project нужно выбрать пункт Option). На вютадке Forms в списке Auto-create forms у нас описаны две формы. Вьщелите Form.2 (эта вторая форма, которую мы только что создали) и переместите ее в список Available forms. Закройте окно свойств кнопкой ОК и посмотрите на исходный код проекта. Вы можете заметить, что строка
инициализации второй формы исчезла. Это потому, что мы перенесли ее из списка автоматически создаваемых форм в список доступных форм. То есть нйша форма доступна в проекте, но не создаст автоматически при старте программы. Таким образом, чтобы использовать Form2, мы ее должны сначала создать.
Чтобы дальше можно было удобнее работать, переименуйте главную форму Forml В Main Form, а вторую форму Form2 В SplashForm. Так МЫ не будем путаться, где главная форма, а где форма^заставка.'
форму кнопку, и по ее нажатии напишите следующий код:
procedure TMainForm.ButtonlClick(Sender: TObject);
begin
Application. CreateForm (TSplashForm, SplashForm);
SplashForm.ShowModal;
SplashForm. Free;
end;
Здесь в первой строке кода мы инициализируем форму SplashForm. Во второй созданное окно выводится на экран. И в последней строке происходит уничтожение окна методом Free.
Но есть еще один способ создания окон, который мы уже использовали, и он предпочтительнее. Напишите следующий код:
procedure TMainForm.ButtonlClick(Sender: TObject);
begin
SplashForm: =TSplashForm. Create (Owner);
SplashForm.ShowModal;
SplashForm.Free;
end;
Здесь переменной SplashForm присваивается результат вызова метода create объекта TSplashForm. Этому методу нужно передать только один параметр —владельца окна. Если владельца нет, то можно передавать nil (нулевое значение указывает на отсутствие владельца). В нашем случае передается owner — свойство, в котором хранится указатель на текущее окно. Если главным окном должно быть не текущее окно, то нужно указать имя объекта — Forml.Owner.
Давайте сделаем так, чтобы наше окно SplashForm появлялось на время загрузки программы. Подобные окна вы видите при старте таких программ как Delphj, Word, Excel и других приложений. Для этого зайдите в исходный код проекта и подкорректируйте так, чтобы он соответствовал коду, приведенному в листинге 9.3.
8 Зак 942
:Листинг 9.3. Исходный код проекта с окном загрузки
begin
SplashForm: =TSplashForm. Create (nil);
SplashForm. Show;
SplashForm.Repaint;
Application.Initialize;
Application. CreateForm (TMainFonn, MainForm);
Sleep(1000);
SplashForm.Hide;
SplashForm.Free;
/^plication.Run;
end;
Рассмотрим этот код построчно.
Создается окно SplashForm. V этого окна не будет владельца, потому что оно показывается даже до того, как создано главное окно. Поэтому в качестве параметра методу Create мы указываем значение nil.
Отображаем окно на экране не модально, чтобы окно отобразилось, а приложение продолжало работать.
Перерисовка окна с помощью вызова метода Repaint.
Инипиализапия приложения.
Создается главная форма ТМаinForm.
Делаем задержку, чтобы окно SplashForm могло хоть немного “зависнуть” на экране. Для этого мы используем процедуру sleep, а в качестве параметра указывается время задержки в миллисекунд ах. Одна секунда равна 1000 миллисекунд. Для использования этой функции в раздел uses НУЖНО ДОбавИТЬ МОДУЛЬ Windows.
Прячем форму SplashForm ВЫЗОВОМ метода Hide.
Уничтожаем окно.
Запускаем приложение.
Запустите программу, и вы сначала увидите окно SplashForm (на него помешен текст TLabel с надписью "Идет загрузка1), а потом уже появится
главное окно.
ОсрветУ
Когда создаются окна (вызывается CreateForm), программа выполняет обработчики события OnCreate всех создаваемых форм. Если у вас приложение слишком большое и операции в этих обработчиках выполняются длительное время, то желательно показывать информацию о ходе выполнения этих операций в окне SplashForm. В этом случае первым делом создается именно это окно и оно отображается на экране. Пользователь видит, что идет загрузка и спокойно ждет ее окончания.
Если вы не будете информировать о ходе загрузки и на экране ничего появляться не будет, пользователь может подумать, что программа зависла, и попытаться запустить ее еще раз. Эго может иногда привести к краху всего вычислительного процесса: например, попытка запустить дважды программы, которые работают с COM-портом; в этом случае вторая копия уже не может открыть порт (COM-порт можно открыть только один раз). Поэтому лучше лишний раз создать окно с отображением хода загрузки, чем выслушивать недовольство пользователей по поводу работы с вашей программой.
工 Примечание 】
На компакт-диске в папке \ Примеры \ Глава 9 \ Splash вы можете увидеть пример этой программы.
Глава 10
Основные приемы программирования
Мы уже изучили достаточно теории и готовы приступить к реальным примерам программирования. В этой главе мы познакомимся с некоторыми приемами программирования утилит и относительно простых программ.
Здесь вам предстоит узнать о работе с системным реестром, файлами и потоками. Все это просто необходимо любому программисту. В главе приводится достаточно много примеров, максимально приближенных к реальным программам.
В этом смысле глава будет более приближена к практике, и теории здесь будет намного меньше. В дальнейшем чем дальше мы будем продвигаться по пути изучения Delphi, тем меньше будет теории и больше практики.
Большинство материала главы посвящено изучению методов записи и чтения данных с использованием разных источников информации (например, из файлов или реестра). Остальной материал понадобится при работе с источниками информации.
Работа с массивами (динамические массивы)
Работа с массивами — одна из наиболее важных и необходимых тем в пр огр аммир овании. Мы будем достаточно часто использовать массивы, поэтому этот раздел необходимо прочитать полностью и понять все, о чем здесь говорится.
По ходу изложения материала очень часто упоминается, что глава очень важна. Это действительно так. Поэтому каждый ее раздел является необходимым. Если вы хотите создать простой и удобный для понимания программный код, придется изучать различные приемы и технологии программирования. В главе даются необходимые для их понимания основы.
Итак, приступим к изучению массивов. Что такое массив? Это просто набор каких-то данных, следующих друг за другом. Массив в Delphi обозначается как array. Чтобы объявить переменную типа “массив”,нужно описать ее в разделе var следующим образом: var
г: array [длина массива] of тип данных;
Как определяется длина массива? Очень просто, это даже похоже на геометрическое определение. Например, пусть нужен массив из 12 значений. Длина такого массива может быть [0.. 11] или [1..12]. В квадратных скобках вы должны поставить начальное и конечное значение массива, а между ними две точки.
Тип данных может быть любой. Например, надо объявить массив из 12 строк; это можно сделать следующим образом: var
г: array [0. .11] of String;
В этом примере мы объявили переменную г типа “массив” из 12 строк.
Чтобы получить доступ к какому-то элементу, нужно написать имя переменной массива и после этого в квадратных скобках написать номер элемента, к которому нужно получить доступ. Например, давайте прочитаем 5-й элемент и запишем 7-й элемент нашего массива (листинг 10.1).
Листинг 10.1. Работа с элементами массива var
г: array [0. .11] of String;
Str:String;
begin
Str:=r[5];
r [7]: =1Привет1;
end;
Неправда ли, все это очень похоже на то, как мы работали с символами в строках. Строки это те же массивы, только отдельных символов.
В этом примере в первой строке кода переменной str присваивается значение пятого элемента массива Str:=r [5]. В следующей строке седьмому элементу присваивается строка "Привет" — г [7] : =*Привет*.
Давайте напишем небольшой пример для работы с массивами. Допустим, требуется узнать какой сегодня день недели. Создайте новое приложение.
Установите на форму один компонент TEdit (дадим ему имя DayOfWeekEdit) и одну кнопку (дадим ей имя GetDayButton и напишем в заголовке "Узнать день недели*). В результате получилась форма, как на рис. 10.1.
Рис. 10.1. Форма будущей программы
По нажатии кнопки мы будем узнавать, какой сегодня день недели, и записывать результат в строку TEdit. Напишем для кнопки код, показанный в листинге 10.2.
Листинг 10.2. Код определения дня недели
procedure TForml.GetDayButtonClick(Sender: TObject);
var
day:Integer;
week: array[1..7] of string; begin
week[1]:二 *Воскресенье *;
week [2]:二 * Понедельник1;
week[3]:二 1 Вторник*;
week[4] : = 1Среда';
week[5] := *Четверг';
week[6]:二 1 Пятница *;
week[7]:二 * Суббота1;
day:=DayOfWeek(Date);
DayOfWeekEdit.Text:=week[day];
end;
Здесь объявлен массив week из семи элементов. После этого мы последовательно всем элементам массива присваиваем названия дней недели.
Для определения дня недели существует функция DayOfWeek. Ей нужно передать только один параметр — дату, день недели которой нужно узнать.
Мы передаем результат работы функции Date, которая возвращает текущую дату. Получается, что DayOfWeek вернет нам день недели текущей даты.
Вроде все нормально, Dayofweek возвращает строку, в которой написано словами какой сегодня день. Если функция возвращает 0, то это воскресенье, если 1 — понедельник, 2 — вторник, 3 — среда и т. д. Как видите, отсчет идет с воскресенья (по европейски). Точно так же мы заполняли и массив: 1 — это воскресенье, 2 — понедельник и т. д.
После этого надо превратить число в строку. Это делается очень просто. Надо получить только соответствующий элемент массива и все. Если функция вернула нам 2, то это должен быть понедельник. В массиве под вторым номером идет "Понедельник", поэтому нам просто нужно получить строку, находящуюся под вторым номером в массиве. Вот именно это и происходит в последней строке week [day].
工 Примечание
На компакт-диске в папке \ Примеры \ Глава 10\ Arrays вы можете увидеть пример этой программы.
Здесь мы закрепили на примере уже пройденный ранее, при рассмотрении строк, материал. Давайте пойдем дальше и познакомимся с динамическими массивами. Это массивы, которые могут изменять свою длину, поэтому при объявлении не надо ее указывать. Вы просто указываете переменную и ее тип:
г: array of integer;
В этом примере объявлена переменная г типа массива целых чисел без указания размера. Чтобы указать размер массива, можно воспользоваться функцией SetLength. У нее два параметра:
П переменная типа динамического массива;
О длина массива.
Давайте посмотрим все это на практике. Создайте новый проект в Delphi и установите на форму две кнопки и один компонент TListBox, как показано на рис. 10.2.
Для первой кнопки мы напишем код, указанный в листинге 10.3.
Листинг 10.3. Первый способ работы с массивом
var
г:array of integer; i:Integer;
begin
ListBoxl.Items.Clear;
SetLength(r,10);
for i:=0 to High(r)-1 do
begin
r[i]:=i*i;
ListBoxl. I terns, Add (IntToStr (i) + ' в квадрате =,+IntToStr(r[i]));
end;
end;
Рис. 10.2. Форма буцущей программы
В области объявлений var объявлены две переменные. Первая это г, которая является массивом чисел типа integer. ВтораЯ переменная — i, которую мы будем использовать в качестве счетчика.
Переходим к самой процедуре. Первая строка очищает все строки у ListBoxl. Для этого вызывается метод ListBoxl. Items.Clear. Мы это уже проходили, но вспомним как это работает. У ListBoxl есть свойство items, где хранятся все строки. У items есть метод clear, который удаляет все находящиеся в нем строки.
Во второй строке вызывается процедура setLeng.th, которая выделяет память для массива г (первый параметр) размером в 10 элементов (второй па-
раметр). Обращение к элементам будет происходить уже знакомым образом, как г [ номер_элемента ]. Элементы будут нумероваться от 0 до 9, потому что мы выделили 10 элементов.
Далее вдет пикл. Функция High (г) возвращает количество элементов в массиве г. В итоге получается, что цикл будет выполняться от i :=0 (от нуля), до количества элементов в массиве г минус 1 (т. е. до 9). Внутри массива выполняются две строки кода.
г[1]:=i*i .//Здесь i элементу массива ирисваивается i*i.
Следующая строка добавляет новый элемент в ListBoxl. Функция intToStr переводит число в строку.
ListBoxl.Items.Add(IntToStr(i)+ * в квадрате =f+IntToStr(r[i]));
С первой процедурой мы разобрались, теперь перейдем ко второй кнопке, по нажатии которой нужно написать содержимое листинга 10.4.
Листинг 10.4, Второй способ работы с массивом
type
TDynArr=array of integer;
var
r:TDynArr;
i:Integer;
begin
ListBoxl. Items. Clear;
SetLength(r, 10);
for i:=0 to High(r)-1 do r[i]:=i*i;
SetLength(r,20);
for i:=10 to High(r)—1 do r[i]:=i*i;
for i: =0 to High(r) do
ListBoxl. I terns. Add (IntToStr (i) +* в квадрате =*+IntToStr (r [i ])); end;
Эта процедура выполняет похожие действия, но с небольшими особенностями. В начале мы объявляем новый тип: TDynArr=array of integer. После этого конструкция г : TDynArr будет означать, что г относится к типу
TDynArr, а тот относится к array of integer. Это то же самое, что мы писали в первой пропедуре г: array of integer, только такая конструкция удобней, если требуется объявить несколько динамических массивов. Вам не приходится сто раз писать громоздкую строку г: array of integer, вы объявляете новый массив KaKTDynArr.
Далее идет все та же очистка строк и выделение памяти под массив.
for i:=0 to High (г) -1 do
r[i]:=i*i;
Эта конструкция заполняет десять элементов квадратами числа i. После этого мы снова вызываем функцию SetLength(г, 20), в которой говорим, что массив теперь будет состоять из 20 элементов. Таким способом можно как увеличивать количество элементов, так и уменьшать его.
for i:=10 to High (г) -1 do
r[i]:=i*i;
Здесь мы заполняем квадратами числа i элементы, начиная с 10 и по последний. В конце мы заполняем ListBoxl значениями элементов массива.
工 Пр-мечанм.】
На компакт-диске в папке \ Примеры \ Глава 10 \ Dy п Ar rays вы можете увидеть пример этой программы.
Многомерные массивы
Мы уже разобрались с массивами, но это, только одномерные массивы, в которых данные располагаются в виде строки. Для Delphi это не предел, и он может работать и с несколькими измерениями массива.
Например, допустим, что вам надо держать таблицу из данных. Таблица будет состоять из пяти колонок и четырех строк. В этом случае вы можете завести, например, четыре массива, в каждом из которых будут храниться по 5 элементов. Но это же неудобно. Вот тут на помошь приходят многомерные массивы.
Объявляются такие массивы так же как и одномерные, разница только в том, что когда в квадратных скобках указывается длина массива, нужно указывать размеры строк и столбцов данных.
Рассмотрим пример объявления двухмерного массива из четырех строк и пяти столбцов:
var
t:array[0..3, 0..4] of integer;
Как видите, в квадратных скобках перечислены через запятую размеры строк и столбцов. Заметьте, что мы объявили массив от 0 до 3 — это будут четыре элемента и от 0 до 4, что будет составлять 5 элементов.
Работа с таким массивом также достаточно простая (см. листинг 10.5).
^g^jg^^^^^^j^^jgj^j^gj^^^^^^^^^j^^j^g^gpg^^^^^^^^^^^^j^ 11t^^ # J г _ ^ff^^
Листинг 10.5. Работа с многомерным массивом
t : array [0 . . 3, 0 . . 4] of integer;
begin
t[0] [0]:=l;
t[l] [0]:=2;
t[2] [0]:=3;
t[3] [0]:=4;
t[I](l]:-5;
end;
После выполнения этого примера таблица будет примет вид:
15000
20000
30000
40000
Двухмерность не предел и вы можете создавать и трехмерные массивы. Давайте просто посмотрим на содержимое листинга 10.6, и вам все должно стать понятно.
Листинг 10.6. Трехмерный массив
var
t:array[0..3, 0..4, 0..2] of integer; begin
t[0][0][0]:=1;
t[l] [0] [0]:=2;
t[2][0][0]:=3;
t[3][0][0]:=4;
t[l] [1] [0] :=5;
end;
Работа с файлами
В этом разделе рассматриваются основные приемы работы с файлами. Здесь мы научимся открывать и закрывать, записывать и читать данные. Конкретного примера пока не будет, потому что для полезного примера у нас пока не хватает знаний. Чуть позже будут показаны интересные программы, но сейчас нам нужно рассмотреть немного теории.
Для работы с файлами многие предпочитают использовать WinAPI. Не пугайтесь этого слова, потому что работа с WihAPI в Delphi очень прозрачна, и у вас не возникнет никаких проблем. В самых первых версиях Windows для чтения из файла использовалась функция Iread. Потом появилась' ReadFile. А сейчас рекомендуют использовать ReadFileEx, которая может работать с файлами большего размера. После каждого изменения функций Win APT приходится переделывать весь код программ, потому что нет гарантии, что старые функции будут корректно работать в новых версиях Windows.
Вот поэтому в Delphi рекомендуется использовать специализированный объект TFileStream.
(Совет )
Объект TFileStream рекомедуется использовать по следующей причине. Если Microsoft снова введет какие-то новшества, Borland учтет их в объекте, и вам нужно будет только перекомпилировать свои программы. Никаких изменений в код вносить не надо. Вы один раз изменяете объект (или это делает Borland) и компилируете все свои программы с новыми возможностям и. Это громадное преимущество ООП, и вы должны его использовать, потому что оно упрощает жизнь.
В любом случае, использование этого объекта намного проще, чем WinAPI, поэтому желательно начинать изучение работы с файлами именно с него.
Итак, давайте взглянем на объект TFileStream. -Первое, что надо сделать, это объявить какую-нибудь переменную типа TFileStream:
var
f: TFileStreciiii;
Вот так мы объявили переменную f типа объекта TFileStream. Теперь можно проинициализировать переменную.
Инициализация — выделение памяти и установка значений по умолчанию.
За эти действия отвечает метод Create. Нужно просто вызвать его и результат выполнения присвоить переменной. Например, в нашем случае нужно вызвать TFileStream. Create и результат записать в переменную f.
f := TFileStream.Create(параметры);
Давайте разберемся, какие параметры могут быть при инициализации объекта TFileStream. У метода create может быть три параметра, причем последний можно не указывать:
О Имя файла (или полный путь к файлу), который надо открыть. Этот параметр — простая строка.
□ Режим открытия. Здесь вы можете указать один из следующих параметров открытия файла.
fmC reate — создать файл с указанным в первом параметре именем. Если файл уже существует, то он откроется в режиме для записи.
fmOpenRead — открыть файл только для чтения. Если файл не существует, то произойдет ошибка. Запись в файл в этом случае невозможна.
fmOpenWrite — открыть файл для записи. При этом во время записи текущее содержимое уничтожается.
fmOpenReadWrite — открыть файл для редактирования (чтения и записи).
口 Права, с которыми будет открыт файл. Здесь можно указать одно из следующих значений (а можно вообще ничего не указывать).
fmS hareCompat — при этих правах другие приложения тоже имеют права работать с открытым файлом.
fmShareExclusive — другие приложения не смогут открыть файл.
fmS ha re Denyw r i t e — при данном режиме другие приложения не смогут открывать этот файл для записи. Файл может быть открыт только для чтения.
fmShareDenyRead — при данном режиме другие приложения не смогут открывать этот файл для чтения. Файл может быть открыт только для записи.
fmShareDenyNone — не мешать другим приложениям работать с файлом.
С первыми двумя параметрами все ясно. Но зачем же нужны права доступа к файлам. Допустим, что вы открыли файл для записи. Потом его открыло другое приложение и тоже для записи. Обе программы записали какие-то данные. После этого ваше приложение закрыло файл и сохранило все изменения. Тут же другое приложение перезаписывает ваши изменения, даже не подозревая о том, что они уже прошли. Вот так ваша информация пропадает из файла, потому что другая программа просто перезаписала ее.
Если вы пишете однопользовательскую программу, и к файлу будет иметь доступ только одно приложение, то про права можно забыть и не указывать их.
После того как вы поработали с файлом, достаточно вызвать метод Free, чтобы закрыть его.
f.Free;
Теперь давайте познакомимся с методами чтения, записи и внутренней структурой файла. Начнем со структуры. Когда вы открыли файл, позиция курсора устанавливается в самое начало и любая попытка чтения или записи будет происходить в эту позицию курсора. Если вам надо прочитать или записать в любую другую позицию, то надо передвинуть курсор. Для этого есть метод seek. У него есть два параметра.
□ Число, указывающее на позицию, в которую надо перейти. Если вам нужно передвинуться на 5 байт, то просто поставьте цифру 5.
П Откуда надо двигаться. Тут возможны три варианта:
S оFromBeginning — двигаться на указанные количество байт от начала файла.
soFromCurrent ——двигаться на указанные количество байт от текущей позиции в файле к концу файла.
soFromEnd — двигаться от конца файла к началу на указанное количество байт.
Не забывайте, что один байт — это один символ. Единственное нсключе- ние — файлы в формате Unicode. В них один символ занимает 2 байта. Таким образом, надо учитывать, в каком виде хранится информация в файле.
Итак, если вам надо передвинуться на 10 символов от начала файла, можете написать следующий код:
f.Seek(10, soFromBeginning);
Метод seek всегда возвращает смещение курсора от начала файла. Этим можно воспользоваться, чтобы узнать, где мы сейчас находимся, а можно и узнать общий размер файла. Если переместиться в конец файла, то функция вернет количество байт от начала до конца, т. е. полный размер файла.
В следующем примере устанавливается позиция в файле на 0 байт от конца, т. е. в самый конеп. Тем самым получается полный размер файла:
Размер файла := Ё.Seek(0, soFromEnd);
Вот таким небольшим трюком можно узнать размер. Правда для этого придется использовать три операции: открыть, переместиться в конец файла и потом закрыть его.
Для чтения из файла нужно использовать метод Read. И CHQBa у этого метода два параметра:
□ переменная, в которую будет записан результат чтения;
П голичество байт, которые надо прочитать.
Давайте взглянем на код чтения из файла, начиная с 15-й позипии. Этот код вы можете увидеть в листинге 10.7.
Листинг 10.7. Код чтения из файла, начиная с 15-й позиции
var
f:TFileStream; //Переменная типа объект TFileStream.
buf: array[0. . 10] of char; // Буфер для ^фанения прочитанных данных begin
// В следующей строчке я открываю файл filename.txt для чтения и записи. f := TFileStream. Create (rc: \filename. txt', fmOpenReadWrite);
f.Seek(15, soFromCurrent) ; // Перемещаюсь на 15 символов вперед.
f.Read(buf, 10); // Читаю 10 символов из установленной позиции.
f. Free; // Уничтожаю объект и соответствеъ.яо закрываю файл. end;
Обратите внимание, что в методе Seek движение происходит на 15 символов не от начала, а от текущей позиции, хотя нужно от начала. Это потому, что после открытия файла текущая позиция и есть начало. Но лучше на это не р ас считывать, потому что в следующей реализации объекта или в ОС Windows что-то может измениться, и движение будет происходить от начала. Метод Read возвращает количество реально прочитанных байт (символов). Если не произошло никаких проблем, то это число должно быть равно количеству запрошенных для чтения байт. Есть только два случая, когда эти числа отличаются:
口 при чтении был достигнут конец файла и дальнейшее чтение стало невозможным;
О ошибка на диске или любая другая проблема.
Осталось только разобраться с записью. Для этого мы будем использовать метод write. У него также два параметра:
口 переменная, содержимое которой нужно записать;
О число байт для записи.
Пользоваться этим методом можно точно так же как и методом для чтения.
「рнмечанме]
После чтения или записи текущая позиция в файле смещается на количество прочитанных байт. То есть текущая позиция становится в конец прочитанного блока данных. 匸
Пример пока рассматривать не будем, потому что в принципе и так все яс- HQ. А если есть вопросы, то на практике мы закрепим этот материал буквально через один или два раздела, коща будем работать со структурами.
Работа с текстовыми файлами
В предыдущем разделе был рассмотрен общий случай работы с файлами. Сейчас мы познакомимся с их разновидностью — текстовым файлом. В этих файлах информация расположена не сплошным одинарным блоком, а в виде строк текста. Было бы удобно воспринимать такие файлы в виде наборов строк, а не побайтно.
Если попытаться прочитать текстовый файл методами, описанными в предыдущем разделе, то работать с текстом будет неудобно. Допустим, что у нас есть файл из двух строчек:
Привет!!!
Как жизнь ?
Если прочитать его с помощью объекта TFileStream, то мы увидим весь текст в одну строку:
Привет ! ! ! <CRXLF>KaK жизнь?
Здесь <CR> — конец строки и <LF> — перевод каретки на новую строку. Таким образом, чтобы найти конец первой строки, мы должны сканировать весь текст с целью поиска признака конца строки и перевода каретки (<CR> и <LF>). Это очень неудобно. А если у вас файл имеет размер в 100 строк и вам нужно получить доступ к 75-й строке? Как вы думаете, долго мы будем искать нужную строку? Нет, точнее сказать, что не долго, а неудобно.
Туг на помощь приходит объект ТStrings, который является простым тн- тейнером (хранилишем) для строк. Можно еще пользоваться более продвинутым вариантом этого объекта TStringList. Если посмотреть на иерархию объекта TstringList (рис. 10.3), то мы увидим, что он происходит от TStrings. Это значит, что TstringList наследует себе все возможности объекта TStrings и добавляет в него новые.
Как всеща для работы с объектом надо объявлять переменную типа "обвект":
var •
f: TstringList; //Переменная типа объект TstringList.
Инициализируется эта переменная как всегда методом create. Никаких параметров не надо. Чтобы освободить память объекта и уничтожить его, как всеща, применяется метод Free. Простейший метод использования объекта показан в листинге 10.8.
Hierarchy
TObiect
I
工 Persistent
I
TStrmgs
I
TSEnqList
Рис. 10.3. Иерархияo6beKTaTStringList(снимок из файла помощи Delphi)
Листинг 10.8. Простои пример использования объекта TStrxngXiiSt
var
f:TStringList; //Переменная типа объект TStringList. begin
f:= TStringList.Create();
f.Free;
end;
В этом примере только создан новый объект и сразу произведено его уничтожение, без использования.
Давайте снова вспомним, что TStringList происходит от Tstrings. Использовать Tstrings напрямую нельзя, потому что это абстрактный объект. Абстрактный объект — объект, который представляет собой пустой шаблон. Он может даже ничего не уметь делать, а только описывать какой-то вид или шаблон, на основе которого можно выводить полноценные объекты. Вот так TStringList добавляет в Tstrings свои функции так, что он становится полноценным объектом.
В результате получается, что мы не можем объявлять переменные типа Tstrings и использовать этот объект, потому что это всего лишь шаблон. Это и так, и не так. Переменную мы можем объявлять, но использовать сам объект не можем. Зато мы можем объявить переменную типа Tstrings, но использовать эту переменную как объект TStringList, потому что данный объект происходит от первого. Это значит, что код в листинге 10.9 идентичен предыдущему.
Листинг 10 9. Работа с тзи . .口工-七 var
f:TStrings; //Переменная типа объект TStringList.
begin
f:= TStringList.Create();
f.Free;
end;
В этом примере мы объявили переменную типа TStrings, но при создании проинициализировали ее объектом TStringList. Это вполне законная запись, потому что объект TStringList происходит от TStrings. Новая переменная f будет работать, как объект TStringList, хотя и о&ьявлена как TStrings. Главное — каким объектом переменная проинициализирована.
Такие трюки можно проводить только с родственными объектами. Они не будут рассматриваться в этой книге, хотя знать их нужно, потому что вам может встретиться что-то подобное в чужом коде.
Итак, давайте узнаем, как можно работать с помощью TStringList, с целью обработки текстовых файлов. Все очень просто. У него есть метод LoadFromFile, для которого нужно указать имя текстового файла. После этого через свойство strings можно получить доступ к любой строчке, а в свойстве count находится число, указывающее на количество строк в файле. В листинге 10.10 показан пример загрузки файла и работа с первыми двумя строчками. Обратите внимание, что строки нумеруются с нуля.
I Листинг 10.10. Пример загрузки файла
var
f:TStrings; //Переменная типа объект TStringList. begin
f := TStringList.Create ();
f.LoadFromFile(*c:\filename.txt);// Загрузка текстового файла
f .Strings [0]; // Здесь находится первая строчка файла
f.Strings[1]; // Здесь находится вторая строчка файла
f.Free;
end;
Давайте напишем пример, который будет искать в файле строку **Привет Вася**. Программный код, реализующий этот пример, приведен в листинге 10.11.
Листинг 10.14. Поиск слова Привет Вася"
var
f:TStrings; // Переменная типа о&ьект TStringList.
begin
f := TStringList.Create();
f. LoadFromFile ("c: \filename.txt); // Загружаем текстовый файл
for i:=0 to f.Count-1 do // Запускаем цикл begin / / Начало для цикла
if f .Strings [I] 二 * Привет Вася * then //Если i-я строка равна нужной, то
Application.MessageBox('Строка найдена',
Поиск закончен * f MB_OKCANCEL)
end; // Конец для цикла
f.Free;
end;
Точно так же можно изменять данные в файле. Например, если вам надо изменить 5-ю строку на "Прощай станция Мир". Изменение строк показано в листинге 10.12.
Листинг 10.12 Изменение строк
var
f:TStrings; // Переменная типа объект TStringList.
В приведенном в листинге коде, прежде чем изменить 5-ю строку, происходит проверка, есть ли в файле эти пять строк. Если окажется меньше пяти, то при попытке изменения данных произойдет ошибка.
В этом же примере в конец файла добавляется новая строка с помошью вызова метода Add. После этого результат сохраняется в том же файле с помощью метода SaveToFile. Если не вызывать метод сохранения, то все изменения пропадут, потому что данные изменяются в объекте, а не в файле. Именно поэтому объект надо сохранять обратно в файл.
На этом можно закончить рассмотрение, но я хочу еще показать вам несколько методов.
口 clear — очистка содержимого объекта.
口 insert — вставить строку. У этого метода два параметра — индекс строки, куда нужно вставить, и сама строка.
П Delete — удалить строку. Здесь только один параметр — индекс удаляемой строки.
Вот теперь можно считать, что с текстовыми файлами и объектом TstringList покончено, и можно двигаться дальше.
Приведениетипов
В этом разделе главы мы как можно более подробно остановимся на теме приведения типов. В любой программе может понадобиться преобразование данных из одного типа в другой. Преобразование типов делится на два вида:
П преобразование несовместимых типов;
П преобразование совместимых типов.
В качестве несовместимых типов можно привести пример превращения строки в число. Допустим, что у вас есть строка ”12345". Это строка, которая может представлять число. Но вы не можете производить с такой строкой математических действий, потому что это все же строка, хотя и содержащее число. Для начала нужно преобразовать эту строку в численное значение.
Преобразование целых чисел в строку и обратно
Начнем с рассмотрения специальных функций для преобразования несовместимых типов. Самое частое, что может понадобиться при программировании, —преобразование строк в число и обратно. Допустим, нужно написать программу, в которой пользователь будет вводить число в компонент TEdit. Чтобы получить доступ к содержимому Editl, надо написать
Editl.Text. Так мы получим текстовое представление числа. Чтобы его преобразовать, необходимо воспользоваться специальной функцией.
Для преобразования строки в число используется функция StrTolnt. У нее только один параметр — строка, а на выходе она возвращает число.
var
ch:Integer;
begin
ch: =StrToInt(Edit 1 .Text);《/ Преобразовываю Editl.Text в число end;
В этом примере мы присвоили переменной ch значение, содержащееся в Editl.Text, которое было преобразовано в число. Теперь можно производить математические действия с введенным числом.
Обратное преобразование (превращение числа в строку) можно произвести С ПОМОЩЬЮ функции IntToStr.
var
ch:Integer;
begin
ch: =StrToInt(Edi11.Text) ; // Преобразовываю Editl.Text в число ch:=ch+l;
Editl.Text:=IntToStr(ch); // Преобразовываю ch в строку
end;
Когда вы преобразовываете строку в число, то должны быть уверены в том, что строка содержит число. Если в строке будет хоть один символ, не относящейся к цифре, то во время преобразования произойдет ошибка. Чтобы избавиться от ошибок, можно использовать исключительные ситуации, заключая преобразование между try и except (исключительные ситуации — это отдельный разговор). Есть еще один способ — использовать функцию StrTointDef, у которой уже два параметра:
口 строка, которую надо преобразовать;
□ значение по умолчанию, которое будет возвращено, если произошла ошибка.
Итак, наш пример можно подкорректировать следующим образом:
var
ch:Integer;
begin
ch: =StrToIntDef (Editl.Text, 0) ; // Преобразование Editl .Text в ^:тесло end;
В этом примере, если произойдет ошибка во время преобразования, то функция не будет выдавать ошибок, а вернет значение 0.
Преобразование даты в строку и обратно
Теперь познакомимся с преобразованием даты. Для этого есть несколько функций.
口 DateToStr — преобразовывает дату в строку. Единственный параметр, который надо указать, — переменная типа TDateTime, и на выходе получим строку.
П strToDate — преобразование строки в дату. Указываете строку (например н 11/05/200 Г') и получаете дату. Строка должна быть в виде даты в формате, соответствующем настройкам в Windows. Настройки можно увидеть в окне Панель управления | Язык и региональные стандарты.
П FormatDateTime — форматирование даты и времени. Это очень интересная функция, поэтому на ней мы остановимся подробнее.
У функции FormatDateTime два параметра.
口 Формат строки, в которую надо преобразовать дату.
口 Переменная типа TDateTime, которую надо преобразовать.
Самое интересное здесь — это формат строки. Он может содержать следующие символы:
d — показать дату, не подставляя нули в начале (1, 2, 3 30, 31).
О Dd — показать дату, подставляя если нужно в начале ноль. В этом случае, если дата меньше 10, то она будет отражаться как 01, 02 09.
ddd — показать день недели, используя короткий формат (Пн, Вт, Ср).
dddd — показать день недели с полным названием (Понедельник, Вторник).
П ddddd — показать дату, используя короткий формат.
П dddddd — показать дату, используя полный формат (например 10 дата /02/2002 будет переведена в ”10 февраля 2002".
m — показать месяп без добавления нулей (1, 2, , 11, 12).
О тт — показать месяц с добавлением нулей (01, 02, 11, 12).
П пишп — показать короткое название месяца.
口 Mmmm — показать полное название месяца (январь, февраль.)-
П уу — показать короткий года (98, 99, 00, 01).
П уууу — показать полный год.
h — показать часы, не добавляя в начале нулей.
П hh — показать часы с добавлением в начале нулей.
П п — показать минуты, не добавляя в начале нулей.
П пп — показать минуты с добавлением в начале нулей.
П s — показать секунды, не добавляя в начале нулей.
П ss — показать секунды с добавлением в начале нулей.
П z — показать миллисекунды, не добавляя в начале нулей.
П zz — показать миллисекунды с добавлением в начале нулей.
П am/pm — использовать 12-часовое представление (до полудня/после полудня).
Это практически полный обзор возможностей, а теперь посмотрим пару примеров:
FormatDateTime ('dd/mm/yyy *, Date О ) ; // Дата будет в виде ”24/02/2002” FormatDateTime (' dddddd1, Date ()) ; // Дата будет в виде и24 февраля 2002 й FormatDateTime (1 hh:пп1 z Time О ) ; // Время будет в виде ”10:48” FormatDateTime(*hh:пп - ss* f Time ()); // Время будет в виде **10:48 - 24й
Преобразование вещественных чисел
Сейчас перейдем к рассмотрению чисел с плавающей точкой. Когда вы строите математику в своей программе, то можете столкнуться с вешествен- ными числами. Например, если у вас есть какая-то формула, в которой используется деление, то результат ее выполнения будет почти всегда дробным, даже если вы уверены в целостности ответа. Например, вы делите 10 на 2, и должен получиться результат 5. Хотя результат целое число, компилятор будет представлять его как дробное.
var
i:Integer;
begin
i:=10/2;
end;
Если вы попытаетесь откомпилировать такой код, то увидите следующую запись об ошибке : 'Incompatible types: 'Integer'and 'Extended* (Несовместимые типы: целое число и дробное). Туг появляется два выхода:
Записывать результат в переменную вещественного типа. Но он подходит не всегда, поэтому лучше перейти сразу ко второму методу.
Округлять результат.
Для округления существует очень удобная функция round. ,
var
i:Integer;
begin
i:=round(10/2);
end;'
Если вы решили хранить результат в переменной вещественного типа, то могут возникнуть проблемы с выводом результата. Для этого может понадобиться преобразование вешественного числа в строку. Для этого есть функция FloatToStr, которой надо передать дробное число и получить строку. Точно так же есть и обратное преобразование strToFloat, где вы передаете строку, а получаете вещественное число.
Отдельного разговора требует функция FormatFloat, которая форматирует вещественное число по вашим нуждам. Туг есть два параметра: строка формата и само число.
В табл. 10.1 показаны разные варианты преобразования. В первой колонке представлены возможные форматы, указываемые в первом параметре функции FormatFioat. В остальных колонках показано, что произойдет с разными числами при данном формате.
Таблица 7а,・ Строки форматирования вещественных чисел
Строка, указываемаяПримеры чисел
в формате
1234
-1234
0,5
0
0
1234
-1234
1
0
0,00
1234,00
-1234,00
0,50
0,00
#,##
1234
-1234
.5
#.##0,00
1.234,00
-1.234,00
0,50
0,00
■#.##о,оо;(#.##о,оо)
1.234,00
(1.234,00)
0,50
0,00
#.##0,00;;Zero
1.234,00
-1.234,00
0,50
Zero
0.000Е+00
1.234Е+03
-1 «234Е+03
5.000Е-01
0.000Е+00
#.###Е-0
1.234E3
-1,234ЕЗ
5Е-1
0Е0
Вот пока что и все, что требовалось знать о преобразовании несовместимых
типов.
Преобразование совместимых типов (преобразование строк)
Теперь можно познакомиться и с не совместимыми типами. Под совместимыми типами понимаются такие типы данных, которые схожи по своим признакам, но хранят данные в разном виде. Например, есть несколько видов строк: строка, оканчивающаяся нулем, и строка, первый байт которой указывает на ее длину. Вроде и то и другое строки, но если ще-то нужен определенный тип строки, то придется преобразовывать свою строку именно в тот формат, который нужен функции или процедуре.
Допустим, что у вас есть строка типа string, и вы хотите ее преобразовать в pchar. Для такого преобразования нужно написать требуемый вам тип и в скобках указать свою строковую переменную:
NewStг:^PChar(МуStг)
В этом примере переменная MyStr имеет тип string, а мы приводим ее к ВИДУ PChar.
Такое преобразование строк мы будем делать очень часто при вызове WinAPI- функций, потому что там большинство строк имеет именно тип pchar.
Точно таким же образом преобразуются не только строки, но и объекты/1 числовые переменные одинакового типа, но разные по размеру. Например, у вас есть два числа integer и byte. Оба они отображают целые числа, но byte меньше и при прямом присваивании вы получите предупреждение о том, что данные будут потеряны. Вы можете явно указать компилятору, что вы преобразовываете правильно. Вот приблизительный пример такого присваивания:
var
i:Integer;
j: byte;
begin
i:=10;
j:=byte(i);
end;
Коща вы попытаетесь без преобразования присвоить переменной j значение i, то компилятор выдаст предупреждение, потому что i может хранить большие числа, которые просто не поместятся в переменную j.
Указатели
Пора познакомиться с указателями. Это очень удобный элемент языка, который мы будем часто использовать. В этом разделе будут рассматриваться указатели на структуры, которые располагаются в динамической памяти.
Но прежде чем что-то объяснять, рассмотрим, зачем нужны указатели. Давайте вспомним про процедуры, а именно, как происходит их вызов. Допустим, у вас есть процедура с именем муРгос, у которой есть два параметра: число и строка. Как происходит вызов такой процедуры, и как ей передаются эти параметры? Очень просто. Сначала параметры принимаются в стек (напомню, что стек — это область памяти для хранения временных или локальных переменных). Первой заносится строка, затем число и после этого вызывается процедура. Прежде чем процедура начнет свое выполнение, она извлекает эти параметры из стека в обратном порядке.
Теперь вспомним о наших параметрах. Первый — это число, которое будет занимать два байта. Когда происходит запись его в стек, оно займет там свои положенные два байта. Второй параметр — строка. Каждый символ строки —это отдельный байт. Допустим, что строка состоит из 10 символов. Это значит, для передачи такой строки в процедуру в стеке понадобится 10 байт плюс один байт для указания конца строки или ее размера (это зависит от типа строки). Всего для передачи в процедуру понадобится в стеке как минимум 12 байт. Это не так уж и много, поэтому такое можно себе позволить.
А теперь представьте, что строка, которую надо передать в процедуру, состоит из 1000 символов. Вот тут нам понадобится в стеке уже около килобайта. При нынешних размерах памяти на это уже никто не обращает внимания, но программисты забывают про то, что такая строка сначала копируется в память стека, а потом извлекается из него. Такое копирование большого размера памяти отнимает достаточно много времени и ваша программа тратит лишнее время на бессмысленное клонирование в памяти большой строки.
Выход из сложившейся ситуации достаточно прост. Можно не передавать строку, а только передать указатель на область памяти, где находится эта строка. Любой указатель занимает всего четыре байта, а это уже существенная экономия. Мы просто даем нашей процедуре понять, где найти строку.
Указатель в Delphi объявляется как pointer. Например, давайте объявим переменную р типа "указатель":
var
р:Pointer
Для того чтобы получить адрес переменной или объеьсга, необходимо перед его именем поставить знак @. Например, у вас есть строка str, и чтобы присвоить ее адрес в указатель р, надо выполнить следующее: p:=@str. Теперь в указателе находится адрес строки. Если вы будете напрямую читать указатель, то увидите адрес, а для того, чтобы увидеть содержащиеся по этому адресу данные, надо разыменовывать указатель. Для этого надо напи- i Итак7мы пришли к следующему: сать рл.
О p:=@str — получить адрес строки;
Пр — указатель на строку;
口 — данные, содержащиеся по адресу, указанному в р.
Давайте создадим маленькое приложение, которое будет работать с указателями. Для этого на форму надо поместить кнопку с заголовком "Работа со ссылками** и строку ввода.
Для события — нажатия кнопки Работа со ссылками — напишем код, представленный в листинге 10.13.
I ЛИСТИНГ ,0*13. Обработчик события! OnClick для кнопкиш
var
р:Pointer
Str:String;
begin
p:=@Str; // Присваиваю указателю ссылку на строку St г : =1 Привет мой Друг1 ; // Изменяю значение строки Editl・Text:=Stning(p,; 11 Вывожу текст end;
В этом примере в первой строке мы присваиваем указателю р ссылку на строку str. После этого меняем содержимое строки. В последней строчке выводится содержащийся по адресу р текст. Для этого приходится явно ука- 〜с- сВспомните
зывать, что по адресу р находится именно строка String (рл). приведение схожих типов. Таким же образом можно приводить строку string к PChar. Здесь указывается, что по адресу, переменной р находится строка, а не какой - нибудь другой тип данных. Это необходимо, потому что данные, расположенные по определенному адресу, могут иметь совершенно любой тип. Как видите, 'жесткое" указание типа похоже на преобразование типов, поэтому никаких проблем с этим не должно возникнуть.
Здесь надо заметить, что мы изменяем строку после присваивания адреса строки в переменную -указатель, и измененные данные все равно будут отражены в указателе. Это потому, что указатель всегда показывает на начало строки. Если мы ее изменим, указателю будет все равно, потому что новые данные будут расположены по тому же адресу, и р будет указывать на новую строку.
「 Примечание :
На компакт-диске в папке \ Примеры \ Глава 10 \ Pointers *вы можете увидеть пример этой программы.
Мы уже не раз использовали указатели, но не углублялись в их изучение. Каждая переменная типа “объект” 一 это тоже указатель на объект. Просто его использование стандартизовано, чтобы не смущать пользователей лишним типом адресации и разыменовывай ием.
Любой переменной-указателю можно присвоить нулевое значение, только это не число 0, a nil — нулевой указатель, например р: =nil. Когда вы присваиваете нулевое значение, то как бы уничтожаете ссылку. Точно так же, если переменной-объекту присвоить нулевое значение, вы его уничтожите, и объект больше не будет существовать.
А что если две переменные указывают на один и тот же адрес данных? Неужели при уничтожении одного из них данные уничтожатся и вторая переменная будет указывать на несуществующие данные? Нет, объект будет уничтожен только после того, как все указатели на него будут уничтожены.
Желательно не обнулять указатели, лучше пользоваться специальными функциями для их уничтожения, например, методом Free у объекта. В Windows нет сборщиков мусора, которые существуют в Java, и если объект не освободит память, то будет происходить ее потеря. Бывают случаи, когда обнуление указателя реально освобождет память, но это происходит далеко не всегда. Именно поэтому обнулять можно только объекты СОМ (о них мы поговорим отдельно). Их обнуление ведет к освобождению памяти.
Структуры, записи
Сейчас мы познакомимся поближе^со структурами и записями. Точнее сказать, оба понятия означают одно и то же. Просто в C++ принято говорить **структура*, а в Delphi говорят “запись”. Вам нужно только привыкнуть к тому, что структура — это та же запись.
Структуры похожи на объекты, не имеют методов и событий, а только свойства.
Для объявления структуры используется следующая запись:
Имя структуры 二 record
Свойство!: Тип свойства!;
Свойство2: Тип свойства2;
end;
Давайте опишем структуру, в которой будут храниться параметры окна. Она будет иметь примерно следующий вид:
WindowsSize 二 record
Left:Integer;
Top:Integer;
Width:Integer;
Height:Integer;
end1;
Структура имеет имя WindowSize, и у нее есть четыре свойства: Left, Тор, Width, Height.
Свойства объявлены каждое в отдельности, хотя в данном случае все они имеют один тип, и их можно просто перечислить через запятую и указать, что все они имеют целый тип, как это сделано в следующем примере:
WindowsSize record
Left, Top, Width, Height: Integer;
-end;
Точно так же можно объявлять и переменные в разделе var, когда несколько переменных имеют один и тот же тип.
Теперь давайте разберемся, как можно использовать структуру. Для этого надо определить переменную типа “структура”:
var
ws: WindowSize;
Структуры — это простые сгруппированные наборы свойств, которые по умолчанию не требуют выделения памяти. Они создаются локально в стеке. Здесь следует вспомнить, чем отличаются локальные переменные от глобальных.
Локальные переменные — объявляются и создаются при входе в процедуру и уничтожаются при выходе. Глобальные переменные — создаются при запуске программы и уничтожаются при выходе из нее. Это значит, что глобальные переменные существуют на протяжении всего времени выполнения программы. Локальные переменные существуют, только когда выполняется код процедуры. После первого выполнения процедуры локальные переменные уничтожаются и при следующем вызове создаются снова с нулевым значением. Поэтому, если надо сохранить значение переменной после выхода из процедуры, ее следует объявить как глобальную.
Итак, переменная объявлена. Инициализация и уничтожение не требуются, поэтому можно сразу же ее использовать. Для доступа к переменным структуры нужно написать имя структуры и через точку указать тот параметр, который вас интересует. Например, для доступа к параметру Left необходимо написать — ws. Left.
Давайте напишем пример, который будет после закрытия программы сохранять текущие значения позиции окна в структуре, а потом эту структуру будем записывать в файл. Для записи будет использоваться простой бинарный файл. В связи с этим воспользуемся объектом TFileStream.
Итак, создайте новое приложение. В разделе Туре опишите нашу структуру так, как это показано в листинге 10.14.
Листинг ЮЛ4, Объявление структуры
type
WindowsSize 二 record
Left, Top, width. Height:Integer; end;
TForml 二 class (TForm)
private public end;
Теперь создайте обработчик события OnClose для формы. Здесь мы заполним структуру значениями позиции окна и сохраним эти данные в бинарном файле. Код этого обработчика события можно увидеть в листинге 10.15.
J Листинг 1d15・ Соэсранен用❷ параметров окна в 中aHfie
procedure TForml.FormClose(Sender: TObject; var Action: TCloseAction); var
ws:WindowsSize;
f: TFil eSt ream;
Str:String;
begin
ws. Left: =Left; / / Заполняем левую позицию
WS.Top: =Top; // Заполняем правую позицию
ws. Width :=Width; // Заполняем ширину окна
ws.Height:=Height; // Заполняем высоту окна
f:=TFileStream.Create(*size.dat*, fmCreate); // Создаю файл Size.dat
f. Write (ws, sizeof (ws)) ; // Записываю структуру
f.Free; // Закрываю файл
end;
Обратите внимание, что при записи в файл надо указывать в качестве параметра буфер памяти для записи. Мы указываем структуру. В качестве второго параметра нужно указывать размер записываемых данных. Для определения размера структуры мы используем функцию sizeOf, которая вернет нам необходимый размер.
Теперь разберемся с чтением из файла. Восстанавливать размеры окна будем по событию OnShow для главной формы. В этом обработчике нужно написать содержимое листинга 10.16.
Листинг 10Л6- Загрузка параметров окна из файла
procedure TForml.FormShow(Sender: TObject);
var
ws:WindowsSize;/
fs:TFileStream;
begin
if FileExists (* size. dat *) then // Если файл Size. dat существует, то begin
fs: =TFileStream.Create (*size.dat *, fmOpenRead) ; // Открываю файл Size.dat
fs.Read(ws, sizeof (ws) ) ; // Читаю содержимое структуры
f S. Free; // Закрываю файл
// Дальше я устанавливаю сохраненные размеры и позицию окна на экран Left:=ws.Left;
Top:=ws.Top;
W idth:=ws • W idth;
Height:=ws.Height;
end;
end;„
Первым делом здесь вызывается функция FileExists которой указали имя интересующего нас файла. Эта функция проверила на существование файл. Если он существует, то мы можем попытаться прочитать его. Иначе это делать бесполезно. При чтении мы также читаем сразу всю структуру.
Как видите, структуры очень удобны для хранения каких-либо структурированных данных. Конечно же, данный пример не очень удачный, потому что такие параметры лучше сохранять в реестре, а не в файле, но главное — это сам процесс. Мы будем часто использовать структуры там, где это необходимо, потому что они действительно упрощают процесс программирования. В данном случае мы записали все значения главного окна программы в файл одной функцией.
Примечание
На компакт-диске в папке \ Примеры \ Глава 10 \ Records вы можете увидеть пример этой программы.
Храним структуры в динамической памяти
Структуры могут быть не только локальными (храниться в стеке), но и динамическими (располагаться в динамической памяти). Почему память называется динамической? Да потому, что стек создается автоматически при запуске программы, а вот дополнительную память нужно выделять самому. Ее можно добавлять и удалять в процессе работы программы; наверно, поэтому ее называют динамической.
Когда объявляется структура, можно указать ее как динамический тип. Для этого нужно объявить еще одну переменную и присвоить ей — ЛИмяСтруктуры. Чаще всего в качестве нового имени используют то же самое имя, только в начале добавляют букву "р” (это означет Pointer или указатель). Кроме того, объявление это делают прямо перед объявлением структуры:
type
PWindowsSize = -WindowsSize;
WindowsSize 二 record
Left, Top, Width, Height: Integer;~
end;
В этом примере PWindowsSize — ссылка на структуру. Теперь, чтобы разместить нашу структуру не в стеке, а в динамической памяти, мы должны использовать именно PwindowsSize:
var
ws:PWindowsSize;
begin
ws:=New(PWindowsSize) ; // Вьщеляем память
ws.Left:=10; / / Изменяем одно свойство
Dispose (ws) ; // Уничтожаем память
end;
В этом примере объявлена переменная ws типа PWindowsSize. Это значит, что ws — это всего лишь указатель, и в самом начале он нулевой. Теперь надо этому указателю выделить память размером в структуру PWindowsSize. Для этого ей надо присвоить результат работы функции New. Эта функция выделяет динамическую память под указанный в качестве параметра объект и возвращает указатель на эту память. После этого в указателе ws находится выделенная память, подготовленная для использования в качестве структуры PWindowsSize.
Доступ к свойствам остается такой же, поэтому нет смысла задерживаться на этом. Но вот в глаза сразу же бросается вызов функции Dispose. Так как
9 Зак 942
мы выделили динамическую память, ее нужно освободить, и для этого служит именно эта функпия. Просто передайте ей в качестве параметра указатель, и функция корректно обнулит его.
Помните, что если вы объявили переменную типа "указатель" на структуру (в нашем примере это PWindowsSize), то для такого указателя обязательно нужно сначала выделить память и потом освободить его. Если вы объявляете переменную типа "структура" (в нашем примере это WindowsSize), а не "указатель", то такая структура автоматически расположится в стеке и ничего не надо выделять или освобождать.
В листинге 10.17 показан пример записи параметров окна в файл, который мы написали в предыдущей главе, но с использованием динамической структуры.
Листинг 10.17. Запись в файл с использованием динамической структуры
procedure TForml.FormClose(Sender: TObject; var Action: TCloseAction}; var
ws:PWindowsSize;
f :TFileStream;
Str:String;
begin
ws: =New(PWindowsSize) ; // Вьщеляем память
ws.Left:=Left; / / Заполняем левую позицию
WS.Top:=Top; // Заполняем правую позицию
ws•Width:=Width; / / Заполняем ширину окна
ws.Height:=Height; // Заполняем высоту окна
f:-TFileStream.Create(*size.dat*, fmCreate); '' Создаю файл size.dat / / Записываю структуру
f .Write (ws74, sizeof (ws^));
f. Free; ■ / Закрываю файл
Dispose (ws) ; // Уничтожаем память end;
Обратите внимание, что при записи в файл структуры и определении ее Иначе в размера мы разыменовываем указатель с помощью записи ws*
файл будет записана не структура, а указатель, а при определении размера мы получим размер указателя, а не структуры. Это очень важно, потому что
вы можете записать в файл недостаточный размер данных или вообще ошибочные данные.
Для чтения можно использовать ту же процедуру, что и раньше, без использования указателя. Это связано с тем, что хотя мы и записывали с помощью динамической структуры, структура данных одна и та же и в файле будут те же данные, просто записаны они будут по-другому. На компакт-диске расположен пример, в котором представлена процедура загрузки данных из файла для использования динамической структуры.
工 Пр|/1мечан1/1е ]
На компакт-диске в папке \ Примеры \ Глава 10 \ DynRecords вы можете увидеть пример этой программы.
(Совет )
Если вы работаете с большими блоками данных, и структуры занимают много места в памяти, то вы должны использовать их динамические варианты, потому что стек не резиновый и ваша программа может рухнуть с ошибкой доступа к памяти или переполнением стека. Никоща не надейтесь на Windows и на большой объем памяти компьютера, на котором будет работать ваша программа. Лучше лишний раз перестраховаться, ведь использование динамических структур не намного сложнее.
Поиск файлов
В этой главе мы уже узнали о работе с файлами и что такое структуры и как с ними работать. Сейчас разберемся, как можно организовать поиск файлов.
Для начала разберемся с алгоритмом поиска файлов, а потом подробно изучим каждую из необходимых функций. Рассмотрим следующий пример:
//Запускаем поиск
hFindFile := FindFirst (Маска поиска, Аттрибуты , Информация);
//Проверяем корректность найденного файла
if hFindFile о 工NVAL工D_HANDLE_VALUE then
//Если корректно, то запускается цикл repeat - until.
repeat
//Здесь вписаны операторы, которые нужно выполнять.
until (FindNext (Информация) о 0);
FindClose (Информация);
FindFirst — открывает поиск. В качестве первого параметра выступает маска поиска. Если вы укажете конкретный файл, то система найдет именно его. Вы также можете искать и целые группы файлов. Например, можно запустить поиск всех файлов в корневом каталоге диска С:. Для этого первый параметр должен быть определен как— 'с:\*.*'. Для поиска только ЕХЕ-файлов, в папке Fold вы должны указать — *С: \Fold\* .ехе*.
Второй параметр — атрибуты, используемые при поиске файлов. Чтобы искать любые файлы, нужно указать faAnyFile. Помимо этого можно искать по следующим атрибутам:
faReadOnly — искать файлы с атрибутом Readonly (только для чтения);
faHidden — искать скрытые файлы;
faSysFile — искать системные файлы;
faArchive — искать архивные файлы;
faDirectory — искать директории.
Последний параметр — это структура, в которой нам вернется информация о поиске, а именно имя найденного файла, размер, время создания и т. д.
После вызова этой процедуры мы должны проверить на корректность найденный файл. Если результат равен invalid_handle_value, то функция не нашла ни одного файла. Иначе все нормально и файл, удовлетворяющий критериям поиска, существует, то запускается цикл Repeat - Until.
Мы уже рассматривали циклы, но все же повторимся и вспомним их работу. Цикл выполняет операторы, расположенные между repeat и until, пока условие, расположенное после слова until, вьшолняется. Как только условие нарушается, цикл прерывается.
Хочу предупредить, что функция поиска может возвращать в качестве найденного имени в структуре searchRec (параметр Name) точку или две точки. Если вы посмотрите на директорию, то таких файлов не будет. Откуда берутся эти имена? Имя файла в виде точки указывает на текущую директорию, а имя файла из двух точек указывает на директорию верхнего уровня. Если мы встречаем такие имена, то их нужно просто отбрасывать.
Параметр информация имеет тип структуры TSearchRec. Давайте рассмотрим ее подробнее. Объявление выглядит так:
type
TSearchRec 二 record
Time: Integer; // Время создания найденного файла-
Size: Integer; // Размер найденного файла
Attr : Integer ; / / Атрибуты найденного файла
Name: TFileName; // Имя найденного файла
ExcludeAtti : Integer; // Исключаемые атрибуты найденного файла
FindHandle: THandle; // Указатель, необходимый для поиска
FindData: TWin32FindData; // Структура поиска файла Winndows end;
Функция FindNext заставляет найти следующий файл, уцовлетворя ющий параметрам, указанным в функции FindFirst. Этой функции нужно передать структуру searchRec, по которой будет определено, на каком месте сейчас остановлен поиск, и с этого момента он будет продолжен. Как только будет найден новый файл, функция вернет в структуре searchRec ин- формапию о новом найденном файле.
Функция FindClose закрывает поиск. В качестве единственното параметра нужно указать все ту же структуру SearchRec.
Давайте напишем какой-нибудь реальный пример, который наглядно покажет работу с функциями поиска файлов. Посмотрите на структуру ТSearchRec. Как видите, она умеет возвращать размер найденного файла. Вот и тема для примера — мы напишем код, который будет определять размер указанного файла.
Создайте новый проект и установите на форму два компонента TEdit и одну кнопку. Можете еще украсить все это текстом. У вас должно получиться нечто похожее на рис. 10.4.
Рис. 10.4. Форма будущей программы
По нажатии кнопки (событие onclick) напишите содержимое листинга 10.18.
Листинг 10.18. Определение размера файла через его поиск
var
SearchRec:TSearchRec;
begin
// Ищем файл
if FindFirst(Editl.Text,faAnyFile,SearchRec)=0 then
/ / Забираем размер
Edit2.Text:=IntToStr(SearchRec.Size)+ *байт';
/ / Закрываем поиск
FindClose(SearchRec);
end;
k Пр-мечанме
На компакт-диске в папке \ Примеры \ Глава 10 \ FindFile вы можете увидеть пример этой программы.
Работа с системным реестром
В этом разделе говорится о том, как можно работать с системным реестром (рис. 10.5). Для этого можно использовать объект TReginiFiie.
(Совет )
Объект TReginiFiie достаточно прост и удобен. Для простого сохранения каких-то параметров программы этого объекта будет вполне достаточно.
Для работы с объектом необходимо подключить в разделе uses модуль Registry. В этом модуле находятся описание объекта и реализация его методов. Если вы не подключите этот модуль, то Delphi не сможет откомпилировать проект.
Давайте разберемся с объектом TReginiFiie и посмотрим, как он работает. Допустим, что у нас есть переменная Reglni типа TReginiFiie. Чтобы ее инициализировать, нужно присвоить переменной результат вызова метода Create объекта TReginiFiie:
Reglni:=TReglniFile.Create(* Software');
По умолчанию при инициализации вы получаете доступ к разделу реестра НKEY_CURRENT_USER. Методу Create нужно передать только один параметр — имя подраздела, который будет сразу открыт в разделе HKEY_CURRENT_USER.
Итак, после выполнения этого кода мы получили доступ к разделу НKEY CURRENT US ER \ Software. А что, если вы хотите открыть еще подраздел и получить доступ к HKEY_CURRENT_USER\ Software \ Microsoft. Для открытия подразделов у объекта TReginiFiie есть метод ОрепКеу.
Вот так можно открыть подраздел Microsoft:
Reg Ini. ОрепКеу Г Microsoft *, true);
Рис. 10.5. Просмотр реестра через программу Reg Edit
У метода ОрепКеу два параметра:
口 имя подраздела, который надо открыть;
надо ли создавать подраздел, если он не существует.
Если в качестве второго параметра передать false, и подраздел не будет существовать, то произойдет ошибка и ничего не откроется, т. е. вы останетесь на том же уровне реестра, где и были. Ну, а если передать значение true и раздел не будет существовать, то программа автоматически создаст его.
Теперь разберемся с чтением и записью. Для чтения есть несколько методов:
ReadBool — прочитать булево значение (true или false);
Readinteger — прочитать целое число;
Re ad St ring — ПрОЧИТаТЬ строку.
Все они очень похожи и имеют одинаковое количество параметров. Единственная разница — в типе третьего параметра. Давайте подробно рассмотрим Readstring, а остальные будем использовать по аналогии с этим методом.
V методов чтения есть три параметра.
П Имя подраздела, из которого мы хотим прочитать. Допустим, что мы открыли раздел Microsoft и находимся сейчас в реестре по адресу HKEY_CURRENT_USER\ Software \ Microsoft. Если мы захотим прочитать строку из подраздела Н KEY_CU RRENT_USER \ Software \ Microsoft \ Му Software, то в качестве первого параметра вы должны написать MySoftware.
Имя параметра. Если вы хотите, чтобы параметр назывался Path, то укажите здесь это имя.
П Значение, которое будет использоваться по умолчанию, если такой параметр не существует. Для метода Readstring это должна быть строка или переменная типа “строка”.
Внимание
Сразу необходимо предупредить, что даже если вы будете читать или записывать В реестр число С ПОМОЩЬЮ методов Writelnt^eger ИЛИ Readinteger, объект TReginiFiie все равно будет сохранять и читать эти числа как строки. Только после чтения будет преобразование в число. TReginiFiie реально хранит все данные в реестре только как строки. Если вы хотите сохранять числа в реестре, как обычные числа, то нужно воспользоваться объектом TRegistry.
Давайте взглянем на команду чтения в действии:
Str:=Reg工ni.Readstring(*MySoftware', 1 Path*, *c:\*);
В этом примере из подраздела MySoftware читается параметр Path. Если такой параметр не существует, то будет возвращено значение по умолчанию — С: \. Результат чтения записывается в переменную str.
Точно так же происходит и запись, только в качестве третьего параметра надо указать не значение по умолчанию, а значение, которое надо записать. Для записи используются три метода:
П WtiteBool — записать булево значение (true или false);Y
П Wtitelnteger — записать целое число;
П WtiteString — записать строку.
Простейший пример записи выглядит так:
RegIni.WriteString(1MySoftware', 'Path1, *c:\Windows');
В этом примере мы записываем в подраздел Му Software параметр Path. Значение, которое будет записано, равно третьему параметру — С: \ Windows.
После всех операций с реестром его нужно закрыть с помощью метода Free: Reglni.Free;
Для примера напишем программу, котора兄 будет сохранять свои параметры при выходе и восстанавливать позицию и размер на экране при запуске.
Создайте простейшую форму с одной только кнопкой Закрыть.
Теперь создайте обработчик события Onshow, в котором нужно восстанавливать параметры программы, которые были после последнего закрытия программы. Чтобы не перезагружать этот обработчик, давайте просто напишем вызов метода LoadProg Ра ram. Этого метода пока не существует, но мы его скоро напишем.
procedure TForml. FormShow(Sender; TObject); begin
LoadProgParam ; end;
Теперь создайте обработчик события OnClose. Здесь будут сохраняться параметры окна. Здесь мы не будем загромождать программу и просто вызовем метод SaveProgParain.
procedure TForml. FormClose (Sender: TObject; var Action: TCloseAction); begin
SaveProgParam;
end;
Если сейчас попытаться скомпилировать программу, то будет получено три ошибки. Компилятор Delphi ,,ругается,* на то, что не может найти процедуры LoadProgParam И Save Prog Par am. Давайте создадим ИХ. Для ЭТОГО ПОДНИМИтесь в начало модуля и найдите раздел описания закрытых процедур private. Опишите здесь эти две процедуры без всяких параметров:
private
{ Private dec1arat ions }
procedure LoadProgParam;
procedure SaveProgParam;
Теперь нажмите сочетание клавиш <Ctrl>+<Shift>+<C>, и Delphi создаст заготовки под эти процедуры:
procedure TForml. LoadProgParam;
begin end;
procedure TForrnl. SaveProgParam; begin end;
Добавим свой код в эти шаблоны. Для этого напишите в процедуре SaveProgParam содержимое листинга 10.19.
Листинг 10.19. Сохранение параметров окна в реестре
procedure TForml.SaveProgРа ram;
var
FIniFile: TReglniFile;
begin
FIniFile := TReglniFile. Create (* Software') ; // Инициализирую реестр
FIniFile.OpenKey (*VR* f true); // Открываю раздел
FIniFile.OpenKey (* VR-Online *, true) ; // Открываю еще один раздел
if WindowState=wsNormal then
1Width1, Width);1Heigth1, Height); 'Left', Left);1 Top *, Top);1Width1, Width);1Heigth1, Height); 'Left', Left);1 Top *, Top);begin
1Width1, Width);
1Heigth1, Height); 'Left', Left);
1 Top *, Top);
1Width1, Width);
1Heigth1, Height); 'Left', Left);
1 Top *, Top);
FIniFile.Writeinteger(1Option*, FIniFile.Writeinteger('Option', FIniFile.Writeinteger('Option1, FIniFile.Writeinteger(*Option*, end;
FIniFile.Writeinteger(•Option1,FIniFile.Writeinteger(•Option1,*WinState1, Integer(Windowstate));*WinState1, Integer(Windowstate));
FIniFile.Writeinteger(•Option1,
FIniFile.Writeinteger(•Option1,
*WinState1, Integer(Windowstate));
*WinState1, Integer(Windowstate));
FIniFile. Free; //Освобождаю реестр end;
После инициализации реестра и подготовки разделов делаем проверку, в каком состоянии находится окно. Если Windowstate равно wsNormal, то сохраняем параметры окна. Если нет, то этого делать не надо. Если у вас стоит разрешение экрана 800x600, то при максимизированном окне значение его ширины будет 802, а высоты 602. Эти значения больше реального разрешения, и если вы установите их при загрузке, то изменить размеры окна мышкой будет очень трудно.
После этого сохраняем состояние окна — windowstate Так как оно имеет ТИП TWindowState, ТО ПРИХОДИТСЯ ПРИВОДИТЬ ЭТОТ ТИП К Integer С ПОМОЩЬЮ записи Integer(WindowState).
Процедура LoadProgParam работает таким же образом, и ее содержимое вы можете увидеть в листинге 10.20.
Листинг 10.20. Загрузка значений из реестра
procedure TForml . LoadProgParam;
var
FIniFile: TReglniFile;
begin
FIniFile := TReglniFile.Create ( •Software1);
FIniFile. OpenKey ( 'VR1, true);
FIniFile. OpenKey ( WR-Online', true);
Width : =FIniFile.Readinteger ( 'Option*, 'Width1, 600);
Height: =FIniFile.Readinteger( * Option*, Heigth , 4。。);
Left:=FIniFile.Readinteger( 1Option*, *Left1, 10);
Top :=FIniFile ・ Readinteger(* Option 1, 1Top1, 10);
WindowState : =TWindowState (FIniFile .ReadInteger ( *Option *, ' WinState*, 2));
FIniFile. Free;
end;
ПримечаниеПримечание
Примечание
Примечание
На компакт-диске в папке \ Примеры \ Глава 10 \ Register вы можете увидеть
пример этой программы.
Рассмотрим еще несколько методов, которые могут быть полезны при рабо
те с реестром.□ DeleteValue — удалить значение удалить значение с именем width, Reglni.DeleteValue( 1 Param')П RootKey 一 это свойство объекта ловной раздел, который сейчасте с реестром.□ DeleteValue — удалить значение удалить значение с именем width, Reglni.DeleteValue( 1 Param')П RootKey 一 это свойство объекта ловной раздел, который сейчасиз реестра. Например, если вам нужно то вы можете написать кед:TReglniFile, которое указывает на го- используется. Как мы уже знаем, поиз реестра. Например, если вам нужно то вы можете написать кед:TReglniFile, которое указывает на го- используется. Как мы уже знаем, по
те с реестром.
□ DeleteValue — удалить значение удалить значение с именем width, Reglni.DeleteValue( 1 Param')
П RootKey 一 это свойство объекта ловной раздел, который сейчас
те с реестром.
□ DeleteValue — удалить значение удалить значение с именем width, Reglni.DeleteValue( 1 Param')
П RootKey 一 это свойство объекта ловной раздел, который сейчас
из реестра. Например, если вам нужно то вы можете написать кед:
TReglniFile, которое указывает на го- используется. Как мы уже знаем, по
из реестра. Например, если вам нужно то вы можете написать кед:
TReglniFile, которое указывает на го- используется. Как мы уже знаем, по
умолчанию используется раздел HKEY CURRENT U SER. Чтобы изменить это значение, нужно просто присвоить другое. Например, в листинге 10.21 приведен код, который записывается значение в раздел HKEY_ LOCAL_MACH1N Е.
Листинг 10.21, Запись значения в раздел HKEY_LOCAL_MACHINE
var
Reglni:TReglniFile;
begin
Reglni:=TRegIniFile.Create(*Software');
Reglni. RootKey: =HKEY_LOCALjyiACHINE;
Reglni.OpenKey(*VR-online1, true);
Reglni.WriteString(*Razd*, 1 Param*, Editl.Text);
Reglni.Free;
end;
Объект TReginiFiie универсален и может работать не только с реестром, но и с INI-файлами. Так как такие файлы морально устарели и их нежелательно использовать, то здесь не будем рассматривать примеры работы с ними.
При рассмотрении реестра был упомянут объект TRegistry. Это объект, который предназначен для работы только с реестром. Как мы уже знаем, объект TReginiFiie реально всегда записывает в реестр строки, даже если пишется число с помощью метода writeinteger. Это связано с тем, что этот объект позволяет работать с реестром как с 1N 1-файлом. А т. к. файл может содержать только строки, то и объект работает со строками.
Реестр, в отличие от INI-файлов, — это база данных. Поэтому она позволяет хранить не только строки, но и числа, и данные, и логические операторы. Если вы хотите, чтобы данные сохранялись и читались в виде типов, отличных от строк, то необходимо работать через объект TRegistry. Он также объявлен в модуле registry, поэтому вы должны подключать этот модуль в раздел uses.
Работа с TRegistry практически такая же, как и с TReginiFiie. Посмотрите на листинг 10.22 и вы увидите много обшего.
Листинг 10.22. Работа с реестром через объект TRegistry var
Reg:TRegistry;
begin
Reg:=TRegIniFile.Create; //Инициализация
Reg.RootKey:=HKEY_LOCAI^MACHINE;//Выбираю корень реестра
//По умолчанию это HKEY_CURRENT_USER
Reg ・ OpenKey (1 SYSTEM'『true) ;/ /Открываю раздел SYSTEM
Reg. OpenKey (' Current ControlSet#, true) ; / /Открываю раздел Current Control - Set
Reg.OpenKey('Control1f true);
Reg.OpenKey (' Session Manager1, true);
Reg. OpenKey (* Memory Management1, true);
/ /Записываю параметр ClearPageFil eAt Shutdown
Reg.Writeinteger('ClearPageFileAtShutdown *, 112);
/ /Читаю параметр Hidden
Reglni.Readinteger(*Hidden')
Reg.CloseKey;//Закрываю ктточ
Reg.Free; //Освобождаю объект
end;
В этом примере чтение и запись будут происходить с числами, а не со строками с последующим преобразованием в число.
Обратите внимание, что после создания объекта нужно выбрать раздел, с которым вы будете работать. В объекте TReginiFiie мы использовали значение по умолчанию. При записи у метода Writeinteger только два параметра.
□ Имя параметра, который мы хотим записать.
□ Значение параметра.
Здесь отсутствует раздел, потому что запись будет происходить в текущий раздел. При использовании объекта TreginiFile в качестве первого параметра метода был указан раздел, в который нужно записать параметр- значение, а здесь раздел отсутствует.
Множества
В английском языке множества называют словом set (набор), поэтому больше привычно понятие — набор данных. Множества — это набор заранее определенных данных. Вы объявляете переменную, которая может принимать определенные значения из какого-то набора. Допустим, что требуется описать часы. Какими они бывают? Можно выделить два типа — цифровые или стрелочные. Чтобы пользователь не ввел чего-то своего, вы можете обь-
явить набор из этих двух значений, и тогда пользовать сможет выбирать только из этого набора.
Множества объявляються в разделе type с помощью конструкции set of. В следующем примере объявляется множество TSetofchar типа *'набор символов" char:
type
TSetOfChar = set of char;
Тип char это число от 0 до 255, значит, наша переменная может принимать одно из этих значений.
Более интересный пример будет с часами:
type
TClockTypeEnum 二(Digital, Analog);
TClockType 二 set of TClockTypeEnum;
В первой строке объявили переменную TClockTypeEnum — перечисление, в которой перечислено два типа часов. Во второй объявили набор TClockType. Теперь любая переменная типа TClockType будет принимать значения Digital, Analog. Другое значение в эту переменную записать нельзя.
Давайте научимся работать с множествами на реальном примере. Для этого представим, что нам надо хранить в какой-то переменной дни недели, когда было полнолуние. Объявление такого множества будет выглядеть следующим образом:
type
TDaysOfWeek 二(Понедельник, Вторник, Среда, Четверг, Пятница, суббота, Воскресень е);
TFullMoonDays 二 set of TDaysOfWeek;
В реальной программе нельзя будет использовать русские значения в множестве, но для данного примера использовались понятные нам названия дней недели.
Чтобы использовать это множество, мы должны объявить переменную такого типа, например MoonDays: TFullMoondays. А чтобы присвоить значение, мы должны указать его в квадратных скобках. В следующем примере заносится в такую переменную два дня — понедельник и четверг:
var
MoonDays: TFulIMoonDays;
begin
MoonDays := [Понедельник, Четверг];
end;
Чтобы проверить, находиться ли какое-то значение в переменной, нужно использовать оператор in:
if Понедельник in moonDays then
Выпол •:инь действие
Для добавления и удаления элементов из множества вы можете использовать операторы сложения и вычитания или функции include и Exclude. В следующем примере показаны различные варианты использования этих функций:
MoonDays :二 MoonDays + [Среда]; //Добавляем сред/
MoonDays := MoonDays — [Понелельник, Четверг] ; // Удаляем лва дня
Include (MoonDays, 1 Воскресенье1); // Добавляем воскресенье
Exclude(MoonDays, 1 Среда *); // Удаляем среду
Функции работают быстрее и реализованы в одной машинной команде. Операторы сложения и вычитания при работе со множествами отнимают больше ресурсов и используют больше команд.
Потоки
Под потоком понимается объект TStream, который является базовым объектом для потоков разных типов. В этом объекте реализованы все необходимые свойства и методы, используемые для чтения и записи данных на различные типы носителей (память, диск, медиа- носители). Благодаря этому объекту доступ к разным типам носителей становится одинаковым.
В этой главе, когда рассматривали работу с файлами, уже использовались потоки. Объект TFileStream является потомком главного объекта ТStream и позволяет получить доступ к диску. Точно так же можно получить доступ:
К памяти через объект TmemorySt ream;
к сети через объект TWinSocketStream;
口 к COM-интерфейсу через ToleStream;
к строкам, находящимся в динамической памяти TStringStream.
Это неполный список объектов потоков, но даже все эти объекты мы рассматривать не будем. Рассмотрим только базовый объект TStream, а вы потом посмотрите на то, как производилась работа с TFileStream, и увидите, что работа объектов идентична и можно работать с любым другим типом потока.
Итак, давайте разберемся со свойствами и методами потока.
Свойства:
口 Position — указывает на текущую позицию курсора в потоке. Начиная с этой позиции будет происходить чтение данных;
口 size — размер данных в потоке.
Методы:
口 CopyFrom — метод предназначен для копирования из другого потока. У него два параметра: указатель на поток, из которого надо копировать, и число, показывающее размер данных, подлежащих копированию;
П Read — прочитать данные из потока, начиная с текущей позиции курсора. У этого метода два параметра — буфер, в который будет происходить чтение, и число, показывающее размер данных для копирования;
О Seek — переместиться в новую позицию в потоке. У этого метода два параметра: число, указывающее на позицию, в которую надо перейти. Если вам нужно передвинуться на пять байт, то просто укажите цифру 5. Второй параметр: откуда надо двигаться. Туг возможны три варианта:
soFromBeginning — двигаться на указанное количество байт от начала файла;
soFromCurrent — двигаться на указанное количество байт от текущей позиции в файле к концу файла;
soFromEnd — двигаться от конца файла к началу на указанное количество байт.
П setsize — установить размер потока. Здесь только один параметр — число, указывающее новый размер потока. Допустим, что вам надо уменьшить размер файла. В этом случае с помощью метода setsize потока TFilestream вы можете уменьшить или даже увеличить размер файла.
П write — записать данные в текущую позицию потока. У этого метода два параметра:
переменная, содержимое которой нужно записать;
число байт для записи.
Это основные методы, которые вам могут понадобиться при работе с потоками. На практике мы еще встретимся с подобными объектами, и вы еще раз увидите, как с ними работать.
Слова stream и thread переводятся на русский язык почему-то одинаково, как поток. Но в программировании это разные термины. Stream — это поток каких-либо данных, a Thread обеспечивает многопоточность приложений (параллельное выполнение или вычисление). Чтобы вас не путать, можно использовать один термин — поток, но в дальнейшем постараемся понять разницу между двумя этими терминами.
(Совет )
Обращайте внимание на англоязычное название объектов, с которыми мы будем работать и помните, что Stream — это поток данных, a Thread — отдельная нить процесса, выполняющего инструкции программы. Процесс — это отдельная программа, которая может состоять из нескольких потоков (Thread). В данном случае поток можно воспринимать как процедуру, которая выполняется параллельно основной задаче. Именно поэтому я советую вам следить за названиями, чтобы не путать поток данных и программный поток.
Сейчас МЫ не будем рассматривать пример использования потоков, а ограничимся только теоретическими знаниями. В дальнейшем будет достаточно практичесьсих знаний, а в последней главе этой книги мы напишем программу создания тестовых заданий, которая будет основана на этой технологии.
ff
f
f
Глава 11
Обзор дополнительных компонентов Delphi
В этой главе будет дан максимальный обзор дополнительных компонентов Delphi. Мы познакомимся с большинством компонентов, которые упрощают создание программ, а также с их свойствами и методами.
Глава будет наполнена большим количеством примеров. Так как наши знания о программировании уже сильно улучшились, то и программы станут более сложными, интересными и, соответственно, более полезными.
Здесь вы на реальных примерах почувствуете всю мощь среды разработки Delphi и, возможно, уже будете готовы самостоятельно создавать свои проекты. Но прежде чем начнете это делать, желательно прочитать в конце книги главу о работе с самой средой разработки. Во многих книгах эта глава вдет в самом начале, но здесь она дается в конце, потому что нет смысла рассказывать о том, чему вы еще не можете найти применения. Только когда почувствуете, что имеете достаточно сил и опыта для создания самостоятельных проектов, тогда и изучайте работу со средой Delphi, а также порядок отладки приложений.
В процессе написании программы всегда возникают ошибки или просто опечатки. При компиляции Delphi находит только синтаксические ошибки. Недочеты в логике выполнения программы компилятор найти не может. Поэтому в Delphi встроены мощные средства отладки приложений, которые позволят находить ошибки и недочеты именно в логике программы. Если вы уже готовы и собираетесь писать самостоятельный проект, вам обязательно необходимо познакомиться с этими возможностями.
Дополнительные кнопки Delphi
(TSpeedButton viTBit Btn)
Мы уже познакомились с кнопкой TButton с вкладки Standard. В Delphi есть еще два вида кнопок на вкладке Additional — TBitBtn и TSpeedButton. Помимо простого текста, они могут содержать и изображения, разница
только в том, что TBitBtn может получать фокус ввода с клавиатуры, а TSpeedBu 七七 о、нет.
Что значит Иполучение фокуса ввода**? Когда вы щелкаете мышью по какому-то элементу управления, то он получает фокус ввода. Например, вы щелкнули по строке ввода TEdit. После этого в ней появляется курсор для ввода текста, и все события от клавиатуры будут посылаться именно этому компоненту. Точно так же с кнопкой. Если вы щёлкнули по ней, то все нажатия на клавиатуре будут посылаться кнопке. Правда, кнопка не может получать текст, но если вы нажмете клавишу <Enter>, когда фокус находится на кнопке, то это будет равносильно щелчку по этой кнопке мышью.
Кнопка TSpeedButton не может получать фокуса. Это значит, что если вы набирали какой-то текст в строке ввода, а потом щелкнули по такой кнопке, то обработается соответствующее событие и фокус возвратится обратно в строку ввода. Он не останется на кнопке.
Как вы знаете, фокус выделенного компонента в программах можно менять клавишей <ТаЬ>. Если вы нажмете ее, то будет выделен следующий по счету компонент. Так вот, клавишей <ТаЬ> невозможно выделить кнопку TSpeedButton.
Объект TBitBtn хорошо подходит там, где нужна кнопка с изображением, а TSpeedButton для кнопок панели инструментов, потому что такие кнопки никогда не должны получать фокуса ввода. Именно поэтому TSpeedButton отображается в Delphi на панели инструментов в виде квадратной кнопки.
Давайте попробуем создать небольшое приложение, в котором будут использоваться оба типа этих кнопок. Запустите Delphi и создайте новый проект.
Установите на форму компонент TPanel с вкладки Standard. Установите у него свойство Align в alTop, чтобы панель растянулась по верху формы. Теперь удалите текст в свойстве caption и измените высоту (свойство Height) на 24.
Установите на панель кнопку TSpeedButton и установите у нее свойства Left (левая позиция) в 0 и тор (верхняя позиция) в 1. Ширина (width) кнопки должна быть равна 23, а высота (Height) равна 22. Давайте изменим имя кнопки на Exit But ton, потому что по нажатии этой кнопки мы будем закрывать программу.
Если вы все сделали правильно, то у вас должно получиться нечто похожее на рис. 11.1.
Теперь дважды щелкните по свойству Glyph и перед вами должно открыться окно загрузки изображения (рис. 11.2). Нажмите на кнопку Load и загрузите картинку. К Delphi придается большая библиотека готовых изображений. Расположены они в папке Program Files \ Common Files \ Borland Shared \ Images \ Buttons. На диске, который прилагается к книге, вы также можете
найти подборку дополнительных картинок, которая может пригодиться при написании программ.
Рис. 11.1. Форма будущей программы
Рис. 11.2. Окно загрузки изображения
Загружаемая картинка должна иметь размер 16x16. Пусть будет загрузка картинки, которая вдет вг^сте с Delphi под названием dooropen.bmp. Можете сделать то же самое. Как только вы выберете картинку, нажмите ОК, чтобы закрыть окно загрузки изображения.
Теперь на кнопке отображается выбранная картинка. Запустите программу, чтобы посмотреть на результат ее работы.
(Совет )
Вы можете заметить, что кнопка выглядит немного выпукло, а во всех современных приложениях кнопки плоские и плавающие. Это легко исправить, если изменить свойство кнопки Flat на true.
Теперь создадим для кнопки событие Onclick. Для этого нужно дважды щелБснуть левой кнопкой мыши по самой кнопке или выделить ее и на вБладке Events объектного инспектора дважды щелкнуть по свойству Onclick. В созданном обработчике события напишите код вызова метода закрытия формы: procedure TForml.ExitButtonClick(Sender: TObject);
begin
Close; //Выход из программы end;
Теперь можно запустить программу и проверить ее работу в действии.
Усложним задачу и поместим на форме еще две кнопки. Пусть они будут расположены так, как это показано на рис. 11.3. В первую из них загружена картинка из файла BulbofLbmp (BulboffButton), а во вторую из Bulbon.bmp (BulbonBut ton).
Рис. 11.3. Улучшенная форма будущей программы
Теперь установите у обеих кнопок свойство Grouplndex равным 1, и у любой из них измените свойство Down на true. Таким*образом, мы назначили этим кнопкам один и тот же индекс группы, и они стали сгруппированными. Можно устанавливать любой индекс больше нуля. При нулевом значении считается, что группировки нет. Кнопка, у которой свойство Down установлено в true, по умолчанию выглядит нажатой.
Попробуйте теперь запустить программу и нажимать на новые кнопки. Когда нажата одна из них, то другая автоматически отпускается. Это связано с тем, что кнопки сгруппированы и работают как одно пелое.
Таким способом часто оформляют операции выравнивания. Например, в том же текстовом редакторе Word выравнивание выполнено именно таким способом.
N
Возможность кнопки находиться в нажатом и нормальном состоянии появилась после того, как были сгруппированы две кнопки путем присвоения их свойству Grouplndex значения 1. Вы можете создать еще одну группу кнопок (количество кнопок не офаничено двумя) и присвоить ей значение 2. В этом случае новая группа кнопок будет работать независимо от первой.
Внимание
Свойство Down может быть равно true только у одной кнопки в группе.
Теперь установим на форму кнопку TBitBtn и назовем ее startBtn. В свойстве caption напишите слово "Старт” и загрузите сюда любую картинку. Загрузка происходит точно таким же образом, как и при использовании TSpeedButton.
Кнопки TBitBtn и TSpeedButton очень похожи и имеют практически все одинаковые свойства и методы, поэтому больше тут сказать уже практически нечего.
Очень интересным является свойство Layout, которое показывает, где должна располагаться картинка, а где текст. На рис. 11.4 показаны разные варианты кнопок, а снизу приписаны установленные значения в свойстве Layout.
Выход
Е
Е Выход
Выход В
В
Выход
HGlyphBattam bIGlyphLeft bIGlyphRight bGlyphTop
Рис. 11.4. Различные варианты свойства layout
Другим интересным свойством является Kind. В нем заложен список заранее подготовленных стандартных кнопок. При выборе любого из них автоматически изменяется текст и изображение на кнопке. На рис. 11.5 вы можете увидеть различные кнопки и соответствующие им значения Kind. Жаль только, что текст англоязычный, но его изменить очень легко.
Помимо картинки и текста, изменяется свойство ModalResult — результат, который вернет кнопка для диалогового окна. Вы можете и сами менять это
свойство для любой кнопки, изменяя как ее реакцию, так и всей программы в целом.
X Abort
bkAbort
0 No
bkNo
5AH
bkAil
,оК
bkUK
JL£bse
bkClose
C Retry
bkReg
? Help
bkHelp
У Yes
bkYes
鹿 Ignore
bklgnore
X Cancel
bkCancel
Рис. 11.5. Кнопки с различными вариантами установок в свойстве Kind
Давайте напишем пример, который будет запускать дочернее модальное окно по нажатии кнопки Старт.
Создайте еще одну форму и сразу откройте Менеджер проектов (Project Manager), чтобы легче было переключаться между формами (для этого нужно выбрать меню Mew | Project Manager). Теперь установите на новую форму три кнопки. Желательно расположить их так, как это показано на рис. 11.6.
Рис. 11.6. Вторая форма программы
Для первой кнопки установим свойство Kind в Ькок, для второй — в bcCancel, а ДЛЯ третей — В bkAbort.
Теперь вернемся в первую форму (для этого дважды щелкните мышью в окне Project Manager по первой форме) и создадим обработчик события onclick для кнопки Старт. В нем нужно написать содержимое листинга I LL
Листинг 11.1. Обработчик события Onclick для кнопки Старт
procedure TMainForm.StartBtnClick(Sender: TObject); begin
Form2. ShowModal; // Показываю вторую форму
if Form2. ModalResult=mrOk then
Application. MessageBox (* Вы нажата кнопку OK*, * Вы нажали1);
if Form2.ModalResult=mrCancel then
Application. MessageBox (1 Вы нажазш кнопку Cancel*, * Вы нажали1);
if Form2 .ModalResult=inrAbort then
Application. MessageBox (* Вы нажали кнопку Abort *, ' Вы нажали *); end;
В первой строчке кода показываеся вторая форма как модальное окно. Если пользователь нажмет одну из трех кнопок, модальное окно закроется и в свойстве формы Form2 .ModalResult будет находиться результат, который указан у нажатой кнопки в свойстве ModalResult. Вот именно этот результат мы проверяем после закрытия окна, и в зависимости от этого выводится на экран необходимое сообщение. Попробуйте запустить этот пример и посмотреть его в действии.
Примечание 】
На компакт-диске в папке \ Примеры \ Глава 11\BitBtn&SpeedButton вы можете увидеть пример этой программы.
Самостоятельная подготовка картинок для кнопок
Стандартный размер картинки для кнопки равен 16x16. Вы можете создавать изображения и большего размера, но если хотите, чтобы кнопочки выглядели элегантно, то желательно, чтобы они имели именно такой размер.
Как теперь уже известно, кнопка может быть в двух состояниях — активная (свойство Enabled = true) и неактивная (Enabled = false). Когда кнопка неактивна, то ее изображение должно отображаться серым цветом.
(СовеЕ)
Если вы будете делать кнопку неактивной, то желательно подготавл ивать изображение размером 32x16. Такое изображение нужно разделить пополам. Слева нарисовать цветную картинку, а справа, в оттенках серого, как это показано на рис. 11.7, ее дубликат.
Коцца кнопка активна, Delphi автоматически будет подставлять изображение слева, а когда неактивна, то правое изображение.
Рис. 11.7. Изображение для кнопки в двух состояниях
В принципе, Delphi и сам может автоматически сделать картинку для неактивного состояния, поэтому этим правилом можно пренебречь. Но если во время тестирования программы вы заметили, что в неактивном состоянии изображение исчезает или выглядит некорректно, нужно подготовить правильную картинку.
Маскированная строка ввода
(TMaskEdit)
Слово "маскированная" в данном случае происходит не от слова "прятаться" ("маскироваться"), а от слова "маска". Очень часто требуется, чтобы пользователь ввел в программу какие-то данные в определенном формате. Для этого существует компонент TMaskEdit.
Давайте создадим небольшой пример, который проиллюстрирует работу с компонентом TMaskEdit. Создайте новое приложение. Поместите на него метку (TLabel) с текстом нВведите дату**. Рядом поставьте компонент TMaskEdit. Щелкните по нему и посмотрите на свойства. Большинство свойств в данном случае идентично компоненту TEdit с палитры инструментов Standard.
Самое интересное здесь свойство — EditMask. Щелкните по нему два раза мышью, и перед вами откроется окно редактора ввода (рис. 1 1.8).
В строке ввода Input Mask вы можете вводить маску. Справа расположен список примеров. Слева внизу расположено поле Test Input, в котором можно тестировать указанную маску.
Создавать маску очень просто. Если вы хотите, чтобы пользователь ввел четыре числа, потом тире и еще три числа, можно в поле Input Mask ввести 9999-999. Цифра 9 означает, что на этом месте должна быть любая цифра. Если вам нужно, чтобы вначале ввода была еще буква R, то укажите маску -R9999-999.
工 Примечание 】
На компакт-диске в папке \ Примеры \ Глава 11 \ MaskEdit вы можете увидеть пример этой программы.
Рис 11.8. Редактор маски
Сеточки (TStringGrid, TDrawGrid)
Очень часто в программах нужны сетки ввода данных. Например, взгляните на электронную таблицу Excel. Ее окно построено на основе сетки ввода. Вы можете себе представить электронную таблицу без такой сетки? Очевидно --нет.
Рис. 11.9. Простейшая сетка
На рис. 11.9 вы можете увидеть простейшую сетку, которая еще не умеет ничего делать. В Delphi вам доступны сразу два вида сеток — TStringGrid и TDrawGrid. Разница в них незначительная. В TStringGrid вы можете вводить данные, и они там будут сохраняться и отображаться, а в TDrawGrid данные могут вводиться, но за отображение их должен отвечать ваш код. Другими словами, TStringGrid — это сетка строк, a TDrawGrid — это сетка рисунков.
Рассмотрим работу только с TStringGrid, потому что он более распространен и потребность в нем появляется на много чаще. Работа второго почти
не отличается, и, кроме того, у нас еще не хватает знаний о графических возможностях Delphi.
Создайте новый проект и поместите на его главную форму сетку TStringGrid. Выделите ее, и давайте рассмотрим ее специфичные свойства в объектном инспекторе (те, что нам известны, рассматриваться не будем).
口 Borderstyle — стиль обрамления. Здесь возможны варианты bsSingle или bsNone. Можно самостоятельно, по очереди, установить оба этих типа и посмотреть, что они собой представляют.
口 ColumnCount — количество колонок в сетке. Оставим так, как есть — 5 штук.
口 DefaultColWidth — ширина колонок по умолчанию.
Default Drawing — рисование по умолчанию. Если здесь установлено true, то компонент сам будет отображать введенные данные. Если false, то это придется делать самостоятельно.
DefaultColHeight — высота строк по умолчанию. Значение, установленное здесь, достаточно большое, поэтому давайте введем 16. Так сетка будет выглядеть более элегантно.
П FixedColor — цвет фиксированных колонок и строк. В фиксированные ячейки нельзя вводить текст, и они используются в качестве заголовков. На рис. 11.9 первая колонка и первая строка фиксированы и поэтому отображены цветом элементов управления.
П FixedCols — количество фиксированных колонок. Они всегда первые, нельзя создать фиксированную колонку в середине сетки. Это можно сделать только самостоятельно.
П FixedRows — количество фиксированных строк. Они всегда первые, нельзя создать фиксированную строку в середине сетки. Это можно сделать только самостоятельно.
口 GridLineWidth — толщина разделительных линий сеток.
П options — настройки сетки. Если дважды щелкнуть левой кнопкой мыши по этому свойству или один раз по квадратику слева от названия свойства, то раскроется большой список дополнительных свойств. Рассмотрим каждое из них в отдельности:
goFixedVertLine — рисовать вертикальные линии сетки у фиксированных ячеек;
goFixedHorzLine — рисовать горизонтальные линии сетки у фиксированных ячеек;
goVertLine — рисовать вертикальные линии сетки у нефиксированных ячеек;
goHorzLine — рисовать горизонтальные линии сетки у нефиксированных ячеек:
go Range Select — позволять выделять несколько ячеек;
goDrawFocuSelected — рисовать m0Kус выделенной ячейки;
go Rowsizing — можно ли изменять размер строк перетягиванием мышкой;
goColSizing — можно ли изменять размер колонок перетягиванием мышкой;
goRowMoving — можно ли перемешать строки (если true, то можно нажать кнопку мыши, установив ее указатель на фиксированную ячеЙБсу строки, и перетащить в новое положение);
goColMoving — можно ли перемещать колонки (если true, то можно нажать кнопку мыши, установив ее указатель на фиксированную ячеЙБсу колонки, и перетащить ее в новое положение);
go Editing — можно ли вводить с клавиатуры данные в сетку (для нашего примера установим в true);.
go Tabs — если здесь установить true, то между ячейками можно путешествовать с помощью клавиши <Tabs>;
goRowSelect — если здесь false, то выделяется только выделенная ячейка (если true, то вся строка);
goAlwaysShowEditor — если false, то когда вы обратились к ячейке, для: ее редактирования нужно нажать <Enter> или <F2>, если true, то как только выделяется ячейка, ее сразу можно редактировать;
goThumbTracking — будут ли данные прорисовываться, пока пользователь перемещает полосу прокрутки.
О RowCount — количество строк. Для первого примера нам хватит 口兄ти.
口 Scrollbars — нужно ли показывать полосы прокрутки. Здесь возможны варианты:
ssNone — не показывать;
ssHorizontal — только горизонтальную полосу;
ssvertical — только вертикальную полосу.
Итак, давайте напишем первый пример. Создайте обработчик события Onshow для формы и напишите там содержимое листинга 11.2.
Листинг 11.2. Обработчик события OnShow для главной формы
procedure TMainFo™. ForraShpw(Sender: TObject);
begin
/ / Заполняем значениями первую колонку
StringGridl.Cells[0,1]:=Иванов*;
StringGridl.Cells[0f 2]: = *Петров, StringGridl.Cells[0,3]: = * Сидоров *; StringGridl- Cells[0.4]: =1Смирнов1;
// Запозшяем значениями первую строку StringGridl.Cells[1,0]:=*Год рожд-1; StringGridl.Cells[2,0]:=*Место рожд.*; StringGridl.Cells[3,0]: = * Прописка *; StringGridl.Cells[4,0]:=*Семейное положение';
end;
У объекта TStringGrid есть еще одно свойство, которое не описано в объектном инспекторе, — Cells. Это свойство — двухмерный массив из строк, в которых хранятся данные, отображаемые в сетке. Чтобы получить доступ к какой-либо ячейке, нужно записать StringGridl.Cells[номер колонки, номер ячейки].
Внимание /
Нумерация колонок и строк начинается с нуля. Например, если вы хотите записать во вторую колонку и четвертую строку текст "Привет'1, то необходимо записать: StringGridl.Cells [1,3] : = *Привет* .Такимже способом можно и читать содержимое ячеек: if StringGridl. Cel Is [ 1 r 3 ] =1 Привет1 then Сделать [какие-либо действия].
На рис. 11.10 вы можете увидеть окно, в котором показан пример в запущенном виде.
Рис. 11.10. Программа в запущенном виде
Давайте усилим этот пример и заодно поглубже познакомимся с сеткой. Как видите, в примере есть поле Год рожд.. В это поле должны вводиться даты, а значит, они должны иметь опр еде ле нный формат. Было бы очень удобно,
если бы в этом поле можно было бы задать маску ввода, но сетка такое не поддерживает. Тут можно использовать одну хитрость — когда пользователь щелкает мышью по нужному полю, подставлять компонент TMaskEdit, и пускай пользователь вводит информацию в него в строго определенном формате.
Давайте реализуем сказанное на практике. Для этого поместите на форму компонент TMaskEdit (место расположения не имеет значения) и назовите его DateEdit. Установите маску для ввода даты 99/99/9999. Теперь установите у него свойство visible в false, чтобы компонент не был виден.
Создайте обработчик события OnDrawcell и в нем мы напишите содержимое листинга 11.3.
Ыистинг 11.3- Обработчик события OnDrawCell
procedure TMainForm.StringGridlDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); begin
DateEdit.Visible :二 false; // Сделать невидимым коъшонент DateEdit
if (gdFocused. in State) then // Если, текущая ячейка, в фокусе, то ... begin
if ACol=l then // Ecj™ рисуется ячейка, первой колонки, то begin
DateEdit.Text:=StringGridl.Cells[ACol, ARow]; // 3a皿сань в DateEdit // текст ячейки
DateEdit.Left :二 Rect.Left + StringGridl.Left+2; // Установить левую // позицию
DateEdit.Top :二 Rect.Top + StringGridl.top+2; // Установить верхнюю // позицию
DateEdit.Width := Rect.Right Rect.Left; // Установить ширину DateEdit.Height : = Rect.Bottom - Rect.Top; // Установить высоту DateEdit.Visible :二 匸rue; // Сделать компонент DateEdit вмд%:••二31M exit; // Выход из процедуры end; end; end;
Этот обработчик вызывается каждый раз, когда надо прорисовать какую- нибудь ячейку. Если прорисовывается вся сетка, то он вызывается для каж-
дой ячейки отдельно. Для нас Delphi создал процедуру — обработчик события StringGridlDrawCel 1 co следующими параметрами:
О Sender — здесь передается указатель на объект, который сгенерировал событие;
口 AR0W и ACol — номер строки и номер столбца (координаты) ячейки, которые надо прорисовать;
口 Rect — структура, в которой указаны относительные размеры и положения ячейки.
Что понимается под словом ,,относительные,*? Структура Reel выглядит так:
type
TRect = record
Left, Top, Righc, Bo匸匸om: Inceger;
end;
Как видите, это структура из четырех параметров — левой, верхней, правой и нижней позиции. На рис. 11.11 стрелками показан тот размер, который будет в параметрах Left и Right структуры Reel. Размеры будут указаны в пикселах. В параметре Right будет указано расстояние в пикселах от левого края сетки до правого края ячейки, а в параметре Bottom будет расстояние от верхнего края сетки до нижнего кран ячейки. То есть мы получаем размеры относительно самой сетки, а не всей формы.
Ну, и последний параметр, передаваемый нам в процедуру-обработчик,— state. В нем находится информация о состоянии ячейки, которую надо прорисовать. Состояния могут быть следующими:
П gdSelected — ячейка выделена;
口 gdFocused — ячейка имеет фоьсус ввода;
口 gdFixed — ячейка является фиксированной.
Если в параметре state нет ни одного из этих значений, то это простая ячейка.
Параметр state объявлен как набор значений. Это значит, что он может принимать любое из указанных значений или их- сочетания. Чтобы проверить, установлено ли что-нибудь в state, надо написать:
if (значение in State) then выполнить действие
Именно так проверяется во второй строчке кода наличие значения gdFocused. И только если ячейка, которую надо прорисовать, имеет фов^с, выполняются следующие действия. Но перед этим мы прячем нашу маскированную строку ввода, потому что она могла находиться видимой в другой ячейке, и чтобы не было проблем, лучше ее спрятать.
Если рисуемая ячейка в фокусе, мы проверяем, в какой колонке находится рисуемая ячейка. Если это первая колонка (ще мы должны вводить дату), то мы показываем DateEdit на месте рисуемой ячейки. Для этого сначала присваиваем в DateEdit текст, который должен находиться в данной ячейке. Затем устанавливаем позицию и размеры компонента DateEdit, и только потом показываем его.
Как видите, способ очень простой и элегантный. Теперь осталось только создать обработчик события OnChange для компонента DateEdit. Это событие происходит, когда данные в строке ввода изменились, а это значит, что нам их надо сразу же прописать в редактируемую ячейку сетки, иначе они потеряются. Это потому что все данные вводятся в DateEdit, а не в сетку, а переносить мы их должны вручную:
procedure TMainForm. DateEditChange(Sender: TObject);
begin
StringGridl.Cells[StringGridl.Col, StringGridl.Row]:=DateEdit.Text;
end;
Чтобы еще больше закрепить материал этой части главы, давайте сделаем в последней колонке появление компонента TCheckBox, по которому можно мен兄те значение в ячейке между ,,Женат,* и "Холост”. Для эторго на форму надо установить компонент checkBox и сделать его невидимым. Потом надо изменить событие OnDrawCell, как это показано в листинге 11.4.
Листинг 11.4. Измененный обработчик события OnDrawCell
procedure TMainForm.StгingGridlDrawCe11(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
begin
DateEdit.Visible := false;
CheckBoxl.Visible := false; if (gdFocused in Stiane) then
10 Зак. 942
begin
if ACol=l then
begin
Dat eEdit. Text :=StringGridl. Cells [ACol, ARow];
DateEdit.Left := Rect.Left + StringGridl.Left+2;
DateEdit.Top : = Rect.Top + StringGridl.top+2;
DateEdit.Width := Rect.Right Rect.Left;
DateEdit.Height :二 Rect.BottomRec匸.Top;
DateEdit.Visible :二 true;
exit;
end;
if ACol=4 then,
begin
CheckBoxl・ Caption:=StringGridl.Cells[ACol, ARow];
if CheckBoxl.Caption=*Женат' then
CheckBoxl.Checked:=true
else
CheckBoxl.Checked:=false;
CheckBoxl.Left := Rect.Left + StringGridl.Left+2;
CheckBoxl.Top :二 Rect.Top + StringGridl.top+2;
CheckBoxl.Width := Rect.RightRect.Left;
CheckBoxl.Height := Rect.Bottom Rect.Top; CheckBoxl.Visible :二 true; exit;
end;
end;..
end;
Ну, и конечно же необходимо "поймать** событие onclick компонента CheckBoxl, чтобы записать измененное значение обратно в сетку. В этом обработчике напишите код листинга 11.5.
Листинг 11.5. Сохранение изменения в сьесквох
procedure TMainForm.CheckBoxlClick(Sender: TObject);
begin
if CheckBoxl.Checked=true then
CheckBoxl.Caption:二'Женат1
else
CheckBoxl.Caption:-'Холост•;
5tringGridl•Cells[StringGridl.Colf StringGridl.Row]:=CheckBoxl.Caption; end;
工 Примечание
На компакт-диске в папке \ Примеры \ Глава 11 \ Grid вы можете увидеть пример этой программы.
11.5. Компоненты-украшения
(Tlmage, TShape, TBeveB
Сейчас мы познакомимся с тремя компонентами, которые чаще всего ис- Г(0льзуются для придания приложениям красочной формы. Но это не значит, что выразительность — их основное назначение, просто на данном этапе будем рассматривать именно это их свойство. Чуть позже мы создадим что-нибудь более полезное из этих компонентов, но пока остановимся на модификации пользовательского интерфейса.
Создайте новый проект и установите на форму компонент т image. Теперь щелкните дважды левой кнопкой мыши по свойству picture, и перед вами появится уже знакомое окно загрузки изображения (рис. 11.12).
Теперь, если вы хотите, чтобы компонент автоматически принимал размеры загруженной картинки, то установите свойство AuroSize в true. Если требуется, чтобы картинка была по центру компонента, нужно выставить свойство center в true (при этом Auro Size надо выставить в false). Ну, а если надо растянуть картинку по всей поверхности компонента, свойство stretch надо установить в true (при этом Auro Size надо установить в false).
Если картинка должна быть , прозрачной, можно выставить свойство Transparent в true. Хотя такая прозрачность и не очень эффективна при использовании растровых картинок, но в случае особой необходимости это можно делать. Если вы будете использовать векторную графику, такую как формат WMF то прозрачность будет идеальной (рис. 11.13).
Остальные свойства Timage вам должны быть уже известны, поэтому на них мы останавливаться не будем.
Рис. 11.12. Окно загрузки изображения
Теперь разберемся с компонентом ТShape. Установите один такой экземпляр на форму и посмотрите на его свойства. Самое интересное здесь — свойство shape, которое отвечает за тип фигуры, отображаемой на компоненте. На рис. 11.13 показана форма программы, на которой расположено шесть разных видов компонента TShape. Справа от компонента подписано, какое именно значение установлено в свойстве shape.
Рис. 11.13. Различные виды компонента TShape
Помимо этого, за отображение отвечают еще и свойства Brush (закраска) и Реп (карандаш). Свойство Brush отвечает за цвет и стиль закраски нашей фигуры, а свойство Реп говорит о стиле и цвете обрамления.
Если дважды щелкнуть мышью по свойству Brush, то появится список из двух дополнительных свойств:
color — цвет заливки;
style — способ заливки.
На рис. 11.14 показаны различные типы заливки, которые вы можете установить, и результат их работы. Попробуйте сами поработать с этими параметрами, устанавливая различные значения цветов и способов заливки.
Рис. 11.14. Различные способы заливки
Когда вы будете изменять значения, то заметите, что изменяется только внутренняя окраска компонента, а обрамление будет оставаться в виде тонкой полоски черного цвета. За обрамление отвечает свойство Реп. Если щелкнуть по нему два раза, то перед вами онфоется список из четырех дополнительных свойств:
О color — цвет заливки;
О Mode — режим отображения;
style — стиль линии;
Width — толщина линии.
Здесь также попробуйте самостоятельно выставлять разные значения, чтобы увидеть результат их работы. На рис. 11.15 вы можете увидеть различные стили карандаша.
Ну, и, наконец, компонент TBevel, который предназначен для простого обведения -чего-либо рамкой. На первый взгляд этот компонент похож на тPanel, но это только на первый взгляд, потому что на ТBevel нельзя уста-
новить компоненты. Это практически прозрачная рамочка, но только на первый взгляд. Если вы поставите ее поверх строки ввода, то эта строка будет видна сквозь ТBevel, а вот доступ к ней получить будет невозможно.
『1 psClear
— WpsDash
卜■ WpD 己shDotpsDashD otDcft
卜 一 dsDoE
K_J psi nsi deFrame
^3p$Solid
Рис. 11.15. Стили карандаша
Самыми интересными свойствами у этого компонента являются shape и style. В разных сочетаниях значений в них можно добиться совершенно невероятных эффектов для рамок. На рис. 11.16 сделана попытка воспроизвести некоторые возможные варианты, но только некоторые. Узнать о рамке больше вы сможете, только если самостоятельно попробуете выставить какие-нибудь значения.
bsBoxbsBottomLine
bsFramebsRightLine
bsSpacerbsRightLine
bsFrame и свойство Stylr равно bsRaised
Рис. 11.16. Разные стили обрамлений
А на рисунке 11.13 было показано окно программы, в которой приводились примеры компонентов. Как видите, окошко выглядит очень даже прилично, за счет того, что по всей его площади растянут компонент ТBevel, который придает вид трехмерности. Многие программисты любят устанавливать на форму компонент ТBevel и растягивать его на всю форму (устанавливать свойство Align в alClient). Это очень сильно украшает окно и абсолютно не влияет на производительность или загруженность памяти.
; Примечание^^
На компакт-диске в папке \ Примеры \ Глава 11 \ Tlmage TShape TBevel вы можете увидеть пример этой программы.
Панель с полосами прокрутки
(TScrollBox)
Теперь я хочу рассказать про компонент TScrollBox. В заголовке этого раздела он назван как панель с полосами прокрутки. Это не совсем точный перевод названия компонента, но именно такое название отражает суть выполняемых компонентом действий.
Создайте новое приложение. Теперь установите компонент TScrollfeox на форму. Поместите на компонент ScrollBox картинку (тimage). Теперь загрузите в image 1 изображение большого размера, чтобы оно не помещалось в пределы экрана, и установите свойство Auto size в true. В этот момент компонент imagel должен увеличиться до реальных размеров картинки. Если он не будет помещаться в пределы ScroliBox, то появятся полосы прокрутки, как это показано на рис. 1L17, и вы сможете прокрутить изображение.
Рис. 11.17. Окно формы
Примечание
На компакт-диске в папке \ Примеры \ Глава 1 1 \ ScroliBox вы можете увидеть пример этой программы.
Маркированный список
(TCheckListBox)
ТCheckListBox очень похож на простой TListBox, только у каждого элемента списка есть еще и квадратик для выделения как у TCheckEox. На рис. 1.18 показан вариант организации компонента TCheckListBox.
П Женщины
□ Кино
П Компьютеры
П Программирование
□ Спорт
Рис. 11.18. Компонент TCheckListBox
Давайте создадим пример, который будет работать с этим компонентом. Создайте новое приложение в Delphi и поместите на него компонент ТCheckListBox. Теперь дважды щелкните левой кнопкой мыши по свойству items, и перед вами появится редактор элементов списка. Это простейший текстовый редактор, в котором каждая строка отображает отдельный элемент в компоненте ТCheckListBox. Введите там несколько строк на свой выбор.
У TCheckListBox есть еще одно интересное свойство — columns, т. е. количество колонок в списке. Если вы укажете здесь числр, большее 1, и ваш список не будет помещаться в одну колонку, то элементы будут разбиты на указанное количество колонок (рис. 11.19).
Рис. 11.19. Список, разбитый на несколько колонок
На рис. 11.19 вы можете видеть еще и кнопку ОК. Добавьте ее на свою форму. По нажатии этой кнопки мы будем проверять, какие элементы выделил пользователь, и сообщать об этом. Создайте обработчик события Onclick для кнопки и напишите там следующий код:
Листинг 11.6. ГЦ оверка выделенных элементов в TCheckListBox
procedure TForml.OKButtonClick(Sender: TObject); var
i:Integer;
Str:String;
begin
Str:二 1 Вы выбрали ';
for i:=0 to CheckListBoxl. Items.Count-1 do // Запуск цикла
if CheckListBoxl.Checked[i] then / /Если i-й элемент вьщелен, то
Str: =Str+CheckListBoxl. Items [i] + * *; // Добавить в строку Str текст
/ / элемента
Application.MessageBox(PChar(Str) , 1Внимание!!!1); // Вывести на экран // строку end;
Теперь можете запустить приложение и посмотреть на результат работы. Выделите несколько элементов и нажмите кнопку ОК. Вы должны увидеть окно, в котором перечислены все выделенные элементы.
Теперь разберемся, что же происходит по нажатии заветной кнопки ОК. Сначала объявляются две переменные — целое число i и строка str. В первой строке кода мы присваиваем строке str текст нВы выбрали**. После этого запускается цикл, в котором проверяются все элементы. Если i-й элемент выделен, то добавляем текст строки к переменной str.
Чтобы узнать, выделена ли какая-то строка, надо проверить свойство checked компонента CheckListBoxl. В квадратных скобках надо указать номер интересующей строки. Например, если вы хотите проверить нулевую строку, то надо написать: if CheckListBoxl.Checked[0] then
В нашем примере перебираются все элементы, поэтому в квадратных скобках указан параметр i. Текст строки можно узнать в свойстве items компонента CheckListBoxl. Чтобы узнать текст нулевой строки, надо написать: CheckListBoxl.Items[0]
工 Прмме“анме ;
На компакт-диске в папке \ Примеры \ Глава 11 \ CheckListBox вы можете увидеть пример этой программы.
Полоса разделения (TSplitter)
Запустите проводник Windows Explorer. Посмотрите на его главное окно, которое разбито на две части. Слева вы можете видеть список дисков и директорий, а справа находятся файлы из выбранной папки. Между двумя половинами окна находится полоска, которую можно двигать, увеличивая или
уменьшая одну из половин окна. Вот именно такой эффект легко создать с помощью компонента TSplitter.
У TSplitter не так уж и много свойств, поэтому мы не будем долго заострять на нем внимания, а просто рассмотрим пример работы с этим компонентом.
Создайте новое приложение. Теперь установите на форму компонент панели (тPanel) и растянем его по верхнему краю формы (установите у него свойство Align в al Тор). В свойстве Caption напишем "Верхняя панель**. Далее установим на форму TSplitter и у него тоже установим в свойстве Align значение alTop.
Еще одну панель выравняем по левому краю. В свойстве caption напишите нЛевая панель**. Бросим еще один TSplitter и тоже установим выравнивание по левому краю.
Установим еще одну панель с выравниванием по всей оставшейся площади формы (СВОЙСТВО Align ДОЛЖНО быть alClient). Ну, а В свойстве Caption напишем нКлиентская панель**.
Рис. 11.20. Форма будущей программы
Если вы все сделали правильно, то у вас должно получиться что-то похожее на рис. 11.20. Здесь три панели и между ними разделители TSplitter. Попробуйте запустить эту программу и двигать мышкой разделители. Размеры панелей будут меняться автоматически, что очень удобно для большинства программ. И при этом мы не написали ни одной строчки кода.
Примечание
На компакт-диске в папке \ Примеры \ Глава 11 \ Splitter вы можете увидеть пример этой программы.
Многострочный текст
(TStaticText)
Иногда бывает необходимость создать текст в нескольких строчках. Для этого можно установить на форму в столбик несколько компонентов TLabel. Можно поступить лучше — использовать компонент TStaticText. Если установить его на форму и отключить свойство Autosize, установив его в false, то компонент не будет автоматически принимать размеры введенного текста. Если введенный текст не вмещается, то он будет разбит на несколько строк.
По своим свойствам TStaticText это полная копия компонента TLabel. Разница только в возможности выводить много строчные поля текста.
Редактор параметров
(TValueListEditor)
В этой части мы рассмотрим компонент TValueListEditor. Это очень удобный компонент для редактирования различных свойств. Например, с помощью этого компонента можно легко создать редактор свойств наподобие того, что используется в объектном инспекторе, где вы изменяете свойства компонентов.
Создайте новый проект и поместите на форму один такой компонент. Рассмотрим его основные свойства.
DefaultColimnWidth — ШИрИНИ КОЛОНОК ПО УМОЛЧННИЮ.
De faultCo 1 iinnHei ght — ВЫСОТа КОЛОНОК ПО умолчанию.
Displayoption — опции отображения компонента. Здесь вам доступны три подпункта:
doColumnTitles — нужно ли показывать заголовки колонок;
doAutoColResize — могут ли колонки автоматически изменять размер;
doKeyCol Fixed — является ли ключевая колонка фиксированной.
О Title Captions — имена заголовков. Щелкните дважды по этому свойству, и вы увидите простой текстовый редактор, в котором можете изменять имена заголовков. Давайте введем там только два заголовка: Свойство и Установленное значение.
FixedColor — цвет фиксированной колонки.
FixedCols — индекс фиксирванной колонки. По умолчанию стоит 0, т. е. фиксированной колонки нет. Измените это значение на 1, чтобы сделать первую колонку фиксированной.
KeyOption — настройки ключевого поля. Их бесполезно менять, если вы установили в свойстве FixedCols какое-либо значение. Но здесь доступны следующие подпункты:
keyEdit — название ключа можно редактировать;
keyAdd — ключи можно добавлять;
keyDelete — КЛЮЧИ МОЖНО удалять;
keyUnique — ключ должен быть уникальным.
□ Strings — имена свойств (рис. 11.21). Этот редактор состоит из двух колонок. В первой колонке вы должны вводить имена ключей (свойств), а в правой их значения по умолчанию.
Давайте введем в свойство Strings несколько значений ДЛЯ нашего будущего примера: Фамилия, Имя, Отчество, Ник, Год рождения, Место рождения, Адрес, Телефон.
Рис. 11.21. Редактор строк
Больше в дизайнере пока нет ничего, особо заслуживающего нашего внимания. На рис. 11.22 показана форма, которая должна у вас получиться.
Списки свойств имеют одну очень удобную особенность — они могут поддерживать свойства с выпадаюшими списками. Это значит, что нужно указать, какое свойство будет иметь выпадающий список, заполнить его значения м после этого оно будет работать как свойство выравнивания любого компонента в объектном инспекторе.
Рис. 11.22. Форма будущей программы
Теперь давайте займемся программированием. Создайте обработчик события для формы onShow. В нем напишите код, показанный в листинге 11.7.
Листинг 11.7. Обработчик события OnShow для нашей формы
procedure TForml.FormShow(Sender: TObject);
begin
ValueListEditorl.ItemProps[6].EditStyle:=esPickList;
ValueLi stEditori.ItemProps[6].PickList.Add(1Москва');
ValueListEditorl.ItemProps[6].PickList.Add(*Питер');
ValueListEditorl.ItemProps[6].PickList.Add('Ростов-на-Дону■);
ValueListEditorl.ItemProps[4].EditMask:=*99/99/9999'; end;
У компонента ValueListEditor есть одно свойство, которое вы не видите в объектном инспекторе, — ItemProps. В нем хранятся свойства элементов списка. Если вы хотите изменить свойства третьего элемента, то надо написать ValueListEditorl. ItemProps [2]. Обратите внимание, как и в большинстве компонентов, здесь элементы нумеруются с нуля. Поэтому нужно указывать на 1 меньше необходимого.
Среди свойств элементов есть еше одно интересное свойство — Editstyle (стиль редактирования). Эго свойство отвечает за вид элемента. В первой строке кода мы изменяем стиль редактирования для седьмой строки ValueListEditorl. ItemProps [6] .EditStyle, присваивая ему значение espickList. Эго значение заставляет эту строку превратиться в выпадающий
список. После этого мы заполняем для седьмого элемента значения, которые будут находиться в выпадающем списке. В этом случае надо выполнить код:
ValueListEditorl. ItemProps [6] . PickList. Add (текст элемента);
Еще одно интересное свойство — EditMask (маска ввода для элемента). В последней строчке кода нашего примера мы изменяем маску для четвертого элемента.
Примечание~~
На компакт-диске в папке \ Примеры \Глава 11 \ValueListEditor вы можете увидеть пример этой программы.
11.11. Набор вкладок
(TTabControl)
Иногда на форме надо иметь возможность показывать несколько вкладок, на каждой из которых будут располагаться разные компоненты. Такое очень часто можно увидеть в окнах настройки программ, например в MS Word. В зависимости от выбранной в данный момент вкладки в основном окне отображаются относящиеся к ней свойства.
В Delphi, во вкладке Win32 палитры компонентов есть два компонента, позволяющие создавать подобные элементы управления. Сейчас мы рассмотрим первый из них — TTabControl.
Запустите Delphi и создайте новое приложение. Разместите на форме компонент TTabControl и растяните его на всю форму. Давайте сразу же изменим имя компонента (свойство Name) на options Tab.
Теперь пора создать сами вкладки. Для этого дважды щелкните по свойству Tabs, и перед вами появится уже знакомое окно текстового редактора. N^»i уже много раз работали с таким окном в других компонентах, поэтому оно не должно вызвать проблем.
В этом окне введем четыре строки:
**Основные настройки*;
**Параметры пользователя**;
口 **Загрузка и восстановление”;
П “Настройки печати**.
После нажатия кнопки ОК на компоненте должны появиться вкладки с введенными названиями (рис. 11.23).
Обратите внимание, что мы ввели четыре названия вкладок, а на рис. 11.23 видно только три. Четвертая вкладка не поместилась, поэтому
справа от имен автоматически появились две кнопки для скроллинга названий вкладок.
Рис. 11.23. Компонент TTabControl с вкладками
Давайте установим свойство MultiLine в true, что позволит создавать многострочные вкладьси. Результат этого вы можете увидеть на рис. 11.24. Как видите, кнопки скроллинга исчезли, зато названия выстроены в две строчки. Что вам больше подходит — зависит от конкретного предназначения компонента. Иногда нужны многострочные вкладки, а иногда они просто мешают. Но если вкладок слишком много, и очень много не помещается на зкран, то желательно использовать многострочный вариант, потому что скроллинг может утомить пользователя.
Рис. 11.24. Компонент TabControl с установленным свойством Multi Line
Давайте еще установим свойство HotTrack в true. Это заставит названия вкладок светиться при наведении на них курсора мыши (вы это увидите, если запустите приложение).
У компонента TabControl есть еще одно интересное свойство — style. Эго свойство отвечает за стиль отображения вкладок. Здесь можно выбрать из списка одно из следующих значений:
tsTabs — пример таких вкладок можно увидеть на рис. 11.24;
tsButtons — пример таких вкладок показан на рис. 11.25;
tsFlatButtons — пример таких вкладок показан на рис. 11.26.
Рис. 11.25. Вкладки в стиле tsButtons
Рис. 11.26. Вкладки в стиле tsFiatButtons
В нашем примере мы не будем менять это свойство и оставим значение по умолчанию.
Здесь показаны возможные варианты только для того, чтобы вы знали об их существовании и могли использовать в своих приложениях. В дальнейшем мы будем использовать вкладки стандартного вида.
Перед тем, как создать реальное приложение, давайте рассмотрим еще три интересных свойства — TabHeight, Tabindex И TabPosition.
口 Tab Height — высота кнопок вкладок. Если здесь указать 0, то будет использоваться значение по умолчанию.
口 Tabindex — индекс выделенной в данный момент времени вкладки. Номера вкладок нумеруются как всегда с нуля, поэтому в нашем случае можно выставлять значения от 0 до 3. Изменяя значение, установленное здесь, вы можете менять выделенную вкладку. Ну, а когда приложение запущено, по этому свойству можно определять, какую вкладку выбрал сейчас пользователь.
□ TabPosition — позиция вкладок. Здесь можно выбрать из списка одно из следующих значений:
tpBottom — вкладки должны быть расположены снизу;
tpLeft — вкладки должны быть расположены слева;
tpRight — вкладки должны быть расположены справа;
tpTop — вкладки должны быть расположены сверху.
По умолчанию здесь установлено значение tpTop.
Теперь попробуем создать приложение. Попробуйте установить в рабочее поле любой из вкладок какой-нибудь компонент и запустить приложение или просто изменить индекс выделенной вкладки. Если вы сделаете это, то заметите одно неудобство — компонент не привязывается к какой-нибудь вкладке. Когда вы выбираете любую вкладку, установленные внутри компоненты всегда остаются видимыми. Хорошо, когда необходимо, чтобы все вкладки имели один и тот же вид, а если они должны отличатся? Значит, нам самим нужно прятать или показывать компоненты, в зависимости от выбранной в данный момент вкладки. Попробуем написать пример, в котором рассмотрим простейший способ избавиться от этого недостатка.
Поместите на форму четыре панели и постарайтесь их расположить рядом, примерно так, как это показано на рис. 1 L27. Это нужно, чтобы ни одна панель случайно не попала поверх другой. Все они должны лежать на компоненте option Tab (это TTabControl). Посмотрите на окно Object TreeView (рис. 11.28). В нем показана иерархия компонентов, что и на чем лежит.
Измените у всех панелей свойство BevelOuter на bvLowered, это сделает панели более приятными на вид. И еще, у компонентов Panel2, Рапе13 и Рапе14 установите свойство visible в true. Видимой должна остаться только первая панель.
Теперь очистите у всех свойство caption и растяните на всю форму. При растягивании компонентов можно поступить несколькими способами.
□ Выбрать каждый компонент в отдельности в окне Object TreeVlew (или в выпадающем списке сверху окна объектного инспектора) и установить у него свойство Align В alClient,
口 Вьщелить все панели (удерживая клавишу <Shift>, щелкнуть по всем панелям левой кнопкой мыши) и потом у всех сразу выставить свойство Align В al Client.
Рис. 11.27. Форма с четырьмя панелями
Рис. 11.28. Иерархия компонентов
Сейчас у нас все панели находятся точно Друг под другом, и только одна из них будет видима. Давайте бросим на каждую из панелей надпись. Если у вас сейчас наверху находиться не первая панель, то щелкните правой кнопкой и выберите из появившегося меню пункт Control | Send to Back. Повторяйте эти действия, пока наверху не окажется первая панель. Установите на эту панель надпись "Здесь можно располагать компоненты для основной настройки*.
После этого можете вьщелить следующую панель и установите на нее любые другие компоненты. Желательно таким образом заполнить все панели, установив на них хотя бы по одному компоненту, чтобы можно было увидеть, как они будут меняться.
Теперь создайте обработчик события onchange для компонента optionTab, и в нем напишите содержимое листинга 11.8. Этот обработчик будет вызы- ватся каждый раз, когда пользователь меняет вкладку.
ЛИСТИНГ 11.8. ООрлб ■•■■ События OnChange
procedure TForml.OptionsTabChange(Sender: TObject); begin
Panell.Visible:=false;
Panel2.Visible:=false;
Panel3.Visible:=false;
Panel4.Visible:=false;
case OptionsTab.Tabindex of
0: Panell.Visible:=true;
Panel2.Visible:=ttue;
Pane13.Visible:=true;
Panel4.Visible:=true;
end;
end;
В самом начале мы делаем невидимыми все панели, а потом проверяем, какая именно вкладка вьщелена с помощью оператора case, и в зависимости от этого делаем вьщеленной конкретную панель. Например, если вьщелена вторая вкладка (в OptionsTab.Tablndex находиться 1), то видимой станет Рапе 12.
Вот таким простым способом можно присваивать различным вкладкам разные компоненты.
(Совет )
Если нужно, чтобы абсолютно все вкладки были разными, то желательно воспользоваться компонентом TPageControl, который будет рассмотрен ниже. Компонент TTabControl удобнее, когда все или некоторые вкладки имеют схожий вид.
工 ПримечаннГ二3
На компакт-диске в папке \ Примеры \ Глава 11 \ TabControl вы можете увидеть пример этой программы.
Набор страниц
(TPageControl)
В предыдущем разделе мы познакомились с компонентом TTabControl. Он достаточно хорош, но работать с ним очень неудобно, потому что постоянно приходится самому следить, какая сейчас выделена вкладка, и в зависимости от этого отображать нужные компоненты. Всех этих недостатков лишен компонент TPageControl, который так же находится на вкладке Win32 палитры компонентов.
Компонент TPageControl обладает практически всеми свойствами TTabControi, плюс несколько дополнительных. Давайте посмотрим на него в работе.
Создайте новое приложение и поместите на форму компонент TPageControl. В этот раз мы не станем менять его имя и оставим^значение по умолчанию PageControll. Единственное, что желательно сделать — растянуть его на всю форму.
Щелкните правой кнопкой мыши по компоненту, и перед вами откроется меню управления компонентом. В верхней части меню находиться 4 пункта, с помощью которых можно управлять страницами:
О New Page — создать новую страницу (вкладку);
О Next Page — перейти на следующую страницу (вкладку);
Previous Page — перейти на предыдущую странипу (вкладку);
Delete Page — удалить выделенную страницу (вкладку).
Создайте новую страницу. Теперь давайте посмотрим в объектный инспектор (рис. 11.29). Обратите внимание, что сверху, в выпадающем списке сейчас показывается выделенный компонент TabSheetl типа TTabSheet — это созданная нами страница. Получается, что когда мы создаем новую страницу, то как 6bl создаем отдельный компонент внутри компонента TPageControl. Именно поэтому TPageControl лишен недостатков компонента TTabControi. Каждая его страница — это отдельный объект внутри целого компонента. Если в прошлый раз нам самим приходилось делать что-то подобное с помощью панелей, то тут это делается автоматически.
У каждой страницы есть свойство caption, в котором можно написать заголовок страницы. Помимо этого, есть свойство Imageindex, в котором можно выбирать картинку, как это делалось при создании меню. В этом случае нужно установить на форму компонент Tlmag^List и загрузить в него картинки. После этого достаточно выбрать компонент PageControl и указать в его свойстве Images компонент ImageList. Теперь В списке Imageindex у страниц появятся картинки, и вы сможете их выбирать. Попробуйте весь процесс подключения картинок проделать самостоятельно.
I Object Inspector
(labSheetl
Properties Events j
BorderWidth1 Caption [□Constraints ]CursorI DragModej Enabled!□ Fontj" Height HelpContexti HelpKeywoidHelpType! Highlightedj HintI Imageindex;Left Name■AH shownBorderWidth1 Caption [□Constraints ]CursorI DragModej Enabled!□ Fontj" Height HelpContexti HelpKeywoidHelpType! Highlightedj HintI Imageindex;Left Name■AH shownin 一TabSheetl(T SizeConstraints) crDefeut jdmManualin 一TabSheetl(T SizeConstraints) crDefeut jdmManualTrue *(TFont) j149 2i htContext FalseTrue *(TFont) j149 2i htContext False?0.....;4TabSheed?0.....;4TabSheed
BorderWidth
1 Caption [□Constraints ]Cursor
I DragMode
j Enabled
!□ Font
j" Height HelpContext
i HelpKeywoidHelpType
! Highlighted
j Hint
I Imageindex
;Left Name
■AH shown
BorderWidth
1 Caption [□Constraints ]Cursor
I DragMode
j Enabled
!□ Font
j" Height HelpContext
i HelpKeywoidHelpType
! Highlighted
j Hint
I Imageindex
;Left Name
■AH shown
in 一
TabSheetl
(T SizeConstraints) crDefeut jdmManual
in 一
TabSheetl
(T SizeConstraints) crDefeut jdmManual
True *(TFont) j149 2
i htContext False
True *(TFont) j149 2
i htContext False
?0.....
;4
TabSheed
?0.....
;4
TabSheed
Рис. 11.29. Свойства страницы
Давайте создадим четыре вкладки со следующими именами: 口 Основные настройки;
Параметры пользователя;
П Загрузка и восстановление;
П Настройки печати.
Рис. 11.30. Форма еще одной нашей будущей программыРис. 11.30. Форма еще одной нашей будущей программы
Рис. 11.30. Форма еще одной нашей будущей программы
Рис. 11.30. Форма еще одной нашей будущей программы
На рис. 11.30 показана форма нашей будущей профаммы. Попробуйте создать нечто подобное. Практически все, что необходимо для этого знать, мы уже
проходили. В данном примере на каждую вкладку установлено несколько компонентов, чтобы можно было видеть, как меняются страницы. Они ничего не делают, а служат просто оформлением для большей наглядности.
Примечание
На компакт-диске в папке \ Примеры \ Глава 1 1 \ PageControl вы можете увидеть пример этой программы.
Набор картинок (TlmageList)
Наборы картинок используются для удобного хранения изображений. Мы уже не раз использовали такие наборы (TlmageList) в примерах. Здесь мы рассмотрим только некоторые особенности их применения, которые могут понадобиться при разработке приложений. Никакие примеры в этом разделе приводиться не будут, потому что мы уже использовали основные функции. Теперь только рассмотрим небольшие пояснения.
В свойствах Height и width находятся ширина и высота хранящихся картинок. Когда вы добавляете новую, то она обязательно приводится к указанным размерам.
Вы можете программно доставать любую картинку из массива с помощью метода Get Bitmap. У этого метода есть два параметра:
индекс картинки, которую надо получить;
объект типа TBitmap, куца запишется результирующая картинка.
Например, чтобы получить четвертую картинку из массива, нужно написать так: var
bitmap: TBitmap;
begin
ImageListl .GetBitmap(3r bitmap);
end;
Внимание
Обратите внимание, что для выбора четвертой картинки надо указать цифру 3, потому что картинки, как и большинство массивов, нумеруются с 0.
Ползунки (ТТгаскВаг)
Ползунки (так их ласково называют) ТТгаскВаг чаще всего используются, когда надо дать пользователю выбрать какое-то значение из определенного
диапазона. Например, вы наверно не раз пользовались архиваторами, так вот там степень сжатия устанавливается таким ползунком. Хотите еще пример? Вспомните, как выглядит регулятор громкости звука в любой из программ. Чаще всего это опять же будут ползунки. Просто они выглядят по- разному, но принцип действия одинаков.
Самый простейший ползунок выглядит, как на рис. 11.31.
У ползунка есть ряд свойств. Рассмотрим их.
□ Frequency — этот параметр показывает, как часто надо рисовать риски значений. Допустим, что у вас ползунок может принимать значения от О до 10. Если указать в этом свойстве 2, то будут нарисованы только 5 рисок (рисуется каждая вторая риска), если указать 3, то будет рисоваться каждая третья риска.
О мах — максимальное значение ползунка.
П Min — минимальное значение ползунка.
П Orientation — вид ползунка. В этом свойстве выбор значений производится с помощью выпадающего списка, в котором можно выбрать одно из двух:
trHorizontal — ползунок горизонтальный;
trVertical — ползунок вертикальный.
口 Position — текущая позиция ползунка.
П Seistart — в ползунке может быть выделено определенное число значений, и это свойство указывает на начало выделения.
П SelEnd — конец выделения.
П SliderVisible — должен ли быть виден бегунок.
П TickMarks — указание, где рисовать риски. Здесь доступны следующие значения:
tmBottomRight — СНИЗу;
tniBoth — снизу и сверху;
tmTopLeft — сверху.
П Tickstyle — стиль рисок. Здесь доступны следующие значения:
tsAuto — риски рисуются автоматически;
tsManual — рисуется только начальная и конечная риска;
tsNone — риски вообще не рисуются.
Попробуйте сами изменять эти свойства, и вы увидите результат их действия. Теперь мы готовы написать простенький пример. Для этого надо создать три ползунка разной формы и установить на форму три компонента TLabel С именами Labell, Label2 И Label3 (см. рис. 11.31),
Рис. 11.31. Форма будущей программы
Теперь необходимо создать обработчик события OnChange для первого ползунка и написать там следующий код:
procedure TForml.ТгаскВаг1Change(Sender: TObject);
begin
Labell•Caption:=IntToStr(TrackBarl.Position); end;
Здесь преобразуется текущая позиция ползунка (TrackBarl. Position) в строку (потому что позиция имеет тип целого числа) с помощью функции IntTostr. Результат запоминается в компонент Labell. Таким образом, после изменения позиции ползунка мы сразу отображаем текущую позицию.
Подобный код написан и для остальных ползунков. Попробуйте написать его сами или посмотрите его в исходном коде.
Примечание ~、
На компакт-диске в папке \ Примеры \ Глава 11 \ TrackBar вы можете увидеть пример этой программы.
Индикация состояния процесса
(TProgressBar) ,
Компонент TProgressBar (индикатор состояния процесса) нашел довольно широкое применение в современных приложениях. Вспомните любое окно копирования файлов или любых других данных. Практически в любом таком окне есть бегунок, который показывает, сколько процентов сейчас выполнено. Такой бегунок выполнен на основе компонента TProgressBar.
У этого компонента есть три необходимых свойства:
мах — максимальное значение (по умолчанию = 100);
Min — минимальное значение (по умолчанию = 0);
О Position — ПОЗИЦИЯ.
Давайте рассмотрим пару примеров. Допустим, что вам нужно вычислить в цикле 100 чисел. В этом случае очень удобно поставить на форму компонент TProgressBar и отображать в нем текущее вычисляемое значение. Давайте рассмотрим общий пример такого случая на реальном примере.
Поместите на форму одну кнопку и компонент TProgressBar. Теперь для события, связанного с нажатием кнопки, напишите содержимое листинга 11.9.
Листинг 11.9* Отображение хода расчета
procedure TForml.ButtonlClick(Sender: TObject);
var
i;Integer;
begin
for i:=0 to 20 do
begin
//Здесь мсжно делать какой-то расчет
//После расчета отображаем текущее состояние
ProgressBarl.Position:=ProgressBarl.Position+5;
Sleep (100); //Делаем задержку в 100 миллисекунд end;
ProgressBarl.Position:=0;
end;
В данном случае запускается цикл от 0 до 20. На каждом этапе цикла позиция progressBar увеличивается на 5 и на 20-м шаге выполнения пикла будет равна своему максимальному значению — 100. В цикле также устанавливается задержка на 100 миллисекунд, чтобы наша полоска не проскочила слишком быстро, и вы хотя бы увидели иллюзию расчета. В реальных примерах задержка не будет нужна, ведь если расчет выполняется так быстро, что пользователь даже не увидит движение ползунка, то нет смысла создавать ProgressBar.
Приведенный пример не совсем универсальный, потому что требует, чтобы мы заранее знали приращение пяти единиц на каждом шаге. В противном случае есть два варианта решения.
口 Изменить свойство мах компонента ProgressBar на 20 и на каждом шаге приращивать только единицу. Это очень хороший способ, потому что позиция ProgressBar будет изменяться от 0 до 20. Цикл тоже действует в этом диапазоне, так что пример может упроститься и выглядеть так, как показано в листинге 11.10.
Ли- • - 11-10. Упрощенна м рас■:,
procedure TForml.ButtonlClick(Sender: TObject); var
i:Integer;
begin
for i:=0 to 20 do begin
/ /Здесь можно делать какой-то расчет
//После расчета отображаем текущее состояние
ProgressBari.Position:=i;
Sleep (100) ; /@елаю задержку в 100 миллисекунд end;
ProgressBarl.Position:=0;
end;
В данном случае нам не надо делать приращение для ProgressBar, а достаточно сразу присваивать в свойство позиции значение i, потому что значение позиции и значение i изменяются в одном диапазоне от 0 до 20.
□ Второй способ заключается в расчете относительного положения состояния ProgressBar. В этом случае позиция ProgressBar должна изменяться от 0 до 100 и нужно воспринимать эти значения как проценты. После этого рассчитывать процент выполнения на каждом шаге цикла. Не пугайтесь, этот расчет очень прост и незатруднителен для машины. Иногда такой способ удобнее (листинг 11.11).
хода расчета в 平叫加 и
procedure TForml.ButtonlClick(Sender: TObject);
var
i:Integer;
begin
for i:=0 to 20 do
begin
/ /Здесь можно делать какой-то расчет
//После расчета отображаем текущее состояние
ProgressBarl.Position:=round(i/20*100);
Sleep (100) ; //Делаем задержку в 100 миллисекунд
end;
ProgressBarl.Position:=0;
end;
В этом примере позиция рассчитывается по формуле преобразования чисел в проценты, т. е. текущее значение делится на максимальное и умножается на 100 (в нашем случае 20// и умножить на 100). Результат этих вычислений нужно округлить, потому что среди операций расчета было деление, значит, общий результат будет в любом случае восприниматься как дробное число, даже если результат явно должен быть целым.
В последнем случае есть небольшая погрешность из-за операций деления и округления, но в реальных условиях заметить эту погрешность невозможно, потому что она равна менее половины процента. Все равно при выводе графики всегда бывают погрешности из-за масштабирования.
工 Примечание ]
На компакт-диске в папке \ Примеры \ Глава 11 \ ProgressBar вы можете увидеть пример этой программы.
Простейшая анимация (TAnimate)
Очень часто в программах надо создавать какую-нибудь анимацию, чтобы пользователь не сильно скучал, пока проходят какие - ни будь долгие расчеты. Анимация позволяет показать пользователю, что компьютер еще не завис и работает. Например, когда программа копирует большой файл, то желательно вывести какой- ни будь мультик, ото бражающий копирование. В Delphi создание такой анимации — самое простое дело.
Компонент TAnimate умеет выводить на экран указанную в свойстве FileName анимацию. Дважды щелкните по этому свойству, и перед вами откроется окно опсрытия AVI-файла. AVI —это стандартный формат видеофайлов в Windows. Только не надо думать, что любой такой файл сможет бьп?ь проигран на любой машине только из-за того, что он стандартный.
На самом деле AVI — это очень сложный формат, потому что в нем может быть сформировано видео любого типа. Формат AVI — это только оболочка, а содержимое может храниться в любом виде. Например, кадры видеофайла могут храниться без сжатия, с простым сжатием — RLE или со сложным сжатием — MPEG4. Для воспроизведения файла, хранящего данные в нестандартном виде, используются специальные программы — кодеки, которые должны бьп?ь установлены в системе. Таким образом, если вы хотите бьп?ь уверены в том, что файл воспроизведется на любой машине, можете поступать следующими способами.
□ Использовать некодированное хранение данных или стандартное X^ndows- кодирование. В этом случае файлы будут достаточно большими, зато воспроизведутся на любой машине.
П Использовать кодек, поддерживаемый какой-нибудь версией MediaPlayer, а потом только указать, что для нормальной работы программы нужно иметь установленный MediaPlayer определенной версии или выше.
П Вместе с программой поставлять и кодек. В этом случае нужно копировать на машину клиента не только свою программу, но и файлы кодека. Кодеки надо не только скопировать, но и установить в системе, чтобы ОС потом смогла их найти при попытке воспроизведения вашего файла.
Но не все так страшно. Для стандартных операций уже предусмотрены стандартные видеоролики. Их список можно найти в свойстве CommonAVi Все эти ролики уже установлены в M^ndows, и их не надо копировать на другую машину вместе с программой. В этом случае можно быть уверенным, что они воспроизведутся где угодно. Вот список доступных роликов:
П aviCopyFile — ролик копирования файла;
П aviCopyFiles — ролик копирования нескольких файлов;
П aviDeleteFile ——рОЛИК удаления файла;
〃 aviEmptyRecycle — рОЛИК ОЧИСТКИ корзины;
П avi FindCompute г — рОЛИК ПОИСКа КОМТЬЮТера;
П aviFindFile — рОЛИК ПОИСКа файла;(
П aviFindFolder — РОЛИК ПОИСКА Директории;
П aviRecycleFile — ОТПравка файла В КОрЗИНу;
П aviNone — не использовать стандартных роликов.
Как только вы выбрали AVI-файл в свойстве FileName или указали стандартный ролик, можно установить свойство Active в true и видео сразу же начнет воспроизводиться в окне (рис. 11.32).
Рис. 11.32. Пример вое叩онзведення ролика копирования файлов
Выпадающий список выбора даты (TDateTimePicker)
На первый взгляд это простейший выпадающий список (тсошЬоВох), но на самом деле вместо выпадающего списка тут выпадает календарь. Получается очень удобная строка ввода с выпадающим списком в виде календаря. На рис. 11.33 вы можете увидеть выпадающий список выбора даты в действии.
Рис. 11.33. Выпадающий, список выбора даты в действии
У этого компонента, как уже было отмечено, большинство свойств похожи на свойства компонента тсотЬовох, но есть и свои отличия. Рассмотрим их.
口 Date — это свойство указывает на выбранную дату.
□ DateFormat — формат даты. Здесь возможны только два значения:
df short — короткий формат; *
df Long — ДЛИННЫЙ формат.
О MaxDate — максимальная дата.
П MinDate — минимальная дата.
Календарь (TMonthCalendar)
Предыдущий компонент позволяет выбирать в виде выпадающего списка дату. А что если вам нужно просто показать календарь? Вот именно для этого и существует TMonthCalendar. Он обладает следующими свойствами:
П FirstDayOfWeek — день недели, указываемый в качестве первого;
П Date — это свойство указывает на выбранную дату;
口 MaxDate — максимальная дата;
П МinDate — минимальная дата;
П MultiSelect — есть ли возможность выбирать диапазон чисел месяца;
О showToday — показывать текущую дату;
П ShowTodayCircle — показывать круг текущей даты (по щелчку в области этого круга календарь перескакивает на текущую дату);
П WeekNunibers — показывать номера недель.
Примера здесь мы не будем рассматривать, потому что он слишком прост, а календарь еще будет часто использоваться в примерах, которые рассматриваются далее по ходу книги.
Дерево элементов (TTreeView)
Сейчас нам предстоит познакомиться с достаточно сложным, но мощным компонентом — деревом элементов (Treeview). Любая более-менее большая программа обязательно использует этот компонент, потому что он очень удобен для отображения древовидных данных.
Давайте. сразу напишем пример и познакомимся с деревом на практике. Компонент Treeview достаточно сложный и с ним нужно знакомиться на реальном примере, чтобы увидеть все прелести работы с ним.
Создайте новый проект н поместите на него два компонента: Treeview н imageList. В список к叩тинок imageList надо поместить пару любых изображений, потом они пригодятся. А пока добавьте еще три* кнопки (TButton):
О кнопка Добавить (в свойстве Name укажите AddButton);
П кнопка Добавить элемент (в свойстве Name укажите AddChildButton);
П кнопка Удалить (в свойстве Name укажите DelButton);
П кнопка Изменить заголовок (в свойстве Name укажите EditButton).
В результате у вас должна быть форма приблизительно такого вида, как показано на рис. 11.34.
Теперь нужно указать у нашего дерева в свойстве images установленный набор картинок. После этого можно переходить к программированию.
Рис. 11.34. Форма будущей программы
По нажатии кнопки Добавить мы должны добавлять в дерево новый элемент. Для этого будем использовать код, представленный в листинге 11.12.
I Листинг 11.12. /Добавление элемента в TreeView
procedure TTreeViewFona.AddButtonClick (Sender: TObject);
var
CaptionStr:String;
NewNode:TTreeNode;
begin
CaptionStr: = * *;
if not InputQuery (1 Ввод имени1, введите заголовок элемента* f CaptionStr) then exit;
NewNode: =TreeViewl. I terns. Add (TreeViewl. Selected, CaptionStr);
if NewNode. ParentOnil then
NewNode. Iinagelndex: =1;
end;
Здесь мы объявили две переменные:
CaptionStr - типа строка string;
NewNode - типа TTreeNode (тип TTreeNode - это тип отдельного элемента дерева).
В первой строчке кода мы обнуляем строку captionstr. Эта строка в будущем будет использоваться для хранения имени нового элемента дерева.
Вторая строка имеет следующий код:
if not InputQuery (' Ввод имени*, введите заголовок элемента', CaPtlonStr) then
exit;
Здесь выполняется фуньщия InputQuery, которая используется для вывода на экран окна ввода. У этой функции есть три параметра.
П Заголовок окна ввода.
口 Текст-пояснение, который подсказывает пользователю, что ему надо вводить.
口 Строковая переменная, в которой мы передаем значение по умолчанию и получаем результат ввода. Если перед вызовом записать в эту переменную какое-нибудь значение, то оно будет использоваться в качестве значения по умолчанию. Но после вызова этой функции этот параметр всегда хранит реально введенное пользователем значение.
На рис. 11.35 вы можете увидеть это окно ввода.
Рис. 11.35. Окно ввода
Если окно было закрыто не бснопкой ОК, то происходит выход из процедуры. Об этом говорит фрагмент кода:
if not InputQuery(...) then
exit;
Следующая строка кода добавляет новый элемент в наше дерево:
NewNode: =TreeViewl.Items.Add(TreeViewl.Selected, CaptionStr);
У компонента TreeViewl есть свойство items, в котором хранятся все элементы дерева. Это свойство имеет объектный тип TTreeNodes. Чтобы добавить туда новый элемент, нужно вызвать метод Add объекта items. Получается, что в объекте TreeViewl есть еще один объект — items, в котором хранятся все элементы. Мы уже сталкивались с такими случаями, когда внутри одного объекта был другой объект.
У метода Add есть два параметра:
элемент, к которому надо добавить новый (здесь мы передаем выделенный элемент TreeViewl. Selected);
заголовок нового элемента.
Результат выполнения этого метода — указатель на новый элемент. Этот результат мы сохраняем в переменной NewNode. Теперь можно изменять и другие значения этого элемента. Например, как в следующем коде изменяется картинка:
if NewNode. ParentOnil then
NewNode.ImageIndex:=1;
Здесь идет проверка, если свойство Parent нашего дерева не равно нулю (т. е. компонент не является верхним в дереве), то изменить значение Iraagelndex созданного нами элемента на 1 (по умолчанию это значение 0).
Для события, вызываемого нажатием кнопки Добавить элемент, напишем код, показанный в листинге 11.13.
Листинг 11.13. Добавление элемента
var
CaptionStr:String;
NewNode:TTreeNode;
begin
CaptionStr: = J;
if not InputQuery {* Ввод имени подэлемента1,
Введите заголовок подэлемента1, CaptionStr) then exit;
NewNode: =TreeViewl. Items.AddChild (TreeViewl. Selected, CaptionStr);
if NewNode. ParentOnil then
NewNode.ImageIndex:=1;
end;
Здесь код практически тот же, что и для кнопки Добавить. Единственная разница в том, что при добавлении нового элемента используется метод AddChild. Отличие этого метода от просто Add заключается в том, что он добавляет дочерний элемент. Например, если вы выделили в списке какой-то элемент и передали его в качестве первого параметра в AddChild, то новый элемент будет как бы подчиняться выделенному. При использовании метода Add новый элемент будет находиться на одном уровне дерева
11 Зак 942
с переданным в качестве первого параметра. Теперь напишем код для кнопки Добавить:
if TreeViewl. SelectedOnil then
TreeViewl. Items. Delete (TreeViewl. Selected);
Здесь нужно удалить выделенный элемент, поэтому сначала мы проверяем, есть ли вообще выделенный элемент в дереве:
if TreeViewl. SelectedOnil then
Если такой элемент есть, то выполнится следующий код:
TreeViewl. Items. Delete (TreeViewl. Selected);
Здесь используется метод Delete объекта items, чтобы удалить элемент дерева. В качестве параметра надо передать элемент, который мы хотим удалить (передаем выделенный TreeViewl. Selected).
Для кнопки Изменить заголовок мы напишем код листинга 11.14.
Лиигмнг 1 ■ 54. Изменение заголо ка
procedure TTreeViewForm.EditButtonClick(Sender: TObject);
var
CaptionStr:String;
begin
CaptionStr: = * *;
if not Input Query (* Ввод имени •,
* Введите заголовок элемента*, CaptionStr) then exit;
TreeViewl.Selected.Text:=CaptionStr;
end;
Здесь снова вызывается окно InputQuery, чтобы пользователь смог ввести новое имя для выделенного элемента. Теперь, чтобы изменить имя, надо изменить СВОЙСТВО Text ДЛЯ выделенного Элемента: TreeViewl. Selected.Text.
Давайте создадим возможность сохранения и зафузки данных в наше дерево. Для этого создайте обработчик события OnClose и напишите в нем следующее:
procedure TTreeViewForm. FormClose (Sender: TObject;
var Action: TCloseAction);
begin
TreeViewl. SaveToFile (Ex tract FilePath (Application. ExeName) +' tree, dat1); end;
Для сохранения дерева нужно вызвать метод saveToFile и в качестве единственного параметра указать имя файла. В качестве имени файла можно указывать что угодно, но здесь используется (и это желательно делать) полный путь, чтобы файл случайно не создался в каком-нибудь другом месте. Для этого надо использовать следующую конструкцию:
ExtractFilePath(Application.ExeName)+'tree.dat'
Application.ExeName — указывает на имя запущенного файла.
ExtractFilePath — извлекает путь к файлу из указанного в качестве параметра ПУТИ К файлу. Получается, ЧТО ВЫЗОВ ExtractFilePath (Application.ExeName) вернет путь к директории, откуда была запущена программа. Остается только к этому пути прибавить имя файла (у нас это ,tree dat『)к можно быть уверенным, что файл обязательно будет находиться там же, где и запускаемый файл.
Теперь нужно загрузить сохраненные данные. Для этого по событию Onshow напишем следующий код:
procedure TTreeViewForm.FormShow(Sender: TObject);
begin
if FileExists(ExtractFilePath(Application.ExeName)+*tree.dat*) then.
Tree-
Viewl.LoadFromFile(ExtractFi1ePath(Application.ExeName) + * tree.dat1);
end;
Здесь сначала проверяется с помощью вызова функции FileExists существование файла. Этой функции нужно передать полное имя файла и если он существует, то функция вернет true, иначе false.
Если файл существует, то можно его загрузить с помощью вызова метода LoadFromFile.
Обязательно проверяйте файл на существование. Если его нет или кто-то его удалил, а вы попытаетесь загрузить данные из несуществующего файла, произойдет ошибка.
Попробуйте запустить пример. Если создать несколько элементов и потом перезапустить программу, то вы сразу же сможете заметить, что старые дочерние элементы имеют один рисунок, а вновь созданные другой. Почему так получилось? Ведь мы же всегда меняем рисунок, если это дочерний элемент? Просто после закрытия программы дерево сохраняется в файле, а после загрузки оно загружается без учета изображений. Состояние картинок не сохраняется, и об этом нам нужно заботиться самостоятельно. Но это уже отдельная история, о которой мы поговорим в другой раз. В следующем примере мы избавимся от этого неприятного эффекта.
工Пр 恥 менанме〉
На компакт-диске в папке \ Примеры \ Глава 11\ TreeView вы можете увидеть пример этой программы.
Итак, чтобы восстановить состояние картинок после загрузки данных из файла, надо скорректировать обработчик события onShow следующим образом:
procedure TTreeViewForm. FormShow (Sender: TObject);
var
i:Integer;
begin
if FileExists (ExtractFilePath(Application.ExeName) +* tree.dat*) then
Tree-
Viewl.LoadFromFile(ExtractFilePath(Application.ExeName)+ ・ tree.dat');
for i:=0 to TreeViewl.Items.Count-1 do begin
if TreeViewl.Items[i].Parent=nil then TreeViewl. Items [i] . Image Index: =0
else
TreeViewl. Items f i]・ Imageindex: =1;
end;
end;
Здесь сначала происходит загрузка данных из файла, а потом запускается цикл, в котором перебираются все элементы дерева. В цикле мы делаем проверку, если у текущего элемента свойство Parent равно nil, значит, у элемента нет родительского элемента и он корневой, поэтому устанавливаем нулевую картинку. Если свойство не равно нулю, то элемент дочерний, и мы устанавливаем ему картинку с номером 1.
Вот и все. Вот таким простым способом мы восстанавливаем картинки элементов, потому что они не сохраняются в файле.
Примечание
На компакт-диске в папке \ Примеры \ Глава 11 \ TreeView2 вы можете увидеть пример этой программы.
Профессиональное использование компонента TreeView
Сейчас мы рассмотрим свойство Data элементов дерева TreeView. Это очень удобное свойство, потому что в него вы можете записывать указатель на лю-
бые свои данные. Именно поэтому с помощью Data компонент превращается в мощнейшее оружие программирования.
Рассмотрим пример, в котором закрепим знания о компоненте Tree view, а также о структурах и о работе с файлами.
Рис. 11.36. Главная форма будущей программьЕ
Создайте новый проект и главную форму, как показано на рис. 11.36. В рабочей области формы располагаются:
口 компонент Treeview, который нужно растянуть по левой стороне окна;
П компонент Panel, с тремя кнопками: Создать, Удалить и Сохранить;
口 четыре строки ввода с подписями для ввода имени, фамилии, адреса и e-mail.
В разделе type до объявления объекта формы нужно описать структуру NodeOptions, как это показано в листинге 11.15. Туг же объявим переменную —указатель на структуру. В ней будет четыре поля, которые соответствуют полям ввода на форме.
Листинг 11.15. Описание структуры NodeOptions
type
PNodeOptions 二 ^'NodeOptions;
NodeOptiensurecord
Name: String[255];
Surname: String[255];
Address: String[255];
EMail: String[255]; end;
Обратите внимание на то, что все используемые строковые Переменные объявлены с ограничением на размер в 255 символов. Это связано с тем, что мы готовим содержимое структуры для сохранения в файл. А т. к. строка имеет переменную длину, то в файл структура будет писаться неравномерно. Туг есть два выхода.
口 При записи в файл писать и размер каждой строковой переменной. В этом случае запись и чтение будут сложнее, но мы сэкономим файловое простр анство.
口 Жестко объявить размер строк, как это сделано в нашей структуре. Здесь уже строка будет писаться с определенным размером и чтение/запись упрощается. Так мы сможем писать в файл всю структуру за один заход, как мы это уже делали.
Теперь напишем обработчики событий для наших кнопок. Для кнопки Создать нужно написать содержимое листинга 11.16. В этом обработчике будут создаваться новый элемент и инициализироваться начальные данные.
Листинг 11.16. Создание нового элемента
procedure TMainForm.NewBtnClick (Sender: TObject);
var
NodeName:String;
NodeData:PNodeOptions;
NewNode:TTreeNode;
begin
if not InputQuery(1Новый элемент1z 1 Введите имя нового элемента *, NodeName) then exit;
NewNode: =TreeView. Items.AddChild(Treeview. Selected, NodeName);
// Инициализация начальных данных структуры
NodeData:=new (PNodeOptions);
NodeData. Name : = * *;
NodeData・ Surname : = *1;
NodeData.Address:=•,
NodeData.EMail: = * *;
NewNode.Data:=NodeData;
end;
В самом начале приведенного выше кода мы запрашиваем у пользователя имя нового элемента с помощью функции Input Query. Далее, новый элемент добавляется в дерево. В качестве корневого элемента используется выделенный в настоящий момент элемент.
Теперь нужно проинициализировать переменную NodeData, которая имеет тип структуры PNodeOptions. Мы создаем ее с помощью функции New и каждому элементу присваиваем значение по умолчанию. В данном случае всем присваивается пустая строка, чтобы в каком-то из элементов случайно не оказался "мусор”.
В самой последней строке происходит сохранение переменной NodeData в свойстве Data нового элемента. Вот тут нужно несколько пояснений.
П Переменная NodeData — это указатель на структуру, поэтому мы сохраняем адрес структуры в памяти.
О Переменная NodeData объявлена локальной и по идее она должна уничтожатся. Это первая ошибка начинающих программисте в. У ничтожаются простые переменные, которые хранятся в стеке, а выделенная память не может освобождаться автоматически, потому что она выделяется в динамической памяти. Именно поэтом указатель на структуру, который мы сохранили в свойстве Data, будет действителен даже после выхода из процедуры.
С созданием элементов разобрались. Теперь продолжим рассматривать наш пример. По нажатии кнопки Сохранить мы будем сохранять введенные в компоненты TEdit данные в структуру данных выделенного элемента. Для этого создайте обработчик события onclick и напишите в нем содержимое листинга 11.17.
Листинг 11.17. Сохранение данных в структуре вн典ел,хх ■ : элемента
procedure ТМаinForm.SaveBtnClick(Sender: TObject);
begin
if TreeView.Selected=nil then exit;
PNodeOptions (Treeview. Selected. Data) .Name: =NameEdit.Text;
PNodeOptions (TreeView. Selected. Data) . Surname: =SurnameEdit. Text;
PNodeOpt ions(TreeView.Selected.Data).Address:=AddressEdit.Text;
PNodeOptions(TreeView.Selected.Data).EMail:=EMailEdit.Text;
end;
Прежде чем сохранять, надо убедиться, что в дереве есть выделенный элемент. Если такого не будет, то при попытке сохранения произойдет ошибка, потому что TreeView. selected будет нулевым указателем. Структура для выделенного элемента находится в свойстве Data выделенного элемента, т. е. в Treeview. Selected.Data. Так как это свойство простой указатель, не имеющий представления о данных, которые хранятся по его адресу, мы должны указать тип самостоятельно. Для этого используем Pnodeoptions (TreeView. Selected. Data). Остальное вам должно быть понятно из кода.
По нажатии кнопки Удалить нужно написать всего две строчки кода:
f TreeView.Selected-nil then exit;
TreeView. Selected. Delete;
В первой строке проверяется, равен ли нулю выделенный элемент. Если да, то нужно выйти из процедуры, потому что ничего не выделено. Если нет, то можно удалять. Для этого используется метод Delete.
Теперь программа умеет создавать элемент, сохранять в структуре данные и удалять элементы. Осталось только научить ее отображать введенные ранее данные. Для этого давайте создадим обработчик события onChanging для компонента TreeView.. Он будет вызываться каждый раз, когда пользователь выбирает новый элемент в дереве. В этот момент мы должны вывести в компоненты TEdit информацию из структуры нового выделенного элемента. В обработчике OnChanging нужно написать содержимое листинга 11.18.
Листинг 11.18. Обработчик события OnChanging .
procedure TMainFonu.TreeViewChanging(Sender: TCfoject; Node: TTreeNode;
var AllowChange: Boolean);
begin
if Node=nil then exit ;
NameEdit.Text:=PNodeOptions(Node.Data) .Name;
SurnameEdit.Text: =PNodeOptions (Node. Data) . Surname; AddressEdit.Text: =PNodeOptions (Node.Data) .Address; EMailEdit.Text: =PNodeOptions (Node. Data) .EMail;
end;
Попробуйте запустить программу и создать несколько элементов. Измените у нескольких из них значения и попытайтесь понять, как работает сохранение и отображение данных из структуры.
Теперь у вас есть хорошая заготовка для работы со сложными данными с помощью Treeview. Вы можете сохранять нужные данные в файле и потом загружать их. На данном этапе не станем описывать сохранение и загрузку, потому что это не совсем просто. Дело усложняется тем, что данные хранятся в виде дерева и нужно сохранять не только структуры, но и позицию в дереве, чтобы потом ее можно было восстановить.
工 Примечание〉
На компакт-диске в папке \ Примеры\ Глава 11 \ТreeView_Data вы можете увидеть пример этой программы.
Список элементов (TListView)
Следующий компонент тоже достаточно сильно распространен. Попробуйте запустить Проводник Windows и посмотрите на появившееся окно. Слева есть дерево каталогов. Как работает такое дерево, мы уже разобрались. А вот справа находится список файлов в выделенной директории. Этот список как раз и хранится в компоненте TListView, с которым мы сейчас и познакомимся.
Для этого напишем простейший файловый менеджер и заодно закрепим большинство уже пройденного материала на практике. Но все это в следующем разделе этой главы, а сейчас остановимся только на основных свойствах компонента TListvew, чтобы легче было двигаться дальше.
V списка элементов очень много свойств, отвечающих за обрамление (внешний вид рамки) компонента. Не будем их все перечислять, потому что разных вариантов очень много. Остановимся лишь на некоторых свойствах.
П BevelEdges — здесь указывается, с какой стороны должна быть оборка. По умолчанию со всех сторон стоит true.
П Bevellnner — вид внутренней оборки.
П BevelOuter — вид внешней оборки.
П BevelKind — ТИП оборки.
П Borderstyle — стиль обрамления (плоский или трехмерный).
Теперь рассмотрим еще несколько интересных свойств.
О checkboxes — если здесь true, то каждый элемент списка содержит еще И компонент Checkbox.
П ColumnClick — свойство определяет, должны ли заголовки колонок выглядеть как кнопки и принимать сообщения от кнопок мыши.
П columns — если здесь щелкнуть дважды кнопкой мыши, то появится маленький редаБсгор колонок списка.
О FlatScrollBar — свойство, определяющее способ отображения полос прокрутки, а именно — должны ли полосы прокрутки выглядеть в стиле Flat (плавающий).
口 FullDrag — определяет полное перетаскивание.
口 GridLines — определяет видимость сетки, когда компонент выглядит в стиле vsReport (отчета).
П HotTrack — включает режим Hot, когда при наведении на элемент списка могут происходить какие-то действия.
口 HotTrackstyles — это группа свойств, в которой описываются действия, происходящие при включенном режиме HotTrack.
htHandPoint — если равно true, то при наведении на элемент курсор мыши принимает вид руки (как в IE при наведении на ссылку).
htUnderlineCold — если равно true, то надо подчеркивать надписи на элементах, даже когда не наведен указатель мыши.
htUnder 1 ineHot — если равно true, то надо подчеркивать надписи на элементах, только когда наведен указатель мыши на элемент.
П Iconoptions — группа свойств, отвечающих за иконки элементов.
П Arrangement — свойство, определяющее расположение иконки (сверху или слева).
П AutoArrange — автоматическое выравнивание.
О WrapText — свойство указывает переносить надпись под иконкой, когда она не помещается в одну строку.
П items — это объект, который хранит все элементы списка. Он очень похож на тот, что был рассмотрен в предыдущем разделе, при написании примера к дереву элементов.
П La где Image — здесь указывается компонент TImageList, в котором должны храниться большие иконки для элементов (32x32).
О MultiSelect — свойство определяет, есть ли возможность выделять сразу несколько элементов.
П Rowselect — здесь дается указание, должна ли выделяться вся строка, когда компонент выглядит в стиле vsReport.
П ShowColumnHeader — свойство определяет возможность показывать заголовки, коща компонент выглядит в стиле vsReport.
口 Smalllmage — здесь указывается компонент TImageList, в котором должны храниться маленькие иконки для элементов (16x16).
口 viewstyle — задается стиль отображения списка. Здесь возможны варианты:
vslcon — больше иконки;
vs Small Icon — маленькие иконки;
vsList — список;
vsReport — отчет.
На этом пока остановимся и перейдем к рассмотрению компонента в действии.
Простейший файловый менеджер
Здесь рассказывается, как самостоятельно написать простейший файловый менеджер. Этим примером мы закрепим наши знания по работе с файлами и научимся работать со списком элементов.
Создайте новый проект в Delphi и установите на него следующие компоненты:
口 одну кнопку;
口 одну строку ввода;
П один список элементов.
Все это расположите так, как показано на рис. 11.37.
Рис. 11.37. Форма будущей программы
Для работы программы этого примера понадобится модуль shellapi, поэтому давайте сразу добавим его в раздел uses.
(Совет )
Здесь и в дальнейшем бу皮т рассматриваться только наиболее интересные моменты программного кода. Для того чтобы уяснить назначения остальных его команд, обращайте внимание на комментарии. Они помогут вам разобраться с происходящим в программе.
Программу начнем тшсать с процедуры FormCreate. Создайте соответствующий обработчик события для формы и напишите в нем содержимое листинга 11.19.
Листинг 1-1.19. Обработчик события OnCreate дпя главной формы
procedure TForml.FormCreate(Sender: TObject);
var
SysImageList: uint;
SFI: TSHFi lelnfo;
begin
//Создаем списки маленьких и больших иконок.
ListViewl.LaгдеImages:=TImageList.Create(self);
ListViewl.SmallImages:=TImageList•Create(self);
/ /Запрашиваем большие иконки
SysImageList := SHGetFilelnfo(1 *, 0, SFI,
SizeOf(TSHFilelnfo)f SHGFI_SYSICONINDEX or SHGFI_LZ^GEICON);
if SysImageList <> 0 then
begin
//Присваиваем системные иконки в ListViewl
ListViewl.Largeimages.Handle :二 SysimageList;
ListViewl.Largeimages.Shareimages := TRUE;
end;
/ /Запрашиваем маленькие иконки
SysImageList :二 SHGetFileinfo(11, 0, SFI, SizeOf(TSHFilelnfo),
SHGFI_SYSICONINDEX or SHGF工_SMALL工CON);
if SysImageList <> 0 then
begin
//Присваиваем маленькие системные иконки в ListViewl
ListViewl. Small images. Handle := SysImageList;
ListViewl. Small images • Sha re Images : = TRUE;
end;
end;
В первых двух строчках кода создаются списки маленьких и больших иконок. Свойства Largelmages И Smallimages имеют ТИП TlmageList, НО сразу после создания компонента они равны nil. Поэтому они создаются, присваивая результат вызова TlmageList. Create (self). Коща они проинициа- лизированы, необходимая память будет выделена.
Раньше мы подключали картинки визуально, добавляя на форму компонент TlmageList, потому что брали их с диска. В этом примере изображения нужно получить из системы Windows и поэтому ставить визуальный компонент нет смысла, в редакторе форм мы картинок не увидим. Они будут существовать в списке только во время выполнения программы.
Можно поступить немного другим способом — поставить на форму два компонента TlmageList и просто указать их в соответствующих свойствах. В этом случае не пришлось бы ничего инициализировать, потому что компоненты, стоящие на форме, инициализируются автоматически. Но здесь так не делается с целью показать, что объектное свойство можно сразу заставить работать без использования дополнительных компонентов.
В дальнейшем мы запрашиваем у системы список больших иконок. Для этого используется функция SHGetFileinfo, которая возвращает информацию о файле, директории или диске. Первый параметр — путь к файлу. Второй — атрибуты. Третий — указатель на TSHFILEINFO. Четвертый — размер TSHFTLEINFO. Последний — флаги, указывающие на тип информации, запрашиваемой у системы.
Теперь о том, как выглядит функция в нашем примере. Первые два параметра пустые, это означает, что нужны глобальные данные, а не информация о конкретном файле. Если указать здесь реальные значения файла, то получим информацию о нем, а если указать нулевые значения, то будет получена системная информация.
В качестве флагов указывается shgfi_sysiconindex и SHGFI LARGEICON, SHGFI SYSICONINDEX означает, что нужно вернуть указатель на системный список иконок (imageList). Второй флаг говорит, что нужны большие иконки.
При втором вызове этой функции (чуть ниже в коде) мы запрашиваем маленькие иконки (shgfi smallicon). Функция возвращает указатель на соответствующий системе SysImageList, который мы впоследствии присваиваем ListViewl. Largelmages. Handle. После этого присваивания ListViewl. Largelmages содержит все системные иконки размера 32x32.
Почему? Потому что свойство Handle — это указатель на выделенную для картинок память. Этот указатель мы изменили на тот, который получили из системы, и теперь наш список картинок указывает на системный.
Системный список иконок (imageList) содержит все иконки, установленные в системе и ассоциированные с разными типами файлов. Эти иконки вы можете видеть в проводнике (Windows Explorer) у файлов doc, txt, ini, zip И др.
На следующем этапе разработки программы создадим обработчик события onshow. В нем вызовем процедуру AddFile, которая считывает все файлы из текущей директории:
AddFile「С: /* ・ *1, faTViyFile);
Процедура AddFile объявлена в разделе private нашей формы Forml следующим образом:
private
{ Private declarations }
function AddFile(FileMask: string; FFileAttr:DWORD): Boolean;
Объявите эту процедуру так же, а потом нажмите комбинацию клавиш <Ctrl>+<Shift>+<C>, и Delphi создаст заготовку для будущей процедуры: function TForml.AddFile(FileMask: string; FFileAttr:DWORD): Boolean; begin end;
В эту заготовку напишите содержимое листинга 11.20. *
- —. 1 . - ■■ ■ - -"~_ .~-7 lj- . ;- Х-, I
г'^сгмнг 11.20. Процедура просмотра текущей директории
function TForml.AddFile(FileMask: string; FFileAttr:DWORD): Boolean; var
Shlnfo: TSHFilelnfo;
attributes: string;
FileName: string; hFindFile: THandle; SearchRec: TSearchRec;
function AttrStr (Attr: integer) : string; begin
Result := 1 * ;
i f (FILE_ATTRIBUTE_DIRECTORY and Attr) > 0 then Result : = Result + * *; if (FILiE_ATTRIBUTE_ARCHIVE and Attr) > 0 then Result : = Result 十 'A';
if(FILE_ATTRIBUTE_E<EADONLY and Attr)>0 then Result : = Result十 *R*;
if(FILE_ATTRIBUTE_HIDDEN and Attr) >0then Result : = Result -+*H*;
if(FILE _ATTRIBUTE_SYSTEMand Attr) >0then Result : = Result +,S‘;
end;
begin
ListViewl.Items.BeginUpdate;
ListViewl.Items.Clear;
Result *f false;
hFindFile := FindFirst(FileMask, FFileAttr, SearchRec);
if hFindFile <> INV/\LID_HZXNDLE_VALUE then
try
repeat
with SearchRec.FindData do
begin
if (SearchRec.Name = '.') or (SearchRec.Name = '.•') or
(SearchRec.Name = * ') then continue;
FileName := SlashSep(Editl.Text, SearchRec.Name);
SHGetFilelnfo(PChar(FileName), 0, Shlnfo, SizeOf(Shlnfo), SHGFI_TYPENAME or SHGFI_SYS ICONINDEX);
Attributes := AttrStr(dwFileAttributes);
/ / Добавляю новый элемент
with ListViewl. I terns. Add do
begin
//Присваиваю его имя
Caption := SearchRec.Name;
//Присваиваю индекс из системного списка изображений
ImageIndex :二 Shlnfo.ilcon;
/ /Присваиваю размер
SubItems .Add (IntToStr (SearchRec. Size));
SubItems .Add ((Shlnfo. szTypeName));
Subitems.Add(FileTimeToDateTimeStr (ftLastWriteTime));
SubItems. Add (attributes);
SubItems.Add(Editl.Text + cFileName);
if (FILE_JXTTRIBUTE_DIRECTORYand dwFileAttributes) > 0 then
SubItems.Add ( ^ir*)
else
SubItems.Add(1 file*); end;
Result := true; end;
until (FindNext (SearchRec) <> 0);
finally
Findclose(SearchRec);
end;尸
ListViewl • Items . EndUpdate; end;
Процедура получилась достаточно большая и надо подробно ее рассмотреть. Делать это будем по частям программного кода, чтобы было легче воспринимать объяснения. Итак, рассмотрим код:
function TForml.AddFile(FileMask: string; FFileAttr:DWORD): Boolean;
var
function AttrStr (Attr: integer) : string; begin
Result := '* ;
if (FILE_ATTRIBUTE_DIRECTORY and Attr) > 0 then Result :二 Result 十 * 1;
if (FILE_ATTRIBUTE^ARCHIVE and Attr) > 0 then Result :二 Result + '1 A';
if (FILE ^ATTR 工 BUTE_READONLY and Attr) > 0 then Result :二 Result 十 *R*; if (FILE_A.TTRIBUTE_HIDDENand Attr) > 0 then Result :二 Result + 'H‘; if (FILE_ATTRIBUTE_SYSTEM and Attr) > 0 then Result :二 Result 十 * S1 ;
end;
После имени процедуры идет объявление локальных переменных. Это все понятно и мы не раз уже такое делали. Но после объявления переменных вместо начала процедуры begin стоит объявление другой локальной функции:
function AttrStr(Attr: integer) : st ing;
Конечно же, эту функцию можно написать как полноценную, но в данном случае мы закрепляем на практике локальные пропедуры.
Если одна процедура или функция (внутренняя) объявлена внутри другой (внешней), то внутренняя процедура может быть вызвана только из внеш-
ней. Весь остальной код программы не будет знать о существовании где-то внутренней процедуры.
К Примечание 工
В примере сложилась ситуация, коща используется внутренняя процедура. Это сделано искусственно, потому что вы можете встретить такую конструкцию в других программах. Однако многие программисты стараются не использовать такой прием, потому что трудно найти случай, когда внутренняя процедура действительно необходима. По этой причине обходятся без нее.
После объявления и описания внутренней процедуры идет начало внешней процедуры. Вот тут начинается самое интересное. В самом начале вызываются два метода компонента Listviewl:
ListViewl. Items. BeginUpdate;
Listviewl. Items. Clear;
Первый метод BeginUpdate говорит о том, что начинается обновление элементов списка. После этого вызова никакие изменения, вносимые в элементы, не будут отражаться на экране, пока не будет вызван Endupdate.
Когда вы хотите произвести незначительное изменение, то не надо вызывать эти методы, но когда предполагается, что элементы списка будут изменяться очень сильно, то лучше все изменения заключить между вызовами BeginUpdate и Endupdate. Это связано с тем, что когда вы вносите хоть какое-то изменение, оно сразу отображается на экране. Логично? А что если вам нужно удалить все элементы и потом в цикле добавить в список 1000 новых элементов. В этом случае после удаления и каждого добавления нового элемента будет происходить прорисовка компонента. Вот тут и возникает вопрос: М3ачем после каждого добавления рисовать?** В этом случае намного эффективнее будет добавить все элементы, а только потом их прорисовать, причем все сразу. Вот именно для этого и существуют своеобразные операторные скобки BeginUpdate И End update:
Listviewl. Items. BeginUpdate; // Запрещаем прорисовку
// Делаем необходимые изменения
Listviewl. Items. EndUpdate; // Прорисовываем все изменения сразу
После вызова BeginUpdate производится очистка текущего списка элементов С ПОМОЩЬЮ вызова Listviewl. Items. Clear.
Далее идет цикл поиска файлов, с которым мы уже немного познакомились в гл. 10. Вспомним этот процесс.
FindFirst — открывает поиск. В качестве первого параметра выступает маска поиска. Если вы укажете конкретный файл, то система найдет его. Второй параметр — атрибуты включаемых в поиск файлов. Здесь мы используем fa Any File, чтобы искать любые файлы. Последний параметр — это структура, в которой нам вернется информация о поиске, а именно имя найденного файла, размер, время создания и т. д.
После вызова FindFirst мы проверяем корректность найденного файла. Если все в норме, то запускается цикл Repeat - until. Этот цикл выполняет операторы, расположенные между Repeat и Until, пока условие, расположенное после слова until, является верным (имеет значение true). Как только условие нарушается, цикл прерывается.
工 Примечание
Цикл until похож на while, но с одним отличием. Если в while условие заведомо неверно, то операторы внутри цикла не выполняться. В Repeat ——until выполнятся, потому что сначала происходит выполнение операторов, а лишь затем проверка Until.
Хочу предупредить, что функция поиска может возвращать в качестве найденного имени в структуре searchRec (параметр Name) точку или две точки. Если вы посмотрите на директорию, то таких файлов нет. Откуда берутся эти имена? Имя файла в виде точки указывает на текущую директорию, а имя файла, состоящее из двух точек, указывает на директорию верхнего уровня. Если встречаются такие имена, то их просто отбрасывют:
/ /Отбрасывание имен с точкой и двумя точками
if (SearchRec.Name 二 1. *) or (SearchRec.Name 二 *..1) or
(SearchRec .Name 上 * *) then continue;
Далее вдет вызов функции siashsep:
FileName :二 SlashSep(Editl.Text, SearchRec. Name );
Эта функция И FileTimeToDateTimestr 6y取T написаны ПОЗЖе И Объявлены в разделе var после объявления объекта:
var
Forml: TForal;
function SlashSep(Path, FName: string): string;
func 匸 ion Fi leT imeToDat eTime Str (FileTime: TFileTime) : string;
implementation
Здесь функция siashsep объявлена не внутри объекта, значит, она никому не принадлежит!
Вообще-то самостоятельные функции не обязательно где-либо объявлять. Вы можете без проблем просто реализовать ее и нигде не описывать. Однако здесь надо учитывать, что если где-то необходимо использовать эту функцию, то реализация обязательно должна быть раньше использования. Пример правильного использования самостоятельной процедуры или функции показан в листинге 11.21.
Листинг 11.21. Пример правильного использования самостоятельной процедуры
procedure Ехалр; begin end;
procedure Forml. Examp2 ; begin
Ехапр;
end;
В этом примере объявлена самостоятельная процедура Examp и метод объекта Forml — Examp2. Из метода Ехатр2 мы вызываем самостоятельную процедуру Examp. Этот код правильный, потому что процедура сначала реализуется, а потом уже используется.
А теперь посмотрите на неправильный код, который показан в листинге 11.22.
I Листинг 11.22* Не叩авнпьние использование самостоятельной процедуры
procedure Fc rml. Examp2;
begin
Ехалр;
end;
procedure Ехалр;
begin
end;-
В этом примере происходит попытка вызвать процедуру, которая реализована после вызова, поэтому компилятор выдаст ошибку. Чтобы этого избежать, самостоятельные процедуры можно описывать в разделе var, как это показано в листинге 11.23.
Листинг 11.23. Объявление самостоятельной процедуры
var
procedure Examp;
procedure Forml. Examp2 ; begin
Ехатцр;
end;
procedure Examp;
begin
end;
А теперь давайте посмотрим, как же выглядит функция Slashsep: function SlashSep (Path, FName : string): string; begin
if Path [Length (Path) ] <> ' \ • then
Result := Path + 十 FName
else Result := Path + FName;
end;
Эта функция получает два параметра — путь к файлу и имя файла, которые она должна соединить в одну строку, чтобы получился полный путь к файлу. Но сначала мы должны проверить, заканчивается ли путь (первый полученный параметр — Path) знаком ' \ ' . Именно это делается в первой строчке кода:
Path [Length( Path) ] о Л*
Переменная Path — это строка типа string, а значит, мы можем к ней обращаться как к массиву символов. Чтобы получить доступ к первому символу, мы должны написать Path [1 ]. Нам нужно проверить последний символ, поэтому в квадратных скобках используется Length (Path) . Функция Length возвращает длину переданной ей строковой переменной, а это значит, что в квадратных скобках мы указываем длину строки, т. е. последний символ.
Если бы нужен был предпоследний символ, то мы бы написали Path [Length (Path) -1] . В этом случае из длины строки вычитается единица и в результате получается индекс предпоследнего символа.
Если последний символ не равен * \', добавляем сначала его, а потом имя ■файла. Если этот символ в строке имеет место, то нужно только добавить
имя файла и записать в переменную Result, чтобы функция вернула полный путь к файлу.
С этой функцией покончено, и пора вернуться к нашему перечислению файлов. Следующим идет вызов системной функции SHGetFileinfo. Она возвращает информацию о файле. Не будем на ней сейчас останавливаться. В принципе она простая н вы, наверно, сможете ее понять по коду, а если нет, то к ней мы вернемся немного позже.
Сейчас больше интересует работа с компонентом Listview. Следующий код добавляет в список новый элемент: Listviewl. items .Add. Это делается внутри конструкции with, значит, все последующие действия между begin и end будут выполняться с новым элементом. А именно, изменяется заголовок нового элемента.
Caption := SearchRec.Name
и картинка
Imagelndex := Shlnfo.ilcon
У каждого элемента есть свойство Subitems, которое хранит дополнительную информацию. Коща компонент находится в режиме отображения иконок, то дополнительная информация не видна. Но если выбрать в свойстве viewstyle значение vsReport, то компонент будет выглядеть как таблица, вде каждый столбец отображает дополнительную информацию, как это показано на рис. 11.38.
Имя
Размер
I sri
]Zara
1 Атри ^
Й SUHDLDG.DAT
914B
20.12.2000...
HS
叫 fdojaBhw.sys
960
Системный Файл
05.05.1999...
AHS
DETLOG.TXT
74063
Т екстовв й документ
11.05.2001 ...
HS
凶 CONFIG.SYS
130
Системный Файл
04.04.2002...
н
2JC0MMAND.C...
Э5202
Приложение MS-DOS
05.05.1999...
HS
圖 IO.SYS
222390
Системный Файл
05.05.1999...
RHS
TjAUTOEXECB .
460
Пакетный Файл MS-DDS
'04.11.2001 ...
н
国 MSDOS.SYS
1696
Системный Файл
29.11.2001 ...
ARHS
^MSDOS.-
9
20.12 2000...
HS
® SETUPLOG.T .
126772
Г екстовый документ
20.12 2000
HS
_J WIN DOWS
0
Папка с Файлами
20.12.2000...
А
*l NETLOG.TXT
3221
Т екстовь йдокумент
20.12.2000...
HS
J Мои докчмен...
0
Папз с Файлами
20.12.2000...
Рис. 11.38. Пример компонента Listview в режиме vs Report
Чтобы добавить дополнительные колонки к новому элементу, надо выполнить оператор:
SubItems.Add(* значениеf); •
I
Чтобы колонки отображались, нужно в свойстве columns указать имена колонок. Если имена колонок не указаны, то ничего отображаться не будет. И не забывайте, что при описании колонок первая — это заголовок элементов, остальные — это до по л ните льные параметры в порядке их добавления с ПОМОЩЬЮ SubItems .Add.
И последнее, что надо еще рассмотреть, — это функция FileTimeToDateTimestr. Ее код можно увидеть в листинге 11.24.
I Листинг 11N4 Функция Fxl«T±meToDa.teT±meStr
function FileTimeToDateTimeStr (FileTime: TFileTime) : string;
var
LocFTime: TFileTime;
SysFTime: TSys temTime;
Dt, Tm: TDateTime;
begin
FileTimeToLocalFileTime (FileTime, LocFTime);
FileTimeToSystenfTime (LocFTime, SysFTime);
try
with SysFTime do
begin
Dt := EncodeDate(wYear, wMonth. wDay);
Tm := EncodeTime(wHour, wMinute, wSecond, wMilliseconds);
end;
Result := DateTimeToStr(Dt+Tm);
except
Result := * * ;
end;
end;
В самом начале мы приводим дату файла в универсальное глобальное время (ПО ГрИНВИЧу). Для ЭТОГО используется функция FileTimeToLocalFileTime, у которой два параметра:
О переменная типа TFileTime, которую нужно перевести;
□ переменная, в которую будет записан результат.
Вторым этапом мы переводим универсальное глобальное время в системное Время Вашего КОМПЬЮТера. Для ЭТОГО нужна функция FileTimeToSystemTime, у которой также два параметра:
П универсальное глобальное время;
О переменная результата.
Теперь у нас дата хранится в переменной SysFTime и имеет тип TSystemTime. Переменная SysFTime имеет тип структуры и следующие свойства:
П wYear — ГОД;
П wMonth — месяц;
口 wDay — день;
П wHour — часы;
П wMinute — минуты;
О wSecond — секунды;
□ w Milliseconds — миллисекунды.
Все это числа, и нам надо превратить их в строку, но так, чтобы строка даты выглядела в соответствии с локальными настройками системы. Если мы пишем программу, которая будет выполняться в однотипной системе (например, в русской версии Windows с российским представлением даты и времени), то вы можете воспользоваться функцией intToStr, чтобы превратить все поля в строки и отформатировать по своему усмотрению.
Однако сейчас поступим более универсально. Сначала данные превратим в формат DateTime. Для ЭТОГО есть функции EncqdeDate И EncodeTime. Эти функции создают переменные типа TDate и TTime на основе переданных им числовых значений.
Получив эти переменные, мы объединяем их в более общий формат DateTime простым сложением и переводим в строку с помощью функции DateTimeToStr. Эта функция переводит дату в строку в соответствии с локальными настройками региона в ОС Windows. Вы же можете воспользоваться и функцией FormatDate, которая может создать строку даты в любом виде.
工 Примечание〉
На компакт-диске в папке \ Примеры \ Глава 11 \ UstView вы можете увидеть пример этой nporpaMivw.
11.23. Улучшенный файловый менеджер (с возможностью запуска файлов)
Давайте добавим к нашему файловому менеджеру возможность путешествия по директориям и запуска файлов. Для этого нужно создать обработчик со-
бытия oriDbiciick для компонента Listview и написать в нем содержимое листинга 11.25.
Листинг 11 2^5. Обработчик события OnDblClick
procedure TForml.ListViewlDblClick(Sender: TObject);
begin
//Это директория?
if (ListViewl. Selected. SubItems [5]二'dir') then begin
//Если да, то прибавить имя вьщеленной директории к пути
//и перечитать файлы из нее.
Editl.Text:=Editl.Text+ListViewl.Selected.Caption+'\';
AddFile(Editl.Text+ * *.* * r faAnyFile)
end
else
//Если нет, то это файл, и я его запускаю.
ShellExecute(Application.MainForm.Handle, nil,
PChar(Editl.Text+ListViewl.Selected.Caption),'',
PChar(Editl•Text), SW_SHOW);
end;
J
В этом коде, в самом начале, о существ ляется проверка произведенного выбора (по какому объекту был произведен щелчок мышью). Если это директория, то надо перейти в нее, а если файл, то надо его запустить. Для этого проверяется 5-й дополнительный параметр выделенного элемента:
ListViewl. Selected. SubItems [5] = ,dir,
Когда мы добавляли элементы и дополнительные параметры в Listview, то в качестве 5-го, для директорий указывали значение 'dir', а для файлов — 'file' Tenefib надо только проверить этот параметр.
Если выделенная строка — это директория, то мы изменяем текст текущей директории в Editl .Text и перечитываем ее с помощью вызова AddFile, указав новое значение директории.
Если выделенная строка — это файл, то его надо запустить. Это можно сделать с помощью вызова функции ShellExecute. У функции следующие параметры.
口 Программа, отвечающая за запуск приложения. Здесь можно указать значение nil, но мы укажем главное окно программы (Appl icat ion. MainForm. Handle).
口 Строка, указывающая на операцию, которую надо выполнить. Укажем nil для запуска файла.
Строка, содержащая полный путь к файлу.
口 Строка параметров, передаваемых программе в командной строке.
Директория по умолчанию.
П Команда показа. Здесь мы укажем SW_SHOW для нормального отображения окна. Можно указать и другие параметры (все команды вы найдете в файле помощи), но чаще всего используются SW_SHOW (нормальный режим), sw_showmax工м工zed (показать максимизированно) или sw_showminimized (показать в свернутом состоянии).
Внимание
Функция ShellExecute объявлена в модуле shellapi, поэтому его необходимо добавить в раздел uses, иначе Delphi не сможет отком пил и ровать проект.
工 ррмменанме 】
На компакт-диске в папке \ Примеры \ Глава 11 \ ListView вы можете увидеть пример этой программы.
11.24. Подсказки для чайников (TStatusBar)
Если ОС UNIX создавалась для профессионалов, то Windows создавалась для пользователей, непрофессионалов в области вычислительной техники, чтобы им легче было работать на компьютере. Потом она превратилась в ОС для всех, ну а сейчас Windows превратили в ОС для "чайников**, которые с компьютером полностью несовместимы. Так что теперь Для успеха любой программы нужно обязательно делать большое количество подсказок, потому что пользователи не любят читать инструкции и файлы помощи (знаю по себе), и любой человек должен разобраться с программой без дополнитель- нойинформации.
Самым первым способом облегчения жизни неопытным пользователям стало использование строки состояния. Такие строки и сейчас широко применяются, потому что просты в использовании и удобны в обращении. Именно с этим компонентом мы сейчас и познакомимся.
Установить этот компонент на форму — это еще не значит, что подсказки сразу же сами появятся на панели. Для полно ценно й работы надо вьшол- нить следующее:
. У компонента, при наведении на который должна отображаться подсказка, в свойстве Hint должен быть занесен текст подсказки.
Если вы хотите, чтобы подсказка появлялась не только в строке состояния, но и над компонентом, то у него или у родительского окна в свойстве showHint нужно установить true.
Итак, пусть необходимо создать обработчик события на подсказки. Может это звучит сложно, но реально все просто.
Создайте новое приложение и установите на него кнопку. Теперь в ее свойстве Hint напишите иЭто кнопка выхода".
Попробуйте запустить приложение и навести на кнопку указатель мыши. Никаких сообщений и подсказок пока не должно быть. Закройте программу и перейдите опять в Delphi. Теперь установите в свойстве showHint, у компонента или у главной формы значение true. Если вы установите только у компонента, то подсказка будет появляться только у него. Если у формы, то подсказка будет появляться у всех компонентов на форме, у которых есть текст в свойстве Hint и ParentshowHint равно true.
Запустите приложение и проверьте появление подсказки.
Теперь мы добавим к нашему приложению возможность отображения такого же текста в строке состояния. Установите на форму компонент TStatusBar. Теперь перейдите в редактор кода и найдите раздел private. В нем добавьте объявление процедуры ShowHint:
private
{ Private declarations }
procedure ShowHint(Sender: TObject);
Имя процедуры может быть и другим (например, MyShowHint), но параметр должен быть именно такой, как показано выше.
Теперь нажмите сочетание клавиш <Ctrl>+<Shift>+<C>, чтобы Delphi создал заготовку для процедуры:
procedure TForml.ShowHint(Sender: TObject);
begin
end;
Можете и сами написать этот текст, но только после текста:
{ TForml }
или еще дальше, после какой-нибудь процедуры:
iinplementat ion
{$R *.dfm}
{ TForml }
procedure TForml.ShowHint(Sender: TObject);
begin end;
Внутри процедуры напишите следующее:
procedure TForml.ShowHint(Sender: TObject);
begin
StatusBarl. SinpleText : = Application. Hint; end;
Итак, наша процедура должна будет вызываться каждый раз, когда надо вывести подсказку. Внутри процедуры мы присваиваем в свойство Simple Text строки СОСТОЯНИЯ текст, НаХОДЯЩИЙСЯ В Application. Hint. А В Zkpplica.tion. Hint всегда находится подсказка, которую надо сейчас отобразить.
Теперь создайте обработчик события onShow для главной формы и в нем напишите следующий код:
procedure TForml.FormShow(Sender: TObject);
begin
Арр 1 i c a t i on. OnHint :二 ShowHint ; end;
Здесь программно назначается процедура ShowHint в качестве обработчика события OnHint. Но можно было поступить и проще:
Поставить на форму компонент TApplicationEvents с вкладки Additional.
У этого компонента на вкладке Events создать обработчик события OnHint и там сразу же написать следующий код:
StatusBarl.SimpleText := Application.Hint
К Примечание
На компакт-диске в папке \ Примеры \ Глава 11 \ Hint вы можете увидеть пример этой программы.
Теперь попробуем создать строку состояния из нескольких панелей. Вы
делите строку состояния и дважды щелкните левой кнопкой мыши по свой
ству Panels. Перед вами должно открыться окно редактора панелей
(рис. 11.39).
В этом окнеВ этом окнесоздает новую панельсоздает новую панель(также можно нажать клави(также можно нажать клави
В этом окне
В этом окне
создает новую панель
создает новую панель
(также можно нажать клави
(также можно нажать клави
шу <Ins>). КнопкаГС-Яудаляет выделенную в окне панель (также можно
нажать клавишу <Del>).
Рис. 11.39. Редактор панелей
Создайте новую панель и в ее свойстве width (ширина) установите значение 200. Теперь создайте еще одну панель. Все, можно закрывать окно.
Перейдите в процедуру — обработчик события OnHint и измените ее текст на:
procedure TForml. ShowHint (Sender: TObj ect);
begin
StatusBarl.Panels[1].Text := Application.Hint; end;
Здесь в свойство Text первой панели строки состояния присваивается текст сообщения (Application.Hint). Во второй панели вы можете выводить любой текст и использовать его по своему усмотрению. Для этого в любом месте кода можно написать:
StatusBarl.Panels[2].Text :二 1Текст1;
Примечание ;
На компакт-диске в папке \ Примеры \ Глава 11 \ HintPanels вы можете увидеть пример этой программы.
Панель инструментов
(TToolBaru TControlBai}
Панель инструментов уже уверенно вошла в нашу жизнь. Трудно представить себе какой-нибудь хоть более-менее значащий проект без такой панели. Некоторые считают, что меню достаточно, а некоторые, наоборот, обходятся только одной панелью инструментов. Однако практика показывает, что любое оконное приложение должно иметь и то и другое.
Панель инструментов чаще всего располагается сразу же под меню, но это не обязательно. Иногда удобно расположить его вдоль какой - нибудь стороны окна (левой, правой или нижней). В наших примерах мы будем распо-
лагать ее в основном сверху (классический вариант), как это делается в большинстве программ, например MS Word.
Давайте создадим приложение, использующее панель инструментов. Установите на форму компонент ControlBar с вкладки Additional, и измените его свойство Align на alTop, чтобы растянуть компонент вдоль верхней кромки окна. Сразу же желательно изменить и свойство AutoSize на true.
Компонент ControiBar не рассматривался, потому что в нем нет ничего особенного, но он хорош тем, что на нем удобно располагать панели инструментов. Они автоматически становятся перемещаемыми внутри ControiBar. Это значит, что панели можно будет двигать по своему усмотрению. Ну, а если свойство Auto Size равно true, то компонент будет автоматически растягиваться и сужаться, котда вы будете выстраивать все панели в одну строку или в столбик.
Давайте теперь установим на компонент ControiBar одну панель ToolBar с вкладки Win32. Сразу же изменим одно его свойство. Дважды щелкните левой кнопкой мыши по свойству EdgeBorders и измените свойство еЬТор на false. Это заставит исчезнуть оборку сверху панели.
Желательно также сразу изменить и здесь свойство Auto Size на true, чтобы панель принимала размеры, соответствующие кнопкам.
Теперь создадим кнопки на панели. Для этого щелкните по ней правой кнопкой мыши и выберите из появившегося меню пункт New Button. Пункт New Separator этого же меню создает разделитель между кнопками. Если вам нужно будет удалить кнопку или разделитель, то просто выделяете его и нажимаете на клавиатуре клавишу <Del>.
Таким образом создайте две кнопки, потом разделитель и еще одну кнопку. У вас должно получиться нечто похожее на рис. 11.40.
В принципе, простая панель уже создана, но она простая. Теперь необходимо сделать так, чтобы кнопки что-то отображали. Но для начала выделите саму панель и измените свойство Flat на true, чтобы кнопки на панели выглядели более изящно (плоско).
Теперь установим на форму компонент TlmageList и добавим в него три картинки. Их изображение пока не имеет особого значения, поэтому можно выбирать любые. Главное, чтобы размер был 16x16.
Выделите панель и в свойстве images укажите созданный набор картинок. На кнопках сразу же отобразятся картинки в той последовательности, в которой вы их добавили. Если вы хотите изменить картинку на какой-нибудь кнопке, надо выделить ее и изменить свойство Image Index.
Для каждой кнопки в свойстве caption напишите осмысленный текст (по умолчанию там стоит текст ToolButton плюс порядковый номер кнопки). Желательно, чтобы текст соответствовал изображению на картинке. Давайте
сделаем так, чтобы панель отображала на кнопках не только картинки, но и указанный в свойствах caption текст. Для этого установите true в свойстве showCaptions у панели инструментов. Результат вы можете увидеть на рис. 11.41.
Рис. 11.40.Создание новой кнопки
Рис. 11.41. Панель, отображающая картинки и текст
Как видно, в данном случае текст отображается под изображением. Если еще установить свойство List у панели инструментов, то текст будет отображаться справа от картинки (рис. 11.42).
Для закрепления материала давайте создадим обработчик события для какой-нибудь из кнопок. Последняя кнопка — Выход, вот для нее и создадим обработчик. Для этого дважды щелкните по ней левой кнопкой мыши и в созданной Delphi процедуре напишите close.
Рис. 11.42. Панель, отображающая текст слева от картинки
ПримечаниеПримечание
Примечание
Примечание
На компакт-диске в папке \ Примеры \ Глава 11 \ ToolBar вы можете увидеть пример этой программы.
Перемещаемые панели и меню в стиле MS (Docking)
Очень часто разработчики приложений интересуются, как добиться такого эффекта, как у ToolBar в MS Office. Для большей ясности — это когда палитру с кнопками можно оторвать от окна и прилепить в другое место или вообще превратить в отдельное окно.
Для того чтобы TToolBar можно было перемещать, достаточно установить в нем свойство DragKind в dkDock. Вот и все. Но главная проблема не в этом. Самое сложное здесь — это сохранить положение TToolBar после выхода из программы и восстановить его при запуске. Для примера напишем маленькую программу, которую вы можете довести до полноценной.
На рис. 11.43 показана форма, которую мы будем использовать для создания примера. Для демонстрации понадобилась кнопка, по нажатии которой будет выводиться положение TTooiBar. Для события, связанного с нажатием кнопки, пишем код, представленный в листинге 11.26.
Листинг 11 26* Вывод информации о положении кнопки
procedure TForml.ButtcnlClick(Sender: TObject); var
r:TRect;
begin
if ToolBarl. HostDockSiteoControlBarl then
begin
GetWindowRect(ToolBarl.Handle, R);
Application.MessageBox(PChar(IntToStr(r.Left)+* — * +IntToStr(r.Top)),
•MM*, IDOK);
end;
end;
Рис. 11.43. Форма будущего примера
В первой строке кода происходит проверка, лежит ли ToolBarl на ControlBarl, с помощью конструкции:
ToolBarl. HostDockSiteoControlBarl
Если лежит, то получить положение ToolBarl очень просто. Для этого можно узнать всего лишь ToolBarl. Left и ToolBarl .Тор.
Если ToolBarl не лежит на компоненте controiBari (ToolBarl выглядит как отдельное окно), то задача усложняется. Вам придется вызывать GetWindowRect, чтобы получить реальное положение ToolBarl на экране. В качестве первого параметра вы должны передать указатель на ToolBarl, а второй — это переменная типа TRect, в которую запишется реальное положение окна. Для удобства мы выводим эти значения в окне сообшения Application.MessageBox.
Все это делается для наглядности. Теперь можно запустить программу и переместить ToolBarl на экране. Каждый раз, когда вы будете нажимать кнопку, программа будет выводить окно сообщения и показывать вам реальное положение ToolBarL
Для события onShow напишите:
procedure TForml.FormShow(Sender: TObject);
begin
Toolbar1.ManualDock(nil,nil,alNone);
ToolBarl.ManualFloat(Bounds(100, 500, ToolBarl.UndockWidth,
ToolBarl.UndockHeight)); end;
ToolBarl .ManualDock заставляет переместить ToolBarl на новый компонент. В качестве первого параметра определяется указатель на компонент или окно, к которому мы хотим прикрепить ToolBarl. Пусть требуется, чтобы после загрузки ToolBarl превратился в отдельное окно, поэтому для первого параметра указывается nil. Значение второго параметра можно ставить nil. Он означает компонент внутри компонента, указанного в качестве первого параметра, на который мы хотим поместить ToolBarl. Третий параметр — выравнивание.
С помощью ToolBarl. ManualFloat ToolBarl двигается внутри нового компонента. Новый компонент nil, т. е. окно, поэтому ToolBarl двигается по окну. Может не совсем понятно? Попробуйте запустить пример и поработать с ним, тогда все встанет на свои места.
И еще ToolBarl .UndockWidth И ToolBarl .UndockHeight возвращают размер компонента ToolBarl, когда он выглядит как окно, а не лежит на ControlBarl.
Когда вы будете использовать это в своей профамме, для сохранения положения ToolBarl вам надо будет написать для события enclose следующий код: var
г:TRect;
begin
if ToolBarl. HostDockSiteoControlBarl then
begin
GetWindowRect(ToolBarl.Handle, R);
Здесь надо сохранить в реестре R. Left и R.Top.
А также признак, что ToolBarl не лежит на. Control Ваг] end else
begin
Здесь надо сохранить в реестре ToolBarl. Lef^ и
А также признак, что ToolBarl лежит на Control Bari end;
end;
12 Зак 942
На запуск программы вы должны написать код:
procedure TForml. FormShow (Sender: TObject);
begin
Прочитать положение ToolBarl. ControlBarl то
Begin
ToolBarl.Left:Сохраненная левая позиция
ToolBarl.Topt:=Сохраненная верхняя позиция
End;
Иначе
begin
ToolBarl.ManualDock(nil,nilr alNone);
ToolBarl.ManualFloat(Bounds(Сохраненная левая позиция. Сохраненная правая позиция, ToolBarl.UndockWidth, ToolBarl.UndockHeight));
End;
end;
Как видите, подводные камни есть. Но все же ничего очень сложного нет.
Теперь мы сделаем меню в стиле MS. Для этого есть два способа и сейчас рассмотрим самый простой из них. Поместите на форму еще один ToolBar и установите его свойство showCaption в true. Создадим на нем две кнопки и назовем их Ffle и Edit. Теперь установим компонент Ma inMenu и сделаем его таким, как показано на рис. 11.44. Пункт меню Not visible сделаем невидимым (свойство visible = false). В этом случае все меню будет подключено к форме, но будет не видно. Для чего это делается, ведь можно было использовать PopupMenu? А потому что при использовании PopupMenu приходится мучиться с клавишами быстрого вызова, а в этом способе они подключаются автоматически вместе с главным меню.
Рис. 11.44. Меню
Чтобы создать подменю для меню File, нужно щелкнуть по нему правой кнопкой мыши и выбрать Create Submenu или нажать клавиши <Ctrl>+ +<->>.
Теперь кнопке File в свойстве Menuitem ставим Filel (имя пункта меню), а кнопке Edit ставим Editl. И последнее — обеим кнопкам нужно установить СВОЙСТВО Grouped В true.
У каждой кнопки панели инструментов надо установить в свойстве Grouped значение true, чтобы переход между ними был упрощенным. Если этого не сделать, то, открыв меню БИе, вы сможете перейти в другое меню только по щелчку мыши.
Но это не единственный способ создания меню. Можно создать полноценное главное меню. Потом просто убрать его из свойства главной формы Menu и указать у созданной вами пустой панели (без всяких кнопок) в таком же свойстве Menu. С одной стороны, это проще, но будет тяжело контролировать такое меню, когда у вас большой проект. Когда у вас указано меню для главной формы, то горячие клавиши автоматически начинают работать. Если свойство Menu главной формы очистить, то горячие клавиши придется устанавливать вручную.
工 Примечаний
На компакт-диске в папке \ Примеры \ Глава 1 1 \ Dock вы можете увидеть пример этой программы.
Меню и панели в стиле ХР
До этого создавались стандартные меню и панели. Для этого мы использовали разные компоненты. Чтобы создать меню и панели в стили ХР, нужно использовать совершенно другие компоненты, потому что MS почему-то не захотела развивать старое направление функций работы с меню.
Пример, описанный в этом разделе, можно воссоздать в Delphi 6 и 7. В более старых версиях необходимых компонентов просто нет. Рассмотрим пример применительно к 7-й версии Delphi, а отличие от 6-й (она небольшая) уточним отдельно.
Некоторых программистов смущают компоненты ActionManager, ActionMainMenuBar и ActionToolBar на вкладке Additional. Это возможно по причине консерватизма и привычки использовать классические ToolBar и MainMenu. Давайте попробуем перебороть себя и написать приложение с помощью новых компонентов. Вы будете в восторге, потому что эти новые компоненты действительно упрощают и улучшают программирование.
Итак, создам новый проект и поместим на главную форму по одной копии компонентов ActionManager, Act i onMa inMenuBa г И ActionToolBar. Для большей Ефасоты нужно поместить на форму еще и набор картинок imageList с вкладки Win32. В этот набор нужно добавить картинки размером 16x16 для ваших будущих кнопок и пунктов меню. После этого выделите ActionManager и в свойстве images укажите созданный набор картинок.
Рис. 11.45. Окно создания действий
Теперь создадим набор действий. Для этого дважды щелкните мышью по компоненту ActionManager, и вы увидите окно, показанное на рис. 11.45. Чтобы создать новое действие, нужно щелкнуть по кнопке国В списке Actions появится новый элемент Actionl. Выделите его и посмотрите в объектный инспектор. Здесь нас интересуют следующие свойства:
caption — заголовок элемента (для начала создадим элемент мНовый");
Categoty — категория (просто введите здесь слово "Файл**, и будет создан новый раздел).
П checked — оставьте true, если элемент должен иметь два состояния — выделенный и нет (для таких элементов нужно еще ставить и свойство AutoCheck в true, чтобы они автоматически выделялись);
П Enabled — доступность;
О Hint — подсказка; чтобы она появлялась, у формы нужно установить в свойстве showHint значение true;
□ Image Index — индекс картинки;
О shotcut — горячие клавиши.
Это основные свойства, с которыми вы будете часто работать. Попробуйте создать четыре элемента с заголовками: "Новый", "Редактировать", иУдалитьи и "Выход" с категорией category, равной иФайли. После этого создайте еще один элемент с заголовком ”О программе" и в свойстве category укажите "Помощь*. Будет создана новая категория с таким именем.
Теперь захватите указателем мыши имя категории иФайли и перетащите его на компонент ActionMainMenuBar. То же самое сделайте и с категорией "Помощь". Будут созданы два пункта меню. Если вы теперь посмотрите меню Файл, то увидите, что все элементы находятся последовательно, хотя желательно сделать разделитель, чтобы отделить пункт Выход. Для этого существует кнопка Drag to create Separators, которая расположена внизу окна создания наборов. Захватите эту кнопку мышью и перетащите на меню Файл, когда меню раскроется, установите указатель мыши между пунктами Удалить и Выход. Если теперь щелкнуть по меню Файл, то можно увидеть результат, показанный на рис. 11.46.
Forml
)ols Помощь
D0X 隼一 I ■ - и + ■ .D0X 隼一 I ■ - и + ■ .Новый Ctri+NРедактироватьУдалитьВыходНовый Ctri+NРедактироватьУдалитьВыходРис. 11.46. Меню ФайлРис. 11.46. Меню Файл
D0X 隼一 I ■ - и + ■ .
D0X 隼一 I ■ - и + ■ .
Новый Ctri+N
Редактировать
Удалить
Выход
Новый Ctri+N
Редактировать
Удалить
Выход
Рис. 11.46. Меню Файл
Рис. 11.46. Меню Файл
Попробуйте сейчас запустить программу, и вы увидите, что все пункты недоступны. Это связано с тем, что для этих пунктов не созданы обработчики событий onExecute. Само событие срабатывает, когда пользователь выбирает какой-то элемент меню, как событие onclick. Опять возвращаемся в окно создания наборов действий. Создайте для всех элементов обработчики событий onExecute. Чтобы эти обработчики не исчезли при компиляции и сохранении проекта, можете написать там какой-нибудь код или просто по-
ставить знак комментария //. Если вы снова запустите проект, то теперь все пунБсгы будут уже доступны.
Рис. 11.47. Результат создания кнопок
Теперь возьмем категорию иФайли в окне создания наборов действий и переместим ее на компонент ActionToolBar. Этим мы создадим кнопки на панели для всех действий из категории иФайли. Единственный недостаток — кнопки отображаются с заголовками, а это действительно недостаток, потому что более удобно, когда видны только изображения (рис. 11.47). Чтобы исправить этот недостаток нужно выделить компонент ActionManager и дважды щелкнуть справа от свойства ActionBars. Перед вами должно открыться окно, показанное на рис. 11.48.
Рис. 11.48. Окно настройки панелей Action
В этом окне созданы два элемента ActionBar. Один связан с меню ActionMainMenuBarl, а второй связан С панелью КНОПОК ActionToolBarl. Вьщелите второй из них, и установите свойство Autosize в значение false. После этого у панели с кнопками справа внизу появится маленькая кнопка
со стрелкой вниз. Нажав на эту кнопку, пользователь сможет корректировать, какие кнопки надо отображать на панели, а какие нет. Только это будет возможно, если в свойстве FileName у компонента ActionManager указано имя файла. В этом случае компонент сможет автоматически сохранять в этом файле информацию о кнопках меню или панелей, а также восстанавливать их после перезапуска.
Теперь выделим вторую строку (ActionBar->ActionToolBarl) и дважды щелкнем мышью по свойству items. Перед вами откроется еще одно окно с названиями кнопок, которые созданы на панели. Здесь выделять ничего не надо. Просто щелкните в окне левой кнопкой мыши, чтобы выбрать свойства самого окна, а не какого-либо элемента. Найдите свойство CaptionOptions и установите его в coNone. После этого все заголовки кнопок исчезнут.
То же самое можно было сделать еще одним способом — дважды щелкнуть кнопкой мыши по компоненту ActionManager и на вкладке ToolBars последовательно выбирать панели и устанавливать для каждой из них в Caption Options значение None.
Чтобы еще больше украсить пример, отсортируйте все кнопки на панели по своему усмотрению и установите разделители (separators) таким же перетаскиванием, как мы делали это для пунктов меню. Можете еще переместить действие “О программе" на панель. Для этого можно переместить всю категорию "Помощь", а можно только одно это действие.
Теперь еще более усложним пример. Снова возвращайся в окно создания наборов действий, сейчас мы создадим еще один элемент. Щелкните по кнопке со стрелкой вниз (расположена справа от кнопки В). В появившемся меню выберите пункт New Standart Action и вы увидите окно, в котором перечислены стандартные действия, которые могут обрабатываться автоматически. В самом низу этого окна найдите пункт ТcustomizeActionBars. Выделите его и нажмите ОК. Так вы создадите новую категорию Tools с одним только действием внутри — Customize. Вы можете переименовать заголовок Captions и категорию Category созданного элемента и написать все на родном языке. Этого делать не будем, чтобы в исходном коде можно было сразу увидеть данный пункт. Перетащите категорию Tools на панель с меню. Попробуйте запустить пример и выбрать пункт customize из меню Tools. Перед вами откроется окно, похожее на окно создания наборов действий. Только здесь вы не можете создать ничего нового, зато можно перетаскивать кнопки на панель, создавая новые кнопки, или удалять имеющиеся с помощью перетаскивания кнопок обратно в окно (можно даже не в окно, а в любое место, как бы снимая кнопки с панели). Получается, что, не написав еще ни одной строчки кода, мы создали редактируемую панель.
Остается сделать один только маленький штрих. Выделите компонент Ac tionManager и в свойстве FileName укажите какое - нибудь имя файла
(пусть это будет toolbar.dat). В этом файле будут автоматически сохраняться все настройки кнопок панелей и любые изменения, внесенные в режиме Runtime. Указав файл, снова запустите пример. Обратите внимание, что кнопка со стрелкой вниз на панели кнопок недоступна. Это связано с тем, что файл toolbar.dat не существует. Закройте программу. Во время закрытия она сохраняет все состояния кнопок и создает файл настроек. Запустите программу еще раз, и теперь уже все доступно и работает, потому что toolbar.dat существует.
Внимание 】
Если вы указали файл, в котором должны сохраняться настройки, то помните, когда в дизайнере изменяется положение кнопок (добавляются новые или удаляются имеющиеся), во время выполнения это отражаться не будет, потому что в файле toolbar.dat нет никакой информации об изменениях. Чтобы изменения прошли, нужно удалить файл или во время выполнения программы проделать те же изменения, вызвав пункт Customize из меню Tools.
Вот таким простейшим образом мы создали набор действий. У каждого действия создали обработчик события OnExecute, в котором написали необходимые для выполнения действия. После этого только перетаскиваем действия на панель меню или панель кнопок.
И наконец, самое последнее — научим наше главное меню отбрасывать тень. Создайте новое действие в категории Tools и укажите у него следующие свойства:
О Caption — тень;
口 AutoCheck — true;
0 Name — MenuShadowAction.
Для события OnExecute напишите следующий код:
procedure TForml. MenuShadowActionExecute (Sender: TCbject);
begin
ActionMainMenuBarl.Shadows := MenuShadowAction.Checked;
end;
Запустите программу и посмотрите как будет изменяться главное меню при разном состоянии пункта Тень.
Внимание
Тень работает только в Delphi 7.
Очень интересным является свойство style компонента ActionManager. Попробуйте изменить его и посмотреть на результат. Это свойство есть и в 6-й, и 7-й версии Delphi.
В Delphi 7 появился новый компонент xPManifest на вкладке \Wn32. Просто поместите его на форму, и все кнопки и элементы управления в Windows ХР будут иметь стиль ХР.
11.28. Всплывающее меню в стиле ХР
Мы научились создавать меню и панели в стиле ХР, но эти методы не работают с всплывающими меню. Давайте установим на форму компонент PopupMenu. Дважды щелкните левой кнопкой мыши по компоненту. В редакторе меню сделайте следующее:
Создайте новый пункт меню (кнопка Insert).
В свойстве Action укажите действие, которое должно быть ассоциировано с этим пунктом меню.
Все необходимые свойства нового элемента заполняются автоматически. Больше здесь ничего делать не надо.
Назначьте это меню главной форме. Единственный недостаток — всплывающее меню будет выглядеть не так, как основное. Но этот недостаток тоже можно исправить. На компакт-диске, прилагаемом к этой книге (в папке Компоненты \ Action), находится пакет для работы со всплывающими меню в стиле ХР (компонент PopupActionBarEx). Просто почему-то он не вошел в состав Delphi 7. Инструкции по установке прочитаете в файле Установка-txt.
Работать с PopupActionBarEx так же просто, как и с PopupMenu, и тут нет никаких различий в свойствах и методах, только во внешнем виде.
Глава12Глава12嚇
Глава12
Глава12
Графические возможности Delphi
Вот мы уже постепенно добрались и до гл. 72 В этой главе рассказывается, как Delphi помогает упростить работу с графикой Windows. Эту тему можно было бы раскрыть даже немного раньше, потому что мы уже познакомились с ее основами и немного рисовали. Сейчас рассмотрим вопросы, связанные с графическими возможностями среды, как можно более подробно.
Windows — это графическая оболочка. Все, что вы в ней видите, — это графика. Но для программиста большинство вещей очень сильно упрощено, особенно в Delphi. Поэтому мы пока еще не сталкивались с графическими средствами очень близко. Но если вы соберетесь писать какой-нибудь большой проект, то обязательно столкнетесь с проблемой рисования при оформлении определенных частей программы.
Так как Windows — это графическая оболочка, то дальнейшее обучение программированию невозможно без изучения этой главы. Так что не пропустите и прочтите ее полностью, даже если вы думаете, что эта тема вам ненужна.
Графическая система Windows
В предыдущих главах мы уже встречались с графикой Delphi и немного рисовали. Когда мы делали это, то обращались к объекту Canvas. Практически у всех компонентов Delphi есть это объектное свойство. Почему объектное? Да потому, что Canvas имеет тип объекта TCanvas. То есть в нашем компоненте за рисование отвечает объект. Таким образом, если компонент поддерживает рисование, то у него обязано быть такое свойство.
Canvas в переводе с английского означает холст. Получается, что каждый компонент — это холст, на котором нарисовано изображение компонента. Взглянем на кнопку. На самом деле это не кнопка, а холст, на котором нарисованы изображения кнопки и текста. Когда вы щелкаете кнопкой мыши по кнопке, изображение изменяется и приобретает вид нажатой кнопки.
Графика Windows действительно представляет рисование на холсте. А для этого необходимы две вещи — карандаш для рисования (реп) и кисть для
закраски (Brush). Именно такие свойства и присутствуют у объекта canvas. Карандаш используется для рисования линий и контуров, а кисть используется для закраски замкнутых областей. У обоих есть свои свойства (цвет, тип и т. д.), но чтобы было понятнее, посмотрите на рис. 12.1.
Рис. 12.1. Простой прямоугольник
Это простой прямоугольник. Контур прямоугольника рисуется карандашом (в данном случае черного цвета). Центр прямоугольника закрашивается кистью (у нас серого цвета). Но не надо думать, что все приходится рисовать по пикселам (точка на экране монитора). В Windows для вас уже заготовлено достаточно инструментов для облегчения процесса работы с графикой.
Вообще, большое количество готовых инструментов — это самое большое достоинство Windows. Благодаря этому операционная система имеет такую популярность у программистов (легче программировать). Значит, больше выходит программного обеспечения и, следовательно, появляется больше возможностей у пользователей. Все это сделало Windows одной из наиболее используемых в настоящее время ОС.
Графические инструменты Windows объединены под одним названием — GDI (Graphic Device Interface — интерфейс графических устройств). Все функции для работы с графикой находятся в одной динамической библиотеке gdi.dll, но подробнее о библиотеках будет сказано чуть позже.
Еще одним плюсом GDI является то, что все функции аппаратно-независимые. Это значит, что результат вывода графики будет одинаков вне зависимости от графического устройства (видеокарты), установленной в компьютере. Каждая карта имеет свои особенности, и специфика работы ее может отличаться от других. Но для GDI все это не имеет значения. Однако тут же появляются минусы GDI:
口 он не использует графического ускорения и других дополнительных возможностей современных видеокарт;
О он слишком медлителен для приложений, нуждающихся в быстрой обрисовке графики (например, для игр);
П в нем поддерживается только двухмерная графика.
Все эти минусы отражаются на том, что GDI не предназначен для создания игр, однако хорошо подходит для офисных приложений. А вот игры хорошо создавать с помощью OpenGL или DirectX, но об этом тоже надо вести отдельный разговор, потому что тема трехмерного программирования слишком объемная.
Первый пример работы с графикой
Давайте попробуем написать простейший пример, в котором будет рисоваться обычный квадрат. Но для усложнения задачи квадрат будем рисовать не на форме, а внутри компонента TPaintBox, который очень хорошо подходит для этих целей.
Создайте новое приложение и поместите на него компонент Paint Box с вкладки System палитры компонентов. Постарайтесь разместить этот компонент в нижней половине окна.
У формы и у PaintBox есть свойство canvas, значит, на них можно рисовать. Рисование лучше всего производить по событию OnPaint, которое также есть у обоих компонентов. Итак, создадим обработчик события OnPaint для формы и напишем тут следующее:
procedure TForml.FormPaint(Sender: TObject);
begin
Canvas.Rectangle(10,10,100,100);
end;
Здесь мы вызываем метод Rectangle объекта canvas нашей главной формы.. У этого метода четыре параметра.
口 Левая позиция квадрата.
口 Верхняя позиция квадрата.
口 Правая позиция.
П Нижняя позиция.
Теперь выделите компонент PaintBox и создайте такой же обработчик события OnPaint для этого компонента. В нем напишите следующее:
procedure TForml.PaintBoxlPaint(Sender: TObject);
begin
PaintBoxl.Canvas.Rectangle(10r10,100,100);
end;
Здесь вызывается тот же метод с такими же параметрами, только для PaintBox. Эго значит, что данный квадрат будет рисоваться уже внутри компонента PaintBox.
Попробуйте запустить приложение, и вы увидите два квадрата (рис. 12.2). Оба они рисуются с помощью метода Rectangle с одними и теми же параметрами и по идее они должны быть нарисованы в одном и том же месте. Но на деле это не так, потому что первый квадрат рисуется на форме, и координаты его отсчитываются относительно формы (10, 0, 100, 100), а второй — внутри компонента, и координаты отсчитываются относительно этого компонента (10, 0, 100, 100).
Рис. 12.2. Окно запущенной программы
工 Примечание ;
На компакт-диске в папке \ Примеры\ Глава 12\ Rectangle вы можете увидеть пример этой программы.
Почему в примере рисование происходит именно в обработчике события onPaint? Просто событие генерируется каждый раз, когда нужно прорисовать окно. Создайте новое приложение и поместите код рисования квадрата в обработчик события Onshow. В лучшем случае вы увидите квадрат, но если свернуть окно или просто перекрыть другим и потом снова показать его, то квадрат исчезнет.
(Совет )
Windows не запоминает графику, которая была в окне, и полагается на программу. Единственное, что ОС делает— направляет программе событие Paint, которое указывает на необходимость самостоятельной перерисовки содержимого окна. Именно поэтому желательно производить рисование в обработчике OnPaint или хотя бы вызывать в нем функцию, которая будет восстанавливать содержимое графики в окне.
Свойства карандаша
Теперь давайте разберемся с цветом. Как мы уже знаем, для рисования используются два понятия цвета — цвет карандаша и цвет кисти. За стиль карандаша (в том числе и цвет) отвечает свойство Реп объекта TCanvas. За стиль кисти отвечает свойство Brush. И Brush и Реп — это тоже объекты. У них есть свои свойства, о которых мы и поговорим в этом разделе главы.
Для начала разберемся с объектом треп. У него есть ряд свойств. Рассмотрим их.
口 color — цвет карандаша.
口 Handle — здесь находится описание карандаша, которое можно использовать при обращении напрямую к WiпАРI-функциям.
工 Примечание 】
У большинства объектов есть свойство Handle, которое нужно только для API- функций. В обычных программах его использовать не будем.
П Mode — режим отображение показывает, как будет рисоваться линия.
П style — стиль карандаша. Существуют следующие стили (графическое отображение стилей линий вы можете увидеть на рис. 12.3):
pssolid — сплошная линия;
psDash — линия в виде пунктира (состоит из коротких линий);
psDot — линия из точек;
psDashDot — линия с чередующимися черточками и точками;
psDashDotDot — линия с чередующимися черточками и двумяточками;
—psSolid
—psDash
二psDot
—psDashDot
psDashDotDot
psInsideFrame
Рис. 12.3. Стили линий
psClear ——невидимая линия;
psi ns ide Frame — линия внутри формы (внешне похожа на сплошную).
口 width — ширина карандаша.
Теперь давайте напишем пример, в котором увидим на практике свойства карандаша в действии. Создайте новое приложение. Создайте обработчик события OnPaint и напишите в нем код, представленный в листинге 12.1.
Листинг 12.1. Рисование карандашом
procedure TForml.FormPaint(Sender: TObject);
begin
//Рисуем сплотшаую линию (psSolid)
Canvas.Pen.Style:=psSolid;
Canvas.MoveTo(10,20);
Canvas.LineTo(200,20);
//Рисуем psDash линию
Canvas.Pen.Style:=psDash;
Canvas.MoveTo(10,40);
Canvas.LineTo(200,40);
/ Рисуем psDash линию
Canvas.Pen.Style:=psDot;
Canvas.MoveTo(10,60);
Canvas•LineTo(200/60);
//Рисуем psDashDot линию
Canvas.Pen.Style:=psDashDot;
Canvas.MoveTo(10,80);
Canvas.LineTo(200, 80);
/Рисуем psDashDotDot линию
Canvas.Pen.Style:=psDashDotDot;
Canvas.MoveTo(10,100);
Canvas.LineTo(200,100);
//Рисуем psClear линию
Canvas.Pen.Style:=psCleNr;
Canvas.MoveTo(10,120);
Canvas.LineTo (200,120);
//Рисуем psInsideFrame л亚тию
Canvas.Pen.Style:=psInsideFrame;
Canvas.MoveTo(10,140);
Canvas.LineTo(200f140);
end;
Результат работы программы вы можете увидеть на рис. 12.4.
Рис. 12.4. Результат работы программы
В данном примере по событию onPaint (когда надо перерисовать форму) поочередно рисуются линии разного стиля. Для этого сначала выбирается нужный стиль (например, ' Canvas. Pen .Style : =psSolid — выбирает стиль сплошной линии).
Потом карандаш перемещаеся в точку начала линии— canvas. MoveTo (X, Y). Метод MoveTo перемещает карандаш в позицию, указанную в качестве параметров х, Y. При перемещении не происходит никакого рисования на холсте (canvas), х и Y — это не сантиметры и не миллиметры, а количество пикселов (количество экранных точек).
Отсчет координаты х идет слева. Это значит, что левая сторона окна равна нулевой позиции х, а правая сторона окна — это максимальное значение. Но это не означает, что х не может быть отрицательным или больше максимума. Вы без проблем можете указывать любые значения, только нужно учитывать, что часть линии может уйти за пределы окна.
Отсчет координаты Y идет сверху. Верхнее обрамление окна является нулевой точкой Y. При этом заголовок окна (с названием формы и системными кнопками) не входит в пространство клиентской области окна.
Теперь мы должны нарисовать линию с помощью метода LineTo (X, Y). В качестве параметров передаются конечные координаты линии. Отрезок будет нарисован от текущей позиции карандаша, куда мы перешли с помощью метода MoveTo, и до координат, указанных при вызове метода LineTo.
После прорисовки первой линии выбираем следующий стиль и перемещаемся в позицию на 20 пикселов ниже уже нарисованной линии. После этого рисуем следующую линию.
Теперь добавим в нашу программу возможность смены цвета карандаша. Для этого установим на форму кнопку с надписью "Изменить цвет" и компонент ColorDialog с вкладки Dialogs. Компонент CoiorDialog предназначен для отображения стандартного диалога выбора цвета. На форме он будет выглядеть в качестве простого квадратика с пиктограммой и при запуске не будет виден.
Для события onclick кнопки напишем следующий код:
if ColorDialogl.Execute then
Canvas.Pen.Color:=ColorDialogl.Color;
FormPaint(nil);
В первой строке кода отображается окно выбора цвета (ColorDialogl .Execute). Если пользователь выбрал цвет, а не нажал Отмена, то окно возвращает значение true. Поэтому мы проверяем, если результат показа окна равен true, то изменить цвет:
if ColorDialogl.Execute then
Изменить цвет холста
Напоминаю, что по умолчанию конструкция if проверяет указанный код на равенство true, если не указано обратное. В связи с этим эту же конструкцию можно было бы записать так:
if ColorDialogl.Execute=true then
Изменить цвет холста
Результат выбранного цвета записывается в свойство color компонента ColorDialogl. Именно его мы и присваиваем цвету карандаша Canvas. Pen. Color. После этого нужно только перерисовать рисунок. Для этого явно вызывается процедура — обработчик события OnPaint формы. У нас обработчик называется FormPaint, именно его и необходимо вызывать.
Можете запустить программу и проверить результат работы смены цветов линий.
Теперь добавим возможность выбора толщины линии. Для этого установим компонент Up Down с вкладки \^п32. Для события Onclick этого компонента, напишем следующий код:
procedure TForml.UpDownlClick(Sender: TObject; Button: TUDBtnType);
begin
Canvas•Pen.Width:=UpDownl.Position;
Repaint;
end;
Компонент UpDown состоит из двух кнопок. Верхняя увеличивает внутренний счетчик. Нижняя уменьшает. Текущее значение счетчика можно прочитать в свойстве Position. Именно это значение мы и присваиваем в свойство ШИРИНЫ карандаша Canvas. Pen .Width.
После этого вызывается метод главной формы Repaint. Этот метод генерирует событие о том, что надо перерисовать содержимое окна. Это значит, что будет автоматически вызван обработчик события OnPaint. Результат тот же, что и просто вызов напрямую обработчика, как это делалось после смены цвета. Но такой способ считается более правильным.
(Совет )
Если говорить о том, какой способ более правильный, то оба они работают без проблем, просто второй способ более эстетичный и красивый, хотя и требует дополнительных затрат на генерацию сообщения о необходимости перерисовать окно.
工 Примечание 〉
На компакт-диске в папке \ Примеры \ Глава 12\ Реп вы можете увидеть пример этой программы.
Свойства кисти
За параметры кисти отвечает свойство Brush объекта TCanvas. Как уже говорилось, кисть используется для закраски замкнутых пространств. Она тоже имеет объектный тип, как и карандаш, а значит, обладает своими свойствами и методами.
У объекта кисти TBrush есть несколько свойств, влияющих на параметры кисти.
□ Bitmap — картинка, которая будет использоваться в качестве фона закраски. Картинка должны быть формата 8x8 пикселов. Если будет больше, то задействованы будут только пикселы верхнего левого квадрата 8x8.
Мы пока не трогали картинки, поэтому это свойство рассматриваться пока не будет. Единственное, что можно сделать — написать небольшой пример, по которому вы сможете вернуться к свойству и разобраться самостоятельно. Код использования свойства Bitmap показан в листинге 12.2.
Листинг 122 Использование свойства Bitmap объекта TBrush
var
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create; //Создается картинка
try
Bitmap. LoadFromFi le (* MyBitmap. bmp1) ; / /Загружается картинка
Forml.Canvas.Brush.Bitmap := Bitmap; //Присваивается в качестве фона
Forml.Canvas.Rectangle(0,0,100.100);'Рисуется квадрат
finally
Forml.Canvas.Brush.Bitmap := nil; Обнуляется фон
Bitmap.Free; Уничтожается картинка
end;
end;
Color — так же как и у карандаша, у кисти тоже может быть свой цвет.
Handle — такой же указатель, как и у карандаша, но на кисть.
О style — стиль фона. Здесь могут быть следующие значения: bsSolid, bsClear, bsHorizontal, bsVertical, bsFDiagonal, bsBDiagonal, bsCross, bsDiagCross. На рис. 12.5 вы можете увидеть графическое отображение каждого из стилей.
Рис. 12.5. Стили фона
Теперь перейдем к практической части работы с кистью и напишем небольшой пример. Для начала создайте новый проект.
Как и в предыдущем примере, создадим обработчик события onPaint для формы, чтобы по этому событию производить рисование. В обработчике напишем содержимое листинга 12.3.
ЛИСТИНГ 12.3. Обработчик события OnPaint
procedure TForml.FormPaint(Sender: TObject); begin
Canvas.Brush.Style:=bsSolid;
Canvas.Rectangle(10,10,50,50);
Canvas.Brush.Style:=bsBDiagonal;
Canvas.Rectangle(10,110,50,150);
Canvas.Brush.Style:=bsFDiagonal;
Canvas.Rectangle(10,160,50,200);
Canvas.Brush.Style:=bsCross;
Canvas.Rectangle(110 r10,150,50);
Canvas.Brush.Style:=bsDiagCros§;
Canvas.Rectangle(110,60,150,100);
Canvas.Brush.Style:=bsHorizontal;
Canvas.Rectangle(110,110,150,150);
Canvas.Brush.Style:=bsVertical;
Canvas.Rectangle(110,160,150,200);
Canvas.Brush.Style:=bsClear;
Canvas.Rectangle(10,60,50,100);
end;
Здесь код разбит на блоки по две строчки. В первой строчке задается стиль кисти, а во второй рисуется прямоугольник с помощью метода Rectangle(х, у, г, Ь), где:
х — левая сторона прямоугольника;
у — верхняя сторона прямоугольника;
г — правая сторона прямоугольника;
ь — нижняя сторона прямоугольника.
Чтобы было более ясно, взгляните на рис. 12.6.
Рамка окна
Рис. 12.6. Размеры прямоугольника
Таким образом, мы рисуем восемь прямоугольников с разными стилями кисти. Если вы запустите сейчас этот пример, то не заметите никакой разницы, все прямоугольники будут одинаковыми. Это потому, что цвет кисти имеет такой же цвет, что и форма. В результате все сливается. Чтобы увидеть разницу, надо изменить цвет фона Кисти.
Давайте установим на форму кнопку Изменить цвет и компонент ColorDialog. Для события, связанного с нажатием кнопки, напишем следующий код:
if ColorDialogl.Execute then
Canvas•Brush•Color:=ColorDialogl.Color;
FormPaint(nil);
Здесь запускается окно изменения цвета, и если цвет выбран, то присваиваем его КИСТИ Canvas.Brush.Color:=ColorDialogl.Color.
Вот теперь можно запускать программу и смотреть результат. Щелкните по кнопке Изменить цвет и выберите какой-нибудь из темных цветов, например, синий. Результат показан на рис. 12.7.
Примечание
Прямоугольник с невццимой кистью (style = bsClear) рисуется последним, хотя на форме он расположен во второй строке первой колонки. Это связано с тем, что после рисования с невццимой кистью цвет теряется. Попробуйте поставить рисование с невидимой кистью где-нибудь раньше, и увидите, что первоначально у прямоугольника с прозрачной кистью фон будет того цвета, что был выбран, а после будет белого цвета.
Рис. 12.7. Результат работы программы
(Совет )
Если программу свернуть и потом развернуть, то цвет кисти опять же будет белого цвета. Это связано с тем, что когда мы нарисовали последний квадрат (который был с прозрачным фоном), цвет кисти все же изменился на белый. Мы этого не увидели, потому что последний квадрат был с прозрачным фоном. При следующей прорисовке цвет уже изначально белый. Чтобы избавиться от этого эффекта, после каждого рисования фигур прозрачной кистью надо устанавливать цвет. Для большей надежности желательно устанавливать цвет непосредственно перед рисованием.
Любые установленные цвета желательно после рисования восстанавливать в исходное состояние. Когда у вас маленький проект, то легко вычислить, почему во время работы программы отображается не тот цвет, но в большом проекте такое сделать будет сложно.
Не устанавливайте цвета в обработчиках событий для каких-либо кнопок или пунктов меню. Все это необходимо делать непосредственно в функции рисования, как показано в следующем примере:
procedure TForml.FormPaint(Sender: TObject);
begin
/ /Обязательно устанавливаю цвет кисти*
Canvas.Brush.Color:=ColorDialogl.Color;
/ /Рисую первый квадрат
Canvas.Brush.Style:=bsSolid;
Canvas.Rectangle(10 f10,50,50);
Хотя мы могли не изменять цвет кисти в данном примере, потому что это уже сделано после вывода окна выбора цвета, мы все же для большей надежности изменяем цвет в функции рисования.
~ Примечание …<
На компакт-диске в папке \ Примеры \ Глава 12\ Brush вы можете увидеть пример этой программы.
Работа с текстом в графическом режиме
Конечно же, название этого раздела достаточно расплывчато и неточно, потому что Windows сам по себе графический и вся работа происходит в графическом режиме. Но все же иногда мы работаем с текстом, воспринимая его как текст, а иногда текст выводится в виде графики. *
Для вывода текста на экран у объекта TCanvas есть метод Textout. У этого метода три параметра:
П х — координата позиции текста;
П У — координата позиции текста;
П непосредственно строка текста, которую надо вывести.
Создайте новое приложение и для события onPaint напишите код:
procedure TForml . FormPaint (Sender: TObject);
begin
Canvas. Text Out (100r100f. •Приветвсем! ! !');
end;
Здесь производится вывод на экран текста в координатах (100, 100).
За стиль шрифта отвечает свойство Font объекта TCanvas. Это свойство также имеет объектный тип (TFont), у которого очень много свойств. Среди них, конечно же, есть и свойство color, так что поместим на форму кнопку, и ColorDialog, чтобы можно было менять цвет текста.
Для события onclick кнопки пишем следующее:
procedure TForml.ButtonlClick(Sender: TObject); begin
if ColorDialogl , Execute then
FormPaint(nil);
end;
Здесь показывается окно выбора цвета, и если цвет выбран, то просто происходит перерисовка окна. А где же изменение цвета шрифта? Как уже говорилось, когда мы работали с кистью, цвет нужно менять непосредственно перед рисованием, и здесь это делать нет смысла. Поэтому корректируем событие OnPaint:
procedure TForml.FormPaint(Sender: TObject); begin *
Canvas.Font.Color:=ColorDialogl.Color;
Canvas.TextOut (100, 100,1Привет всем! !!*);
end;
Теперь установим на форму компонент FontDialog с вкладки Dialogs. Это почти такой же компонент, как мы использовали для смены цвета, только здесь будет появляться стандартное окно смены шрифта. Добавьте на форму кнопку с надписью **Изменить шрифт** и для ее события Onclick напишите следующее: procedure TForml.Button2Click(Sender: TObject); begin
if FontDialogl.Execute then
Canvas.Font:=FontDialogl.Font;
FormPaint(nil); end;
В первой строчке кода показывается окно смены шрифта так же, как это делалось для изменения цвета. Если пользователь выбрал шрифт и нажал ОК, то устанавливается его свойство Font объекта Canvas. После этого мы заставляем форму обновить свое содержимое. При новой прорисовке содержимого формы текст уже выводится с новым шрифтом.
Единственный недостаток этого примера заключается в следующем. Если сначала выбрать большой шрифт, а затем маленький, то старое содержимое текста, написанного большим шрифтом, не уничтожается, а новый рисуется поверх старого (рис. 12.8).
Самый простейший способ избавиться от такого нежелательного эффекта — после смены шрифта вместо прямого вызова функции FormPaint (nil) использовать вызов метода формы Repaint или invalidate. О первом из них мы уже говорили и использовали его. Второй метод — invalidate — имеет практически тот же смысл, только заставляет прорисоваться полностью все окно, а не только его элементы.
Итак, мы научились менять все параметры текста с помощью стандартного диалогового окна. Как менять отдельные свойства — это отдельная тема, ведь свойство Font имеет объектный тип TFont с массой свойств: имя шрифта, стиль, цвет, размер. Но все это отдельная тема и мы ее пока опустим.
Рис. 12.8. Нежелательный эффект при смене шрифта
Примечание
На компакт-диске в папке \ Примеры \ Глава 12 \ Text вы можете увидеть пример этой программы.
Вывод текста под углом
Сейчас мы рассмотрим один интересный прием — как можно вывести текст под углом. Как вы могли заметить, пока использовалась только функция, которая умеет выводить тест горизонтально, и никаких больше намеков на возможность развернуть текст и написать его, например, вертикально.
Создайте новый проект. Теперь создайте обработчик события OnCreate для главной формы. В этой процедуре напишите следующее:
procedure TForml.FormCreate(Sender: TObject);
begin
index:=0;
end;
index — это будет счетчик, но его еще надо объявить, поэтому переходим в объявления private и там пишем следующее:
private
{ Private-Deklarationen }
index:Integer;q
cl:Boolean;
Основные приготовления закончены, и мы можем переходить непосредственно к программированию. Назначение переменных мы рассмотрим позже, а сейчас создайте обработчик события для главной формы OnMouseDown. Это событие срабатывает, кодца пользователь щелкнул мышью по форме. В нем надо написать содержимое листинга 12.4.
ЛИСТИНГ 124. Обработчик . ОбЫТИЯ OnMouseDown
procedure TForml.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
Var
Ar Integer; // Объявление переменной A - целое число. begin
A := randbm(3600);
CanvasSetAngle(Canvas, A / 10);
Canvas.TextOut(X, Y, FormatFloat(*##0.01, A/10) +1 ° 1);
end;
Давайте рассмотрим текст этой процедуры. В первой строчке используется функция random, она возвращает случайное значение, но не больше чем число, указанное в скобках. В нашем случае это — 3600.
Вторую строчку пока опустим, а рассмотрим третью. Canvas. TextOut — выводит текст на форме, и мы с ней уже работали.
Для вывода текста использовалась функция Format Float. Она переводит число с запятой (вещественное, или, так сказать, дробное) с учетом формата.
function FormatFloat (
const Format: string; // Строка формата
Value: Extended // Число
В качестве формата указано ##0.0, что приводит указанное число к этому виду, т. е. отрезает все знаки после запятой, оставляя только одно (об этом говорит один ноль после запятой в строке формата). Перед запятой может быть любое количество чисел, потому что стоит два знака решетки. В качестве числа указывается переменная А, деленная на 10.
Теперь возвращаемся ко второй строке CanvasSetAngle. Этой процедуры еще нет, мы ее должны написать. Посмотрите на листинг 12.5, дце находится весь текст программы, а потом мы рассмотрим эту процедуру отдельно.
Листинг 12. Пгограммы
unit Textroti;
interface
uses
SysUtils, WinTypesr WinProcs, Messages, Classes z Graphics, Controls z Forms, Dialogs, ExtCtrls;
type
TForml 二 class(TForm)
procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure TimerlTimer(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
( Private Deklarationen }
index:Integer;
cl:Boolean;
public
{ Public-Deklarationen }
end;
var
Forml: TForml ;
implementation
($R *.DFM)
procedure CanvasSetAngle(C: TCanvas; A: Single); var
LogRec : TLOGFONT ; / / Объявляем перемениую логического шрифта begin
GetObject (C.Font.Handle. SizeOf (LogRec), Addr (LogRec));
LogRec.1fEscapement :二 Trunc(A*10);
LogRec.1fOrientation :二 Trunc((A+10) * 100);
C.Font•Handle : = CreateFontlndirect(LogRec); end;
procedure TForml.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
Va工 A: Integer;
begin
A := Random(3600);
CanvasSet/kngle (Canvas, A / 10);
Canvas.TextOut(x, y, FormatFloat(* ##0.0 *, A/10) + * ° *);
end;
procedure TForml.FormCreate(Sender: TObject); begin
index:=0;
1
Canvas.Brush.Style:=bsClear;
end;
end
В качестве параметров для функции CanvasSetAngle процедуре передается Canvas -и угол разворота текста. Угол разворота имеет значение single, что означает вещественное (дробное). Ранее имя процедур вместо нас писал Delphi. Данную процедуру вам придется вписывать своими руками, потому что она самостоятельная и не принадлежит никакому объекту. Это очень важно, потому что если она будет принадлежать объекту, то текст не развернется.
Теперь перейдем к содержимому процедуры. Рассмотрим цо частям первую строчку:
Getobject — эта функция возвращает информацию о графическом объекте.
С. Font. Handle — объект, на который нужно получить значение.
sizeOf (LogRec) — передаем размер возвращаемого значения.
Ad dr (LogRec) — передаем адрес возвращаемого значения.
С помощью этой функции"мы получаем информацию о шрифте, используемом нами для рисования. Вторая и третья строчки процедуры изменяют значения полученной информации. Четвертая функция записывает измененную информацию.
Запустите получившееся приложение и цошелкайте мышью цо форме. В месте щелчка должен появиться текст цод случайным углом.
Теперь поставьте на форму компонент Timer, который находится на вкладке System. Этот компонент умеет генерировать события через определенные промежутки времени. Создайте для таймера обработчик события Опт inter и напишите в нем содержимое листинга 12.6.
Листинг 12.6. Обработчик события таймера
procedure TForml.TimerITimer(Sender: TObject); begin
Canvas SetAngie (Canvas, index);
Canvas.TextOut(100, 100, *CyD Soft *);
index:=index+45;
if index>=360 then
begin
index:=0;
if cl then
Canvas.Font.Color:=clBlack
else
Canvas.Font.Color:=clRed;
cl:=not cl;
end;
end;
Эта процедура будет вызываться каждый раз, когда пройдет интервал времени, указанный в свойстве interval (интервал) компонента Timer. По умолчанию там указано 1000 (число в миллисекундах, что равно 1 секунде), значит, процедура будет вызываться через каждую секунду. Если свойство Enabled компонента Timer выставлено в true, то таймер генерирует события, иначе он отключен и событие OnTimer срабатывать не будет.
Внутри процедуры выставляется угол, на который надо вывести текст (Canvas Set Angle), ПОТОМ ВЫВОДИТСЯ сам текст (с ПОМОЩЬЮ Canvas. TextOut).
После этого прибавляем переменной index значение 45. В этой переменной хранится значение угла, под которым надо вывести текст, и через каждую секунду это значение увеличивается на 45 градусов.
Далее идет проверка — если index больше или равен 360, значит, программа просчитала полный круг, а если так, то выполняется следующий код:
index:=0;
if cl then
Canvas. Font. Color: =clBlack
else
Canvas.Font.Color:=clRed;
cl:=not cl;
Здесь в первой строке обнуляется переменная index, чтобы начать вывод текста с 0 градусов (мы же уже прошли полный круг). Дальше проверяется значение переменной с! если она равна true, то значению цвета текста будет присвоено clBlack, что равно черному цвету. Иначе цвет сменится на красный (clRed).
После этого изменяется значение переменной cl на противоположное, о чем говорит конструкция cl : =not cl. Здесь мы присваиваем переменной противоположное (not) значение ее самой. Это значит, что если cl равнялась true, то после этого кода будет равняться false. Таким образом, после
прохождения текстом очередного круга цвет будет меняться с черного на красный, и обратно.
工 Примечание
На компакт-диске в папке \ Примеры \ Глава 12\TextAngle вы можете увидеть пример этой программы.
Попробуйте запустить пример. Пускай он немного поработает, чтобы форма заполнилась текстом. Теперь попробуйте перекрыть окно программы другим окном или свернуть его. Потом снова восстановите окно. Что вы видите? Все, что было нарисовано — исчезло. Это потому что Windows не сохраняет содержимое окна. Мы сами должны его восстанавливать.
Когда окно свернулось и восстановилось, то генерируется событие OnPaint, по которому нужно перерисовать содержимое окна. Поэтому все функции рисования стараются располагать именно в обработчике этого события. Так мы будем рисовать и реагировать на события, когда надо перерисовать содержимое зкрана одной и той же функцией.
В нашем примере использование события OnPaint неудобно, потому что зкран меняется динамичесьси и мы не можем постоянно запоминать его содержимое. В таких случаях на форму ставят компонент тImage и рисуют .в нем. Этот компонент, в отличие от формы, сохраняет свое содержимое. Когда вы рисуете на холсте компонента тImage, то делаете это в специально отведенной под компонент памяти. А вот когда Timage нуждается в прорисовке, эта область памяти копируется на экран. Таким образом, не надо само стоятельно ни за чем следить. Подробнее с компонентом Timage мы познакомимся немного позже.
工 Примечание ;
Таким образом развернуть можно только TrueType-текст. В последних версиях Delphi в качестве шрифта по умолчанию для холста используется именно такой шрифт. В старых версиях может понадобиться явное указание шрифта в свойстве Canvas. Font. Если вы сами захотите изменить шрифт, то меняйте его только на TrueType.
Работа с цветом
Мы уже научились менять цвет и даже в предыдущем разделе узнали, что константа clBlack равна черному цвету, a clRed — красному. Но есть еще много констант, которые определяют стандартные цвета для более удобного их использования. Вот именно с ними нам предстоит сейчас познакомиться и узнать, как хранится цвет в памяти машины.
Цвет хранится в виде типа TColor. Хотя в названии типа вначале стоит буква т, этот тип не объектный, а просто число из 4 байт, но реально нас будут интересовать только последние три.
Вы наверняка должны знать, что в компьютерной графике цвет представляется тремя составляющими: красным, зеленым, голубым цветом (RGB — Red, Green, Blue). В разных пропорциях из этих трех базовых цветов можно получить любой другой. Например, если взять красного и зеленого по максимуму, а синего вообще не брать, то получится желтый цвет.
Каждый из цветов представляется в виде одного байта, так что для хранения трех цветов достаточно 3 байтов. Но зачем же тогда для TCoior выделено четыре байта? Это сделано потому, что в компьютере регистры четные и могут хранить только 1, или 2, или 4 байта. Так что у переменной цвета один байт избыточен (первый) и чаще всего он равен нулю. В дальнейшем он может быть использован для обозначения прозрачности.
Конечно же, пр огр аммисты могли поместить в шесть байт две точки цвета (3 + 3), но этого не стали делать для использования в будущем 32-битного цвета. Сейчас же более распространены 24-битные градации.
В играх и графических пакетах этому байту нашли применение. Он часто указывает прозрачность, но в офисных приложениях его просто игнорируют.
Как вы уже знаете, один байт может принимать значения от 0 до 255 (в десятичной форме) или от 0 до FF (в шестнадцатеричной). Так что в шестнадцатеричной форме цвет будет выглядеть как SOOFFFFFF. Только тут сразу надо отметить, что первые два нуля — это лишний байт, потом идут FF для голубого цвета, затем FF для зеленого и последние FF для красного. Получается, что в памяти цвет хранится как BGR (в обратном порядке). Абсолютно красный цвет будет равен $OOOOOOFF, абсолютно зеленый — SOOOOFFOO, а голубой 一 SOOFFOOOO.
Давайте попробуем научиться работать с цветом на практике, заодно и познакомимся с необходимыми функциями. Создайте новое приложение и установите на него компоненты так, как это показано на рис. 12.9.
Итак, на форме три компонента TEdit. Для красного цвета компонент назовите RedEdit, для зеленого GreenEdit, ну, и для синего BlueEdit. Также на форме есть кнопка для смены цвета (ее имя не имеет значения) и компонент ColorDialog — для смены цвета.
Если вы сами создаете пример, а не пользуетесь готовым с компакт-диска, постарайтесь все разместить так, как показано на рис. 12.9 (ближе к правому краю), потому что слева будем рисовать квадрат.
Для события кнопки onclick пишем код, показанный в листинге 12.7.
Рис. 12.9. Форма будущей программы
Листинг 12.7. Обработчик события для кнопки
procedure TForml.ButtonlClick(Sender: TObject); var
c:LongInt;
begin
if not ColorDialogl.Execute then exit;
C:=ColorToRGB(ColorDialogl.Color);
RedEdit.Text:=IntToStr(GetRValue(C));
GreenEdit.Text:=IntToStr(GetGValue(C));
BlueEdit.Text:=IntToStr(GetBValue(C));
Repaint;
end;
В разделе var объявлена одна переменная целого типа Longint. Это целое число размером в 4 байта и оно будет использоваться его для хранения значения цвета.
В первой строчке показывается окно смены цвета ColorDialogl. Execute. Если пользователь не выбрал цвет (об этом говорит конструкция if not), то выполнение процедуры прерывается с помощью exit.
Дальше выбранный цвет ColorDialogl. Color из типа TColor преобразовывается В простое ЧИСЛО С ПОМОЩЬЮ функции ColorToRGB. Этой функции на-
13 Зак. 942
до передать цвет в виде TColor (мы передаем ColorDialogl. Color), и она вернет целое 4-байтное число, которое мы записываем в переменную с.
В следующей строке идет присвоение строке ввода RedEdit значения красной составляющей цвета. Для этого сначала используется функция GetRValue. Ей передается значение цвета в виде целого числа (переменная с). Результат — 1-байтное число, которое показывает значение красной составляющей. Этот результат — число, и прежде чем его присваивать в строку ввода, оно должно быть преобразовано в строку. Для этого переводим его в текст с помощью знакомой нам функции intToStr.
То же самое проделываем и с зеленым цветом в следующей строке кода. Только для получения зеленой составляющей используется функция GetGValue.
Для получения синей составляющей используется функция GetBValue. Таким образом, после выполнения всех этих действий мы разбили 4 байта цвета из переменной с на отдельные байты по цветам и разнесли их в соответствующие строки ввода.
После этого нужно заставить окно прорисоваться с помощью вызова метода Repaint.
Для события onPaint напишем следующий код:
procedure TForml.FormPaint(Sender: TObject);
begin
Canvas.Brush.Color:=RGB(StrToIntDef(RedEdit.Text, 0),
StrToIntDef(GreenEdit.Text, 0).
StrToIntDef(BlueEdit.Text,0));
Canvas.Rectangle(10,10, 250, 150);
end;
Здесь надо проделать обратные действия — превратить три составляющих цвета из строк ввода в одно целое значение цвета. Для этого используется функция rgb(r, G, в). У этой функции три параметра и все они — целые числа:
G R — значение красного цвета;
□ G — значение зеленого цвета;
О в — значение синего цвета.
В качестве параметров передаются значения, указанные в соответствующих строках ввода, с предварительным преобразованием их из строк в числа.
Результат преобразования цвета записываем в цвет кисти. После этого рисуется прямоугольник, у которого цвет фона будет тот, что мы выбрали.
И последнее — создадим обработчик события onchange для всех строк ввода.
Выделите строку ввода для красного цвета.
Удерживая <Shift>, щелкните мышью по остальным строкам.
У вас должны быть вьщелены все строки ввода серыми рамками. Теперь перейдите в объектный инспектор на вкладку Events и дважды щелкните мышью по событию onchange, чтобы создать обработчик сразу для всех выделенных компонентов. В нем напишите следующий код: procedure TForml.RedEditChange(Sender: TObject);
begin
Repaint;
end;-
Попробуйте запустить этот пример. Теперь выберите какой-нибудь цвет, и вы увидите составляющие этого цвета. Можете даже напрямую изменять значения в строках ввода, и результат моментально будет отражаться на цвете прямоугольника.
Внимание
Помните, что ни одна из составляющих цвета не может быть больше 255.
Примечание
На компакт-диске в папке \ Примеры \ Глава 12 \ Color вы можете увидеть пример этой программы.
jFoiml
Properties j Events j i ClienlH eight )200 ! QentWidth ‘422
I Color
clBtnFace
tE ConstraM с:1Йаск— ! Ctl3D IBclMaroon [Си/sor ИсЮгееп
I DefaultJWc:IOIive
i D8ksim d№vy
lEFont
i FormStyle
I Height “
I HelpContext
(TFont) fsNormal 227 ■ '0
iAII shown
Рис. 12.10. Цветовые константы
Ну, а теперь познакомимся с константами, которые уже заготовлены для основных цветов. Вы можете их реально использовать в своих программах и присваивать, как ранее это делалось в примере (см. разд. 12.6). Здесь не будут перечисляться все константы, потому что вы можете их сами в любой момент найти, если щелкнете в объектном инспекторе по свойству color любого компонента (рис. 12.10). Все, что вы увидите в этом спи*ске, это и есть константы, которые можно использовать. Этот прием довольно удобный, потому что сразу видно константу и цвет.
Методы объекта TCanvas
Последнее, что нам надо узнать в этой главе книги, — это методы рисования. Пока что мы использовали только линии, прямоугольники и текст, но на этом возможности объекта TCanvas не заканчиваются.
Pixels
Свойство Pixels — двухмерный массив, указывающий на битовую матрицу изображения. Что это значит? Проще всего показать. Допустим, что вам нужно поставить точку черного цвета в координатах (10,10). Для этого вы пишете следующий код:
Canvas.Pixels[10f10]:=Black;
С помощью этого же свойства можно узнать цвет в какой-нибудь точке. Например:
var
c:TColor;
begin
с:=Canvas.Pixels[10,10];
if c=clBlack then
//Точка с координатами (10, 10) черного цвета
End;
Как видите, свойство Pixels это самый настоящий двухмерный массив из цветов точек нашего изображения. Вы легко можете получить доступ к любому пикселу картинки, прочитать его или изменить. Только не думайте, что так можно посимвольно строить целые изображения. Доступ к массиву слишком медленный и после каждого изменения цвета любого пиксела картинка будет прорисовываться, а при обновлении всей картинки таким способом рисование будет происходить слишком долго.
TextWidth и TextHeight
Давайте сразу познакомимся еще с двумя методами для работы с текстом — Textwidth и TextHeight. Обоим методам нужно передать какой-нибудь текст, и первый из них вернет его ширину, а второй — высоту в пикселах. Эти методы очень удобны, когда нужно выводить форматированный текст.
Допустим, что необходимо вывести две строки текста. Вы можете вывести одну из них, а потом чуть ниже вывести другую. А вот теперь самое интересное —на сколько ниже? Ведь если взять слишком много, то будет большой промежуток, а если слишком мало, то одна строка наедет на другую или попросту ее перекроет. Эти методы позволяют точно узнать длину или высоту текста в зависимости от используемого в данный момент шрифта.
「Совет )
Используйте их каждый раз, когда это нужно и не надейтесь на собственные расчеты, потому что шрифт на разных машинах может выглядеть по-разному. Все зависит от настроек экрана.
Аге
Следующий метод объекта TCanvas — это Arc, который предназначен для рисования дуги. У него 8 параметров: XI, Yl, х2, Y2, хз, Y3, Х4, Y4. Как видите, это 4 пары координат х и Y, которые указывают 4 точки, через которые надо провести дугу.
CopyRect
Этот метод предназначен для копирования указанного прямоугольника из одного объекта TCanvas в другой. У метода три параметра:
П Dest: TRect — область, указывающая? куда надо копировать;
口 canvas: TCanvas — объект, из которого надо копировать;
□ source: TRect — область, указывающая, откуда надо копировать.
Первый и последний параметры имеют тип TRect. Это простая структура из четырех целых чисел — Left, Top, Right, Bottom. Не трудно догадаться, что это координаты прямоугольника. Для создания переменной такого типа лучше всего использовать функцию Rect. Ей нужно передать четыре этих параметра Left, Top, Right, Bottom и она вернет вам готовую структуру.
Давайте рассмотрим пример и увидим все на практике. Допустим, у нас есть две формы. Мы хотим из второй формы Form2 скопировать все ее содержимое в первую форму Forml. При этом отобразим содержимое второй формы
на первой в прямоугольнике размером (10, 10, 110, ПО), т. е. программный код, выполняющий эти действия, будет выглядеть так:
var
SRectr DRect : TRect; // Объявляю две переменные типа TRect
begin
SRect:=Rect(0, 0, Fonn2.Width, Form2.Height);
DRect:=Rect(10,丄0, 110, 110);
Forml.Canvas1.CopyRect(DRect, Form2.Canvas, SRect);
end;
В первой строке мы заполняем структуру SRect с помощью функции Red. При этом указываем полные координаты окна Form2 т. е. (0, о, ширина второй формы, высота второй формы).
Во второй строке заполняется структура DRect с помощью все той же функции Rect. В принципе, ей можно не пользоваться и заполнять поля, как и для любой другой структуры:
DRect.Left:=10;
DRect.Top:=10;
DRect.Right:=110;
DRect.Bottom:=110;
В этом случае кед займет четыре строчки, а это уже недостаток. Лучше все записать одной строкой. Чем проще пишете, тем легче потом воспринимается код.
И последнее — мы копируем холст второй формы в первую. Сразу надо заметить, что если размер области источника больше приемника, то область будет растянута или сжата до размеров приемника. Таким образом, если размеры второй формы больше чем 100x100 (именно в такой квадрат на форме 1 мы хотим скопировать вторую форму), то изображение второй формы будет сжато до размеров 100x100.
Draw
Этот метод тоже предназначен для копирования изображений, но другого формата. У него три параметра: х и Y — координаты, куца копировать: объект типа Tgraphic, который надо копировать. Этот объект мы еще не рассматривали и узнаем о нем чуть позже. Тодца и будут примеры с методом Draw. Сейчас необходимо только запомнить, что этот метод не сжимает и не растягивает картинки при копировании, а оставляет их в неизменном виде.
Ellipse
Метод предназначен для рисования эллипса (овала). Есть две его реализации: procedure Ellipse(XIf Yl, X2, Y2: Integer);
procedure Ellipse(const Rect: TRect);
В первом случае нужно передать четыре координаты прямоугольника, в который будет вписан эллипс. Во втором случае достаточно одного параметра типа TRect (как вы уже знаете, у этой структуры есть все необходимые четыре поля). Какой вы будете использовать — ваше дело. Иногда удобнее один способ, а иногда другой.
FillRect
У этого метода только один параметр — TRect, указывающий область, которую необходимо залить цветом Кисти. В принципе, это то же самое, что и нарисовать прямоугольник.
FloodFill
Заливка. У этого метода четыре параметра. Первые два — х и Y — координаты точки, с которой нужно начинать заливку. Третий параметр — цвет. Последний параметр — способ заливки. Возможны два способа заливки:
fsSurface — залить всю область, ще цвет равен цвету, указанному в третьем параметре;
fsBorder — залить всю область, ще цвет не равен цвету, указанному в третьем параметре.
Этих метсдов пока достаточно. Здесь показаны только основные методы, которые вам могут пригодиться. С ними, и многими другими, мы познакомимся на практике чуть позже.
Компонент работы
с графическими файлами (Tlmage)
Этот компонент вы можете найти на вкладке Additional палитры компонентов. С ним мы уже немного познакомились в разд. 11.5, но тоща рассматривали его только как компонент, который просто располагает на форме красивое изображение. Тоща же не затрагивались другие его возможности, потому что вы еще не были готовы познакомиться с графикой. Сейчас, ко- ща рассмотрели все необходимое, пора разобрать этот компонент по свойствам и методам.
Компонент тimage достаточно универсальный и может отображать картинки разного формата. Но в начальной установке он может загружать файлы только форматов BMP, JPG, JPEG или WMF. Давайте посмотрим, как это делается. Создайте новое приложение и установите на форму одну кнопку и компонент Tim age с вкладки Additional.
Теперь бросьте на форму компонент OpenPictureDialog с вкладки Dialogs. Этот компонент предназначен для отображения на экране стандартного окна открытия картинки. Нам также понадобится кнопка, по нажатии которой мы будем отображать окно открытия картинки и потом загружать выбранную.
Рис. 12.11. Форма буцущей программы
На рис. 12.11 вы можете увидеть форму будущей программы. Но пока готова только форма. Чтобы программа стала полноценной, надо написать код загрузки картинки. По нажатии кнопки Открыть картинку пишем следующий код:
procedure TForml.ButtonlClick(Sender: TObject);
begin
if OpenPictureDialog1.Execute then
Image 1 • Picture. LoadFromFi 1 e (OpenPictureDialogl. FileName); end;
В первой строке отображается стандартное окно открытия картинки. Для ЭТОГО ДОСТаТОЧНО ВЫЗВЯТЬ МеТОД Execute Компонента OpenPictureDialogl, т. е. написать OpenPictureDialogl. Execute. Этот метод возвращает логическое значение. Если оно равно true, то пользователь выбрал файл, иначе
нажал отмену. Именно поэтому мы проверяем результат вызова метода Execute С ПОМОЩЬЮ if OpenPictureDialogl.Execute then.
Если файл выбран, то выполняется следующий код:
Тшадё!. Picture. LoadFromFile (OpenPictureDialog 1. FileName);
Разберем эту конструкцию по частям. У компонента image 1 есть свойство picture. Эго свойство имеет объектный тип (а значит, и свои свойства и методы) TPicture. Этот объект предназначен для работы с изображениями практически любого типа. Он достаточно универсален, в чем мы убедимся очень скоро.
Для загрузки изображения мы используем метод LoadFromFile (загрузить картинку из файла) объекта picture. В качестве единственного параметра этого метода нужно указать имя открываемого файла или полный путь, если картинка находится не в том же каталоге, что и сама программа.
Мы выбираем имя файла с помощью стандартного окна, и полный путь к файлу находится В свойстве FileName компонента OpenPictureDialogl.
Все достаточно просто. Попробуйте теперь запустить программу и посмотреть на результат ее работы. В окне открытия файла посмотрите, какие типы файлов можно открывать. В зависимости от версии и установленной комплектации количество типов может быть от 1 до 3 — BMP, ICO и WMF.
Давайте научим нашу программу работать с форматом JPEG. Не волнуйтесь, это не сложно и нам- не придется писать сложный алгоритм распаковки изображения. В Delphi уже есть все необходимое, надо только это необходимое подключить к проекту.
Для начала переместитесь в раздел uses проекта и подключите туда модуль jpeg. В этом модуле описано все необходимое для работы с форматом изображений JPEG.
В принципе этого достаточно. Осталось только заставить окно открытия файлов показывать фильтр на данный тип файлов. Для этого выделите компонент OpenPictureDialogl и дважды щелкните по свойству Filter. Перед вами откроется окно настройки фильтра, как показано на рис. 12.12.
В этом окне есть несколько заполненных строк. На рисунке только три. В четвертой строке (первой пустой строке) напишите в первой колонке "JPEG Files**, а во второй колонке напишите ”*.jpg". Можете нажимать ОК и запускать программу. Теперь в окне открытия графического файла можно выбрать тип .jpg и открыть нужный файл. Он также будет загружен в компонент image 1, даже не смотря на свой сложный алгоритм сжатия и другой вид хранения данных.
ПримечаниеJ
На компакт-диске в папке \ Примеры \ Глава 12 \ Loading Images вы можете увидеть пример этой программы.
Рис. 12.12. Окно редактирования фильтра
Теперь попробуем модернизировать пример и получить доступ к содержимому картинки. Для этого мы будем копировать изображение, используя прозрачность. Как? Сейчас узнаете.
Создайте обработчик события on Paint для главной формы в уже созданном нами примере. В процедуре FormPaint напишите следующее:
procedure TFoml. FormPaint (Sender: TObject);
begin
Canvas.BrushCopy (Reqt (200,16,200+Imagel .Width, 16+Image 1.Height), Image 1. Picture. Bitmap.
Rect (0, 0, Image 1.Width, Image 1 .Height),
Imagel.Picture.Bitmap.Canvas.Pixels[1,1]);
end;
А в процедуре, ще мы загружаем изображение, нужно в конце добавить вызов метода Repaint, чтобы после открытия графического файла форма прорисовалась заново, и вызвался обработчик onPaint.
Теперь попробуйте запустить программу и загрузить в нее файл типа bmp. Вы должны увидеть результат, подобный тому, что показан на рис. 12.13. Слева у нас находится изображение картинки Imagel, а справа мы делаем копию изображения на форму.
А вот теперь давайте посмотрим, что тут происходит. Код окажется немного сложным, но это только на первый взгляд. Рассмотрим все по частям. Мы используем процедуру Brushcopy у уже знакомого canvas. Эта процедура копирует на canvas картинку.
procedure BrushCopy(
const Dest: TRect; // Область приемника
Bitmap: TBitmap; // Картинка, которая будет копироваться const Source: TRect; // Область источника
Color: TColor); // Прозрачный цвет
Рис. 12.13. Результат работы программы
Область приемника объявлена как TRect, который имеет вид TRect - (Left, Top, Right, Bottom: Integer). С тем, ЧТО находится В скобках, мы познакомились ранее. То же самое и с областью источника. В качестве картинки передается Bitmap из тimage.
В качестве прозрачного цвета мы использовали цвет пиксела в позиции {1,1| картинки Timage. На это указывает запись
Image1.Picture.Bitmap.Canvas.Pixels[1.1]
Попробуем записать ее немного по другому:
Tlmagel. Его^картинка. Bitmap. Холст. Пиксел [ 1_по_оси_Хг 1_по оси_У]
Примечание [
На компакт-диске в папке \ Примеры \Глава 12 \ Imagel вы можете увидеть пример этой программы.
Обратите внимание, что если вы сейчас попытаетесь открыть какой-нибудь не BMP-файл, то программа ничего не отобразит. Это связано с тем, что только BMP-файлы хранят свои изображения в свойстве Bitmap, все остальные хранят в свойстве Graphic или Metafile (для векторного формата). Так что получить доступ к JPEG-изображению таким образом нельзя. Зато мож-
но с помощью метода Draw. Для этого нужно подкорректировать обработчик события OnPaint:
procedure TForml.FormPaint(Sender: TObject);
begin
Canvas.Draw(200, 16, Imagel.Picture.Graphic);
end;
Здесь мы уже рисуем не Bitmap, a Graphic, поэтому программа будет работать корректно.
Векторные файлы, такие как WMF-хранят свои данные в свойстве Metafile. Для их отображения обработчик события OnPaint должен быть таким:
procedure TForml. FormPaint (Sender: TObject); begin
Canvas.Draw(200r 16, Imagel.Picture.Metafile);
end;
Рисование
на стандартных компонентах
Очень часто, для лучшего представления данных, нужно рисовать внутри компонента TListBox. Что здесь имеется в виду? Посмотрите на рис. 12.14, и вы все поймете.
IIIIIIIIIIII 3Рис. 12-14. ListBox с графическими даннымиДля создания этого примера нам понадобится i TListBox. В его свойстве items нужно создать 8 строк, для которых будут выступать числа от 1 до 8. Почему потому, что существует 8 стилей кисти, и у нас будет е изображением каждого стиля.ia форме компонент в качестве заголовков именно эти числа? Да списке 8 элементов сIIIIIIIIIIII 3Рис. 12-14. ListBox с графическими даннымиДля создания этого примера нам понадобится i TListBox. В его свойстве items нужно создать 8 строк, для которых будут выступать числа от 1 до 8. Почему потому, что существует 8 стилей кисти, и у нас будет е изображением каждого стиля.ia форме компонент в качестве заголовков именно эти числа? Да списке 8 элементов с
IIIIIIIIIIII 3
Рис. 12-14. ListBox с графическими данными
Для создания этого примера нам понадобится i TListBox. В его свойстве items нужно создать 8 строк, для которых будут выступать числа от 1 до 8. Почему потому, что существует 8 стилей кисти, и у нас будет е изображением каждого стиля.
ia форме компонент в качестве заголовков именно эти числа? Да списке 8 элементов с
IIIIIIIIIIII 3
Рис. 12-14. ListBox с графическими данными
Для создания этого примера нам понадобится i TListBox. В его свойстве items нужно создать 8 строк, для которых будут выступать числа от 1 до 8. Почему потому, что существует 8 стилей кисти, и у нас будет е изображением каждого стиля.
ia форме компонент в качестве заголовков именно эти числа? Да списке 8 элементов с
Может это покажется странным, но все делается за 1 строчек кода. Конечно же, в одну строку можно записать и 20 операций, но этот случай не учитывается.
Секрет рисования заключается в том, что у компонента TListBoxl свойство Style ДОЛЖНО быть IbOwnetDrawFixed ИЛИ IbOwnerDrawVariable.
После этого создайте обработчик события onDrawitem для этого компонента и в нем пишите содержимое листинга 12.8.
Листинг 12.8. Рисование внутри компонента LxstBox
procedure TForml.ListBoxlDrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
begin
with ListBoxl.Canvas do
begin
Brush.Color:—clRed; // Задаем красный цвет кисти.
Brush. Style :=TBrushStyle (Index) ; //Выбираем стиль кисти
Pen. Style: =psClear;
Rectangle(Rect.Left,Rect.Top,Rect.Left+100,Rect.Bottom);
Brush.Style:=bsClear;
Font.Color:=clBlue;
TextOut (Rect.Left+110,Rect.Top, IntToStr(index))';
end;
end;
Вот и все. Программа готова, нажмите на кнопку запуска и наслаждайтесь. Только давайте посмотрим, что тут написано.'
Первая строка:
with ListBoxl.Canvas do
Оператор with говорит, что все последующие операпии будут производиться с компонентом (объектом) ListBoxl. Canvas. Для того чтобы вы лучше понимали, код без этой строки показан в листинге 12.9.
I ЛЙСТИНГ12.9. Рисование в ListBox6e3 использования оператора with
procedure TForml.ListBoxlDrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
begin
ListBoxl ・ Canvas ・ Brush. Color: =clRed;
ListBoxl. Canvas. Brush. Style: =TBrushStyle (Index);
ListBoxl . Canvas. Pen.Style : =psClear;
List
Boxl .Canvas. Rectangle (Rect. Left, Rect. Top,Rect. Left+100,Rect. Bottom);
ListBoxl . Canvas. Brush. Style: =bsClear;
ListBoxl. Canvas. Font. Color: =clBlue;
ListBoxl. Canvas. TextOut (Rect. Left+110,Rect. Top, IntToStr ( index)); end;
Как видите, в каждой строке появились подписи ListBoxl. Canvas. Код стал очень некрасивым. Постоянно нужно говорить, что Brush или еще что-нибудь нужно взять у ListBoxl. Canvas. Поэтому и используется оператор with.
Теперь рассмотрим еще несколько подводных камней нашей программы. Конструкция Brush. Style :=TBrushStyle (Index) выбирает КИСТЬ в зависимости от рисуемого в данный момент элемента. Всего существует восемь стилей кисти. Коща выдается сообщение OnDrawltem для первого элемента (об этом говорит параметр index передаваемый в процедуру ListBoxlDrawltem), мы рисуем элемент с кистью первого стиля. Для второго элемента будет использоваться второй стиль кисти и т. д.
Карандаш будет прозрачным Pen. style :=psClear, это для того, чтобы не было никаких оборок. Попробуйте убрать эту строку и посмотреть на результат.
Функция Rectangle (xl r у2 г х2, у2) рисует прямоугольник с соответствующими координатами. Дальше делается кисть прозрачной и задается цвет фона. После этого просто выводится текст строки с помощью функции TextOut(х, у, текст).■
Попробуйте сделать то же самое с компонентом тСошЬоВох Не забудьте про свойство style у этого компонента. А в остальном весь код будет таким же.
Примечание、
На компакт-диске в папке \ Примеры \ Глава 12 \ ListBox вы можете увидеть пример этой программы.
Еще один пример рисования в стандартных компонентах, который используется здесь с целью обучения работы с графикой, — рисование графических подсказок в строке состояния. Что имеется в виду под выражением ^графические подсказки1? Все очень просто. Вы каждый день встречаете в программах строку состояния внизу экрана, в которой выскакивают под-
сказки. Сегодня мы посмотрим, как сделать текст в компоненте statusBar трехмерным.
Рис. 12.15. Форма будущей программы
На рис. 12.15 показана форма, которая будет использоваться нами для вывода графической подсказки. Прежде чем приступить к программированию, надо вспомнить, как вообще выводятся подсказки. В листинге 12.10 показан пример программы (точнее, часть программы), которая выводит подсказки.
Листинг 12.10. Фрагмент код貝,отвечающий за вывод подсказки
procedure TForml. ForwCreate (Sender: TObj ect);
begin
Application.OnHint :二 ShowHint;
end;
procedure TForml.ShowHint(Sender: TObject); begin
StatusBarl.SinpleText:=Application.Hint;
end;
Вспомним, что здесь происходит. В процедуре FormCreate (обработчик события oncreate для главной формы) мы устанавливаем в качестве обработчика события Application. OnHint свою процедуру ShowHint. Теперь, коща будет происходить событие OnHint (т. е. коща нужно вывести подсказку), будет вызываться процедура ShowHint. В этой процедуре мы просто выводим подсказку в компонент StatusBarl.
Теперь можно переходить к графической подсказке. Полный исходный текст кода нового вида функции ShowHint вы можете увидеть в листинге 12.11.
Листинг 12.11. Процедура ShowHint, которая выводит графические подсказки
procedure TForml.ShowHint(Sender: TObject); var
1,t:Integer;
begin
StatusBarl.Repaint;*
with StatusBarl.Canvas do begin
Brush.Style:=bsClear;
Font.Color:=clWhite;
1:^10; t :^1;
TextOut (1, t, Application. Hint); if RadioButtonl.Checked then begin
inc(l);
inc(t); end else begin
dec(1);
dec(t);
end;
Font.Color:=clBIue;
TextOut (1, t,^plication.Hint); end; end;
Здесь ничего сложного нет. Мы просто выводим два раза текст подсказки с разным цветом и небольшим смещением. Это происходит точно так же, как и расположение двух компонентов TLabel на форме с небольшим смешением и разным цветом текста.
К Примечание ]
На компакт-диске в папке \ Примеры \ Глава 12 \ StatusBar вы можете увидеть пример этой программы.
Работа с экраном
Не знаю зачем, но очень часто меня спрашивают о том, как получить доступ к экрану. Такие люди хотят скопировать содержимое экрана в виде Бсартин- ки и потом использовать это по своему усмотрению. Так как такой вопрос появляется не редко, то решено рассмотреть здесь пример его решения. В любом случае пример интересен и полезен в познавательных целях.
Создайте новый проект и разместите на форме две кнопки ТButton и один тImage. Для первой кнопки напишем в событии Onclick содержимое листинга 12.12.
:—-1
Листинг 12.12. Рисование на экране
procedure TForml.ButtonlClick(Sender: TObject);
var
ScreenDC:HDC;
begin
ScreenDC := GetDC(O);
Rectangle(ScreenDC, 10, L0, 200, 200);
ReleaseDC(0,ScreenDC);
end;
С помощью этой процедуры мы рисуем прямо на экране, вне области окна своей программы. Во время рисования не обращаем внимания на запущенные приложения. Если они попадаются, то рисование происходит поверх них.
Теперь о содержимом программного кода. Вначале объявлена переменная ScreenDC типа HDC. HDC — это тип контекста рисования самой windows, и работает он почти так же, как и TCanvas (чуть позже вы увидите связь). С помощью функции GetDC (0) мы получаем контекст окна, указанного в качестве параметра. Но в скобках стоит 0 (ноль), а не указатель на реальное устройство или окно. Это значит, что мы хотим получить глобальный контекст, т. е. самого экрана.
Далее, вызывается функция Rectangle, она похожа на ту, что мы использовали раньше, когда работали с* холстом TCanvas. Rectangle. Есть только одно отличие — первый параметр теперь контекст устройства, а затем идут координаты прямоугольника. Это связано с тем, что раньше мы рисовали через объект TCanvas, который привязан к определенному компоненту. А это значит, что объект может самостоятельно подставлять указатель на свой объект. Сейчас мы будем рисовать средствами GDI Windows. Его
функция Rectangle универсальна и не связана с определенными элементами управления или окнами. Если честно, то процедура TCanvas. Rectangle всего лишь вызывает Rectangle из Windows API и подставляет нужный контекст устройства и размеры, поэтому в ней на один параметр меньше. Сейчас мы сами делаем это без помощи TCanvas.
После рисования освобождается больше ненужный контекст рисования через функцию ReleaseDC. Такие вещи обязательно надо делать, чтобы не засорять память.
Если вы захотите рисовать не на экране, а внутри определенного окна, то в этой процедуре нужно поправить только первую строчку. А именно, в качестве параметра Get DC передавать указатель на окно.
Прммечанид ;
Указатель окна находится в свойстве Handle объекта TForm.
Сейчас можно запустить программу и посмотреть на результат. Теперь перейдем ко второй кнопке. Для нее напишем (для процедуры события onclick) содержимое листинга 12.13.
Листинг 12.13. Процедура обработки события Onclick для второй кнопки
procedure TForml.Button2Click(Sender: TObject);
var
Canvas:TCanvas;
ScreenDC:HDC;
begin
ScreenDC := GetDC(O);
Canvas: =TCanvas. Create ();
Canvas.Handle:-ScreenDC;
Image1.Canvas.Copyrect(Rect(0,0.Image1.Width,Image1•Height), Canvas, Rect(0,0,Screen.Width,Screen.Height));
ReleaseDC(0,ScreenDC);
Canvas•Free;
end;
Здесь мы получаем копию экрана и сохраняем ее в компоненте Image 1.
Первая строка такая же, как и в предыдущей процедуре. Мы точно так же получаем контекст рисования звфана. Потом инициализируется переменная canvas типа TCanvas (знакомый нам контекст рисования). Потом мы связываем их ме-
ЖДУ собой С ПОМОЩЬЮ ПРОСТОГО Присваивания В Canvas. Handle: =ScreenDC. Теперь TCanvas указывает на экран, и можно рисовать на нем привычными методами. Теперь вы видите связь между холстом Canvas и контекстом рисования HDC. Объект холста всегда содержит указатель на контекст рисования HDC в свойстве handle и использует этот контекст при вызове всех своих методов (таких как Rectangle). Для компонентов Delphi это свойство заполняется автоматически, и нам не надо о нем заботиться.
Получается, что TCanvas — это объект, который упрощает API-функции и превращает работу с графикой в объектную.
Далее, мы получаем копию ЭБфана и записываем ее в картинку тImage с помощью функции ' copyRect у контекста рисования картинки (Imagel•Canvas.CopyRect).
После копирования нужно освободить контекст рисования ScreenDC и созданный холст Canvas, чтобы освободить выделенную память.
工 При!иечание
На компакт-диске в папке \ Примеры \ Глава 12 \ Screen можно увидеть пример этой программы.
Режимы рисования
У Бсарандаша (свойство Реп холста) есть очень много режимов рисования. Благодаря им можно добиться очень интересных эффектов. Режимы рисования устанавливаются в свойстве Mode карандаша. Это свойство имеет тип TPenMode и может принимать следующие значения: pmBlack, pmwhite, pmNop, pmNot, pmCopy и т. д. Здесь мы рассмотрим, как можно использовать это свойство в своих целях.
Это не справочник, а книга, по которой вы должны научиться программировать, познакомиться с некоторыми алгоритмами и научиться правильно мыслить. Именно поэтому здесь не будут рассматриваться все возможные варианты режимов, а будет описан только один — pmNotXor. Почему этот режим? Да потому, что ему легко найти применение. Остальные встречаются довольно редко.
Итак, представим, что нам надо составить пример, в котором пользователь должен иметь возможность рисования на форме прямоугольника. Кода в нашем примере будет немного и логика до предела проста:
По нажатии кнопки мыши мы запоминаем текущие координаты точки, где был произведен щелчок кнопкой мыши.
При движении указателя мыши мы должны проверять — если кнопка мыши нажата, то пользователь растягивает прямоугольник, и мы должны его рисовать, начиная от стартовой позиции до текущей.
Теперь реализуем эти пункты в программе. Создайте новый проект и перейдите в раздел private описания объекта. Там нужно добавить следующие переменные:
private
{ Private declarations }
StartX, StartY:Integer;
dragging:Boolean;
Переменные startx, startY будут использоваться для хранения координат начала прямоугольника. Переменная dragging будет использоваться для признака растягивания. Если эта переменная равна true, то пользователь нажал кнопку мыши и перемещает курсор, н мы должны растягивать прямоугольник.
По событию OnCreate ДЛЯ формы МЫ ДОЛЖНЫ задать переменной dragging значение по умолчанию — false. Ведь после старта приложения мышь ничего не "тянет" и кнопка не нажата. Вот чтобы избежать случайного попадания в переменную значения true, мы должны по событию создания формы OnCreate написать следующий код:
procedure TForml.FormCreate(Sender: TObject);
begin
dragging:=false; end;
Для события — щелчек кнопкой мыши (onMouseDown) — пишем следующий код:,
procedure TForml. FonriMouseDown (Sender: TObj ect ; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
StartX:=X;
StartY:=Y;
dragging:=true;
end;
Графические аозможности Delphi,395
Сначала рассмотрим параметры, которые мы получили вместе с обработчиком. У нас тут пять параметров.
sender — здесь хранится объект, который сгенерировал событие.
口 Button — признак кнопки, которая была нажата. Этот параметр может быть равен: mbLeft (нажата левая кнопка), mbRight (нажата правая кнопка) или mbMiddle (нажата средняя кнопка).
О shift — состояние дополнительных клавиш клавиатуры. Это набор параметров типа TShiftstate. Параметр может хранить любые из следующих значений:
ssShift — нажата клавиша <Shift>;
ssAlt —— нажата клавиша <Alt>;
ssCtrl — нажата клавиша <Ctrl>;
ssLef t — левая кнопка мыши нажата;
ssRight — правая кнопка мыши нажата;
ssMiddle — средняя кнопка мыши нажата;
ssDouble — был двойной щелчок кнопкой мыши.
Чтобы проверить, была ли нажата клавиша <Shift> во время нажатия кнопки мыши, можно написать следующий код:
if ssShift in Shift then
Почему этот параметр — набор? Да потому, что одновременно на клавиатуре могут быть нажаты две клавиши <Ctil> и <Shift> и даже три клавиши. Именно поэтому мы в данном коде проверяем, если константа ssShift содержится в наборе shift, то соответствующая клавиша нажата и выполняется код, указанный после проверки.
О х, У — последние два параметра. Это координаты, в которых была нажата клавиша мыши.
Внутри самой процедуры координаты сохраняются в переменных startx и startY, и значение переменной dragging изменяется на true.
Теперь напишем обработчик события onMouse Move, который генерируется при каждом перемещении указателя мыши:
procedure TForml.ForraMouseMove(Sender: TObject; Shift: TShiftstate; X,
Y: Integer);
begin
if dragging—false then exit;
Canvas.Rectangle(StartX, StartY, X, Y); end;
Параметры этого обработчика похожи на обработчик события OnMouseDown.
В первой строчке происходит проверка, если переменная dragging равна false, то сейчас нет никакого перемещения и нужно выйти из процедуры. Иначе нужно нарисовать прямоугольник с координатами первой точки StartX, StartY И второй точки X, Y.
По событию OnMouseUp нужно присвоить переменной dragging значение false. Напишите этот код сами.
Теперь запустите программу. Попробуйте щелкнуть кнопкой мыши и потянуть ее. Будет создаваться эффект, как будто вы растягиваете прямоугольник. А теперь, не отпуская кнопку мыши, попробуйте начать уменьшать прямоугольник. Вот тут начинается полный ужас (рис. 12.16). Пока мы растягивали прямоугольник, он спокойно накладывался сверху старого. Но как только мы начали уменьшать, новые прямоугольники становятся меньше старого и старый остается на зкране, а новый оказывается как бы внутри.
Рис. 12.16. Пример работы программы
* Прммечани1е、
На компакт-диске в папке\ Примеры \ Глава 12\ CopyModel вы можете увидеть пример этой программы.
Чтобы избавиться от этого эффекта, есть два способа.
Перед каждым рисованием запоминать содержимое зкрана и потом восстанавливать его. Такой способ связан с большими нагрузками на процессор и лишним расходованием памяти.
Затирать только старые прямоугольники и восстанавливать то, что было под линиями.
Все почему-то боятся использовать второй способ, думая, что он сложен. Сейчас докажем обратное. Мало того, что этот способ абсолютно прост в программировании, но и очень быстр.
Для начала добавим в раздел private еше несколько переменных: private
{ Private declarat ions }
OldPenMode: TPeriMode;
StartX, StartY, OldX, OldY: Integer;:
dragging:Boolean;
Здесь добавлена переменная OldPenMode, в которой будет сохраняться текущее значение режима рисования. Переменные Oldx, OldY нужны для хранения конечных координат старого прямоугольника (начальные координаты StartX И StartY).
Теперь подправим обработчик события OnMouseDown:
procedure TForml . FonriMouseDown (Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Canvas. Brush. Color: =clWhite;
i
OldPenMode: =Canvas. Pen. Mode;
Canvas • Pen. Mode: =pniNotXor;
StartX: =Xr
StartY: =Yr
OldX:=X;
OldY:=Y;
dragging :=true;
end;
самом начале мы добавили изменение свойства кисти холста. Кисть стала белой, чтобы прямоугольники имели белый фон и лучше было видно эффект закраски.
Во второй строке сохраняется текущий режим рисования в переменной OldPenMode. В следующей строке меняется режим на pmNotXor. В таком режиме, когда мы рисуем первый раз какую-то фигуру, она выводится в нормальном виде. Если нарисовать второй раз прямоугольник, то он просто стирается и старое изображение восстанавливается на экране.
Попробуйте выполнить над какой-нибудь переменной операцию NotXor (исключающее "ИЛИ"). Эта переменная примет нечитабельный вид. Вторая операция вернет переменную в первоначальное состояние. Именно так делались первые простейшие алгоритмы шифрования, потому что Notxor легко обратимая математическая операция.
Вернемся к нашему коду. После этого мы просто заполняем текущие координаты StartX, StartY, oidX И OldY.
Теперь посмотрим обработчик события OnMous eMove:
procedure TForml. FormMouseMove (Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
if draggings false then exit;
Canvas.Rectangle(StartX, StartY, OldX, OldY);
Canvas.Rectangle(StartX, StartY, X, Y);
01dX:=X;
OldY:=Y;
end;
Начало обработчика такое же. Потом рисуется прямоугольник в старой позиции, где он был нарисован на прошлом шаге. Так как используется режим pmNotxor, то повторное рисование прямоу го льни ка в старой позиции просто восстанавливает старое значение. После этого рисуется фигура в новой позиции и сохраняется текущая позиция х и Y в переменных OldX и OldY.
И наконец, обработчик события QnMouseUp:
procedure TForml. FormMouseUp (Sender: TObject ; Button: TMouseButton;
Shift : TShiftState; X. Y: Integer);
begin
dragging:=false;
Canvas・ Pen.Mode:=01dPenMode;
Canvas.Rectangle(StartX, StartY, X, Y);
end;
В первой строке мы присваиваем переменной dragging значение false. Это говорит о том, что создание прямоугольника закончено. Во второй восстанавливается старое значение режима рисования. И в самой последней строке рисуется уже окончательный вариант прямоугольника.
Запустите программу и попробуйте нарисовать прямоугольник. Никаких накладок в этом случае не происходит. Обратите внимание, что когда вы растягиваете прямоугольник, то его фон прозрачный. Только когда вы отпускаете кнопку мыши (режим рисования восстанавливается), рисуется прямоугольник с фоном белого цвета.
Попробуйте нарисовать сразу несколько прямоугольников на форме и убедиться, что все работает нормально.
Примечание 】
На компакт-диске в папке \ Примеры \ Глава 12\CopyMode2 вы можете увидеть пример этой программы.
Глава 13
Печать в Delphi
嚇
Число 13 действительно несчастливое. Это можно отнести и к данной главе, т. к. она переделывалась несколько раз. Сначала предполагалось, что здесь должен быть начат рассказ про базы данных. Потом решение изменилось, и базы данных были перенесены на более поздний этап, а гл. 13 переделана в графику. Но не тут-то было. До баз данных надо еще рассказать про печать в Windows. Пришлось опять немного подкорректировать план. В результате гл. 13 получила новое название — ^Печать в Delphi”.
Но и на этом цифра 13 не закончила свои издевательства. Когда был написан первый пример для книги и потребовалось его протестировать, то оказалось, что на НР400 в картридже закончились чернила. Заправлять картридж не имело смысла, потому что он уже несколько раз испытал на себе эту процедуру, да и покупать новый тоже не выгодно (слишком дорого). Поэтому решено было купить новый принтер. После этого пришлось срочно бежать в магазин и покупать новый НР5 потому что доверять своим знаниям хорошо, а все же проверять желательно любые примеры. Мы люди, а значит, нам свойственны ошибки, даже в простых примерах (ну бывает, не доглядел что-то).
Почему здесь говорится "Печать в Delphi”,а не "Печать в Windows". Во- первых, потому что Delphi сильно упрощает не только сам принцип печати, но и доступ к ее возможностям. Во-вторых, существует несколько отличий, которые нужно учитывать. В-третьих, мы будем печатать не только через функции Windows, но и практически напрямую, отправляя данные в порт принтера.
13.1. Объект TPrinter
Печать необходима везде и всегда. Еще со времен первых компьютеров разработчики задумались об устройстве, которое могло бы выводить результаты расчетов на бумагу, чтобы не приходилось переписывать их с монитора. Так и появился принтер, который очень сильно изменился за все время своего существования, но не потерял актуальности. Например, первые принтеры
были матричными и для печати текста использовали встроенные шрифты, а теперь везде струйные и лазерные, да еще и графические (текст выводится как графика, поэтому он не зависит от установленных в принтере шрифтов). Читать с монитора не всегда удобно, да и глаза устают от длительного чтения. Более удобно — распечатать все необходимое для чтения на принтере, а потом изучать в спокойной обстановке. Когда вы путешествуете по Интернету и видите что-то интересное, это можно сразу отправлять на принтер. Так экономится время пребывания в сети и сберегается зрение., Офисные приложения также не возможно представить без возможности печати. Возьмем те же программы — Word и Excel. Грош им цена, если они не смогут печатать созданные с их помощью документы. Любые базы данных (о них мы поговорим в следующей главе) должны уметь выводить данные на принтер, формировать отчетность, которая также должна быть доступна для печати, и т. д.
Изначально в Windows задумывался очень удобный и простой механизм вывода информации — контекст устройства. Контекст устройства — это независимая от устройства отображения область памяти. В ней формируются графические данные, которые могут быть впоследствии выведены на любое устройство — принтер, монитор и т. д. Благодаря этому один и тот же код может выводить данные на монитор и на принтер, надо только указать нужный контекст (устройства, с которым вы хотите работать).
Мы уже познакомились с графикой и увидели, как ее выводить на контекст графического устройства — монитор. Когда нужно вывести информацию на экран, следует получить контекст рисования монитора (или видеокарты, кому как удобнее) и рисовать на нем. Для удобства в Delphi есть объект TCanvas, который упрощает доступ к функциям отображения данных.
Так вот, функции вывода на контекст устройства универсальны. Им все равно, куда выводить данные — будь это контекст монитора или принтера или еще какого-нибудь другого устройства отображения или документирования информации. Очевидно, что пока еще не совсем ясно, о чем идет речь, но сейчас все встанет на свои места.
В Delphi есть объект (обратите сразу внимание, что это не компонент), который содержит все необходимые для доступа к принтеру свойства и методы — TPrinter. Если посмотреть на эти свойства (о них будет рассказано чуть позже), то сразу бросается в глаза свойство Canvas объектного типа TCanvas.
Это тот же самый объект TCanvas, который мы использовали при выводе графики. В нем собраны все необходимые функции для работы с ней. Вы также должны уже знать, что этим функциям абсолютно все равно, куда выводить данные — на принтер или на экран монитора. Вот именно поэтому все компоненты, способные отображать графику, содержат в себе объектное
свойство ТCanvas. Объект Tprinter, способный выводить информацию на принтер, тоже работает через это свойство.
Раз у принтера есть такое свойство canvas, то значит, информацию, которую мы хотим вывести на печать, надо выводить в виде графики. Даже текст выводится как графика. Это связано с изначальной графической сущностью Windows, с одной стороны, и с тем, что современные принтеры являются графическими, с другой.
Немного теории закончено, теперь давайте познакомимся с самим объектом TPrinter и посмотрим на его свойства и методы.
Объекта Tprinter имеет следующие свойства.
П Aborted — переменная типа Boolean. Если она равна true, то пользователь прекратил вывод информации на принтер.
О canvas — объект типа TCanvas. Это холст, на который можно выводить информацию в графическом виде.
Copies — количество копий документа, необходимых для печати.
Fonts — список шрифтов, поддерживаемых принтером.
П Handle — здесь хранится контекст принтера. Его вы можете использовать, когда захотите воспользоваться напрямую функциями WinAPI.
П Orientation — ориентация страницы. Это свойство может иметь одно из следующих значений:
PoPortrait — КНИЖНЫЙ;
PoLandscape — альбомный.
О PageHeight — высота страницы в пикселах.
П Pagewidth — ширина страницы в пикселах.
П PageNumber — номер печатаемой сейчас страницы.
П Printerindex — число, которое указывает на номер выбранного сейчас принтера.
П Printers — список типа Тstrings установленных в системе принтеров.
П Printing — если это свойство равно true, то принтер в данный момент печатает.
П Title — заголовок или просто текстовое описание печатаемого документа. Этот заголовок будет отображаться во время печати в менеджере печати.
Теперь давайте разберемся с методами объекта Tprinter.
П Abort — прерывает текущую печать.
П BeginDoc — начало печатаемого документа.
П EndDoc — конец документа.
□ GetPrinter — получить индекс текущего принтера.
NewPage — новая страница документа,
Refresh — обновить информацию о шрифтах и принтерах.
О Set Printer — установить текущий принтер. Если не устанавливать принтер, то будет использоваться тот, что установлен в системе по умолчанию.
Внимание
Чтобы объект TPrinter стал доступным вашему проекту, вы должны добавить в раздел uses модуль Printers.
TPrinter — это объект, а мы уже знаем, что все объекты нужно инициализировать (инициализируются также компоненты, создаваемые во время выполнения процесса, т. е. те, что не установлены на форме). Этот объект является исключением и его не надо инициализировать. Достаточно только подключить модуль и объект становится доступным через переменную Printer. Это вы увидите в примерах данной главы.
Но прежде чем переходить к практике, надо сделать еще несколько замечаний. Печать, как уже говорилось, похожа на отображение информации на экране и на нижнем уровне программирования, здесь используются одни и те же API-функции. Однако прежде чем печатать, вам нужно учесть следующие особенности контекста печати.
Если с экрана можно стереть информацию, то изображение, выведенное на canvas объекта принтера, затереть невозможно после начала процесса печати.
Принтеры имеют большее разрешение, чем экран. Самые простейшие принтеры сейчас могут печатать графику с разрешением до 600 точек на дюйм. Если вы попытаетесь вывести картинку размером 200x200 на современный принтер, без каких-либо корректировок, то пользователь этого изображения просто не увидит или оно будет слишком маленьким.
Не все принтеры одинаково работают с графикой, поэтому нужно давать пользователю возможность выбирать качество печати. Желательно всегда перед выводом на печать отображать стандартное окно настроек печати. Если на зьфан вывод производится только один раз, то на принтере может понадобиться несколько копий. Это замечание не является обязательным, но советую учитывать это, чтобы ваша программа соответствовала правилами хорошего тона. А вдруг пользователю нужно 100 копий, так что ему теперь сто раз нажимать кнопку печати?
П Вы должны давать возможность прервать печать в любой момент. Например, это может понадобиться, когда закончилась бумага, и ее больше нет. Конечно же такую возможность всегда предоставляет драйвер печа-
ти, но и вы не забывайте о хорошем тоне. В некоторых системах используется очередь печати, при которой программа блокируется до завершение процесса печати.
Последняя рекомендация является необязательной, а вот все остальные вы обязаны учитывать в процессе программирования.
Получение информации об установленном принтере
Сейчас мы напишем программу, которая пока еще не буцет печатать, но зато она буцет получать из системы информацию об установленных принтерах. Мы сделаем ее минимально простой для того, чтобы вы смогли понять *основыработы с объектом TPrinter.
Создайте новый проект и сразу же допишите в разделе uses модуль Printers, чтобы объект TPrinter стал доступным проекту.
Наша программа буцет показывать текущий выбранный принтер и список всех доступных в системе устройств печати. Для этого на форме понадобится одна строка ввода TEdit для отображения текущего принтера, и один список TlistBox для отображения полного их списка. У строки ввода можно установить свойство Readonly в true, потому что информация в этой строке только отображается и не должна редактироваться, да и нет смысла ее редактировать.
На рис. 13.1 показана форма будущей программы. Вы можете расположить компоненты по-другому, но я предпочитаю именно такое расположение. Главное, делайте подробные подписи для выводимой информации, иначе программа буцет понятна только вам, и больше уже никому не понадобится.
Теперь поместите на форму две кнопки, а в заголовках напишите ”О6но- вить” (для обновления информации о принтерах) и "Сменить принтер" (понятно зачем).
Нам так же понадобится компонент printerSetupDialog с вкладки Dialogs палитры компонентов. Этот компонент предназначен для отображения стандартного окна настроек принтера.
Для события onclick, связанного с нажатием кнопки Обновить, пишем следующий код:
procedure TForml.ButtonlClick(Sender: TObject);
var
i:Integer;
begin
ListBoxl.Items.Clear;
for i:=0 to Printer.Printers.Count-1 do
ListBoxl.Items.Add(Printer•Printers.Strings[i]);
Editl.Text:= Printer.Printers•Strings[Printer.PrinterIndex]; end;
Рис. 13.1. Форма будущей программы
В первой строке кода очищается текущее содержимое списка ListBoxl. Потом запускается цикл от 0 до количества установленных в системе принтеров МИНУС 1 (количество Принтеров наХОДИТСЯ В Printer. Printers. Count И нумеруется с нуля). Свойство Printers объекта ТPrinters имеет тип Тstrings, как свойство items компонента TListBox, поэтому их свойства и методы одинаковы, и мы с ними уже не раз работали.
Почему отнимается единицу? Да потому что цикл начинается с нуля. Допустим, что у вас установлено 2 принтера и в переменной Printer. Printers. Count будет находиться 2. В этом случае цикл будет выполняться от 0 до 2, т. е. три раза для О, 1 и 2. Но принтеров только 2, поэтому нужно отнять единицу.
Внутри цикла в список добавляются названия принтеров. Названия хранятся в переменной Printer. Printers. Strings [i], где i — это индекс принтера, изменяющийся от 0 до значения из Printer. Printers. Count, минус 1.
После этого мы выводим в строку ввода название текущего принтера. Индекс текущего Принтера — это Printer. Printerindex значит, название текущего принтера можно получить так:
Printer・ Printers.Strings[Printer.PrinterIndex]
Эту же процедуру можно вызвать по событию oncreate или Onshow главной формы, чтобы после старта профамма сразу же получала необходимую информацию:
procedure TForml.FormCreate(Sender: TObject);
begin
ButtonlClick(nil);
end;
Нам осталось только написать код для кнопки Сменить принтер, и профамма готова. Итак, для события Onclick, связанного с этой кнопкой, пишем:
procedure TForml.Button2Click(Sender: TObject);
begin
PrinterSetupDialogl.Execute;
ButtonlClick(nil);
end;
В первой строке кода просто отображается окно PrinterSetupDialogl (окно настроек принтера). Во второй строке вызывается процедура, которую мы написали для обновления информации о принтерах.
Рис. 13.2. Результат работы программы
14 Зак. 942
Теперь запустите программу и посмотрите на результат.
К Примеиаш〉
Если у вас в системе только один принтер или вообще нет ни одного, то зайдите в Панель управления Windows, выберите там пункт Принтеры и установите пару любых принтеров вручную. Система от этого работать хуже не будет, зато вы сможете полноценно протестировать этот пример.
Нажмите на кнопку Сменить принтер, и перед вами откроется окно, похожее на рис. 13.3. В этом окне в выпадающем списке Имя измените имя текущего принтера на другое и закройте окно кнопкой ОК. Обратите внимание, что в вашей программе в строке Название текущего принтера, название автоматически изменилось. Это потому что после отображения окна сразу произошло чтение свойств принтеров. А они изменились, т. к. окно PrinterSetupDialogl тесно связано с системой и автоматически обновляет всю информацию в объекте TPrinter. Поэтому нам не надо самостоятельно читать данные, измененные в окне свойств принтера, и переносить в объект печати.
Рис. 13.3. Окно свойств принтера
При смене принтера по умолчанию он изменяется только для вашей программы. В системе принтер по умолчанию не изменяется. Поэтому после перезапуска программы все вернется в начальные установки. Тут вы можете сохранять индекс и имя принтера при выходе или оставить все как есть. В большинстве программ выбирают второй метод.
(CosgT^j
Необходимо обратить внимание на то, что Printer— это объект типа TPrinter. Как говорилось ранее, все объекты должны создаваться с помощью их же метода Create и уничтожаться с помощью метода Free. В данном случае этого делать не надо, все делается автоматически. Именно поэтому в гримере не показывались методы Create и Free. Это надо помнить.
Примечание
На компакт-диске в папке \ Примеры \ Глава 13 \ Свойства_принтера вы можете увидеть гример этой программы.
Текстовая печать
Хотя нынешние принтеры и графические, но вывод на печать в виде текста еще возможен. Представьте себе, как сложно было бы жить, если бы нельзя было вывести содержимое компонента TMemo как текст. В этом случае нам пришлось бы рисовать каждую строку в контексте рисования принтера (рисовать на Canvas с помощью его метода Textout), при этом учитывая расстояния между строками.
Но есть еще Бог на свете и нам не надо так сильно мучиться. С принтером можно работать как с простым текстовым файлом, хотя реально он будет печатать в графике. В современных принтерах нет шрифтов, поэтому он не сможет печатать в реальном текстовом режиме.
Для открытия принтера в текстовом режиме используется процедура AssignPrn. В качестве единственного параметра этой процедуре надо передать переменную типа TextFile. После этого переменной будет назначен принтер по умолчанию. Дальше его нужно открыть с помощью процедуры Rewrite.
Как только файл открыт, в него можно печатать с помощью процедуры writein, у которой два параметра:
П переменная типа TextFile, которой назначен принтер;
□ текст, который надо распечатать.
После печати переменную надо освободить (закрыть файл, ассоциированный с принтером) с помощью процедуры CloseFile.
Простейший пример вывода текста "Hello world** на принтер вы можете увидеть в листинге 13.1.
Листинг 13.1. Печать текста
var
f:TextFile;
begin
AssignPrn(f);
try
Rewrite(f);
Writein (f, * Hello world1);
finally
CloseFile(f);
end;
end;
В первой строке кода мы назначаем переменной f принтер. После этого происходит открытие файла, ассоциированного с принтером, и вывод на печать мы заключаем между try и finally. Это необходимо, потому что если после вьшолнения процедуры AssignPrn переменной f не будет назначен принтер (ну, нету его в системе, не установлен или вообще отсутствует), то при попытке открыть файл или начать печать произойдет ошибка.
Между finally и end (код, написанный здесь, будет выполняться всегда, вне зависимости от того, была ошибка или нет) мы закрываем открытый файл. Если бы мы не вставили try ... finally . .. end, а во время печати произошла ошибка, то файл, ассоциированный с принтером, остался бы открытым. Это значит, что последующая нормальная работа принтера уже не гарантируется.
Еще один пример вывода на принтер содержимого компонента тмето вы можете увидеть в листинге 13.2.
Листинг 13.2 Вывод на принтер содержимого компонента тмвто
var
f:TextFile;
i:Integer;
begin
AssignPrn(f);
try
Rewrite(f);
fo工 i:=0 to Memol.Lines.Count-1 do
Writein(f, Memol.Lines.Strings[i]); finally
CloseFile(f);
end;
end;
Попробуйте сами разобраться, как работает этот пример. Для этого нужно вспомнить свойства компонента тмето и как работать с ним.
Печать содержимого формы
Мы научились получать информацию о принтере и печатать текст, теперь пора научиться выводить на печать графику. Хотя сейчас мы рассмотрим простейший пример печати, зато в нем будет все необходимое для построения сложных сцен печати.
Этот пример взят из файла помощи Delphi. На нем можно довольно хорошо изучить основы печати. Следующий пример мы напишем более сложным для более качественной печати.
Для примера понадобится форма, на которой будет расположен один компонент TPageControi. На нем можно создать несколько вкладок (например, две) и поместить на вкладки различные компоненты. На первую вкладку установим текст и несколько компонентов тShape. На второй вкладке расположим картинку Timage. На печать будет выводиться содержимое вкладок компонента TPageControi вместе с компонентами и картинкой. Каждая вкладка буцет печататься как отдельная страница.
Рис. 13.4. Окно запуска печати
Ну, и напоследок установим на форму кнопку Печать и компонент PrintDialog с вкладки Dialogs палитры компонентов. Второй компонент
предназначен для отображения стандартного окна запуска печати (рис. 13.4). Окно печати похоже на окно настроек печати PrinterSetupDialog, но имеет свои отличия. Именно такое окно вы видите каждый раз, когда запускаете печать в других программах, таких как MS Word, Excel и др.
Вид главной формы будущей программы представлен на рис. 13.5. Можете сделать такую же, а можете попробовать что-нибудь свое.
Рис. 13.5. Форма будущей программы
Теперь перейдем к написанию кода. Создайте обработчик события onclick для кнопки Печать и напишите в нем содержимое листинга 13.3.
Листинг 13.3. Печать содержимого формы
procedure TForml.ButtonlClick(Sender: TObject);
var
i, Start, Step: Integer; begin
PrintDialogl.Options :二[poPageNums, poSelection];
PrintDialogl.FromPage := 1;
PrintDialogl.ToPage := FageControl1.PageCount;
PrintDialogl.MinPage := 1;
PrintDialogl.MaxPage := PageControll.PageCount; if not PrintDialogl.Execute then exit;
if PrintDialogl. PrintRange 二 prAllPages then begin
Start 二二 PrintDialogl.MinPage - 1;
Stop := PrintDialogl.MaxPage 1; end
else //Если выбрано отзтачное от печати "Все" if PrintDialogl. PrintRange 二 prSelection then begin
Start := PageControll.ActivePagelndex;
Stop := Start ; end
else /Если выбрано отличное от "Выделенный фрагмент" begin
Start := PrintDialogl. FromPage 1;
Stop := PrintDialogl.ToPage 1; end;
/ /Начало печати
Printer.BeginDoc;
for 1 := Start to Stop do begin
PageControll.Pages[i].PaintTo(Printer.Handle, 10, 10);
if 1 <> Stop then
Printer.NewPage;
end;
Printer.EndDoc; end;
В первой строке кода мы задаем опции окна печати PrintDialogl.Options. 、В этом свойстве хранится информация о том, что должно отображаться в окне Печать. Возможны следующие значения:
poDisablePrintToFile — отключить возможность выбора печати в файл (если вы добавите в опции это значение, то пользователь не сможет выбрать Печать в файл);
poHelp — показывать кнопку Помощь в окне печати;
poPageNums — разрешить пользователю выбирать диапазон печати (какие страницы нужно распечатать);
口 poPrintToFile — показывать в окне печати checkBox, в котором можно выбрать печать в файл;
poSelection — давать возможность пользователю выбрать в окне печати печать выделенного фрагмента;
poWaming — показывать пользователю сообщения, если он пытается запустить работу на неустановленный принтер.
В раздел настроек Добавляем Две ОПЦИИ poPageNums И poSeiection:
PrintDialogl.Options: = [poPageNums, poSeiection]
Все это можно было бы сделать проще. Просто выделить компонент PrintDialogl и в объектном инспекторе дважды щелкнуть по свойству options. После этого откроется список из всех этих свойств, где можно указать напротив необходимых свойств значение true. Но, как ранее говорилось, пример взят из файла помощи по Delphi, а там настройки делали программно.
Во второй строке кода свойству FromPage компонента PrintDialogl присваивается начальное значение, с которого должна происходить печать. А в следующей строке устанавливается в свойстве То Раде количество печатаемых страниц. В это свойство мы занесли количество вкладок у компонента PageControll. Мы же договорились, что каждая вкладка будет печататься на отдельной странице, значит, сколько вкладок, столько н страниц будет печататься.
После этого устанавливается минимальное и максимальное значение страниц, доступных для печати:
PrintDialogl.MinPage :二 1;
PrintDialogl.MaxPage :二 PageControll.PageCount;
Все, настройки произведены. Теперь можно отображать окно печати:
if not PrintDialogl.Execute then exit;
Здесь используется конструкция if not. Значит, если пользователь закроет окно через кнопку Cancel, то выполнится код, написанный после then. А там у нас написан выход из процедуры — exit. Так что если не будет нажата в окне печати кнопка ОК, то процедура не будет выполняться дальше и печать не произойдет. Ну, а если нажата ОК, то код процедуры продолжит свое выполнение.
Дальше идет проверка, какой диапазон печати был выбран пользователем: if PrintDialogl. PrintRange = prAllPages then
Этот код проверяет, если выбран диапазон печати всех страниц, то переменным start и stop будут присвоены значения минимального и максимального количества страниц соответственно, чтобы были распечатаны все вкладки нашего компонента.
Если пользователь выбрал не все страницы, то нужно проверить, а может, он выбрал печать выделенного фрагмента?
if PrintDialogl.PrintRange 二 prSelection then
Если пользователь выбрал печать выделенного фрагмента, то переменным start и stop будет присвоено одно и то же ——номер выделенной в данный момент вкладки PageControll. ActivePagelndex.
Ну, и если пользователь не выбрал ни того, ни другого, значит, он выбрал начальную и конечную страницу, которые надо распечатать. В этом случае переменным start и stop будут назначены значения выделенных пользователем страниц:
Start:=FrintDialogl.FromPage - 1;
Stop:=PrintDialogl.ToPage - 1;
Внимание
Обратите внимание, что от выделенных пользователем страниц вычитается единица. Это потому, что пользователь, как нормальный человек, будет нумеровать страницы, начиная с единицы, а вкладки компонента PageControll нумеруются с нуля.
Все, теперь наши переменные start и stop содержат диапазон, который надо распечатать, в зависимости от выбранных пользователем настроек, и можно переходить к самой печати. Для начала распечатки нужно начать новый документ. Для этого вызваем метод BeginDoc объекта TPrinter.
После этого запускаем циют от стартовой страницы до последней выделенной:
for i:=Start to Stop do
Внутри цикла выполняется следующий код:
PageControll.Pages[i].PaintTo{Printer.Handle, 10, 10);
if i <> Stop then
Printer.NewPage;
В первой строке мы заставляем прорисоваться очередную страницу на определенное устройство. Об этом говорит конструкция PageControll. Pages [i] . PaintTo. Метод PaititTo заставляет Прорисоваться страницу i компонента на указанное устройство. Нужное устройство указывается в качестве первого параметра метода (здесь указан принтер). Остальные два параметра указывают отступ слева и сверху.
Далее происходит проверка, если i не. равна последней печатаемой страницы, то создать новую страницу, на которой будет распечатана следующая вкладка. Если не производить этой проверки, то когда распечатается последняя страница, то будет создана новая, которая выйдет из принтера пустой. Это не ошибка, но будет неприятно, если из принтера в конце печати будет выползать пустой лист бумаги.
Самым последним методом вызывается EndDoc объекта Tprinter, после чего принтер начинает печатать весь документ.
工 Примечание ;
На компакт-диске в папке \ Примеры \ Глава 13\Печать_содержимого_формы вы можете увидеть пример этой программы.
Вывод на печать изображения
В принципе, уже в прошлом примере мы напечатали изображение, потому что вкладки компонента TPageControl печатались как самые настояшие картинки. Но пример был простой и не учитывал, что разрешение принтера выше, чем у монитора. Поэтому, если вы пытались протестировать пример, то изображения получались слишком маленькими. Сейчас мы узнаем, как учитывать разрешение принтера.
Но сначала давайте рассмотрим простейший пример вывода изображения на принтер:
begin
Printer.BeginDoc;
Printer.Canvas.Draw(10, 10, Imagel.Picture.Bitmap);
Printer.EndDoc;
end;
Этот пример не отображает никаких диалогов, а просто начинает новый документ на принтере, потом копирует в его canvas картинку из компонента Image 1 и заканчивает документ.
Это еще один простейший пример, который не учитывает разрешение принтера. Он прост, но в реальных условиях неприменим, потому что никому не нужно изображение, которое на бумаге меньше того, что видно на мониторе. Необходимо учитывать масштаб, чтобы приложение было более полезным.
Теперь пора узнать, как определить разрешение принтера. Для этого используется функция WiiiAPI — GetDeviceCaps, которая предназначена для получения информации о нужном устройстве.
У этой функции два параметра.
Устройство, информацию которого мы хотим получить. Нам нужна информация о размере canvas принтера, поэтому следует использовать его указатель Printer. Canvas. Handle.
Какую именно информацию надо. Нам нужно количество пикселов по оси х и Y, поэтому будем указывать LOGPIXELSX (разрешение по оси х) и LOGPIXELSY (разрешение по оси Y).
Теперь реальный пример с использованием этой функции. Создайте новый проект и поместите на форму один компонент Тimage, который будет хранить картинку для печати (сразу же загрузите туда любое изображение в формате BMP) и кнопку Печать. На рис. 13.6 можно увидеть форму будущей программы.
Рис. 13.6. Форма будущей программы
Теперь создайте обработчик события onclick для кнопки Печать и напишите там код, представленный в листинге 13.4.
Листинг 13.4. Печать картинки с учетом масштаба
procedure TForml.ButtonlClick(Sender: TObject);
var
XI, X2, Y1, Y2: Integer;
PointsX, PointsY: double;
PrintDlg:TPrintDialog;
begin
// Создаю и отображаю на экране стандартное окно печати
PrintDlg:=TPrintDialog.Greate(Owner);
if PrintDlg.Execute then
begin
/ /Начинаю новый документ
Printer.BeginDoc;
Printer.Canvas.Refresh;
//Получаю информацию о разрешении принтера
PointsX: =GetDeviceCaps (printer. Canvas. Handler LOGPIXELSX) /70;
PointsY: =GetDeviceCaps (Printer. Canvas. Handle, LOGPIXELSY) /70;
//Рассчитываю размеры изображения
XI:=round((Printer.PageWidth -
Image 1. Picture. Bitmap. Width * Point sX) /2);
Y1 : round ((Printer. PageHeight -
Imagel. Picture.Bitmap.Height*PointsY) /2);
X2: =round (Xl+Imagel. Picture. Bitmap. Width*PointsX);
Y2 : = round (Y1+Imagel ・ Picture. Bitmap. Height*PointsY);
//Вывод изображения на печать
Printer. Canvas. CopyRect (Rect (X工,Y1, X2. Y2), Imagel. Picture. Bitmap. Canvas,
Rect (0,0, Imagel. Picture.Bitmap.Width, Imagel. Picture.Bitmap. Height));
Printer.EndDoc;
end;
/ /Уничтожаю созданное окно печати
PrintDlg.Free;
end;
В самом начале мы программно создаем компонент PrintDiaiog (стандартное окно печати), потому что на форме нет такого компонента. Это сделано специально, чтобы лишний раз показать вам, как программно создаются и используются компоненты Delphi. Для создания выполняется код TPrintDialog. Create (Owner), который выделит память под объект и возвратит нам ссылку на него. Эта ссылка сохраняется в переменной PrintDlg, которая объявлена в разделе var в виде переменной типа TErintDialog.
После этого мы отображаем окно печати (if PrintDlg. Execute then), и если пользователь нажал ОК, то выполнится код между последующими операторными скобками — begin. . . end.
С первой строкой кода уже понятно — начало нового документа. После этого мы обновляем всю информацию на холсте принтера (Printer. Canvas. Refresh). Дальше надо получить информацию о разрешении принтера по вертикали и горизонтали с помощью функции GetDeviceCaps. Результат делится на 70 и сохраняется в переменных Pointsx и PointsY.
工 Примечание (
70 — это коэффициент масштабирования. Можете подобрать любое подходящее вам значение.
После этого идет расчет координат картинки. В примере ее вывод идет по центру листа бумаги. Отступ слева вычисляется по такой формуле: (ширина листа принтера минус ширина картинки, умноженная на PointsX) деленные на 2. Отступ сверху вычисляется по следующей формуле (высота листа принтера минус высота картинки, умноженная на PointsY) деленные на 2.
Отступы слева и сверху рассчитаны. Теперь нужно найти правую и нижнюю стороны изображения. Для этого мы просто прибавляем к левой стороне ширины изображения (imagel. Picture. Bitmap .Width * Pointsx) И К верхней стороне ВЫСОТУ изображения (imagel. Picture.Bitmap.Height * PointsY). При этом размеры умножаются на коэффициент масштабирования.
Теперь можно выводить картинку на холст принтера. Для этого нужно скопировать изображение из imagel на холст принтера с помощью процедуры Printer. Canvas. CopyRect. Мы уже пользовались этой процедурой при работе с графикой, и вы должны знать, что она умеет масштабировать копируемую картинку.
Все, можно заканчивать документ (Printer. EndDoc) и уничтожать созданное окно (PrintDlg. Free). Хотя окно создано как локальное (объявлено внутри процедуры) и должно уничтожаться автоматически, желательно делать это самостоятельно, не надеясь на компилятор. Вспомним еще раз — все переменные, объявленные как локальные (в разделе var внутри процедуры), инициализируются в стеке и автоматически уничтожаются сразу после выхода из процедуры. Но все же те переменные, которым вы выделяли память или создавали, как объекты, желательно уничтожать самостоятельно,, не надеясь на чистку стека. Ведь в стеке хранится только ссылка на объект или выделенную память, а сама память может быть выделена где угодно, но только не в этом стеке.
Примечание二
На компакт-диске в папке \ Призеры \ Глава 13 \ Печать картинки вы можете увидеть прииер этой программы.
13.6. Еще немного о печати
Мы теперь знаем, что для печати используются одни и те же функции, что н для рисования. Мы также узнали, что для вывода графики (рисунки и графические объекты кроме текста) нужно учитывать разрешение принтера, которое намного выше разрешения экрана.
Давайте напишем интересный пример, который вам пригодится в будущем, и на котором мы закрепим наши знания о печати в Delphi и в ОС Windows. При печати ОС играет очень важную роль, потому что мы используем ее API-функции и драйвер печати Windows формирует изображение в соответствии с нашими требованиями.
Итак, исходя из того, что используются одни и те же функции, мы можем использовать для вывода на форму и для печати одну и ту же процедуру. Давайте посмотрим, как это будет выглядеть.
Создайте новый проект и поместите на форму одну кнопку с надписью Печать и один компонент paint Box с вкладки System. В раздел uses добавим модуль Printers, чтобы получить доступ к функциям принтера. Теперь по событию onPaint для компонента PaintBox и для кнопки мы можем вызвать одну и ту же процедуру, которая будет рисовать на определенном холсте. Процедура будет выглядеть так:
procedure TForml.PaintForm. (can: TCanvas) begin
can. Brush. Color: =clRed;
can.Rectangle(10, 10, 100, 100);
end;
Эту процедуру нужно еще описать в разделе private нашей формы:
private
{ Private declarat ions }
procedure Pa intForm(can:TCanvas);
Программный код в целом готов. Осталось процедуре передать только холст, и она нарисует на нем квадрат с красным фоном. Теперь по событию OnPaint для компонента PaintBox эту процедуру можно вызвать следующим образом:
Paint Form (Ра intBox. Canvas);
А для события onclick кнопки можно написать:
Printer.BeginDoc;
Printer.Canvas.Refresh;
PaintForm (Printer.Canvas);
Printer.EndDoc;
Здесь сначала создается документ и обновляется холст. Затем вызывается процедура рисования с указанием того, что надо рисовать на холсте принтера. В конце — завершаем документ.
Попробуйте создать этот пример н убедиться, что все работает. Только в этом примере есть один недостаток — не учитывается разрешение устройства, на котором происходит рисование. Давайте учтем масштабирование. Для этого сначала подкорректируем объявление процедуры paintForm:
private
{ Private declarations }
procedure Paint Form (can: TCanvas; scaleX, scaleY: Double);
Здесь добавлены еще два параметра scalex н scaleY, которые показывают коэффициент масштабирования по горизонтали и вертикали. Сама же процедура будет выглядеть так:
procedure TForml. Paint Form (can: TCanvas; scaleX, scaleY: Double);
begin
can.Brush•Color:=clRed;
can.Rectangle(round(10*ScaleX), round(10*ScaleY),
round(100*ScaleX), round(100*ScaleY));
end;1
При выводе прямоугольника мы масштабируем координаты с учетом переданных коэффициентов.
Обработчик события onPaint изменится несильно, потому что здесь мы вызываем пропедуру рисования с единичным коэффипиентом:
procedure TForml. PaintBoxlPaint (Sender: TObj ect);
begin
PaintForm(PaintBoxl.Canvas, 1, 1);
end;
По нажатии кнопки Печать нужно написать следующий код:
procedure TForml. PrintButtonClick (Sender: TObject);
var
PointsXtPointsY:double;
begin
/ /Начинаю новый документ
Printer.BeginDoc;
Printer.Canvas.Refresh;
//Получаю информацию о разрешении принтера.
PointsX: =GetDeviceCaps (Printer. Canvas. Handle, LCX5PIXELSX) /70;
PointsY:=GetDeviceCaps(Printer.Canvas.Handle,LOGPIXELSY)/70;
Paint Form (Printer. Canvas, Point sX, PointsY);
Printer.EndDoc;
end;
Код, описанный здесь, уже должен быть вам знаком. Мы так же как и раньше начинаем документ, потом получаем параметры принтера и вызываем процедуру печати с полученными коэффициентами масштабирования.
Вот теперь пример работает отлично, код компактный и учитывается масштабирование устройства.
< Примечание
На ком пакт-диске в папке \ Примеры \ Глава 13 \ Печатьграфики вы можете увидеть пример этой программы.
Глава 14
Delphi и базы данных
Базы данных считаются основным достоинством Delphi. Это действительно так. Хотя язык и не создавался специально под эту предметную область программирования, но реализация работы с данными здесь просто поражает. Даже специализированные языки, которые предназначены для работы с базами данных (такие, как MS Visual FoxPro), явно уступают Delphi по простоте и мощи программирования.
Delphi скрывает все сложности и в то же время предоставляет широчайшие возможности при создании баз данных. Практически любую задачу в этой предметной области можно реализовать средствами этого языка, причем за довольно короткий промежуток времени. Главное здесь то, что реализация приложения очень удобна и проста в понимании.
В этой главе мы познакомимся с основами построения баз данных и рассмотрим несколько полезных примеров.
Для примеров будут использовать базы данных Access (Access — типовая система управления базами данных, поставляемая в пакете MS Office) и современный формат файлов баз данных — XML.
(Совет )
Рекомендуется использовать эти базы в качестве локальных, потому что они поддерживаются большинством систем и отличаются высокой надежностью.
В последствии мы рассмотрим самые простые и распространенные таблицы в формате DBF и Paradox. Многие программисты стараются не использовать их в своих проектах из-за ненадежности, а также потому, что в них регулярно нарушается индексная целостность, что приводит к неработоспособности программ. Однако из-за их довольно широкого распространения необходимо знать принципы работы с ними. Даже локальная версия 1С: Предприятия использует этот ужасный DBF-формат. Таким образом, если необходимо написать программу для работы с чужими данными, вы просто обязаны знать, как работать с этим форматом баз данных.
Теория реляционных баз данных
Еще десять лет назад программирование баз данных было очень сложным занятием. За определенные достижения в этой области многие программисты получили в свое время докторские степени. Сейчас уже такое трудно себе представить, потому что благодаря Delphi процесс написания программ упростился.
Базы данных (БЗ) делятся на локальные (установленные на компьютере клиента, там же, где и работает программа) и удаленные (установленные на сервере — удаленном компьютере). Для обозначения второго типа БЗ используются названия клиент -серверные или сетевые БД, потому что программа обработки данных находится у клиента, а данные на сервере.
В случае клиент-серверной БД данные хранятся на сервере. Клиентская программа получает доступ к данным с помощью запросов на специальном языке (SQL или Transact-SQL). При этом следует учитывать, что программа получает только необходимые данные, а загружает на сервер только измененные данные.
Сетевые базы данных также хранятся на сервере, но каждый клиент получает собственную копию данных. В этом случае возникают два основных недостатка:
Большая нагрузка на сеть. При подключении пользователю передаются все данные, а на клиенте уже происходит их фильтрация. Если вы внесли изменения, то ваша копия будет полностью или частично загружаться обратно. Это очень неудобно, потому что создается большая нагрузка на сеть из-за излишней непроизводительной передачи данных.
П Сложная процедура обеспечения целостности. Если два пользователя обновляют данные, то при загрузке этих данных на сервер сложно решить, данные какого пользователя следует считать более верными.
Из-за этих недостатков такие программы уже не используют, и мы с такими базами работать не будем.
При клиент-серверной технологии построения БД программа (клиент) посылает простой текстовый запрос на сервер с целью получения каких-либо данных. Сервер обрабатывает его и возвращает только необходимую порцию данных. Когда нужно изменить их, опять посылается запрос к серверу на изменение и сервер изменяет данные в своей базе. Таким образом, по сети происходит перекачка только текстовых запросов, которые в основном занимают размер меньше одного килобайта. Все данные обрабатывает сервер, а значит, машина клиента загружается намного меньше и не так сильно требовательна к ресурсам. Сервер отсылает клиенту только самые необходимые данные, а значит, отсутствует излишняя перекачка копии всей базы.
Благодаря всему этому сетевые базы данных практически не используются. В настоящее время их полностью вытесняет технология кл иент-среверных БД. В отличие от сетевых, локальные базы данных будут жить всегда. При этом может измениться формат их хранения или добавятся какие-то новые функции.
К Примечание
В этой главе мы затронем только локальные базы данных, а серверные рассмотрим немного позже.
Для дальнейшего рассмотрения механизмов синтеза БД надо определить новое понятие — таблица. Таблица базы данных — это двухмерный массив (табл. 14.1), в котором в столбец (графы) выстроены данные (пример таблицы —документ в формате Excel). База данных — это всего лишь файл, в котором может храниться от одной до нескольких таблиц. Большинство локальных баз данных могут хранить только одну таблицу (dBase, Paradox, XML). Но есть представители локальных баз, где в одном файле заключено несколько таблиц (например, Access, который мы будем рассматривать в этой главе).
Таблица 74. У. Пример простейшей базы данных
КлючФамилияИмя
Отчество
1ИвановИван
Петрович
2ПетровСергей
3СидоровАлексей
4СмирновАндрей
Владимирович Викторович Сергеевич
Локальные базы данных
Из всего множества локальных баз данных будем рассматривать реляционные, как наиболее распространенные в настоящее время. В то же время это один из самых старых типов ДБ.
Что такое реляционная база данных (РБД)? Это таблица, в которой в качестве столбцов выступают имена хранимых в ней данных, а каждая строка содержит сами данные. Таблица базы данных похожа на электронную таблицу Excel (если быть точнее, то Excel хранит свои данные в виде собственного формата, построенного на основе технологии баз данных). Локальные таблицы баз данных могут храниться на локальном жестком диске или централизованно сохраняться на сетевом диске файлового сервера. Файлы БД
можно копировать с помощью стандартных средств, как любой другой файл, потому что сами таблицы базы данных не привязаны к определенному месту расположения. Главное, чтобы программа могла найти нужную ей таблицу.
В каждой таблице должно быть одно уникальное поле, которое однозначно будет идентифицировать строку. Это поле называется ключевым. Без такого поля таблицу нельзя будет изменять. Ключевые поля очень часто используются для связывания нескольких таблиц между собой. Но даже если у вас таблица не связана, ключевое поле все равно обязательно. Представьте, что вы пишете телефонную базу данных. Сколько у вас будет "Нвановых"? Как вы будете отличать их? Вот тут вам поможет ключ.
В качестве ключа желательно использовать численный тип и если позволяет база данных, то будет лучше, если он будет типа Mautoincrement" (автоматически увеличивающееся или уменьшающееся число- счетчик). В современных базах данных есть более надежный способ создания уникальности, например GUID (глобальный уникальный идентификатор) из MS SQL Server.
Имена столбцов в таблице баз данных также должны быть уникальными, но в этом случае не обязательно числовыми. Их можно называть как угодно, лишь бы соблюдалась уникальность и было понятно пользователю, а остальное никого не интересует.
Каждый столбец (поле базы данных) обязательно должен иметь определенный тип. Количество типов и их разновидность зависят от типа базы данных, например формат dBase (файлы с расширением dbf) поддерживает только 6 типов, а система управления БД (СУБД) — Paradox уже до 15.
База данных может храниться в одном файле (например, СУБД Access) или в нескольких (Paradox, dBase). Точнее сказать, данные таблицы всегда хранятся в одном файле, а вот дополнительная информация может располагаться в отдельных файлах. В качестве дополнительной информации могут быть индексы, ограничения или список значений по умолчанию для конкретных полей. Если хотя бы один из файлов испортится или будет удален, то данные могут стать недоступными для редактирования.
Что такое индексы? Очень часто данные из таблиц подвергаются каким-то изменениям, поэтому прежде чем произвести редактирование над какой- либо строкой, необходимо ее найти. Даже статические таблицы, использующиеся в качестве справочников, тоже подвергаются операциям поиска перед выводом запрашиваемых данных. Поиск — достаточно трудоемкая операция, особенно если таблица содержит очень много строк. Индексы направлены на ускорение этой процедуры, а также могут использоваться в качестве отправной точки при сортировке.
В общем случае индексы можно рассматривать как таблицу адресов на строки данных. По этой таблице легко найти какую-то определенную строку таблицы. При этом следует учитывать то, что непроиндексированное поле невозможно упорядочить без использования SQL-запросов.
Если вам надо, чтобы какая-то таблица была упорядочена по полю Фамилия, то это поле надо сначала проиндексировать. Затем нужно только указать, что таблица должна работать с таким-то индексом. Далее сортировка производится автоматически. В этом случае индекс используется для сортировки и значительно ускоряет этот процесс. Для большинства баз данных используется правило — если по какому-либо полю (колонке данных) будет происходить сортировка, то эта колонка должна быть проиндексирована, чтобы скорость сортировки была максимальной.
Delphi и базы данных
Для работы с базами в Delphi есть несколько наборов компонентов. Каждый набор очень хорошо подходит для решения определенного круга задач. Почему такое разнообразие компонентов? Все они используют разные технологии доступа к данным и отличаются по своим возможностям. Microsoft встроила в свои продукты разработки только технологию доступа к данным ADO, собственной разработки. Фирма Borland предоставила разнообразные средства, работающие через разные технологии, и не ограничивает программиста только своими разработками. Такое положение вещей дает громадные преимущества. Помимо этого, есть группы компонентов, которые могут использоваться в любом случае.
Произведем краткий обзор имеющихся средств доступа к базам данных.
На вкладке Data Access расположены основные компоненты доступа к данным. Эти компоненты общие для всех и могут использоваться совместно с другими группами компонентов.
На вкладке Data Controls расположены компоненты для отображения и редактирования данных в таблицах. Эти компоненты так же используются в независимости от используемой на данный момент технологии доступа к данным.
П Вкладка BDE содержит компоненты, позволяющие получить доступ к базам данных по технологии, разработанной фирмой Borland, под названием Borland Database Engine. Эта технология сильно устарела и поставляется только для учета совместимости со старыми версиями. Несмотря на это, она хорошо работает с устаревшими типами баз данных, например, такими как Paradox и dBase.
П DBExpress — это новая технология доступа к данным фирмы Borland. Она отличается большей гибкостью и хорошо подходит для программи-
рования клиент-серверных приложений, использующих базы данных. Компоненты с одноименной вкладки желательно использовать с базами данных, построенных по серверной технологии, например, Oracle, DB2 или MySQL.
口 ADO (Active Data Objects) — технология доступа к данным, разработанная корпорацией Microsoft. Очень хорошая библиотека, но использовать ее желательно только с базами данных Microsoft, а именно MS Access или MS SQL Server. Ее также можно использовать, если у вас специфичный сервер баз данных, который может работать только через ODBC.
(Совет)
Работа с базами данных Access идет через специальную надстройку DAO, которая может устанавливаться на компьютер вместе с программой Office или идти как втдельная установка. Так что если ваша программа не будет работать на компьютере клиента, то надо позаботиться ©б установке DAO и ADO на этот компьютер. На компакт-диске в папке DLL вы можете найти файл mdac_typ.exe, который устанавливает компоненты ADO версии 2.7. Это самые свежие компоненты на момент написания книги.
На данном этапе не ставится цель рассмотреть абсолютно все компоненты. Однако информапия по наиболее важным из них будет рассмотрена. Это обеспечит возможность писать профессиональные приложения для работы с базами данных.
Создание
первой базы данных Access
Сейчас мы подробно рассмотрим, как создавать и использовать базы данных Access. Для последующей работы необходимо, чтобы на вашем компьютере были установлены MS Office и его компонент MS Access. Именно в нем и будут создаваться РБД, а вот работать с ними мы будем уже из Delphi.
Запустите Access и выберите меню Файл | Создать. В мастере создания базы данных выберите пункт База данных и нажмите ОК. Вам предложат выбрать имя базы и место расположения, укажите, что угодно (например, файл Database.mdb).
После этого Access создаст базу и сохранит ее по указанному пути. Далее вы увидите окно, показанное на рис. 14.1, в котором и происходит работа с базой.
С левой стороны окна находится колонка выбора объектов РБД, с которыми вы можете работать. Первым стоит пункт Таблицы (он выделен по умолчанию), который и будет нас интересовать. Если этот объект у вас не выделен, выделите его.
Создание таблицы с помощью мастераСоздание таблицы путем ввода данныхСоздание таблицы с помощью мастераСоздание таблицы путем ввода данныхСоздание таблицы в режиме конструктораСоздание таблицы в режиме конструктораОбъектыТаблицы 百1 Запросы 国 Фор-ы ■ Отчеты 勲 Страницы Я Макросы 4t МодулиОбъектыТаблицы 百1 Запросы 国 Фор-ы ■ Отчеты 勲 Страницы Я Макросы 4t Модулиs^dbl : база данных (формат Access 2000)s^dbl : база данных (формат Access 2000)СдОткрыть ^ Конструктор ^Создать УСдОткрыть ^ Конструктор ^Создать УРис. 14.1. Окно управления базой данныхРис. 14.1. Окно управления базой данныхГруп-ы 兩 ИэбранмовГруп-ы 兩 Иэбранмов
Создание таблицы с помощью мастера
Создание таблицы путем ввода данных
Создание таблицы с помощью мастера
Создание таблицы путем ввода данных
Создание таблицы в режиме конструктора
Создание таблицы в режиме конструктора
Объекты
Таблицы 百1 Запросы 国 Фор-ы ■ Отчеты 勲 Страницы Я Макросы 4t Модули
Объекты
Таблицы 百1 Запросы 国 Фор-ы ■ Отчеты 勲 Страницы Я Макросы 4t Модули
s^dbl : база данных (формат Access 2000)
s^dbl : база данных (формат Access 2000)
СдОткрыть ^ Конструктор ^Создать У
СдОткрыть ^ Конструктор ^Создать У
Рис. 14.1. Окно управления базой данных
Рис. 14.1. Окно управления базой данных
Груп-ы 兩 Иэбранмов
Груп-ы 兩 Иэбранмов
В окне справа находятся три пункта:
口 Создание таблицы в режиме конструктора;
П Создание таблицы с помощью мастера;
П Создание таблицы путем ввода данных.
С помощью этих команд можно создать таблицы внутри созданной базы данных Access, которая может хранить в одном файле несколько таблиц.
Все данные в базах данных хранятся в виде двухмерных таблиц. На рис. 14.2 вы можете видеть пример простой таблицы, состоящей из семи колонок и множества строк.
Колонки в таблицах называются полями. По ним определяется, какие именно данные хранятся в таблице. Давайте попробуем создать базу данных телефонного справочника. Щелкните кнопкой мыши по пункту меню Создание таблицы в режиме конструктора (см. рис. 14.1), чтобы создать новую таблицу в базе данных. Перед вами откроется окно, например, как показано на рис. 14.3.
В верхней части окна находится сетка, в которой вы вводите поля таблицы, их тип и описание (последнее не обязательно). Когда вы вписали в сетку имя нового поля и указали тип, внизу окна появляются свойства нового поля. В зависимости от типа поля изменяется и количество свойств.
Рис. 14.2. Пример простой таблицы
Рис. 14.3. Окно создания таблицы
Рассмотрим наиболее важные из них.
□ Масксимальная длина поля. Для текстового поля размер не может быть больше 255. Если текст длиннее, то надо использовать Поле Мето.
П Формат поля. Здесь вы можете указать внешний вид данных. Например, поле может выглядеть как Yes / NO для логических полей, или — mm уууу для поля даты.
О Маска ввода. Здесь мы вводим маску, которая отвечает за отображение поля при редактировании. Если вы щелкнете мышью на кнопке с точками —... — в строке Маска ввода, то увидите мастер создания маски.
П Значение по умолчанию. Указанное здесь значение будет использоваться, если пользователь не указал в поле каких-либо данных.
П Обязательное поле. Если пользователь не введет сюда значение, то появится сообщение об ошибке. Такое поле не может быть пустым.
П. Пустые строки. Похоже на предыдущее поле, потому что оно тоже не может быть пустым.
П Индексированное поле. Основной индекс всегда без допуска совпадений. Остальные мотуг допускать двойные значения. Может быть:
неиндексированным;
индексированным с допуском совпадений;
индексированным без допуска совпадений.
□ Сжатие Юникод — позволяет сжать данные в соответствии с кодировкой Unicode.
Теперь создайте шесть полей.
П Имя поля — Keyl. Тип — счетчик. Это будет ключевое поле. Размер поля — Длинное целое. Индексированное поле — Да (совпадения не допускаются).
П Имя поля — Фамилия. Тип — текстовый. Размер поля — 50. Индексированное поле —Да (допускаются совпадения).
П Имя поля — Имя. Тип — текстовый. Размер поля — 50. Индексированное поле —Да (допускаются совпадения).
П Имя поля — Телефон. Тип — текстовый. Размер поля — 10. Индексированное поле — Да (допускаются совпадения).
П Имя поля — e-mail. Тип ——текстовый. Размер поля — 20. Индексированное поле —Да (допускаются совпадения).
П Имя поля — Город. Тип ——числовой. Размер поля —Длинное целое. Индексированное поле — Нет.
工 Пршуеиание〉
Почему город не строковый, ведь названия городов — это текст? Пока здесь не будет объясняться этот феномен, оставим его на потом. Чуть позже мы увидим, почему город должен быть числовым.
Помимо этого, у всех полей — Обязательное поле значение стоит в Нет, и в пустые строки выставлено Да. Если вы сделаете поле обязательным, то во всех строках обязательно должно быть заполнено соответствующее поле. Если вы запретите пустые строки (значение — Нет), то в указанном поле должно быть обязательно что-то введено, иначе произойдет ошибка. В реальных условиях, если какое-то поле обязательно должно иметь значение, то лучше сделать его обязательным. Не надо надеяться на добропорядочность пользователей, цотому что они слишком часто подводят. Пусть лучше база данных следит за правильностью данных.
Теперь выделим первое поле (KeyГ). Щелкните правой кнопкой мыши и выберите пункт Ключевое поле. Задание ключевого поля является обязательным действием, если вы этого не сделаете, то таблица не сможет редактироваться, а это значит, что в нее нельзя будет добавлять строки.
工Примечание ~
В Access при попытке сохранить таблицу без ключевого поля программа предлагает самостоятельно создать ключ. Если вы согласитесь, то таким полем будет первое по счету.
Все, таблицу можно сохранять и закрывать. На вопрос: ^Сохранить таблицу** отвечайте положительно и сохраните под именем Справочник.
Наша первая база данных готова. Теперь можно перейти к следующему разделу, где мы напишем первый пример для работы с созданной БД и таблицей.
14.3. Пример работы с базами данных
Составим программу, которая будет работать с базой данных MS Access. Как уже говорилось, для такой разработки лучше всего использовать ADO. Давайте напишем наше первое приложение для работы с базой данных.
Создайте новый проект. Теперь поместите на форму компонент ADOConnection с вкладки ADO палитры компонентов. Настроим соединение с сервером, которое должно быть указано в свойстве Connectionstring. Для этого надо дважды щелкнуть кнопкой мыши по строке connectionstring (или дважды щелкнуть по самому компоненту). Перед вами открывается окно, показанное на рис. 14.4.
Здесь перед нами стоит выбор:
П использовать специальный файл (Use Data Link File);
П использовать строку подключения (Use Connection String).
Рис. 14.4. Окно создания подключения к базе
Второе, на мой взгляд, более предпочтительно, поэтому рассмотрим, как создать строку подключения. Для этого щелкните мышью по кнопке Build. Перед нами откроется еще одно окно, показанное на рис. 14.5.
Рис. 14.5. Окно сюздания строки подключения
На вкладке Поставщик данных (Provider) перечислены все доступные ADO драйверы доступа к базам данных. Если какого-то драйвера нет, то можно попробовать выделенный по умолчанию — Microsoft OLE DB Provider for ODBC Drivers. Этот драйвер позволяет получить доступ к данным через ODBC-драйвер, который можно отнести,к большинству существующих БД. Здесь следует учесть то, что данный драйвер может быть и не установлен на вашем компьютере.
В нашем случае для доступа к базам данных MS Access используется драйвер Microsoft Jet OLE DB Provider. Такой драйвер обязательно устанавливается на машину вместе с MS Office, а в последних версиях Windows он устанавливается по умолчанию.
В определенных случаях может быть установлено сразу две версии этого драйвера, поэтому выберем более новый — Microsoft Jet 4.0 OLE DB Provider. После этого нажмите кнопку Далее (Next), или перейдите на вкладку Подключение (Connection).
Вид вкладки Подключение (Connection) зависит от выбранного драйвера. В нашем случае она должна выглядеть, как показано на рис. 14.6.
Рис. 14.6. Вкладка Подключение
Первым делом, в строке Выберете или введите имя базы данных (Select or enter a database name), надо ввести имя базы данных (при необходимости и путь). Если база данных будет располагаться в той же директории, что и исполняемый файл приложения, то путь указывать не надо.
(Совет )
Храните базы в одном каталоге с исполняемыми (программными) файлами. Если вы будете хранить файлы отдельно от исполняемого, то вам придется указывать полный путь, а это может вызвать проблемы при переносе программы на другой компьютер. Ведь там программа будет искать базу по указанному пути, который может измениться. Если хотите держать файлы в другом каталоге, то указывайте относительный путь (относительно текущего каталога).
Чуть позже мы узнаем, как самим сгенерировать строку подключения и избавиться от использования окна и зависимости от путей.
工 Примечание
Чтобы легче было выбрать файл базы данных, необходимо щелкнуть мышью по кнопке с точками справа от строки ввода.
Теперь заполним следующие поля.
口 Пользователь (User name): Значение поля можно оставить по умолчанию, если не задано иное при создании базы в MS Access.
О Пароль (Password). Если база имеет пароль, то его необходимо указать.
口 Пустой пароль (Blank password). Если пароль не нужен, то здесь желательно поставить галочку.
П Разрешить сохранение пароля (Allow saving password). Если здесь поставить галочку, то пароль может быть сохранен. Если нет, то при каждом запуске программы будет появляться окно с просьбой ввести пароль.
Как только вы выберете базу данных, нажмите кнопку Проверить подключение (Test Connection), чтобы протестировать соединение. Если все указано правильно, то должно появиться сообщение Тестирование соединения прошло удачно (Test connection succeeded). Все, можно нажать ОК, чтобы закрыть окно создания строки подключения. Затем еще раз нажать ОК, чтобы закрыть окно редактора строки подключения (см. рис. 14.5).
Продолжим настройку приложения. Теперь в свойствах компонента ADOConnection отключите СВОЙСТВО LoginPrompt, выставив его В false. Это нужно для того, чтобы при каждом обращении к базе не происходил вызов окна ввода пароля. Далее выставим свойство connected в true, чтобы произошло соединение с базой.
На этом соединение можно считать оконченным. Теперь нам надо получить доступ к созданной таблице Справочник. Для этого поместим на форму ком-
понент ADOTable с вкладки ADO палитры компонентов. Сразу измените его СВОЙСТВО Name на BookTable.
В этом компоненте тоже есть свойство Connectionstring, и его также можно настраивать. Почему — можно. Да потому что, для того, чтобы этого не делать, мы установили на форму компонент ADOConnection. Теперь можно указать у нашего компонента BookName в свойстве connection, созданный нами компонент соединения с базой данных. Щелкните по выпадающему списку в свойстве connection и выберите там единственный пункт ADOConnectionl. Теперь не надо Заполнять СВОЙСТВО Connectionstring.
В свойстве Tabi eName нужно выбрать имя нашей таблицы (Справочник). Все, таблица и соединение указаны, можно подключатся. Для этого выставьте СВОЙСТВО Active В true.
Для отображения данных из таблицы надо установить на форму компонент DataSource с вкладки Data Access палитры компонентов. Теперь этому компоненту надо указать, какую именно таблицу он должен отображать. Для этого в свойстве DataSet нужно из выпадающего списка выбрать компонент BookTable, который связан с нашей таблицей Справочник.
Все приготовления готовы, можно приступать к реальному отображению данных. Самый простой способ отобразить таблицу — установить компонент DBGrid. Этот компонент — сетка, которая может отображать данные в виде таблицы. В этом же компоненте можно добавлять, удалять и редактировать строки нашей таблицы.
Рис. 14.7. Форма приложения
И последний этап создания приложения — связывание компонента сетки с компонентом отображения таблицы. Для этого в свойстве DataSuorce компонента DBGrid нужно указать созданный нами компонент DataSourcel.
Вот теперь приложение готово (рис. 14.7). Может быть, вы не заметили, но мы не написали ни одной строчки кода. Вот до какой степени Delphi упро-
щает процесс программирования баз данных, что даже программировать не надо.
Попробуйте запустить этот пример, а затем создать несколько строк, отредактировать уже существующие или удалить что-нибудь. Для вставки строки используй клавишу <Ins>, а для удаления — сочетание клавиш <Ctrl>+<Del>.
<Примечанме ~
На компакт-диске в папке \ Примеры \ Глава 14 \Database1 вы можете увидеть пример этой программы.
Свойства компонента TADOTable
Компонент TADOTable имеет множество полезных свойств. Большинство из них просты в использовании. В связи с этим, чтобы не писать большое количество примеров, кратко рассмотрим основные из этих свойств. В дальнейшем с некоторыми мы познакомимся на практике.
口 Mastersource — в этом свойстве указывается главная по отношению к текущей таблица. Мы рассмотрим это свойство достаточно подробно, когда будем изучать связанные таблицы.
П Readonly — если это свойство равно true, то таблицу нельзя редактировать. В этом случае данные только отображаются. Обязательно устанавливайте это свойство для тех таблиц, где данные не должны изменяться и пользователь не должен вносить в них изменения.
□ TableDirect — это свойство отображает, как будет происходить доступ к таблице. Если этот параметр равен true, то будет происходить прямой доступ к таблице по имени. Если false, то незаметно для вас будет происходить специальный SQL-запрос к базе данных (о SQL-запросах читайте ниже). Не все базы данных позволяют работать через прямой доступ, поэтому это свойство по умолчанию равно false.
П TableName — имя таблицы, данные которой мы хотим обрабатывать.
П cachesize — размер кэш-памяти. Если здесь установить число 50, то при первом подключении к таблице компонент выберет первые 50 строк и разместит их в локальной памяти, что ускорит доступ к ним. Остальные строки будут подгружаться с сервера по мере надобности.
CanModify — свойство похоже на Readonly и указывает на возможность редактирования данных таблицы.
П CommandTimeout — время ожидания выполнения команды. Когда компонент направляет команду базе данных, то он запускает таймер ожидания, по истечению которого (если команда не выполнилась) происходит сообщение об ошибке.
П Connection — здесь указывается компонент TADOConnection, через который происходит подключение.
П connectionstring — строка подключения к базе данных.
П CursorLocat ion — расположение курсора, который считывает данные и указывает текущую позицию в таблице. Курсор может находиться на сервере или на машине клиента.
П CursorType — тип курсора. Туг возможен один из следующих вариантов:
ctUnspecified — расположение курсора не указано;
ctOpenForwardOnly — курсор может двигаться только вперед;
”• ctKeyset — изменения, внесенные одним пользователем, не видны остальным пользователям, подключенным к этой таблице;
工 Примечание
Если с одной таблицей работают одновременно несколько пользователей, то при таком курсоре для отображения изменений других пользователей нужно отключиться от базы и подключиться к ней снова).
ctDynamic — динамический курсор, при котором изменения одного пользователя видят все остальные;
ctstatic — статический курсор, при котором изменения одного пользователя не видны остальным.
_ Вн—ман—е
Если курсор расположен в таблице БД клиента, то можно использовать только статический курсор. Не все типы курсоров могут работать с определенной базой данных. Одна база данных может поддерживать один тип, а другая может поддерживать все типы курсоров.
П Filter — строка фильтра.
口 Filtered — является ли таблица фильтруемой. Если здесь установить false, то строка фильтра (filter)игнорируется.
П indexFieldNames — имя индексированной колонки. Индексы используются для сортировки данных или для связи между таблицами.
П RecNo — номер текущей выделенной строки.
П RecordCount — количество строк в таблице.
П sort — строка, в которой указывается тип сортировки. Например, для сортировки по полю Телефон нужно записать строку: ADOQueryl.Sort: = Телефон ASC*. Оператор ASC говорит о том, что надо
сортировать в порядке возрастания. Оператор DESC говорит о сортировке в порядке убывания.
П Active — если это свойство равно true, то таблица открыта.
口 AggFields — здесь хранятся все агрегатные поля.
О AutoCalcFields — если здесь true, то надо автоматически пересчитывать поля.
П Bof — на это свойство влиять нельзя, но если оно равно true, то мы находимся в начале файла.
П Bookmark — здесь находится текущая вкладка.
口 Eof — на это свойство влиять нельзя, но если оно равно true, то мы находимся в конце файла.
П Fieldcount — здесь хранится количество полей в таблице.
П Fields — через это поле можно получить доступ к значениям других полей. Допустим, что вам надо узнать, какое значение храниться в 4-м поле. Для ЭТОГО Нужно написать Table.Fields. Fields |4] .AsString. Метод As String говорит о том, что надо получить значение в виде строки. В книге используется обращение к полям по именам.
П Fieldvalues — с помощью этого свойства можно легко получить доступ к любому значению указанного поля. Имя поля нужно указывать в квадратных скобках. Например, Tablel. Fieldvalues [ 'Телефон' ] : = * 334 6598 1;.
П FilterOption — настройки фильтра. Здесь можно указывать следующие параметры:
FoCaselnsensitive —фильтр будет нечувствителен к регистру;
foNoPartialCompare — если стоит этот параметр, то сравнения будут происходить с точной копией указанного в фильтре значения.
[Примечание ;
Если параметр foNoPartiaiCompare не указан, то в фильтр будут попадать строки, содержащие значение в фильтре, но не являющиеся его точной копией. Например, если в фильтре указано показывать слова "са", то в фильтр попадут все слова, начинающиеся на "са” (самолет, самокат).
П Modified — если это свойство равно true, то в таблице были внесены изменения.
Методы компонента TADOTable
Как видите, свойств очень много, и большинство из них очень полезные. В течение всей этой главы мы будем знакомиться с ними на практике. Te
is Зак 942
перь приготовьтесь к рассмотрению методов компонента. Они не менее полезны, и мы также будем знакомиться с большинством из них на практике.
□ BookmarkValid — этот метод проверяет правильность вкладки. В качестве единственного параметра нужно указать вкладку типа Tbookmark, и если она является Ндействительнои *, то результатом будет true.
П CancelUpdates — отменить обновления, сохраненные в кэш-памяти.
П comp are Bookmarks — сравнение двух вкладок. У метода два параметра типа TBookmark. Эти две вкладки сравниваются. Если вкладки равны, то результат равен нулю. Если первая меньше второй, то результат будет -1. Если первая больше второй, то результат равен единице.
П DeleteRecords — удалить записи. У метода один параметр, определяющий, какие записи удалять. Вы можешь указать следующие его значения:
ArCurrent — удалить только текущую запись;
arFiltered — удалить записи, удовлетворяющие установленному фильтру;
аг АИ — удалить все записи;
arAHChapters — удалить записи во всех разделах ADO.
П Append — добавить новую запись в конец таблицы.
П cancel — отменить изменения текущей строки, если изменения еще не были сохранены с помощью метода Post.
П close — закрыть таблицу.
ГГ Delete — УДАЛИТЬ Текущую строку.
П Edit — перейти в режим редактирования. После этого можно изменять значения полей.
П FieldByName — найти поле по имени. В качестве единственного параметра нужно указать имя поля в виде строки, и в результате получаем ссылку на поле в виде объекта TField.
П First — перейти на первую строку в таблице.
П insert — вставить новую строку в таблицу.
П IsEmpty — если метод вернет true, то в таблице нет записей.
П Last — перейти на последнюю запись в таблице.
口 Next — перейти на следующую запись.
ГТ Post — принять все изменения.
П Prior — двигаться на предыдущую запись в таблице.
П Refresh — обновить информацию о данных.
П up date Re cord — обновить текущую запись.
14.4. Управление отображением данных
В предыдущем примере все работает прекрасно, только вот поле Key пользователю видеть абсолютно ненужно. Это поле — счетчик, и его значение увеличивается автоматически. А раз пользователь не может влиять на значения этого поля и оно не несет для него полезной информации, то и отображать это поле не надо.
Чтобы спрятать от пользователя ненужные поля и показывать только то, что мы хотим, и в том виде, в котором хотим, необходимо научиться управлять отображением данных. Но прежде чем приступить к изучению этого вопроса, давайте создадим в нашей базе еще два поля Дата и мобильник. Загрузите базу данных в Access, щелкните по ней правой кнопкой мыши и в появившемся меню выберите Конструктор. Далее, в режиме конструктора необходимо выполнить ряд действий.
Добавьте поле с именем Дата и типом — Дата / время.
Добавьте поле с именем мобильник и типом — логический. Если в строке находится номер мобильного телефона, то в этом поле будем ставить true, иначе false.
Закройте таблицу.
Теперь перейдем в Delphi и попробуем отобразить изменения в уже созданном примере.
Для начала давайте перенесем компоненты доступа к базе данных в отдельное специальное окно. Выделите компоненты ADOConnectionl, DataSourcel и BookTable. Теперь выберите из меню Edit пункт Cut, чтобы эти компоненты скопировались в буфер обмена и сразу удалились с формы.
Выберите меню File | New | Data Module. Этим вы заставите Delphi создать специальное окно Data Module, которое подходит для хранения компонентов доступа к базам данных.
Выберите из меню Edit пункт Paste, чтобы вставить в это окно вырезанные нами компоненты. Расположите теперь эти компоненты в окне так, как вам, буцет удобно (например, как показано на рис. 14.8).
Теперь все компоненты, которые предназначены для организации доступа к базе данных, будем располагать в этом окне, чтобы с ними удобно было работать. Сохраните новый модуль под именем DataModuleUnit.
Откройте Менеджер проектов (в меню Project надо выбрать Project Manager) и расположите это окно так, чтобы вам было удобно в любой момент получить к нему доступ (рис. 14.9). Теперь, когда потребуется перейти из главной формы в модуль данных DataModule или обратно, вы легко можете это сделать с помощью Менеджера проектов, дважды щелкнув кнопкой мыши по нужной форме.
t Мапа югt Мапа югРис. 14.8. Окно DataModuleРис. 14.8. Окно DataModulellelephon.exellelephon.exe& X ®New Remove& X ®New RemoveFlesFlesPathPath曰電 ProjectGfoupIВ 厚 Telephon.exe ffi 匐 M.ainU”曰電 ProjectGfoupIВ 厚 Telephon.exe ffi 匐 M.ainU”DataModule...DataModule...EAProgram Ffle$\Borlarid\Delphi7\Pfoiects F:\CyD\B оок\Б иблия D elphi\n римеры\Г лава 該 F: 4CyD \B ook\ Б иблия D elphiXTI римеры\Г лава 14 F:\CyD\B оок\Библия D е1рЬ|\Примеры\Г лаваEAProgram Ffle$\Borlarid\Delphi7\Pfoiects F:\CyD\B оок\Б иблия D elphi\n римеры\Г лава 該 F: 4CyD \B ook\ Б иблия D elphiXTI римеры\Г лава 14 F:\CyD\B оок\Библия D е1рЬ|\Примеры\Г лаваРис. 14.9. Менеджер проектовРис. 14.9. Менеджер проектовЕсли вы хоть раз уже открывали какую-то форму из Менеджера проектов и не закрывали, то ее можно найти на вкладках в окне редактора кода, как это показано на рис. 14.10.Если вы хоть раз уже открывали какую-то форму из Менеджера проектов и не закрывали, то ее можно найти на вкладках в окне редактора кода, как это показано на рис. 14.10.ВниманиеВнимание
t Мапа юг
t Мапа юг
Рис. 14.8. Окно DataModule
Рис. 14.8. Окно DataModule
llelephon.exe
llelephon.exe
& X ®
New Remove
& X ®
New Remove
Fles
Fles
Path
Path
曰電 ProjectGfoupI
В 厚 Telephon.exe ffi 匐 M.ainU”
曰電 ProjectGfoupI
В 厚 Telephon.exe ffi 匐 M.ainU”
DataModule...
DataModule...
EAProgram Ffle$\Borlarid\Delphi7\Pfoiects F:\CyD\B оок\Б иблия D elphi\n римеры\Г лава 該 F: 4CyD \B ook\ Б иблия D elphiXTI римеры\Г лава 14 F:\CyD\B оок\Библия D е1рЬ|\Примеры\Г лава
EAProgram Ffle$\Borlarid\Delphi7\Pfoiects F:\CyD\B оок\Б иблия D elphi\n римеры\Г лава 該 F: 4CyD \B ook\ Б иблия D elphiXTI римеры\Г лава 14 F:\CyD\B оок\Библия D е1рЬ|\Примеры\Г лава
Рис. 14.9. Менеджер проектов
Рис. 14.9. Менеджер проектов
Если вы хоть раз уже открывали какую-то форму из Менеджера проектов и не закрывали, то ее можно найти на вкладках в окне редактора кода, как это показано на рис. 14.10.
Если вы хоть раз уже открывали какую-то форму из Менеджера проектов и не закрывали, то ее можно найти на вкладках в окне редактора кода, как это показано на рис. 14.10.
Внимание
Внимание
Переключаться между визуальной формой и ее кодом очень удобно простым нажатием клавиши <F12>.
На этом инструкции по работе с оболочкой заканчиваются.
Перейдите в главную форму, и вы сразу увидите, что в нашей сетке DBGridl нет данных. Почему? Да потому, что она потеряла связь с компонентами доступа к данным. Выделите сетку и щелкните кнопкой мыши по свойству DataSource. Вы увидите, что в выпадающем списке ничего нет. Это потому
что все нужные компоненты мы убрали в, отдельную форму и главная форма пока об этом не знает.
Рис. 14.10. Вкладки форм в редакторе кода
Чтобы форма узнала о существовании компонентов, ей нужно определить в разделе uses модуль DataModuleUn.it. Это можно сделать вручную или выбрать из меню File пункт Use Unit (в этот момент должно быть выделено окно кода главной формы, потому что мы подключаем новый модуль именно к ней). В появившемся окне нужно выбрать имя нового модуля — Dat a Module Unit (пока оно одно в списке) и нажать ОК.
Проверьте теперь в редакторе кода, чтобы после ъслючевого слова implementation Появилась ЗЭПИСЬ — uses DataModuleUnit ;
implementation
uses DataModuleUnit;
{$R *.dfm}
Вот теперь можно выделять сетку DBGridl и в свойстве Datasource указать компонент Datasource, данные которого должны быть отображены в сетке (DataModulel.DataSourcel).
Теперь перейдем в модуль DataModule и попытаемся настроить отображение данных. Дважды щелкните мышью по компоненту BookTable, и перед вами появится окно редактирования полей базы данных (рис. 14.11).
Рис. 14.11. Окно редактирования полей базы данных
Пока оно пустое. В него нужно добавить все поля базы данных. Для этого щелкните по нему правой кнопкой мыши и в появившемся меню выберите пункт Добавить все поля (Add All Field). Окно автоматически заполнится именами полей.
Поля можно переставлять местами, двигая их мышью. При этом физическое положение их в базе данных не меняется. Однако при отображении данных в сетке они будут отображаться в том порядке, в ютором выстроены. Таким образом, вы в любой момент можете изменить порядок отображения данных, не обращаясь к самой БД.
Также можно выделять отдельные поля и в объектном инспекторе редактировать их свойства. Свойства у полей могут быть разные в зависимости от типа поля.
Продолжим формирование базы данных. Первое, что мы должны сделать,— убрать видимость счетчика (поле Key). Ранее уже договорились, что пользователю оно не нужно, и он не должен его видеть. Выделите это свойство и в объектном инспекторе установите в свойстве visible значение false (это свойство есть у всех полей). Сразу же можете перейти в главную форму или
запустить программу, чтобы убедиться в том, что поле Keyl больше не отображается.
Теперь отредактируем длину отображения колонок. Для этого выделите поле Фамилия. В базе данных мы выделили под это поле 50 символов (на всякий случай). В сетке ширина колонки будет отображаться по умолчанию на всю длину. Но чаще всего фамилии не превышают 15 символов, поэтому нет смысла отображать всю длину. Намного удобней отображать только 15 символов, а если что-то не поместится, то пользователь программы в любой момент сможет раздвинуть колонку и увидеть недостающие символы.
За ширину колонки отвечает свойство Displaywidth (это свойство есть у всех полей). По умолчанию в нем стоит значение физической ширины поля, но мы укажем там 15. Опять же, на саму базу данных это не влияет, и поле все еще имеет размер 50, но ширина отображаемой колонки в сетке 6: цет 15. Точно так же сократите ширину поля имя.
Р&смотрим еще несталько свойств полей.
Defau 11Exprеssion — здесь можно оставить значение по умолчанию. В дальнейшем, когда будут создаваться новые строки, то в поля будут сразу заноситься указанные здесь значения.
П Maxvalue — максимально допустимое значение. Если это числовое поле, и оно должно изменяться в определенных рамках (например, от 0 до 100), то желательно указать эти ограничения здесь, чтобы сократить вероятность опечатки пользователя. Все люди склонны к ошибкам, так пускай программа автоматически соьфащает вероятность таких ошибок.
Minvalue — минимально допустимое значение.
口 Readonly — поле толыю для чтения. Если какое-то поле не должно изменяться, установите у него в свойстве Readonly значение true. В этом случае вы обезопасите программу от случайного изменения данного поля пользователем.
П Required — если здесь true, то поле является обязательным и обязательно должно иметь какое-то значение. Если гюльзователь ничего не укажет, то программа сообщит об этом. Допустим, что какое-то поле у вас участвует в расчетах. Если в этом поле не окажется данных, то программа может зависнуть. Есть два пути решения этой задачи. Первый путь — при расчете проверять наличие в поле данных или требовать, чтобы пользователь обязательно что-то ввел. Второй путь предпочтительней, если это поле действительно важное. В этом случае представьте запись в телефонном справочнике без телефона. Спрашивается, зачем тогда нужна эта запись, если не указан телефон? Таким образом поле для номера телефона можно делать обязательным.
□ Tag — просто числовое значение, которое можно использовать по своему усмотрению.
Теперь в нашем окне помещается практически вся необходимая информация и не надо лишний раз использовать полосы прокрутки.
Запустите программу и заполните поле Дата любыми значениями и для всех записей. При заполнении будьте внимательны и указывайте реальные даты. Если вы введете недопустимое значение, то программа выдаст ошибку.
.Внимание .
При вводе данных учитывайте разделитель чисел. В большинстве русскоязычных ОС Windows в качестве разделителя используется точка или знак косой черты (/). К тому же первым идет число, потом месяц и потом год (в англоязычной версии первым может идти месяц). Пробелы недопустимы. Этот порядок и разделитель настраиваются в настройках ОС. Войдите в Панель управления и запусти окно Настройка региональных параметров (рис. 14.12). Здесь вы можете изменить все настройки ввода даты, времени и чисел.
Рис. 14.12. Настройка формата ввода и отображения даты
Вернемся к Delphi. Выделите поле дата. Первое, что мы должны сделать,— уточнить, какую именно дату здесь надо вводить. Так как это телефонный
справочник, то мы будем здесь указывать дату рождения владельца телефона. Поэтому вполне разумно будет в заголовке сетки отображать не просто дата, адата рождения. Тут можно поступить двумя способами:
口 отредактировать имя поля в базе данных (не совсем разумно);
О заставить Delphi отображать в заголовке поля нужный текст.
За текст, отображаемый в заголовке, отвечает свойство DisplayLabel (это свойство есть у всех полей). Давайте в нем введем текст "Дата рождения”. Можете проверить, что теперь в заголовке отображается нужный текст.
Теперь отредактируем формат отображения даты. За это отвечает свойство Di splay Format. Тут можно указывать текстовый формат, в котором нужно отображать дату. Как отображать? Вспомните функцию FormatDateTime и ее первый параметр (см. разд. 10.5). Вот именно это здесь и можно указывать. Многие программисты предпочитают использовать для отображения полный формат — "dddddd".
Ry, и, наконец, нужно указать маску ввода для даты. Ее нужно указывать в свойстве EditMask и так же, как мы это делали — у компонента TMaskEdit.
(Совет )
Для даты желательно всегда указывать маску ввода — 99/99/9999.
Если вы теперь запустите пример, то в поле дата рождения все даты будут отображаться в полном формате (рис. 14.13). Если дважды щелкнуть мышью по этому полю в любой строке (войти в режим редактирования строки), то дата сразу перейдет в режим редактирования (см. вторую строку на рис. 14.13).
Рис. 14.13. Улучшенный вид поля Дата рождения
Последнее, что нам надо сделать, — это отредактировать поле мобильник. Пока что здесь отображаются значения true или false. Но мы русские люди и для нас будет удобнее видеть родные Да или Нет. Выделите это поле и в объектном инспекторе найдите свойство Displayvalues* Для булевых полей здесь нужно указать два значения в формате true ; false, т. е. сначала указываем положительное значение и после точбси с запятой отрицательное (кавычки указывать не надо). Таким образом указывается значение — да ; нет.
Теперь в поле мобильник будут отображаться понятные слова, да и при редактировании теперь нужно вводить не true или false, а понятные да или нет.
工 Примечание
На компакт-диске в папке \ Примеры \ Глава 14\ Database2 вы можете увидеть пример этой программы.
Поисковые поля
Настало время разобрать поле с именем город, которое имеет числовой тип и пока никак не может хранить данные о городе. Для начала создадим отдельную таблицу в нашей базе данных с полями (рис. 14.14):
П Keyl — счетчик (ключевое поле);
口 название города — текстовое поле размером в 30 символов.
Сохраните таблицу под именем Справочник городов. Теперь база данных должна состоять из двух таблиц:
О Справочник;
П Справочник городов.
Давайте откроем проект созданный в прошлом разделе, и модуль DataModuleUnit (рис. 14.15). Добавьте компоненты DataSource (назовем его Town Source) и ADOTable (его назовем TownTable). После этого у компонента Town Source В свойстве DataSet укажите таблицу TownTabie.
Теперь настроим TownTabie на отображение справочника городов. Для этого:
П в свойстве connection укажите компонент ADOConnectionl, который указывает на нашу базу данных.
П в свойстве TableName укажите таблицу — Справочник городов;
П установите свойство Active в true, чтобы активизировать таблицу.
Войдите в редактор полей таблицы TownTabie и добавьте все поля. Сделайте поле Keyl невидимым, потому что это счетчик й пользователю он абсолютно не нужен.
Рис. 14.14. Таблица Справочник городов
Рис. 14.15. Добавление модуля DataModuleUnit
Теперь создадим новую форму для редактирования справочника и сохраним ее в модуле под именем TownBookUnit. Саму форму назовем ТownBookForm.
Подключите к новой форме модуль DataModuieUnit, чтобы отсюда можно было получить доступ к компонентам для работы с базами данных. Для этого из меню File выберите пункт Use Unit (Использовать модуль), в появившемся окне выберите модуль DataModuieUnit и нажмите ОК
Поместите на форму сетку DBGrid и в свойстве Datasource укажите таблицу Справочник городов — DataModulel. TownSource. Можете все эти действия красиво оформить и добавить кнопку ОК для закрытия окна справочника. Окно редактора справочника городов вы можете увидеть на рис. 14.16. Для большей красоты добавим на форму еще три кнопки Добавить, Сохранить и Удалить дл兄 добавления, удаления и сохранения строк справочника.
Рис. 14.16. Обновленная форма справочника
Для события onclick кнопки Добавить пишем следующий код:
procedure TTownBookForm.AddBtnClick(Sender: TObject);
begin
DataModulel.TownTable.Insert;
DBGridl.SetFocus;
end;
Метод insert таблицы TownTable добавляет новую строку. Во второй строке вызывается метод SetFocus нашей сетки, чтобы фокус ввода перешел на него. После нажатия кнопки Добавить фов^с попадает на нее, но после добавления новой строки, вполне логичным будет перенести фов^с на сетку, потому что пользователь будет вводить имя города для новой строки.
Для события Onclick кнопки Сохранить пишем следующий код:
procedure TTownBookForm.SaveBtnClick(Sender: TObject);
begin
if DataModulel. TownTable.Modified then
DataModulel.TownTable.Post;
end;
Если текущая строка претерпела изменения, то в свойстве Modifies будет true, иначе false. Однако если произошли изменения, то их надо сохранить, иначе при закрытии окна пользователем данные могут не сохраниться. Для сохранения изменений используется метод Post.
Здесь также можно переносить фокус обратно в сетку, но в данном случае этого не сделано.
Для события Onclick кнопки Удалить пишем следующий код:
procedure TTownBookForm.DelBtnClick(Sender: TObject);
begin
DataModulel.TownTable.Delete;
end;
Метод Delete удаляет текущую строку из таблицы.
Все, формирование внешнего вида таблицы Справочник городов закончено. Теперь перейдите к главной форме и создайте меню или кнопку (в примере выбрано первое) для вызова справочника городов (рис. 14.17).
Рис. 14.17. Меню вызова справочника городов
По событию onclick меню пишем код вызова окна справочника городов:
procedure TForml.TownBookMenuItemClick(Sender: TObject); begin
TownBookForm.ShowModal;
end;
Если вы не добавили модуль справочника городов к главной форме и попробуете сейчас откомпилировать проект, то перед вами появится окно, в котором Delphi говорит о том, что форма TownBookForm объявлена в модуле TownBookUnit и ваша форма не имеет ссылки на него. Вам предлагается добавить ее автоматически. Выберите Да и модуль будет добавлен автоматически, после этого можно опять компилировать проект, не внося никаких изменений. Теперь все должно пройти удачно. Запустите проект и проверьте работу программы.
Запустите программу, вызовите таблицу Справочник городов и добавьте туда несколько строк. Это будет полезно на будущее, заодно и проверите правильность работы программы.
Теперь у нас есть справочник городов и мы можем связать его данные с основной таблицей. Но перед этим немного .улучшим форму. Вьвделите сетку DBGridl на главной форме и в свойстве Options отключите возможность редактирования данных в сетке — в свойстве dgEditing установите значение false.
Теперь редактирование данных в сетке невозможно, поэтому мы сделаем для этого отдельные окна. В главном меню создайте пункт Редактирование со следующими подпунктами:
П Добавить запись;
П Редактировать запись;
П Удалить запись.
На форме создана еще панель с кнопками, чтобы к этим командам можно было быстро получить доступ, а также назначены командам клавиши быстрого вызова.
Теперь создадим новую форму, которая будет использоваться для редактирования данных каждой записи. Создайте форму и сохраните ее под именем Edit FormUnit. Саму же форму назовем EditRecordForm. Теперь измените у формы следующие свойства:
П ДЛЯ В order Style — bs Single;
IT ДЛЯ Position — poMainFormCenter.
Этого достаточно, чтобы форма выглядела представительно.
Дальнейшее оформление зависит от ваших пристрастий. Здесь рассмотрим только самое необходимое. Для начала подключите к новой форме модуль с данными, потому что нам необходимо будет иметь к ним доступ. Для этого выберите из меню File пункт Use Unit. В появившемся окне выберите DataModuleUnit и нажмите ОК.
Теперь посмотрите на вид формы для редактирования данных (рис. 14.18).
Рис. 14.18.-Окно редактирования данных
В этом окне у нас несколько компонентов для украшения внешнего вида (картинка и панель белого цвета), но это не главное. Вы можете повторить действия и сделать подобную форму, а можете ограничиться только основными компонентами. Основными тут являются — кнопка Сохранить, надписи и компоненты доступа к данным, можно также добавить кнопку, по нажатии которой изменения не будут сохранятся в базе данных.
Напротив надписей Фамилия, Имя, Телефон, e-mail и Дата рождения находятся компоненты DBEdit с вкладки Data Controls. Эти компоненты представляют собой простые строки ввода типа TEdit, только они умеют автоматически редактировать указанные Поля в базе данных. Чтобы компонент видел данные из нужного поля, следует указать у него в свойстве DataSource необходимую таблицу (DataModule 1. DataSource 1, как мы это делали с сеткой редактирования), а в свойстве DataField указать поле, которое надо редактировать. Обязательно попробуйте сделать это сами, чтобы подробно разобраться с процессом установки полей.
Для свойства Мобильник лучше использовать компонент DBCheckBox, который похож на checkbox. У него также надо указать поле в таблице, как и у КОМПОНеНТОВ DBEdit.
Самое интересное — поле Город. Названия городов у нас хранятся в отдельном справочнике, а в основной таблице должны храниться только числа — номера строк из справочника городов. Таким образом, у нас не будет храниться в каждой строке длинное название города, а только число. Допустим, что у вас несколько друзей живет в городе Санкт-Петербург. В этом случае для всех них будет в поле Город длинное название. Это лишний расход памяти. Легче создать справочник, в котором будет только один раз указано название города, а потом только ссылаться на него из основной таблипы.
Допустим, что у вьвделенной записи нужно указать город Москва, который идет под номером 2 (поле Keyl для этой строки в справочнике городов равно 2). В этом случае в основном справочнике в поле Город нужно указать только цифру 2, а название города в любой момент можно найти в справочнике городов по полю Keyl. Так как оно уникально (это определит счетчик), то проблем не возникнет.
Чтобы все это реализовать, достаточно поставить компонент DBLookupOoniboBox с вкладки Data Controls. Теперь нужно указать у него в свойстве Datasource основную таблицу (DataModulel. DataSourcel), которая будет редактироваться, а в свойстве DataField указать поле, которое надо редактировать — Город.
Компонент DBLookupComboBox выглядит как выпадающий список (он похож на TConiboBox). В качестве элементов выпадаюшего списка можно указать содержимое таблицы. В свойстве List source нужно указать таблицу, из которой будут браться элементы для вьшадающего списка. Давайте укажем наш справочник городов——DataModulel .TownSource. В свойстве ListField укажем поле из этой таблицы, которое будет использоваться для заполнения выпадающего списка — Название города. В свойстве KeyField нужно указать поле, значение которого будет вноситься в указанное поле основной таблицы (поле Keyl).
工]р6|у|ечанне
Если что-то непонятно, то попробуйте перечитать все заново. Если не поможет, то подождите немного, скоро мы все увидим на практике.
Для события onclick кнопки Сохранить напишите следующий код:
procedure TEditRecordForm.BitBtnlClick (Sender: TObject);
begin
if DataModulel.BookTable. Modified then
DataModulel.BookTable.Post;
Close;
end;
В первой строке мы проверяем, если таблица была изменена (DataModulel.BookTable. Modified равна true), ТО принять изменения DataModulel.BookTable.Post.
Для события onclick кнопки Отмена напишите одну строчку кода:
DataModulel.BookTable.Cancel;
Метод обеспечивает выход из режима редактирования без внесения изменений в таблицу.
Теперь перейдите в основную форму и по нажатии пункта меню Добавить запись напишите:
DataModulel.BookTable.Insert;
EditRecordForm.ShowModal;
Здесь в первой строке мы вставляем в основной таблице новую строку. Во второй строке отображаем окно редактирования данных. По нажатию пункта меню Редактировать запись напишите просто код отображения окна редактирования —EditRecordForm.ShowModal.
Теперь запустите программу. Создайте новую запись. В поле Город выберите какое-нибудь значение из справочника. После нажатия кнопки Сохранить окно закроется. Посмотрите на сетку. В поле Город новой строки вы можете увидеть код города из нашего справочника (в примере было число 3). Эго значит, что в справочнике городов есть запись со значением в поле Keyl, равным 3, и в поле название города указано название, которое мы выбрали. Давайте откроем таблицу через профамму Access и посмотрим на ее содержимое (рис. 14.19).
Рис. 14.19. Таблица, открытая в Access
Поле с названием города (поле Keyl) Ростов-на-Дону действительно имеет значение 3. Таким образом, в основном справочнике не надо указывать полный текст имени города. Достаточно только указать нужный ключ справочника городов, и мы в любой момент сможем найти нужное название.
В следующем разделе мы улучшим пример, а пока попробуйте поэкспериментировать с уже созданным примером. Создайте несколько строк и потом попробуйте отредактировать их. Обратите внимание, что когда вы открываете окно редактирования, в компоненте DBLookupComboBox отображается правильное название города для указанной записи.
工 Примечание、
На компакт-диске в папке \ Примеры \ Глава 14 \ Link вы можете увидеть гример этой программы.
Улучшенный пример с поисковыми полями
Прежде чем двигаться дальше, давайте сначала сделаем для нашего примера с телефонным справочником обработчик для меню Удалить запись. По этому событию нужно вывести запрос на подтверждение удаления и если ответ будет утвердительный, то можно удалять. Создайте такой обработчик и напишите в нем следующее: begin
if Application.MessageBox(PChar(*Вы действительно хотите удалить * +DataModulel.BookTableDSDesigner.AsString), 1Внимание!!! I MB__OKCANCEL) =id_OK then
DataModulel.BookTable.Delete;
end;
Здесь мы отображаем сообщение уже знакомой функцией MessageBox. В первом параметре (текст сообщения) написан текст *Вы действительно хотите удалить' плюс значение поля Фамилия вьвделенной строки —— DataModulel. BookTableDSDesigner .AsString. Самое СЛОЖНОе здесь — DataModulel.BookTableDSDesigner.AsString. Чтобы ПОНЯТЬ эту КОНСТРУКЦИЮ, перейдите в модуль DataModule. Здесь щелкните дважды кнопкой мыши по компоненту BookTable, где у нас подключен основной справочник, и затем по полю Фамилия. Посмотрите в объектном инспекторе имя этого поля, оно должно быть В о okTableD S Designer. Теперь ясно? Мы пишем имя модуля данных (DataModulel), затем через ТОЧКУ ИМЯ ПОЛЯ (BookTableDSDesigner) И метод AsString, который возвращает значение поля в виде строки.
Можете подняться в раздел type модуля DataModulel и убедиться, что внутри нашего объекта TD ata Modulel есть Объявление свойства В ookT able DS Designer типа TWideStringField.
Надеюсь, что с первой строкой теперь все понятно. Если нет, то запустите пример и посмотрите, что произойдет, если попытаться удалить строку.
Во второй строке мы просто удаляем текущую строку с помощью вызова метода Delete таблицы — DataModulel. BookTable . Delete.
Теперь пример практически ‘готов. Единственный недостаток — в сетке просмотра данных вместо названия города отображается индекс строки в справочнике. Это очень неудобно, поэтому давайте исправим этот недостаток.
Перейдите в модуль DataModulei и вьвделите компонент BookTable. Сделайте его неактивным — в свойстве Active установите значение false. Теперь дважды щелкните по этому компоненту и перед вами отмоется уже знакомый редактор полей. Давайте создадим новое поле, которое будет содержать текстовое название города для строк таблицы. Для этого щелкните мышью внутри окна редактора и в появившемся меню выберите пункт New Field. Перед вами должно открыться окно, как показано на рис. 14.20.
Рис. 14.20. Окно создания нового поля
Создание нового поля возможно только при неактивной таблице, поэтому мы выставили в свойстве Active значение false.
Заполните поля этого окна следующим образом:
О В поле Name введите Town;
П В поле Туре укажите тип string — строка;
口 В области Field Туре выберите Lookup — поисковое поле;
口 В списке Key Fields (БСлючевое поле) выберите поле город (это поле основной таблицы, по значению которого надо будет искать текст в другой таблице);
口 В списке DataSet надо указать TownTable — это таблица-справочник городов, тде нужно искать.
口 В списке Lookup Keys укажите Keyl — это поле в таблице- с пр авочнике, по которому надо искать.
П В списке Result Field укажите поле название города — это поле, текст которого будет подставляться.
Теперь нажмите ОК.
В окне редактора полей появится новое поле с именем Town. В самой базе данных такого поля не будет, потому что оно динамичес кое и существует только в памяти машины, кодца программа запущена. Перетащите его мышью выше (ближе к полю город).
Снова сделайте таблицу ВоокТаЫе активной и попробуйте теперь запустить программу. Посмотрите на поле Town и вы увидите теперь там тестовое название города.
Рис. 14.21. Результат работы программы
Теперь программа выглядит намного красивее и интереснее. Единственное —можно сделать поле город невидимым, чтобы пользователь не видел эти непонятные числа, а над полем Town написать надпись Город. Для этого дважды щелкните по компоненту BookTabie, вьвделите поле город и установите в свойстве visible значение false. Теперь выделите поле Town и в свойстве DisplayLabel напишите Город (рис. 14.21).
Примечание
На компакт-диске в папке \Примеры\Глава 14\ Linkl вы можете увидеть пример этой программы.
Сортировка
Наш телефонный справочник уже достаточно хорош. В него можно добавлять новые записи, редактировать или удалять существующие. Но было бы удобней научить нашу программу сортировать записи по определенному полю. Допустим, что вам надо отсортировать данные по полю Фамилия. Как это сделать? Очень просто, для этого существуют индексные поля.
В любой базе данных существует понятие индексного поля. Мы пока создавали только один индекс — главный, для поля счетчика. Эго обязательный индекс и существует всегда, но вы можете создавать любое количество дополнительных индексов. Но это не значит, что надо все поля базы данных сделать индексными, ведь индексирование отнимает дополнительное место на диске, и если переборщить, то можно наоборот сделать хуже — снизить быстродействие программы. Поэтому нужно находить золотую середину., Какие же поля индексировать? Я советую это делать только с теми полями, по которым будет чаще всего происходить поиск. В телефонном справочнике чаще ищут по номеру телефона или по фамилии. В домашнем справочнике это делают чаще по фамилии. Если у вас будет справочник всего города, то, возможно, что чаще будут искать по телефону или даже по адресу. Ну, представьте себе записную книжку с телефонами друзей. По каким параметрам вы будете искать номер своего друга? Ну, конечно же, по фамилии, ведь вы ее знаете, а номер телефона можете забыть. Вот именно поэтому в нашем справочнике желательно сделать поле Фамилия индексным.
Индексы увеличивают скорость поиска данных и позволяют сортировать все записи. Если первое практически невозможно увидеть на небольших примерах этой книги (потому что количество записей у нас очень маленькое, а для ощущения скорости нужно большое количество данных), то сортировку можно ощутить без проблем.
Откройте нашу базу данных в программе Access, вьвделите таблицу Сгфавоч- ник и нажмите кнопку Конструктор. Перед вами откроется окно редактирования свойств полей. Здесь вьвделите поле Фамилия и посмотрите на свойство индексированное поле. Свойство имеет выпадающий список для выбора нужного параметра. Здесь доступны три варианта.
П Нет — поле не индексировано.
П Да (допускаются совпадения) — поле индексировано и две (или более) записи могут иметь одно и то же значение. Это значит, что у вас может
быть две записи, у которых в поле Фамилия указано, например, значение Сидоров. Для нашего случая это идеальный вариант, потому что много людей могут быть под одной и той же фамилией.
□ Да (совпадения не допускаются) — если выбран этот параметр, то в проиндексированном поле нельзя хранить одинаковое значение в разных записях. База данных будет сама следить за уникальностью поля. Если выбрать этот параметр, то две записи в нашем справочнике не смогут иметь, например, значение сидоров. Этот параметр очень удобен, когда вам нужно, чтобы поле было действительно уникальным. Такое бывает в офисных приложениях, когда номер документа часто должен быть уникальным.
Давайте сделаем индексными поля:①амилия, телефон. У обоих нужно выбрать в свойстве индексированное поле параметр Да (допускаются совпадения).
Все, закрываем базу данных и переходим к программированию. Загрузите в Delphi пример, который мы написали в предыдущем разделе, чтобы можно было добавить к нашему справочнику возможность сортировки.
Для начала улучшим нашу форму. Для этого добавьте в меню нашей программы пункт Сортировка и два его подпункта По фамилии и По телефону (рис. 14.22).
Рис. 14.22. Улучшенная форма нашей программы
Для пункта меню По фамилии напишите следующий код: procedure TForml.N8Click (Sender: TObject);
begin
DataModulel. BookTable. IndexFieldNames: = ' Фамилия * ; end;
Для пункта меню По телефону напишите код:
procedure TForml.N8Click(Sender: TObject);
begin
DataModulel. BookTable ・ IndexFieldNames: = ' Телефон, end;
В обоих случаях мы присваиваем свойству IndexFieldNames таблицы BookTable значение поля, по которому нужно сортировать записи.
К Примечание :
На компакт-диске в папке \ Примеры \ Глава 14\ Index вы можете увидеть пример этой программы.
Фильтрация данных
Наш телефонный справочник достаточно быстро пополняется новыми возможностями, но в нем до сих пор нет самого главного — поиска. Представьте себе, что у вас в базе данных находятся телефоны ста человек. Как вы будете искать телефон определенного человека? А если в базе данных будет 1000 записей? Без возможности поиска тут очень тяжело.
Для поиска в компоненте TADOTable есть свойство Filter. В нем можно указывать условие, по которому будут отображаться данные. Например, вы можете указать там отображение только записей, в которых поле Фамилия содержит значение Сидоров. Но для того, чтобы фильтр заработал, надо еще установить свойство Filtered нашей таблицы в true. После этого можно изменять свойство Filter и все изменения сразу же будут вступать в силу.
Свойство Filter — это строка. В ней нужно писать текст условия в виде:
Поле [Оператор сравнения] 1 Значение1
Например, если вы хотите отобразить все записи, в которых поле Фамилия равно значению сидоров, то нужно написать:
AdoTablel.Filter: = *Фамилия='1Сидоров * * 1;
Обратите внимание, что значение нужно указывать в одинарных кавычках. Но п/ г/ одинарные кавычки используются в Delphi для ограничения строк, то тут приходится немного включить соображение. Чтобы внутри строки поставить одинарную кавычку, ее нужно поставить дважды:
•После этого текста будет одинарная кавычка' * это продолжение текста *
Именно таким способом мы ставим перед значением одинарную кавычку. После значения нам нужно поставить одинарную кавычку и закрыть строку, поэтому мы ставим три одинарных кавычки (две для того, чтобы поставить кавычку для значения и одна для конца строки).
Если вы не хотите мучаться с такими кавычками, то можете пользоваться функцией Quotedstr. Ей нужно передать строку, а она вернет вам ее в кавычках. Таким образом, ваш код может упроститься до следующего:
AdoTablel.Filter: = * Фамилия='+ QuotedStr(Сидоров);
Это был пример простейшего условия на равенство. Вы можете использовать любые другие операторы сравнения (больше или меньше). Можно даже создавать составные операторы сравнения, в которых сравниваются сразу два или более значений. Например:
AdoTablel. Filter: = * Фамилия^1 1 Сидоров *' or Телефон^* * 3326523 * 1 * ;
В этом примере происходит поиск всех записей, в которых поле Фамилия равно значению Сидоров и поле телефон имеет значение 3326523. Для объединения двух условий используется оператор or. Можно также использовать оператор логического типа — and.
(Совет 丿
При программировании фильтров будьте внимателены к кавычкам. Помните, что значения должны выделяться одинарными кавычками и внутри строки для этого приходится ставить две одинарных кавычки, а не одну двойную.
Внимание
Помните, что если имя поля состоит из двух слов, то его нужно заключать между квадратными скобками [ и ]. Допустим, что у нас поле город состоит из двух слов — Название города в этом случае фильтр по этому полю будет выглядеть так: AdoTablel. Filter: =• [Название города] = ' Москва* 1 *;.
Теперь переходим к программированию. Откройте пример из предыдущего раздела. Сейчас будем добавлять к нему возможность поиска.
Для начала улучшим нашу форму. Для этого добавьте панель, на которой будет располагаться текст "Найти” и строка ввода TEdit с именем FindEdit (рис. 14.23). Теперь создайте обработчик события OnChange для строки ввода. Когда пользователь изменил текст в строке ввода, мы должны изменить и фильтр. Напишите в этом обработчике следующий код: procedure TForml.FindEditChange(Sender: TObject); begin
if Length(FindEdit.Text)>0 then
DataModulel.BookTable.Filtered:=true
IS回副IS回副Файл Редактирование Справочники Сортировка ПомощьФайл Редактирование Справочники Сортировка Помощь飞圈直:? 审1飞圈直:? 审1中 аммлия I Имя[Тез^он |е-гп%]「орсд45Ростов-на-Дону06Питер4545Москва02Ростов-на-Дону中 аммлия I Имя[Тез^он |е-гп%]「орсд45Ростов-на-Дону06Питер4545Москва02Ростов-на-Дону> Иванов_ ПетровСидоров> Иванов_ ПетровСидоровj Дата рожд.1 2 Декабр30 Январь22 Март 2Сj Дата рожд.1 2 Декабр30 Январь22 Март 2С*叩 НОЕ ^^,*叩 НОЕ ^^,Андр 蜀Андр 蜀<1 I<1 I
IS回副
IS回副
Файл Редактирование Справочники Сортировка Помощь
Файл Редактирование Справочники Сортировка Помощь
飞圈直:? 审1
飞圈直:? 审1
中 аммлия I Имя[Тез^он |е-гп%]「орсд
45Ростов-на-Дону
06Питер
4545Москва
02Ростов-на-Дону
中 аммлия I Имя[Тез^он |е-гп%]「орсд
45Ростов-на-Дону
06Питер
4545Москва
02Ростов-на-Дону
> Иванов
_ ПетровСидоров
> Иванов
_ ПетровСидоров
j Дата рожд.
1 2 Декабр
30 Январь
22 Март 2С
j Дата рожд.
1 2 Декабр
30 Январь
22 Март 2С
*叩 НОЕ ^^,
*叩 НОЕ ^^,
Андр 蜀
Андр 蜀
<1 I
<1 I
else
DataModulel.BookTable.Filtered:^false;
DataModulel. BookTable. Filter: = Фамилия> * ' 1 +FindEdit .Text+ * end;
Рис. 14.23. Улучшенная форма нашей программы
Сначала мы проверяем строку. Если в строке поиска что-то есть, то включаем фильтр, иначе его можно отключить, чтобы показать всю таблицу. После этого создается условие фильтра: ,Фамилия>, * ,+FindEdit.Text+*11 *. Здесь используется знак "болыпе", чтобы отображать все записи, похожие на введенный текст.
Внимание
Если установить знак равенства и пользователь введет букву "с", то в таблице ничего отображаться не будет, потому что нет такой фамилии "с". А при знаке "больше11 будут отображаться все фамилии, начинающиеся на букву "с".
К Примечание
Знак "больше" в базах данных Access работает не очень хорошо. MS Access хорошо и быстро работает только со знаком равенства (жесткий поиск). Там, где поиск должен быть не жестким, мы будем использовать запросы SQL, с которыми познакомимся немного позже.
В конце фильтра добавляются четыре одинарных кавычки. Почему четыре? Да потому, что после значения нам надо добавить одну кавычку. Чтобы сделать это, надо добавить строку, содержащую кавычку. Поэтому у нас стоит две одинарных кавычки, чтобы открыть и закрыть эту строку. Внутри строки мы ставим эту кавычку, а, как вы помните, для этого надо ставить две кавычки и в результате получится одна. Вот так и получается четыре одинарных кавычки.
Как уже говорилось, фильтры в ADO работают плохо. Но Delphi предоставляет нам очень хорошую возможность расширить возможности фильтрации и сделать их действительно уникальными и мощными.
Перейдите в модуль данных (DataModuleUnit) и создайте для компонента BookTable обработчик события OnFilterRecord. Он вызывается при включенной фильтрации и каждый раз, когда программе надо выяснить, соответствует ли строка фильтру. Здесь мы можем самостоятельно управлять логикой фильтрации. Напишите в созданном обработчике следующий код:
procedure TDataModulel.BookTableFilterRecord(DataSet: TDataSet;
var Aucep匸:Boolean);
begin
Accept:=false;
if copy(BookTableDSDesigner.AsString,1r Length(Forml.FindEdit.Text))= Forml.FindEdit.Text then
Accept:=true;
end;
В качестве второго параметра в обработчик передается булева переменная Accept. Если мы в нее занесем значение false, то текущая строка не соответствует фильтру. Если true, то строка соответствует и ее можно отображать.
В самом начале мы даем переменной Accept отрицательное значение. После этого происходит проверка соответствия текущей строки фильтру. Для этого нужно понять, что происходит во второй строке. Рассмотрим ее по частям:
.copy (BookTableDSDesigner.AsStringf 1, Length (Forml. FindEdit. Text))
Здесь BookTableDSDesigner — имя поля с фамилией. Если дважды щелкнуть по компоненту BookTabie и в появившемся редакторе полей посмотреть свойство Name поля Фамилия, то вы должны увидеть именно это имя. Если нет, то вы должны подкорректировать код.
Итак, функция сору возвращает из строки, указанной в качестве первого параметра, символы, начиная с позиции, указанной в качестве второго параметра, и количество возвращаемых символов равно третьему параметру. У нас возвращаются символы фамилии, начиная с первого, и столько же символов, сколько и в строке ввода для фильтра на главной форме.
Результат мы сравниваем с содержимым строки ввода для фильтра. Если фамилия в,текущей строке базы данных начинается с текста, указанного в качестве фильтра, то эта строка будет отображаться.
Запустите пример и убедитесь, что он работает корректно. Вот теперь наш фильтр стабилен и отлично работает. Если изначально ADO плохо работает с этим механизмом, то теперь проблем нет. Таким образом, вы можете сделать фильтрацию любой сложности. Единственный недостаток — медленная скорость обработки фильтров. Запросы SQL с большими данными работают быстрее.
С Совет )
Если у вас слишком большая база, то желательно использовать SQL-запросы.
Вот так мы сделали простую возможность поиска. Более эффективные возможности можно получить, используя язык SQL —это язык запросов к базам данных. Но это отдельная тема и она требует отдельного рассмотрения.
К Примечание ]
На компакт-диске в папке \Примеры\ Глава 14\ Filter вы можете увидеть пример этой программы.
14-9- Язык запросов SQL
SQL переводят на русский как Структурированный Язык Запросов. С помощью SQL-запросов можно создавать и работать с реляционными базами данных. Этот язык стал стандартом, поэтому если вы хотите работать с базами данных, то вы должны хорошо знать этот язык.
SQL определяется Американским национальным институтом стандартов и Международной организацией по стандартизации (ISO). Несмотря на это, некоторые производители баз данных вносят изменения и дополнения в этот язык. Изменения эти, как правило, незначительны и основа остается совместимой со стандартом.
Вспомним, что такое реляционная база данных (РБД). Это таблица, в которой в качестве столбцов выступают поля данных, а каждая строка хранит данные. В каждой таблице должно быть одно уникальное поле, которое однозначно будет идентифицировать строку. Это поле называется ключевым. Эти поля очень часто используются для связывания таблиц или для обеспечения уникальности каждой записи. Но даже если таблица не связана, ключевое поле все равно обязательно.
Столбцы в РБД также должны быть уникальными, но в этом случае не обязательно числовыми. Их можно называть как угодно, лишь бы было уникально и вам понятно, а остальное никого не интересует.
SQL может быть двух типов: интерактивный и вложенный. Первый — это отдельный язык, он сам выполняет запросы и сразу показывает результат работы. Второй — это когда SQL-язык вложен в другой, как, например, в C++ или Delphi.
Интерактивный SQL более близок к стандартному, а во вложенном очень часто встречаются отклонения и дополнения. Например, в стандартном SQL различаются только два типа данных: строки и числа* но некоторые производители добавляют СВОИ ТИПЫ (Date, Time, Binary И Т. Д.). Числа В SQL делятся на два типа:
целые — integer (int);
дробные — decimal (dec).
Строки ограничены размером в 254 символа.
^^Примечание
Более подробную информацию о SQL вы найдете в докумеьггации — "Язык запросов SQL.pdf* на диске к книге в папке \ Документация. В этом документе приведено достаточно подробное описание языка SQL, поэтому прежде чем двигаться дальше, изучите этот докумеьгт. Особенно, если вы хотите в дальнейшем профессионально писать программы для работы с базами данных.
Давайте посмотрим, как можно направить базе данных простейший SQL- запрос. В качестве примера реализуем возможность поиска записей по номеру телефона в нашем телефонном справочнике.
Для отправки запросов базе данных используется компонент TADOQuery с вкладки ADO палитры компонентов. Работа этого компонента схожа с таблицей TADOTabie и у них много схожих полей. В TADOQuery вы также должны выбирать строку подключения (свойство connectionstring) или связываться с компонентом TADOConnection через свойство Connection. У запросов даже есть возможность установки фильтра (свойства Filtered и Filter). Но мы его рассматривать не будем, потому что работа с этим фильтром ничем не отличается от фильтра таблиц TADOTabie, который мы рассматривали (см. разд. 14.8).
Давайте откроем наш телефонный справочник и дополним его новыми возможностями.
Откройте модуль данных DataModule, где у нас расположены все компоненты доступа к базе данных. Добавьте сюда компонент TADOQuery (назовем его FindQuery) и компонент TDataSource (назовем его FindSource). Теперь надо
связать эти компоненты, указав у компонента FindSource в свойстве DataSet компонент FindQuery.
工 Примечание
TDataSource отвечает за отображение данных из таблиц. Компонеьгг TADOQuery предназначен для отправки SQL-запросов базе данных. Результат запросов возвращается в виде таблиц и для отображения результата нам будет необходим ком поненгт TDataSou гее. Именно поэтому мы их установил и на форму и связали между собой, чтобы компонеьгг отображения видел данные, которые надо отображать.
Теперь выделите компонент FindQuery. Здесь необходимо указать в свойстве connection наш компонент подключения к базе данных ADOConnectionl. Этим мы укажем компоненту, какой базе будут отправляться запросы.
Теперь напишем сам запрос. Для этого дважды щелкните кнопкой мыши по свойству SQL и перед вами отвфоется окно редактора запросов (рис. 14.24).
Рис. 14.24. Редактор SQL-запросов
В этом редакторе написан простейший запрос — выбор всех строк и всех столбцов из таблицы Справочник базы данных, к которой мы подключились:
SELECT *
FROM Справочник
Мы пока только написали запрос, но еще не выполнили его. Для его выполнения нужно установить свойство Active в true. Сделайте это.
Теперь перейдем в главный модуль, где у нас хранится основное окно. Выделите сетку DBGridl, которая у нас отображает данные из таблицы BookTable. Перейдите в объектный инспектор и измените свойство DataSource на DataModulel. FindSource, чтобы увидеть таблицу результата запроса.
Посмотрите на результирующую таблицу, она похожа на то, что мы видели при работе с компонентом TADOTable, только пока что вы видите все поля (даже те, которые мы уже скрыли из виду от пользователя в компоненте BookTabie) и отображение полей не настроено. Но все это можно исправить, если дважды щелкнуть по компоненту FindQuery. Перед вами отмоется то же самое окно настройки свойств полей. Сейчас мы сделаем это, но сначала снова выделите сетку DBGridi в главном окне. Перейдите в объектный инспектор И измените СВОЙСТВО DataSource на DataModulel. DataSourcel, чтобы все вернуть.
А вот теперь переходите в модуль данных DataModule. Дважды щелкните кнопкой мыши по компоненту FindQuery. В появившемся окне щелкните правой кнопкой мыши и выберите пункт меню Add all fields. В редакторе должны отобразиться все поля нашей таблицы. Теперь дважды щелкните кнопкой мыши по компоненту BookTabie, чтобы отобразить окно свойств нашей уже настроенной таблицы. Расположите оба окна рядом друг с другом, чтобы вы могли всегда их видеть. Теперь выделяйте первое свойство в редакторе свойств таблицы BookTabie, запоминайте его, переходите в редактор свойств полей запроса и делайте там те же настройки.
Когда перенесете все свойства (не забудьте создать поисковое поле для поля город), можете снова посмотреть на результат, используя сетку главного окна. Только опять верните все назад. Теперь результат вообще не должен отличаться.
Давайте реализуек”непосредственно поиск с помощью нашего SQL-запроса. Для этого создадим новую форму, в которой будет отображаться результат, и назовем ее FindResultForm. Измените у нее следующие свойства:
Caption — измените на Результат поиска;
Position — установите в poMainFormCenter, чтобы наше окно отображалось по центру главного окна.
Чтобы окно видело таблицы, к нему надо подключить модуль данных — DataModuleUnit. Для этого используйте уже знакомое меню Rie | Use Unit. Теперь бросьте на форму сетку DBGrid с вкладки Data Controls палитры компонентов и растяните ее по всей форме. Перейдите в объектный инспектор И измените СВОЙСТВО DataSource на DataModulel. FindSource, чтобы увидеть в сетке результат запроса.
Все, можно сохранять форму под именем FindResultUnit.
Теперь перейдите в главную форму и на панели поиска добавьте надпись и строку ввода для поиска по телефону. Самой последней добавьте кнопку, по нажатии которой будет запускаться поиск. На рис. 14.25 вы можете видеть улучшенное главное окно нашей программы.
Рис. 14.25. Улучшенная панель поиска
Для события onclick кнопки Найти пишем следующий код: procedure TMainForm.FindButtonClick(Sender: TObject); begin
DataModulel.FindQuery.Active:=false;
DataModulel.FindQuery.SQL.Clear;
DataModulel.FindQuery.SQL.Add('SELECT * 1);
DataModulel.FindQuery.SQL.Add(1 FROM Справочник1);
DataModulel.FindQuery.SQL.Add('WHERE телефон LIKE
'* +FindTelephoneEdit.Text+ * * *1);
DataModulel. FindQuery ・ Active: =true ;
FindResult Form. ShowModal;
end;
В первой строке кода мы делаем компонент запроса неаьсгивным. После этого надо заполнить свойство SQL запросом на поиск данных. Это свойство имеет уже знакомый тип TStrings, который мы использовали при работе с элементами списка TListBox, TComboBox и др. Но прежде чем заполнять свойство новыми значениями, нужно очистить его от старого запроса, который мог остаться после последнего вызова (если пользователь уже нажимал
кнопку Найти, то там будет старый запрос, который при следующем нажатии надо очистить). Для очистки вызываем метод clear.
Далее идет заполнение свойства SQL текстом запроса. Нам нужно внести следующий запрос: SELECT *
FROM Справочник
WHERE Телефон LIKE • 1 r+FindTelephoneEdit .Text+* '''
Здесь написано примерно следующее — выбрать все поля из таблицы Справочник, где поле телефон равно указанному в компоненте FindTelephoneEdit тексту.
Внимание
Обратите внимание, что параметр, с которым происходит сравнение (текст из FindTelephoneEdit), должен быть заключен в кавычки. Поэтому, как и при работе с фильтром, нам приходится использовать множество одинарных кавычек.
Как только текст запроса занесен в свойство SQL, его можно выполнять. Для этого делаем компонент активным. Ну, и чтобы отобразить результат, Показывается ОКНО FindResultForm.
Для выполнения запроса чаще всего достаточно сделать компонент ADO Query активным. Это прекрасно работает, если вы запрашиваете данные из таблицы базы данных. Но если в запросе удаляются строки или изменяется структура таблицы (в запросе есть такие операторы, как INSERT; UPDATE, delete или create table), необходимо вызывать метод ExecSQL компонента ADOQuery.
工 Примечание ;
На компакт-диске в папке \ Примеры \ Глава 14 \ SQL1 вы можете увидеть пример этой программы.
В принципе, пример готов, и на этом можно было бы остановиться. Однако рассмотрим еше один вариант использования динамических запросов. Что понимается под словом динамический! Если мы просто вписали запрос в свойство SQL компонента ADOQuery и в течение всей программы его не изменяем, то такой запрос можно назвать статическим. Но если в течение выполнения программы надо изменять текст запроса, то его можно назвать динамическим.
При поиске записей нам приходится изменять запрос, потому что каждый раз используется разный параметр, который необходимо найти. Но зачем же каждый раз изменять весь запрос, когда он не меняется? Не легче ли внести
в запрос переменную и изменять только ее? Легче, поэтому давайте подкорректируем наш пример.
Выделите компонент FindQuery и дважды щелкните по свойству SQL. В редакторе запроса введите следующий запрос:
SELECT *
FROM Сггра вочник/
WHERE Телефон LIKE : Telephone
В принципе, здесь написан практически тот же запрос, что мы уже использовали. Единственная разница — вместо параметра FindTelephoneEdit.Text стоит : Telephone. Что это такое? Это переменная, о чем говорит двоеточие в начале имени. Переменная в SQL-запросе оформляется как:
:ИмяПеременной
Закройте окно редактора запроса и дважды щелкните по свойству Parameters. Перед вами откроется окно редактора параметров (рис. 14.26). Как видите, описанный в запросе параметр автоматически попадает сюда. Выделите параметр Telephone и посмотрите на его свойства в объектном инспекторе.
Рис. 14.26. Редактор параметров
В свойстве DataType вы должны указать тип переменной. В нашем случае буцет строка с номером телефона, поэтому выберите из списка тип ftstring. В свойстве Value вы можете указать значение по умолчанию. Попробуйте указать там любой номер телефона из вашей базы и сделать компонент FindQuery активным. Теперь откройте окно, которое должно отображать результат поиска, и посмотрите на сетку, в которой уже отображаться результат поиска.
Ну, и, наконец, изменим обработчик события кнопки Найти. Там мы полностью формировали запрос, но теперь этого не надо делать. Достаточно
16 Зак 942
только изменить значение параметра. Для этого удалите старый код обработчика и напишите следующий:
DataModulel.FindQuery.Active:=false;'
DataModulel.FindQuery.Parameters•ParawByName(1Telephone1)•Value:=
FindTelephoneEdit.Text;
DataModule1.FindQuery.Active:=true;
FindResul t Form. ShowModa 1;
В самом начале мы также делаем компонент запроса неактивным. После этого надо изменить значение параметра, для чего используем конструкцию DataModulel.FindQuery.Parameters.ParamByName(*Telephone1).Value.
Сложно? Зато удобно.
Все параметры хранятся в свойстве Parameters компонента запроса. В этом свойстве, чтобы найти нужный параметр, используем метод ParamByName. В качестве единственного параметра этому методу нужно передать имя нашего параметра. В свойстве value мы записываем значение для найденного параметра.
После этого запрос можно делать активным, чтобы он выполнился, и мы увидели результат его работы. Такая работа с динамическими запросами намного легче, особенно, если запросы большие, а изменяется только какое-то значение. Проще использовать переменную. Это будет лучше и для программы, и для базы данных. Серверы баз данных перед выполнением нового запроса производят его компиляцию, что отнимает время. Если в запросе изменилось хотя бы одно число, будет происходить компиляция. При использовании переменных запрос не изменяется, потому что изменилось только значение переменной, и лишних затрат на перекомпиляцию не будет.
工 Примечание
На компакт-диске в папке \ Примеры \ Глава 14\ SQL1 вы можете увидеть пример этой программы.
На этом можно закончить разговор про запросы. Осталось лишь добавить пару замечаний. Как уже говорилось, компонент ADOQuery очень похож на ADOTable. В них очень много общего. Поэтому часто используют в качестве основного доступа к данным именно ADOQuery. В нашем случае можно было поступить так же, потому что основное предназначение телефонного справочника —поиск необходимой информации. А вот дополнительные справочники (как, например, справочник городов) можно реализовывать в виде таблицы ADOTable.
Компонент ADOQue г у имеет все необходимые методы, нужные для полноценной работы С базой данных, такие как Insert, Delete, Edit, Post, First, Next, Prev, т и т. 1, которые мы рассматривали для компонента ADOTable.
Связанные таблицы
Вы наверно можете уже сказать, что у нас получился полноценный телефонный справочник. В него уже можно не только заносить данные, редактировать, удалять, но и искать по разным параметрам. Мы получили достаточно информации, чтобы можно было написать уже сравнительно сложную программу для работы с базой данных. Но пока еще не все так просто.
Теперь представьте себе ситуацию, когда у одного человека есть два телефона. Один из телефонов может быть простым, а другой может быть сотовым. Как внести такую информацию в нашу базу? Нужно внести две записи, в которых ПОЛЯ Фамилия, Имя, Город, e-mail И Дата рождения будут одинаковыми. Но это же неудобно и сразу заметно, что идет лишний расход места на диске для хранения двух практически одинаковых строк.
Такая ситуация нарушает рациональность хранения информации. Мы уже знаем, что строки таблицы должны хранить как можно меньше одинаковой информации. Именно поэтому мы убрали поле город из таблицы в отдельный справочник, чтобы сэкономить место на диске и увеличить эффективность нашей базы данных. Но как поступить в данном случае, когда фамилию, имя и другие поля убирать в справочник нет смысла, да и телефоны держать в справочнике нерационально? Очень просто. В этом случае нам помогут связанные таблицы.
В одной таблице надо хранить следующие данные полей: Фамилия, имя, город, e-mail И Дата. В другой таблице будут: Телефон И Мобильник. Обе таблицы будут связаны между собой, как на рис. 14.27.
ТелефоныКеу1■• LinkKeyТелефонМобильникТелефоныКеу1■• LinkKeyТелефонМобильникСправочник
Телефоны
Кеу1
■• LinkKeyТелефонМобильник
Телефоны
Кеу1
■• LinkKeyТелефонМобильник
Кеу1
Фамилия Имя e-mail Город Дата
Рис. 14.27. Схема данных
Здесь показаны две таблицы. В таблице Справочник вы видите все описанные поля общих сведений о владельце телефона. Во второй таблице Телефоны у нас четыре ПОЛЯ: Счетчик, LinkKey, Телефон И Мобильник. Поля Телефон (строковое цоле, хранящее реальный номер телефона) и ' (логическое поле) выполняют ту же роль, что и раньше (одноименные по im в основной таблице Справочник). В основной таблице теперь этих полей нет
Что же такое LinkKey и почему между ним и полем Keyl таблицы Справочник проведена линия связи? Keyl — уникальное поле, по которому мы точно можем его найти. LinkKeyl — связующее поле и будет хранить ссылки на поле Keyl. Например, взгляните на таблицы, показанные на рие. 14.28.
Рис. 14.28. Схема данных
В верхней части рисунка показана таблица Справочник, а внизу таблица Телефоны. Если посмотреть на данные, то поле Keyl постоянно увеличивается на единицу, потому что это счетчик. Теперь давайте посмотрим на этом примере, как происходит связь через поля Keyl таблицы Справочник и LinkKey таблицы Телефоны.
В верхней таблице у нас первая запись принадлежит Иванову Сергею. Во второй таблице ищем записи, у которых в поле LinkKey стоит цифра 1. Такая запись только одна и она первая.
Вторая запись в верхней таблице принадлежит Петрову Ивану. Смотрим в нижней таблице записи, у которых в поле LinkKey находится число 2. Таких записей две (2 и 3), значит, у Петрова есть два телефона.
Третья запись в верхней таблице принадлежит Иванову Алексею. Ищем в правой таблице записи с цифрой 3 в поле LinkKey. Таких записей опять 2, значит и у Алексея тоже есть два телефона.
Таким образом, мы связываем две таблицы с помощью ключей. Когда мы создавали поисковые поля, у нас получалась связь, чем-то похожая на эту,
только тогда главная таблица содержала поле город, которое связывалось под главный ключ справочника городов.
На словах все рассмотрено, пора сделать и реальную программу. Переходим к нашему примеру. Для начала нужно открыть базу данных в Access и отредактировать поля таблицы Спдравочник, а именно убрать поля телефон и мобильник.
Теперь создайте новую таблицу в режиме конструьсгора со следующими полями:
О Keyl — счетчик, ключевое поле;
О LinkKey — числовое поле, В свойстве Индексированное поле укажите Да (допускаются совпадения);
□ телефон — текстовое, размер 10;
П мобильник — логическое.
Сохраните таблицу под именем Телефоны.
Теперь запускаем Delphi. Откройте модуль с компонентами для доступа к данным и дважды щелкните по компоненту BookTable. В редакторе полей выделите поле телефон. Удалите его при помощи клавиши <Del>. Потом удалите поле мобильник, потому что мы удалили эти поля из таблицы. Удалите также эти же поля из компонента FindQuery. Еще здесь надо удалить поле Keyl (это потому, что запрос на поиск будет проходить сразу на две таблицы, и в обоих есть поле с именем Keyl). Потом вы увидите, почему мы удалили ключевое поле. А пока помните, что поиск у нас не работает, потому что SQL-запрос сейчас неправильный. Чуть позже мы исправим его.
Теперь добавьте сюда компоненты Datasource (назовем его Тelephonsource) и ADOTable (назовем его ТelephonTable) для доступа к таблице Телефоны. Наведите СВОЙСТВО DataSource компонента Telephonsource на Те 1 ephonTablе.
Теперь установите следующие свойства компонента ТelephonTable:
О connection — укажите здесь наш компонент присоединения к базе данных ADOConnectionl ;
П TableName — здесь укажите имя таблицы Телефоны;
П Active — установите в true, чтобы открыть таблицу;
П MasterSource — выберите здесь из выпадающего списка Booksource (этим вы указываете главную таблицу для таблицы Телефоны);
П MasterFields — здесь мы должны указать связующие поля.
Для указания связующего поля щелкните по нему дважды и перед вами откроется окно, показанное на рис. 14.29. В списке Detail Fields (поля подчиненной базы) выберите поле LinkKey, а в списке Master Fields (поля главной базы) выберите поле Keyl. Нажмите кнопку Add и в списке Joined Fields (Связанные поля) появиться строка, отображающая выделенную связь. Закройте окно кнопкой ОК, чтобы сохранить указанную связь.
Посмотрите теперь в объектном инспекторе на свойство IndexFieIdNames. Как видите, там появилось имя поля LinkKey — поле, через которое происходит связь. В связанных таблицах нельзя использовать это поле для обеспечения сортировки, иначе нарушится связь.
Рис. 14.29. Окно создания связей между главной и подчиненной таблицей
Теперь дважды щелкните по компоненту TelephonTable, чтобы увидеть окно редактирования свойств полей. Здесь вам надо добавить поля, чтобы вы могли обращаться к ним потом по именам, и спрятать первые два цоля (ьшючевые поля, которые не несут пользователю полезной информации).
Наконец можно переходить к црограммированию или почти к программированию. Откройте главную форму и добавьте сюда еще одну сетку DBGrid. В примере она расположена по правому краю окна, как показано на рис. 14.30. У сетки нужно изменить только свойство DataSource, указав там нашу таблицу телефонов DataModulel. TelephonSource. Может вы уже заметили —еще устанавливается свойство Borderstyle в bsNone. Так красивее смотрится таблица.
В принципе, пример готов к запуску. Вы можете выбирать в левой сетке любого человека, а в правой сетке можно вносить любое количество телефонов для выбранной записи. Для вставки новой записи можно использовать клавишу <Ins>, для удаления — сочитание клавиш <Ctrl>+<Del>. Чтобы сохранить изменения, нужно курсором перейти на другую запись. Отменяет редактирование записи (если изменения еще не приняты) — клавиша <Esc>.
Рис. 14.30. Улучшенная главная форма программы
Вообще на счет сохранения изменений можно сказать следующее. Пользователи очень часто забывают или даже не знают о том, что для запоминания введенных изменений надо перейти на другую запись. Именно поэтому по событию OnClose для главной формы можно писать следующий код для всех таблиц программы: if Table1.Modified then
TableL. Post;
Здесь проверяется, если во время закрытия программы таблица Tablel изменена, то запоминаются изменения.
Еще один способ следить за изменениями — устанавливать в сетках DBGrid в свойстве options параметр dgCancelOnExit в false. А лучше использовать сразу оба этих способа.
Теперь давайте скорректируем наш запрос для поиска по номеру телефону. Откройте модуль данных и введите в компонент FindQuery следующий запрос:
SELECT *.
FROM Справочник, Телефоны
WHERE Телефон LIKE : Telephone
AND Справочник.Key1=Телефоны.LinkKey
Здесь мы уже выбираем все поля из двух таблиц, где есть записи с указанным значением телефона. К тому же здесь указана связь между таблицами.
Если вы прочитали описание по SQL, который прилагается на диске, то с пониманием этого запроса проблем не будет.
Закройте редактор SQL-запроса и дважды щелкните по компоненту FindQuery. В редакторе свойств полей щелкните правой кнопкой мыши и выберите пункт Add All Fields. Таким образом в редактор добавляются все новые поля, которых в нем не было (это поля из таблицы Телефоны и ключевое поле таблицы Справочник). Обратите внимание, что теперь нет поля Keyl. У нас две таблицы и в обеих есть поле Keyl, поэтому для их различия названия полей изменены на следующие:
Имя Таблицы . Имя поля
Спрячьте ключевые поля, потому что пользователь не должен их видеть и отсортируйте все так, чтобы удобно было работать. Попробуйте запустить программу и посмотреть на результат ее работы. Несмотря на то, что данные о телефонах хранятся в трех таблицах, мы их получаем от SQL-запроса в виде одной.
Обратите внимание, что в окне результата поиска можно редактировать данные, и они будут правильно отображены в своих таблицах. Попробуйте изменить какое-нибудь поле. Когда закроете окно результата поиска, вы ничего не увидите, потому что в сетке главной формы данные нужно обно.- вить методом Refresh таблиц. Таким образом, чтобы увидеть изменения, нужно закрыть программу и открыть ее снова.
Для решения этой проблемы нужно подкорректировать обработчик события на нажатие кнопки Найти. Допишите в конце (после показа окна результата) следующий код:
Da taModul el .Boo kTable. Refresh;
DataModulel.TelephonTable.Refresh;
Кстати, в нашей программе можно смело удалять сортировку, потому что использовать ее в том виде, в котором мы ее написали, нельзя. Мы сортировали с помощью индекса, а в связанных таблицах индекс выполняет роль связей. Если изменить значение индексного поля, то нарушится связь между таблицами.
Но можно воспользоваться свойством Sort таблицы. Единственное, что мы не сможем, это сортировать цо телефону, потому что он находится в другой таблице. Но можем упорядочить записи цо любому полю главной таблицы. Для пункта меню Сортировка | По фамилии напишите следующий код:
DataModulel. BookTable. Sort: =' Фамилия ASC ;
Пункт меню По телефону можно убирать, а вместо него давайте сделаем сортировку по городу. В этом случае для соответствующего пункта меню напишите следующий код:
DataModulel.BookTable.Sort:='Город ASC*;
Программу можно считать законченной, если бы не одно "но" — нужно скорректировать окно редактирования данных. У нас изменилась главная таблица, значит, и это окно должно измениться. Попробуйте сделать это сами, потому что все необходимые знания у вас уже есть. Если что-нибудь будет непонятно, вы всегда сможете обратиться к исходным программам на диске.
Примечанме
На компакт-диске в папке \ Примеры\ Глава 14\ LinkTables вы можете увидеть пример этой программы.
.Вычисляемые поля.Вычисляемые поля
.Вычисляемые поля
.Вычисляемые поля
Допустим, что у вас есть база данных со следующими полями: наименование, кол-во, цена. А почему “допустим”? Давайте создадим новую базу данных, в которой будет таблица с такими полями (рис. 14.31). Сохраните эту таблицу под именем Товары. В ней мы будем хранить наименование покупки, количество и пену.
Рис. 14.31. Таблица Товары новой базы данныхРис. 14.31. Таблица Товары новой базы данных
Рис. 14.31. Таблица Товары новой базы данных
Рис. 14.31. Таблица Товары новой базы данных
Цена в таблице будет указываться за 1 товар, т. е. за штуку, килограмм или метр. Чтобы узнать общую стоимость товара, надо цену за единицу умножить на кол-во товара. При этом итог должен моментально реагировать на любые изменения колонок кол-во и цена. На первый взгляд задача достаточно сложная, но в программировании она проста, как никогда.
Создайте новый проект в Delphi и сразу добавьте в него модуль DataModule. В этот модуль поместите компоненты ADOConnection (для соединения с базой данных), Datasource (для возможности отображения данных из таблицы) и ADOTable (для соединения с таблицей). На рис. 14.32 показано окно DataModuie. Подключитесь к новой базе данных с помошью компонента ADO Connection!.
У DataSourcel в свойстве DataSet укажите таблицу ADOTablel. В таблице ADOTablel В свойстве Connection укажите компонент ADO Connection!, а В свойстве TableName нужно указать таблицу Товары. После этого можно делать таблицу активной (в свойстве Active нужно указать true).
Рис. 14.32. Окно DataModuie
Теперь щелкните дважды по компоненту ADOTablel и в появившемся окне редактора свойств добавьте все поля таблицы. Для начала здесь надо сделать невидимым ключевое поле. Потом нужно установить значения по умолчанию для полей Кол-во и цена. Эти поля будут участвовать в математических расчетах, поэтому в них обязательно должны быть какие -нибудь значения. Если в одном из полей не будет данных, то программа во время расчетов выдаст ошибку. ПуСТЬ ДЛЯ ПОЛЯ Кол-во В СВОЙСТВе DefaultExpression (значение по умолчанию) стоит значение единицы, а для поля цена в том же свойстве — ноль.
Теперь создадим новое поле, которое будет хранить итог расчетов. Но прежде чем это делать, нужно сделать таблицу неактивной. Как вы помните, при создании поисковых полей было предупреждение, что новое поле можно создавать только при неактивной таблице. Щелкните правой кнопкой в ок-
не редактора свойств, и выберите пункт New Field. В окне свойств нового поля заполните следующие поля:
口 Name (имя нового поля) — назовем none、Sum;
口 Туре (тип поля) — у нас будет числовая сумма, поэтому выберите тип Integer;
Z7 Field туре (тип поля) — выбирайте calculated, чтобы создать вычисляемое поле.
Как только поле создано, таблицу снова можно делать активной. Теперь выделите компонент ADOTable 1 и создайте обработчик события OnCalcFields. Это событие вызывается каждый раз, когда надо пересчитать вычисляемые поля. Оно будет вызываться для всех видимых пользователю записей. В этом обработчике напишите следующее:
procedure TDataModulel.ADOTablelCalcFields(DataSet: TDataSet);
begin
ADOTable 1 Stun. Value: =ADOTablelDSDesigner2. As Integer*
AD0TablelDSDesigner3.AsInteger;
end;
Прежде чем разбираться с этим кодом, откройте окно редактора свойств полей таблицы ADOTablel и посмотрите имена (свойство Name) полей Кол-во, Цена И Sum. Это ADOTablelDSDesigner2, ADOTablelDSDesigner3 И ADOTablelSum соответственно. С помощью этих имен мы можем обращаться к значениям, находящимся в полях. Надо только написать имя поля и вызвать один из его методов для преобразования значения в нужный формат. Вам доступны следующие методы полей:
П Aslnteger — получить значение, хранящееся в данном поле в виде числа;
П As Dat eTime — получить значение В виде объекта TdateTime;
П AsBoolean — получить значение в виде булева значения;
П AsCurrency — получить значение в виде цены;
П AsFloat — получить значение в виде вещественного значения;
П Asstring — получить значение в виде строки;
П AsVariant — получить значение в виде типа variant.
Последний — это универсальный тип, который может принимать любые значения, хоть число, хоть строку, в общем, любые доступные типы.
Теперь взгляните на код и сразу же станет все понятно. Здесь мы записываем в свойство value поля ADOTablelSum результат перемножения значений полей цена и кол-во. Значения полей мы получаем как целые числа — Aslnetger.
Теперь переходим в главное окно нашей программы. Подключите к нему модуль DataModule (меню File | Use Uiit) и установите на форму одну сетку DBGrid. Теперь в свойстве DataSource сетки выберите таблицу DataModulel.DataSourcel.
Все. Программа готова.
Запустите ее и попробуйте ввести в базу несколько полей. На рис. 14.33 вы можете увидеть окно результата работы рассмотренного примера.
Кстати, поле итога не должно изменяться пользователем вручную, потому что оно вычисляемое. Именно поэтому вы не сможете туда ввести никакого значения. Delphi просто блокирует любые такие попытки, хотя поле и не имеет признака 'только для чтения1 (Readonly).
Примечание
На компакт-диске в папке \ Примеры \ Глава 14 \ Count вы можете увидеть при
мер этой программы.
Цветные сетки DBGrid
Много раз встречается ситуация, когда возникает потребность в выделении некоторых записей каким-нибудь цветом. Это действительно удобно, да и в программировании не очень сложно. Сейчас рассмотрим, как это делается.
Откройте базу данных из предыдущей главы и добавьте туда поле color. Это поле должно иметь текстовый тип, а размер поля достаточно установить длиной 15 символов.
Теперь откройте Delphi. Загрузите наш пример и в модуле данных дважды щелкните по компоненту ADOTabiel. Выберите пункт меню Add All Fields. В редактор будут загружены новые поля, точнее сказать одно поле — color. Сделайте его сразу же невидимым, потому что пользователю не надо видеть его текст, его интересует цвет строки.
Теперь перейдите в главное окно программы. Добавьте компонент PopupMenu (всплывающее меню). Щелкните по нему дважды, чтобы открыть редактор меню. Создайте в нем следующие пункты:
口 Черный; О Желтый;
口 {фасный; □ Синий;
口 Зеленый; 口 Пурпурный.
Имена пунктов меню должны идти именно в таком порядке, а в свойстве Tag всех этих пунктов должен находиться порядковый номер пункта. Нумеровать цвета надо с нуля и до 5. Это значит, что у пункта меню Желтый в свойстве Tag будет находиться число 3, а у .пункта Пурпурный значение 5.
Теперь выделите все эти пункты меню (щелкните кнопкой мыши на первом пункте и, удерживая клавишу <Shift>, щелкните на последнем). Перейдите на вкладку Events объектного инспектора и создайте обработчик события Onclick. Будет создана процедура-обработчик события, которая назначается всем выделенным пунктам меню, т. е. одна процедура отвечает за нажатие любого из пунктов. В этом обработчике напишите содержимое листинга 14.1.
Листинг 14.1. Обработчик события onclick для пунктов меню
procedure TForml.N13Click (Sender: TCbject);
const
MenuColors: array[0. .5] of TColor = (clBlack, clRed^ clGreen, clYellow, clBlue, clPurple);
begin
/ /Перейти в режим редактирования поля
DataModulel. ADOTabiel. Edit;
/ /Заносим в поле Color выбранный цвет
DataModulel. ADOTabiel Color. Z\s String:=
ColorToString(MenuColors [TMenuItem(Sender) .Tag]);
этому добавьте его в раздел uses. После этого при компиляции ошибок не должно быть.
Теперь рассмотрим параметры, которые мы включили в обработчик. У нашего обработчика события есть следующие параметры:
О sender — объект, который сгенерировал это событие (у нас это будет сетка);
口 Rect — здесь хранятся границы области ячейки, которую надо перерисовать (границы передаются в виде структуры Trect);
П Field — этот параметр имеет тип TField и указывает на поле, которое надо перерисовать;
П state — здесь находятся параметры, указывающие на текущее состояние ячейки, при этом допускаются следующие параметры:
GdSelected — ячейка выделена;
GdFocused — ячейка имеет фокус ввода;
GdFixed — ячейка является фиксированной (такие ячейки используются для названий колонок сверху сетки и для индикации текущей строки слева сетки).
Теперь давайте знакомиться с кодом самой процедуры. В первой строке устанавливается стиль шрифта у холста сетки. Точнее сказать, очищаются все настройки шрифта, потому что стилю присваивается пустой набор []
В следующей строке проверяется, если текущая ячейка выделена или имеет фокус ввода, то цвет кисти изменяем на clHighLight (это константа, хранящая системный цвет, используемый для подсветки). Цвет шрифта устанавливаем в белый, хотя, наверно, лучше будет использовать системный цвет для подсвеченного текста — clHighLightText.
Если ячейка не выделена, то цвет фона (цвет кисти) делаем белым, а цвет шрифта черным. Далее идет проверка, если поле цвета текущей строки не пустое, то цвет шрифта меняем на тот цвет, который указан в поле цвета. Единственное, что надо учитывать, — цвет хранится в виде строки, поэтому преобразовываем его в цвет с помощью функции stringToCoior.
Все приготовления закончены, можно заняться рисованием. Для начала очищаем старое значение ячейки с помощью вызова метода FiiiRect холста сетки, который закрашивает указанную область цветом кисти (фона). В качестве единственного параметра указываем область ячейки в виде структуры TRe'ct, которую мы получили через параметры обработчика.
После закраски рисуем текст ячейки. Если произошла какая-то ошибка во время подготовки к рисованию (она может возникнуть только при преобразовании stringToCoior, если в поле находится неправильное значение), то мы пытаемся снова вывести текст в блоке except... end.
Откройте базу данных из предыдущей главы и добавьте туда поле Color. Это поле должно иметь текстовый тип, а размер поля достаточно установить длиной 15 символов.
Теперь откройте Delphi. Загрузите наш пример и в модуле данных дважды щелкните по компоненту ADOTablel. Выберите пункт меню Add All Fields. В редактор будут загружены новые поля, точнее сказать одно поле — color. Сделайте его сразу же невидимым, потому что пользователю не надо видеть его текст, его интересует цвет строки.
Теперь перейдите в главное окно программы. Добавьте компонент PopupMenu (всплывающее меню). Щелкните по нему дважды, чтобы открыть редактор меню. Создайте в нем следующиепункты:
П Черный;口Желтый;
口 Красный;ПСиний;
П Зеленый;ППурпурный.
Имена пунктов меню должны идти именно в таком порядке, а в свойстве Тag всех этих пунктов должен находиться порядковый номер пункта. Нумеровать цвета надо с нуля и до 5. Это значит, что у пункта меню Желтый в свойстве Tag будет находиться число 35 а у пункта Пуртурный значение 5.
Теперь выделите все эти пункты меню (щелкните кнопкой мыши на первом пункте и, удерживая клавишу <Shift>, щелкните на последнем). Перейдите на вкладку Events объектного инспектора и создайте обработчик события Onclick. Будет создана процедура-обработчик события, которая назначается всем выделенным пунктам меню, т. е. одна процедура отвечает за нажатие любого из пунктов. В этом обработчике напишите содержимое листинга 14.1.
Листинг 14.1 ? Оз^ботчик события onclick для пунктов меню
procedure TForml.N13Click(Sender: TObject);
const
MenuColors: array[0. .5] of TColor =(clBlack, clRed, clGreen, clYellow, clBlue, clPurple); begin
/ /Перейти в режим редактирования поля
DataModulel. ADOTablel. Edit;
//Заносим в поле Color выбранный цвет
DataModulel. ADOTablelColor. As String :=
ColorToString (MenuColors [TMenuItem(Sender) .Tag]);
//Запомнить изменения
DataModulel.ADOTablel. Post;
end;
В разделе const мы объявили одну константу — массив из 6 элементов, имеющих тип TColor. Так как это массив — константа и в процессе программирования не может менять свои значения, эти значения нужно обязательно описать. А именно, сразу же после объявления массива ставиться знак равенства и в скобках перечисляются значения элементов массива. Так как массив из 6 элементов, то и в скобках должно быть именно 6 элементов, ни больше, ни меньше. Мы указали цвета, которые у нас указаны в пунктах всплывающего меню. Их имена перечислены в том же порядке, что и в меню.
В первой строке кода мы переводим таблицу в режим редактирования с помощью вызова метода Edit. Если этого не сделать, то любые попытки изменить данные в полях текущей записи встретят вас шквалом ошибок.
В следующей строке мы присваиваем полю Color значение выбранного цвета. Выбранный цвет получаем следующим образом:
MenuColors[TMenuItem(Sender).Tag].
Рассмотрим эту запись по частям.
П Sender — переменная передается нам в качестве параметра в обработчик события и указывает на объект, который породил данное событие.
П TMenul ten (Sender) — переменная Sender универсальна, поэтому имеет тип Tobject, т. е. родитель всех компонентов. В таком виде мы не можем обратиться к свойствам, которых нет у TObject, но есть у пунктов меню (нас интересует свойство Tag). Поэтому мы показываем компилятору, что данное событие sender имеет тип TMenuitem.
О TMenuitem(Sender) .Tag — получаем значение, указанное в свойстве Tag пункта меню, породившего это событие.
О MenuColors [TMenuitem(Sender). Tag] — Получаем ИЗ массива MenuColors значение цвета, соответствующее значению, указанному в свойстве Tag.
Значение полученного цвета преобразуется в строку с помощью функции ColorToString и записывается в поле color в виде строки.
В самой последней строке мы запоминаем изменения с помощью метода Post. Если вам надо отменить все изменения после последнего входа в режим редактирования, вы должны использовать метод Cancel. Это очень удобно при отображении окон редактирования строк, как мы это уже делали при работе с телефонным справочником.
Код написан, теперь надо выделить сетку DBGrid и в свойстве popupMenu указать созданное нами меню. Сейчас, после всех проделанных действий, мы можем запускать приложение, изменять свойство color в базе данных. Осталось только научить программу читать это свойство и выводить текст в
зависимости от указанного там значения цвета. Для этого создайте обработчик событая OnDrawDataCeii для нашей сетки. Это событие вызывается, когда нужно перерисовать данные какой-нибудь ячейки сетки. В обработчике напишите содержимое листинга 14.2.
Листинг 14.2. Обработчик события OnDrawDataCeii
procedure TForml.DBGridlDrawDataCell(Sender: TObject; consE RecC: TRect;
Field: TField; State: TGridDrawState);
begin
try
DBGridl. Canvas. Font • Style : = [];
if (gdSelected in State) or (gdFocused in State)then
begin
DBGridl.Canvas.Brush.Color:=clHighLight;
DBGridl.Canvas.Font.Color:=clWhite;
end,
else
begin
DBGridl.Canvas.Brush.Color:=clWhite;
DBGridl.Canvas.Font.Color:=clBlack;
//Если поле цвета не пустое, то использовать цвет из поля
if DataModulel - ADOTablelColor. AsStringo1 * then
DBGridl.Canvas.Font.Color:=
StringToColor (DataModulel .ADOTablelColor. As St ring);
end;
/ /Очищаю ячейку
DBGridl.Canvas•FillRect(Rect);
//Вывожу текст ячейки
DBGridl.Canvas.TextOut(Rect.Left, Rect.Top, Field.AsString);
except
DBGridl.Canvas-TextOut(Rect.Left, Rect.Top, Field.AsString);
end; *
end;
Прежде чем анализировать этот код, необходимо учесть следующее замечание. При попытке откомпилировать приложение ничего не получится. Delphi будет "ругаться** на тип TField. Этот тип описан в модуле db, по-
этому добавьте его в раздел uses. После этого при компиляции ошибок не должно быть.
Теперь рассмотрим параметры, которые мы включили в обработчик. У нашего обработчика события есть следующие параметры:
sender — объект, который сгенерировал это событие (у нас это будет сетка);
口 Rect — здесь хранятся границы области ячейки, которую надо перерисовать (границы передаются в виде структуры Trect);
П Field — этот параметр имеет тип TField и указывает на поле, которое надо перерисовать;
П state — здесь находятся параметры, указывающие на текущее состояние ячейки, при этом допускаются следующие параметры:
GdSelected — ячейка выделена;
GdFocused — ячейка имеет фокус ввода;
GdFixed — ячейка является фиксированной (такие ячейки используются для названий колонок сверху сетки и для индикации текущей строки слева сетки).
Теперь давайте знакомиться с кодом самой процедуры. В первой строке устанавливается стиль шрифта у холста сетки. Точнее сказать, очищаются все настройки шрифта, потому что стилю присваивается пустой набор [].
В следующей строке проверяется, если текущая ячейка выделена или имеет фокус ввода, то цвет кисти изменяем на clHighLight (это константа, хранящая системный цвет, используемый для подсветки). Цвет шрифта устанавливаем в белый, хотя, наверно, лучше будет использовать системный цвет для подсвеченного текста — clHighLightText.
Если ячейка не выделена, то цвет фона (цвет кисти) делаем белым, а цвет шрифта черным. Далее идет проверка, если поле цвета текущей строки не пустое, то цвет шрифта меняем на тот цвет, который указан в поле цвета. Единственное, что надо учитывать, — цвет хранится в виде строки, поэтому преобразовываем его в цвет с помощью функции StringToColor.
Все приготовления закончены, можно заняться рисованием. Для начала очищаем старое значение ячейки с помощью вызова метода FillRect холста сетки, который закрашивает указанную область цветом кисти (фона). В качестве единственного параметра указываем область ячейки в виде структуры TReifct, которую мы получили через параметры обработчика.
После закраски рисуем текст ячейки. Если произошла какая-то ошибка во время подготовки к рисованию (она может возникнуть только при преобразовании stringToCoior, если в поле находится неправильное значение), то мы пытаемся снова вывести текст в блоке except... end.
К Примечание 】
На компакт-диске в папке \ Примеры \ Глава 14 \ Color вы можете увидеть пример этой программы.
Подключение к базе данных во время выполнения программы
Использовать заготовленную базу данных очень удобно, но вдруг пользователь захочет выбрать — с какой базой данных ему сейчас работать? Зачем это нужно? Допустим, что у вас программа каждый месяц копирует базу данных в отдельное место и потом очищается, чтобы не содержать устаревших данных. А что, если пользователь захотел посмотреть эти старые данные? Ему надо писать отдельную программу для просмотра или придется пользоваться неудобной программой Access. Но есть выход проще — подключить программу к старой архивной базе данных.
Возьмем пример, написанный в предыдущем разделе главы. Добавьте на форму еще одну кнопку. Для ее события Onclick, связанного с нажатием кнопки, напишите следующий код:
procedure TForml.ButtonlClick(Sender: TObject);
begin
DataModulel.ADOConnect ionl•Close;
if EditConnectionString(DataModulel.ADOConnectionl) then begin
DataModulel.ADOConnection1.Connected:=true;
DataModulel.ADOTablel.Active:=true;
end;
end;
Чтобы этот код откомпилировался, нужно в раздел uses добавить модуль ADOConEd.
Теперь программа откомпилируется и без проблем запустится.
Посмотрим, как работает код после запуска программы. В первой строке выполняется метод close компонента ADOConnection, чтобы закрыть соединение с базой данных. После этого вызывается функция EditConnectionString, отображающая окно подключения к базе данных, которое вы уже видели и можете еще раз увидеть на рис. 14.34. В качестве параметра в эту функцию нужно передать компонент ADOConnection, параметры которого будут изменяться.
Если пользователь выбрал новое имя файла, программа вернет значение true. В результате мы откроем соединение с базой и сделаем единственную таблицу активной. Помните, что после закрытия и открытия базы данных все таблицы становятся неактивными, поэтому вам придется программно восстанавливать их активность.
Рис. 14.34. Результат работы программы
Примечание ;
На компакт-диске в папке \ Примеры \ Глава 14 \ Connect вы можете увццеть пример этой программы.
Некоторым может не понравиться стандартное окно выбора базы данных, потому что оно слишком сложное и оформлено с использованием английского языка. Конечному пользователю эта сложность абсолютно не нужна. Для того чтобы самому написать более простое окно подключения, давайте разберемся со строкой подключения. Минимальн ая строка подключения к базе данных Access выглядит так:
Provider=Microsoft. Jet.OLEDB.4.0;Data Source=sclad.rndb; Persist Security Info=false
Эта строка состоит из трех частей:
О Provider=Microsof t. Jet.oledb.4,0 — имя поставщика данных (Provider), через который будет происходить доступ;
П Data Source=sсlad.mdb — путь к базе данных и имя файла;
口 Persist Security Info^false — сохранять информацию о безопасности.
Если у вас не буцет меняться тип базы данных, что бывает чаще всего, то изменение базы данных можно упростить. Поместите на форму компонент TOpenDialog с вкладки Dialogs и напишите в обработчике события Onclick для кнопки Подключиться следующий код:
procedure TForml.ButtonlClick(Sender: TObject);
begin
if OpenDialog1.Execute then begin
DataModulel.ADOConnectionl.Close;
DataMod-
ulel.ADOConnectionl.Connectionstring:=■Provider^Microsoft.Jet.OLEDB.4.0;* +
'Data Source= * +OpenDialogl. FiIeName+
1; Persist Security Info= false ';
DataMpdulel.ADOConnectionl.Open;
end;
end;
Здесь мы отображаем стандартное окно открытия файла, и если пользователь выбрал файл, то закрываем соединение и формируем строку подключения. После этого снова открываем соединение с базой данных.
Какой вы будете использовать способ — зависит от вас.
(Совет )
Если всегда используется один и тот же тип баз данных, то желательно отображать только стандартное окно открытия файла. Ест тип базы данных меняется, то лучше научить пользователя использовать ваше окно подключения.
При использовании других баз данных, отличных от Access, строка подключения может выглядеть по-другому и параметры будут отличаться, но принцип везде один и тот же.
Расширения ADO
Ранее для доступа к данным использовались компоненты ADO, которые предоставляют разработчику высокоуровневый доступ к таблицам. Этого достаточно для разработки большинства программ работы с базой данных. Но иногда требуется нечто большее, например, создать базу данных и получить доступ к метаданным (описанию таблицы). Это возможно с помощью расширения ADOX (ADO Extension). Таким образом, данное расширение предназначено для решения задач, которые недоступны компонентам ADO.
Только что появилось новое понятие — метаданные. Чтобы двигаться дальше, нужно четко понимать, что это такое. Многие усложняют данное понятие. Постараемся объяснить его смысл как можно проще. В общем случае метаданные — это описание содержимого базы данных, его таблиц, полей, ключей, индексов, хранимых процедур, представлений, работ и т. д. Этими объектами вы можете управлять не только с помощью ADOX, но и с помощью языка DDL (Data Definition Language), который является подмножеством языка SQL или компонента ADO Command. Если вы поймете работу ADOX, то вам уже не понадобится изучать DDL.
С помощью ADOX можно работать с метаданными БД, влиять на структуру данных, а также управлять безопасностью. Все это невозможно делать с помощью доступных в Delphi компонентов. Однако это не значит, что сделать вообще нельзя. Просто эти задачи решаются реже, поэтому Borland не разрабатывала для них компоненты, но вы можете получить доступ напрямую к библиотеке msadox.dll, в которой и находятся все расширения.
Сразу же нужно предупредить, что ADOX работает не со всеми базами данных, а только производства Microsoft (MS Access и MS SQL Server). Если вы используете или планируете использовать в своих проектах что-то другое, то сначала убедитесь, что ADOX поддерживает вашу базу данных. Это можно сделать на сайте Microsoft (www.msdn.microsoft.com).
Чтобы получить доступ к функциям расширения, необходимо подключить библиотеку ADOX к Delphi Для этого выберете меню Project | Import Туре library (Проект | Импортировать библиотеку типов). Перед вами появится окно импортирования (рис. 14.35). Здесь нужно найти пункт Microsoft ADO Ext. 2.Х for DDL and Security (Version 2.X), где 2.X — это номер версии.
К Примечание ]
На момент написания книги в системе использовалась версия 2.8.
Выберете этот пункт и нажмите кнопку Install. Среда разработки Delphi выдаст ошибку, сделав ссылку на то, что объект ТТаЫе уже существует в системе. Чтобы избежать этой ошибки, в поле Class names (здесь находится список всех классов библиотеки) этого окна добавьте какой-нибудь уникальный префикс к каждому имени объекта.
(生веу )
Лучше добавить ADOX, чтобы явно указать принадлежность объектов к библиотеке ADOX.
В результате произойдет преобразование имен объектов к форме:
TADOXTable;
TADOXColumn;
П TADOXIndex;
TADOXKey;
TADOXGroup;
TADOXUser;
П TADOXC a t a1og.
Import Type Library
Import Type Library |
Microsoft ActiveX Data Objects 2.7 Library (Version 2.7)
Microsoft ActiveX Data Objects Recordset 2.7 Library (Version 2.
Microsoft ActiveX Plugin (Version 1.0)
,■l 心2®n 四PQExL 上?[更一口DLdnd—上变n.i"v №名"侬]/,_j,■l 心2®n 四PQExL 上?[更一口DLdnd—上变n.i"v №名"侬]/,_jM icrosoft Add-In Designer (Version 1.Q).
,■l 心2®n 四PQExL 上?[更一口DLdnd—上变n.i"v №名"侬]/,_j
,■l 心2®n 四PQExL 上?[更一口DLdnd—上变n.i"v №名"侬]/,_j
MicrosoftAgent Control 2.0,(Version 2.0)
IMicrosoft Agent Server 2 0 (Version 2-0)
Microsoft Agent Server Extensions 2.0 (Version 2.0)
I EAProgram Fi1es\Common Files\System\ado\msadox dll
Add. Remove I
£lajs names: ТТаЫе
TColurnn二
Tlndex二j
I Palette page:「ctiv以
I Unit dir name: jEAProgram FilesVBorlandVDelphi7\lmports ... I j
I
I Search path: J$(DELPHiK^^...I j
Install... JI 口一.毋;| Cancel」 如圻审 fienerate Component Wrapper
Рис. 14.35-УстановкаАООХ
Снова нажмите кнопку Install, и перед вами должно появиться окно выбора пути, куца будет установлен ADOX (рис. 14.36). Выберите вкладку Into new package и укажите файл в поле со списком File name, или можете оставить все как есть и нажать кнопку ОК. После этого перед вами откроется окно пакета и запрос на перекомпиляцию (см. рис. 14.37). Согласитесь. Если запрос на компиляцию не появился, то самостоятельно нажмите кнопку Install в окне пакета.
После компиляции закройте окно пакета.
Рис. 14.36. Выбор пакета для установки
Рис. 14.37. Окно пакета
При инсталляции запомните имя файла для ADOX (например, ADOX_tlb.pas). Имя файла вы можете увидеть в окне пакета.
Теперь попробуем сделать намеченное, а именно, создать базу данных и таблицу из программы на Delphi без использования Access. Благодаря ADOX это не отнимет много времени, хотя будет немного запутанно, потому что код придется писать в стиле C++.
Создайте новый проект и поместите на форму только одну кнопку. Для события onclick, связанного с нажатием кнопки, напишите код, показанный в листинге 14.3.
Листинг 14.3. Создание базы данных и таблицы
procedure TForml.ButtonlClick(Sender: TObject); var
Table: _Table;
Co luim: _ Co luim;
begin
// Создание базы данных
Catalog :二 CoCatalog.Create;
if FileExists(*c:Xdb.mdb*) then
DeleteFile ( *c:Xdb.mdb*).;
Catalog.Create(* Provider=Microsoft.Jet.OLEDB.4.0;Data
Source^c:\db.mdb1);
Catalog.Set_ActiveConnection('Provider^Microsoft.Jet.OLEDB.4.0;Data Source=c:\db. mdb*);
// Создание таблицы
Table := CoTable.Create;
Table.Name:二 гМои друзья*;
Table.Parentcatalog :二 Catalog;
// Создать ключевое поле
Coluim :二 CoColumn.Create;
with Column do
begin
ParentCatalog :二 Catalog;
Name:=*Keyl';
Type_:=adlnteger;
Properties[1Autoincrement*]-Value :二 true ;
Properties [ * Description1 ] . Value: = • Ключевое поле,;
end;
// Добавить поле к таблице
Table. Columns. Append (Column, 0, 0);
Column := Nil;
// Создаем еще несколько полей
with Table.Columns do
begin
Append(1Фамилия * r adVarWChar, 50), Append ("Имя*, adVarWChar,50);
Append('Телефон*, adVarWChar, 15);
Append('Адрес', adVarWChar, 255);
Append(* Возраст *, adlnteger, 100 储 end;
/ / Добавить таблицу к базе данных
Catalog. Tables. Append (Table);
end;
База данных в данном случае воспринимается как каталог, и переменная для нее будет иметь тип _Catalog. Давайте объявим такую переменную в разделе private:
private
{ Private declarations }
Catalog : Catalog;
Вот теперь программа готова и мы можем разобраться, что туг происходит.
В самом начале происходит инициализация каталога с помощью вызова метода Create объекта CoCatalog:
Catalog:=CoCatalog.Create;
Потом проверяем, если файл db.mdb на диске С: уже существует, то удаляем его, иначе при создании произойдет ошибка.
Следующим этапом будет создание базы данных с помощью метода create. В качестве параметра нужно указать строку подключения. Из этой строки наш каталог получит информацию о версии (Microsoft.Jet.OLEDB.4.0) и о расположении базы данных (c:\db.mdb).
В принципе, база данных готова. Теперь можно создавать таблицу. Для этого нужно проини[диализировать переменную Table с помощью вызова метода Create объекта CoTable. После этого в свойстве Name нужно указать имя таблицы и в свойстве Parentcatalog указать базу данных, которой будет принадлежать таблица. Если этого не сделать, то таблица будет существовать в памяти, но не будет связана с каталогом.
Таблица создана, переходим к ключевому полю. Для его объявления мы инициализируем переменную Column вызовом метода Create объекта CoColwnn. После этого нужно задать следующие свойства у колонки:
Parentcatalog — база данных;
П Name — ИМЯ ПОЛЯ;
Туре_ — тип;
П Properties — свойства.
Для этого выполняется следующий код:
with Column do
begin
Parentcatalog : = Catalog;
Name:=*Keyl';
Type:=adlnteger;
Properties['Autoincrement1].Value :二 true ;
Properties['Description'].Value: = *Ключевое поле'; end;
Из свойств заполняем Auto increment — автоувеличение и необязательное поле Description — описание поля. Последнее делать необязательно.
Теперь добавляем поле к таблице с помощью метода Append свойства columns и обнуляем его:
Table.Columns.Append(Column, 0, 0);
Column := Nil;
Поля таблицы находятся в свойстве Columns. Мы можем добавлять их к таблице с помощью метода Append, который имеет несколько параметров.
Имя поля или объект типа Column. Если это объект, то остальные параметры игнорируются, потому что они описаны в объекте.
Тип поля.
Размер.
Воспользуемся возможностью этого метода сразу создавать поле без определения колонки. Именно так мы создаем остальные поля:
with Table. Columns do
begin
Append(*Фамилия1, adVarWChar, 50);
Append('Имя11 advarWChar,50);
Append('Телефон1, adVarWChar, 15);
Append('Адрес', adVarWChar, 255);
Append(1Возраст1, adlnteger, 100 );
end;
И наконец, добавляем созданную таблицу к базе данных:
Catalog.Tables.Append(Table);
Таким образом, программа может управлять структурой базы данных во время выполнения. На рис. 14.38 вы можете увидеть структуру созданной базы данных.
R Примечание ;
На компакт-диске в пагке \ Примеры \ Глава 14\CreateDatabase вы можете увидеть гример этой программы.
Рис. 14.38. Структура базы данных, созданная в ADOX
Обработка базы данных
Бывают случаи, когда нужно сканировать данные всей таблицы. Для этого нужно перебирать все ее строки. Возьмем пример, который был рассмотрен в разд. 14.13. Там с помощью вычисляемого поля подсчитывали общую сумму товара. А что, если нужно суммировать все строки таблицы, чтобы узнать общую стоимость покупок. В данном случае можно воспользоваться SQL- запросом, но что, если задача более сложная и запрос не подходит? В этом случае придется перебирать все строки и рассчитывать вручную.
Откроем приложение, иллюстрирующее пример ш разд. 14.13 и добавим одну кнопку, по нажатии которой должен будет происходить расчет. Простейший расчет может выглядеть так:
var
Sunun: Integer; begin
Summ:=O;
DataModulel.ADOTablel.First;
while DataModule1.ADOTable1.Eof<>true do
begin
Summ: =Surnm+DataModu le 1. ADOTab le 1 Sum. As Intege r;
DataModulel. ADOTablel. Next;
end;
Application・MessageBox(PChar(1 Результат: 1+lntToStr(Summ)), 'Внимание *, MB_OK);
end;
Здесь определена переменная Summ. В ней будем суммировать значения итоговой колонки. В самом начале процедуры мы обнуляем переменную.
На следующем шаге мы переходим на первую строку таблицы:
DataModulel .ADOTablel - First;
Далее запускается цикл, который будет выполняться, пока переменная Eof не равна true, а она будет равна "истине", только когда мы дойдем до конца таблицы. Если вы захотите запустить обратный цикл — от конца к началу,— то можно перейти на последнюю строку и двигаться в начало, пока переменная Eof не станет равной true. Эго свойство станет равным "истине", когда мы достигнем начала файла.
Внутри цикла прибавляем переменной Summ значение последнего поля. Затем переходим на следующую строку и так до конца таблицы. После цикла выводим сообщение, в котором отображается результат расчета.
Запустите программу, чтобы увидеть результат ее работы. Если у вас достаточно много строк, то вы сможете заметить, как во время расчета курсор движется по таблице. Это не просто некрасиво, а довольно чувствительно затормаживает расчет. После каждого перехода на новую строку программа должна перерисовать курсор и выделение, что отнимает достаточно времени и замедляет расчеты.
Чтобы не было отображения, отключите таблицу от Datasource. Для этого скорректируйте код следующим образом:
var
Summ:Integer;
begin
Summ:=O;
DataModulel.DataSource1.DataSet:=nil;
DataModulel-ADOTablel.First;
while DataModulel. ADOTablel. Eof Otrue do
begin
Summ: =Summ+DataModule 1. TkDOTablel Sum. As Integer;
DataModulel .TkDOTablel. Next;
end;
DataModulel.DataSource1.DataSet:=DataModule1.ADOTable1;
Application-MessageBox(PChar(1 Результат: *+IntToStr(Summ)), ,Внимание *, MBjOK);
end;
В первой строке кода свойству DataSet компонента DataSourcel присваивается нулевое значение. Этот компонент отвечает за отображение нашей таблицы. После отключения связи ничего отображаться не будет, зато скорость расчета возрастет в несколько раз.
После расчета мы возвращаем свойству DataSet его первоначальное значение.
Но и это еще не все. После расчета текущей строкой станет — последняя. Это неудобно. Желательно возвращать первоначальное состояние полностью и выделение должно оставаться на месте. Для этого хорошо подходят вкладки. Мы устанавливаем вкладку на текущую строку, потом производим расчет и после расчета возвращаемся по вкладке на строку, которая была выделена до расчетов. Окончательный код расчета будет выглядеть так:
var
Summ: Integer;
bml: TBookmarkStr;
begin
Summ:=O;*
DataModulel.DataSourcel.DataSet:=nil;
bml:=DataModulel.ADOTable1.Bookmark;
DataModulel.ADOTable1.First;
while DataModulel.ADOTable1.Eof<>true do
begin
Summ: =Suinm+DataModule 1 .ADOTabl el Sum. As Integer;
DataModulel. ADOTable 1. Next;
end;
DataModulel.ADOTablel.Bookmark:=bml;
DataModulel.DataSourcel.DataSet:=DataModulel.ADOTablel;
Application.MessageBox(PChar(1 Результат: 1+IntToStr(Summ)), *Внимание', MB_OK);
end;
В разделе var появилась новая переменная bml типа TBookmarkstr. Этот тип используется для вкладок. После отключения отображения мы устанавливаем вкладку, сохраняя ее в переменной bml:
bml: =DataModulel. ADOTablel. Bookmark;
Здесь мы сохраняем в переменной bml свойство Bookmark. После расчета мы переходим по этой вкладке обратным присваиванием свойству Bookmark переменной bml:
DataModulel. ADOTable 1. Bookmark: =bml;
К Прммечанме
На компакт-диске в папке \ Примеры \ Глава 14\Scan вы можете увидеть пример этой программы.
Бинарные данные
Если вам необходимо хранить в базе данных бинарные данные, то все описанные ранее способы не подходят. Сейчас мы рассмотрим, как можно поместить в БД изображения и звук, а потом просматривать и прослушивать их.
Создайте новую базу данных и в ней — таблицу ВiobExampie. В этой таблице понадобятся три поля:
口 Keyl — ключевое поле (счетчик):
Image — установим Здесь ТИП данных (Поле объекта OLE);
Sound —установим здесь тип данных (поле объекта OLE).
На рис. 14.39 вы можете увидеть структуру этой базы данных в программе Access.
Теперь создадим новый проект в Delphi. На рис. 14.40 показано главное окно будущей программы. Форма окна содержит:
口 компонент ADOTablel, который связан с базой данных и таблипей Вi obExampiе;
компонент DataSourcel, связанный с ADOTablel для отображения данных в сетке;
口 DBGridl — используется для отображения строки таблицы;
DBNavigatorl — используется для навигации по таблице:
工 Прммечанме ]
С компонентом DBNavigatori мы еще не работали, но он прост. Достаточно указать в свойстве DataSource компонент DataSourcel ——и кнопки на панели
навигации смогут управлять нашей таблицей. В реальных приложениях этой панелью пользуются редко, но в данном случае требуется что-то простое для добавления строк.
Рис. 14.39. Структура базы данных в программе Access
О DBlmagel — используется для отображения картинки из базы данных, причем у этого компонента нужно установить в свойстве DataSource компонент DataSourcel, а в поле DataField поле с картинкой;
О четыре кнопки — Вставить из файла. Звук из файла. Играть звук и Вставить из буфера;
О openDialogl — для отображения окна открытия файла.
Чтобы присвоить всем полям таблицы имена, дважды щелкните кнопкой мыши по компоненту ADOTablel и в окне редактора полей добавьте все поля (щелкните правой кнопкой мыши и выберите пункт меню Add All Fields).
Рис. 14.40. Главное окно будущей программы
Для события Onclick кнопки Вставить из файла напишите следующий код:
procedure TForml.OpenlnageFromFileButtonClick(Sender: TObject); begin
if OpenDialogl.Execute then
begin
ADOTablel.Edit;
ADOTablelImage.LoadFromFile (OpenDialogl. FileName);
ADOTablel-Post;t
end; end;
Здесь сначала отображается окно открытия файла. Если файл выбран, то таблица переводится в режим редактирования, в поле АРОТаЫ е 1 Image (это имя поля картинки) загружается содержимое выбранного файла и изменения сохраняются. Для загрузки файла используется метод LoadFromFile, которому нужно только указать файл для загрузки.
Уже сейчас вы можете запустить программу и загрузить картинку. Она должна отобразиться в компоненте DBimagel. Загружать желательно файлы формата BMP, потому что для остальных форматов нужно подключить в раздел uses необходимые заголовочные файлы. Иначе будет ошибка.
Для события onclick кнопки Звук из файла напишем следующий код:
procedure TForml.Button3Click(Sender: TObject);
begin
if OpenDialogl.Execute then
begin
ADOTablel.Edit;
ADOTablelSound.LoadFromFile(OpenDialogl.FileName);
ADOTablel.Post;
end;
end;
Здесь происходит та же загрузка, только в поле Sound. В это поле лучше загружать WAF-файлы. Чтобы их проиграть прямо из базы данных по нажатии кнопки Играть звук, напишите для ее события onclick следующий код:
procedure TForml.Button4Click(Sender: TObject);
var
mem: TMemoryStream;
begin
mem: =TMemoryStream. Create;
TkDOTablelSound. SaveToStream(mem);
PlaySound(mem.Memoryf 0, SND_SYNC+SNDJ<EMORY);
mem* Free;
end;
Здесь у нас объявлена переменная mem типа TMemorystream. Этот тип похож на TFileStream и большинство свойств и методов одинаковы. В первой строке инициализируется объект mem. Далее сохраняется содержимое поля ADOTab lei Sound В ПОТОКе TMemoryStream.
В следующей строке воспроизводится звук с помощью функции PlaySound. Мы пока не затрагивали тему звука. Это тема отдельного разговора, который нас ожидает чуть позже, а пока вам необходимо знать, что этой функции нужно передать три параметра.
О Откуда играть звук. Здесь можно указывать имя файла или, как в нашем случае, — указатель на область памяти, содержащей звуковые данные.
□ Указатель на исполняемый файл, который содержит ресурс, необходимый для загрузки. Нам не нужны никакие файлы, поэтому здесь выставлен 0.
П Параметры воспроизведения. Параметров достаточно много, но мы рассмотрим только те, которые будут использованы.
SND_MEMORY — ВОСПРОИЗВОДИТЬ ЗВуК ИЗПЯМЯТИ.
SND SYNC — воспроизводить синхронно. Это значит, что программа не продолжит выполняться, пока не доиграет звук. Если установить асинхронное проигрывание (SND ASYNC), то программа запустила бы воспроизведение и продолжила выполняться.
Внимание
Если после установки ——"воспроизводить синхронно", стоит код уничтожения памяти со звуком (в примере именно так), то данные, которые еще не проиграны полностью, будут уничтожены. Поэтому в нашем примере блокируем выполнение программы до окончания их воспроизведения путем использования snd memory (воспроизводить звук из памяти).
Для удачной компиляции нужно подключить в раздел uses модуль mmsystem, в котором находится описание функции PlaySound.
Напоследок удаляем объект mem, который уже не нужен.
Запустите программу и попробуйте вставить какой-нибудь звуковой файл. После этого вы сможете воспроизвести его прямо из базьк данных.
На форме у нас еще осталась без действия кнопка Вставить из буфера. Для события OnClick, связанного с ее нажатием, напишем только одну строчку кода:
DBImagel.Paste FromClipboard;
У компонента DBImagel есть одно очень полезное свойство — PasteFromClipboard, — которое позволяет вставлять данные из буфера обмена. Вы также можете скопировать или вырезать данные в буфер с помощью методов CopyFromClipboard И Cut FromC lipboa rd.
Чтобы сохранить бинарные данные из базы данных обратно в файл, можно воспользоваться методом SaveToFile. Например, вот так можно сохранить звук обратно в файл:
ADOTablelSound.SaveToFile(имя файла);
К Примечанме ;
На компакт-диске в папке \ Примеры \ Глава 14 \ Blob вы можете увидеть пример этой программы.
17 Зак 942
События таблицы
У таблицы ADOTable достаточно много событий и большинство из них очень полезны. Рассмотрим эти события.
□ AfterCancel — событие генерируется после вставки строки.
О AfterClose — событие генерируется после закрытиячтаблицы (она перестала быть активной).
П AfterDelete — событие генерируется после удаления строки.
П AfterEdit — событие генерируется после перехода в режим редактирования строки (после вызова метода Edit).
CD Afterinsert — событие генерируется после вставки строки.
П Afteropen — это событие генерируется после открытия таблицы.
П AfterPost - событие генерируется после запоминания строки (после вызова метода Post).
П Afteri- fr₽ -^h — это событие генерируется после обновления таблицы.
О Aft, 丄 —это событие генерируется после перехода на новую строку.
Все эти обработчики генерируются до возникновения какого-то события. Для каждого из них есть аналог, начинающийся не со слова After, а со слова Before (BefbreCancel, BeforeClose И T. Д.). Эти обработчики срабатывают до возникновения соответствующего события.
Рассмотрим как и коша можно ими пользоваться.
Допустим, что у вас в таблице есть колонка, значение которой может изменяться в зависимости от определенных параметров. Например, у вас есть три работника склада, которые работают по сменам. В зависимости от смены при создании новой строки нужно указывать определенного работника. Здесь можно понадеяться на оператора программы, рассчитывая на то, что он будет правильно указывать работника склада. Но желательно этого не делать. Лучше создайте обработчик для события Afterinsert и там напишите:
ScladTable.Edit;
if сегодня работает смена 1 then
WorkerColumn.AsString:='Иванов';
if еегодня работает <:мена2 then
WorkerColumn.AsString: = *Петров *;
if сегодня работает смена3 then
WorkerColumn.AsString:='Сидоров';
ScladTable.Post;
Таким образом, после создания строки, в зависимости от смены, программа сама устанавливает фамилию работника в поле WorkerColumn.
Рассмотрим еще один пример, который встречается в жизни довольно часто. Допустим, что у вас есть две связанных таблицы. Если удалить в главной таблице строку, то соответствующие строки в подчиненной таблице останутся несвязанными, т. е. неиспользуемыми. Это лишние данные для подчиненной таблицы, обусловленные нарушением ссылочной целостности, т. е. когда нарушается связь между строками таблиц.
Эту задачу удобно решать с помощью триггеров (процедуры, которые выполняются на сервере в ответ на определенные события — вставка, изменение или удаление строк в таблице) на уровне базы данных. Но мы пока работаем с таблицами Access, которые не поддерживают триггеры. Поэтому можно создать обработчик события BefireDelete и написать в нем следующий код: while SlaveTable.RecordCount>0 do
SlaveTable.Delete;
Здесь запускается цикл — пока в подчиненной таблице количество строк больше нуля, то удалить строку. Если вы навели связь между таблицами, то свойство Recordcount будет указывать на количество строк, связанных с текущей строкой главной, т. е. только те строки, которые надо удалить. Если связь не наведена, то вы можете очистить всю подчиненную таблицу.
Почему здесь используется цикл while, а не repeat или for? В данном случае так надо. Допустим, что вы решили использовать инкл repeat. Взгляните на следующий код:
repeat
SlaveTable. Delete;
until SlaveTable.RecordCount=0;•
На первый взгляд все нормально, но представьте себе ситуацию, когда в подчиненной таблице нет ни одной строки, связанной с выделенной строкой в главной таблице. Цикл repeat выполняется хотя бы один раз, но в таблице нет строк, поэтому при единственной попытке удалить произойдет ошибка, потому что удалять нечего.
Теперь посмотрим на следующий код:
for i:=0 to SlaveTable.RecordCount-1 do
SlaveTable.Delete;
Здесь тоже все вроде нормально, даже коша в подчиненной таблице нет строк. Но попробуйте мысленно выполнить этот цикл. После первого же шага значение SlaveTable. Recordcount будет уменьшено на 1, потому что удалена строка. Таким образом, на определенном этапе тоже произойдет ошибка, т. к. значение переменной i увеличивается, а значение SlaveTable. Recordcount уменьш ается.
(Совет )
Никогда не используйте цикл for, если конечное значение изменяется.
Глава 15Глава 15幡
Глава 15
Глава 15
Создание отчетности
Мы уже научились работать с базами данных и создали полноценный телефонный справочник. Но какая база данных без отчетности? Практически всеща возникает потребность в использовании ее данных в других программах или в формировании на их основе документов с целью последующей печати.
В этой главе будут рассматриваться вопросы, связанные с формированием выходной информации (отчетов). Материал базируется на примерах, поясняющих механизмы формирования выходных документов.
Все примеры будут работать с базами данных, потому что именно при их программировании возникает потребность в создании каких-то выходных документов. Не имеет смысла формировать сложные базы данных, которые нельзя документировать (распечатать) или преобразовать в другой формат. В связи с этим настоящая глава также знакомит с преобразованием данных в формат Excel, а также с механизмами формирования выходных печатных документов.
Если вы пока не думаете о создании отчетности, желательно ознакомиться с этой главой, потому что здесь будет достаточно много интересных вещей, которые мы пока что не рассматривали. Например, в предыдущей главе мы не могли рассмотреть все свойства и методы таблицы ADOTable, поэтому были рассмотрены только основные ее возможности.
Внимание
В Delphi 7 отчеты QuickReport не устанавливаются, но вы можете сделать это самостоятельно. Для этого выберете меню Project | Options и на вкладке Packages нажмите кнопку Add. Теперь найдите файл dcltqr70.bpl, который должен находиться в директории Bin, где установлен Delphi. Откройте его, а потом закройте окно настроек проекта кнопкой ОК.
К Примечание ?
Вполне возможно, что в будущих версиях фирма Borland отключит возможность использования QuickReport. Однако пока он есть, его можно использовать. Даже после отключения QuickReport в Internet можно будет найти эти компоненты и установить их вручную.
Создание отчетности в Excel
Первое, с чем мы познакомимся, — отчетность в Excel. Потребность в выгрузке данных в Excel может возникнуть у каждого программиста баз данных, ведь Office установлен в нашей стране практически на каждом компьютере. А это значит, что отчетность можно смело переносить между компьютерами и быть уверенным, что ее смогут прочитать.
В этом разделе будет не так уж много визуальных манипуляций, зато программирования будет предостаточно.
Первое, что сейчас сделаем, это добавим на форму нашего телефонного справочника одну кнопку, по нажатии которой будет создаваться отчет. Можно еще добавить пункт меню Экспорт в Excel в меню Файл. Результат этих действий вы можете увидеть на рис. 15.1.
Теперь переходите в редактор кода и сразу же добавьте в раздел uses модуль comobj. В этом модуле описаны все необходимые функции для работы с СОМ-объектами (о них пока ничего не говорилось, все еще впереди).
Рис. 15.1. Кнопка создания отчетности в Excel
Теперь создайте обработчик события для кнопки и укажите его же в качестве обработчика для пункта меню (если вы его создали). В этом обработчике нужно написать код, приведенный в листинге 15.1.
Листинг 15.1. Создание отчетности в Excel
var
XLApp, Sheet, Colum: Variant;
index, i:Integer;
begin
XLApp:= CreateOleObject(1Excel.Application*);
XL?^)p. Visible: =true;
XL?^)p. Workbooks. Add (-4167);
XLApp.Workbooks[1].Worksheets[1].Name:=*Отчет';
Colum:=XLApp.Workbooks[1].Worksheets['Отчет1].Columns;
Colum.Columns [1] .Columnwidth: =20;
Colum. Columns [2] . ColumnWidth: =20;
Colum. Columns[3].ColumnWidth:=20;
Colum.Columns[4].ColumnWidth:=20;
Colum.Columns[5].ColumnWidth:=20;
Colum:=XLApp、Workbooks[1].Worksheets[*Отчет *].Rows;
Colum. Rows[2].Font.Bold:=true;
Colum.Rows[1].Font.Bold:=true;
Colum. Rows[11.Font.Color:=clBlue;
Colum.Rows[1].Font.Size:=14;
Sheet:=XLApp.Workbooks[1].Worksheets[*Отчет1];
Sheet•Cells[1,2]:=*Телефонный справочник';
Sheet.Cells[2,1]: = 'Фамилия *;
Sheet.Cells [2,2]: = *Имя,;
Sheet. Cel Is [2,3] : = * e-mail •;
Sheet.Cells[2,4]:=1Город1;
Sheet.Cells[2,5]:='Дата рождения';
index:=3;
DataModulel.BookTable.First;
for i:=0 to DataModulel.BookTable.RecordCount-1 do
begin
Sheet.Cells[index,1]:=DataModulel.BookTable.Fields.Fields[1].AsString;
Sheet.Cells[index, 2}:=DataModulel.BookTable.Fields.Fields[2].AsString;
Sheet.Cells[index,3]:=DataModulel.BookTable.Fields.Fields[3].AsString;
Sheet. Cells [index,4] :=DataModulel.BookTable.Fields. Fields [5] .AsString;
Sheet. Cells [ indexr5] : =FormatDateTiine( * dddddd1, DataModulel. BookTable. Fields. Fields [6] .AsDateTime); Inc(index);
DataModulel .BookTable.Next;
end;
Первая строка кода создает объект Excel и записывает его в переменную
XLApp
XLApp: = CreateOleObject ( 1 Excel. Application *))
Эта переменная типа Variant. Тип variant может принимать любые значения: строки, числа, указатели и др.
Функцию CreateOleObject сейчас подробно рассматривать не будем, потому что она не относится к базам данных или отчетности и требует отдельного разговора. Единственное, что сейчас надо сказать, — это то, что она позволяет наладить связь с другим приложением по технологии СОМ. Через эту связь можно передавать данные в другие приложения. Для этого программа, с которой происходит соединение, должна иметь соответствующие возможности для получения данных извне (как, например, Excel). Поэтому вам должны быть известны функции, с которыми можно работать.
КПримечание ~
Чаще всего такие вещи документируются на сайге разработчиков. В одной книге невозможно описать все возможности всех программ, потому что, если это сделать, то "Война и Миргг покажутся детской колыбельной. Этого никому не нужно. Так что рассмотрим только Excel, чтобы на этом примере показать возможности передачи данных между приложениями.
Вторая строка кода заставляет отобразить Excel. Потом добавляется новая рабочая КНИГа (XLApp.Workbooks .Add (-4 167)).
Внимание
Число в скобках — это константа, которая означает создание книги, и ее изменять нельзя. Подробнее обо всех константах вы можете почитать в руководстве разработчика на сайте MS или в файле excel97.pas. Однако немного позже, будет показан один интересный прием, с помощью которого вы всегда сможете определить необходимую константу для Excel.
Дальше формируется название созданной книги:
XLApp.Workbooks[1].Worksheets[1].Name: = * Отчет1
Это действие не обязательно, но его желательно делать, потому что название по умолчанию будет——MJIucm 7二
Теперь у нас Excel запущен и создана новая книга. Можно переходить к переносу данных. Но прежде чем это сделать, давайте отформатируем колонки и строки. Для этого надо получить указатель на колонки рабочей книги:
Colum:=XLApp. Workbooks[1]. Worksheets[* Отчет1]. Columns)
Результат записывается в переменную Colum типа variant. Теперь последовательно изменяем ширину колонок:
Colum.Columns[1]. Columnwidth:=20
На русском языке эта команда будет выглядеть так:
Колонки. Колонка [1] . ШиринаКолонки: =2 0.
После этого в ту же переменную записываем указатель на строки рабочей книги:
Colum:= XLApp. Workbooks[1]. Worksheets[* Отчет1] . Rows)
Для украшения строк нашего отчета, устанавливаем у первых двух строк жирный шрифт:
Colum. Rows [1] . Font. Bold:=true
В квадратных скобках теперь порядковый номер строки. Далее вдут две строки, в которых устанавливается цвет первой строки в синий и размер шрифта равным 14.
Форматирование окончено, теперь можно передавать (выводить) данные. Для этого получаем указатель на лист:
Sheet :=XL7^)p. Workbooks [1] . Worksheets [1 Отчет1 ])
Для того чтобы вывести данные, нужно просто присвоить значение в Sheet.Cells[строка, колонки].
Посмотрим на код вывода данных таблицы:
index:=3;
DataModulel.BookTable.First;
for i:=0 to DataModulel.BookTable.RecordCount-1 do begin
Sheet.Cells[index,1]:=DataModulel.BookTable.Fields.Fields[1].Assuring; Sheet.Cells[index,2]:=DataModulel.BookTable.Fields.Fields[2].AsString; Sheet.Cells[index,3]:=DataModulel.BookTable.Fields.Fields[3].AsString; Sheet.Cells[index,4]:=DataModulel.BookTable. Fields.Fields[5].AsString; Sheet.Cells[index,5]:=FormatDateTime(•dddddd',
DataModulel.BookTable.Fields.Fields[6].AsDateTime);
Inc(index);
DataModule1.BookTable.Next;.
end;
Здесь сначала задается переменной index значение 3. Эта переменная будет отображать, в какую строку таблицы Excel сейчас должны выводиться данные. Первые две строки у нас уже заняты заголовками для отчета, поэтому данные нужно начинать выводить с третьей строки.
Внимание
Обратите внимание, что строки и таблицы в Excel нумеруются начиная с единицы, а не с нуля, как в остальных таблицах и массивах.
После этого мы переходим на первую строку таблицы в базе данных с помощью метода First компонента AdoTable. Это необходимо, потому что пользователь может перед нажатием кнопки отчета выделить любую строку в середине таблицы, и в этом случае отчет пойдет от этой выделенной строки.
Теперь подготовлены условия для запуска цикла, в котором будут перебираться все строки, и информация из них будет попадать в Excel. Цикл запускается, начиная с 0, и продолжается до достижения значения количества строк в таблице. Таким образом мы переберем все записи.
Почему в качестве цикла используется именно конструкция for. .to. .do? Просто здесь удобней так. Иноща можно использовать цикл while. С циклом while этот код выглядел бы так:
while DataModulel. BookTable. Eofotrue do
begin
//Вывод данных в Excel
Увеличение переменной index;
Переход на следующую строку;
end;
В принципе, здесь все то же самое, только используется цикл while. Этот цикл удобнее использовать, когда нужно вывести не всю таблицу, а только ее часть. Например, только начиная с текущей позиции. В этом случае не надо будет перед циклом переходить на первую строку, а достаточно только запустить этот цикл, который будет выполняться, пока DataModulel. BookTable. Eof не станет равным true, т. е. не будет достигнут конец таблицы.
Внимание JJ
Не забывайте внутри цикла переходить на следующую строку. Если вы забудете это сделать с циклом for, то у вас в Excel попадет столько же строк, сколько есть и в таблице, но все они будут одинаковыми (равны первой строке таблицы). Ну, а при цикле while программа зависнет, потому что цикл будет бесконечным, ведь конец таблицы никогда не будет достигнут, если не переходить на следующие строки.
Теперь поговорим о выводе данных. Мы последовательно заполняем колонки, присваивая в Sheet.Cells [index, номер колонки] соответствующие данные из таблицы. Данные из базы данных берутся по индексу (до этого мы обращались по имени, но здесь используется индекс). Для этого используется следующая конструкция:
DataModulel.BookTable.Fields.Fields[Номер поля].AsString
После очередного вывода увеличивается значение переменной index, чтобы на следующем этапе вывести данные в следующую строку и затем перейти на новую.
В принципе, это и все. Однако здесь необходимо еще учесть несколько замечаний и рассмотреть универсальный способ вывода данных из сетки:
for i:=1 to Table.RecordCount do
begin
for j:=1 to DBGrid.Columns.Count do
Sheet.Cells[Index, j]:=DBGrid.Fields[j-1].AsString;
Inc(Index);
Table.Next;
end;
Это просто общий пример, не связанный с нашей программой. Здесь также запускается цикл по всем строкам таблицы. Но вывод данных происходит по-новому. Для этого запускается еще один цикл от 1 до значения количества колонок в сетке DBGrid. Внутри этого цикла очередной колонке Excel присваивается значение из такой же колонки сетки DBGrid:
Sheet.Cells [Номер строки, Номер колонки]:-
DBGrid.Fields[Номер колонки в сетке].AsString;
Этот способ более универсален и может подойти почти всегда, когда надо вывести все содержимое сетки DBGrid и все данные одного типа. В нашем случае в таблице есть дата, поэтому, чтобы она красиво выглядела, ее надо форматировать с помошью Format Dat eTime. Так что прямой перенос для этой колонки был бы не очень удобен, но остальные колонки можно было бы перенести указанным способом.
В процессе вывода данных можно изменять простым присваиванием Цвет строк — Sheet. Rows [ строка ] . Font. Color ИЛИ КОЛОНОК — Sheet. Columns [колонка] .Font. Color (как мы это делали при форматировании). Если нужно изменить цвет отдельной ячейки, то это можно сделать, ПРИСВОИВ НОВОе значение В Sheet.Cells [строка, колонка] .Font.Color.
Вот еще некоторые параметры, которые можно изменить:
Sheet. Cells [строка, колонка] .Font. Italic — курсивный шрифт;
О Sheet.Cells [строка, колонка] . Font .Bold — жирныв шрифт;
О Sheet.Cells[строка, колонка].Font.Underline — подчеркнутый Шрифт;
Sheet. Cells [строка, колонка] . Font. Size — размер Шрифта.
工 Примечание
С помощью всего этого создаются простые, но эффективные отчеты. В Delphi, в директории Lib, есть файл excel97.pas. В нем вы найдете все доступные функции Excel. Если что-то будет неясно, то отложите их изучение. Чуть позже вы наберетесь навыков для понимания структуры заголовочных файлов.
У нашей программы остался один недостаток — она выводит данные только из основного справочника и не учитывает телефоны. Чтобы избавиться от этого недостатка, можно написать отдельный SQL-запрос, который будет формировать сводную таблицу из наших двух. Этот запрос можно поместить в отдельный компонент ADO Query, выполнить его и брать данные из запроса, а не из таблицы ВоокТаЫе.
Вот пример запроса, который выбирает все строки из двух таблиц, формируя отдельную базу данных.
SELECT *
FROM Справочник, Телефоны
WHERE Справочник. Кеу1=Телефоны. LinkKey
Рис. 15.2. Результат работы программы
На рис. 15.2 вы можете увидеть результат работы программы. Вот так выглядит созданная отчетность.
К Прмметанме [
На компакт-диске в папке \ Примеры \ Глава 15 \ Excel вы можете увццеть пример этой программы.
Теперь рассмотрим один интересный прием, с помощью которого вы сможете сделать практически все, что угодно при выводе отчетности в Excel. Допустим, что вам надо отформатировать ячейку по правому краю. В нашем отчете все ячейки выровнены влево, а надо вправо. Для этого запускаем Excel. Выбираем меню Сервис | Макрос | Начать запись. Перед вами откроется окно, показанное на рис. 15.3. В нем все можно оставить по умолчанию и нажать ОК. Таким образом, вы запустили запись макроса. Напечатайте в какой-нибудь ячейке текст и установите выравнивание по правому краю. Теперь остановите запись с помошью пункта меню Сервис | Макрос | Остановить запись»
Рис. 15.3. Окно запуска мастера
Выберите меню Сервис | Макрос | Начать запись и перед вами откроется окно со списком макросов. Выделите имя созданного макроса и нажмите кнопку Войти. В рассматриваемом примере макрос один и называется ”Макрос 7:
Если у вас Office ХР, то после окончания записи макроса окно выбора может не появиться. В этом случае выберете меню Сервис | Макрос | Редактор Visual basic. Перед вами откроется окно редактора скриптов (код на языке Basic, который выполняет Word). Слева вверху вы можете увидеть дерево с элементами проекта. В разделе Modules найдите свой макрос и дважды щелкните по нему кнопкой мыши. Откроется редактор Visual Basic, в котором будут записаны на языке Basic все выполненные действия (листинг 15.2).
Рис. 15.4. Редактор Visual Basic
Листинг 15.2. Код макроса на языке Basic
&иЪ Макрос!()
Макрос1 Макрос
Макрос за.писа.н 10.10.2003 (Flenov)
ActiveCell. FonnulaRlCl = -цц» Range("At").Select With Selection
.Horizontal Alignment = KiRight
.Ve rtica1Alignment = xlBottom
.WrapText = false .Orientation = о .Addindent = false
.IndentLevel = О
.ShrinkToFit = false
.Readingorder = xlContext
.MergeCells = false
End With
End Sub
Даже если вы не знаете язык Visual Basic, в этом коде разобраться достаточно легко, если у вас есть хоть малейшие представления об английском языке.
Выравнивание вправо на английском языке пишется как Right. Ищем строку, содержащую это слово:
.Horizontal Alignment = xlRight
Здесь свойству HorizontalAlignment присваивается выравнивание xlRight. В идеальном варианте вы можете в Delphi написать:
Sheet.Cells[строка, колонка].HorizontalAlignment:=xlRight;
Но это идеальный вариант, а в реальной ситуации Delphi не поймет константу xlRight. Вы можете ее поискать в заголовочном файле, о котором мы говорили, или вернуться в редактор Visual Basic и просто установить на нее указатель мыши. У вас должна появиться подсказка с числовым значением константы -4152. Получается, что мы легко можем написать в Delphi следующую строку:
Sheet.Cells[строка, колонка].HorizontalAlignment:=-4152;
Таким образом, если вы хотите применить какое-то форматирование или выполнить какое-то действие, но не знаете, как это сделать, то просто запишите макрос и посмотрите его исходный код.
Отчетность в Quick Reports
Теперь познакомимся с мощным средством создания отчетов, которое входит в поставку Delphi — 1енератор отчетов Quick Reports. Он не является самым быстрым, и в сети Internet можно найти множество более быстрых, как платных, так и бесплатных генераторов отчетов. Но Quick Reports очень мощный, к тому же он установлен в Delphi и готов к работе. Поэтому мы и будем рассматривать именно его.
Все компоненты Quick Reports находятся на вкладке QReport палитры ком
понентов.
Для начала давайте посмотрим на головной компонент Quick Reports — TQuickRep. Этот компонент — основа любого отчета. Он представляет собой холст листа будущего отчета. Попробуйте дважды щелкнуть мышью по значку этого компонента в палитре компонентов, и он автоматически буцет перенесен на форму. Вам надо выровнять края компонента по форме и вы увидите готовый белый лист, на котором можно буцет размещать будущее вашего документа. По краям листа находятся синие пунктирные линии, которые показывают границы документа.
Давайте посмотрим на объектный инспектор и разберемся с полями отчета.
П Bands — здесь вы можете указать, что должен иметь будущий документ.
А он может содержать целый ряд инструкций, касающихся его оформления.
HasColumnHeader — заголовки колонок. Если ваш отчет буцет содержать таблицу, то она должна иметь шапку, ще будут определены названия колонок. Вот именно эту шапку создают в этой части документа. Так что если вам нужна буцет таблица, то этому свойству нужно будет присвоить true.
HasDetail — если в отчете есть таблица, то вид строк формируется в этом разделе.
HasPageFooter — в этом разделе создается нижний колонтитул.
HasPageHeader — здесь создается заголовок документа.
Has Summa г у — содержимое этого раздела печатается один раз в конце отчета (на последней странице).
HasTitle — в этом разделе делается заголовок отчета.
工 Примечание ;
Попробуйте создать новый проект и установить на форму один компонент QuickRep. Теперь включите какие-нибудь разделы и посмотрите на результат. На форме должны появиться области, очерченные пунктирной линией. Внизу области должно быть написано ее назначение. Так вы легко можете отличить их между собой. Любую область можно выделить и растянуть или уменьшить (рис. 15.5).
□ DataSet — здесь указывается таблица, из которой отчет буцет брать данные.
О Font — шрифт, который буцет использоваться по умолчанию.
О Frame — параметры рамки.
О options — здесь вам доступны три параметра.
FirstPageHeader равно true — заголовок печатается только на первой странице отчета.
Co吧 аниео 丁чдтнос 丁и一 -579
LastPageFooter равно true — нижний колонтитул печатается только на последней странице отчета.
compression установлено в true — отчет будет сохраняться в сжатом виде.
Рис. 15.5. Вид компонента QuickRep с включенными областями
О Page — здесь определяются все необходимые опции для контроля над бумагой отчета. Вы можете установить ее размеры, отступы и ориентацию.
О Printersettings — настройки принтера. С принтером мы уже работали, да и настройки практически не требуют пояснения.
О ReportTitle — заголовок печатаемого документа.
О showProgress — если этот параметр равен true, то во время печати вам будет доступен индикатор хода выполнения печати.
О SnapToGrid — определяет, нужно ли выравнивать компоненты по установленной сетке.
О Zoom — масштаб отображения данных.
Если дважды щелкнуть по компоненту QuickRep, то перед вами откроется окно, в котором все эти настройки представлены в одном окне и в очень удобном виде. Они достаточно понятны и с ними легко можно разобраться самостоятельно (рис. 15.6).
Рис. 15.6. Окно настроек отчета
Пока теории хватит, давайте посмотрим, как же действует отчетность. Откройте телефонный справочник, созданный в гл. 14. Добавьте на панель кнопку печати, а обработчик ее события Onclick мы напишем чуть позже.
Сейчас будем создавать форму отчета.
Создайте новую форму (назовем ее ReportForm) и сохраните в модуле Report FormUnit. Сразу же подключите к этому модулю модуль данных (DataModule), ще у нас ранятся компоненты доступа к таблицам базы данных.
Установите на форму компонент QuickRep. Вьвделите этот компонент и в объектном инспекторе включите параметры HasTitle и HasDetail свойства Bands.
Теперь нужно в этих секциях расположить компоненты, которые будут отображать нужную нам информацию. На вкладке Qreport, палитры компонентов доступны ряд компонентов, которые можно располагать в этих разделах. О QRLabel — НЭДПИСЬ. ЭТОТ КОМПОНСНТ ПОХОЖ На стандартный TLabel и просто отображает нужные данные.
О QRDBText — данные. Этот компонент тоже похож на TLabel, только он предназначен для отображения значения какого-либо поля из базы данных. Тип поля базы данных должен быть совместим с текстом, т. е. мо- 氷ет быть целым числом, строкой, датой, но не может быть картинкой или бинарными данными.
О QRSysData — системная информация. Это опять копия Tiabei, только с возможностью отображать системную информацию: дату, время, номер страницы, номер строки в таблицы, общее количество страниц и т. д.
П QRMemo — набор строк. ЭТОТ компонент ПОХОЖ на ТМето и способен отображать Мето данные из базы данных.
П QRShape — компонент для создания обрамлений. Он чем-то похож на Стандартный TShape.
О QRImage — картинка. Компонент, ПОХОЖИЙ на Tlmage.
Теперь примемся за оформление отчета. Выделите область заголовка (Title) и увеличьте ее размер примерно в два раза. В правый верхний угол области (именно области Title, а не компонента QuickRep) поместите один компонент QRSysData. Вьвделите его и в свойстве Data выберите значение qrsDateTime. Теперь этот компонент будет отображать в правом верхнем углу дату распечатки документа.
(Совет )
Рекомендуется всегда это делать, чтобы вы сразу видели, какая версия документа была распечатана последней.
В центре области поместите компонент QRLabei, увеличьте шрифт в свойстве Font и напишите в свойстве Caption текст "Распечатка строки из базы Телефонов*1. Слева области можете установить картинку QRimage, чтобы убедиться, что работа с ней ничем не отличается от работы с компонентом Tlmage.
Результат работы вы можете увидеть на рис. 15.7.
Теперь переходим к области Detail. Здесь давайте выстроим в строку пять компонентов QRLabei и дадим им заголовки: Фамилия, Имя, e-mail, Город, Дата рождения. Под ними поставьте пять компонентов QRDBText. У всех установите СВОЙСТВО DataSet В DataModulel. BookTable, а в свойстве DataField укажите соответствующие поля. В результате должно получиться что-то похожее на рис. 15.8.
Рис. 15.7. Вид заголовка нашего отчета
Рис. 15-8. Вид блока Detai I
Теперь переходим в главный модуль и по нажатии кнопки печати напишем следующий код:
procedure TMainForm.PrintButtonClick(Sender: TObject);
begin
Report Form. QuickRepl. Previ ewModa 1;
end;
В ЭТОМ КОДе вызывается метод PreviewModal Компонента QuickRep. ЭТОТ метод модально показывает окно предварительного просмотра созданного
нами документа. Попробуйте запустить программу, вьвделить какую-нибудь строку и нажать кнопку печати. Перед вами откроется окно предварительного просмотра, как на рис. 15.9.
! О В i@ I И - <! О В i@ I И - <Фамилия ИмяФамилия Имя12 12 2U03 1В 23 2GРаспечатка строки //> оазы Тетефоное12 12 2U03 1В 23 2GРаспечатка строки //> оазы Тетефоноеe-maii Городe-maii ГородДата рождения:Дата рождения:ИвановИвановРостов-Ростов-ДоДоДекабрьДекабрь20022002фамилияИмяПетровФамилияИмяСидоровфамилияИмяПетровФамилияИмяСидоровГородГородМоскваГородГородМоскваДата рождения:30 Январь 2312 г.Дата рождения:22 Март 2002 г.Дата рождения:30 Январь 2312 г.Дата рождения:22 Март 2002 г.фамилияфамилияИмяИмяГородГородДата рождения;Дата рождения;Ю Октябрь 1992 гЮ Октябрь 1992 гСмирновСмирновandrey@mail а Ростов-на-Донуandrey@mail а Ростов-на-ДонуАндрейАндрейРис. 15.9. Окно предварительного просмотраРис. 15.9. Окно предварительного просмотра
! О В i@ I И - <
! О В i@ I И - <
Фамилия Имя
Фамилия Имя
12 12 2U03 1В 23 2G
Распечатка строки //> оазы Тетефоное
12 12 2U03 1В 23 2G
Распечатка строки //> оазы Тетефоное
e-maii Город
e-maii Город
Дата рождения:
Дата рождения:
Иванов
Иванов
Ростов-
Ростов-
До
До
Декабрь
Декабрь
2002
2002
фамилияИмя
Петров
ФамилияИмя
Сидоров
фамилияИмя
Петров
ФамилияИмя
Сидоров
Город
Город
Москва
Город
Город
Москва
Дата рождения:
30 Январь 2312 г.
Дата рождения:
22 Март 2002 г.
Дата рождения:
30 Январь 2312 г.
Дата рождения:
22 Март 2002 г.
фамилия
фамилия
Имя
Имя
Город
Город
Дата рождения;
Дата рождения;
Ю Октябрь 1992 г
Ю Октябрь 1992 г
Смирнов
Смирнов
andrey@mail а Ростов-на-Дону
andrey@mail а Ростов-на-Дону
Андрей
Андрей
Рис. 15.9. Окно предварительного просмотра
Рис. 15.9. Окно предварительного просмотра
В этом окне достаточно нажать кнопку печати, и документ будет распечатан
на принтере.
К Грммеч 吧 не |
На компакт-диске в папке \ Примеры \ Глава 15 \ QuickRep вы можете увидеть пример этой программы.
Печать таблиц с помощью Quick Reports
Как видите, создание отчетности с помощью Quick Report — задача не сложная. За каких-то 10 минут мы создали красивый и удобный отчет, №-
торы访 легко распечатать. В нем мы подготавливали к печати одну запись из таблицы. Но что делать, если нужно распечатать все записи базы данных? Неужели нужно для каждой строчки ставить отдельные компоненты? Конечно нет, все делается очень просто.
Откройте пример из разд. 15.2. Сейчас мы его скорректируем.
Откройте модуль ReportForm, где находятся компоненты отчетности. Вьвде- лите QuickRepl и в свойстве Bands установите true у параметра HasColumnHeader. На форме ПОЯВИТСЯ НОВЫЙ блок Column Header, который можно использовать для создания заголовков таблиц.
Теперь, удерживая клавишу <Ctrl>, обведите все компоненты QRLabel в блоке DetailBandl. Выберите из меню Edit пункт Cut, чтобы вырезать компоненты и передать их в буфер обмена. Теперь вьвделите блок ColumnHeaderBandl и выберите из меню Edit пункт Paste, чтобы вставить вырезанные компоненты из буфера обмена в блок CoiumnHeaderBandl.
Снова выделяем компонент QuickRepi и в свойстве DataSet указываем таблицу DataModulel.BookTable. ЕСЛИ сделать ЭТО, ТО компонент QuickRepi автоматически будет перебирать все записи из этой таблицы и использовать их в компонентах, которые стоят в блоке DetailBandl. Как уже говорилось, этот блок предназначен для создания строк таблиц, так что теперь вы можете увидеть это на практике.
Запустите программу и нажмите кнопку печати. Перед вами должно от- Бфыться окно, показанное на рис. 15.10. Как видите, блок заголовка и CoiumnHeaderBandl распечатались только по одному разу, а блок DetailBandi был напечатан для каждой строки таблицы. Блок заголовков колонок будет печататься по одному разу сверху каждой страницы, а после него будут идти строки таблиц.
В принципе, на этом можно было бы закончить разговор, но мы же создаем таблицу, а для этого нужно нарисовать сетку. Вот тут можно заметить единственный недостаток отчетности с помощью Quick Report — каждую ячейку придется рисовать отдельно, с помошью компонентов QRShape.
Внимание
На диске имеется программа, которая формирует рамку. С ней вы можете тренироваться в оформлении таблиц.
工 ррммечанне^Л
На компакт-диске в папке \ Примеры \ Глава 15 \ QuickRepi вы можете увидеть примеры программ, рассмотренные в этом разделе.
Рис. 15.10. Окно предварительного просмотра
Печать связанныхтаблиц
У нас уже получился достаточно хороший отчет, но теперь еще более усложним задачу. В телефонном справочнике используется связанная таблица, и хотелось бы, чтобы в выходном документе печатались все телефоны, принадлежащие людям. Можно снова написать SQL-запрос, который будет создавать сводную таблицу из двух, и потом использовать этот запрос для отчета вместо таблиц, но это не выход.
Поместите на форму отчета еще один компонент — QRSubDetaii с вкладки QReport. Этот компонент предназначен для перебора данных, относящихся к подчиненным таблицам. Установите у него следующие свойства:
О DataSet—— здесь установите DataModulel. TelephonTable, чтобы СВЯЗЭТЬ блок с таблицей Телефоны, которая у нас является подчиненной к основному справочнику;
口 Master — здесь нужно указать главный компонент с основными данными (выберите в этом свойстве QuickRepl).
Теперь поместите на этот блок компонент QRLabel, чтобы сделать надпись телефон. Справа от него поместите компонент QRDBText. У него установите СВОЙСТВО DataSet В DataModulel.TelephonTable, а СВОЙСТВО DataField В Телефон .
Рис. 15-11. Окно предварительного просмотра
Теперь можете запускать программу. Нажмите кнопку печати, и если вы ввели в свою базу данных хоть какие-нибудь телефоны, у вас должно получиться нечто похожее на то, что показано на рис. 15.11.
Примечание
На компакт-диске в папке \ Примеры \ Глава 15 \ QuickRep2 вы можете увидеть пример этой программы.
Дополнительные возможности
Здесь будут рассмотрены некоторые возможности, которые можно добавить в свои отчеты. Мы рассмотрим некоторые компоненты, которые не только украшают любой отчет, но и делают его более удобным и мощным.
Первый компонент — QRExpr. Этот компонент очень удобен для создания вычисляемых полей именно для отчета. Вычисления будут происходить автоматически и практически не влияют на скорость работы самой программы.
У компонента есть свойство Expression. Если дважды щелкнуть по нему кнопкой мыши, то перед вами откроется окно, в котором можно создавать достаточно сложные расчеты. На рис. 15.12 вы можете увидеть это окно. Если нажать на кнопку Database field, то перед вами откроется окно, в котором можно выбрать таблицу (таблица должна находиться на этой же форме) и поле, которое должно участвовать в расчете.
Рис. 15.12. Создание отчетов
Если нажать на кнопку Function, то вы увидите большой список доступных функций. Под кнопкой Variable спрятаны переменные, которые могут также помочь в расчетах.
Все это рассматривать нет смысла, потому что здесь возможностей очень много, и по работе с Quick Report можно писать отдельную книгу.
Теперь посмотрим на компонент QRSysData. Мы уже использовали его и в свойстве Data ставили параметр qrsDateTime, чтобы можно было видеть дату и время распечатки документа.
Однако тут есть еше несколько полезных параметров:
□ qrsDate —дата распечатки;
П qrsDetailCount — количество строк в таблице;
П qrsDetailNo — номер строки в таблице;
П qrsPageNumber — текущий номер страницы;
П qrsReportTitle — заголовок отчета;
П qrsTime — время распечатки отчета.
Теперь рассмотрим, как можно еще больше упростить создание отчета. В Delphi есть мастер, который может облегчить создание отчетов. Для его вызова выберите из главного меню File пункт New и затем пункт Other. В появившемся окне перейдите на вкладку Business и выберите пункт QuickReport Wizard (рис. 15.13).
Рис. 15-13- Мастер создания отчетов
В принципе, мастер достаточно прост, но при использовании таблиц ADO он абсолютно не подходит. Если вы будете работать со старыми базами типа DBF или Paradox (об этом будет говориться в следующей главе), то этот мастер принесет свои плоды, но при работе с ADO он практически бесполезен.
Пока что нам больше подходят шаблоны, которые есть в том же окне создания новой формы, но на вкладке Form. Здесь три шаблона (имя QuickReport), содержащие все необходимое для создания отчета, а также
хорошо документированные поля.
Глава 16
Работа с DBF, Paradox, XML и клиент-серверными базами данных
Некоторые программисты любят старые таблицы Paradox и DBF, а некоторые уже перешли на новый и перспективный формат — XML. Про эти форматы нельзя забывать, поэтому они и рассматриваются в этой книге.
Если вы думаете, что в жизни хватит одного только Access, то сильно ошибаетесь. Допустим, вы пришли на новую работу, ще люди еще работают со старым форматом DBF. Из-за вас никто не будет менять уже устоявшихся традиций и, вам придется смириться с консерватизмом, точнее сказать, с устаревшими форматами баз данных (БД). Даже если вы и будете работать со своим любимым форматом, то от конвертирования данных вам никуда не уйти.
Конечно же, можно попытаться убедить начальство в том, что DBF и Paradox не надежны и у них регулярно рушатся индексы. Можно убедить и в том, что XML еще не на столько гибок и отстает в возможностях от полноценных БД, но на это нужно время и хотя бы какой-то промежуток времени вам придется работать со старыми форматами. А если не удастся убедить, то будете использовать нелюбимые технологии до тех пор, пока не поменяете работу.
Так что не надо расслабляться и приступайте к изучению этой главы, потому что она достаточно важна. Тем более что работа с другими форматами данных очень похожа на работу с ADO и весь материал вы усвоите очень легко.
Кроме этого, мы рассмотрим достаточно интересный и полезный материал —клиент-серверные базы данных. Здесь кроме необходимой теории будут показаны примеры построения БД для реальных приложений.
Создание таблицы Paradox
Paradox и DBF — это таблицы, а не базы данных. Если в одной базе Access могло храниться несколько таблиц, то у Paradox и DBF в одном файле хра-
нится только одна. К тому же индексы хранятся отдельно от таблицы, что создает определенные неудобства.
Создание и работа с таблицами Paradox и DBF абсолютно одинаковы, поэтому мы будем все рассматривать на примере Paradox. Когда-то многие программисты на этих таблицах строили налоговую и пенсионную отчетность достаточно крупных предприятий.
Для создания первой базы данных вам понадобится запустить отдельную программу Database Desktop, которая входит в поставку с Delphi. После запуска выполните следующие действия:
Выберите меню File I New | Table.
В появившемся окне выберите из списка — Paradox 7.
Нажми кнопку ОК
Перед вами откроется окно, показанное на рис. 16.1. Можно сказать, что первая база данных готова. Теперь вы должны заполнить ее поля. Однако сначала рассмотрим появившийся в окне диалог.
Рис. 16.1. Окно редактирования полей таблицы Paradox
□ Номер по порядку — Database Desktop генерирует его автоматически, и
изменять вы его не можете.
口 Field Name — имя поля. Здесь вы можете называть свои поля как угодно, но только английскими буквами и нельзя использовать пробелы. Так что, в отличие от Access, здесь могут быть проблемы с нормальным именованием полей и придется текст колонок настраивать в Delphi.
G Туре —тип поля. Щелкните в этой колонке правой кнопкой мыши и перед вами появится меню со всеми допустимыми типами, вам необходимо только выбрать нужный.
О Size — размер. Это размер поля. Не у всех типов полей можно менять размер. У большинства он задан жестко. Размер в основном меняется у СТРОКОВЫХ ТИПОВ (alpha), бинарных (binary) И Др.
О Key — ключ. Если вы дважды щелкните по этой колонке, то текущее поле станет ключевым. Это значит, что по умолчанию по нему будет отсортирована вся таблица. Ключевыми могут быть только первые поля, т. е. второе поле сможет быть ключевым только вместе с первым. Без ключевого поля невозможно добавлять новые записи в таблицу. Точно так же как и в Access — создание ключа обязательно.
В качестве примера возьмем классический пример — прием заказов. Самое главное, это правильно представить себе и создать структуру базы данных. В нашем случае пусть будет вестись учет по следующим полям:
口 ФИО Покупатель;
п Адрес;
О Дата заказа товара;
О Наименование заказанного товара;
П Количество заказанного товара.
Теперь нужно продумать структуру БД. Если создать все поля в одной таблице, то будет не совсем рационально. Это так, потому что если один и тот же покупатель возьмет два товара, то у обеих строчек базы первое и второе поля будут содержать одинаковые данные. Будет лучше, если мы вынесем первые два поля в отдельную таблицу, и потом будем использовать две связанные таблицы.
Итак, пусть наша база данных будет состоять из двух таблиц. В первой будут следующие поля (после тире стоит тип поля, а в скобках размер):
П Ключ 1 — autoincrement (ключевое);
□ ФИО Покупатель — alpha (размер 50);
П Адрес — alpha (размер 50);
Во второй соответственно:
□ Кш)ч 1 — auto increment (ключевое);
О Ключ 2 — Integer;
口 Дата заказанного товара — date;
О Наименование заказанного товара — alpha (размер 20);
О Количество заказанного товара — Integer.
ключ 1 — это уникальное ключевое поле в обеих таблицах, поэтому поставьте им значок ключевого, ключ 2 во второй таблице будет связан с полем ключ 1 из первой, пока он не станет ключевым, мы это сделаем чуть позже.
Создайте эти таблицы. Первую назовем mast.db, а вторую child.db.
Внимание
Запомните, что имена полей должны быть написаны английскими буквами.
После того как создадите вторую таблицу, вы должен сделать ключ 2 во второй таблице индексным, чтобы можно было связать обе таблицы с помощью этого поля. Для этого нужно открыть таблицу child.db и из меню Table выбрать пункт Restructure. Перед вами откроется окно, которое вы видели при создании полей таблицы. Теперь в этом окне будем вносить изменения, а именно добавлять дополнительный индекс.
В выпадающем списке Table properties выберите Secondary Indexes (Дополнительные индексы) и нажмите кнопку Define (Определить). Выберите свой второй ключ и переместите его в список Indexed fields (Индексированные поля). Для этого надо нажать кнопку со стрелкой вправо (рис. 16.2). Можете использовать кнопку ОК
Рис. 16.2. Создание вторичных индексов
После этого у вас запросят имя индекса. Введите, например hhh и снова нажмите ОК Затем сохраните таблицу. Индексы готовы, теперь перейдем к последнему этапу подготовки БД.
Запустите программу SQL Explorer из меню Пуск | Программы | Borland Delphi. Его главное окно вы можете увидеть на рис. 16.3. Здесь создаются псевдонимы (Alias) к разным директориям с таблицами. Эти псевдонимы сохраняются в реестре, и потом все программы при запуске смогут по этим псевдонимам найти директорию таблицы и прочитать необходимые настройки, которые надо использовать при доступе к данным.
•- SQL Explorer
Object Dictionary Edit View Ofitions Help
J ^C b』函
I All Database Aliases
DBDEMOSDelaultDDIBLocalБаза данных MS Q—DBDEMOSDelaultDDIBLocalБаза данных MS Q—Файлы dBASEФк以 “7 FvualФайлы dBASEФк以 “7 FvualD^sbast.D^sbast.ррм咱墨00享ррм咱墨00享Databases j Dictionary |
DBDEMOS
DelaultDD
IBLocal
База данных MS Q—
DBDEMOS
DelaultDD
IBLocal
База данных MS Q—
Файлы dBASE
Фк以 “7 Fvual
Файлы dBASE
Фк以 “7 Fvual
D^sbast.
D^sbast.
ррм咱墨00享
ррм咱墨00享
fe terns in Databases.
Рис. 16.3. Окно SQL Explorer
Чтобы создать новое имя, нужно из меню Object выбрать пункт New. Перед вами откроется окно, в котором поле Database driver name должно быть STAMDART. Теперь нажмите кнопку ОК. Это создаст новый Alias (имя). ПереименуйтееговЗа1е51.
В правой половине окна щелкните по строке PATH. Перед вами откроется окно выбора директории. Выберите ту, дде находятся созданные нами таблицы, и нажмите кнопку ОК. Для сохранения того, что вы создали, выберите из меню Object пункт Apply.
Вот теперь таблицы готовы. Имена созданы и можно запускать Delphi, чтобы написать первую программу, которая будет работать с таблицами Paradox.
Создайте новый проект. Поместите на него из вкладки Data Access два компонента DataSource и с вкладки BDE два компонента ттаЫе палитры компонентов. Для ПерВОГО DataSource установите СВОЙСТВО DataSet В Tablel, а
для второго в ТаЫе2. Теперь у Tablei измените следующие свойства (желательно в такой последовательности):,
□ DatabaseName — выставьте Salesl (это Alias, который мы создали в SQL Explorer);
О TableNaine — выставьте в masLdb. Если вы все правильно сделали, то имя этой базы будет в выпадающем списке данной строки;
О Active — выставьте в true, чтобы активизировать таблицу.
То же самое сделайте и с ТаЫе2, только в TableName укажите child.db. После того как вы все это сделаете, из палитры компонентов DataControls поставьте на форму два компонента DBGrid. В свойстве DataSource у одного из них выставьте DataSourcel, а у другого DataSource2. Постарайтесь расположить компоненты, как это показано на рисунке 16.4.
Рис. 16.4. Расположение компонентов на форме
Уже можно запустить программу и убедиться в ее работоспособности. Но еще все не очень красиво, ведь таблицы не связаны, и никому не нужно видеть ключи, да и подписи на английском. Выделите ТаЫе2, здесь у вас должна находиться таблица child.db. В свойстве Mastersource выставьте DataSourcel. После этого дважды щелкните в поле MasterFields. Перед вами откроется окно, показанное на рис. 16.5. В верхнем списке Available Indexes выберите имя, которое вы задали, когда индексировали Кеу2, в данном случае это hhh Теперь выделите Кеу2 в левом окне и Keyl в правом и нажмите кнопку Add. Так вы добавили связь, которая будет использоваться между таблицами. Нажимайте ОК и снова установите в свойстве Active
этой таблипы — true, потому что при наведении связи таблипа автоматически закрылась.
Рис. 16.5. Окно связывания таблиц
Теперь дважды щелкните по компоненту ТаЫе2. Перед вами откроется окно редаьсгора свойств полей. С подобным окном мы работали, когда писали примеры с помощью ADO. В этом окне щелкните правой кнопкой мыши и в появившемся меню выберите пункт меню Add All Fields. После этого все поля таблицы будут добавлены в это окно. В свойствах Keyl и кеу2 установите свойство visible в false, чтобы спрятать индексы. В свойстве поля Data установите DisplayFormat В dddddd, a EditMask В 99/99/9999. У всех полей свойство DisplayLabel отвечает за имя, отображающееся в компонентах, поэтому напишите здесь у всех нормальные русские имена.
После того как закончите работу с первой таблицей, проделайте подобные операции со второй. Нужно будет также спрятать ключевые поля и написать нормальные подписи к полям.
Ваше первое приложение, работаюшее с базой данных Paradox, готово. Как видите, работа с такими базами практически не отличается от работы с ADO. Единственное отличие — используются другие компоненты, а именно вкладка BDE. Но большинство свойств компонента ттаЫе похожи на свойства компонента TADOTable. У них есть все необходимые свойства, такие как First, Next, Prev, Last, Edit, Post И МНОГИЕ другие.
Для создания запросов к таблицам Paradox и DBF нужно использовать компонент Query с вкладки BDE. У него так же есть свойство SQL, в котором надо писать SQL-запрос. Но т. к. таблицы Paradox и DBF — это таблицы, а
18 Зак 942
не базы данных, то не надо указывать никаких строк подключения. Достаточно только ввести запрос (имя таблицы вы укажете в запросе в поле from) и выполнить его, сделав свойство Active равным true.
Используя таблицы Paradox, можно также создавать поисковые и вычисляемые поля, и здесь особых отличий от ADO нет. Все это связано с тем, что в обоих компонентах использована одна и та же основа.
Примечание ]
На компакт-диске в папке \ Примеры \ Глава 16 \ Paradox вы можете увидеть пример этой программы.
В примере мы не указывали псевдонимы (Alias) таблиц. Это не обязательно, но принято дл兄 удобства. Если не указывать у таблиц в свойстве DatabaseName никакого псевдонима, то таблица будет искаться в текущей директории, где и программный файл. Можно также указывать полный путь В свойстве TableName.
Русификация таблиц Paradox и DBF
Для работы с таблицами Paradox и DBF мы используем компоненты с вкладки BDE. Это не просто название вкладки — это целый набор драйверов или программная надстройка, через которую происходит работа с таблицами. Эта надстройка устанавливается вместе с Delphi, ее также можно найти на диске отдельным установочным файлом.
По умолчанию BDE работает с таблицами в кодировке, не поддерживающей русский язык. Для русификации нужно запустить программу BDE Administrator. Ее главное окно похоже на SQL Administrator. Перейдите в этом окне на вкладку Configuration (рис. 16.6) и откройте в указанной древовидной иерархии ветвь Configuration—Drivers—Native. Здесь выберите пункт PARADOX и в правой половине окна вы увидите настройки доступа к таблицам Paradox.
Здесь нужно изменить параметр LANGDRIVER — драйвер языка. По умолчанию здесь стоит asci, при котором русские буквы превращаются в непонятно что. Выберите у этого параметра в выпадающем списке Pdox — ANSI Cyrillic. Теперь щелкните кнопкой мыши в окне слева (в структуре настроек) по пункту PARADOX и выберите в появившемся меню пункт Apply, чтобы сохранить настройки. После этого появится окно с подтверждением о сохранении данных. После этого — предупреждение о том, что для получения нужного эффеьста нужно перезапустить все программы, работающие с BDE.
Теперь выберите в структуре .настроек пункт DBASE и у него в настройках выберите драйвер языка dBASERUScp866. Сохраните эти настройки.
Рис. 16.6. Обновленная форма программы
Теперь ваши таблицы будут правильно отображать русские буквы, и вы сможете работать с ними на родном и понятном языке.
Быстрый поиск
Поиск одного поля с помощью SQL-запроса или фильтра просто — глупая затея. Этот поиск будет проходить достаточно долго. Но есть способ лучше. Точнее сказать, поиск по ключевым полям. Этот поиск происходит практически моментально, даже при больших базах данных. Недостатком здесь является —отсутствие шаблонов и невозможность использовать привязанную таблицу.
Быстрый поиск мы не рассматривали в главе про ADO, потому что это особенность таблиц Paradox и DBF, точнее сказать, драйвера BDE. В связи с этим рассмотрим это сейчас.
За счет чего достигается большая скорость поиска? Все очень просто — за счет индексации. Так быстро искать можно только по индексированным полям.
Для дальнейшей работы нам понадобятся все те же таблицы из расемшречного выше примера.
В качестве основы возьмите только одну таблицу — masLdb. Как я уже говорил, дда быстрого поиска нужно, чтобы поле было проиндексировано, поэтому добавьте еще один вторичный индекс для поля FIO (там хранится фамилия). Теперь у компонента ттаЫе, к которому привязана эта таблица (Tablel), свойство indexFieldName измените на FIO. Все, таблица готова к использованию.
Теперь добавьте на форму строку кода. В ее коде определим ввод данных, которые надо искать, а также кнопку, по нажатии которой будет запускаться поиск. На рис. 16.7 показана обновленная форма программы.
Рис. 16.7. Обновленная форма программы
Создадим обработчик события Onclick для кнопки и напишем там следующий код:
procedure TForml.ButtonlClick(Sender: TObject);
begin
Tablel. SetKey;
Table1FIO.As S tring:=FindEdit.Text;
Tabiel.GotoKey; end;
Здесь все очень просто. Первая строка говорит, что сейчас мы будем устанавливать ключ. Вторая строка устанавливает его. Заметьте, что это происходит как изменение содержимого поля, но поле не меняется, потому что
мы вызвали перед этим SetKey. Наконец, последняя строка говорит, что надо найти установленный ключ.
Все время мы создавали одиночные вторичные ключи (состоящие из одного поля), но вы можете с помощью программы Database Desktop создавать ключи, состоящие из любого количества полей. Для этого в Database Desktop в выпадающем списке Table properties нужно выбрать Secondary Indexes и нажать кнопку Define. Теперь выберите любое поле и переместите его в список Indexed fields, затем другое поле и снова переместите его в список Indexed fields и т. д.
Если у вас установлен ключ из нескольких полей, то вы должны между вызовами SetKey и GotoKey установить все ключи. Иначе результат может быть с ошибкой.
Есть еще одна процедура, с которой вы должны познакомиться,— GotoNearest. Если GotoKey не находит нужного ключа, то генерируется ошибка. GotoNearest тоже производит поиск ключевого поля, но если4 поле не найдено, то ошибка не генерируется, а ищется ближайший похожий ключ.
Прммечанме""^
На компакт-диске в папке \ Примеры\ Глава 16\ FastFind вы можете увидеть пример этой программы.
Создание псевдонимов
В первом примере работы с таблицами Paradox мы рассмотрели, как с помощью SQL Explorer можно создавать псевдонимы. Но что делать, если в вашей программе используются псевдонимы, а на компьютере пользователя их нет? Напрашивается ответ — создать их. Теперь представьте себе, что пользователь живет в другом городе и абсолютно ничего не понимает в компьютере. В этом случае вы не сможете объяснить ему процесс создания псевдонимов по телефону, а из этой ситуации нужно как-то находить выход. А выход простой — создать псевдонимы программно. Пускай ваша программа следит за наличием на компьютере пользователя псевдонимов.
Давайте напишем маленькую программу, которая будет создавать какой- нибудь произвольный псевдоним для таблиц. Запустите Delphi и создайте новый проект.
Установите на форму компонент TSession с вкладки BDE. Если вы работаете с базой данных, то этот объект всегда создается автоматически без вашего ведома. В нем хранится низкоуровневая информация о BDE, драйверах и
многом другом.
Если вы хотите изменить какие-то значения, указанные в этом объекте по умолчанию, то вам необходимо:
Поставить этот компонент на форму.
Указать любое имя в его свойстве SessionName.
Всем компонентам ттаЫе и TQuery, для которых будут действовать установленные вами значения, в поле SessionName нужно указать имя объекта TSession.
Таким образом вы сами создаете сессию.
Для нашего примера достаточно первых двух действий, потому что у нас не будет компонентов ттаЫе или ТQuery. Посмотрите на рис. L6.8 и сконструируйте форму, подобную той, которая показана на этом рисунке. На форме всего лишь две кнопки, один компонент TListBox и один компонент TSession. Вы можете расположить их по-другому, главное, чтобы они присутствовали, и вам удобно было с ними работать.
Рис. 16.8. Форма будущей программы
Создайте обработчик события Onshow для главной формы и напишите в нем следующий код:
procedure TForml. ForrnShovz (Sender: TObj ect);
var
str:TStrings;
begin
Str:=TStringList.Create;
Sessionl.GetAliasNames(Str);
ListBoxl.Iterns.Assign(Str);
Str.Free;
end;
Здесь мы получаем список всех доступных в системе псевдонимов и вставляем этот список в компонент TListBox. Разберем код построчно.
Сначала объявляется переменная str типа TStrings.
Функция GetAliasNames компонента TSession возвращает все доступные системе псевдонимы. В качестве параметра передается указатель на объект типа TStrings, куца запишется весь список.
Оператор ListBoxl. items .Assign (str) копирует список в элементы List- Boxl. Последняя строка кода освобождает переменную str.
Теперь создадим новый псевдоним стандартного типа. К этому типу относятся таблицы Paradox и DBF. Мы создаем псевдоним по нажатии первой кнопки. Соответствующая процедура будет выглядеть следующим образом:
procedure TForml.ButtcnlClick(Sender: TObject); var
str:TStrings;
begin
Sessionl.AddStandardAlias ('VROnline', 'c: \ ', ' Paradox');
Str:=TStringList.Create;
Sessionl.GetAliasNames(Str);
ListBoxl. Items .Assign (Str);
Str.Free; end;
Создание происходит с помощью функции AddStandardAlias компонента TSession. В качестве первого параметра вы должны указать имя нового псевдонима. Второй — путь к базам данных, которые будут связаны с псевдонимом. Третий — драйвер, который будет использоваться по умолчанию.
После добавления мы снова получаем уже обновленный список доступных псевдонимов и копируем его в компонент TListBox.
Теперь создадим псевдоним нестандартного типа. Для примера будет использоваться псевдоним базы данных Interbase. Эти базы данных в книге описываться не будут, но для примера мы рассмотрим, как создаются такие псевдонимы. Пусть он будет создаваться по нажатии второй кнопки (листинг 16.1).
Листинг 16.1. Создание псевдонима Interbase
procedure TForml.Button2Click(Sender: TObject); var
L: TStringList;
Xml Table, и перед вами откроется стандартное окно сохранения файла. Введите имя exapl и нажмите кнопку Сохранить.
Все, таблица готова. Теперь можно с ней работать абсолютно так же, как и с ADO- или BDE-таблицами. Дважды щелкните по компоненту ClientDataSet, чтобы открыть редактор полей. Здесь добавьте все поля и дайте им нормальные русские заголовки. Кстати, ключевое поле можно спрятать.
Теперь установите на главную форму программы сетку DBGrid и свяжите ее с нашей таблицей. Программу можно запустить и попробовать с ней работать.
Здесь следует сделать одно замечание. Таблицы XML по умолчанию растут достаточно быстро. Эго связано с тем, что в них сохраняется журнал изменений. С одной стороны постоянный рост файлов очень быстро поглощает пространство на диске, а с другой, благодаря этому журналу вы можете в любой момент отменить последние действия. Для этого нужно вызвать метод UndoLastChange компонента ClientDataSet. У ЭТОГО метода есть ОДИН параметр — булево значение. Если он равен true, то текущий курсор перебежит на строку, изменения которой были отменены, иначе курсор останется на месте.
Чтобы очистить журнал изменений, вызовите метод MergeChangeLog, а если хотите, чтобы журнал вообще не велся, то присвойте свойству Log changes значение false.
Вот и все, что надо было сказать про ХМL-таблицы. В остальном работа с ними ничем не отличается от работы с другими базами данных. Все те же свойства и те же методы.
На компакт-диске в папке \ Примеры \ Глава 16\XML вы можете увидеть пример этой программы.
Теория клиент-серверных баз данных
Когда с вашей базой данных работает только один пользователь, то тут практически не возникает проблем. Проблемы начинают возникать, когда нужно, чтобы к данным по сети получило доступ множество пользователей. Суть возникающих проблем можно определить в виде нескольких замечаний.
口 Paradox и DBF ненадежны. Эти форматы очень старые и не рассчитаны на сеть. При одновременном обращении нескольких компьютеров к одной таблице в ней очень часто рушатся индексы.
工 Примечание
В свое время ругали локальные версии программы 1С. Они работали с DBF- таблицами и если использовать передачу их по сети, то индексы рушились чуть ли не ка>кдый день. Чаще всего встроенная утилита восстановления помогала, а иногда приходилось запускать Database Desktop, удалять индексы и создавать их заново.
□ Access — это база данных, которая может хранить множество таблиц и содержит достаточно много возможностей для предотвращения разрушения. В принципе, ее уже можно использовать в сети, достаточно только при подключении указать, что таблица расположена на другом компьютере. Но тут возникает другая проблема — изменения, введенные одним пользователем, не будут видны другим. Для этого случая есть два выхода:
перезапускать программу;
регулярно переактивировать таблицу (свойство Active устанавливать в false, а ПОТОМ В true).
工 Примечание ;
Когда таблица маленькая, клиентов мало и соединение с сервером быстрое, то это еще сносно, но при большой базе данных переподключение может занять много времени и работать с программой будет невозможно.
Вот тут на помощь приходят клиент-серверные базы данных. Что это значит? Сервер — это программа, которая отвечает за хранение данных и обработку SQL-запросов пользователей. Сервер может располагаться как удаленно, так и на одном компьютере с клиентом. Клиент, это любая программа, подключающаяся к серверу для получения доступа к данным.
Если до этого нам нужен был только файл базы данных и драйвер доступа, то теперь для работы приложения нужны драйвер и программа сервера. Как сервер хранит данные, нас уже не волнует, главное, чтобы мы могли получить к нему доступ, а размещение файлов это уже не наша задача.
Сервер заботится о целостности данных, предоставляет разделяемый доступ к данным (изменения одного пользователя будут видны всем после последующего считывания данных), позволяет создать безопасную систему и ограничивать права пользователя на доступ к данным на уровне сервера.
При построении клиент-серверной программы выделяют три уровня:
Сервер — на этом уровне хранятся данные. Здесь может располагаться и логика (код, который надлежащим образом обрабатывает или формирует данные), процедуры и функции.
Бизнес-логика (бизнес-правила) — здесь расположена логика программы, а именно процедуры и функции для управления записями. Логика может
находиться на сервере в виде хранимых (встроенных) процедур на отдельном компьютере (в отдельной программе) или прямо в вашей программе. В первых двух случаях клиент только отображает данные и отсылает команды, а они выполняются или на уровне бизнес-логики или на уровне сервера.
Клиент — отображение данных. Здесь могут располагаться и логика, и процедуры, и функции.
Где же размещать логику приложения? Если у вас в сети с базой данных работает множество клиентов, то для этого можно использовать сервер или отдельное приложение. В этом случае логика централизована и при изменении каких-либо правил вам не надо обновлять программное обеспечение на всех клиентах. Достаточно обновить сервер или единственное приложение.
Клиент отправляет серверу SQL-запросы, а тот возвращает данные, соответствующие критерию выбора. Эго очень сильно уменьшает трафик, потому что нам не приходится перекачивать всю таблицу по сети. В сетевой модели каждый пользователь получает копию базы данных с сервера и загружает сеть излишней передачей данных.
Несмотря на изначальную ориентацию на запросы, в Delphi вы также можете использовать компоненты ттаЫе и TADOTable, потому что они могут работать с данными через запросы. Если ваша таблица на сервере не слишком большая, то никто не запрещает вам использовать то, что вы больше предпочитаете.
(Совет )
Если же таблицы большие, то лучше использовать TQuery или TADOQuery, чтобы уменьшить количество данных, передаваемых клиенту, и получать только необходимое.
Пример работы с SQL Server
На компакт-диске к книге в папке \ Документация \ SQL Server вы можете найти начальную информацию по установке SQL Server 2000, а также по созданию, изменению и удалению баз данных. Если вы ни разу не работали с этим сервером баз данных, то советую ознакомиться с этими документами. Сейчас мы поговорим о том, как работать с сервером.
Отдельный пример описывать не имеет смысла, потому что для подключения к базе данных и таблицам нужно использовать те же компоненты ADO, только выбрать драйвер для подключения к SQL Server (рис. L6.9).
После выбора драйвера и нажатия кнопки Next, вы переходите на вкладку настройки подключения (рис. 16.10). Здесь в первой строке нужно указать
имя экземпляра SQL Server. Если он установлен на этом же компьютере, это поле можно оставить пустым. Тогда программа подключится к экземпляру по умолчанию локального сервера.
zlzlData Link Properties
zl
zl
Provider j Connection | Advanced | All
Select flie data you want to connect to:
OLEO9自5期寸
Med'aCatalogDB OLE DB Provider M ediaCatalogM ergedD В OLE DB Provider M ediaCatalogV/ebD В OLE DB Provider Microsoft Jet 4 0 OLE DB Provider
Microsoft OLE DB Provider For Data Mining Services Microsoft OLE DB Provider for DTS Packages Microsoft OLE DB Provider lor Indexing Service Microsoft OLE DB Provider for Inteinet Publishing Microsoft OLE DB Provider for Microsoft Search Microsoft OLE DB Provider for ODBC Drivers Microsoft OLE DB Provider for OLAP Services 8.0 Microsoft OLE DB Provider for Oracle
E DB Provider fi 1SQL5,5
Microsoft OLE DB Simple Provider
MSDataShape
SQLServer Replication OLE DB ProviderforDTS ПоставщикОЬЕ DB для служб каталогов
Next»
UKОтменаUKОтменаСправкаСправка
UKОтмена
UKОтмена
Справка
Справка
Рис. 16.9. Окно подключения, выбор драйвера
Чуть ниже идет выбор метода аутентификации (метод, по которому будут проверяться права доступа к серверу). У вас возможны два варианта:
О Use Windows NT integrated security (Использовать безопасность, интегрированную в Windows NT) — в этом случае будет использоваться локальное имя и пароль, под которым вы вошли в Windows;
О Use a specific user name and password (Использовать указанные имя и пароль) —в этом случае вам надо указать имя пользователя и пароль.
И последнее, что вам надо сделать, это указать базу данных, к которой вы хотите подключиться. Если вы указали предыдущие параметры правильно, то в выпадающем списке будут перечислены все доступные на указанном сервере базы. Выберите необходимую и можете нажимать кнопку ОК.
Рис. 16.10. Окно настройки подалючения
Для подключения к MS SQL Server вам надо иметь необходимые права. Чтобы дать определенному пользователю такие права, можно запустить SQL Server Enterprise Manager. Это программная оснастка, которая позволяет управлять сервером. Ее главное окно можно увидеть на рис. 6.11.
Слева, в дереве структуры, нужно выбрать пункты Console Root | Microsoft SQL Server | Имя сервера | Security | Logins. Здесь перечислены все пользователи, которые имеют право доступа к серверу. На рис. 6.11 вы можете увидеть, что имеются два пользователя с такими правами: Администратор и sa. Вторая учетная запись создается автоматически при установке и на реальной базе данных ее необходимо удалить, потому что у нее права администратора и любой хакер может подобрать под нее пароль. Для тестирования запись можно оставить, но нужно хотя бы поменять пароль, потому что по умолчанию он пустой и это сильно снижает безопасность системы.
Чтобы добавить нового пользователя, нужно щелкнуть правой кнопкой в центре окна и выбрать во всплывающем меню пункт New User. Перед вами откроется окно настройки пользователя, в котором надо указать имя, права доступа и базу данных.
Рис. 16.11. MS SQL Server Enterprise Manager
Рис. 16.12. Окно добавление пользователя
Более подробную информацию о настройке и работе с SQL Server смотрите в специализированной литературе. Эго выходит за рамки данной книги, потому что программист — не администратор, и вам необходимо знать только начальные сведения.
Теперь, когда произведена настройка подключения, можете обращаться к таблицам сервера так же, как это делали с Access. Никаких отличий нет, потому что надстройка ADO скрывает сетевое происхождение базы, и вы даже не ощутите разницы от месторасположения сервера (на локальном компьютере или на удаленном).
В заключение учтите несколько советов, которые вам помогут при написании программ для работы с SQL-сервером.
口 Если нужно получить все данные с сервера, то можете использовать компонент TADOTable. Он реализован очень Хорошо и обрабатывает данные достаточно эффективно. Но не используйте для таблиц фильтры. Если вам нужны только определенные данные — используйте SQL-запросы, они работают на много быстрее.
口 Для добавления данных на сервер можно использовать стандартные средства. Если позволяет специфика программы, лучше использовать запросы.
口 Обновление данных одной строки можно делать через компонент, но если вам нужно отредактировать определенным образом набор данных, то не надо путешествовать по всей таблице и программно обновлять каждую строку. В случае с сервером это займет слишком много времени и ресурсов, поэтому желательно использовать опять запросы.
П Для связи с поисковыми полями не используйте TADOQuery. Лучше, когда поиск поискового поля происходит по TADOTabie. В этом случае запросы наоборот понизят производительность.
П Стратегия отображения данных должна быть следующая. Выбор данных происходит с помощью запроса и TADOQuery. Если внешний вид данных надо улучшить и использовать поисковые поля, то для хранения справочника таких полей используйте таблицы.
Вообще, для программирования клиент-серверных приложений очень важно хорошо разобраться с языком SQL, потому что он может повысить скорость работы вашего приложения в несколько раз. Остальная оптимизация работы приложения зависит от ее специфики.
Теперь рассмотрим маленький пример работы с сервером. Создайте новое приложение и поместите на форму два компонента TDataSource и TADOQuery для отправки запросов на сервер. У DataSource в свойстве DataSet укажите наш компонент ADOQueryl. Теперь дважды щелкните по свойству connectionstring у компонента запросов и установите соединение с серве-
ром. В качестве базы данных будем использовать Northwind^ которая устанавливается по умолчанию и используется для тестирования или обучения.
Теперь дважды щелкните по свойству SQL и введите следующий запрос:
SEbHCT *
FROM Customers
WHERE CompanyName LIKE :cn
В первой строке запроса мы запрашиваем все колонки. Во второй строчке указываем секцию FROM, чтобы показать, откуда надо получить данные. В данном случае нас интересует таблица customers. В последней строке указывается условие. В данном случае поле CompanyName должно соответствовать значению : сп. Что это за значение? Любое слово, начинающееся с двоеточия, воспринимается как переменная. Теперь мы в своей программе должны только назначить ей значение, и можно выполнять запрос.
Когда вы работаете с серверами баз данных, настоятельно рекомендуется использовать переменные для указания изменяющихся значений. Для понимания причины этого нужно знать, как сервер выполняет запрос. Это происходит примерно так (учтите, что разные серверы делают это по- разному, но принцип везде одинаков):
разбор запроса;
оптимизация и генерирование плана выполнения;
выполнение и возврат результата;
П план выполнения помещается в кэш.
При выполнении этого же запроса второй раз сервер пропускает разбор и оптимизацию и сразу выполняет запрос по определенному плану, который был помещен в кэш. Поэтому в любом случае второе выполнение будет быстрее.
Посмотрите на следующие два запроса:
SELECT * FROM Customers
WHERE CompanyName LIKE 'АвтоВАЗ*
и
SELECT * FROM Customers
WHERE CompanyName LIKE * ВимВильДан'
\
На первый взгляд запросы одинаковые и отличается только значение поиска. Но сервер об этом не знает, и поэтому будет дважды выполнять разбор и оптимизацию. Чтобы он этого не делал, используйте параметры.
Дважды щелкните по свойству Parameters компонента ADO Query, и перед вами откроется окно настройки параметров. В этом окне будет только один
с именем сп. Как видите, Delphi автоматически определил имя параметра, проанализировав наш запрос. Выделите этот параметр и в объектном инспекторе у свойства value укажите % (знак процента). Если вы прочитали описание языка запросов SQL на диске к книге, то вы должны уже понимать смысл этого знака. Он указывает на то, что могут использоваться любые символы в любом количестве. В данном случае если выполнить запрос со значением по умолчанию, то мы получим все строки.
Вернемся к нашему примеру. Сделайте главную форму, как на рисунке 16.13. Здесь у нас строка для ввода названия компании, которую надо найти, и кнопка, по нажатии которой буцет происходить поиск.
Рис. 16.13. Форма будущей программы
Теперь создайте обработчик события Onclick для кнопки поиска и напишите в нем следующий код:
procedure TForml.ButtonlClick(Sender: TObject);
begin
ADOQueryl. Active:=false;
ADOQueryl. Parameters. ParaiuByName (* cn') .Value : =SearchEdit. Text+1 % *;
ADOQueryl .Active: =true ;
end;
Здесь мы закрываем запрос, потом меняем значение параметра и снова открываем, чтобы выполнить. Попробуйте теперь запустить пример и набрать в строке поиска А% без кавычек. Нажмите кнопку поиска, и вы должны увидеть все компании, названия которых начинаются на букву А. Всю эту информацию можно изменять, редактировать и удалять точно так же, как мы это делали и раньше.
П^мм^чанм£ ;
На компакт-диске в папке \ Примеры \ Глава 16\ SQL Server вы можете увидеть пример этой программы.
Многоуровневые приложения для баз данных
Все программы, которые были рассмотрены в этой главе, использовали одноуровневую или двухуровневую логику. Когда говорят об одном уровне, то это означает, что данные, логика работы с ними и программа для доступа к ним находятся на одном компьютере (локальные таблицы). В двухуровневом представлении разделяют базу данных как отдельную единицу, а программу для работы с ними как другую. В этом случае говорят о клиент-серверной архитектуре. Логика доступа к данным может быть реализована как на сервере, так и на клиенте, а может разделяться между двумя уровнями.
Многоуровневая система подразумевает три отдельных уровня:
База данных. Здесь хранятся данные, и если позволяет сервер баз данных, то и логика доступа в виде программ просмотра данных (view), процедур и функций.
Логика приложения. Это сервер приложений, тде располагается вся логика доступа к данным и их обработка. Фирма Borland называет этот уровень —бизнес-логика.
Представление данных. На этом уровне располагается программа, через которую конечный пользователь просматривает данные из базы и изменяет их.
Рассмотрим простейший и самый распространенный пример трехуровневой архитектуры. Допустим, что у вас есть база данных, и вы хотите, чтобы ваши пользователи могли получить к ней доступ через Интернет. Классическое решение будет таким (рассмотрим уровни в обратном порядке):
Клиент использует браузер, например Internet Explorer. С помощью его он посылает запросы серверу, а затем получает обратно данные. Это классический уровень представления, потому что на клиенте (в браузере) не будет никакой логики, только отображение данных.
Так как сервер базы данных не может получать запросы от клиента, это должна делать наша программа, которая будет работать на WEB-сервере. Она будет получать запросы от клиента и в зависимости от потребностей запрашивать данные у сервера баз данных и после этого возвращать их клиенту. Именно в этой программе располагается логика того, как получить данные и как их вернуть. На клиенте они только просматриваются, и в браузер никто не будет встраивать дополнительные модули для реализации этой логики. Это будет классическим уровнем логики приложений.
Сервер баз данных получает запросы от уровня логики приложений и возвращает результат. Это уже уровень базы данных.
В чистом виде такой архитектуры не существует. Хоть какая-то логика приложений встраивается как на уровень представления, так и на уровень базы данных. Поэтому в настоящее время доминирует смешанная модель.
Все уровни могут находиться как на одном компьютере, так и на отдельном. На это ограничений нет и тут вы должны действовать по ситуации. Если сервер базы данных позволяет (не сильно загружен и есть возможность) установить логику приложений, то в этом случае часто экономят и хранят оба уровня на одном сервере. Но это не всегда дает очевидное преимущество из- за больших объемов исполняемых программ.
工—примечание ;
В одной крупной фирме программисты написали программу ведения склада в вцце двух уровней "клиеьгг-сервер". Сложность заключалась в том, что фирма большая и программа развивалась достаточно большими темпами. Через некоторое время исполняемый файл был размером 50 Мбайт. Вдумайтесь в эту цифру. В этом файле была реализована вся логика работы с базой данных и представления. Каждую неделю выходила новая версия файла, и его распространяли по всем компьютерам. Администраторам приходилось в срочном порядке менять на сотнях компьютеров один и тот же файл. А теперь представьте себе, что у фирмы есть несколько офисов. Такая удаленность усложняла обновление, потому что приходилось перекачивать по Интернету большой файл, чтобы все филиалы работали в единой программе. Это еще не самое страшное. Теперь представьте ситуацию, когда утром приходит менеджер и замечает, что в обновленной версии не работает необходимая ему функция. Чтобы не встала работа служащих фирмы, программисты должны в срочном порядке исправлять ошибку и снова устанавливать обновление на сотнях компьютеров.
Эта проблема решается достато/чно просто, если использовать многоуровневую архитектуру. Нужно отделять логику от представления, чтобы не приходилось обновлять каждый раз абсолютно все компьютеры. Достаточно обновить сервер приложений и работа возобновляется в кротчайшие сроки.
Саму логику можно разделить по нескольким серверам. Логику складского учета расположить в одном сервере приложений, а логику бухгалтерского
учета на другом. Это немного усложнит тестирование, но размер файлов будет меньше и удобнее в разработке.
Когда вы выбираете самую простую модель при разработке программы, то вы усложняете себе жизнь во время сопровождения и развития.
Реализация сервера бизнес-логики
Уровень данных нам реализовывать не придется. Туг уже работает достаточно много фирм, и они реализовали сравнительно много серверов баз данных. Нам же остается использовать преимущества этих разработок.
Для реализации остальных уровней фирма Borland предоставила очень удобную технологию MIDAS (Middie-tier Distributed Application Servers или средства разработки приложений среднего уровня). Эта технология упрощает создание уровня логики и доступа к ней из уровня представления.
Создайте новое приложение в Delphi. Главное окно нас не особо интересует, поэтому его оформлять не будем. Теперь создадим модуль Remote Data Module (удаленный модуль данных). Для этого нужно выбрать File | New | Other и в появившемся окне выбора типа создаваемого модуля выбрать Remote Data Module на вкладке Multitier. Перед вами откроется окно, показанное на рис. 16.14.
Рис. 16.14. Окно настроек удаленного модуля данных
В первом поле CoClass Name вы должны ввести имя класса. Оно может быть любым, например MyRDM.
В списке Instancing вы можете выбрать одно из следующих ниже значений.
Multiple Instance — все клиенты подключаются к одному экземпляру сервера. Это значит, что доступ будет последовательный, и если один клиент работает с сервером, то остальные ждут его освобождения.
Single instance —для каждого клиента заводится собственный экземпляр сервера. Таким образом, обеспечивается параллельный доступ, но расходуется слишком много ресурсов сервера, если с программой будет работать слишком много пользователей.
Internal — такой сервер не может быть использован из внешних приложений.
Модель потоков (Threading Model) также может принимать ряд значений.
Single (Одиночная) — такой сервер может выполнять одновременно только один запрос. Если у вас программа однопользовательская или вы уверены, что обращения пользователей будут достаточно короткими, то можно воспользоваться этим методом, потому что тут не надо заботиться о разделении. В каждый момент времени с программой работает только один пользователь. Но если один пользователь начал создавать какой-то отчет, который будет формироваться несколько часов, то остальные будут ждать все это время.
Apartment (Разделяемая) — это значение используется по умолчанию и вместе с Multiple Instance максимально эффективно. Все клиенты используют один экземпляр сервера, но при этом не будут мешать друг другу, потому что каждый будет использовать собственный поток. Такая модель гарантирует, что данные из модуля Remote Data Module будут находиться в целостности и сохранности, потому что ни один клиент не сможет воздействовать на переменные другого. Безопасность остальных переменных (глобальные или из других модулей) вы должны обеспечивать сами с помощью методов синхронизации. О потоках мы будем говорить позже. Такая модель рекомендуется при доступе к данным через BDE.
Free (Свободная) — в этом случае клиент может передавать серверу несколько запросов, но безопасность данных модуля и глобальных переменных обеспечиваете уже вы. Эту модель лучше использовать с компонентами ADO.
Both (Смешанная) — обладает теми же преимуществами, что и модель Free, но при этом обеспечивается автоматическая сериализация обратных вызовов.
口 Neutral (Нейтральная) — несколько клиентов могут обращаться к разным потокам в одно и то же время, а технология СОМ гарантирует, что конфликтов не будет. Эту модель нельзя использовать с объектами, которые имеют пользовательский интерфейс. Она также доступна только при использовании СОМ+, иначе модель автоматически переводится в Apartment.
Выберите параметры, которые вас больше устраивают, и нажмите ОК В примере будут использоваться компоненты ADO для доступа к SQL Server, поэтому надо выбрать Multiple Instance в сочетании с потоками Free.
Теперь в вашем проекте появились еще два модуля. Первый имеет имя, начинающееся с имени проекта, и в конце добавлен префикс tlb. Его генерирует Delphi, и мы в нем ничего изменять не будем. Пускай все остается как есть. Для понимания его содержимого нам необходимы знания СОМ, а это пока лишнее.
Второй файл будет пока без имени, и вы можете его сохранить под любым именем, которое вас устраивает. В файле хранится удаленный модуль (наподобие Data Module, который уже использовали) с именем, которое вы указали в мастере.
В нем вы можете уже увидеть одну процедуру (листинг 16.2). Она очень важна, потому что вызывается каждый раз, когда нужно зарегистрировать или отменить регистрацию сервера в системе. Для примера вы можете оставить все, как есть, но вы должны знать, что процедуру можно использовать для обеспечения безопасности доступа к серверу. Мы эту тему не будем рассматривать подробно, потому что она выходит за рамки книги.
Листинг 16.2. Регистграция сервера в системе
class procedure TTestRDM.UpdateRegistry (Register : Boolean; const Class ID, ProgID: string);
begin
if Register then
begin
iriherited UpdateRegistry (Register f Class ID, ProgID);
Enabl eSo eke tTr anspoXHassID);
EnableWebTransport (ClassID);
end else
begin
DisableSocketTransport(ClassID);
DisableWebTransport(ClassID);
iiiherited UpdateRegistry(Register, ClassID, ProgID); end;
end;
Давайте поместим в модуль три компонента: ADOconnection, ADOQuery и DataSetProvider (с вкладки Data Access). Вид формы вы можете увидеть на рис. 16.15. С первыми двумя компонентами вы уже знакомы и они не должны вызывать проблем. А вот третий — новый.
Раньше, чтобы отобразить данные, мы использовали компонент Datasource. Он привязывался к запросу или таблице и был как бы промежуточным зве-
ном между данными и компонентами для отображения и редактирования. В данном случае у нас компоненты будут располагаться в другой программе и здесь промежуточным звеном как раз и будет выступать TD ata Set Provider, который соединяет данные между клиентом и сервером бизнес-логики. Укажите в свойстве DataSet этого компонента компонент нашего запроса.
Рис. 16.15. Внешний вид удаленного модуля данных
Единственное, что мы еще изменим, это свойство ResolveToDataSet. Если у него установлено значение true, то все изменения будут кэшироваться в наборе данных DataSet, иначе изменения будут проходить напрямую в сервере.
Теперь настройте компонент ADOConnection так, чтобы он указывал на какую-то базу данных. Здесь настроено соединение с сервером MS SQL и базой данных Northwind. В компоненте ADO Query укажите ваше соединение в свойстве Connection, а в качестве запроса (свойство SQL) укажите следующий код: SELECT * FROM Customers
Сделайте запрос активным и можно компилировать проект. Запустите приложение, чтобы сервер зарегистрировался в системе. После этого его можно закрыть. Вообще-то для регистрации можно указать при запуске в командной строке параметр /regserver, в этом случае произойдет только регистрация, а приложение не запустится. Чтобы удалить из системы регистрационную информацию, при старте укажите параметр /unregserver.
工 12PsivieMaHne :
На компакт-диске в папке \ Примеры \ Глава 16 \ Midas\ RDM вы можете увидеть пример этой программы.
Клиент для бизнес-логики
Для соединения с сервером нам понадобится пустое приложение. Первое, что необходимо, — это определить, как мы будем соединяться с бизнес-ло- гикой. На вкладке Data Snap есть компоненты, которые рассмотрены ниже.
DCOMConnection — используется технология DCOM, которая обеспечивает основные методы для аутентификации. Очень хорошо подходит для случая, когда доступ происходит внутри одного домена. Если в качестве клиента будет выступать компьютер с ОС Windows 95, то придется устанавливать на него DCOM95, остальные версии Windows могут работать без дополнительных установок программ. Если между клиентом и сервером стоит брандмауэр (программный или аппаратный комплекс зашиты сети), то настройка будет достаточно сложной, потому что используется несколько портов.
П SocketConnection — здесь работа через брандмауэр упрощается, потому что будет использоваться только один порт. Для обеспечения соединения используется WinSock второй версии, поэтому пользователи ОС Windows 9х должны установить соответствующее обновление. На сервере также необходимо запустить программу ScktSrv^ которая есть в директории Bin той папки, ще установлен сервер.
Есть еще несколько возможностей, но мы их рассматривать не будем. Для корректной работы большинства компонентов необходима настройка протокола TCP/IP.
Для примера воспользуемся соединением по DCOM, поэтому установите на форму соответствующий компонент. Первое, что необходимо указать у сервера, —имя компьютера, на котором зарегистрирована программа с нашей бизнес-логикой. Пусть в нашем примере все находится на одном компьютере с адресом 127.0.0.1.
Теперь щелкните по вьшадающему списку в свойстве ServerName. Если компьютер был указан верно и сервер зарегистрирован, то вы должны увидеть в списке имя программы с бизнес-логикой. Имя будет состоять из двух частей — имя проекта вдюс имя модуля (Remote Data Module). В нашем случае это RDMSampleProject. TestRDM. Если все указано верно, то в свойстве serverGuid должен появиться уникальный номер сервера.
Теперь поместим на форму два компонента ClientDataSet с вкладки Data Access. Этот компонент применяется для связи с компонентом TDataSetProvider, который используется при написании бизнес-логики сервера. Этот компонент будет связываться с таблицей и предоставлять нам данные с сервера. На клиенте работа ClientDataSet будет схожа с работой компонентов таблиц или запросов.
Установите следующие свойства у обоих компонентов ClientDataSet:
口 Remote Serve г — здесь нужно указать наше соединение (компонент DCOMConnectionl).
ProviderName — здесь в выпадающем списке вы можете выбрать имя компонента DataSetProvider с сервера, данные которого нужны. Для обоих компонентов указываем единственного провайдера (например, TestDataSetProvider).
Теперь для каждого компонента ClientDataSet нужно установить по DataSource, чтобы мы могли для отображения и редактирования данных использовать компоненты Borland. У каждого из них укажите в свойстве DataSet своего клиента.
Для отображения информации будем использовать компонент DBGrid. Поставьте два экземпляра и свяжите их с компонентами DataSource. На форме (рис. 16.16) нам еще понадобится 6 кнопок с названиями:
口 Подключиться;口 Восстановить строку;
Просмотреть изменения;口 Точка сохранения;
П Отмена действия;□ Сохранить.
Рис. 16.16. Форма будущей программы
Для события Onclick кнопки Подключиться пишем следующий код:
prbcedure TForml.ConnectButtonClick(Sender: TObject);
begin
DCOMConnectionl.Connected:=true;
ClientDataSetl.Active:=true;
ClientDataSet2 .Active: =true;
end;
Сначала мы делаем активным DCOM-соединение, а потом активируем клиентские наборы данных. Уже сейчас вы можете запустить приложение и посмотреть, что произойдет после подключения. Если вы сделаете это, то заметите, что даже если сервер не был запущен, он запускается автоматически.
Для события onclick кнопки Просмотреть изменения пишем следующий код:
procedure TForml.ViewChangesBtnClick(Sender: TObject);
begin
ClientDataSet2.Data:=ClientDataSetl.Delta;
end;
Всю работу с таблицей мы будем производить в первом наборе данных (ClientDataSetl). Все изменения, которые вы делаете в данных, автоматически регистрируются в свойстве Delta в виде набора данных с теми же полями. Чтобы просмотреть изменения, нужно любому другому набору присвоить значение этого свойства в свойство Data.
Запустите программу, подключитесь к набору данных и попробуйте произвести несколько изменений. Теперь нажмите кнопку просмотра, и вы увидите во второй сетке DBGrid все, что вы делали (см. рис. 16.17).
Коща вы отредактировали какую-то строку, то в наборе данных Delta появляются две записи. Первая содержит все данные, которые были до изменения, а во второй заполнены новыми значениями те колонки, которые были изменены. На рисунке это первые две строки в нижней сетке. Изменилось название компании на АвтоВАЗ.
Третья строка сгенерирована. Ксдда удаляете строку, то в набор данных Delta попадает удаляемая строка с данными до удаления. Таким образом вы можете просмотреть все данные, которые были в уничтоженной строке.
Почему можно видеть изменения? Да потому, что они реально происходят не в базе данных, а в наборе данных. Пока что физически в базе данных ничего не происходит. Вы легко можете отменять действия, и только коща редактирование будет закончено, принять все изменения.
Рис. 16.17. Пример работы программы
Теперь добавим возможность отмены последнего действия.
Для события Onclick соответствующей кнопки пишем код:
procedure TForml.CancelActionButtonClick(Sender: TObject);
beg-in
ClientDataSetl.UndoLastChange(true);
end;
Здесь вызывается метод UndoLastChange и указывается один только параметр. Если передаваемый параметр равен true, то курсор должен переместиться на строку, в которой были отменены изменения. Иначе курсор остается неподвижным.
(Совет)
Желательно указывать значение true, чтобы всегда было видно, где и что происходит.
Для события OnClick кнопки Восстановить строку запишем следующий код: procedure TForml.RestoreLineButtonClick(Sender: TObject);
begin
ClientDataSetl.RevertRecord;
end;
Здесь вызывается метод RevertRecord, отменяющий все изменения, которые были сделаны над текущей строкой.
Теперь поставим точку сохранения. Она позволяет добиться логики, которую нам дают транзакции. Для события onclick соответствующей кнопки запишем:
procedure TForml.SavePointButtonClick(Sender: TObject); begin
ClientDataSetl.SavePoint;
end;
Внимание
Метод UndoLastChange портит значение точки сохранения.
По нажатии кнопки Сохранить все внесенные нами изменения будут сохраняться в базе данных. Для этого мы должны выполнить следующий код:
procedure TForml.ApplyApdatesButtonClick(Sender: TObject); begin
ClientDataSet2.ApplyUpdates(0);
end;
Здесь вызывается метод ApplyUpdates, которому в качестве параметра передается количество ошибок, которые нужно игнорировать при сохранении. Такие ошибки часто возникают, когда с сервером работают несколько пользователей. Тут могут быть разногласия, например, один пользователь удалил строку, а другой пытается ее изменить. В большинстве случаев нужно ставить количество игнорируемых ошибок в 0.
工Прщу|ечание
На компакт-диске в папке \ Примеры \ Глава 16 \ Midas \ Client вы можете увидеть пример этой программы.
Язык Delphi предоставляет нам еще много возможностей по управлению МIDAS-приложениями, но описывать их все не имеет смысла. При хорошем знании технического английского языка вы без труда сможете разобраться с остальными методами и свойствами. Если вам нужна более подробная информация, то вам надо обратиться к специализированной литературе по программированию баз данных в Delphi, потому что эта тема обширна, а основная задача книги — научить вас программировать и мыслить так, чтобы вы могли развиваться дальше.
工 Примечание
За дополнительной информацией можете обратиться на сайт www.vr-online.ru, где регулярно рассматриваются различные статьи и документы по программированию.
Глава 17
Потоки
Операционная система Endows является многопоточной. Это значит, что она может выполнять несколько задач одновременно. Почему именно задач, а не программ? Да потому, что одна программа может состоять из нескольких независимых блоков кода, которые тоже могут выполняться одновременно. Каждый такой блок называется потоком.
Ксвда вы запускаете новое приложение, то для него автоматически создается главный поток, в котором и будет выполняться код программы. Но это не значит, что вы ограничены этим потоком. В любой момент вы можете создать дополнительные потоки, которые будут выполняться параллельно главному.
Таким образом, можно добиться многозадачности внутри самой программы. Это очень удобно, когда в вашей программе должны выполняться какие-то долгие расчеты. Допустим, что у вас складская программа, которая должна сформировать большой и сложной отчет. Время формирования будет около часа, и ваша программа будет заниматься только формированием. Но как же тогда работать со складом? Ту тесть два выхода.
П Создать спдельное приложение для формирование отчета и запускать его параллельно основной программе.
П Разделить основную программу на два потока, которые будут выполняться параллельно.
Для компьютера все равно, какой из этих вариантов вы выберете. Многие программисты все делают в одном модуле, а для формирования отчета используют отдельный поток. Таким образом, и программа будет работать, и склад будет получать оперативные данные. Кроме этого, программа будет обрабатывать данные в отдельном потоке.
Теория потоков
Часто в этой книге приводятся примеры на основе таких программ, как Word и Excel, и сейчас рассмотрим пример, основанный на работе этих
программ. Коща вы запускаете Word и набираете текст, то встроенный модуль проверки орфографии автоматически следит за тем, что вы пишете, и подправляет орфографические ошибки. Теперь представьте логику проверки. После нажатия кнопки нужно отобразить на экране необходимую букву, затем проверить ближайшие слова на изменения и правильность их написания. После проверки слов следует проверять все предложение на наличие пропущенных запятых или других знаков препинания.
На словах алгоритм описывается достаточно просто. Но попробуйте представить себе ту большую работу, которую надо проделать после каждого нажатия кнопки. Если бы алгоритм проверки орфографии действительно действовал так, то буквы появлялись бы на экране не чаще 1 в пару секунд.
К счастью, проверка орфографии работает как отдельный процесс. Вы спокойно набираете текст, а проверка идет в отдельном потоке, не мешая вам работать с текстом. При этом практически незаметны задержки и нет никаких неудобств.
Когда вы пишете новую программу, то не надо пытаться установить все функции в отдельные потоки. Каждый поток накладывает на программу дополнительную сложность и неустойчивость, да и отлаживать потоки намного сложнее.
Какой код нужно помещать в отдельный поток? Вот некоторые примеры.
口 Если какие-то функции должны выполняться параллельно основному процессу, то тут деваться некуда и нужно обязательно помещать их в поток.
口 Если какие-то расчеты идут достаточно долго, то многие считают, что их тоже нужно помещать в поток. Просто когда идут такие расчеты, программа блокируется и невозможно нажать кнопку Отаена или что-нибудь подобное. Это неправильное утверждение. Поток тут абсолютно необязателен, потому что можно обойтись и без него. Достаточно внутри расчетов поставить ВЫЗОВ Application. ProcessMessages, И В ЭТОМ месте выполнение расчетов будет прерываться на некоторое время, а программа будет обслуживать другие сообщения, пришедшие от пользователя. Таким образом получится простой эффект многозадачности без использования потока.
口 Код критичен к времени выполнения. Допустим, что ваша программа должна принимать какие-то данные по COM-порту. Как только в порт поступили данные, они должны быть моментально обработаны с минимальной задержкой. Обработку таких ситуаций желательно выносить в спдельный поток, потому что если в момент поступления данных программа занята большими расчетами, то данные могут оказаться, в лучшем случае, необработанными, в худшем — потерянными.
Истинную многозадачность можно получить только на многопроцессорных системах, ще каждый процессор выполняет свою задачу. В домашних ком-
пьютерах в основном ставится только один процессор. Чтобы создать многозадачность на таких процессорах, используют псевдомногозадачность. В этом виде один процессор выполняет сразу несколько задач благодаря быстрым переключениям между ними. Например, процессор может выполнять сразу десять задач, при этом каждой из них выделять по 10 миллисекунд своего рабочего времени. В этом случае процессор будет через определенные промежутки времени переключаться между задачами, и у пользователя будет создаваться иллюзия того, что они выполняются одновременно. Но это общий вид псевдомногозадачности, реально она реализована более сложным образом.
В 32-разрядных версиях Windows используется вытесняющая многозадачность (до этого была согласованная). В такой среде ОС разделяет процессорное время между разными приложениями и потоками на основе вытеснения. Разделение происходит в основном благодаря приоритету потока. У каждого потока есть приоритет, по которому определяется его важность. Чем выше приоритет, тем больше процессорного времени ему выделяется. Потоки с одинаковым приоритетом будут получать одинаковое количество процессорного времени.
У дополнительных потоков приоритет выставляется такой же, как и у главного потока программы, но вы его можете увеличить или уменьшить. Чем выше приоритет потока, тем больше на него отводится процессорного времени.
Допустим, что ваша профамма должна принимать какие-то данные по СОМ- порту и сразу же их обрабатывать. Для этого создаем новый поток и в нем реализуем код получения и обработки данных. Теперь достаточно поднять приоритет потока, чтобы на него при необходимости выделялось больше процессорного времени, и задача решена. Теперь, как только на COM-порт поступают новые данные, поток сразу же обработает их, потому что с более высоким приоритетом он получит больше процессорного времени.
В этой книге потоки будут использоваться достаточно часто в главе о программировании звука. Там мы будем создавать отдельный поток, в котором будут выполняться воспроизведение или запись звука, при этом основная профамма сможет работать без каких-либо ограничений.
Простейший поток
Давайте попробуем написать простейший поток и в процессе создания познакомимся с его возможностями. На практике этот материал усваивается лучше, поэтому не будем больше тратить время на лишние разговоры и посмотрим на потоки в действии.
19 Зак. 942
Создайте новый проект. Установите на форму компонент TRichEdit с вкладки Win32 и один компонент TLabel. Еще понадобится несколько кнопок — одна для запуска потока, другая для его остановки. Посмотрите на рис. 17一 1, ще показана форма будущей программы. У вас должно получиться нечто похожее.
Рис. 17.1. Главная форма нашей программы
Теперь создадим модуль для потока. Для этого выберите пункт меню File | New | Other для открытия окна создания нового модуля. Найдите в этом окне на вкладке New пункт Thread Object. Выделите его и нажмите кнопку ОК. В результате появится окно, показанное на рис. 17.2. В этом окне нужно указать имя создаваемого потока. Назовем поток — TCountObj. Теперь нажмите кнопку ОК, и Delphi создаст модуль-заготовку для нашего будущего потока.
Сохраните весь проект — главную форму под именем Main, поток под именем MyThread.
Рис. 17.2. Задание имени потока
Теперь посмотрим на код созданного модуля потока. Этот код показан в листинге 17.1.
Листинг 17.1. Содержимое модуля потока
unit MyThread;
interface
uses
Classes;
type
TCountCbj 二 class (TThread)
private
{ Private declarat ions •
protected
procedure Execute; override;
end;
implement at ion
{ Iinportant : Methods and properties of objects in VCL can only be used in a method called using- Synchronize, for example,
Synchronize (UpdateCaption);
and UpdateCaption could Look like.
procedure TCountObj .UpdateCaption; begin
Forml. Caption := * Updated in a thread*; end; }
I TCountObj }
procedure TCountObj.Execute;
begin
{ Place thread code here } end;
end.
У объекта есть только одна процедура Execute. В любых потоках эта процедура должна быть переопределена, и в ней должен быть написан собственный код. Это связано с тем, что в объекте TThread эта процедура объявлена как абстрактная (abstract) — пустая. Это значит, что процедуре дали имя, выделили место в памяти, но ее код должен быть написан объектами потомками, т. е. нами.
Метод Execute и есть заготовка для кода потока. То, что мы напишем здесь, будет выполняться параллельно основной задаче. Давайте напишем здесь код, приведенный в листинге 17.2.
Листинг 17.2. Код метода Execute
procedure TCountObj.Execute;
begin
index:=1;
//Запускаем бесконечный счетчик
while index>0 do
begin
Synchronize(UpdateLabel);
Inc (in*dex);
if index> 100000 then index:=0;
//Если поток остановлен, то выйти. if terminated then exit;
end;
end;
Переменную index мы объявим как integer в разделе private объекта потока. Там же мы объявим процедуру UpdateLabel. Эта процедура выглядит так: procedure TCountObj.UpdateLabel;
begin
Forml.Labe11.Caption:=IntToStr(Index);
end;
И последнее, что мы сделаем — подключим главную форму в раздел uses, потому что обращение к ней происходит в коде выше (Forml. Labe 11. Caption) для обновления текста компонента Labell.
В методе Execute запускается цикл while, который будет выполняться, пока переменная index больше нуля. Внутри цикла мы вызываем метод synchronize (о нем чуть позже) и увеличиваем переменную index. Если эта переменная становится больше 100 000, то в index присваивается 0 и расчет прекращается. Таким образом, цикл будет бесконечно выполнять увеличение переменной index от 0 до 100 000.
Последней идет проверка следующего характера, если свойство terminated равно true, то выйти из процедуры. Когда мы выйдем, то работа потока закончится, ПОТОМУ ЧТО ЗаКОНЧИТСЯ КОД Процедуры Execute. СВОЙСТВО terminated станет равным true тогда, когда будет вызван метод Terminate потока.
Теперь о магической функции synchronize. В качестве параметра ей передается процедура updateLabel, которая производит вывод в главную форму. Для чего нужно вставлять процедуру вывода на экран в synchronize? Библиотека VCL имеет один недостаток — она не защищена от потоков. Все пользовательские компоненты разрабатывал ись так, что к ним может получить доступ только один поток. Если главная форма и поток попробуют одновременно вывести что-нибудь в одну и ту же область экрана или компонент, то программа рухнет. Поэтому весь вывод на форму нужно выделять в отдельную процедуру и вызывать эту процедуру с помощью Synchronize.
Если процедура вызвана в методе Synchronize, то к компонентам окна получает доступ только объект, вызвавший метод synchronize. Этот процесс незаметен для пользователя.
Таким образом, если вам нужно вывести какие-то данные из потока на экран главного окна, то делайте это в отдельной процедуре и вызывайте ее с ПОМОЩЬЮ Метода Synchronize.
Поток готов. Возвращаемся к главной форме. В раздел uses (самый первый, который идет после interface) добавим модуль потока MyThread. Почему именно в этот раздел, а не тот, что расположен ниже? Это связано с тем, что в разделе private нам нужно объявить переменную, имеющую тип нашего объекта. Если добавить имя модуля во второй раздел uses, то он должен находиться ниже той части кода, ще нам нужно написать объявление. Именно поэтому добавлять модуль MyThread нужно в первый раздел uses.
В разделе private объявим переменную со типа TCountobj (объект потока).
По нажатии кнопки Запустить напишем такой код:
procedure TForml,ButtonlClick(Sender: TObject);
begin
co:=TCountObj.Create(true);
со. Resume;
со.Priority:=tpLower;
end;
В первой строке кода мы создаем поток со. В качестве параметра может быть true или false. Если false, то поток сразу начинает выполнение, иначе поток создается, но не запускается. Если поток создан незапушен- ным, то для запуска нужно использовать метод Resume, что и происходит во второй строке.
В третьей строке мы устанавливаем приоритет потока поменьше, чтобы он не мешал работе основного потока и выполнялся в фоновом режиме. Если установить приоритет повыше, то основной поток начнет притормаживаться, потому что у них будут одинаковые приоритеты.
Для события onclick кнопки Остановить напишем следующий код:
procedure TForml.ButtcnlClick (Sender: TObj ect);
begin
co.Terminate;
end;
Здесь происходит остановка выполнения потока с помощью вызова метода Terminate объекта потока. После вызова этого метода свойство terminated станет равным true и выполнение процедуры Execute заканчивается.
Попробуйте запустить эту программу, запустить поток (нажатием кнопки Запустить) и набирать текст в RichEdit. Текст будет набираться без проблем, и в это время в компоненте TLabel будет работать счетчик. Если бы вы запустили счетчик без отдельного потока, то не смогли бы набирать текст в RichEdit, потому что все ресурсы программы (основного потока) уходили бы на работу счетчика.
工 Примечание
На компакт-диске в папке \ Примеры \ Глава 17\Thread1 вы можете увидеть пример этой программы.
Дополнительные возможности потоков
Теперь рассмотрим еще некоторые свойства и методы объекта потока, а также несколько замечаний, касающихся работы с ним. В принципе, у объекта не так уж и много свойств и методов. Большую их часть мы уже рассмотрели, но все же здесь необходимо сделать еще несколько методов и
свойств объеБсга TTread, которые помогут вам при написании реальных приложений.
Метод suspend — приостанавли вает поток. Для вызова просто напишите со. Suspend. Чтобы возобновить работу С ЭТОЙ Же ТОЧКИ, НУЖНО Вызвать Resume.
Метод Priority — устанавливает приоритет потока. Например, Priority:=tpldle;
Рассмотрим возможные приоритеты:
tpldie — поток будет работать, только когда процессор простаивает без работы;
tpLowest — самый слабый приоритет;
tp Lower — слабый приоритет;
П tpNormal — нормальный;
tp Higher — ВЫСОКИЙ;
П tpHighest — самый высокий;
tpTimeCritical — критичный (не советую использовать, потому что может разрушить систему).
Свойство suspended — если этот параметр true, то поток находится в паузе.
Свойство Terminated — если true, то поток должен быть остановлен, иначе поток должен продолжать работу.
Метод Terminate — остановить выполнение потока.
Свойство FreeOnTerminate — если равно true, то по завершении выполнения процедуры Execute поток самоуничтожится.
(Совет )
Используйте это свойство, чтобы быть уверенным, что поток корректно удалился из памяти.
Давайте посмотрим, как будет выглядеть метод Execute из предыдущего Примера С использованием СВОЙСТВа FreeOnTerminate:
procedure TCountObj.Execute;
begin
FreeOnTerminate:=true;
index:=1;
//Запускаем бесконечный счетчик
while index>0 do
begfn
Synchronize(UpdateLabel);
Inc (index);
if index>100000 then
index:=0;
//Если поток остановлен, то выйти.
if terminated then exit;
end;
end;
В принципе, информации, которая изложена в этой главе, вам будет достаточно для написания собственных потоков. Здесь следует помнить, что у объекта TThread есть еще несколько свойств и методов, но они не так важны и ими профаммисты редко пользуются. Поэтому не будем тратить место в книге на описание оставшихся возможностей, а лучше рассмотрим несколько советов.
С~Совет)
Объекты потоков создаются как полноценные объекты. В основной программе мы создаем в памяти отдельный экземпляр потока и потом работаем с ним. Вы можете создавать по несколько экземпляров одного потока и они будут работать одновременно абсолютно не мешая друг другу. Представим пример программы, копирующей файлы. Вы можете создать поток, который будет копировать файлы из одного места в другое. В основной программе можно создать два экземпляра таких потоков и каждому из них определить копирование разных файлов в разные места. Оба потока будут копировать свои файлы, абсолютно не мешая друг другу.
Подробней о синхронизации
В предыдущем примере использовалась процедура UpdateLabel, в которой на главную форму выводится значение переменной index. Если бы мы программировали главное окно, то вполне логичным было бы создать переменную index локальной для процедуры Execute, а ее значение передавать в UpdateLabei в качестве параметра. В потоках с этим проблема. Чтобы передать какие-то значения в процедуру, которая должна вызываться методом Synchronize, нужно пользоваться переменными объекта.
(Совет)
Нежелательно пробовать передавать параметры в процедуры, которые вызываются методом Synchroni ze.
Но использование синхронизации — не единственный способ обновления параметров окна. Мы можем использовать для этого событийную модель Windows. Каждый раз, когда надо обновить содержимое текста, мы можем посылать окну сообщение SendMessage с указанием значения, которое надо установить. Главное окно буцет получать это сообшение, и компонент сам изменит заголовок. В этом случае мы не обращаемся к главному окну из потока, а только отправляем сообщение, поэтому никаких проблем не буцет.
Итак, функция SendMessage имеет следующие параметры:
Р указатель на окно (компонент), которому нужно послать сообщение;
Р тип сообщения;
口 первый параметр;
口 второй параметр.
Суця по функции, нам нужен компонент, у которого есть свойство Handle. В предыдущем примере был TLabel, у которого нет такого свойства, значит, он нам не подходит. Замените этот компонент на TEdit. Теперь перейдем в поток. Тут в разделе uses нужно добавить два модул兄:windows (здесь объявлена сама функция) и messages (здесь находятся все типы сообщений Windows).
Теперь удалите из потока процедуру UpdateLabel. Больше она не нужна, потому что мы не будем использовать метод synchronize. Ну, и, наконец, подкорректируем метод Execute:
procedure TCountObj.Execute;
begin
index:=1;
while index〉0 do
begin
SendMessage(Forml.Editl.Handle, WM_SETTEXT, 0, Integer(PChar(IntToStr(index))));
Inc(index);
if index>100000 then
index:=0;
if terminated then exit; end; end;
Как видите, теперь вместо метода synchronize генерируется событие на обновления компонента TEdit. В качестве второго параметра мы указываем
тип сообщения wm settext — обновить информацию. Третий параметр равен нулю. В последнем параметре нужно указать значение, которое следует установить. Вот тут есть небольшая сложность. У нас значение представлено в виде целого числа, но нужно превратить его в PChar. Для этого сначала конвертируем переменную index в строку (intToStr), потом приводим ее к типу pchar и тут же указываем размер integer. Сложно? Зато не надо ничего синхронизировать.
~Примечание
На компакт-диске в папке \ Примеры \ Глава 17 \Thread2 вы можете увидеть пример этой программы.
Глава 18
Динамически компонуемые библиотеки
Вы уже наверно много раз слышали выражение 'динамически подключаемые библиотеки**. Пора познакомиться с ними поближе. В этой главе будет дана вся необходимая информация о них. Здесь вам предстоит подробно познакомиться с библиотеками DLL (Dynamic Library Link или динамически подключаемые библиотеки), изучить все их преимущества и недостатки. Здесь мы также напишем и разберем песколько примеров с целью приобретения вами практического опыта в (ipoi рам мнpoiuiHiin.
Главная цель изучения — понять, 口田 и koi ij тжпо использовать DLL-файлы.
Что такое DLL
Программисты всех стран уже более 30 лет борются с проблемой многоразового использования написанного кода. Так уж повелось, что 3()—50% кода в простых офисных приложениях схожи между собой или решают одни и те же задачи. Ни один уважающий себя программист не захочет всякий раз заново писать один и тот же код. Как хорошо, когда можно многократно использовать уже написанный и отлаженный фрагмент программы. Путей решения данной задачи несколько. Рассмотрим каждый из них в отдельности, а также возникающие при этом проблемы.
Решение № 1
Самым первым решением этой задачи стало использование модульного программирования. Вы пишете определенную часть программы, оформляете ее в виде модуля, а потом просто используете этот модуль в своих программах. Все прекрасно и удобно, а главное, что все довольны. Теперь не надо каждый раз выдумывать велосипед. Просто добавили к своей программе определенный модуль и без проблем используйте когда-то написанный вами или кем-то другим программный код.
Казалось, что это самое простое и самое эффеБсгивное решение. Все было прекрасно, пока не появилась многозадачность. Вот тут программисты и простые пользователи заметили, что эффективность программ стала снижаться.
Проблема № 1
Давайте представим ситуацию, когда один программист написал прекрасный модуль размером в 1 Мбайт. Другой решил воспользоваться его (модул兄)возможностями и подключил к своей программе. Модуль и программа слились в одно целое. Вроде все нормально. Но программа и модуль слились в одно целое. Это значит, что размер результирующей программы увеличился на размер модул兄,т. е. на 1 Мбайт.
А теперь представьте, что третий программист написал утилиту с использованием того же модул兄.Его программа тоже увеличилась на 1 Мбайт. Получается, что на диске два гюльзовател兄 хранят две программы, в которых по 1 Мбайт одного и того же кода. Естественно, такое положение дел никому не нужно.
工 Пр-меча^не
Конечно же, на счет модуля в 1 Мбайт здесь немного преувеличено. В те времена даже модуль размером в 100 Кбайт тяжело было найти. Но надо учитывать, что и диски тогда были не очень большими по своей емкости. Самым хорошим винчестером считался диск емкостью 20 Мбайт. Это вам не нынешние десятки Гбайт на одной пластине.
Проблема № 2
Пока существовали однозадачные операционные системы, проблема с излишней тратой дискового пространства была единственной. Но как только задумались о многозадачности и в мыслях Билла Гейтса появились идеи создать Windows, сразу возникла другая проблема.
Представьте себе ситуацию, когда вы запускаете две программы (программы имеют одинаковые фрагменты кода) одновременно. При старте любой код грузится в оперативную память и только потом выполняется. Таким образом получается, что обе программы загрузят в память один и тот же код. Вот это уже абсолютно никому не нужно.
Это только в последнее время память подешевела в несколько раз, и теперь лишние 100 Кбайт погоды не сделают.
工 Трммеианме 工
Оперативная память во все времена была и остается сейчас самым дорогостоящим и дефицитным ресурсом вычислительной системы.
А раньше она стоила достаточно дорого и программисты боролись за каждый байт. Но если вы думаете, что установив в свой компьютер 500 Мбайт оперативной памяти, решите проблему, го вы крупно ошибаетесь.
Хотя память и дешевая, программы от этого меньше не станут. Если посмотреть на запросы той же Endows 2000, то сразу понятно, что 500 Мбайт это только капля в море. Самая простая ОС Windows 2000 Professional отнимет от них около 128 Мбайт. Это что же там такое натворили, если Windows 2000 Server просит для нормальной работы минимум 256 Мбайт? А если учесть, что еще недавно материнские платы не поддерживали памяти более 512 мегабайт (это сейчас можно использовать от 2 до 3 Гбайт), то о нормальной одновременной работе Windows 2000 Server, 3D Studio Мах и MPEG4 можно забыть. Они всю память отберут, как термиты за пять секунд.
Решение № 2
И вот тут было найдено вполне солидное решение. Не стыковать модули с основной программой, а сохранять их в отдельный файл, и пусть любая программа загружает их по мере надобности. Так появились библиотеки DLL. Эти библиотеки подключаются к программе динамически. В них можно хранить исполняемый код в виде процедур или функций, ресурсы программы, графику или даже видеоролики.
Теперь программа не увеличивалась на размер модул兄 при компиляции, а просто загружала код из DLL-файла в память, а затем использовала его. Если одна программа уже загрузила DLL, то следующая не будет делать этого. Она воспользуется уже загруженной версией. Таким образом, экономится не только диск, но и оперативная память, которой, как и денег, много не бывает.
Сейчас DLL — это не просто динамически подгружаемая библиотека. Вы, наверно, уже не раз слышали про компоненты ActiveX. Библиотеки в чистом виде не поддерживают объекты. Хотя особо одаренные программисты умудряются вставить в них достаточно сложные объекты. ActiveX можно назвать объектными библиотеками. Хотя они выполнены по технологии СОМ, смысл остается прежний — динамическое разделение кода.
ActiveX также могут быть выполнены в виде OCX- или DLL-файлов. Да оно и понятно, файлы ActiveX используются сейчас достаточно активно и занимают места в несколько раз больше, чем самая большая DLL-библиотека. Так образом, с целью экономии места на диске и в памяти вычислительной системы, необходимо преобразовать ActiveX в динамически подгружаемую библиотеку. Хотя это будет уже не та DLL, но все же работает она по тем же принципам.
У динамических библиотек есть единственный недостаток — на их загрузку тратится лишнее время. Если бы код, находящийся в DLL, был бы скомпо-
но ван с программой, то он грузился бы намного быстрее. Зато если библиотека уже загружена другой программой, то она появляется намного быстрей.
工 Примечание
Не верите? Отложите сейчас книгу и возьмите в руки секундомер. Теперь запустите Word или Excel. Засеките сколько времени будет проходить загрузка. Теперь закройте эту программу и запустите ее снова. Она появится на экране практически моментально. Это потому,, что после выхода из программы DLL- файл не выгружается из памяти. Это происходит только тогда, когда операционной системе не хватает памяти и ни одна из программ не использует в данный момент эту библиотеку. А теперь представьте себе, что такое Word. Представили? Это и текстовый редактор, и проверка орфографии, и построитель диаграмм, редактор формул и много еще других программных возможностей. Что было бы, если все это определить в одном файле? Нет, вы это не можете представить. Это был бы один исполняемый файл размером в 30——50 мегабайт.
При использовании динамических библиотек в исполняемом файле находится только самое основное, а дополнительные возможности подгружаются по мере надобности из DLL-файлов. Например, когда стартует Word, то загружается только модуль текстового редактора. Когда вы выбрали редактор формул или объект WordArt, то Word подгружает из DLL-файла код выбранного объекта и выполняет его. Таким образом, суммарная скорость загрузки уменьшается, причем очень значительно.
Еще одно большое преимущество динамических библиотек — при их использовании код программы разбивается на несколько файлов (зависит от количества DLL-файлов). Допустим, что в одной из функпий, находяшихся в DLL-файле, оказался код с ошибкой. В этом случае не надо обновлять всю программу, а достаточно передать всем полвзовател兄м только этот DLL- файл, и программа получит необходимые обновления.
У динамических библиотек много преимуществ и только два недостатка.
□ Код из DLL-файла выполняется в том же участке памяти, что и основная программа. Поэтому программа и DLL-код используют один и тот же стек данных, что иногда накладывает свои ограничения. Например, DLL- код не может хранить глобальных переменных. Воспринимайте динамические библиотеки просто как набор пропедур и функпий, которые могут хранить только локальные переменные.
□ Изначально динамические библиотеки были процедурными. Хотя сейчас умельцы умудряются использовать их для хранения объектов, но это очень неудобно. Несмотря на это, программы ActiveX (изначально объектные) могут храниться в файлах с расширением dll и выполняют те же задачи, что и классические динамические библиотеки.
Но все же динамические библиотеки получили широкое распространение, и программисты используют их повсеместно, коша надо и когда не надо.
Нельзя быть уверенным, что какой-то код уже больше никогда не понадобится. Всегда нужно рассчитывать на будущее.
В этой главе мы затронем только классические библиотеки DLL, а компоненты ActiveX и технологию СОМ оставим на будущее.
Из чего сделан Windows
Большинство думает, что Windows — это все что, находится в папке с : \ windows, а ее ядро — это win.com. В какой-то степени это так, но не совсем. Ядро ОС Wndows — это простой DLL-файл, а если быть конкретным, то это Kernel32.dll. При старте операционной системы эта библиотека загружается в память в единственном экземпляре. Любая программа может обращаться к содержащемуся в ней коду и использовать его в своих целях. В этой библиотеке расположены API-функции, предназначенные для распределения памяти, и многое другое. Мы эти функции не вызываем напрямую, потому что Delphi прячет этот сложный процесс от нас, но иногда вам может понадобиться воспользоваться ими. Так что помните, если вы выделяете память, то в этот момент используется Kernel32.dll.
За вывод графики в Windows отвечает GD132.DLL, которая также загружается при старте в единственном экземпляре. Все функции для работы с графикой находятся в этой библиотеке. Есть и еще одна библиотека — User32.dll, которая отвечает за создание окон и обработку сообщений. Все эти три библиотеки составляют ядро ОС Windows.
В Windows очень много недостатков, но динамические библиотеки — это достаточно гениальное решение многократно используемого кода.
Графические движки
Любой игрок обязан знать про существование OpenGL. Что это такое? Какой-то пакет программ? Какой-то SDK для создания графики? Ничего подобного, это всего лишь две динамические библиотеки opengl.dll (opengl32.dll) и glu.dll (glu32.dll).
Что такое DirectX? Это графическая библиотека, которая состоит из DirectDraw, Direct Input, Direct Music, Direct Play и т. д. Все это не что иное, как простые динамически подгружаемые библиотеки. DirectDraw это Ddraw.dll, Directlnput это Dinput.dll, DirectMusic это Dmusic.dll и т. д. Хотя DirectX это не простые библиотеки — это библиотеки, созданные на основе технологии СОМ (та же технология, что и ActiveX).
Любые игровые движки (набор функций для облегчения создания игры определенного жанра с определенными возможностями) выполнены в виде динамически загружаемых библиотек, потому что их использование очень простое и удобное для любого программиста.
Давайте подведем итог тому, что уже было сказано.
□ Динамические библиотеки практически ничем не отличаются от ЕХЕ- файлов. Это такой же скомпилированный код, только он не может запускаться самостоятельно, потому что в библиотеке нет точки входа (точки, с которой начинает свое выполнение любая программа).
口 В DLL-файлах хранятся процедуры, функции и различные ресурсы, которые можно вызывать из других программ.
口 Чаще всего динамические библиотеки имеют расширение dll, но можно установить и любое другое расширение или вообше убрать его.
口 Библиотеки имеют свои разновидности, например, драйверы — это тоже динамические библиотеки и им принято давать расширение di*v. Возможно также использование расширения sys для системных файлов. Так что операционная система не накладывает ограничений на расширения динамических библиотек, главное, чтобы вам было понятно и удобно работать.
口 Когда одно приложение загружает библиотеку, то она загружается ее в глобальную память, а потом только проецируется в адресное пространство программы. Это значит, что программа будет видеть функции библиотеки как свои собственные, хотя они расположены совершенно в отдельном адресном пространстве.
О Операционная система Windows гарантирует, что в любой момент будет загружена в память только одна версия DLL-файла. Если две программы обращаются к одной и той же библиотеке, то в памяти будет находиться только одна копия DLL-файла.
Говоря о библиотеках (DLL), мы имели в виду динамические библиотеки. Но существуют и статические варианты библиотек. Чем они отличаются? В принципе, библиотека одна и та же, поэтому термин статической DLL считается неправильным (хотя иногда встречается в статьях).
Библиотеки DLL всегда динамические и создаются они с целью динамической загрузки находящихся в них ресурсов. Но, не смотря на это, многие компиляторы позволяют присоединять код DLL статически. В этом случае при компиляпии программы код или данные, находящиеся в DLL-файле, становятся неотъемлемой частью исполняемого файла. Программа и библиотека становятся как единое целое. Это очень удобно, когда библиотека небольшая или вам необходимо, чтобы программа состояла только из одного запускного файла. Здесь динамическая загрузка не подходит, и надеяться на существование библиотеки, на машине клиента нельзя. Такая ситуация может возникнуть, когда вы хотите показать клиенту демонстрационный файл вашей программы и удобно иметь только один запускной файл, а не множество библиотек.
Простой пример создания DLL
Так как если DLL — отдельный файл, то и создается он в Delphi как отдельный проект. Для создания новой динамической библиотеки нужно выбрать меню File | New | Other. В окне создания нового проекта нужно выбрать на вкладке New пункт DLL Wizard. Выберите этот пункт и нажмите кнопку ОК.
Несмотря на то, что мы выбрали DLL Wizard (слово Wizard говорит о том, что должен запуститься мастер), будет просто создан пустой проект с одним только модулем. Модуль будет содержать следующий текст (без комментариев):
library Project2;
uses
SysUtils, Classes;
{$R *.res}
begin end.
Если открыть Менеджер проектов (меню Mew | Project Manager), то в окне вообще не будет видно ни одного модуля. Это потому что код, который вы видели выше, относится к самой библиотеке. Выберите из меню File пункт Save All, и вам предложат сохранить только один проект и никаких модулей. Сохраните проект под именем FirsDLLProject. Теперь откройте файл проекта FirsDLLPrqject.dpr с помошью блокнота и вы увидите тот же самый код.
Теперь давайте добавим в нашу библиотеку одну функцию с именем Sunm У этой функции будет два параметра в виде целых чисел, а возвращать она будет сумму этих чисел (листинг 18.1).
ЛИСТИНГ 18.1. Функция Summ
library FirsDLLProj ect;
uses
SysUtils, Classes;
function Suiran(XzY: Integer) : Integer; StdCall;
begin
Result :=X+Y;
end;
exports Summ;
{$R *.res}
begin
end.
Внимание
Обратите внимание, что функция объявлена не так, как всегда. В конце строки объявления после типа возвращаемого значения стоит ключевое слово StdCall. Оно говорит о том, что для вызова процедуры нужно использовать стандартный тип вызова.
Ранее говорилось, что все параметры, передаваемые в процедуры и в функции, передаются через стек. Если не указать ключевое слово StdCall, то параметры будут передаваться способом, заложенным фирмой Borland. Этот способ работает быстрее, но он несовместим со стандартными правилами.
(Совет)
Если вы уверены, что к процедуре будут обращаться только программы, скомпилированные компиляторами фирмы Borland, то можете не ставить это ключевое слово. Но если библиотека будет выставлена на всеобщее использование или к ней будут обращаться программы сторонних разработчиков, то желательно ставить StdCall, иначе у программистов на языках Visual C++ или других языках будут проблемы. Желательно сделать для себя правилом—всегда ставить StdCall, потому что способ Borland дает незначительный выигрыш.
В остальном функция ничем не отличается от тех, которые мы уже писали.
После описания функпии идет новое ключевое слово exports. После этого ключевого слова должно идти описание процедур, которые должны быть доступны внешним программам. Если нашу функцию Summ не описать в разделе exports, то мы ее не сможем вызвать из внешней программы.
Теперь откомпилируйте проект (нажмите клавиши <Ctrl>+<F9> или выберите из меню Project пункт Compile FirsDLLProject), чтобы создать нашу динамическую библиотеку.
Внимание
Можете не пытаться запускать проект, потому что это библиотека, и она не может выполняться самостоятельно. Так что, единственное, что вы можете увидеть —ошибку.
Теперь напишем программу, которая будет использовать написанную функцию из динамической библиотеки.
Для этого создайте новый проект простого приложения (File | New | Application). На форму установите только одну кнопку и по событию onclick этой кнопки напишите следующий код:
procedure TForml.ButtonlClick (Sender: TObject);
var
r:Integer;
begin
r:=Suimi(10, 34);
Application.MessageBox (PChar (IntToStr (r) ) ,1 Результат функции Surom1);
end;
В первой строчке вызывается функция Summ с двумя числовыми параметрами. Результат записывается в переменную г. Вторая строка всего лишь выводит окно с результатом.
Если вы попытаетесь сейчас откомпилировать проект, то у вас ничего не выйдет. Компилятор Delphi скажет, что он не знает такой функции Summ Мы должны показать Delphi, что это за функция и где ее искать.
Для начала покажем компилятору, что это за функция.
Для этого в разделе type после описания объекта TForml (нашей главной формы) нужно написать следующую строку:
function Summ(X,Y:Integer):Integer;StdCall;
В принципе, это такое же объявление функции, которое определено в библиотеке, только здесь нет begin и end и самого кода процедуры. По этой строке Delphi узнает, что где-то существует такая функция Summ, у нее есть два параметра, и она должна вызываться стандартным вызовом.
Теперь нужно сказать компилятору, где же искать эту загадочную функцию.
Для этого после слова implementation напишите следующий код: function Summ; external * Fi rs DLLPrcj ect.DLL * name 'Summ';
Здесь написано, что есть такая функция Summ. После точки с запятой стоит ключевое слово external, которое говорит о том, что функция внешняя, не принадлежит программе. После этого слова указывается имя динамической библиотеки, где нужно искать функцию. Далее идет ключевое слово name,
которое означает, что функцию надо искать по имени. После этого ключевого слова указывается точное имя функции в библиотеке.
Вот теперь проект готов и его можно компилировать, запускать и проверять результат.
工 Примечание ;
На компакт-диске в папке \ Примеры \ Глава 18 \ FirstDLL вы можете увидеть пример этой программы.
В нашем примере использовался вызов по имени функции. Когда программе нужно выполнить функцию Summ, то она просматривает все функции динамической библиотеки и ищет функцию с указанным именем. Это очень неэффективно и перед первым вызовом будет ощущаться большая задержка. Чтобы хоть немного ускорить процесс вызова таких, можно использовать индексы. Каждой функции в библиотеке может быть назначен индекс, и ,при вызове можно указывать его.
Давайте скорректируем наш пример, введя в него индексы.
Откройте проект динамической библиотеки FirsD LLProject.dpr. Найдите ключевое слово export и напишите там такой код:
exports Summ index 10;
После имени функпии стоят ключевое слово index и числовой индекс функции.
Этой функции мы дали десятый индекс (вы можете попробовать другое число. Но надо помнить, что индексы и имена должны быть уникальными. Вот несколько примеров: exports
Fund index 10 name * Fun*,
Func2 Insert,
Func3 index 11,
Func4 index 11,//Ошибка, такой индекс уже существует
Fun с 5 name ' Don';
В объявлении последней процедуры явно используется ключевое имя name, чтобы указать экспортной функции новое имя. Теперь внутри библиотеки эта функция реализована как Func5, но внешние приложения должны обращаться к ней по имени Don.
Объявлять можно и так:
exports Fund index 10 name 1 Fun *,
exports Func2 Insert f
exports Func3 index 11,
Перекомпилируйте проект, чтобы изменения вошли в силу (нажмите вша- виши <Ctrl>+<F9>).
Теперь возвращаемся в проект, где мы используем функцию. В разделе implementation корректируем описание нашей функции:
function Summ; external * FirsDLLPro j ect. DLL * index 10;
Теперь вместо ключевого слова name стоит слово index и тот же номер.
Запустите проект и убедитесь, что он работает корректно.
工 Примечание 】
На компакт-диске в папке \ Примеры \ Глава 18 \ IndexName вы можете увидеть пример этой программы.
Замечания
по использованию библиотек
Коцца говорилось, что DLL-файлы нельзя запускать, то это было немного преувеличино. В принципе, библиотеки действительно нельзя запускать, но Delphi может сделать это. Откройте нашу библиотеку и выберите из меню Run пункт Parameters. Перед вами откроется окно, показанное на рис. 18.1.
Рис. 18.1. Окно параметров запуска программы
В области Host Application нужно указать имя приложения, которое умеет загружать библиотеку. Теперь попробуйте запустить проект (клавиша <F9>). Запустится указанная программа, которая использует библиотеку.
Зачем нужен этот способ? Если вы попытались запустить программу, и она показала ошибку в коде, где вызывается функция из динамической библиотеки, то можно попытаться запустить библиотеку таким образом. Если снова произойдет ошибка, то Delphi покажет строку с ошибкой.
Немного позже мы рассмотрим, как можно отлаживать программы, выполнять их по шагам. Тогда вы сможете узнать, что таким образом можно отлаживать и динамические библиотеки. А именно, они могут выполняться в пошаговом режиме (построчно) и вы будете контролировать весь процесс выполнения.
Пока что о библиотеках сказано достаточно много хорошего, но не сказано самого главного. Функции и процедуры из динамической библиотеки не могут прямо влиять на ход основной программы. Это значит, что мы не можем получить доступ к окнам основной программы, изменить какие-то переменные или еще чего-нибудь.
Функции библиотеки — как бы изолированы от всего остального, хотя и выполняются в одном адресном пространстве с основной программой. Они могут использовать только переданные им параметры, а результат работы возвращать в качестве результата работы функции.
Внимание
Имена библиотек пишите полностью, вместе с расширением. Без расширения DLL-библиотека (файл) может быть не найдена в Windows NT/2000/XP, хотя в Windows 98 все будет работать нормально.
Г~Совет)
Обязательно соблюдайте индексы и параметры процедуры, иначе могут возникнуть ошибки. Лучше лишний раз проверить, чем потом долго искать опечатку.
Хранение формы в динамических библиотеках
Теперь рассмотрим, как можно хранить в динамических библиотеках целые окна. Очень удобно, когда редко используемые окна находятся в динамической библиотеке. В этом случае основной файл очень сильно разгружается от лишнего кода.
Еще одно преимущество такого кода — библиотека может использовать для вывода информации на экран свое окно. Как уже говорилось, из DLL-файла
нельзя получить доступ к переменным и данным основной программы. Это значит, что из DLL-файла нельзя ничего вывести в окна основной программы. Но библиотека может создать собственное окно и использовать для вывода необходимых данных именно его.
Создайте новую DLL-библиотеку и сохраните ее под именем Project.DLL. Теперь добавим к нашей библиотеке одну экспортную процедуру showAbout:
library ProjectDLL;
uses
SysUtils, Classes;
{$R *.RES}
exports ShowAbout index 10;
begin end.
Здесь мы добавили к коду, созданному мастером, только одну строку: exports ShowAbout index 10;
У нас будет только одна пропедура showAbout с индексом 10. Эта процедура будет показывать окно О программе.
Теперь щелкните мышью по пункту меню File | New Form, чтобы создать новую форму. Нарисуйте на ней что-нибудь, например, как показано на рис. 18.2).
Рис. 18.2. Окно О программе
Переходите в текст модуля. В разделе var после объявления формы опишите процедуру ShowAbout: var
Forml: TForml;
procedure ShowAbout(Handle: THandle);export;stdcall;
Здесь опять присутствует ключ export и добавлен еще stdcall, указывающий на обязательность использования стандартного вызова процедуры.
Теперь напишем саму функцию после ключевого слова implementation и ключа ($R *. dfm } (листинг 18.2).
Листинг 18.2.Функция отображения окна
procedure Show/\bout (Handle: THandle);
begin
/ /Установить указатель на приложение
Application.Handle : = Handle;
//Создать форму
Forml:= TForml.Create(Application);
//Отобразить
Forml.ShowModal;
//Очистить
Forml.Free;
end;
Эта процедура получает в качестве параметра указатель на главное приложение. В первой строке мы устанавливаем этот указатель в свойство Handle объекта Application. Этот объект хранит настройки всего приложения, и этим присваиванием мы как бы связали оба приложения. А точнее, наша библиотека теперь сможет использовать глобальный указатель основного приложения. Этот указатель нам понадобится при создании модальных окон из библиотеки.
Во второй строке кода мы создаем окно TForml.Create (Application), в результате чего будет возвращен указатель на это окно. Результат мы сохраняем в переменной Forml. Эта переменная объявлена в разделе var проекта.
Следующей строкой мы отображаем созданное нами модальное окно. Как только оно закроется, будет выполнена последняя строка кода этой процедуры, а именно, окно будет уничтожено из памяти и пропедура закончит свое выполнение.
Внимание
В процедурах DLL-библиотек будьте более внимательны к высвобождению памяти. Ошибки в библиотеках переносятся программами более критично, потому что тут основная программа практически бессильна.
Откомпилируйте библиотеку (<Ctrl>+<F9>) и DLL-файл готов. Можно закрывать этот проект (File I Close All) и создавать новое приложение, из ко-
торого мы будем вызывать созданную в библиотеке процедуру (File | New Application).
В новом проекте переходим в редактор кода и объявляем функцию show About (листинг 18.3).
Листинг 18.3. Объявление процедуры ShowAbout
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
procedure ShowAbout(Handle: THandle)stdcall;
type
TForml 二 class (TForm)
Buttonl: TButton;
procedure ButtonlClick(Sender: TObject);
private
{ Private declarat ions }
public
f Public declarations }
end;
VcLT
Forml: TForml ;
procedure ShowAbout;external * ProjectDLL.DLL * index 10;
implementat ion
Обратите внимание, что первое описание процедуры написано не в разделе type, а до него:
procedure ShowAbout(Handle: THandle)stdcall;
Это не является ошибкой, и вы можете выбрать любой из этих способов.
(Совет )
Чаще всего внешние процедуры объявляются до раздела var, чтобы их потом легче было найти.
Теперь установим на форму кнопку и напишем для ее события onclick следующий код:
procedure TForml.ButtonlClick(Sender: TObject);
begin
ShowAbout(Handle);
end;
Запустите пример и убедись в том, что пример работает корректно. Как видите, окно не так уж трудно поместить в библиотеку, и в нем могут быть свои события и свои процедуры и функции. К тому же окно может создавать дочерние окна по отношению к себе и той же динамической библиотеке.
工 Примечание
На компакт-диске в папке \ Примеры \ Глава 18 \ Form вы можете увидеть пример этой программы.
Немодальные окна в динамических библиотеках
В предыдущем примере мы поместили в библиотеку модальное окно. А что, если вам понадобится показать немодальное окно? Ведь мы показываем окно и по его закрытию должны освободить память. А как узнать, что окно закрыто? Некоторые программисты ленятся и просто не освобождают память, выделенную под окно. Но это неправильно и просто глупо, потому что показать немодальное окно не намного сложнее.
Давайте откроем предыдущий пример и подкорреьсгируем его. Для начала нужно добавить одну экспортную процедуру FreeAbout с индексом П. Теперь у нас будут экспортироваться две процедуры: exports ShowAbout index 10;
exports FreeAbout index 11;
Теперь переходим в модуль Unitl, где у нас находится форма динамической библиотеки. Процедуру ShowAbout превращаем в функцию, которая будет возвращать значение типа Longlnt. В качестве возвращаемого значения будет идентификатор окна, по которому мы потом будем его закрывать.
Еще нужно добавить процедуру Free About с одним параметром типа Long I nt. function ShowAbout(Handle: THandle):Longlnt;export;stdcall;
procedure FreeAbout (FcrmRef: Long In t); expert; stdcall;
Как говорилось ранее, динамические библиотеки не могут хранить переменных. Именно поэтому после создания окна мы должны вернуть идентификатор основной программе, чтобы она сохранила эту переменную. Когда нужно будет закрыть окно, мы передадим этот идентификатор библиотеке, и она освободит память, выделенную под 0Kно.
Теперь посмотрим на реализацию функции ShowAbout:
function ShowAbout(Handle: THandle):LongInt;
begin
Application.Handle:= Handle;
Forml:= TForml.Create(Application);
Forml.Show;
Result:=LongInt(Forml);
end;
Здесь все осталось так же, за исключением последней строчки. Если раньше мы освобождали память, то сейчас возвращаем окно Forml, приведенное к типу integer. Если бы мы тут вызвали метод Free, то окно сразу же после появления закрылось бы.
Теперь посмотрим на процедуру FreeAbout:
procedure FreeAbout(FormRef: Longlnt);
begin
if FormRef>0 then
TForml(FormRef).Free;
end;
В этой процедуре мы сначала проверяем, если переменная FormRef (идентификатор окна) больше нуля, то окно можно уничтожать, иначе оно могло быть уже уничтожено. Во второй строке мы вызываем метод Free нашего окна. Так как переменная FormRef — это числовая переменная и у нее нет методов, то мы должны перевести ее обратно к объекту TForml (FormRef).
Теперь подкорректируем проект, который использует DLL-файл. Для начала подправьте объявления процедур библиотеки. Перед разделом type напишите следующее:
'unction ShowAbout(Handle: THandle):LongInt;stdca11;
procedure FreeAbout(FormRef: Longlnt);export;stdea11
В разделе var пишем следующее:
function ShowAbout;external * Proj ect DLL.DLL * index 10; procedure FreeZ\bout;external * Prej ectDLL.DLL * index 11;
Все это уже должно быть знакомо и не должно вызывать вопросов. Теперь в разделе private объекта главной формы добавляем 'Переменную f типа Longlnt.
Подготовка закончена. Осталось только вызвать эти процедуры. Добавьте на форму еще одну кнопку. По нажатии первой мы будем показывать окно: procedure TForml.ButtenICliсk(Sender: TObj ect); begin
f:=ShowAbout(Handle); end;
Здесь мы вызываем функцию показа окна и сохраняем результат в переменной.
По нажатии второй кнопки вызываем процедуру освобождения памяти (событие Onclick):
procedure TForml.Button2Click(Sender: TObject);
begin
FreeAbout (f); end;
Запустите пример и убедитесь, что все работает корректно. Самое сложное здесь — определить, когда пользователь с амостоятельно закрыл окно (например, кнопкой Закрыть в нашем окне О программе), чтобы мы вызвали процедуру FreeAbout. В качестве решения такой проблемы можно посоветовать следующее. При старте приложения надо присваивать переменной f значение 0. В этом случае мы будем застрахованы от попадания в нее случайного числа. Перед созданием окна вызывать FreeAbout. В этом случае сначала будет происходить проверка переменной f на 0. Если переменная больше 0, то окно уже создавалось, но память не освободилась. Вот обновленный код нажатия кнопки показа диалогового окна: procedure TForml.ButtonlClick(Sender: TObject);
begin
if f>0 then
FreeAbout(f);
f:=ShowAbout(Handle); end;
Здесь идет проверка, если f больше нуля, то надо освободить память от старого окна, а потом пытаться создавать новое.
По событию OnClose для главной формы тоже не помешает вызвать процедуру освобождения памяти. Если программа закрывается, то окно из библиотеки уж точно уже не по надобится, значит, переменную f можно проверять на 0, и если там большее значение, то освобождать память.
На компакт-диске находится пример, в котором реализовано все сказанное, и вы можете увидеть этот кол своими глазами, а также проверить его в действии.
[ Примечание〉
На компакт-диске в папке \ Примеры \ Глава 18 \ NoModal вы можете увидеть пример этой программы.
Явная загрузка библиотек
Предыдущие примеры хороши тем, что они просты, но у них есть особенность —динамическая библиотека загружается автоматически при старте программы. В этом просматриваются два недостатка.
□ Загрузка программы немного теряет в скорости (не сильно, но все же), а функции из библиотеки могут вообще не понадобиться за все время выполнения программы.
О Может понадобиться поставлять программу в укороченном варианте без некоторых фунъщий (без каких-либо DLL-файлов), но это не получится, потому что программа на этапе загрузки будет выдавать ошибку о том, что библиотека не найдена.
От всего этого можно избавиться, если использовать явную загрузку библиотеки и в определенный момент.
Давайте в предыдущем нашем примере будем использовать явную загрузку библиотеки для вызова функции ShowAbout. В принципе, если делать явную загрузку, то это нужно делать для всех функций и процедур библиотеки, потому что если вы оставите хотя бы одну неявной, то библиотека все равно будет грузиться на этапе старта программы, а явная загрузка будет только повторять уже выполненные действия. Для примера мы взяли только одну функцию, а процедуру попробуйте перевести на явную загрузку самостоятельно. Тем более что для этого не надо делать много изменений.
Итак, загрузите приложение, написанное в прошлом разделе главы, которое использует динамическую библиотеку. В основном модуле уберите объявление функции ShowAbout. Теперь в разделе type напишите объявление нового типа:
ShowA= function (Handle : THandle):Longlnt;stdcall;
Здесь мы объявляем новый тип ShowA, который равен функции с параметрами функции ShowAbout из динамической библиотеки. Параметры должны быть точными, как при объявлении, иначе могут возникнуть проблемы.
Этого достаточно. Теперь нужно переходить к обработчику события Onclick для первой кнопки, где мы показывали окно. Код, который вы должны здесь написать, приведен в листинге 18.4.
[Листинг 18.4. Код ной загрузки библиотеки
procedure TForml.ButtcnlClick(Sender: TObject);
var
DLLHandle:THandle;
sa:ShowA;
begin
if f>0 then
FreeAbout(f);
DLLHandle: =LoadLibrary (・Proj ectDLL. DLL');
if DLbHandle=O then
exit; //Библиотека не загрузилась
@ sa:=GetProcAddress(DLLHandle# 'ShowAbout*);
if @sa=nil then
exit;//Функция не найдена
f:=sa(Handle);
FreeLibrary(DLLHandle);
end;
Здесь объявлены две локальные переменные:
口 DLLHandle — будет храниться указатель на загруженную библиотеку;
О sa — имеет тип ShowA, т. е. тип фунвдии из библиотеки.
В начале кода выполняется уже знакомая проверка переменной f. Если она больше нуля, то окно уже показывалось, и нужно освободить память от старого окна, прежде чем создавать новое.
Дальше вызывается функция Load Lib гагу. Эта АР 1-функция загружает указанную в качестве параметра динамическую библиотеку в память. Результа-
том выполнения является указатель на загруженную библиотеку. Этот указатель мы сохраняем в переменной DLLHandie. После этого нужно проверить, если указатель DLLHandie равен нулю, то библиотека не загрузилась.
Теперь требуется получить адрес функции ShowAbout в загруженной памяти, чтобы мы могли выполнить функцию. Для этого вызывается функция GetProcAddress. Фунвщии нужно передать два параметра:
О указатель на загруженную библиотеку;
口 имя искомой процедуры.
Результатом будет адрес искомой функции, и мы его сохраняем по адресу переменной @sa. Теперь sa указывает на адрес, по которому загружена процедура ShowAbout из динамической библиотеки. Единственное, что надо проверить, — корректность адреса. Если он равен nil, то процедура не найдена (возможно, что это старая версия библиотеки или неправильно указано имя).
Если все нормально, то мы вызываем функцию через переменную f :=sa (Handle) , почти так же, как это делалось раньше. Результат выполнения функции сохраняется в переменной f.
Последняя строчка кода выгружает динамическую библиотеку из памяти — FreeLibrary. Точнее сказать, на этом этапе реальной выгрузки не происходит. Функция только сообщает системе о том, что больше библиотека программе не требуется. Если эту библиотеку использует другая программа (одну библиотеку могут использовать одновременно несколько программ), то она останется в памяти, пока та не сообщит о ненужности загруженного DLL-файла.
~Прммечанмё~^Д
На компакт-диске в папке \ Примеры \ Глава 18 \CallFunc вы можете увидеть пример этой программы.
Точка входа
Вы наверно заметили, что в исходном коде библиотеки есть begin и end, не относящиеся к ни одной из процедур или функций. Код, описанный здесь, выполняется самым первым при загрузке библиотеки в память. Зачем это нужно? Здесь можно было бы инициализировать какие-то переменные, но библиотека не может сохранить их.
В библиотеках есть одна глобальная переменная, которая существует всегда, и ее имя DLLProc. Эго не просто переменная, а указатель на процедуру. По умолчанию он равен nil, но если сюда записать адрес реальной процедуры,
то эта процедура может отзываться на определенные события, происходящие в библиотеке. В процедуре можно регистрировать следующие события:
О DLL_PROCESS_ATTACH — событие генерируется при загрузке библиотеки; 口 DLL_PROCESS_DETACH — событие генерируется при выгрузке библиотеки; О DLL_THREAD_ATTACH — событие генерируется при создании нового потока; 口 dll THREAD—DETACH — событие генерируется при отключении нового потока.
Рассмотрим небольшой пример, чтобы вы увидели, как это работает.
Откройте библиотеку, написанную в предыдущем разделе главы. Теперь добавьте в нее код, приведенный в листинге 18.5.
рй<стннгЧ6.5 Код 口LL-бнблмотеки
1ibrary ProjеctDLL;
uses
SysUtils, Classes, Windows , dialogsf Unitl in 1 Unitl .pas1 {Forml};
($R *・RES}
[
exports ShowAbout index 10 ; exports FreeAbout index 11;
procedure DLLEntryPoint (dwReason:DWord); begin case dwReason of
DLL_PROCESS_ATTACH: ShowMessage (* Attach to process '); DLL_PROCESS_DETACH:ShowMessage( 'Detach to process');
DLL_THREAD_ATTACH: ShowMessage (1 Thread attach to process 1);
DLL_THREAD_DETACH: ShowMessage ( 1 Thread detach to process 1); end;
end;
begin
DLLProc:=@DLLEntryPoint;
DLLEntryPoint(DLL_PROCESS_ATTACH); end.
В разделе uses появилось объявление двух новых модулей — windows и dialogs. Без них наш код не с ко м п и л и руется. Чуть дальше появилась процедура DLLEntryPoint с одним параметром, в котором будет передаваться событие, которое произошло. Внутри процедуры оператором case проверяется тип пришедшего сообщения и в зависимости от этого выводится сообщение.
Между begin и end библиотеки переменной DLLProc присваивается наша процедура. После этого вызываем ее и в качестве параметра указываем событие DLL PROCESS ATTACH.
工 npsivie4anse :
На компакт-диске в папке \ Примеры \ Глава 18 \ Entry вы можете увидеть пример этой программы.
Вызов из библиотек процедур основной программы
Теперь мы рассмотрим еще один вариант использования DLL-библиотек. Мы уже многое узнали и пора увидеть, как можно из библиотеки DLL вызывать процедуры, описанные в основной программе. Это очень мощный способ, позволяющий сообщать основной программе о каких-либо событиях, происходящих в библиотеке.
Создайте новый проект динамической библиотеки. В основном модуле напишете код, представленный в листинге 18.6.
Листинг 18.6. Код динамической библиотеки
library FuncProject;
uses
SysUtils,
Classes;
type
TConpProc= procedure(Str:PChar);StdCall;
20 Зак 942
procedure CompS(Str:PChar; Proc:TConipProc);StdCal1;
begin
if ©Proconi 1 then
TCompProc(Proc)(Str);
end;
exports CompS index 10;
{$R *.res}
begin end.
Первое, что здесь бросается в глаза, — это объявление в разделе type нового типа(Т comp Proc). Новый тип объявлен как процедура с одним параметром в виде переменной типа pchar, имеющей стандартный вызов. Объявление этого типа необходимо, чтобы объяснить динамической библиотеке, какого вида буцет процедура в основной программе, которую надо буцет вызывать.
Внимание
Тип PChar, это указатель на строку, оканчивающуюся нулем (в шестнадцатеричной системе— #0). Сама переменная типа Pchar — только указатель на начало строки, а конец строки определяется по наличию значения #0. О конце строки нам никогда не придется заботиться, нас больше будет волновать выделенная память, потому что под строку типа Pchar нужно резервировать память.
После объявление нового типа процедуры идет процедура CompS, которая будет экспортироваться из модуля. Эту процедуру мы будем вызывать из основной программы, а из нее уже будем обращаться к процедуре основной программы.
В первой строке процедуры проверяется значение переданного параметра Proc. В этом параметре мы должны получать адрес той процедуры, которую надо вызвать. Если параметр не равен нулю, то можно сделать вызов. Для вызова напишем следующий кед:
ТСопрРгос(Proc)(Str)
Этим кодом вызывается процедура И ей в качестве параметра передается переменная str, которую мы сами же получили в качестве входного параметра.
В принципе, с процедурой все. Остальное вам уже должно быть знакомо.
Теперь переходим к написанию основного модуля.
Создайте новое приложение. В разделе type сразу же объявите следующее:
type
ТСопрРгос= procedure(Str:PChar);StdCall;
procedure CompS(Str:PChar; Proc:TCompProc);export;StdCall;
В первой строке мы объявляем тот же процедурный тип, что и в динамической библиотеке. Во второй строке объявляется процедура, которую мы экспортируем из библиотеки. Объявление должно быть именно в таком порядке. Если вы попытаетесь объявить сначала процедуру из библиотеки, то при компиляции Delphi выдаст ошибку, потому что в качестве второго параметра в процедуре стоит тип TCompProc, и сначала его нужно описать, а потом использовать.
Теперь напишем процедуру CallFromDLL. Эта процедура будет вызываться из динамической библиотеки. Она будет выглядеть так:
procedure Са11FгomDLL(Str:PChar);StdCall;
begin
ShowMessage (* DLL вызвала эту процедуру. Параметр равен: *+Str);
end;
Наша процедура должна соответствовать объявленному типу тсотрргос, а именно, в типе описано, что это процедура и она имеет один параметр типа Pchar и вызывается стандартно. Процедура должна соответствовать всему этому описанию, иначе произойдет ошибка.
Внутри процедуры вызывается только одна функция ShowMessage, которая формирует на экране окно сообщения. В качестве единственного параметра в ней нужно указать текст сообщения.
Теперь поместим на форму кнопку и по ее событию Onclick напишем следующий код:
procedure TForml.ButtonlClick(Sender: TObject);
begin
CompS(1Привет *, @ Ca11FromDLL);
end;
Здесь просто вызывается процедура CompS, которая хранится в динамической библиотеке. Попробуйте запустить приложение и проверить результат работы программы.
工 Примечанием^
На компакт-диске в папке \ Примеры \ Глава 18 \ Call вы можете увидеть пример этой программы.
Глава 19
Разработка собственных компонентов
На протяжении всей книги мы уже использовали достаточно много компонентов Delphi. Вы уже убедились, что среда разработки достаточно хорошо продумана и имеет очень много инструментов для создания полноценных приложений. Но готовых компонентов никогда не бывает слишком много. Всегда хочется все больше и больше.
Примечшше
Зайдите на сайт www.torry.net и посмотрите раздел VCL. Тут целое множество компонентов, написанных разными программистами-одиночками и маленькими фирмами. Некоторые даже зарабатывают этим деньги.
Допустим, что вы написали какой-то код, который может пригодиться вам в дальнейшем. Можно оформить этот код в виде DLL-файла, но это не всегда возможно. Например, DLL-код не может реализовать часы, которые должны выводиться на главной форме. В этом случае лучше всего подходят компоненты.
Вы можете создать свои компоненты, похожие на те, что видите на палитре компонентов, и потом многократно использовать написанный там код. Если сделаете так, то в будущем достаточно будет только установить свой компонент на форму, и его можно будет использовать точно так же, как и любой другой стандартный компонент Delphi.
Если вас заинтересовало сказанное, то можете приступать к чтению этой главы, чтобы побыстрей узнать, как все это делается. Но даже если вы не будете создавать собственные компоненты, желательно эту главу не пропускать, потому что здесь будет описано достаточно много теории, касающейся струьсгурной организации компонентов. Вы узнаете, из чего они состоят, и лучше будете понимать, как они работают.
Пакеты
Любой компонент, который устанавливается в Delphi, должен попасть в какой-то пакет. Для этих целей по умолчанию уже есть один пакет, но вы можете создавать новые пакеты. Таким образом, компоненты можно группировать по смысловому признаку в разные пакеты. Например, те, что работают с графикой, устанавливать в один пакет, а те, что работают с файлами —в другой.
Давайте посмотрим, как работать с пакетами.
Для начала создайте где-нибудь у себя на компьютере директорию (папку) Components. В нее вы будете складывать все созданные или скачанные из Internet компоненты. Внутри нее создайте еше одну — Other.
L Пр-мечанме 工
На диске в папке Компоненты \ Handles вы найдете исходный кед, который будем сейчас устанавливать. Скопируйте найденный там файл в папку Other. Скопировать нужно файл Handles, pas. Здесь находится исходный кодкомпоненгта. Теперь запустите Delphi и закройте все, что в нем открыто (File | Close АН).
Чтобы установить компонент, выберите из меню Component пункт Install Component. Перед вами откроется окно, показанное на рис. 19.1. В этом окне вы можете увидеть четыре строки ввода. Рассмотрим их назначение.
Рис. 19.1. Окно установки компонента
□ Unit file name — в этой строке нужно указать имя устанавливаемого компонента. Для этого щелкните по кнопке Browse и вы увидите стандартное окно открытия файла. Откройте файл Handles.pas.
О Search Path — здесь находится список путей, по которым Delphi при компиляции ищет исходный код установленных компонентов. В принципе, в конец этой строки надо было бы добавить, после точки с запятой, и путь к нашему компоненту. Но пока этого делать не будем.
О Package file name — это имя пакета, в который будет помещен устанавливаемый компонент. Здесь можно выбирать в выпадающем списке любой суще ствующий.
О Package description — текстовое описание пакета. Здесь может быть любой текст, заданный вами. Он ни на что не влияет.
Будем считать, что у вас в системе еще нет своих пакетов, и Delphi предложил установить новый компонент в пакет по умолчанию. Мы не будем этого делать, потому что так у нас в системе возникнет неопределенность. Давайте создадим новый пакет, в который будем помещать все файлы из директории Components \ Other. Для этого в этом же окне выберите вкладку (она находится в верхней части окна) Into new package и нажмите кнопку Browse напротив строки Package file name. В появившемся стандартном окне открытия файлов перейдите в директорию Components \ Other и здесь вручную введите имя пакета —otherComponents — и нажмите кнопку ОК.
Теперь нажмите кнопку ОК в окне InstallComponent, чтобы закрыть окно установки компонента. Перед вами автоматически должно появиться сообщение типа: "Package OtherComponents.bpl will be build then installed. Continue?" Смысл сообщения следующий — "Пакет OtherComponents.bpl будет откомпилирован и установлен 6 систему. Продолжить ?w Можете нажать кнопку Yes и Delphi сделает все необходимое для установки нового компонента в систему. Однако нажмем кнопку No, чтобы рассмотреть, как это делается не автоматически и что при этом происходит.
Для начала у вас появилось новое окно, показанное на рис. 19.2.
Рис. 19.2. Окно пакета
В центре окна можно увидеть дерево из двух ветвей.
Contains — здесь содержатся модули, входяшие в пакет. У нас пока один модуль и вы видите только его файлы.
Requires — здесь находится список имен пакетов, необходимых для компиляции данного пакета. В принципе, эти имена можно удалить (иногда они не нужны), но если при компиляции хотя бы один из этих пакетов понадобится, то появится сообщение о необходимости подключения данных пакетов, и Delphi снова их вернет автоматически. Если попытаться отказаться от автоматического добавления, то пакет не сможет быть откомпилированным и установленным в систему.
В верхней части окна находятся панели со следующими кнопками:
Compile — компилировать пакет;
Add — добавить новый модуль в этот пакет;
П Remove — удалить модуль;
П Install — установить пакет в систему (при нажатии этой кнопки пакет, при не обходимости, будет перекомпилирован);
П Options — настройки пакета.
Прежде чем компилировать наш пакет, давайте посмотрим его настройки. Нажмите кнопку Options и вы увидите окно, показанное на рис. 19.3.
В принципе, большинство настроек схожи с настройками исполняемых файлов. Обратите внимание на раздел Usage options, где надо указать, для чего буцет использоваться пакет. Здесь доступны три варианта.
□ Designtime only — компоненты пакета могут использоваться только во время проектирования формы в оболочке Delphi.
□ Runtime only — компоненты нельзя использовать во время проектирования, а можно только создавать во время выполнения программы. Для этого на машине, запускающей программу, обязательно должен присутствовать откомпилированный файл проекта (файл с тем же именем и расширением — bpl).
□ Designtime and runtime — компоненты пакета можно использовать в обоих случаях.
三 Примечание
Чаще всего используют именно третий пункт, а второй ставят для коммерческих пакетов, которые распространяются за деньги. В этом случае пользователь может познакомиться с возможностями ваших компонентов, но только используя их во время выполнения программы и с помощью дополнительных файлов (bpl). Если он захочет использовать компоненты и во время проектирования формы в оболочке Delphi, то за это можно брать отдельную плату.
Чуть позже мы рассмотрим, чем отличаю!си пакеты Designtime и Runtime на практике, а пока закройте окно свойств пакета и возвращайтесь в окно пакета.
Нажмите кнопку Install, чтобы установить пакет в систему. Если кнопка Install недоступна, то сначала отком 11 ил I ру iiTe проект, а потом установите. Delphi откомпилирует пакет и выведет сообщение об удачной установке. Закройте окно пакета. При закрытии Delphi спросит о необходимости сохранить его, ответьте Да, чтобы все изменения сохранились.
Теперь на палитре компонентов у вас должна появиться новая вкладка с именем QD, ще вы сможете найти новый компонент. Некоторые компоненты попадают на вкладку Symples, а для некоторых создаются новые вкладки.
Теперь снова закройте все (File | Gose All) и создайте новый проект приложения. Сейчас мы рассмотрим, как используются пакеты Designtime и Runtime. Выберите пункт Options из меню Project- Перед вами откроется окно свойств проекта, как это показано на рис. 19.4. В этом окне иерей ли на вкладку Packages.
Здесь (в нижней части окна) вы можете видеть флажок Build with runtime packages. Если установить этот флажок, то ваши программы резко уменьшаются в размере, потому что в них буцет храниться только код программы. Все компоненты, которые установлены на форме, не попадут в исполняемый файл.
Рис. 19.4. Окно свойств проекта
Когда программа будет запускаться, она будет подгружать эти компоненты из файлов пакетов, перечисленных в строке чуть ниже вышеописанного флажка. Получается, что пакеты bpl могут работать как самые настоящие динамические библиотеки. Так вы можете экономить размер программ, но при переносе программы на другой компьютер, вы должны заботиться о том, чтобы и необходимые BPL-файлы тоже попали на тот компьютер. Их достаточно скопировать в папку System (System32 для Windows NT/2000/XP) директории Endows.
Если снять флажок Build with runtime packages, то программа становится полноценной и не нуждается в дополнительных BPL-файлах. Исключения составляют только случаи, когда вы использовали пакет Runtime, который не может компилироваться в программу и обязательно должен использоваться только на этапе работы программы. Но такие пакеты редкость и использовать их нежелательно. Работайте с теми компонентами, которые можно установить на форму во время проектирования приложения.
Теперь проверим путь к нашему компоненту. Выберите из меню Tools пункт Environment Options. Перед вами откроется окно настроек Delphi. В этом окне нужно перейти на вкладку Library (рис. 19.5).
I Environment Options
Type L brafy . ]E11vironment Variables| Internet , S Delphi Direct
Preferences | Desigrrar | ObiectInspector | Palette Lbrary ] Explorer
Directories )
Library path: ]$ЮЕ1РнпТадБЕй^^
BPL output directory: ]$(DELPHi)\Proiects\Bpl
DCP oulput directory; J$(DELPHl)\Projects\Bp1
Browsing path: j$[DELCM]sciurce\vcl;$(DE LPH沁ourceSFiU,§Q j …]j
?K ― 4 Cancel Help
Рис- 19.5. Окно свойств проекта
В этом окне вы можете увидеть следующие опции:
Library path — здесь перечислены пути, ще Delphi должен искать исходный код компонентов;
BPL output directory — здесь указывается директория, куда будут сохраняться откомпилированные пакеты (по умолчанию здесь указано $(DELPHI)\Projects\BpI);
р^имечанмеi
Конструкция $(DELPHI) указывает на директорию, куда установлен Delphi. По、 лучается, что BPL-файлы будут сохраняться в директорию, где установлен Delphi, поддиректорию Projects \ Вр1.
口 DCP output directory — директория, в которую будут помешаться DCP- файлы (это скомпилированные файлы пакета, которые хранят всю информацию о всех компонентах, скомпилированных в пакет);
口 Browsing path — пути для просмотра.
Нас сейчас интересует только первый список, остальные вообще можно оставлять по умолчанию. Нажмите на кнопку с тремя точками справа от списка Library path. Перед вами откроется окно редактора путей (рис. 19.6).
Рис. 19.6. Окно редактора путей
В списке (в центре окна) вы можете увидеть перечень директорий, в которых Delphi будет искать исходные программные коды. Когда вы выбираете любой из них, то этот путь перемешается в строку ввода под списком.
Проверьте список на наличие пути к директории, где находится исходник файла Handles.pas. Если такого пути нет, то щелкните по кнопке с тремя точками справа от строки ввода и вы увидите окно выбора директории. Найдите нужную директорию и нажмите ОК. Теперь выбранный путь находится в строке ввода. Чтобы его добавить в список, нужно нажать кнопку Add. Сделайте это и можете закрывать окно редактора путей нажатием кнопки ОК. После этого закройте окно настроек Delphi.
Если вы не добавите путь к исходным кодам в настройках Delphi, то компилятор не найдет их и при компиляции выдаст ошибку.
Когда вы устанавливаете пакет Runtime, то вам необходимо найти файл с расширением bpl в директории $(DELPH!) \ Prefects \ Вр/ и скопировать его в системную папку Windows.
Если у вас уже есть где-то откомпилированный пакет (файл с расширением bpl), то его можно сразу же установить в Delphi без компиляции. Но даже в этом случае наличие исходных программных кодов компонентов из пакета
обязательно. Их надо будет записать в доступную для Delphi директорию или создать новую директорию, но добавить путь в настройках Delphi.
Рис. 19.7. Окно установки пакетов
Для установки уже скомпилированного пакета выберите из меню Component пункт Install Packages. Перед вами откроется окно (рис. 19.7), которое похоже на вкладку Packages окна свойств проекта (см. рис. 19.4). В принципе, можно использовать любое из этих окон.
Для установки нового пакета нужно нажать кнопку Add. Перед вами появится стандартное окно открытия файлов. Найдите нужный bpl-файл и откройте его. Delphi автоматически установит все компоненты из пакета в палитру компонентов.
19.2. Подготовка к созданию компонента
Прежде чем начинать создавать собственный компонент, необходимо решить для себя несколько вопросов. Самым первым вы должны решить вопрос, от какого компонента или объекта будет происходить ваш. Как говорилось ранее, все объекты в Delphi происходят от объекта TObject. А все
компоненты имеют среди родственников объект TComponent. На рис. 19.8 показана иерархия предков компонента TButton.
Hierarchy
TObject
I
TPersistent
I
TComponent
I
TControl
I
IWinCcm"川
I
TButtcinCciMrQl
I
[Biutton
Рис. 19.8. Иерархия предковкомпонента TButton
В самом верху иерархии находится объект TObject. Все объекты происходят именно от него. В TObject находятся базовые свойства и методы, которые необходимы любому другому объекту. Именно поэтому, чтобы во всех объектах не прописывать одни и те же свойства и методы, все уже реализовано в TObject. Остальные просто наследуют эти возможности.
Далее по иерархии идет объект TPersistent, который является предком для всех объектов, которые должны уметь назначаться другим объектам. Например, если наш объект должен уметь выполнять метод Assign (назначить), то этот объект должен иметь в предках объект TPersistent.
Следующим в иерархии идет объект TComponent. Этот объект должен быть предком для любых компонентов Delnhi, которые должны уметь ставиться ап Этот объект наследует все свойства и
на форму в режиме проектирования.1~
методы своих предков (TPersistent и TObject) и добавляет новые возможности по работе с объектом, как с полноценным компонентом на форме.
Если компонент должен быть видимым во время выполнения программы, то он обязательно должен происходить от компонента TControl (следующий в иерархии для кнопки). Для невидимых компонентов (например, компоненты с вкладки Dialogs, которые не видны во время выполнения), этот объект среди предков не нужен.
Следующий в иерархии идет TWinControl. Этот объект добавляет функции получения фокуса ввода, работы с текстовым буфером, возможность содержания дочерних компонентов. Если ваш компонент будет иметь среди предков TWinControi, то поверх этого компонента можно будет ставить еще компоненты (не обязательно, но возможно) в режиме дизайна формы или во время выполнения программы. TwinControl имеет дескриптор (Handle) окна и может получать фокус. Вспомните пример с потоками, ще мы использовали для вывода текста из потока компонент TLabel. Когда потребовалось подкорректировать поток и добавить возможность посылать сообщения SendMessage, то нам пришлось заменить TLabei на TEdit, потому что у первого не было дескриптора окна, и мы не моши ему отсылать сообщения Windows.
Следующий — TButtonControl. Это уже компонент кнопки, в котором реализуется множество не обходимых кнопке свойств и методов.
Нужно еще сказать об одном базовом компоненте — TGraphicControl. Если ваш компонент должен будет иметь метод Paint, т. е. уметь рисовать на поверхности графику, то он должен иметь среди предков этот объект.
И последний базовый объект — TcustomControl. Он представляет сочетание двух объектов — ТGraphicControi и TWinControi, т. е. компонент имеющий дескриптор окна и метод Paint.
Иерархия предков компонента читается сверху вниз. Каждый новый объект иерархии дополняет уже существующую структуру новыми свойствами и методами. Таким образом, в конечном итоге мы получаем полноценный самостоятельный объект.
Прежде чем создавать свой собственный компонент, вы должны решить, от какого уже существующего объекта он будет происходить. Выбор должен зависеть от необходимых будущему компоненту возможностей.
Откройте файл помощи в Delphi (меню Help | Delphi help). В появившемся окне найдите объект TButton. Для этого введите в строку ввода (сверху) имя TButton. Теперь в большом списке выделите строку TButton и нажмите кнопку Показать. Перед вами откроется окно с найденными разделами. В данном случае список будет состоять из двух строк:
О Tbutton;
□ TButton (VCL Reference).
Первая строка явно относится к объекту TButton из библиотеки CLX (если ничего не указано, то это скорей всего библиотека CLX). Вторая строка — это объект в библиотеке VCL. Оба варианта компонента схожи и имеют очень много одинаковых свойств и метопов, но могут быть и отличия, специфичные для платформы Windows. В книге рассматривается платформа Windows, поэтому выбирайте вариант VCL и нажимайте кнопку Показать.
Перед вами откроется окно помощи по выбранному объекту. Оно должно выглядеть, как это показано на рис. 19.9.
Рис. 19.9. Помощь по компоненту ТВutton
Сверху окна установлены следующие ссылки:
口 Hierarchy — если щелкнуть по этой ссылке, то перед вами откроется окно с иерархией компонента;
口 Properties — здесь будут показаны все свойства компонента;
П Methods — это методы компонента;
П Events — события, которые может генерировать компонент.
Попробуйте посмотреть свойства. Щелкните кнопкой мыши по ссылке Properties, и перед вами откроется окно со списком свойств. В этом окне вы можете увидеть все свойства, разбитые по разделам. Большим жирным шрифтом написаны имена разделов, а после этого идут ссылки на свойства. Щелкая по любой ссылке, можно получить ее описание.
Обратите внимание на имена разделов. Например, Derivedfrom TWinControl. Судя по названию, в этом разделе будут перечислены все свойства, которые компонент получил от объекта TWinControl. Оно так и есть. Выбирая любой компонент, вы можете увидеть его и унаследованные от предков свойства. То же самое и с методами.
Создание первого компонента
Теперь давайте попробуем создать первый собственный компонент. Здесь выбрана не совсем простая задача, чтобы вы в очередной раз могли потренироваться в программировании.
В качестве примера будем создавать часы. Пусть наши часы могут работать как аналоговые, так и как числовые. Для создания нового компонента выберите из меню Component пункт New Component. Перед вами откроется окно, показанное на рнс. 19.10, где вы должны будете заполнить основные параметры будущего компонента.
Давайте рассмотрим каждую опцию окна по отдельности.
Рис. 19.10. Окно создания нового компонента
О Ancestor type — тиц предка. Это имя объекта, от которого мы породим объект. Это даст нашему объекту все возможности его предка, плюс мы добавим свои. Для часов нам понадобится TGraphicControl, потому что наш компонент должен будет иметь метод Paint, т. к. часы будут графическими.
П Class Name — имя нашего будущего компонента. Назовем его TGraphicClock.
Palette Page — имя палитры компонентов, куда будет помещен компонент после инсталляции.
~ Примечание
Здесь оставлено значение по умолчанию — Samples. Но вы можете поместить его даже на вкладку Standard.
口 Unit file name — имя и путь к модулю, ще будет располагаться исходный код компонента. Это поле заполняется автоматически, но вы его можете изменить. Если не хотите менять, то хотя бы посмотрите, под каким именем сохранен ваш компонент и ще.
Search path — здесь перечислены пути, где Delphi ищет исходные коды. Если вы располагаете компонент в новой директории, о которой Delphi еще не знает, то обязательно нужно добавить ее сюда.
Введите данные о будущем компоненте и нажмите ОК (именно — ОК, а не Install). После этого Delphi создаст новый модуль с шаблоном для будущего компонента. Код показан в листинге 19-1.
Листинг 19」. Заготовка, ,пя будущего компонента
unit GraphicClock;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
TGraphieClock 二 class(TGraphicControl)
private
{ Private declarations }
protected.
{ Protected declarations }•
public
{ Public ctecloirations }
published.
{ Published declarations }
end;
procedure Register;
inplementcition
proceclure Register;
begin
RegisterComponents(,Samples *, [TGraphicClock]);
end;
end.
В шаблоне реализована только одна процедура — Register. В этой процедуре происходит вызов RegisterComponents с двумя параметрами:
口 имя вкладки, на которую нужно будет поместить этот компонент;
口 имя компонента, которое надо зарегистрировать при установке в системе Delphi.
Процедура Register обязательно должна присутствовать в любом модуле компонента. Она вызывается автоматически оболочкой Delphi при установке компонента. Если у вас пакет из большого количества компонентов, то процедуры для каждого из них иногда группируют в отдельный файл. ■
В принципе, простейший компонент готов и его можно установить в Delphi. Но этот компонент ничего не умеет, и он пока является полным аналогом своего предка TGraphicControl. Чтобы он стал отличаться — добавим ему разные свойства.
Начнем формирование компонента с конструктора и деструктора. Конструктор —это метод объекта, который автоматиче с ки вызывается при его создании. Деструктор — тоже метод, только он автоматически вызывается при уничтожении компонента. Вызывать эти метода напрямую нельзя, а если и можно, то нежелательно.
В конструкторе мы проинициализируем все наши переменные, которые понадобятся при его работе, а в деструкторе — уничтожим.
Итак, напишите в разделе public:
constructor Create (AOwner: TComponent) ; override ;
Как видите, объявление метода похоже на объявление любой другой процедуры или функции, только вместо ключевого слова procedure стоит слово constructor. Ключевое слово override; после имени этих процедур говорит о том, что мы хотим переписать уже существующую у предка функцию с таким именем. У большинства компонентов есть конструктор, и когда мы
создаем конструктор у потомка, то у нас получается два метода с одним именем (у предка и у нашего объекта).
Теперь нажмите клавиши <Ctrl>+<Shift>+<C>, и Delphi сам создаст заготовку для ко нструктор а: constructor TGraphicClock.Create{AOwner: TComponent);
begin iriher'itecl;/
encl;
Подправим ее. Для этого в эту заготовку нужно вписать код, показанный в листинге 19.2.
Листинг 19.2. Конструктор
constructor TGraphicClock.Create(AOwner: TComponent); begin
/ /Вызываем конструктор предка iriher'itecl Create(AOwner);
/ /Устанавливаем значения ширины и высоты по умолчанию Width := 50;
Height := 50;
/ /Устанавливаем переменную ShowsесondArrow в true.
//Она будет у нас отвечать за показ секундной стрелки ShowSecondArrow :二 tme;
//Инициализируем остальные переменные
PrevTime := 0;
CHourDiff := 0;
CMinDiff := 0;
//Инициализируем растры TBitmap, в которых будут храниться //фон и сам рис. часов.
FBGBitmap: = TBitmap. Create;
FFont:=TFont.Create;;
FBitmap:= TBitmap.Create;
FBitmap.Width t— Width;
FBitmap. Height := Heigh匸;
/ /Выставляем формат времени
DateFojzmat:«* tt * ;
/ /Запускаем таймер
Ticker := TTimer.Create( Self);
/ /Интервал работы таймера одна секунда
Ticker.Interval := 1000;
//По событию OnTimer будет вызываться процедура TickerCall Ticker. OnTimer := TickerCall ;
/ /Включаем таймер
Ticker.Enabled : = true;
/ /Устанавливаем цвета по умолчанию
FFaceColor := clBtnFace;
FHour'ArrowColor :二 clAct iveCapt ion ;
FMinAr rowColo r :== c lAct iveCapt ion ;
FSecArrowColor := c lAc t iveCapt ion ; end;
Ключевое слово inherited вызывает такую же процедуру или конструктор предка (в нашем случае TGraphicClock). Это необходимо, потому что предок тоже может делать что-то важное в конструкторе, и если мы не вызовем его конструктор, то могут возникнуть проблемы, например, не создадутся какие-то до полнительные объекты или не будет выделена память.
В остальном, я надеюсь, что с конструктором все ясно. Дальше идет инициализация переменных. Я постарался снабдить код подробными комментариями, чтобы вы смогли разобраться с происходящим. Сами переменные мы пока не добавили, и я их буду описывать постепенно.
Теперь создадим деструктор. Для этого также опишем его в разделе public:
public
{ Public declarations }
constructor Create (AOwner: TComponent) ; override ;
destructor Destroy; override;
Деструктор тоже объявляется как простая процедура, но здесь стоит ключевое слово destructor. Теперь нажмите клавиши <Ctrl>+<Shift>+<C> и получаем заготовку для деструктора. Скорректируем ее до вида:
destructor TG г aphi сС1 ос k. Dest гоу; begin
Ticker.Free;
FBitmap. Free;
FBGBitmap. Free;
iriher'ited Destroy;
end;
Здесь освобождается вся память, выделенная для хранения картинок и объекта TTimeг в конструкторе. Заметьте, что в конструкторе мы вызывали предка в самом начале inherited, а в деструкторе — в самом конце. В конструкторе сначала нужно, чтобы инициализировался предок (он проинициа- лизирует необходимые ссылки), а потом можно инициализировать свои объекты. В деструкторе все наоборот — предок уничтожается в последнюю очередь. Если в деструкторе мы сначала вызываем предка, то по следующая работа с компонентом уже может быть невозможна, потому что предок уничтожит все ссылки. Поэтому этот вызов ставится в самом конце.
Теперь опишите все необходимые переменные в разделе private. Они показаны в листинге 19.3.
Листинг 19.3. Переменные из раздела private
private
//Для часов обязательно понадобится таймер Ticker: TTiiner;
/ /Картинки часов и фона FBitmap t FBGBitmap : TBitmap;
/События
FOnSecond, FOnMinute, FOnHour: TNotifyEvent;
//Центральная точка
CenterPoint: TPoint;
//Радиус
Radius: integer;
//Переменная, отвечающая за показ секундной стрелки ShowSecondAr row: boolean;
//Цвет фона часов
FFaceColor: ТСо lor;
//Цвета стрелок часов FHourArrowColor,FMinArrowColor, FSecArrowColor: TColor;
//Формат даты
FDateFormat: String;
//Стиль шрифта
FFont: TFont;
//Остальные параметры, которые мы рассмотрим в процессе.
LapStepW: integer;
PrevTime: TDateTime;
CHourDi f f, CMiiiDi f f: integer;
FClockStyle:TClockStyle;
Теперь в разделе private опишем процедуру TickerCall. Мы ее уже использовали в конструкторе (она вызывается по событию от таймера), но пока еще не написали:
protected
(Protected declarations }
procedure TickerCall (Sender: TObject);
Нажмите клавиши <Ctrl>+<Shift>+<C> и напишите в ней код, показанный в листинге 19.4.
ЛИСТИНГ 19.4. Обработчик события для таймера
proceclure TGraphicClock. TickerCall (Sender: TObject); var,
H,M,S,Hp,Mp,Sp: word;
begin
/ /Если компонент создан в конструкторе, то выход
if csDesigning in ConponentState then exit;
/ /Иначе это уже запущен яая программа
//Получить время
DecodeCTime ( Time, Н, М, S); //Получить ттредыдущее время. DecodeCTime ( PrevTime, Нр, Мр, Sp);
//Сгенерировать событие if Assigned ( FOnSeconcl) //Сгенерировать событие if Assigned ( FOnMinute) //Сгенерировать событие//Сгенерировать событие if Assigned ( FOnSeconcl) //Сгенерировать событие if Assigned ( FOnMinute) //Сгенерировать событиеOnSecond
//Сгенерировать событие if Assigned ( FOnSeconcl) //Сгенерировать событие if Assigned ( FOnMinute) //Сгенерировать событие
//Сгенерировать событие if Assigned ( FOnSeconcl) //Сгенерировать событие if Assigned ( FOnMinute) //Сгенерировать событие
then FOnSecond(Self);
OnMinute
AND (Mp < M) then FOnMinute (Self) OnHour
if Assigned( FOnHour) AND//Сохранить текущее время PrevTime := Time;if ( NOT ShowSecondArrow)if Assigned( FOnHour) AND//Сохранить текущее время PrevTime := Time;if ( NOT ShowSecondArrow)(功 < H) then FOnHour (Self);
if Assigned( FOnHour) AND
//Сохранить текущее время PrevTime := Time;
if ( NOT ShowSecondArrow)
if Assigned( FOnHour) AND
//Сохранить текущее время PrevTime := Time;
if ( NOT ShowSecondArrow)
в PrevTime
AND (Sp v二 S) then exit;
//Прорисовать часы
DrawArrows;
end;
Процедура DrawArrows напичкана математикой, и она сейчас не имеет для нас особого значения. Немного ниже рассмотрим ее полный исходный код, а сейчас мы рассматриваем, как создавать компоненты. Поэтому рассмотрим события, генерируемые в функции TickerCall, и узнаем, как они генерируются.
У нас уже объявлено три события в разделе private компонента:
FOnSecond, FOnMinute, FOnHour: TNotifyEvent;
Все они появятся на вкладке Events окна объектного инспектора, когда вы установите компонент на форму. Чтобы приложение могло реагировать на эти события, были объявлены переменные типа TNotifyEvent (этот тип означает события).
Но все это пока переменные, а как же Delphi узнает, что это события, которые надо поместить на вкладку Events объектного инспектора? Для этого в разделе published надо описать само событие OnSecond, а также другие, которые будут использоваться:
property OnSecond: TNoti fу Event read FOnSecond write FOnSecond; property OriMinute: Tt-IotifyEvent read FOriMinute write FOriMinute; property OnHour: TNot.ifyEvent read FOnHour write FOnHour;
В начале каждой строки стоит ключевое слово property, которое говорит о том, что объявляется свойство. После этого идет имя свойства и после двоеточия стоит тип. Вот как раз по типу Delphi и узнает, что объявленное свойство на самом деле событие.
Тип TNotifyEvent объявлен в Delphi следующим образом:
type TNotifyEvent • procedure (Sender: TObjeCt) of object;
Как видите, этот тип равен процедуре, которая имеет один параметр Sender типа TObject. Это стандартный тип события, который вы можете использовать, когда вам не надо передавать в обработчик никаких параметров. События —это достаточно интересная тема, и чуть позже мы рассмотрим ее более подробно, а пока вам достаточно этих знаний.
После этого идет ключевое слово read, за которым должны идти переменная или функция, которые будут использоваться при чтении события. У нас после этого ключевого слова стоит имя переменной, соответствую шей событию. После ключевого слова write нужно ставить переменную или функцию, которая будет использоваться для записи в событие. Тут опять стоит соответствующая переменная.
Для генерации события мы пишем следующий код:
FOnSecond (Self) ДЛЯ события OnSecond.
FOnMinute (Self) ДЛЯ события OnMinute.
FOnHour (Self) ДЛЯ события OnHour.
При генерации события в качестве переменной передается self. Эта переменная всегда указывает на объект, в котором мы сейчас находимся, в данном случае — компонент TGraphicClock. Вспомните любой обработчик события. В каждом из них есть как минимум один параметр — переменная Sender, которая указывает на объект, который сгенерировал событие. Теперь посмотрите на код, которым мы генерируем событие. Как видите, мы при генерации указывается объект Self, который генерирует событие, и пользователь получит его в переменной Sender обработчика события.
Но нельзя вслепую генерировать событие. Прежде чем это делать, нужно проверить, есть ли обработчик события. Для этого нужно вызвать функцию Assigned и в качестве параметра указать тип события. Получается, что следующий код проверяет, установлен ли обработчик события OnSecond, и если да, то генерирует событие:
if Assigned( FOnSecond) then FOnSecond(Self);
Теперь наш компонент сможет генерировать события.
С событиями вроде все ясно (если нет, то посмотрите на исходный код, который приведен на прилагаемом к книге компакт-диске, и попробуйте разобраться). Теперь переходим к свойствам. В разделе published мы можем создавать свойства, которые будут отображаться в объектном инспекторе при выделении наших часов. Все свойства, которые есть у предка, нужно описать:
published
property Align;
property Enabled;
property ParentShowHint;
property ShowHint;
property Vis ible;
Все эти свойства мы получаем от предков TGraphicControl, TControl и т. д. Слово property говорит о том, что мы описываем свойство. Для них не нужны процедуры или функции, потому что эти свойства уже есть у предка. Надо только описать их и все, чтобы Delphi знал, какие из свойств предков мы хотим увидеть в объектном инспекторе. Здесь описана только маленькая часть из доступных у TGraphicControi свойств. Вы можете добавить любые из доступных. Чтобы узнать, какие функции можно добавлять, откройте помощь (Help I Delphi Help) и найдите там объект ТGraphic Controi. Щелкните кнопкой мыши по нему дважды и в появившейся справке выберите пункт properties (в верхней части окна). В результате проделанных действий должно появиться окно с перечнем всех свойств. Вы можете добавить любое из них. Например, чтобы добавить свойство Action, нужно написать в разделе published:
published
property Action;
Чтобы добавить новое свойство, которое не существует у предков, нужно немного постараться. Например, добавим возможность, чтобы пользователь мог менять картинку фона. Для этого описываем в разделе published свойство BGBitmap:
property BGBitmap: TBitmap read FBGBitmap write SetBGBitmap;
Подобную строку вы видели при описания события, только там свойство имело тип события, а здесь это картинка типа TBatmap, так что Delphi воспримет эту запись как свойство.
Для чтения свойства BGBitmap мы поставили переменную FBGBitmap. Это тоже картинка, и когда мы будем обращаться к свойству с целью прочитать картинку, то она будет просто копироваться из переменной FBGBitmap.
Для записи используется процедура SetBGBitmap. В принципе, можно было и для чтения написать функцию, но это не имеет смысла. А вот для записи сложных переменных (для которых выделяется память) лучше использовать функции. Для простых переменных (числа, булевы переменные), писать процедуры не надо, но если вы напишете, то это ошибкой не будет.
Итак, для записи свойства BGBitmap мы напишем следующую процедуру:
proceclure TGraphicClock. SetBGBitmap (Value: TBitmap);
begin
FBGBitmap. Assign (Value);
invalidate;
end;
В первой строке мы копируем в переменную FBGBitmap картинку, переданную в качестве параметра. Во второй строке заставляем наш компонент прорисоваться.
Теперь вы можете изменять фон простой операцией
GraphicClockl.BGBitmap:=bitmap
Если вы хотите создать свойство с выпадающим списком (как, например, у свойства Align), по щелчку которого выпадает список возможных параметров, то тут уже немного сложнее. В наших часах есть такой параметр, который делает выбор, какого типа будут часы — аналоговые или цифровые. Объявление делается так:
property Clockstyle: TClockStyle read FClockStyle write SetStyleStyle default scAnalog;
Здесь Объявляется СВОЙСТВО ClockStyle ТИПа TClockStyle. ТИП TClockStyle мы должны описать в самом начале, до описания объекта TGraphicClock, в разделе type:
type
TClockStyle = (scAnalog, scDigital);
TGraphi cClock = class(TGraphicContro1)
private
Ticker: TTimer;
Строка
TClockStyle = (scAnalog, scDigital)
объявляет список переменных, которые и будут появляться при выборе свойства.
Все остальное происходит так же, за исключением нового слова default, которое устанавливает значение по умолчанию для данного свойства — sc Analog.
Остальной код рассматриваться не будет, потому что там сплошная математика, которой достаточно много, но вам желательно с ним разобраться.
В результате получен новый компонент — часы — форма которого показана на рис. 19.11.
工 Примечание 】
На компакт-диске в папке \ Примеры \ Глава 19\ Component вы можете увидеть пример этой программы.
Рис. 19.11. Пример наших часов
Создание иконки компонента
Если вы установили созданный нами компонент в Delphi, то заметили, что он имеет абсолютно некрасивую картинку. Эта иконка выбирается по умолчанию для всех компонентов, если у них нет своей. У созданного компонента пока нет своей иконки, и ее сейчас предстоит создать.
Для создания иконки будем пользоваться программой Image Editor, которая входит в поставку Delphi. Запустите ее (Пуск | Про1раммы | Borland Delphi 7 \ Image Editor). Здесь нужно выбрать из меню File пункт New и затем Component Resource File (/dcr). Программа создаст новое окно, содержащее дерево, в котором пока только один элемент Contents.
Для создания нового элемента нужно щелкнуть правой кнопкой мыши в созданном окне проекта ресурса и в появившемся меню выбрать пункт меню New I Bitmap. Перед вами отмоется окно свойств создаваемой картинки (рис. 19.12).
Ширина и высота (параметры Width и Height) картинки должны быть равны 24. Цветовой режим оставляем VGA, потому что для иконки 16 цветов достаточно. Нажмите теперь кнопку ОК. В результате этого в структуре дерева элементов ресурсов появится раздел Bitmap, и в нем наша картинка Bitmapl (рис. 19.13). Щелкните правой бснопкой по элементу Bitmapl и в появившемся меню выберите пункт Rename. Переименуйте картинку, дав ей имя нашего компонента TGraphicClock.
Рис- 19.12. Окно создания растровой картинки
Рис. 19.13. Пример наших часов
Внимание
Картинка обязательно должна иметь то же имя, что и компонент, которому она предназначена, потому что в одном файле исходного кода может быть несколько компонентов. Delphi по имени картинки будет определять, к какому компоненту она относится.
Теперь дважды щелкните по элементу картинки. В результате этого появится графичесьсий редактор. В этом редакторе вы можете нарисовать что угодно. Нарисуйте кавсие-нибудь часы. После этого файл ресурсов можно закрывать. На запрос системы о сохранении файла сохраните его под именем Graphicclock. dcr. Скопируйте этот файл в директорию, где находится исходный код компонента. Оба файла должны находиться в одной директории.
Теперь откройте в Delphi пакет otherconiponents.dpk. Удалите из него файл с исходным кодом часов и откомпилируйте пакет. Это заставит Delphi удалить из оболочки наш компонент. После этого кнопкой Add снова добавьте файл с исходным кодом и откомпилируйте пакет еще раз. В результате этих действий компонент установился заново с уже новой иконкой.
工 Примечание
На компакт-диске в папке \ Примеры А Глава 19 \ IconForComponent вы можете увидеть пример моей иконки.
События в компонентах
Мы уже знаем, что для того, чтобы сгенерировать событие, надо объявить свойство, вюторое будет иметь тип процедуры. До этого мы использовали свойство TNotifyEvent. Теперь мы улучшим наши часы и добавим возможность передачи в обработчик события определенного значения и заодно более подробно разберемся с событиями.
Откройте файл, в котором у вас находится исходный код для работы часов. Первое, что мы сделаем, — объявим новый тип события с именем Т ClockNotifyEvent в разделе type:
TClockNotifyEvent = procedure (Sender: TObject; TimeParam:Integer) of object;
В этом типе уже два параметра:
О указатель на компонент, который сгенерировал событие;
П через этот параметр мы будем передавать обработчику значение времени.
Этот тип события мы назначим всем переменным FOnSecond, FOnMinute и FOnHour. Для этого найдите следующую строку в объявлении нашего компонента:
FOnSeCond, FOnMinute, FOnHour: TNotifyEvent;
Здесь нужно изменить тип TNotifyEvent на TClockNotifyEvent. То же самое нужно сделать и в следующих строках:
property OnSecond: TCiockNotifyEvent read FOnSecond write FOnSeconcl; property OnMinute: TCiockNotifyEvent read FOnMinute write FOnMinute; property OnHour: TCiockNotifyEvent read FOnHour write FOnHour;
Теперь события имеют новый тип и два параметра. Через второй мы будем передавать текущее значение часов, минут или секунд.
Далее найдем процедуру TickerCall и изменим в ней генерацию события таким образом, как это показано в листинге 19.5.
инг 19.5 Генерация события
procedure TGraphicClock.TickerCall(Sender: TObject);
var
H;M,S,Hp,Mp, Sp: word;
begin
if csDesigning in CoraponentState then exit;
DecodeCTime (DecodeCTime (DecodeCTime (DecodeCTime (Time, H, M, S);
DecodeCTime (
DecodeCTime (
DecodeCTime (
DecodeCTime (
PrevTime, Hp, Mp, Sp);
if Assigned.( if Assigned( if Assigned( PrevTime : ^if Assigned.( if Assigned( if Assigned( PrevTime : ^FOnSecond) then FOnSecond(Selfr s);
if Assigned.( if Assigned( if Assigned( PrevTime : ^
if Assigned.( if Assigned( if Assigned( PrevTime : ^
FQriMinute) AND (峥 < M) then FOnMinute (Self, m);
FOnHour) AND (功 < H) then FOnHour (Self, s);
Time;
if ( NOT ShowSecondArrow) AND (Sp <= S) then exit;
DrawArrows;
end;
Теперь наш компонент генерирует события следующим образом:
if Assigned( FOnSecond.) then FOnSecond (Self, s);
Сначала мы проверяем, если программа назначила обработчик события Assigned (FOnSecond), то он вызывается и ему передаются значения. В качестве первого параметра передается указатель на себя ——self, а в качестве параметра передается значение, в данном случае — количество секунд.
Перекомпилируйте пакет, в который вы добавили компонент. Для этого его нужно открыть и нажать кнопку Compile (см. рис. 19.2).
Создайте новое приложение и поместите в главное окно наш компонент. Выделите его и в объектном инспекторе перейдите на вкладку Events. Создайте обработчик события для любого из событий, и Delphi вам создаст примерно следующую заготовку: procedure TForml.GraphicClocklHour(Sender: TObject; TimeParam: Integer); begin
end;
Как видите, здесь два параметра, и они именно такие, как и в созданном нами типе события. Вот теперь можно сказать, что мы знаем о событиях практичесьси все, что необходимо.
工 Примечание
На компакт-диске в папке \ Примеры \ Глава 19 \ Clock вы можете увидеть пример этого компонента.
Глава 20
Мультимедиа
Еще несколько лет назад наличие звуковой карты в компьютере было достаточно редким явлением. Не все могли позволить себе использовать такую дорогую игрушку, поэтому приходилось работать и играть в тишине.
В настоящее время звук стал неотъемлемой частью любого компьютера, и звуковые контроллеры устанавливаются непосредственно на материнских платах. Так что купить сейчас компьютер без звука практически невозможно, хотя бы какой-то звук он сможет воспроизвести. А совсем недавно у нас заговорили даже материнские платы, которые при старте сообщают свое состояние.
Современные программы уже и поют, и говорят, в нужный момент подсказывая, что делать. В самые ответственные моменты нам помогают звуковые сигналы. Особенно хорошо звук проявляется в программах, которые выполняются в фоновом режиме (незаметно для пользователя). Когда возникает определенная ситуация, такие программы могут ненавязчиво сообщить о каком-либо событии звуковыми сигналами. Примером такой программы можно назвать будильник, который работает незаметно в системе и зазвенит в указанное время, сообщая нам об определенном событии.
Применению звука в программных продуктах нет предела. Он оиень удобен во многих ситуациях. Обратите внимание на свой MS Word, если его версия больше Word 2000. В нем появился помощник, который регулярно издает какие-нибудь звуки, по которым можно определить ситуацию, когда что-то происходит не так.
В этой главе представлены материал, связанный с созданием звука, чтобы вы смогли сами написать мультимедийное приложение практически любой сложности.
Простейшие способы проигрывания звука
Для начала познакомимся с простейшей функцией, с помощью которой можно воспроизвести аудиофайл. Эта функция Называется sndPlaySound. Ее
21 Зак 942
описание находится в файле MMSystem.pas, так что если вы хотите ею воспользоваться, нужно в разделе uses добавить модуль MMSystem.
У функции sndPlaySound два параметра:
О строка, хранящая имя или полный путь к файлу;
флаг воспроизведения.
В качестве флага можно указывать несколько значений.
П SND SYNC — воспроизводить звук синхронно. Это значит, что пока звук не будет воспроизведен полностью, программа не начнет или не продолжит свое выполнение. Этот флаг стоит по умолчанию, и будет использоваться, если вы не укажете другого значения.
口 SND ASYNC — воспроизводить звук асинхронно. В этом случае запускается воспроизведение музыки, и оставшийся код программы сразу же начинает выполняться.
SND NODEFAULT — если не указать этот флаг и файл, который надо воспроизвести, не найден, то прозвучит звук ошибки, установленный в системе. Если флаг указан, то сообщения об ошибки не будет.
П SND MEMORY — если указан этот флаг, то в качестве первого параметра стоит указатель на образ звуковых данных, уже загруженных в память.
SND LOOP — этот флаг заставляет воспроизводить звуковой файл, повторяя его бесконечно.
С помощью функции sndPiaySound можно воспроизводить файлы в формате WAV. Но файл типа WAV не ограничен определенным алгоритмом хранения данных и не обязан сжимать данные каким-то особым способом. Это всего лишь оболочка, которая может хранить данные множества форматов. Можно увидеть в таких файлах нечто подобное MP3. Во всем этом вы сможете убедиться чуть позже, когда мы начнем рассматривать WAV-структуру и будем учиться вручную загружать данные.
Давайте посмотрим работу функции на примере. Создайте новый проект и сразу же добавьте в раздел uses модуль MMSystem. Теперь поместите на форму две кнопки Открыть и Проиграть.
Добавим в программу еще и возможность управлять флагами воспроизведения. Для этого на форму надо поместить два компонента TradioButton.
П Имя — SyncRadioButton; заголовок — Играть синхронно.
П Имя — AsincRadioButton; заголовок — Играть асинхронно.
Еще на форму надо поместить два компонента TcheckBox.
О Имя — LoopCheckBox; заголовок — Повторять.
П Имя ——LoopCheckBox; заголовок ——Не проигрывать ошибку.
И нанаконец установим компонент окна открытия файла — TOpenDialog. Это окно будет использоваться для открытия звукового файла.
На рис. 20.1 вы можете увидеть результирующую форму.
Рис. 20.1. Форма будущей программы
Для события Onclick кнопки Открыть пишем следующий код: procedure TForml.ButtonlClick(Sender: TObject); begin
if OpenDialogl. Execute then
sndP lay Sound (PChar (OpenDialogl. Filename) , SND」kSYNC); end;
В первой строке кода мы отображаем на экране окно открытия файла, и если пользователь выбрал файл, то он воспроизводится с флагом SNDASYNC.
По нажатии кнопки Проиграть будем проигрывать уже открытый файл с учетом выбранных параметров. Для события Onclick этой кнопки напишем следующий код:
procedure TFonul.Button2Click(Sender: TObject); var
Flag:Integer; begin
if SyncRadioButton * Checked then Flag:=SNP_SYNC
else
Flag: =SND_JkSYNC ;
if LoopCheckBox. Checked then Flag:=Flag or SND_LOOP;
if NoErrorCheckBox . Checked then
Flag:=Flag or SND_NODEFAULT;
sndPlaySound(PChar(OpenDialogl.Filename) , Flag);
Application. MessageBox ( * Воспроизведение окончено1 f 1 Внимание! ! ! *);
end;
В этой процедуре о&ьявлена одна переменная целого типа — Flag. В этой переменной мы будем формировать флаги с учетом выбранных запросов.
Сначала происходит проверка, как нужно играть файл — синхронно или асинхронно. Если выделен SyncRadioButton, то переменной Flag присваивается значение SND SYNC, иначе присваивается SND ASYNC:
if SyncRadioButton. Checked then
Flag:=SND_SYNC
else
Flag:=SNDJkSYNC;
Здесь надо заметить следующее. Можно использовать или тот, или другой флаг. Однако оба одновременно использовать нельзя. Мы же не можем проиграть файл сразу синхронно и асинхронно. Именно поэтому для выбора между этими флагами используется компонент TRadioBox.
Дальше проверяется, если выбрано повторение, то к переменной Flag прибавляем SND LOOP. Сложение происходит с помощью логической операции or (Flag or SND LOOP).
Примечание
В принципе, можно использовать и простое сложение (Flag + SND LOOP), но при работе с флагами предпочтительнее использовать логическое сложение. Вы можете использоёать тот способ, который больше вам подходит или более понятен.
Точно так же происходит и проверка на выделение параметра Не проигрывать ошибку.
После этого происходит проигрывание файла, а затем вывод диалогового окна с сообщением о том, что воспроизведение закончено.
Попробуйте запустить этот пример, открыть файл и воспроизвести какой- нибудь WAV-файл с разными параметрами. Заметьте, что когда вы воспроизводите звук синхронно, то сообщение появляется на экране только по завершении звучания музыки. Это потому, что выполнение программы блокируется, пока воспроизведение не закончится.
Когда мы воспроизводим звук асинхронно, то сообщение появляется сразу же после начала звучания музыки. Это потому, что функция sndPlaySound начинает воспроизводить звуковой файл и не блокирует программу, и дальнейший код программы может спокойно продолжать свою работу. Такое использование функции sndPiaySound очень удобно, когда нужно, чтобы музыка воспроизводилась в фоне, пока работает основная программа.
Нужно также заметить и то, что не все флаги могут работать совместно. Например, нельзя воспроизвести файл синхронно с повторением. Если бы такое было возможно, то программа бы начала воспроизводить файл бесконечно, каждый раз начиная сначала. А т. к. нужно воспроизводить синхронно, то программа блокируется, пока у нее не закончится воспроизведение, которое будет играть бесконечно. Получается, что программа блокируется навечно и никогда не отреагирует на любые события.
Именно поэтому при попытке воспроизвести что-нибудь синхронно с бесконечностью вы сразу же увидите сообщение об окончании воспроизведения и при этом не услышите звука в колонках.
工 Примечание 】
На компакт-диске в папке \ Примеры \ Глава 20 \Easy Play вы можете увидеть пример этой программы.
Медиа-проигрыватель средствами Delphi
В прошлом примере мы рассмотрели, как воспроизвести простой звуковой файл с помощью WinAPI-функции sndPiaySound. Да, эта функция не из Delphi, а чисто WinAPI. В Windows достаточно хорошо продумана система мультимедиа и в модуле MMSystem объявлено очень много полезных вещей для воспроизведения практически любого медиа-формата. Туг вам и работа с видео, и звуком любого вида. Единственный недостаток — сложность разработки. Но Delphi может спрятать все трудности под одним лишь компонентом — TMediaPlayer. Этот компонент находится на вкладке System палитры компонентов.
Для написания простого плеера достаточно бросить этот компонент на форму, указать в свойстве Fi leName имя файла, затем установить свойство AutoOpen в true, и программа готова. Ее можно запускать и воспроизводить указанный в свойстве файл.
Но этого мало. Давайте сейчас мы напишем более полноценный плеер практически с возможностями входящей в поставку Windows программы — MediaPlayer.
Для начала спрячем компонент. Его использование позволяет учесть много параметров воспроизведения звука. У него также есть все нужные кнопки. Поэтому заставим его программно выполнять все необходимые действия. Для этого выделите компонент MediaPlayerl и в объектном инспекторе измените свойство visible на false. Теперь, после запуска программы компонент не будет виден.
Поместите на форму две панели тPanel. Первую растяните по нижнему краю, а вторую по всему окну. Еще нужно установить на форму меню, в котором создадим пункты Файл и Помощь. Добавим пункты Выход и Опфыть — для ощущения полноценности программы (рис. 20.2).
Рис. 20.2. Форма будущей программы
Кстати, еще нужен будет компонент TOpenDialog для отображения стандартного окна открытия файла. Пока с дизайном покончено. Обратите внимание, что компонента MediaPiayeri на рисунке невидно даже в конструкторе, потому что его перекрыли панели. Но он нам в принципе и не нужен, так что не волнуйтесь.
Создайте обработчик события для пункта меню Открыть (для этого просто в дизайнере щелкните мышью по этому меню) и напишите в нем содержимое листинга 20.1.
'Листинг 20 1. Обработчик события для кнопки Открыть
procedure TForml.OpenFileMenuItemClick(Sender: TObject);
begin
EnableBattons (false);
if not OpenDialogl. Execute then exit;
try
MediaPlayerl. FileName : =OpenDialogl-FileNcune;
MediaPlayer1.Open;
except
Application-MessageBox(*Ошибка открытия файла1, * Ошибка!!!*);
exit;
end;
EnableBattons(true);/
end;
В первой строчке вызывается процедура EnableBattons. Ее пока нет, но скоро мы ее напишем. Сейчас вам надо только знать, что она должна делать. В этой процедуре мы будем изменять доступность компонентов для пользователя в зависимости от значения, которое указано в параметре. Например, когда наш плеер запущен, но не открыт файл, то кнопки Hipaib, Стоп и другие (которые тоже будут в нашей программе) должны быть недоступны (потому что файл еще не открыт, и проигрывать нечего). После открытия файла мы должны сделать кнопки доступными, т. е. установить им в свойстве Enabled значение true.
Именно поэтому в самом начале процедуры открытия файла процедуре EnableBattons в качестве параметра передается значение false. Если файл будет успешно открыт, то мы изменим это значение на true.
Второй строчкой отображаем стандартное окно открытия файла. Если файл не был выбран, то произойдет выход из процедуры. Если файл выбран, то код процедуры продолжит выполняться.
Туг начинается блок обработки ошибок try. .except. .end. Между try и except написан код открытия файла, потому что если программа не сможет открыть файл, то необходимо в понятной форме сообщить об этом событии пользователю, а не засыпать его непонятными сообщениями об ошибках.
В первой строке блока мы присваиваем в свойство FileName компонента MediaPlayerl ИМЯ запрашиваемого файла, чтобы компонент MediaPlayerl узнал, что надо воспроизвести. Второй строкой заставляем MediaPlayerl открыть файл с помощью метода open.
Если произошла ошибка, то выводим сообщение и выходим из процедуры. Если ошибок нет, то выполнится процедура EnableBattons с параметром true.
Теперь давайте создадим эту процедуру EnableBattons. Для этого перейдите в раздел private нашего модуля и напишите там объявление процедуры:
private
{ Private declarations }
procedure EnableBattons (State : Boolean);
public
{ Public declarations }
Теперь нажмите <Ctrl>+<Shift>+<C>, чтобы Delphi создал пустую заготовку для нашей процедуры. В принципе, можете написать следующий код самостоятельно, но зачем это делать, когда Delphi это может сделать автоматически:
procedure TForml. EnableBattons (State: Boolean);
begin end;
Теперь давайте создадим кнопки Играть и Стоп с именами PlayButton и stopButton на нижней панели. В процедуре EnableBattons мы должны изменять доступность этих кнопок:
procedure TForml.EnableBattons(State: Boolean); begin
PlayButton.Enabled:=state;
StopButton.Enabled:=state;
end;
Для события onclick кнопки Ифать напишите следуюший код: procedure TForml.PlayButtonClick(Sender: TObject); begin
Mediaplayer 1. Play; end;
Здесь вызывается метод Play нашего плеера. Этот метод заставляет проиграть открытый файл.
Для события onclick кнопки Стоп напишем следуюшее: procedure TForml.StopButtonClick(Sender: TObject); begin
MediaPlayerl•Stop;
end;
Здесь вызывается метод stop плеера, который останавливает воспроизведение.
Все, программу можно запускать. Теперь плеер может открывать и проигрывать любые музыкальные файлы, которые умеет открывать Windows MediaPIayer. Осталось только сделать небольшие усовершенствования, а именно добавить вызов процедуры EnableBattons с параметром false по событию OnShow главной формы. Это нужно, чтобы после запуска программы были недоступны кнопки воспроизведения, потому что никакого файла после запуска программы еще не загружено.
Запустите плеер и попробуйте открыть какой-нибудь музыкальный файл. Если у вас в системе установлен Windows MediaPIayer, то можно открыть МРЗ-файлы. Если в системе есть нужный кодек (программа раскодирования звукового файла), можно проиграть и другие звуковые файлы.
Однако на этом возможности плеера еще не закончились. Его легко заставить воспроизводить и видеофайлы. Для этого выделите компонент MediaPlayerl (если он у вас не виден, выберите его имя в выпадающем списке вверху окна объектного инспектора) и в свойстве Display укажите имя панели, которая расположена в центре окна (в примере это Рапе 12).
Вот и все. Попробуйте теперь запустить программу и открыть какой-нибудь клип в формате MPEG или AVI. На рис. 20.3 показан скрин, на котором плеер проигрывает видео в формате MPEG 4.
Рис. 20.3. Плеер, проигрывающий вццео в формате MPEG 4
Как видите, создание собственного плеера не мечта, а реальность. С помощью Delphi это делается не то, что просто, а очень просто.
Примечание、
На компакт-диске в папке \ Примеры \ Глава 20 \ Easy Media Player вы можете увидеть пример этой программы.
В заключение этого раздела рассмотрим описание нескольких полезных свойств и методов, доступных в компоненте TMediaPlayer.
Свойства:
П Length (целое число) — это длина открытого музыкального или видеофайла;
П startPos (целое число) — позиция, с которой надо начать воспроизведение;
П EndPos (целое число) — позиция, на которой надо закончить воспроизведение;
П Frames (целое число) — количество кадров, на которые будет происходить перемотка назад или вперед.
Методы:
П Back — перемотать назад на количество кадров, указанных в свойстве Frame;
П Next — перемотать назад на количество кадров, указанных в свойстве Frame;
П Pause — сделать паузу;
П Resume — продолжить воспроизведение.
Как видите, вы легко можете добавить возможность перемотки, паузы и продолжения воспроизведения к нашему плееру. Попробуйте сделать это самостоятельно, ведь на это уйдет не так уж много времени и сил.
Звук без компонентов
Использование стандартного компонента — хорошее занятие, но не всегда подходящее. Именно поэтому дальше в этой главе мы будем рассматривать программирование без использования готовых компонентов. Сразу надо заметить, что это достаточно сложно, и понадобится достаточно много времени, чтобы разобраться во всем происходящим. Но в любом случае этот материал полезен, потому что здесь рассматривается множество интересных приемов программирования.
Далее мы рассмотрим, как эффективно выводить звук в колонки и как его записывать. Для воспроизведения и записи звука нам придется познако-
миться с форматом хранения аудиофайлов — WAV. Помимо этого, предстоит научиться преобразовывать файлы из одного формата в другой.
Для понимания технологии программирования звука нужно познакомиться с его представлением в цифровой форме. Вот именно с этого мы и начнем рассмотрение данной темы.
Звуковые данные хранятся в компьютере с помощью метода импульснокодовой модуляции. Вы наверно не раз встречали сокращение РСМ. Оно именно это и означает. Расшифровывается РСМ как Pulse-Code Modulation. При этом методе аналоговый звук квантуется по времени и амплитуде. При выводе звука на колонки происходит обратное преобразование. Для большего понимания на рис. 20.4 показан механизм квантования аналогового звукового сигнала.
На графике слева нарисована амплитуда аналогового сигнала. Вверх направлена ось амплитуды звука, а вправо — ось времени звучания. Вертикальными штриховыми линиями показано время, в которое произошел замер амплитуды и сохранение ее величины.
На правом рисунке показан результат. Как видно, кривая звука превратилась в точки. Все, что между точками, потеряно, поэтому чем чаще происходит замер амплитуды, тем выше качество звука и меньше потерь. Значение, обратное времени замера (Т3), называется частотой дискретизации — Тд. Для качественного звука частота дискретизации должна быть вдвое больше, чем высшая частота в обрабатываемом сигнале звука.
С основами физики мы разобрались. Теперь давайте перейдем к программированию. Сколько бы не ругали Windows за его неустойчивость в работе, ему надо отдать должное за то, что в нем есть практически все необходимое
программисту. А именно, в Windows есть хорошо продуманная и хорошо документированная (жаль, что только на английском) библиотека для работы со звуком.
Для работы со звуковой картой используется все тот же алгоритм, как и для работы с любым другим устройством.
Инициализировать звуковой драйвер.
Установить параметры.
Воспроизвести или записать звуковые данные.
Закрыть драйвер.
Для вывода звука мы просто отправляем звуковой карте данные о звуке в цифровом виде. Она преобразует их в аналоговый сигнал.
Внимание
Здесь надо напомнить, что ваша звуковая карта не резиновая. Поэтому она не сможет воспроизводить все файлы подряд. Вам придется самим заботиться о выводе на колонки файлов больших размеров. Для этого придется делать цикл, в котором последовательно отправляются драйверу данные маленькими блоками.
Сейчас давайте познакомимся с основными функциями, необходимыми для воспроизведения звука.
Для примера нам понадобится пять основных функций. Все они определены в модуле mmsystem, поэтому вам придется подключить его в раздел uses.
Первая функция, которую мы рассмотрим, будет waveOutOpen. Она открывает устройство вывода для звукового потока. В модуле она объявлена следующим образом.
function waveOutOpen (
IphWaveOut: PHWaveOut;
uDevicelD: UINT;
lpFormat: PWaveForma tEx;
dwCallback, dwlnstance,
dwFlags: DWORD
):MMRESULT;
В функции определен ряд параметров. Рассмотрим их основное назначение.
口 LphWaveOut — адрес, по которому будет записан указатель на устройство воспроизведения.
口 uDeviceiD — идентификатор устройства, которое вы хотите открыть. Если вы поставите сюда константу wave mapper, то откроется устройство по умолчанию.
口 LpFormat — указатель на структуру типа WAVEFORMATEX, в которой описан формат воспроизводимых звуковых данных.
口 Dwcallback — указатель на функцию “семафор”. Эта функция будет вызываться, чтобы сообщить вам о происходящем.
□ dwFlags — параметры открываемого устройства. Может принимать следующие значения:
CALLBACK EVENT — в параметре dwCallback находится событие THandle, через которое будет происходить информирование о ходе воспроизведения;
CALLBACK_THREAD — BdwCallback находится идентификатор потока;
с allb ack_fun ст I on — в dwCaiiback находится указатель нафункцию;
callback_wINDOW — в dwCaiiback указатель на окно, которому будут посылаться сообщения;
CALLBACK NULL — В dwCaiiback НИЧеГО нет;
wave_allowsync — можно открыть устройство в синхронном режиме;
wave_format_direct — запрещается преобразование данных с помощью АСМ-драйвера (АСМ — Audio CompressionManager);
wave_format_QUERY — если вы установите этот параметр, то реально】。 открытия звуковой карты не произойдет, а функция проверит возможность открытия с заданными параметрами, и если все нормально, то вернет mmsYserr_noerror (если параметры недопустимы, то вернется код ошибки).
Функция может вернуть следующие значения:
口 MMSYSERR—ALLOCATED — устройство уже ОТКрЫТО;
□ mmsyserr_baddeviceid — указан неправильный идентификатор устройства uDevicelD;
П MMSYSERR—NODRIVER — Драйвер отсутствует;
О mmsyserr_nomem — не может выделить память, или она заблокирована;
口 waverr_badformat — указан неправильный формат;
П waverr_sync — устройство синхронно, но оно вызвано без флага WAVE_ALLOW SYNC.
Теперь рассмотрим используемую здесь структуру WAVEFORMATEX, В Delphi она описана как структура следующего вида: PWaveFormatEx 二 ^TWaveFormatEx;
tWAVEFORMATEX 二 packed record
wFormatTag: Word;
nChannels: Word;
nSamplesPerSec: DWORD;
nAvgBytesPerSec: DWORD;
nBlockAlign: Word;
wBitsPerSample: Word;
cbSize: Word;
end;
В этой структуре:
口 WFormatTag — формат звуковых данных (мы будем использовать в основном WAVE_FORMAT_PCM);
口 nchanneis — количество каналов (1 — моно, 2 — стерео);
О nSamplesPerSec — частота дискретизации (возможны значения 8000, 11 025, 22 050 и 44 100);
О nAvgBytesPerSec — количество байт в секунду (wave_format_pcm это является результатом nSamplesPerSec* nBlockAlign);
О nBlockAlign — выравнивание блока (для wave_format_pcm равен wBitsPerSample/8* nChannels);
口 WB its Per Sample — КОЛИЧССТВО бит В ОДНОЙ Выборке (ДЛЯ WAVE_FORMAT_PCM может быть 8 или 16);
口 cbSize — размер дополнительной информации, которая располагается после структуры (если ничего нет, то должен быть 0).
Теперь у нас есть вся информация о том, как открыть звуковое устройство. На первый взгляд уже можно приступить к воспроизведению звуковых данных, но это не так. Теперь предстоит подготовить заголовки, которые и будут отправляться драйверу. Для ЭТОГО есть функция waveOutPrepareHeader: function waveOut Prepar eHeader (
hWaveOut: HWAVEOUT;
IpWaveOutHdr: PWaveHdr; uSize: UINT
):MMRESULT; stdcall;
Возвращаемые параметры те же, а вот внутреннюю структуру рассмотрим подробно.
О HWaveOut — идентификатор устройства воспроизведения (вы его получили после вызова функции waveOutOpen).
口 LpWaveOutHdr — указатель на структуру wavehdr_tag (она уже должна быть специально подготовлена). Как это сделать, см. ниже.
О USize — размер структуры wavehdr tag.
Теперь структура wavehdr tag. Она выглядит следующим образом: wavehdr_tag = record
IpData: PChar;
Мильтимепма645
dwBufferLength: DWORD;
dwBytesRecorded: DWORD;
dWUser: DWORD;
dwFlags: DWORD;
dwLoops : DWORD;
lpNext: PWaveHdr;
reserved: DWORD;
end;
Рассмотрим составляющие структуры:
П IpData — указатель на звуковые данные;
П DwBufferLength — размер звуковых данных;
口 dwBytesRecorded — количество записанных байт (при воспроизведении этот параметр не используется);
П dwUser — сюда можно записать все, что угодно;
П dwFlags — здесь происходит описание заголовка, при этом возможны следующие значения:
whdr__beGinloop — звуковые данные являются первыми в цикле;
whdr_endloop — звуковые данные являются последними в цикле;
whdr_done — окончание воспроизведения (этот флаг может выставить только драйвер);
WHDR—QUEUE — данные помещены в очередь (этот флаг может выставить только драйвер);
whdr_prepared — заголовок инициализирован (этот флаг может выставить только драйвер);
口 dwLoops — количество повторов воспроизведения звуковых данных;
П lpNext — зарезервировано;
П reserved — зарезервировано.
Прежде чем вызывать waveOutPrepareHeader, вы должны заполнить структуру wavehdr_tag. Для этого ее сначала нужно наполнить нулями (чтобы незаполняемые поля были нулевыми и не содержали случайный мусор), а затем установить правильные значения в поля, указывающие размер и положение звуковых данных. Если необходимы флаги, то они тоже должны быть уже выставлены. После подготовки заголовка с помощью wave Ou tPrepareHeader в заголовке нельзя уже ничего менять, кроме: ipData (можно подставить следующие данные), dwBufferLength (можно только уменьшать) и dwFlags (можно добавлять и удалять флаги whdr_beginloop и whdr_endloop).
После окончания работы с этим заголовком нужно освободить его с помощью функции waveOutUnprepareHeader.
Теперь уже можно смело отправлять подготовленный заголовок драйверу. Для этого используется функция waveOutWrite, которая выглядит следующим образом: function waveOutWrite( hWaveOut: HWAVEOUT;
lpWaveOutHdr: PWaveHdr; uSize: UINT
):MMRESULT; stdcall;
В ней:
О hWaveOut — идентификатор устройства воспроизведения (вы его получили после вызова функции waveOutOpen);
О LpwaveoutHdr — это указатель на структуру, которую вы сформировали с ПОМОЩЬЮ waveOutPrepareHeader;
О USize — размер структуры wavehdr.
Возвращаемые значения те же, что и при открытии звуковой карты.
После вывода звука нужно вызвать waveOutUnprepareHeader, чтобы очистить заголовки и закрыть устройство. Давайте посмотрим на эти функции, function waveOutUnprepareHeader( hWaveOut: HWAVEOUT; lpWaveOutHdr: PWaveHdr; uSize: UINT
):MMRESULT; stdcall;
В них:
口 HwaveOut — идентификатор устройства воспроизведения (вы его получили ПОСЛе вызова функции waveOutOpen);
П LpWaveOutHdr — указатель на структуру wavehdr_tag (она уже должны быть специально подготовлена);
О uSize — размер структуры wavehdr_tag.
Как видите, параметры те же, что и у функции waveOutPrepareHeader. Теперь нам осталось только закрыть устройство: function waveOutClose ( hWaveOut : HWAVEOUT
):MMRESULT; stdcall;
hwaveOut — идентификатор устройства воспроизведения. Вы его получили после вызова функции waveOutopen. Здесь это устройство закрывается.
Вот и все, что необходимо знать для воспроизведения звука. Пример рассмотрим чуть позже, потому что еще надо узнать формат файла WAV, из которого будет происходить загрузка данных, и еще несколько функций, улучшающих воспроизведение.
Формат звукового файла WAV
Этот формат наиболее распространен в системе Windows. В большинстве это связано с тем, что его структура очень удобна для использования в API- функциях. А самое главное это то, что данные в файле могут находиться как в сжатом, так и несжатом виде. Это дает возможность его использовать во всех случаях жизни.
Помимо звуковых данных, вы можете установить сюда все, что угодно. Примером может быть любая информация о создателях файла нлК графические картинки. Короче говоря, формат WAV — универсален.
Примечание
Единственный недостаток WAV-файла ——файл не может быть более 4 Гбайт. Однако этого размера вам будет достаточно для записи трех часов музыки отличного качества и без сжатия.
Данный формат относится к разряду RIFF (Resource Interchange File Formats —формат файла обмена ресурсами). Чтобы было понятно — к этому разряду относится также и знаменитый видеоформат AVI.
Теперь переходим к структуре. В WAV-файле могут храниться один или несколько блоков данных. Каждый блок может содержать еще несколько блоков. Так получается дерево звуковых данных. Каждый блок имеет свой заголовок. Заголовок состоит из следующих частей:
口 идентификатор фрагмента (4 байта);
口 звуковые данные (4 байта).
Идентификатор может содержать значения: RIFF, DATA, TXT и т. д. В дальнейшем нас будут интересовать только блоки RIFF и DATA. Теперь посмотрим на структуру WAV-файла, с которым будем работать при программировании звука:
口 4 байта — идентификатор файла (в нашем случае всегда RIFF);
口 4 байта — размер данных;
口 8 байт — идентификатор (первые 7 байт равны WAV fmt, последний байт — в основном пробел);
口 4 байта — размер последующих данных;
П wave format ЕХ — структура, описывающая хранящиеся звуковые данные;
口 4 байта — идентификатор (равен DATA);
О 4 байта — размер звуковых данных;
□ непосредственно данные.
Это структура файла. Как видите, данные в файле уже хранятся в виде, удобном для воспроизведения средствами Windows. Это очень упрощает программирование. Достаточно прочитать структуру wave formatex и звуковые данные и отправить все это драйверу. Никаких преобразований делать не надо.
Пример воспроизведения WAV-файла
Мы уже знаем основные функции воспроизведения звуковых данных, знаем формат WAV-файла. Теперь настало время написать реальный пример. Сейчас мы выведем звуковые данные в колонки.
На рис. 20.5 показана форма программы. Очевидно, вы не станете писать все, о чем здесь говорится, своими руками, но все же рассмотрим внутреннюю структуру примера. Это вам пригодится, а исходный код вы найдете на прилагаемом к книге компакт-диске.
Рис. 20.5. Форма будущей программы для воспроизведения звука
Самое главное — подключить модуль inmsystem в раздел uses. В этом модуле объявлены все мультимедиа-функции.
Теперь переходим к примеру. Он достаточно большой и его изучение отнимет очень много времени. Если вы сможете с ним разобраться, то поймете достаточно много тонкостей в программировании. Здесь использовано очень много приемов, которые мы еще не обсуждали.
Начнем как всегда с загрузки, а именно — с обработчика OnShow:
procedure TSounderForm.FormShow(Sender: TObject);
begin
EnableBut(false);
Fil IDevice;
RestartPlay:=false;
end;
EnableBut — функция, которая делает недоступными кнопки воспроизведения, потому что еше не загружен звуковой файл. Как только пользователь выберет WAV-файл, мы снова сделаем эти кнопки доступными. Этот прием уже использовался и будет еще часто использоваться в примерах.
FillDevice — функция, которая определяет возможности звуковой карты. Ее код можете увидеть в листинге 20.2.
I Листинг 20.2. Функция определения возможностей устройства
procedure TSounderForm.FillDevice;
var
pwoc: TWaveOutCaps; / / Объявляю переменную типа TWaveOutGaps begin
//Запросить свойства звуковой карты
waveOutGetDevCaps (WAVE_J4APPER, @pwoc, sizeof (pwoc));
// Ecmi звуковая карта поддерживает изменение звука, то ...
if (pwoc.dwSupport and WAVECAPS_VOLUME)>0 then begin
// Делаю доступными регуляторы громкости.
VolumeLBar. Enabled: =true;
VolumeRBar. Enabled: = true;,
end;
end;
Здесь использована пока еще неизвестная функция waveOutGetDevCaps. Но это только пока. Сейчас мы разберемся с ней. Она выглядит следующим образом:
function waveOutGetDevCaps (
uDevicelD: UINT;//Указатель на устройство воспроизведения
IpCaps: PWaveOutCaps; //Структура, куда 6ylгут записаны данные
uSize: UIOT//Размер структуры
):MMRESULT; stdcall;
Второй параметр — структура waveoutcaps. Она выглядит так:
PWaveOutCapsA - ^TWaveOutCapsA;
PWaveOutCapsW 二 ^TWaveOutCapsW;
PWaveOutCaps 二 PVteiveOut Caps A ;
{$EXTERNALSYM tagWAVEOUTCAPSA}
tagWAVEOUTCAPSA 二 record
wMid: Word;//Идентификатор ггроизводителя
wPid: Word;//Идентификатор устройства .
vDriverVersion: MMVERSION; / /Версия драйвера szPname: array[0. .MAXPNAMELEN-1 ] of AnsiChar; //Название //устройства
dwFormats : DWORD;//Поддерживаеьлые ферматы
^Channels : Word;// Количество каналов
dwSupport: DWORD;//Свойства
end;
Самым интересным здесь является последний параметр — dwSupport. Он может принимать значения:
□ wavecaps_lrvolume — поддерживается изменение правого и левого каналов;
П WAVE CAPS PIT СН — поддерживаются подтяжки;
П WAVECAPS_PLAYBACKRATE — ПОДДерЖИВЗетсЯ playback rate;
П WAVECAPS SYNС — драйвер синхронный и будет блокировать работу, пока играет музыка;
П wavecaps_volume — поддержка громкости;
П wavecaps_sampleaccurate — возвращает позицию;
П параметр wChannels может быть 1 (для моно) и 2 (для стерео).
О параметр dwFormats может быть комбинацией следующих значений:
wave_format_imo8 — 11.025 kHz, mono, 8-bit;
wave_format_1m1 6 —11.025 kHz, mono, 16-bit;
wave_format_i s 0 8 — 1.025 kHz, stereo, 8-bit;
wave_format_is 16 — 11.025 kHz, stereo, 16-bit;
wave_format_2M08 — 22.05 kHz, mono, 8-bit;
wave_eormat_2m16 — 22.05 kHz, mono, 16-bit;
wave_format_2s08 — 22.05 kHz, stereo, 8-bit;
wave_format__2S 16 — 22.05 kHz, stereo, 16-bit;
wave_format_4M08 — 44.1 kHz, mono, 8-bit;
WAVE_FORMAT_4M16 — 44.1 kHz, mono, 16-bit;
WAVE_FORMAT_4 S 0 8 —44.1 kHz, stereo, 8-bit;
• WAVE_FORMAT_4 s 16 —44.1 kHz, stereo, 16-bit.
Таким образом мы можем узнать, поддерживает ли наша звуковая карта изменение громкости, подтяжку, стерео и нужные форматы звуковых данных.
Теперь перейдем к загрузке WAV-файла. Для события, связанного с нажатием кнопки Открыть, нужно написать код, показанный в листинге 20.3.
Листинг 20.3. Открытие звукового файла
procedure TSounderForm.OpenButtonClick(Sender: TObject);
begin
//Сначала, на всякий случай, остановим воспроизведение StopButtonClick(nil);
DataSize:=0; // Обнулить размер данных
if OpenDialogl.Execute then //Показать окно открытия файла OpenWaveFile; //Если файл выбран, то открыть его
WavFi 1 eName: =OpenDialogl. FileName; / / Сохраняем имя файла
//Изменяем заголовок окна.
Caption: =1 Sounder - '+ExtractFileName(WavFi1eName);
/ /Показывать параметры звуковых данных.
if wfx.nChannels=l then
ChanelsBox.Text: =1Моно1
else
ChanelsBox. Tex匸:二 * Стерео ';
FreqBox.Text:=IntToStr(wfx.nSamplesPerSec);
BitsBox.Text:=IntToStr(wfx.wBitsPerSample);
Label?.Caption:=IntToStr(wfx.nAvgBytesPerSec);
end;
Здесь можно разобраться co всем происходящим по комментариям. В принципе, ничего особенного не происходит. Самое интересное буцет в процедуре OpenWaveFile. Вот ее мы сейчас и будем разбирать. Если вы уже усвоили предыдущий материал, то и она покажется вам достаточно простой. Ее содержимое приведено в листинге 20.4.
инг 20Л. Функция загрузки данных мз звукового файла
procedure TSounderForm.OpenWaveFile;
var
hFile:THandle;
Str:array [0..255] of char;
TerapStr:String;
BytesReaded,Size:DWORD;
begin
/ /Если мы уже открывали WZXV-файл, то закрыть предыдущий if w_JnData<>0 then
Cl os eHandle (w_JiData);
//Открыть файл для чтения
/ /Познакомимся с ней отдельно
hFile: =CreateFile(PChar (OpenDialogl-FileName) .GENERIC一READ, FILE_SHARE_READ,nil,OPEKLEXISTING, FILEL-ATTRIBUTELjmCHIVE, 0);
//Если файл открыт нормально, то продолжаем
if hFi1е=INVALID_HANDLE_VALUE then exit;
try
//Получаем размер файлы с помощью GetFileSize
wFileSize:=GetFileSize(hFile,nil);
try
/ /Устанавливаем позицию в файле на 8 байт от начала
SetFilePointer(hFile,8,nil,F工LE_EEG工N);
//Прочитаем, начиная от этой позиции, 8 байт
ReadFile(hFile,Str, 8,BytesReaded,nil);
Str[7]:=# 0;//Обнуляю седьмой байт
TenpStr: =Str; / /Перегоняю результат в строку
/ ато это не W2W
//Если прочитанная стрд^^не равна WAVEtmt f
if TempStro *WAVEfmt'
begin
//Вывожу сообщение об ошибке
application.MessageBox(*Ошибка заголовка1.■Error1,
MB_JOK+MB_ICONINFORMATION);
/Иуэтьтимедма653
CloseHandle(hFile);//Закрываю файл exit;//Выход из процедуры
end;
/ /Читаем размер следующего блока
ReadFile(hFile,Size,Sizeof(Size),BytesReaded,nil);
/ /Обнуляем структуру wfx
ZeroMemory (@wfx, sizeof (wfx));
/ / Если размер следующего блока равен размеру структуры, / /ТО читаем структуру. Иначе вьщаем ошибку if Size=sizeof(PCMWAVEFORMAT) then begin
ReadFile(hFile, wfx,Size,BytesReaded # ni1);
end else begin
MessageBox(Handle, * Формат не поддерживаетсяf, 'Error1, №_OK+MB_ICONEXCLAMATION);
exit; end;
wfx. cbSize : =0;
ReadFile(hFile,Str,4,BytesReaded,nil);
//Читаем размер следующего блока ReadFile (hFile, DataSize, 4, BytesReaded, nil);
/ /Далее идут звуковые данные, поэтому со^^аняем текущую позицию //в DatciOffset, чтобы можно было сразу перейти к данным.
DataOffset:=SetFi1ePointer(hFile,0,nil,FILE_CURRENT);
//Создаем образ файла в памяти с помощью CreateFi lemapping
w_hData: =Cr eateFi 1 emapping (hFile,nil, PAGE..READONLY, 0,
Integer(wFileSize),nil);
//Закрываем файл
CloseHandle(hFile);
hFile:=INVALID_HANDLE_VALUE;
except
Application.MessageBox(* Формат не поддерживается* Л *Error*,
MB_OK+MB_ICONINFORMATION);
CloseHandle(hFile);、
hFile : =INVALIP_HANDLE_VALUE;
end;
finally
if hFile<>INVALID_HANDLE_VALUE then CloseHandle (hFile);
//Если размер звуковых данных больше нуля, значит, можно делать
/ / доступными кнопки воспроизведения
if DataSize>0 then
u •\
begin
EnableBut(true);
InitSoundControls;
Л end
else
EnableBut(false);
end;
end;
Постарайтесь хорошо разобраться co всем происходящим в этой процедуре. Ничего особо сложного нет, если вы усвоили формат файла.
Единственное, чего вы еще можете не знать, так это функцию CreateFilemapping. Данная функция отображает файл в памяти. По идее, мы должны были открыть звуковой файл и загрузить все данные в оперативную память. Но это слишком большой и нерациональный расход памяти. Музыка в формате WAV может занимать по 20—50 мегабайт на 5 минут звуковых данных. Если загрузить один такой файл, то мы заберем у операционной системы слишком много памяти.
Именно поэтому файл не загружаем в память, а только создаем карту. Функция CreateFileMapping формирует специальную карту файла в памяти, но не загружает его. Потом мы обращаемся к этому файлу на диске, как будто он был полностью загружен в память. Таким образом, в этой процедуре открывается файл путем считывания только заголовка. Звуковые данные мы не трогали, только создали карту файла.
У функции CreateFiieMapping есть ряд параметров.
口 Указатель на файл, который нужно отобразить (мы указали здесь открытый нами звуковой файл).
П Параметры безопасности (здесь указан nil, чтобы воспользоваться значениями по умолчанию).
□ Защита отображенного файла. У нас указан флаг PAGE READONLY, означающий, что файл можно будет только читать. Если бы нам нужно было еще и изменять данные, то следовало было бы указать PAGE_READWRITE.
口 Следующие два параметра в виде чисел (размер — DWORD, двойное слово) образуют размер файла. Мы предполагаем, что размер наших файлов будет не слишком большой, и первый из этих параметров устанавливаем в ноль, а во втором указываем размер открытого файла. Таким образом, размер нашего файла не может быть больше двойного слова, а это 4 Гбайт, что вполне достаточно.
Файл в формате WAV зафужен. Теперь переходим к воспроизведению. Для события, связанного с нажатием кнопки Играть, напишите код, показанный в листинге 20.5.
;Листинг 20.5. Код воспроизведения файла
procedure TSounderForm.PlayButtonClick(Sender: TObj ect); begin
//Если была нажата пауза, то продолжить воспроизведение.
if ((w_Pause) and (w_hPlay<>0)) then
begin
waveOutRestart (vChPlay);
w_Pause: =not w_Pause;
exit;
end;
/ /Если данные воспроизводятся, то остановить вывод if vOiPlayoO then StopButtonClick(nil);
/ /Включить таймер
~ SoundTimer. Enabled: =true;
w_Pause:=false; vL_Restart:=false; w_Stop:=false;
w_PlayPosition:=PositionBar.Position*1000;
w_Res tart Pos i t i on:=PositionBar.Position*1000;
/ /Устанавливаем устройство по умолчанию.
w_DeviceID:=Integer(WAVE_MAPPER);
//Запускаем поток восгтроизведения данных.
CyDSounderl: =TSoundPlayer.Create (true);
/ /Устанавливаю высокий приоритет
CyDSounderl.Priority:=tpHigher^
CyDSounderl. Resume;
end;
Сначала мы проверяем, если воспроизведение было приостановлено или нажата пауза, то продолжаем проигрывать звук дальше и выходим из процедуры. Затем проверяем, если данные в текущий момент воспроизводятся, то останавливаем воспроизведение, чтобы начать с начала.
После этого подготавливаем множество переменных, необходимых в дальнейшем для воспроизведения звука. Чуть позже вы увидите их использование, но даже из названия можно понять их предназначение:
П w pause — указывает, нажата ли в данный момент кнопка паузы;
□ w Restart — нужно ли перезапустить воспроизведение;
口 w stop — если эта переменная равна true, то пользователь нажал кнопку останова;
О wPlayPosition, w_Res tart Posit ion — здесь запоминается позиция воспроизведения и позиция для перезапуска воспроизведения данных.
После этого мы присваиваем переменной w_DeviceID значение WAVE MAPPER. В этой переменной находится устройство, которое надо будет открыть, а WAVE_MA₽per указывает на то, что открыть надо устройство, которое установлено в системе по умолчанию.
Дальше запускается поток воспроизведения данных. При этом мы инициализируем переменную CyDSounderl, которая будет указывать на поток типа TSoundPlayer. Поток создается приостановленным, потому что перед его запуском нужно изменить приоритет (поставить более высокий — tpHi'gher)〉чтобы система больше внимания уделяла воспроизведению, и не было "заиканий" при проигрывании звука.
В потоке TSoundPiayer мы вызываем только одну процедуру, которая будет отвечать за вывод звуковых данных:
procedure TSoundPlayer.Execute; begin
Process;
end;
Давайте мельком посмотрим на процедуру Process. Ее описание можно увидеть в листинге 20.6.
Листинг 20.6. Основная процедура потокаf воспроизЕюдящая звуковые данные ।
procedure TSoundPlayer.Process;
const
DEFAULT_BLOCKS I ZE= 65536; / / Размер 1 - го блока
var
Blocksize, curr_block,shift,size:Integer;
s i:TSystemlnfо;
curHdr: WAVEHDR;
ofs:Int64;
p:PSoundArray;
begin
BlockSize:=DEFAULT_BLOCKSIZE;
GetSystemlnfo (si) ; //Получаю системную информацию
ZeroMemory (@Headerf sizeof (Header)) ; //Обнуляю заголовок
hEvent:=CreateEvent(nil,false,false,nil);
try
//Открываем звуковую карту
WaveOutCheck (waveOutOpen (©Sounder Form. vOiPlay x SounderForm. w_DeviceID, ©SounderForm.wfx, hEvent, 0, CALLBACK_EVENT));
/ /Выделяем память для данных
Header[0].lpData:=Virtual7klloc(nil,round((BlockSize*2+
Integer(si.dwPageSize)-1)/si.dwPageSi ze* s i.dwPageSi ze), MEM_RESERVE+MEML_.COMMIT, PAGE_READWRITE);
if Header[0] .lpData=nil then exit;
//Выделенную память разделяю на два заголовка
Header[1].IpData:=Header[0].IpData+BlockSize;
Header[0].dwBufferLength:=BlockSize;
Header [ 1 ] .dwBufferLength: =BlockSi ze;
//Подготавливаю заголовки
waveOutCheck (waveOu t Pr epareHeader (SounderForm. vHhPlay, ©Header[0],sizeof(waveHDR)));
waveOutCheck (waveOutPrepareHeader (SounderForm. w_hPlay,
©Header[1],sizeof(waveHDR)));
658.Глава 20
curr_block:=0;
if SounderForm.DataSize<=Blocksize then ResetEvent (hEvent);
//Запускаю цикл восттроиз ведения
while SounderForm.w_PlayPosition<SounderForm.DataSize do
begin
curHdr : =Header [curr_block and 1];
if BlockSize>SounderForm.DataSize-SounderForm.w_PlayPosition then Blocksize : =SounderForm. DataSize-SounderForm. w_PlayPosition;
//Рассчитываю размер данных
curhdr.dwBufferLength:=BlockSize;
ofs:= SounderForm. w_PlayPos iti on+SounderForm .DataOffset;
shift:=(ofs mod si.dwAllocationGranularity);
ofs:=ofs-shift;
size:=(BlockSize+ofs+si.dwPageSize-1);
size:=size-(size mod Integer(si.dwPageSize));
if size>SounderForm.wFileSize-ofs then size:^SounderForm.wFileSize-ofs;
//Загружаю из памяти файла звуковые данные
р: =MapViewOfFi le (SounderForm. w_hData, FILELMAP_READ,
ofs shr 32 and $FFFFFFFF, ofs and $FFFFFFFF, Size);
try
//Копирую в заголовок
CopyMemory(curhdr.IpData, @p[shift], min(size-shift,Blocksize));
finally
UnMapViewOfFile (p);
end;
/ /Воспроизвожу.
waveOutCheck (waveOutWrite (SounderForm.w^hplay, @curhdr, sizeof (waveHDR)));
/ /Ожидаю окончания, воспроизведения
if (WAIT_TIMEOUT=WaitForSingleObject (hEvent, 10000)) then
begin
if not SounderForm.w_Pause then
begin
MessageBox(SounderForm.Handle.'Ошибка воспроизведения *,'Ошибка',
MB_OK+MB_ICONI№ORMATION);
break;
end
else
WaitForSingleObject (hEvent, INFINITE);
end;
//Если нажат стоп, то выход
if SounderForm.w_Stop then ClosePlay;
//Если отпущена пауза, то восстановить восттроизведение
if SounderForm.w_Restart then
begin
waveOutReset (SounderForm.vOiPlay);
S ounderForm. vcRes tart: =false;
SounderForm. w^PlayPos ition: =SounderForm. w_Res tartPos ition;
BlockSize:=DEFAULT_BLOCKSIZE;
if SounderForm.DataSize>BlockSize then SetEvent (hEvent);
contiriLLe;
end;
//Новая позиция для чтения следующих данных
SounderFo2nn.w_PlayPosition:=SounderForm.w_PlayPosition+BlockSize; end;
finally
/ /Ожидаю окончания восттроиз ведения
Wai tForSingleObj ec t(hEvent,20000);
//Закрываю восцроизведение
ClosePlay;
end;
end;
Прежде чем разбирать эту процедуру, нужно заметить, что в разделе protected у нас должны быть объявлены еще две переменные, которые используются здесь. А именно:
Header : array [0. .2 ] of WAVEHDR;
hEvent : THandle;
В самом начале кода мы объявили константу DEFAULT_BLOCKS I ZE, которая указывает на то, что звуковые данные будут воспроизводиться блоками по
65 536 байт. Это оптимальное значение для звуковых файлов различного размера.
В самом начале процедуры мы устанавливаем размер блока, получаем системную информацию (она будет использоваться чуть позже) и обнуляем массив заголовков Header. Обнуление в данном случае необходимо, чтобы в поля структуры случайно не попал мусор. После этого создается событие hEvent, которое будет генерироваться каждый раз, когда можно передавать драйверу звуковой карты новые данные.
Дальше идет блок try, который не совсем оптимален с точки зрения вылавливания ошибок, но очень удобен с точки зрения обучения. Внутри блока мы открываем устройство воспроизведения с ПОМОЩЬЮ функции waveoutopen. Вызов этой функции происходит внутри функции WaveOutCheck. Это означает, что WaveOutCheck проверит на корректность возвращаемое значение из waveoutopen.
Дальше идет выделение памяти для заголовка Header [0] в свойстве IpData Это свойство должно указывать на звуковые данные в памяти. Выделение происходит с помощью функции VirtualAlloc. В качестве второго параметра этой функции нужно передать размер выделяемых данных. Обратите внимание, что памяти выделяется достаточно много и ее хватает для хранения двух блоков воспроизведения:
Blocksize*2+специальные корректировочные данные
Корректировочные данные нужны для того, чтобы память была выровнена постранично. Если вы не собираетесь заниматься системным программированием, то это не обязательно понимать. Со временем все придет. А вот с приемом блока данных двойного размера лучше разобраться. Его используют очень часто, когда нужно переключать какую-то область памяти (для звука, видео и др.).
Итак, под первый заголовок мы выделили размер памяти, достаточный для блока двойного размера. Но реально использовать будем только его половину. Просто ipData второго заголовка будет указывать на вторую половину области памяти:
Header[1].IpData:=Header[0].IpData+BlockSize;
Здесь мы говорим, что свойство ipData второго заголовка должно указывать на вторую половину выделенной памяти для первого заголовка. Адрес памяти IpData плюс размер блока будет равняться как раз середине выделенной памяти. Таким образом, мы будем использовать один большой участок памяти в виде использования по очереди двух маленьких.
Следующим этапом мы должны указать размер блока данных в заголовке в свойства dwBuf ferLength. После этого заголовки готовы, и их можно подготавливать К воспроизведению С ПОМОЩЬЮ функции waveOutPrepareHeader-
Перед началом воспроизведения нужно еще установить переменную curr block в 0. Через эту переменную мы будем вычислять, какой блок сейчас воспроизводится и какой будет следующим. И еще, если размер звуковых данных меньше блока данных, который мы будем отправлять драйверу, то событие hEvent можно отключить с помощью функции Reset Event. Эго потому, что данные воспроизведутся одним блоком и событие не понадобится.
Теперь можно начинать цикл воспроизведения. Он будет выполняться, пока текущая позиция воспроизведения меньше звуковых данных, т. е. пока драйверу не отправим все данные:
while SounderForm.w_PlayPosition<SounderForm.DataSize do
В цикле мы должны определить, какой блок сейчас должен быть активным. Эго делается достаточно просто: curHdr:=Header[curr_block and 1];
Если переменная curr_block четная, то в переменную curHdr попадет нулевой заголовок, иначе это будет первый заголовок в нашем массиве Header.
Теперь мы должны проверить, если оставшееся количество данных меньше блока, то установить размер блока равным количеству оставшихся данных: if BlockSize>SounderForm.DataSize-SounderForm.w_PlayPosition then
Blocksize:=SounderForm.DataSize-SounderForm.w_PlayPosition;
Далее увеличиваем переменную curr_block, чтобы на следующем этапе цикла мы работали уже со следующим заголовком.
Теперь устанавливаем в свойство dwBuf f er Leng th текущего заголовка размер блока данных. Некоторые программисты упускают это и думают, что блок данных всегда равен заранее принятой константе. В нашем случае это DEFAULT_BLOCKS工ZE, т. е. 65 536. Это так, но не всегда. Если последний блок звуковых данных меньше этого значения, то необходимо указать реальное количество оставшихся данных, иначе в конце воспроизведения можно услышать мусор. Вспомните, свойство dwBufferLength мы можем только уменьшать, что и произойдет на последнем шаге воспроизведения.
Сейчас мы должны узнать позицию данных, которые мы должны воспроизвести. Для этого нужно прибавить текущую позицию воспроизведения к смещению звуковых данных в файле:
Sounde гFo rm.w_PlaуРоs i tion+Sounde rForm.DataOffset
Эти данные снова корректируются с учетом системных параметров страниц памяти.
Теперь мы знаем позицию данных в файле, которые мы должны воспроизвести. Сами данные файла у нас отображены в памяти (мы создали карту файла в памяти при его загрузке), и мы можем получить к ним доступ следующим образом:
р:=MapViewOfFile(SounderForm.w„hData,FILE_MAP_READ,
ofs shr 32 and $FFFFFFFF, ofs and $FFFFFFFF, Size);
У функции MapViewOfFile следующие параметры:
口 hFileMappingObj ect — объект, который отображен в памяти (это наш файл);
口 hFileMappingAttributes — атрибуты доступа (здесь мы используем значение по умолчанию);
П dwDesiredAccess — режим доступа (нам данные нужны только для чтения РAGE_READON LY);
О dwFileOf f setHigh и dwFileOf fsetLow —— в сумме дают смещение в файле (первый байт мы изначально задали нулевым, а вот второй указывает рассчитанное нами смешение);
П dwNumberOfBytesToMap — количество считываемых данных.
Значение, которое вернет функция, будет указывать на блок считанных данных. Эти данные нужно скопировать в свойство LpData заголовка с помощью функции СоруМешогу. У этой функпии три параметра — куда, откуда и сколько данных нужно копировать.
После копирования нужно освободить считанные из карты файла данные с помощью функции UnMapViewOfFile. Этой функции требуется передать только одно значение — указатель на считанные из карты данные.
Теперь в заголовок загружены звуковые данные, и мы можем их передать драйверу. Для этого вызываем функцию waveoutwrite. Записав данные, мы вызываем функцию WaitForSingieObject. Эта функция ждет наступления события. В качестве первого параметра нужно указать переменную события (эту переменную мы проинициализировали в начале), а в качестве второго параметра — сколько времени в миллисекундах нужно ждать.
Если за указанное время событие не наступило, то функция вернет значение wait_timeout. Если это так, то нужно проверить — не нажата ли сейчас пауза. Если нет, то, видимо, произошла ошибка воспроизведения. Если пауза нажата, то снова вызовется функция WaitForSingieObject для ожидания события, но в качестве второго параметра уже указано INFINITE. Теперь мы будем ждать события бесконечно, потому что знаем, что ошибок нет, а просто нажата пауза.
Мы ожидаем срабатывания события, чтобы узнать, когда драйвер будет готов принять следующий блок данных. Как только очередные данные попадут в буфер звуковой карты и начнут воспроизводиться, событие будет сгенерировано и цикл продолжит выполнение и передаст очередной заголовок с данными драйверу звуковой карты.
Дальше идут проверки, если нажата кнопка останова, то нужно прекратить воспроизведение. Если рестарт, то нужно перезапустить. В самом конце
цикла нужно увеличить текущую позицию воспроизведения на количество байт в блоке:
SounderForm.w_PlayPos i t ion:=SounderForm.w_PlayPosition+BlockSize
После воспроизведения мы делаем еще одно ожидание события в течение 500 миллисекунд, чтобы дождаться окончания воспроизведения последнего блока. После этого вызывается функция ClosePlay, в которой уничтожаются заголовки, и завершается воспроизведение.
Таким образом, поочередно записывая в два заголовка звуковые данные, мы можем воспроизвести файл любого размера и при этом затратим минимум ресурсов операционной системы. Пример получился немного сложным, но очень эффективным, нетребовательным к ресурсам и достаточно масштабируемым.
工 Примечание
На компакт-диске в папке \ Примеры \ Глава 20 \ Play Wav вы можете увидеть пример этой программы.
Выбор устройства воспроизведения или записи
В этом разделе главы мы улучшим пример воспроизведения файла и начнем знакомиться с функциями, которые могут записывать цифровой поток. Наш прошлый пример умеет воспроизводить звук, но только на устройстве, установленном по умолчанию. У вас может быть несколько звуковых плат и пользователь должен иметь право выбора. Вот именно это мы и улучшим в нашей программе.
В компьютере может быть несколько устройств воспроизведения, точно так же, как и устройств записи. Чтобы узнать количество устройств воспроизведения, МОЖНО ВОСПОЛЬЗОВаТЬСЯ функцией WaveOutGetNumDevs. ВОЗМОЖНОСТИ КОНКреТНОГО устройства МОЖНО уЗНаТЬ С ПОМОШЬЮ waveOutGetDe-^aps. ЭТО касается воспроизведения. Для устройств записи существуют такие же функции, которые мы еще не рассматривали. Сейчас нам это предстоит.
Начнем мы с WaveOutGetNumDevs. Она выглядит так:
function WaveOutGetNumDevs: UINT; stdca.l±;
Функция не имеет параметров, а только возвращает количество установленных в системе устройств воспроизведения.
Для получения количества устройств записи используется wavelnGetNuml 'uv:: ^unction wavelnGetNumDevs : UINT; stdca.ll;
22 Зак 942
Эта функция так же не имеет параметров, а только возвращает количество установленных в системе устройств записи. Разница в названиях этих двух функций только в замене слова Out (выход) на In (вход). Большинство (если не все) функций записи можно получить простой заменой out на In. Параметры в большинстве случаев одинаковы.
Теперь научимся получать информацию об установленных устройствах воспроизведения с помощью waveOutGet DevCaps. Эту функцию мы использовали в прошлый раз (когда изучали реальный пример), но сейчас рассмотрим ее внешний вид, чтобы вспомнить, о чем идет речь.
function waveOutGetDevCaps (
uDevicelD: UINT;IpCaps: PWaveOutCaps;uSize: UINT):MMRESULT; stdcall;uDevicelD: UINT;IpCaps: PWaveOutCaps;uSize: UINT):MMRESULT; stdcall;//Указатель на устройство воспроизведения
uDevicelD: UINT;
IpCaps: PWaveOutCaps;
uSize: UINT
):MMRESULT; stdcall;
uDevicelD: UINT;
IpCaps: PWaveOutCaps;
uSize: UINT
):MMRESULT; stdcall;
//Структура, куда будут записаны данные
//Размер структуры
Посмотрите на описание waveinGetDevCaps ниже, и все станет понятно, потому что эти функции очень похожи. А сейчас перейдем к функции, с помощью которой можно получить информацию об устройствах записи звука wave inGetDevC ap s:
function waveinGetDevCaps (
hwo: HWAVEOUT;. //Указатель на ус тройство записи
IpCaps : PWavelnCaps; / / Структура, куда будут записаны данные uSize: UINT//Размер структуры
):MMRESULT; stdcall;
С первым параметром уже должно быть все ясно. Второй параметр — струк
так:/ /Идентификатор ггроизводителя/ /Идентификатор устройства//Версия драйвераof AnsiChar; / /Название устройства/ /Поддерживаемые форматы//Количест во каналов//Свойстватак:/ /Идентификатор ггроизводителя/ /Идентификатор устройства//Версия драйвераof AnsiChar; / /Название устройства/ /Поддерживаемые форматы//Количест во каналов//Свойстватура типа Т Wave inCaps. Она выглядит TWaveOutCaps = t agWWOUTCAPSA; tagWAVEOUTCAPSA 二 record
так:
/ /Идентификатор ггроизводителя
/ /Идентификатор устройства
//Версия драйвера
of AnsiChar; / /Название устройства
/ /Поддерживаемые форматы
//Количест во каналов
//Свойства
так:
/ /Идентификатор ггроизводителя
/ /Идентификатор устройства
//Версия драйвера
of AnsiChar; / /Название устройства
/ /Поддерживаемые форматы
//Количест во каналов
//Свойства
wMid: Word;
wPid : Word ;
vDriverVers ion : MMVERSION;
szPname: array [0. .MAXPNAMELEN-1 ] dwFoirmats: DWORD; wChannels: Word;
dwSupport: DWORD;
end;
Параметр Support может принимать значения:
О wavecaps_lrvolume — поддерживается изменение правого и левого каналов; 口 WAVE CAPS PITCH — поддерживаются подтяжки;
G wavecaps_playbackrate — поддерживается playback rate;
□ wavecaps sync — драйвер синхронный и будет блокировать работу, пока играет музыка;
О wavecapsjzolume — поддержка громкости;
口 WAVECAPS_SAMPLEACCURATE — Возвращает ПОЗИЦИЮ.
Параметр wchannels может быть 1 (для моно) и 2 (для стерео). Параметр dwFormats может быть комбинацией поддерживаемых значений форматов. Все ЭТО МЫ уже Видели ДЛЯ фунКЦИИ wave I nGe t DevCaps.
Вот и все функции, которые надо знать на данный момент. Давайте теперь перейдем к реальному примеру.
Рис. 20.6. Окно выбора устройств
Сейчас мы будем модернизировать пример из предыдущего раздела главы. Основные изменения коснулись создания новой формы, в которой можно выбирать устройство воспроизведения и записи (рис. 20.6). Для заполнения параметров измените функцию FillDevice. Ее обновленный код вы можете увидеть в листинге 20.7.
Листинг 20.7. функция определения устройств воспроизведения
procedure TSounderForm.FillDevice;
var
Num, i: Integer;
WaveCaps:TWaveOutCaps;
pwoc:TWaveOutCaps;
begin
//Очищаю CoinboBox, в котором хранится информация об устройствах //воспроизведения
SoundOptionForm.AudioDeviceCBox-Items.Clear;
//Запрашиваю инфу о количестве устройств воспроизведения Num: —waveOu tGe tNumDevs;
/ /Запрашиваю параметры каждого
for i:=0 to Num-1 do
begin
/ /Получить информацию об устройстве воспроизвеления i WaveOutGetDevCaps(i r QWaveCaps,sizeof(WaveCaps));
//Добавить новый элемент
SoundOpt i onForm. AudioDevi ceCBox. I terns. Add (WaveCaps. s z PName);
end;
//Добавить новый элемент в ComboBox устройство по умолчанию
SoundOptionForm. AudioDeviсeCBox. I terns. Add (' Автомат. .-чески выбрать устр-во воспроизведения');
//Очищаю ComboBox, в котором хранится информация об устройствах записи SoundOpt i onForm. RecDevic eCBox. I tems .Clear;
/ /Запрашиваю информацию о количестве устройств записи
Num : =waveInGe tNumDevs;
/ /Запрашиваю параметры каждого
for i :=0 to Num-1 do
begin
/ /Получить информацию об устройстве записи i
WaveInGetDevCaps(i,@WaveCaps,sizeof(WaveCaps));
//Добавить новый элемент
SoundOptionForm.RecDeviceCBox. Items .Add(WaveCaps. szPName);
end;
//Добавить новьй элемент в ComboBox устройство по умолчанию
SoundOptionForm. RecDeviceCBox. Items. Add (* Ав тематически выбрать устройство записи *);
//Это мы разбирали в прошлый раз. Здесь изменения нет
waveOutGetDevCaps (w_DeviceID, @pwoc, sizeof (pwoc));
if (pwoc.dwSupport and WAVECAPS—VOLUME) >0 then
begin
VolumeLBar. Enabled: =tme;
VolumeRBar.Enabled:=true;
end;
end;
В код события FormShow нужно добавить две строки, где устанавливаются значения по умолчанию для устройств:
procedure TSounderForm. FormShow (Sender: TObject);
begin
EnableBut(false);
RestartPlay:=false;
//Заполнить информсщию об устройствах
FiliDevice;
/ /Установить значение по умолчанию.
S oundOp t ionForm. Audi oDevi ceCBox. I temlndex:=
SoundOpt ionForm. AudioDeviceCBox. I terns. Count-1 ;
SoundOptionForm. RecDeviceCBox. Itemindex:=
S oundOpt ionForm. RecDevi ceCBox. I terns. Count-1;
end;
Выбор записывающего устройства пока не используется, это мы сделаем позже, когда разберемся с функциями записи звука. А для воспроизведения уже есть все необходимое.
Откройте исходный код этого примера в директории \ Примеры \ Глава 20 \ Play Wav и загляните в обработчик события для кнопки Коща нажали кнопку Play, и вы увидите, что там добавлены новые строчки:
//Вот здесь мы берем выбранное в настройках значение звукового устройства w_DeviceID : =SoundOpt ionForm. Aud i oDev iceCBox. I temlndex;
//Если выбран последний элемент (туда мы засунули устройство по //умолчанию), то
if SoundOptionForm. AudioDeviceCBox. Itemlndex=
SoundOptionForm. AudioDeviceCBox. Items. Count-1 then
/ /Изменяем значение на WAVE」MAPPER.
vCDevicelD: =Integer (WAVE_MAPPER);
Главное не забыть добавить кнопку, которая вызывает диалог с настройками устройств: procedure TSounderForm. ToolButton3Click (Sender : TObject); begin
SoundOptionForm.ShowModal; end;
По ее нажатию мы вызываем окно, которое показано на рис. 20.6.
工 Прммеианме
На компакт-диске в папке \ Примеры \ Глава 20 \ Play Wav вы можете увидеть пример этой программы.
Функции записи звука
Вот теперь о воспроизведении цифрового звука мы знаем абсолютно все, что необходимо. Пора перейти непосредственно к записи.
В принципе, вся работа с записью идентична воспроизведению. Только имена функций немного изменились и появилось несколько новых. Даже параметры многих функций одинаковы.
Как при воспроизведении, так и при записи в системе может быть несколько устройств, позволяющих выполнять такие функции. Если вы хотите узнать о характеристиках устройства записи, то можно воспользоваться функцией wavelnGetDevCaps (если вы помните, то же самое выполняет wave OutGetDevCapsOTM устройств воспроизведения).
function wavelnGetDevCaps(
uDevicelD: UINT;//Указатель на устройство затеи
IpCaps: PWavelnCaps; //Структура, куда бу取丁 за皿саны да»..1ые
uSize: UINT//Размер структуры
):MMRESULT; s匸dcall;
Второй параметр — структура WAVEOUTCAPS. Она выглядит так:
PWave InCapsA 二 ^TWave InCaps А;
PWaveInCapsW 二 ^TWavelnCapsW;
PWaveInCaps 二 PWaveInCapsA;
{$EXTERNALSYM tagWAVEINCAPSA}
tagWAVEINCAPSA 二 record
wMid: Word;//Идентификатор производителя
wpid: Word;/ /Идентификатор устройства
vDriverVersion: MMVERSION; //Версия драйвера
szPname: array [ 0 . .MAXPNAMELEN-1 ] of AnsiChar; / /Название устройства
dwFormats: DWORD;// Поддерживаемые форматы
I wChannels: Word;//Количество каналов
dwSupport: DWORD;//Свойства
end;
Как видно из текста кода функции записи, здесь все то же самое, что и при воспроизведении, только теперь указатели и идентификаторы относятся к устройству записи.
Чтобы узнать количество установленных устройств записи, используется функция wave I nGe t NumDe vs, о которой мы уже немного говорили. Функция определяется следующим образом: function wave InGetNumDevs : UINT; stdcall;
Как и при воспроизведении, здесь также нужно сначала открыть устройство записи. При открытии вы получаете указатель на устройство, который потом можно использовать при вызове остальных функций.
function wave InOpen (
IphWaveln: PHWAVEIN; //Сюда будет помещен указатель на устройство
uDevicelD: UINT; //Идентификатор устройства
lpFormatEx: PWaveFormatEx; //Адрес структуры WAVEFORMATEX
dwCallback,
//Функция, которая будет вызываться на все //сообщения
dwInstance, dwFlags: DWORD
):MMRESULT;
//Произволе ный параметр
//Флаги открытия устройства
Параметр uDevicelD (идентификатор устройства) может принимать значение wave_MAPPER. В этом случае будет использоваться устройство записи по умолчанию.
Структуру WAVEFORMATEX мы уже применяли при воспроизведении. Она не изменилась и используется здесь в том же виде и описывает параметры записываемого звука. В зависимости от поступивших данных будет установлено качество записываемого звука. Вы обязательно должны указать параметры звука, который хотите записывать.
Параметр dwFlag может принимать следующие значения:
О CALLBACK_EVENT — в dwCallback находится событие THandle, через которое будет происходить информирование о ходе воспроизведения;
О CALLBACK_THREAD — в dwCallback находится идентификатор потока;
口 callback_funct:£ON — в dwCallback находится указатель на функцию;
□ CALLBACK_WINDOW — в dwCallback указатель на окно, которому будут посылаться сообщения;
О CALLBACK NULL — В dwCallback Ничего Нет;
П WAVE_ALLOWSYNC — можно открыть устройство в синхронном режиме;
П wave_FORMAT_diRECT — запрещается преобразование данных с помощью АСМ-драйвера;
О WAVE_FORMAT_QUERY 亠 если вы установите этот параметр, то реального открытия звуковой карты не произойдет (функция проверит возможность открытия с заданными вами параметрами), это просто тестирование, как и при воспроизведении звука.
С остальными параметрами и так все ясно по комментариям. Тем более что они похожи на параметры функции открытия устройства воспроизведения.
После окончания использования устройства оно должно быть освобождено.
ДЛЯ ЭТОГО еСТЬ фуНКЦИЯ wavelnClose:
function waveInClosе(hWavein: HWAVETN): MMRESULT; stdcall;
Параметр только один — указатель на открытое устройство, полученный при выполнении функции wave InOpen.
Для записи также используются блоки данных, которые надо подготавливать и уничтожать. Для подготовки блока данных используется функция wavelnPrepareHeaderZ
function wave InPrepar eHeader (
hWaveln: HWAVEIN; //Указатель на уже открытое устройство IpWavelnHdr: PWaveHdr; //Адрес структуры WAVEHDR uSize : U工NT//Размер структуры
):MMRESULT; stdcall;
Структура WAVEHDR уже была описана, когда мы готовились к воспроизведению звука. Здесь именно она. Когда блок данных уже не нужен, его надо УНИЧТОЖИТЬ. Для ЭТОГО используется функция waveInUnPrepareHeader:
function wave I nUnprepareHeade t (
hWaveln: HWZWEIN;//Указатель на уже открытое устройство
IpWavelnH<±c: PWaveHdr; //Адрес структуры WAVEHDR uSize: UINT//Размер структуры
):MMRESULT; stdcall;
После того как блок данных подготовлен, он должен быть подключен к устройству записи. ДЛЯ ЭТОГО ИСПОЛЬЗуется функция wavelnAddBuffer.
С помощью этой функции блок данных помещается в очередь записи.
function wave I nAddBu f fer (
hWaveln: HWAVEIN; //Указатель на уже открытое устройство
IpWavelnHdr: PWaveHdr; //Адрес структуры WAVEHDR
uSize: UINT//Размер структуры
):MMRESULT; stdcall;
Параметры те же самые, что и при подготовке блока данных.
На этом пока все. Устройство открыто, блоки подготовлены и поставлены в очередь записи. Теперь можно приступить к записи звуковых данных. Для этого используется функция wavelnStart:、
function wavelnStart ( hWaveln: HWAVEIN
)•: MMRESULT; stdcall;
Единственный параметр — указатель на открытое устройство.
Внимание
Запись начнется только тогда, когда в очереди есть хотя бы один блок данных. Второй блок может быть подключен после начала записи. Если вы начнете запись без блоков данных, то данные потеряются, потому что им негде будет сохраняться.
Для окончания записи может использоваться функция waveinReset:
function wavelnReset (
hWaveln: HWAVEIN
):MMRESULT; stdcall;
Параметр тот же. После вызова этой функции запись прекращается, все блоки данных возвращаются из очереди и позиция записи устанавливается в ноль.
Для прекращения записи есть еще одна функция — wave InStop. Она также останавливает запись, но если в очереди есть хотя бы один блок данных, он останется в очереди. При последующем старте, запись будет происходить в этот блок.
function wave InStop (
hWaveIn: HWAVEIN
):MMRESULT; stdcall;
Есть еще две интересные функции, используемые при записи. Первая из них — wavelnGetErrorText. С помощью этой функции можно узнать текст сообщения о происшедшей ошибке при записи.
function wavelnGetErrorText(
ramrError: MMRESULT; //Код ошибки, полученный при записи
IpText: PChar; //Сюда поместят текст, соответствующий коду uSize: UINt//Размер буфера pszText, отведенного под текст
):MMRESULT; stdcall;
И последняя функция — waveinGet posit ion. Эта функция возвращает текущую позицию записи, function waveInGetPosition(
hWaveln: HWAVEIN; //Указатель на открытое устройство
Iplnfo: PMMTime; //Структура MMTIME, в которую поместят результат uSize: и工NT//Размер структуры MMTIME
):MMRESULT; stdcall;
На этом все. Алгоритм работы записи идентичен алгоритму воспроизведения, поэтому смотрите исходный код и разбирайтесь. Вы должны также создать пару заголовков, поместить их в очередь и ждать событие — окончание воспроизведения первого заголовка. Как только один блок будет записан, событие сработает, и пока второй блок записывается, вы можете сохранить данные из первого заголовка и снова поместить его в очередь для дальнейшей записи. Таким циклом вы можете записать любое количество данных.
Если вы читали про воспроизведение, то у вас здесь не будет проблем.
工 Примечание—]
На компакт-диске в папке \ Примеры \ Глава 20 \ Record вы можете увидеть пример этой программы.
Преобразование форматов данных
На данном этапе вы уже должны уметь воспроизводить и записывать цифровой звук. Осталось только рассмотреть, как можно преобразовывать данные из одного формата в другой. Например, вы хотите сократить размер звукового файла за счет понижения частоты дискретизации. Для этого вам понадобятся специальные функции. Информация о них мало где рассматривается, более того, в Internet она почти не освещена.
Все описываемые функции относятся к АСМ -драйверам (драйвер Audio Compression Manager). Если в компьютере есть звуковая карта, и она проинсталлирована ,то драйверы присутствуют. Только заголовочных файлов для них нет.
Внимание
На компакт-диске в папке \ Заголовочные файлы \АСМ вы найдете необходимый файл msacm.pas. Его нужно переписать в любую папку, в которой его най-
дет Delphi, или в туже папку, где находится исходный код программы, написанный на языке Delphi.
При преобразовании данных используется АСМ-менеджер, который обеспечивает доступ к универеальным данным. Давайте перейдем к рассмотрению функций этого менеджера и в процессе этого познакомимся с его работой.
Первая функция самая простая — acmGetVersion. Она возвращает версию менеджера. Функция выглядит так:
function acmGetVersion : DWORD; stdcall;
Никаких параметров. Функция только возвращает два байта в формате ВВННСССС:
П ВВ — универсальный верхний номер версии;
П НН — нижний номер версии;
П СССС — номер сборки.
Если номер сборки отличен от нуля, то это отладочная версия. Если он равен нулю, то это уже окончательная версия.
Для получения информации о параметрах АСМ-менеджера используется функция acroMetrics:
function acmMetrics (,
hao : HACMOBJ;
uMetric : UINT;
var pMetric
):MMRESULT;
Функция имеет следующие параметры:
П hao — объект, о котором надо получить информацию (может быть равен нулю);
П uMetric — этот параметр может принимать значения:
ACm__metric_COUNT_drivers — количество АСМ-драйверов, установленных в системе;
ACM—METR工 C_COUNT_CODECS — количество установленных кодеков;
ACM METRIC COUNT CONVERTERS ——количество установленных конверте- 、Р°в из одного формата в другой;
acm_metr 工 C_COUNt_f 工 lters — количество установленных фильтров;
ACM_METRI C_COUNT_DI SableD —количество отключенных драйверов;
acm_metr工 C_COUNT_hardware — количество аппаратных драйверов;
acm_metric_count_local_drivers ——количество локальных АСМ-драй-
веров, установленных в системе;
acm_metri c_count_local_codecs — количество локальных у станов^ ленных кодеков;
acm_metric_count_local_converters - количество установленных локальных конвертеров из одного формата в другой;
ACM_METRIC_COUNT_LOCAL_FI LTERS - КОЛИЧССТВО ЛОКЗЛЬНЫХ уСТаНОВленных фильтров;
ACM_HETRIC_COUNT_LOCAL_DISABLED - КОЛИЧССТВО ОТКЛЮЧеННЫХ Л0- кальных драйверов;
acm_metric_hardware_wave_ineut — идентификатор драйвера устройства ввода, с которым связан АСМ-драйвер;
acm_metri c_hardware_wave_out put — идентификатор драйвера устройства вывода, с которым связан АСМ-драйвер;
acm_№TRI C_max_s I ze_format — максимальный размер структуры WAVEFORMATEX;
ACM—METR 工 C—MAXS 工 ZEF 工 lter — максимальный размер структуры WAVEFILTER;
acm_metric_driver_SUpport — флаги поддерживаемых возможностей устройства;
acm_metric__driver_priority — приоритет драйвера;
П pMetric — сюда будет записан результат запроса.
Для того чтобы получить все установленные АС М -драйверы, используется функция acmDriverEnum:
function acroDriverEnum (
fnCallback : ACMDRIVERENUMCB;
dwlnstance : DWORD;
fdwEnum : DWORD
):MMRESULT; stdcall;
Рассмотрим ее параметры.
□ FnCallback — адрес функции, которой будет передаваться информация о драйверах;
П dwlnstance — произвольный параметр;
П fdwEnum — флаги перечисления, которые могут:
ACM DR工VERENUMF NOLOCAL —не перечислять локальные драйверы;
AC^_DPIVERENUMF_DISABLED — будут перечисляться даже запрещенные
драйверы.
Функция обратного вызова должна выглядеть следующим образом:
function Имя_Функции( hadid : HACMDRIVERID; dwlnstance : DWORD; fdwSupport : DWORD ):BOOL; Stdcall;
Здесь:
□ hadid — идентификатор ACM -драйвера;
口 dwlnstance — дополнительный параметр;
П fdwSupport — флаги, указывающие на назначение драйвера:
acmdr工verdeta工 ls_supportf_codec — означает, что этот драйвер относится к кодекам;
ACMDRIVERDETAILS_SUPPORTF_CONVERTER — ПОЗВОЛЯСТ Конвертировать данные;
ACMDRIVERDETAILS_SUPPORTF_FILTER — Драйвер ДЛЯфиЛЬТраЦИИ ДНННЫХ;
ACMDRIVERDETAILS_SUPPORTF_HARDWARE — ЭТОЗППараТНЫЙ Драйвер;
ACMDRIVERDETAILS_SUPPORTF_ASYNC — ДрайверМОЖСТ работать ВЗСИН- хронном режиме;
acmdriverdetails_supportf_local — этолокальный драйвер;
ACMDRIVERDETAILS_SUPPORTF_DISABLED —Драйвер ОТКЛЮЧСН.
Перед использованием драйвера его нужно открыть:
function acmDriverOpen (
var phad : HACMDRIVER; / /Сюда будет исмещен указатель зкзе» -ляра драйвера
hadid : HACMDRIVERID;//Идентификатор открываемого драйвера
fdwDpen : DWORD/ /Зарезервировано г должно быть ноль
J : MMRESULT; stdcall;
Для закрытия драйвера используется функция acmDriverClose:
function acmDriverdosе (
had : HACMDRIVER; / /Указатель на устройство, полученный при открытии fdwClose : DWORD //Зарезервировано. Должен быть ноль ):MMRESULT; stdcall;
Чтобы получить дополнительную информацию о драйвере, можно использовать функцию acmDriverDetails: function acmDriverDetails (
hadid : HACMDRIVERID; / /Указатель на устройство, полученный при открытии
var padd : TACMDRIVERDETAILS; //Структура, куда будет загтасан результат fdwDetails : DWORD/ /Флаги r указывающие на получаемую инф.
):MMRESULT; stdcall;
Не будем больше тратить время на общие функции. Этой информации вам будет достаточно для начального понимания и самостоятельного изучения. Более глубокое изучение возможности АСМ вам понадобится только тогда, когда вы сами захотите написать свой драйвер.
Сейчас я перейду к тем функциям, которые нам понадобятся для преобразования данных. Первая функция выводит на экран стандартное диалоговое окно для выбора звукового формата, в который вы хотите конвертировать данные:
function acmFormatChoose (
var pafmtc : TACMFORMATCHOOSE //Указатель на с тру туру ACMFORMATCHOOSE
):MMRESULT; stdcall;
Структура ACMFORMATCHOOSE выглядит следующим образом:
TACMFORMATCHOOSEA 二 packed record
obstruct
:DWORD;
fdwStyle
hwndOwner
:EWORD;
:HWND;
pwfx
cbwfx
:PWAV^ORMATEX;
:EWORD;
pszTitle
:LPCSTR;
szFormatTag :
array[0. .ACMFORMATTAGDETAILS_FORMATTAG_CHARS-1 ] of char;
szFormatr
array[0. .ACMFORMATDETAILS_FORMAT_CHARS-1 ] of chax;
pszName
cchName
:LPSTR;
:DWORD;
fdwEnum
:EWORD;
pwfxEnvm
Mnstance
:PWAVEFORMATEX;
:THandle;
pszTemplateName : LPCSTR;
iCustData
:LPARAM;
pfnHook
:ACMFORMATCHOOSEHOOKPROCA;
end;
Рассмотрим параметры структуры.
口 obstruct — размер структуры acmformatchoosea.
П fdwstyle — флаги, определяющие стиль и возможности окна. Может принимать значения:
acmformatchoose_stylef_showhelp — у окна будет кнопка Справка (Help);
ACMFORMATCHOOSE—STYLEF—ENABLEHOOK — ПОЛе pfnHook СОДерЖИТ адрес функции-ловушки сообщений. Этой функции будет передаваться сообщение MM_ACM_FORMAT CHOOSE ;
ACMFORMATCHOOSE_STYLEF_ENABLETEMPLATE - ОКНО будет ВЬГГЛЯДеТЬ на основе шаблона, указанного В ПОЛЯХ hlnstance И pszTemplateName;
ACMFORMATCHOOSE_STYLEF_ENABLETEMPLATEHANDLE - В ПОЛе hlnstance находится адрес шаблона, поле pszTemplateName игнорируется;
ACMFORMATCHOOSE_STYLEF_INITTOWFXSTRUCT 一 В pwfx ННХОДИТСЯ ИНформация, которая будет использоваться в качестве значения по умолчанию;
ACMFORMATCHOOSE_STYLEF_CONTEXTHELP — будет Присутствовать КНОПКа контекстной подсказки.
hwndOwner — идентификатор родительского окна для создаваемого окна.
П pwfx — структура wave FORMATEX, в которой находится значение по умолчанию.
П cbwfx — размер структуры WAVEFORMATEX из параметра pwfx.
pszTitle — заголовок окна выбора.
П szFormatTag — здесь будет возвращаться строка с описанием типа формата.
口 szFormat — здесь будет возвращаться строка с описанием формата.
П pszName — будет возвращаться строка, содержащая имя выбранного формата. Желательно, чтобы строка была больше 128 байт.
П cchName — ДЛИНЭ ПОЛЯ pszName.
П fdwEnum — флаги, указывающие на то, что должно содержатся в списке выбора. Здесь могут быть следующие значения:
ACM_FORMATENUMF_WFORMATTAG — В СПИСОК ВКЛЮЧНЮТСЯ ТОЛЬКО форматы С ТИПОМ, указанным В pwfxEnum;
ACM_FORMATENUMF_NCHANNELS — В СПИСОК ВКЛЮЧНЮТСЯ ТОЛЬКО форматы, содержащие такое же количество каналов, как и в структуре WAVEFORMATEX, указанной В pwfxEnum;
ACM_FORMATENUMF_NSAMPLESPERSEC - В СПИСОК ВКЛЮЧНЮТСЯ ТОЛЬКО форматы, содержащие такое же количество выборок в секунду (nSamplesPerSec), как и в структуре WAVEFORMATEX, указанной в pwfxEnum:
ACM_FORMAT ENUM F_WB ITSPERSAMPLE - В СПИСОК ВКЛЮЧЗЮТСЯ ТОЛЬКО форматы, содержащие такое же количество байт в выборке, как и в Структуре WAVEFORMATEX, указанной В pwfxEnum;
АСМ_ formatenumf convert — в список включаются только форматы, которые можно получить из указанного в pwfxEnum;
ACM_FORMATENUMF_SUGGEST — В СПИСОК ВКЛЮЧЗЮТСЯ ВСе форматы, КОторые можно получить из указанного в pwfxEnum. (я не понял разницы с предыдущим параметром);
ACM_FORMATENUMF_HARDWARE — В СПИСОК ВКЛЮЧаЮТСЯ форматы, ПОДдерживаем ые аппаратно;
acm_formatenumf_input - в список включаются форматы, поддерживаемые при записи звука.
acm_formatenumf_output — в список включаются форматы, поддерживаемые при воспроизведении звука.
口 pwfxEnum — структура wave FORMATEX. Ее назначение зависит от предыдущего параметра и описано там же.
□ hlnstance — указатель на память, содержащую шаблон диалога.
口 pszTemplat eName — ИМЯ шаблона.
П ICustData — произвольный параметр.
П pfnHook — адрес функции-ловушки.
В принципе, существуют все необходимые функции для того, чтобы самому узнать все форматы и сделать подобное окно. Но их описзние зэймет очень много времени и места.
Сейчас перейдем к рассмотрению функций, которые непосредственно необходимы для преобразования данных.
В процессе преобразования используются потоки. Вы, нэверно, понимаете, что исходные данные и конечные могут отличаться в размере. Например, если вы преобразовываете WAV-формэт в MP3 (если установлен соответствующий конвертер), уменьшаются результирующие данные в несколько раз. Это необходимо учитывать при преобразовании и правильно рас считывать размеры потоков.
Сначала рассмотрим алгоритм преобрззовзния, а потом перейдем к изучению необходимых функций.
Открывается ПОТОК. ДЛЯ ЭТОГО ИСПОЛЬЗуеТСЯ функция acmStreamOpen.
Подготзвливаются два блока данных. Размер исходного выбирается по желанию. Размер результирующего блока можете выбрать или подсчи- ТЗТЬ, НО лучше узнать С ПОМОЩЬЮ фуНКЦИИ acmStreamSize.
Заполняется структура acmstreamheader. В нее заносятся данные о блоках.
Структура ACMSTREAMHEADER подготавливается с помощью acmStreamPrepareHeader.
Исходный блок заполняется данными и происходит преобразование с помощью acmSt reamConvert. Полученные данные сохраняются.
По окончании преобразования освобождаем все выделенные данные с ПOMOЩЬЮacmStreamUnprepareHeaderИacmStreaInClose.
А теперь перейдем к изучению функций. Начнем с acmS t reamOpen, которая открывает поток для начала преобразования данных: function acmStreamOpen (
phas : PH ACM S TRE AM ;
had : HACMDRIVER;
const pwfxSrc : TWAVEFORMATEX;
const pwfxdst : TWAVEFORMATEX;
pwfltr : PWAVEFILTER;
dwCalIback : DWORD;
dwlnstance : DWORD;
fdwOpen : DWORD
):MMRESULT; sEdcall;
Как всегда, подробно рассмотрим на параметры:
П phas — сюда будет помещен результат (указатель на поток);
П had — драйвер, используемый при преобразовании (если указать ноль, то будет использоваться драйвер по умолчанию);
П pwfxSrc — структура WAVEFORMATEX, в которой содержится информация об исходных данных;
pwfxDst — структура WAVEFORMATEX, в которой содержится информация о результирующих данных;
pwfltr — структура WAVE FILTER описывает фильтр, с помощью которого будет происходить преобразование (если вы производите не фильтрацию, то сюда нужно установить ноль);
П dwCallback — функция обратного вызова;
О dwlnstance — произвольный параметр;
П fdwOpen — флаги открытия потока, которые могут быть:
ACM STREAMOPENF_QUERY — Проверяется ВОЗМОЖНОСТЬ ОТКРЫТИЯ (реально ничего не открывается, происходит только проверка);
ACM STREAMOPENF ASYNC - открытие ПРОИСХОДИТ В фоНОВОМ (асинхронном) режиме;
acm_streamopenf_nonrealt工me — производить нормальное преобразование (если параметр не указать, то преобразование буцет происходить в реальном времени и качество результата может оказаться немного хуже);
CALLBACK EVENT — обратный вызов буцет происходить через функцию, которая указана в параметре dwCallback;
CALLBACK-FUNCTION — обратный вызов будет происходить через функцию;
Callbaci^_w I ndow — обратный вызов будет происходить через оконные сообщения.
Сразу рассмотрим, как закрыть поток: function acmStreamClose( has : HACMSTREAM; fdwClose : DWORD ):MMRESULT; Stdcall;
Здесь:
has — указатель на открытый поток, который был получен при открытии; О fdwClose — зарезервировано.
Внимание
При закрытии драйвера очередь преобразования должна быть пуста.
Следующая функция — acmStreamSize, с помощью которой можно узнать необходимый размер результирующего потока.
function acmStreamSize(
has : HACMSTREAM;
cbIприt : EWORD;
var pdwOutputByt e : DWORD; fdwSize : DWORD
):MMRESULT; Stdcall;
В функции:
口 has — указатель на открытый поток;
口 cblnput — размер данных исходного или конечного потока (зависит от флагов);
П pdwOutputBytes — адрес переменной, куца буцет записан результат;
П fdwSize — флаги, которые в свою очередь могут быть:
ACM—STREAMS 工ZEF—SOURCE — поле cblnput должно содержать размер исходных данных, а в pdwOutputByte буцет записан размер результирующих данных;
ACM__STREAMSIZEF_DESTINATION — полесЬinput должно содержать размер результирующих данных, а в pdwOutputByte будет записан размер исходных данных.
Теперь переходим к подготовке заголовка:
function acmStreamPrepareHeader(
has : HACMSTREAM;
var pash : TACMSTREAMHEADER;
fdwPrepare : DWORD
):MMRESULT; stdcall;
Здесь:
□ has — указатель на открытый поток;
О pash — адрес струьсгуры ACMSTREAMHEADER (она должна быть уже заполнена; все, что не заполнено, должно быть равно нулю);
П fdwPrepare — зарезервировано, должно быть равно нулю.
Теперь струБсгура ACMSTREAMHEADER:
PACMS TREAMHEADER =
^TACMSTREAMHEADER;
TACMSTREAMHEADER 二
packed record
obstruct:
:DWORD;
fdwStatus
DWORD;
dwUser
DWORD;
pbSrc
PBYTE;
cbSrcLength
DWORD;
cbSrcLengthused
DWORD;
dwSrcUser
DWORD;
pbDs 匸
PBYTE;
cbDstLength
DWORD;
cbDstLengthUsed
DWORD;
dwDstUser
DWORD;
dwReservedDriver
array [0 . .9] of DWORD;
end;
В этой структуре:
П obstruct — размер структуры;
П fdwstatus — флаги состояния преобразования (используются системой), имеющие следующие значения:
ACMSTREAMHEADER STATUSF DONE — указывает на то, что преобразование окончено;
ACMSTREAMHEADE& STATUS J PREPARED — блокДИННЫХ ПОДГОТОВЛеН;
• acmstreamheader_statusf_inqueue — блок находится в очереди Преобразования;
П dwUser — дополнительные произвольные данные;
口 pdSrc — указатель на исходные данные;
П cbSrcLength — длина исходного блока данных;
П cbsrcLengthused — после преобразования здесь будет количество преобразованных данных;
П dwsrcuser — произвольные данные;
П pdDst — указатель на результирующие данные;
П cbDstLength — размер памяти под результирующие данные;
П cbDstLengthUsed — размер преобразованных данных;
П dwDstUser — произвольные данные;
П dwResеIvedDriveг — зарезервировано (никогда не изменяйте эти данные).
Теперь поговорим про уничтожение данных. Как мы уже знаем, для этого используется функция acmStreamUnprepareHeader: function acmStreamUnprepareHeader (
has : HACMSTREAM;
var pash : tacmstreamheader ;
fdwUnprepare : DWORD
):MMRESULT; stdcall;
У этой функции:
П has — указатель на открытый поток;
П pash — адрес структуры acmstreamheader;
П fdwUnprepare — зарезервировано, должно быть равно 0.
Ну, а теперь сама функция преобразования данных: function acmStreamConvert (
has : HACMSTREAM;
var pash : TACMSTREAMHEADER; fdwConvert : DWORD
):MMRESULT; stdcall;
О Параметры функции имеют следующие значения:
О has — указатель на открытый поток;
П pash — адрес структуры ACMSTREAMHEADER;
口 fdwconvert — флаги преобразования:
acm_streamconvertf_BLOCKAL工GN — производить выравнивание (желательно использовать всегда);
ACM_STREAMCONVERTF^START — всегда указывается для первого блока данных;
ACM_stre7\mconvertf_end — всегда указывается для последнего блока данных.
Есть еще одна функция, которая останавливает преобразование данных — acmStreamClose:
7-unction acmStreamReset (
has : HACMSTREAM; fdwReset : DWORD
):MMRESULT; stdcall;
Здесь:
□ has — указатель на открытый поток;
П fdwClose — зарезервировано, должно быть ровно 0.
Вот и все. Получилось достаточно много функций и на первый взгляд все слишком сложно. Но реально логика примера не сильно будет отличаться от воспроизведения или записи. На этом заканчиваем обзор функций и можно переходить к рассмотрению примера, использующего преобразование данных.
20.9. Пример преобразования форматов данных
Для реализации преобразования взят пример, который был рассмотрен в разд. 20.7. К нему добавлена возможность преобразования. Для этого первым делом нужно установить на форму кнопку Convert, по нажатии которой будет происходить преобразование звуковых данных. Для события onclick этой кнопки нужно написать код, представленный в листинге 20.8.
^Листинг 20.8. Обработчик события onclick для кнопки Convert
procedure TSounderForm.ConvBu11onC 1 ick (Sender: TObject);
var
afc: TACMFORMATCHOOSE ;
MaxSize: Integer;
ftx: TWAVEFORMATEX ;
begin
//Закрываю файл в памяти, открьтгый с помощью CreateFilemapping Cl os eHandle (w_JiData);
/ /Обнуляю структуру a£c, которая имеет нтт TACMFORMATCHOOSE ZeroMemory(@afcr sizeof(afc));
/ /Заполняю структуру
afc.obstruct:=sizeof(afc);
MaxSize:=sizeof(TWAVEFORMATEX);
acmMetrics (0, ACM_METRIC_MAX_SIZE_FORMAT, MaxSize);
afc.pwfx:=@ftx;
try
af c.hwndOwnet:=handle;
afc.cbwfx: =MaxSize;
afc. fdwEnum: =ACM^FORMATENUMF_CONVERT;
afc. pwfxEnum : =@wfx ;
afc. pszTitle: =1 Выберете НОВЫЙ формат данных';
if acmFormatChooseA(afc) =MMSYSERR_NOERROR then
begin
//Вызываю процедуру, которая будет преобразовывать данные Conver tToFormat (@w£x, afc.pwfx);
// Вызываю процедуру, которая будет сохранять результат WavSaveToF ile;
end;
except
end;
end;
Так как файл мы открывали с помощью функции Create Filemapping, то он сейчас загружен в память, и к нему нельзя получить полный доступ. Именно поэтому первой строчкой кода мы закрываем указатель на файл, чтобы он был выгружен из памяти.
Внимание
Запомните, что после преобразования воспроизведение будет невозможно, если снова не открыть файл.
После этого мы обнуляем струьсгуру afc, которая у нас имеет тип TACMFORMATCHOOSE. Эта струюура будет хранить информацию, которую нужно отобразить в окне выбора формата преобразования. Обнуление необхо-
димо, чтобы поля, которые мы не будем использовать, были нулевыми и не содержали мусора. На рис. 20.7 вы можете видеть пример такого окна.
Рис. 20.7. Окно выбора формата, в который надо преобразовать данные
Мы хотим, чтобы в окне выбора формата отображались только те форматы, которые можно получить из открытого файла. Для этого заносим в поле fdwEnum структуры afc значение ACM_FORMATENUMF_CONVERT. Ну, а в поле pwfxEnum заносим адрес структуры wfx, которая содержит текущий формат.
После отображения окна выбора формата мы вызываем процедуру ConvertToFormat, которая показана в листинге 20.9.
Листинг 20.9. Преобразование звуковых данных
procedure TSounderForm.ConvertToFormat(oldFormat, newFormat: PWAVEFORMATEX);
var
hStream: HACMSTREAM;
Header: TACMSTREAMHEADER ;
hSRCFile, hDstFile:THandle;
SourceSize, DestSize, Flags, BytesReaded:DWORD;
Sourceoffset, DestOffset:Integer;
begin
//Открываю поток
if acmStreamOpen(©hS七ream, 0,oldFormatA, newFormatA,nil, 0f 0f ACM_STREAMOPENF_JJONREALTIME) oWMSYSERECNOERROR then begin
MessageBox(Handle, *He могу конвертировать', 'Error', MB_JOK+MB__ICONEXCLAMATION);
exit;
end;
// Создаю имя временного файла
TempWavFile: =ExtractFilePath (Application. ExeName) +' Tenpwave. dat';
/ /Обнуляю структуру заголовка.
ZeroMemory (©Header, sizeof (Header));
hSRCFile: =INVALID_HANDLE_VZkLUE;
hDstFile:二工NVAL工D_HANDLE_VALUE;
DestOffset:=0;
try
Progres sBar1.Position:=0;
Header.obstruct:=sizeof(Header);
//B качестве размера, исходного блока, беру число 64*1024
Sourcesize:=64*1024;
DestSize:=SourceSize;
//Рассчитываю размер результтирующего блока.
acmStreamSize(hStreain, SourceSize, DestSize, ACM_STREAMSIZEF_SOURCE);
/ /Выделяю память для исходного и результирующего блоков
GetMem(Header.pbSrc,SourceSize);
GetMem(Header.pbDst, DestSize);
/ /Указываю размеры блоков в заголовке
Header.cbSrcLength:=SourceSize;
Header.cbDstLength:=DestSize;
/ /Подготавливаю заголовки
if acmStreamPrepar eHeader (hStream, Header f 0) <>MMSYSERR__NOERROR then MessageBox (Handle, 1 He моту подготовить заголовкиr,
* Error', MB_OK+MB_ICONEXCLAMATION);
//Открываю WAV-файл м временный файл
hSrcFile:=CreateFile(PChar(WavFileName), GEMER工C_READ, FILE_SHARE_READ,
nil, OPEN_EXISTING, file_attribute^ARCHIVE, 0);
hDstFile: =CreateFile(PChar (TempWavFile) , GENERIC_WRIТЕf 0, nil, CREATE_ALWAYS, FlLE_ATTRIBUTE_7mCHIVE, 0);
/ /Вызываю функцию InitFile, которая записывает в временный
//файл заголовок WZW-файла. После этого нужно будет только у. / / дописать конвертированные данные
InitFile(hDstFile, newFonnat);
/ /Перейти в исходном файле на. начало звуковых данных SetFilePointer(hSrcFile, DataOffset, nil, FILE_BEGIN); SourceOffset:=0;
/ /Запускаю цикл преобразования while SourceOffsec<DacaSize do
begin
Flags: =ACM„STREAMCONVERTF_BLOCKALIGN;
//Если мы преобразуем первый блок, то к флагам нужно
//добавить флаг ACM_STRE2\MCONVERTF_START
if SourceOf fset=0 then Flags : =Flags +ACM_STREAMCONVERTP\_START;
//Если размер буфера, больше, чем осталось даь .^ых, ТО...
if Header.cbSrcLength>DataSize-SourceOffset then begin
/ /Установить размер буфера, в колкгчество оставшихся данных Header.cbSrcLength:=DataSize-SourceOffset;
//Установить флаг последнего блока
Flags: =Flags+ACM_STREAMCONVERTF_END;
end;
/ /Прочитать из файла порцию данных
ReadFi 1 е (hSrcFi 1 е, Header. pbSrc人,Header. cbSrcLength, BytesReaded‘ nil);
//Конвертировать
if acmStreamConvert (hStream, Header, Flags)<>MMSYSERR_NOERROR then MessageBox (Handle, 1 He могу конвергировать данные *,
1 Error' , МВ_ОК+МВ_工CONEXCLAMAT工。N);
//Записать результат во временный файл
WriteFile(hDstFile, Header.pbDsСЛf Header.cbDstLengthUsed, BytesReacled, nil);
/ / Увеличиваю счетчики, указывающие на позицию // данных, которые надо прочитать и преобразовать
SourceOf fsеt: =SourceOffset+Integer (Header _ cbSrcLengthUsed);
DestOf f set: =DestOf f set+Integer (Header .cbDstLengt±rused),-
// Если прочитаны все данные, то прервать чтение
if SourceOffset>DataSize then break;
if Header.cbSrcLengthUsed<=0 then break;
if Header.cbSrcLengthUsed<Header.cbSrcLength then SetFilePointer(hSrcFile, Inte
ger (Header.cbSrcLengthUsed)+SourceOffset+DataOffset f nil, FILE^BEGIN);
/ / Обновить строку процесса, преобразования
ProgressBarl.Position: =round(SourceOffset/DataSize*100);
end;
except
// Если произошла, ошибка., то сообщить об этом
on Е: Exception do
begin
MessageBox(Handle, PChar(E.Message), 'Error', MB_OK+MB_ICONEXCLAMATION);
if hSRCFile<>INVALID_J<ANDLE_VALUE then Clos eHandl e (hSrcFile);
if hDstFile<>INVALID_HANDLELVALUE then CloseHandle (hDstFile);
ProgressBarl.Position:=0;
end;
end;
// Эта. функция обновляет заголовок WZW7-файла.
UpdateWavFi le (hDs tFi 1 e, newFormat, DestOffset);
// Закрываем ACM-драйвер
acmStreairiUnprepareHeader (hStream# Header, 0);
acmStreamClose (hStream, 0);'
// Закрываем открытые файлы
if hSRCFile<>INVALIELHANDLE_VALUE then CloseHandle(hSrcFile);
if hDstFile<>INVALID_HANDLE_VALUE then
begin
CloseHandle(hDstFile);
end;
Progres sBar1.Position:=0;
// Освобождаю выделенную память
FreeMem (Header. pbSrc);
FreeMem (Header. pbDs t);
Первым делом мы открываем поток. Если его не удается открыть, то выводим сообщение об ошибке, и выполнение процедуры прерывается.
После этого создается временный файл. Это имя записывается в переменной TempWavFile. Временный файл будет храниться в той же директории, что и программа, и будет иметь имя Tempwave.dat. Все преобразованные данные будут сначала записываться в этот файл, а потом мы перенесем их в реальный WAV-файл.
Следующим этапом мы подготавливаем структуру заголовка и рассчитываем память для блоков данных, необходимых при преобразовании. После необходимых расчетов выделяем память с помощью функции Get Мет. У этой функции два параметра:
口 переменная-указатель, которая будет указывать на область выделенной памяти;
口 размер памяти, который нужно зарезервировать.
Следующим этапом подготавливаем заголовки с помощью функции acmStreamPrepareHeader. После этого мы открываем файлы. Звуковой файл открывается в режиме чтения с помощью функции CreateFile. Временный файл создается (если такого файла еще не было, то он будет создан, если был, то будет обнулен) и сразу же открывается той же функцией, но в режиме записи.
Все, подготовительные действия закончены, и запускается цикл. Он будет выполняться, пока значение переменной sourceo.ffset (которая вначале имеет значение 0) не превысит размер блока данных, который надо преобразовать (DataSize). После каждого выполнения блока цикла мы будем увеличивать переменную Sourceoffset на размер преобразованных данных.
Внутри цикла первым делом нужно установить флаги преобразования. Если мы преобразовываем первый блок, то нужно добавить к флагам ACM_STREAMCONVERTF_START, ну, а если последний, то нужно добавить ACM_S TREAMCONVERTF_END.
Далее производится чтение данных из WAV-файла. Преобразовываем их в новый формат и сохраняем во временный файл. Почему мы не можем записать их на то же место, откуда они были считаны? Да потому, что результирующий блок может иметь размер, больший или меньший исходного. Новый формат может использовать более качественное сжатие или просто делать это с большими потерями. В этом случае результирующий блок будет намного меньше исходного, при записи получатся "дыры”, которые будут содержать мусор, или усложниться процесс записи-чтения. Ну, а если результирующий блок будет больше, то при записи на то же место мы можем затереть некоторые данные, которые еще не были прочитаны, ведь мы запишем больше, чем прочитали.
Дальнейший код вы можете разобрать по подробным комментариям, а дей- ствуюший пример можно увидеть на диске.
工 Примечание
На компакт-диске в папке \ Примеры \ Глава 20 V Formats вы можете увидеть пример этой программы.
Глава 21Г рафика OpenGLГлава 21Г рафика OpenGL
Глава 21
Г рафика OpenGL
Глава 21
Г рафика OpenGL
В этой книге уже были главы, в которых рассматривалась графика, но они рассказывали про стандартный графический интерфейс ОС Wndows — GDI. Здесь мы будем говорить о принципиально другой технологии, которая используется в профессиональной графике, — OpenGL.
Это достаточно сильная библиотека, потому что она может использовать все преимущества современных графических ускорителей, работать с трехмерной графикой и строить сцены практически любой сложности. Благодаря простоте и гибкости в использовании OpenGL применяется как в сложных графических пакетах, так и в играх.
Но даже если вы не будете разрабатывать сложные графические программы или игры, знать технологию OpenGL необходимо просто в познавательных целях. Быть может вам она пригодится. В жизни может пригодиться что угодно.
Инициализация
и отображения 20-графики
Как и все другие главы, эта сразу же начнется с рассмотрения реального примера. Посмотрите на рис. 21.1. Вот такое получится изображение после выполнения кода программы примера. Изображение пока двухмерное, но это не главное. Для начала нам нужно разобраться с процессом инициализации OpenGL и основами работы с ним.
Для того чтобы использовать функции OpenGL, вам понадобится подключить к проекту два файла: gl.pas и glu.pas. Оба файла вы можете найти на прилагаемом к книге компакт-диске в папке \ Заголовочные файлы. Их желательно записать в отдельную директорию так, чтобы Delphi смог к ним обратиться. Затем необходимо в Delphi выбрать Environment Options (Настройки окружения) из меню Tools. В появившемся окне перейти на вкладку Library и добавить путь к PAS-файлам в строку Library Path.
pfd:TPixelFormatDescriptor;
begin1
FillChar (pfdz Sizeof (pfd) , 0) ; //Заполняем структуру pfd нулями.
pfd.nSize:=sizeof (pfd) ; //Заполняем размер структуры.
pfd.nVersion:=l; //Версия
pfd. dwFlags : =PFD_DOUBLEBUFFER+PFD_SUPPORT_OPENGL+
PFD_DRAW_TO_WINDOW; / /Флаги ксптекста
pfd. iPixelType: =PFD_TYPE_RGBA; / /Тип цвета
pfd. cColorBits :=24; //Количество цветов
pfd. cAlphaBits: =64;
pfd. cAccmnBits: =64;
pfd.cDepthBits:=32;
pfd.cStencilBits:=64;
pfd. iLayerType: = PFD _J<AIN__PLANE;
nPixelFormat:=ChoosePixelFormat(DCf @pfd);
if nPixelFormatoO then
SetPixelFormat(DC,nPixelFormat,©pfd);
end;
В разделе var у нас объявлены две переменные:
О nPixelFormat — будет использоваться для получения индекса формата пиксела;
□ pfd — структура с описанием формата пиксела.
Перед использованием структуры pfd, ее нужно обнулить. Для этого мы используем функпию FillChar — заполнить символом. У этой функции три параметра:
П указатель на память, которую надо обнулить (здесь указываем структуру);
П размер (длина) памяти (здесь указываем размер структуры);
П чем обнулять (указываем 0, потому что хотим заполнить всю структуру нулями).
Далее заполняем необходимые поля структуры, которые не должны быть нулевыми. Самое главное — указать размер в поле nsize. Это поле обязательно должно быть заполнено реальным значением и не может иметь ноль. Размер структуры определяем с помощью функции sizeOf. В поле версии — nversion указываем единицу.
Глава 21Г рафика OpenGLГлава 21Г рафика OpenGLLL
Глава 21
Г рафика OpenGL
Глава 21
Г рафика OpenGL
L
L
В этой книге уже были главы, в которых рассматривалась графика, но они рассказывали про стандартный графический интерфейс ОС Windows — GDI. Здесь мы будем говорить о принципиально другой технологии, которая используется в профессиональной графике, — OpenGL.
Эго достаточно сильная библиотека, потому что она может использовать все преимущества современных графических ускорителей, работать с трехмерной графикой и строить сцены практически любой сложности. Благодаря простоте и гибкости в использовании OpenGL применяется как в сложных графических пакетах, так и в играх.
Но даже если вы не будете разрабатывать сложные графические программы или игры, знать технологию OpenGL необходимо просто в познавательных целях. Быть может вам она пригодится. В жизни может пригодиться что угодно.
Инициализация и отображения 20-графики
Как и все другие главы, эта сразу же начнется с рассмотрения реального примера. Посмотрите на рис. 21.1. Вот такое получится изображение после выполнения кода программы примера. Изображение пока двухмерное, но это не главное. Для начала нам нужно разобраться с процессом инициализации OpenGL и основами работы с ним.
Для того чтобы использовать функции OpenGL, вам понадобится подключить к проекту два файла: gl.pas и glu.pas. Оба файла вы можете найти на прилагаемом к книге компакт-диске в папке \ Заголовочные файлы. Их желательно записать в отдельную директорию так, чтобы Delphi смог к ним обратиться. Затем необходимо в Delphi выбрать Environment Options (Настройки окружения) из меню Tools. В появившемся окне перейти на вкладку Library и добавить путь к PAS-файлам в строку Library Path.
Рис. 21.1. Окно будущей программы
Можно поступить еще проще — установить файлы gl.pas и glu.pas в ту же папку, где находятся файлы с исходным кодом вашей программы. В этом случае Delphi точно найдет их. Однако в этом случае одни и те же файлы будут находиться в нескольких местах диска компьютера, что не совсем экономично.
В принципе, для обучения можно обойтись без этих файлов, а просто подключать в разделе uses модуль opengl. Этот модуль входит в поставку Delphi, и можно быть уверенными, что он уже установлен на компьютере и компилятор Delphi без проблем найдет его. Большинство примеров из этой книги будут работать и с этим модулем, но между opengl.pas и gkpas есть небольшие различия в названиях некоторых типов данных и процедур, поэтому иногда могут возникать проблемы. Здесь мы будем использовать файл — gl.pas.
Теперь перейдем к программированию. Создайте новый проект и подключите к нему файлы OpenGL. Для этого напишите имена заголовочных файлов OpenGL в разделе uses в начале файла.
Теперь приступим к программированию. Для начала нам понадобятся две переменные. Их вы должны объявить в разделе private:
private
{ Private declcLTcitions }
DC:HDC;
hrc:HGLRC;
Первая переменная будет хранить контекст рисования (информацию об устройстве, на котором мы будем рисовать). Вторая переменная имеет тип HGLRC — это новый тип, который нам еще не встречался, потому что он является внутренним типом библиотеки OpenGL.
Создайте обработчик события OnCreate. В нем нужно написать следующее:
DC:=GetDC(Handle);
PixelFormat;
hrc:=wgiCreat eContext(DC);
wglMakeCurrent(DC,hrc);
В первой строке кода получаем контекст рисования на форме с помощью функции GetDC. В качестве единственного параметра мы передаем функции свойство Handle. Раз не указано, какой именно Handle мы передаем, то будет использоваться указатель на текущее окно. Это значит, что будет получен контекст рисования всего текущего окна. Результат (контекст рисования) мы сохраняем в объявленной нами переменной DC.
Во второй строке вызываем процедуру, которая будет устанавливать формат пиксела. Эту процедуру мы напишем чуть позже.
Третья строка на основе полученного контекста формы создает OpenGL- контекст рисования. Это делается с помощью вызова функции wglCreateContext. У нее есть только один параметр — контекст рисования. Мы передаем полученный нами контекст рисования окна. В результате получаем OpenGL-контекст и сохраняем его в переменной hrc.
И последняя строка делает OpenGL-контекст активным. Таких контекстов может быть много, но для работы один и только один обязательно должен быть активным.
Теперь перейдем к процедуре PixelFormat.
В разделе private после объявленных переменных объявите процедуру:
procedure PixelFormat;
Теперь нажмите <Ctrl>+<Alt>+<C>, после чего Delphi создаст пустую процедуру. В ней нужно написать код, представленный в листинге 21.1.
j Листинг 21 1. Рроцедура PlxtlFoxmat
procedure TForml. PixelFormat;
var
nPixelFormat:Integer;
pfd:TPixelFormatDescriptor;
begin
FillChar(pfd,Sizeof (pfd) ,0); //Заполняемструктуру pfcl нулями.
pfd.nSize: —sizeof (pfd); //Заполняемразмер структуры. pfd.nVersion:=l; //Версия
pfd.dwFlags:=PFD_DOUBLEBUFFER+PFCLSUPPORT_OPENGL+
PFD_DRAW_TO_WIbIDOW; //Флаги китекста
pfd. iPixelType: =PFD_TYPE_RGBA; / /Тип цвета.
pfd. cColorBits:=24; //Количество цветов
pfd. cAlphaBits:=64 ;
pfd. cAccumBits: =64;
pfd.cDepthBits:=32;
pfd.cStencilBits:=64;
pfd. iLayerType: -PFD_J4AIN_PLANE;
nPixelFormat: =ChoosePixelFormat (DC, @pf d);
if nPixelFormat<>0 then
SetPixelFormat(DC,nPixelFormat,©pfd);
end;一
В разделе var у нас объявлены две переменные:
nPixelFormat — будет использоваться для получения индекса формата пиксела;
pfd — структура с описанием формата пиксела.
Перед использованием структуры pfd, ее нужно обнулить. Для этого мы используем функцию Fillchar — заполнить символом. У этой функции три параметра:
П указатель на память, которую надо обнулить (здесь указываем структуру);
П размер (длина) памяти (здесь указываем размер структуры);
П чем обнулять (указываем 0, потому что хотим заполнить вею структуру нулями).
Далее заполняем необходимые поля структуры, которые не должны быть нулевыми. Самое главное — указать размер в поле nsize. Эго поле обязательно должно быть заполнено реальным значением и не может иметь ноль. Размер структуры определяем с помощью функции Sizeof. В поле версии — nVersion указываем единицу.
Далее идет самое интересное для нас — флаги:
口 pfd_doublebuffer — флаг указывает, что при рисовании будет использована двойная буферизация;
О PFD SUPPORT OPENGL — этот флаг говорит, что окно будет поддерживать OpenGL;
pfd_draw_to_window — этот флаг говорит, что рисование будет происходить в окне.
Внимание
Параметры pfd. cColorBits.pfd. cAlphaBitsMTaKflanee можно назначать любые (например, 128). В этом случае OpenGL создаст максимально близкий по возможностям к этим значениям контекст рисования, для вывода графики средствами OpenGL
После заполнения структуры вызываем функцию ChoosePixelFormat — запрашиваем формат пиксела. В качестве первого параметра передаем контекст устройства, в гатором собираемся рисовать. Второй параметр — структура, которую мы уже заполнили. В этой структуре хранятся данные о запрашиваемом нами формате пиксела. Если возвращаемое значение не равно нулю, то все в порядке, и получен индекс установленного формата пиксела.
SetPixelFormat — устанавливает запрошенный формат пиксела. У этой функции три параметра:
口 контекст устройства;
индекс выбранного формата пиксела;
П структура pfd.
Переходим к последнему этапу подготовки к рисованию. Создайте обработчик события onPaint. В нем нужно написать следующее: glMatrixMode (GL_PROJECTION) ; // Устанавливаем матрицу glLoadldentity ( ) ; / / Загружаем единичную матрицу
if width <= height then
glOrtho (-2, 2, -2*height/width, 2*height/width, -10.0, 10.0) else
glOrtho (-2*width/height, 2*width/height, -2, 2, -10.0, 10.0);
glViewport (0, 0, width, height) ; //Область видимости на форме
Со всем, что здесь происходит, вы разберетесь по комментариям. Сейчас рассмотрим только действие функции glOrtho.
23 Зак. 942
Эта функция устанавливает область отсечения. Все точки, выходящие за эту область, будут отсечены.
glOrtho( GLdouble left, GLdouble right, GLcLoiible bottom, GLdouble top, GLdoiible near, GLdoiible far
)•;
//Левая позиция
//Правая //Нижняя //Верхняя //Ближняя //Дальняя
Теперь мы готовы к рисованию с помощью OpenGL. В обработчике OnPaint напишите код, представленный в листинге 21.2.
Листинг 21.2. Рисование,• • • •
var
ps:TPaintStruct;
begin
BeginPaint (Handle, ps) ; //Начало рисования
glClearColor (l,0.5,0.5;l); //Выставляем цвет фона
glClear (GL_COLOR_BUFFER_BIT) ; //Очищаем нарисованное ранеe
glPointSize (10); //Выставляем размер точки
glColor3f (0.0, 1.0f 0.0);//Выбираем цвет для рисования
glBegin (GL_POINTS) ; //Рисуем две точки
glVertex3f (1.25,1.25,0);
glVertex3f(-1.25,-1.25f 0);
glEnd;
glEnable(GL_POINT_SMOOTH); //Включаем режим с глаживания g 1 Begin (GL_POINTS) ; //Рисуем еще две точки
glColor3f (1.0, 0.0, 0.0);
glVertex3f(1.25,-1.25,0);
glVertex3f(-1.25,1.25, 0);
glEnd;
glColor3f (0.0, 0.0, 1.0);
glBegin(GL LINES); //Рисуем две линии
glVertex3f(1.0,-1.0,0); glVertex3f(-1.0,1.0,0); glVertex3f(1,1,0);
glVertex3f(-1,-1,0);
glEnd;
glFlush();//Конец рисования OpenGL
swapBuf fers (de) ; / /Вывести буфер на экран
EndPaint(Handle,ps);//Конец рисования
Комментарии поясняют структуру кода. И все же разберем этот код подробнее. Функция В eginPaint говорит о том, что сейчас начнется перерисовка экрана. После этого вызывается функция giciearcolor, которая задает цвет фона. Фон задается тремя переменными цвета R, G, В (красная, зеленая и голубая составляющие цвета).
Внимание
Цвет в OpenGL измеряется не от 0 до 255, а от 0 до 1. Поэтому каждая составляющая может иметь дробное значение. Минимальная интенсивность составляющей равна 0, а максимальная равна 1. Это значит, что если нам нужен красный цвет, то нужно указать 1,0,0. Для зеленого цвета указываем 0,1,0. Для получения различных оттенков цветов нужно указывать дробные значения.
Далее идет вызов процедуры giciear, которая очищает в текущей сцене то, что указано в качестве параметра. У нас указан флаг gl_color_buffer_bit, который говорит о необходимости очистить цветовой буфер.
После этого задается размер точек с помощью процедуры glPointSize. Этой процедуре нужно указать только один параметр — число, которое указывает размер точек. После этого все точки будут рисоваться указанным размером. Если не вызывать эту процедуру, то точки будут единичного размера (значение по умолчанию).
Рисование всех объектов начинается с glBegin. В скобках мы передаем значение, указываюшее на тип объекта, который будет рисоваться. Например:
glBegin (GL_POINTS); // Рисуем две точки
glVertex3f(1.25,1.25z 0); // положение точки 1
glVertex3f(-1.25,-1.25,0);// Положение точки 2
glEnd;
Если указать в функции giBegin в качестве параметра GL POINTS, значит, после этого будут рисоваться точки, пока не встретится glEnd.
Для указания координат точки используется процедура givertex3f (х, у, z), которая рисует точку в позициях X, Y, Z, переданных в качестве параметров. Между glBegin и glEnd здесь рисуются две точки.
Функция glEnable — включает и выключает специальные режимы рисования. Мы использовали эту функцию, чтобы включить сглаживание (передаем в качестве параметра GL POINT SMOOTH), после чего наши точки стали круглыми.
工 Примечание ]
Не все версии OpenGL поддерживают сглаживание. Например, в Windows 98 оно есть, а в Windows ХР сглаживания почему-то нет. Видимо, это зависит от версии файла opengl.dll.
После этого снова вызывается giBegin, только с параметром GL LINES, который означает, что до следующей giEnd будут идти координаты линий. Между giBegin И giEnd МЫ дважды вызываем Процедуру glVertex3f ДЛЯ рисования двух точек, между которыми будет проведена линия. В конструкции GL_L工NES должно быть четное число точек. Если будет три точки, то третья точка отбрасывается.
Внимание
Цвет задается с помощью функции glColor3f (интенсивность красного, интенсивность зеленого, интенсивность синего). Интенсивность цвета изменяется от нуля до 1.Это не совсем привычно для нас, ведь мы привыкли, что цвет изменяется от 0 до 255, но к этому вы быстро привыкнете.
После того как была произведена прорисовка (все примитивы сцены — точки и линии), нужно вызвать процедуру glFlush. Эта процедура окончательно прорисовывает подготовленную сцену в буфере видеопамяти.
При инициализации OpenGL мы указали флаг двойной буферизации для максимальной скорости отображения графики. Двойная буферизация говорит о том, что под содержимое экрана будут выделены две области памяти —главный и вторичный буфер.
Одна область будет отображать данные на экране (главный буфер), а во второй мы будем готовить следующую картинку (вторичный буфер). Как только следующая картинка подготовлена во вторичном буфере, мы просто меняем местами главный и вторичный буфер, и новая картинка моментально оказывается на экране.
Использование двух буферов очень удобно, потому что сцена всегда рисуется постепенно. Компьютер не может вывести на экран всю сцену целиком. Сначала мы выводим точки, потом линии, и все это делается поочередно. Если рисовать сразу в первичном буфере, то, нарисовав первую точку, она
должна будет Пр9рисоваться. После того как нарисована вторая точка, опять потребуется прорисовка. А поскольку прорисовка — одно из самых слабых и медленных мест работы графики, то такой способ отображения сцены будет не совсем эффективен, особенно при большом количестве примитивов на экране.
~ Пр-мечанме ?
При использовании двух буферов мы можем с вторичным делать все, что угодно, и информация не будет прорисовываться, пока вторичный буфер не станет главным. Зато когда это произойдет, вся сцена моментально окажется на экране без всяких задержек.
В OpenGL механизм двух буферов очень прост. Если вы запросили двойную буферизацию (как сделано в этом примере), то все отображения происходят автоматически во вторичном буфере. Для того чтобы вторичный стал главным, нужно вызвать процедуру swapBuffers и в качестве единственного параметра указать контекст рисования. Эту процедуру желательно вызывать только после того, как сцена уже окончательно сформирована и вызван glFlush. После этого вторичный буфер становится главным, а главный — вторичным.
Ну, и в последнюю очередь нужно вызвать End Paint (если вы в начале вызывали BeginPaint).
ОЬовет )
Вызов процедур BeginPaint и EndPaint является не обязательными, но желательными.
После выхода из программы нужно уничтожить все, что мы создали при старте. Для этого в обработчике события OnDistroy напишите код:
wglMakeCurrent (0,0);
wglDeleteContext(hrc);
ReleaseDC (handle fDC);
Этими процедурами мы уничтожаем контекст и освобождаем его память.
Первая программа с использованием OpenGL готова. Хотя рисунок пока еще двухмерный и не используется третье измерение, но все же пример познавателен и показывает основы программирования OpenGL. Главное тут — разобраться с инициализацией и принципом отображения данных.
К Прммеч^уме^Л
На компакт-диске в папке \ Примеры \ Глава 21 \ Точки вы можете увидеть пример этой программы.
Третье измерение и тест глубины
Мы пока познакомились с инициализацией библиотеки OpenGL и смогли нарисовать только простейший рисунок в двух измерениях. Но такое же можно было бы нарисовать с помощью стандартного интерфейса GDI, без всяких дополнительных расходов на инициализацию. Но не этим же славиться OpenGL! Сейчас мы начнем погружаться в третье измерение.
OpenGL обладает большой библиотекой, когда речь вдет о трехмерных картинках. С помощью нее можно создавать такие сцены, с которыми стандартному интерфейсу GDI практически не справиться.
Загрузите проект из предыдущего раздела главы и исправьте процедуру FormPaint так, как это показано в листинге 21.3.
Листинг 21.3 Переработанная процедура прорисовка
procedure TForml.FormPaint(Sender: TObject);
var
ps:TPaintStruct;
begin
BeginPaint(Handle,ps);
glClearColor(l,0.5,0.5,1);
glClear (GL_COLOR_BUFFEICBIT or GL_DEPTH_BUFFER_BIT);
glRotated(45,1,0,0);
g-lColor3f (0.0, 1.0, 0.0); glBegin(GL_QUAD_STRIP);
glVertex3f(1.25,1.25,1);
glVertex3f(1.25,-1.0,1);
glVertexS f (-1,1.25,0);
givertex3f(-1#-1.0,0);
glEnd;
glColor3f (1.0, 1.0, 0.0);
glBegin (GL_QUAD_STRIP);
glVertex3f(1.0,1.0,0);
glVertex3f(1.0,-1.25,0);
glVertex3 f(-1.2 5,1.0,1);
glVertex3f(-1.25,-1.25,1);
glEnd;
glFlush{);
swapBuffers (de);
EndPaint (Handle,ps);
end;
Рис. 21.2. Первое трехмерное изображение
Если вы запустите сейчас профамму, то увидите нечто похожее на рис. 21.2. Что же нового произошло? Во-первых, это очистка экрана: glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
Эта функция очищает то, что указано в качестве параметров. У нас указано два флага:
О GL_COLOR_BUFFER_В1Т — очистить от цвета;
□ gl_depth_buffer_biт — ОЧИСТИТЬ буфер глубины.
Мы включили сюда очистку буфера глубины GI^DEPTH BUFFER BIT (глубину еще не использовали).
Второе это — glRotated (g『x,y,z)_ Данная функция поворачивает все, что будет рисоваться после ее вызова, на угол д, по осям X, Y, Z. Если нужно повернуть на указанный угол по оси X, то во втором параметре должна быть
единица. Если нужно повернуть по оси Y, то в третьем параметре нужно указывать 1. Ну, а если нужно повернуть по оси Z, то единицу ставим в последнем параметре.
В нашем случае сцена повернется по оси X на угол 45°. Если нужно повернуть сцену на 45° по оси X, и 30° по оси Y, то нужно вызвать функцию glRotated дважды: glRotated(45,1.0.0);
glRotated(30,0/1,0);
Далее мы рисуем квадрат: glBegin (GL_QUM_STRIР); glVertex3f(1.0f1.0,0); glVertex3f(1.0,-1.25,0); glVertex3f(-1.25,1.0,1); glVertex3f(-1.25,-1.25,1); glEnd;
Эта конструкция рисует квадрат из четырех точек. Об этом говорит флаг GL_QUAD_STR工Р, указанный в качестве параметра функции glBegin. Так мы постепенно будем знакомиться со всеми возможными примитивами. Между giBegin и glEnd для прямоугольника (квадрата) нужно нарисовать четыре точки, по которым будет нарисован прямоугольник.
В отличие от прошлого примера, где мы не использовали у вершин параметр Z, здесь мы его используем. Поэтому наша сцена получилась трехмерной, хотя на первый взгляд это не очень заметно.
Теперь еще раз взгляните на результат работы программы — он немного неправильный. Если представить себе сцену с указанными координатами, то получится, что два наших квадрата должны пересекаться в середине, но этого не происходит. Эго связано с тем, что не включен тест глубины и сцена не учитывает третье измерение. Для того чтобы его включить, вы должны перед рисованием квадратов написать — glEnable (GL_DEPTH_TEST). Подкорректируйте свою программу так: glEnable(GL_DEPTH_TEST);
glColor3f (0。1.0, 0.0);
glBegin (GL_QUAD_STRIP);
glVertex3f(1.25,1.25,1);
givertex3f(l.25,-1.0,1);
givertex3f(-1,1-25,0);
glVertex3f(-1, -1.0,0);
glEnd;
glColor3f (1.0, 1.0, 0.0);
glBegin (GL_QUAD__STRIP);
glVertex3 f (1.0,1.0,0);
glVertex3f(l.0,-1.25,0);
glVertex3f(-1.25,1.0,1);
glVertex3f(-1.25,-1.25,1);
glEnd;
glDisable(GL_DEPTH_TEST);
Обратите внимание, что после окончания рисования мы отключаем тест глубины с помощью вызова процедуры glDisable с параметром gl_depth_test. Если glEnable включает какие-либо возможности, то glDisable отключает их.!
Рис. 21.3. Результат включения теста глубины
Теперь у вас получится правильная сцена, показанная на рис. 21.3. Для того чтобы регулировать рисунок функцией, отвечающей за тест глубины, вы можете воспользоваться процедурой glDepthFunc. В качестве параметров можно передавать следующие значения:
口 GL—NEVER — тест не выполнится никогда;
П GL LESS — тест выполнится, если текущее значение Z меньше, чем со
храненное;
口 GL EQUAL — тест выполнится, если текущее значение Z равно сохраненному;
口 GLLEQUAL — тест выполнится, если текущее значение Z меньше или равно сохраненному;
О GL GREATER — тест выполнится, если текущее значение Z больше сохраненного;
□ GL NOTEQUAL — тест выполнится, если текущее значение Z не равно сохраненному;
口 GL_GEQUAL — тест выполнится, если текущее значение Z больше или равно сохраненному;
口 GL_ALWAYS — тест выполнится всегда.
Эти функции вы можете включать после включения теста глубины. Например: glEnable(GL_DEPTH_TEST);
glDepthFunc(glLess);
Попробуйте поэкспериментировать с параметрами функции glDepthFunc и посмотреть на результат.
Теперь попробуйте запустить программу, свернуть окно, а затем восстановить его. После восстановления вы увидите совершенно другую картину. Повторите эту операцию и картина снова изменится. Почему? Давайте попробуем найти ошибку.
При каждом вое становлении окна происходит прорисовка, где сцена поворачивается на 45° и потом рисуется. Вот именно поворот и влияет на сцену. При каждой прорисовке наша сцена поворачивается и получается эффект вращения.
Перед поворотом нужно вызвать процедуру glPushMatrix. Эта процедура сохранит матрицу преобразования объектов. Изначально мы загрузили единичную матрицу glLoadldentity. Эго значит, что повороты и масштабы объектов равны единице. В предыдущем примере после каждой прорисовки наша матрица изменяется, увеличивая угол поворота. Если перед прорисовкой вызвать giPushMatrix, то она сохранит текущее состояние сцены. Потом мы можем вращать объекты как угодно. После вызова glPopMatrix все вернется на место.
Следующий код иллюстрирует работу сохранения и восстановления матрицы: giPushMatrix
Повернуть объект на 45 градусов по оси X
Нарисовать объект 1
Повернуть объект на 20 градусов по оси Y
Нарисовать объект 2
glPopMatrix
нарисовать объект 3
Если выполнить этот код, то первый объект будет развернут на 45° по оси X. Второй будет повернут на 45° по оси X и 20° по оси Y. Потом матрица восстанавливается, и третий объект будет нарисован без поворотов, выполненных между glPushMatrix И glPopMatrix.
прямей 吧 не ]
На компакт-диске в папке 1 Примеры \ Глава 21 \ Квадраты вы можете увидеть пример этой программы.
Реалистичное изображение (Туман)
Мы уже научились делать реалистичные трехмерные изображения. Но первая сцена получилась не совсем реалистичной. Тест глубины, который мы включали, проверял только отсечение поверхностей, но никак не влиял на перспективу. Что здесь имеется в виду? С удаленностью объекта изменяется теплота цвета, нет бликов от освещения и т. д. Освещение рассмотрим позже, а сейчас мы поговорим о теплоте цвета.
Когда мы смотрим вдаль, то удаленные объекты кажутся нам расплывчатыми. Этого эффекта можно добиться с помощью тумана. Что такое туман? Эго, грубо говоря, густой воздух. Чем дальше находится объект, тем более размытыми кажутся его границы. Помимо этого, цвет объекта изменяется и с уцаленностью принимает цветовые очертания тумана. Если туман серый, то с удаленностью цвет объекта принимает оттенок серого.
На рис. 21.4 вы можете увидеть сцену из предыдущего примера (два пересекающихся прямоугольника), но с эффектом красного тумана. Именно поэтому дальние грани прямоугольника получились окрашенными в красный цвет. В результате на этом рисунке вы можете наблюдать переход от реального цвета объекта в цвет тумана.
Давайте посмотрим, как рисуется туман на реальном примере. Для этого загрузите предыдущий пример и скорректируйте процедуру FormPaint, как это показано в листинге 21.4.
[Лнстнн「214 Прс^цеду肥 прорнсовкн сщены
procedure TForml.FormPaint(Sender: TObject); var
ps:TPaintStruct;
fogColor : Array [0 . . 3 ] of GLfloat;
begin
BeginPaint(Handle,ps);
glClearColor(1,0.5,0.5,1);
glClear (GL_COLOR_BUFFEf<_BIT or GL_DEPTH_BUFFER_BIT);
glRotated(45,lr0,0);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glEnable (GL__FOG);
glFogi(GL_FOG_MODE r GL_EXP2);// Выставляю режим тумана.
fogColor[0]:=1;
fogColor[1]:=0;
fogColor[2]:=0;
glFogfv (GL__FOG_COLORZ @fogColor) ; //Выставляю цвет тумана glFogi(GL_FOG_DENSITY,100);// Плотность
glFogi (GL_FOG_ENDr 10) ; / /Дальняя точка тумана
glColor3f (0.0, 1.0, 0.0);
glBegin (GLjQUATLSTRIP);
glVertex3f(1.25,1.25,1);
glVertex3f(1-25,-1.0,1);
givertex3 f(-1,1.25,0);
givertex3f(-1,-1.0, 0);
glEnd;
glColor3f (1.0, 1.0, 0.0);
glBegin (GL_QUAD_STRIP);.
glVertex3f(1.0*1.0,0);
givertex3f(1.0,-1.25,0);
glVertex3f(-1.25,1.0,1);
givertex3f(-1.25,-1.25,1);
glEnd;
glFlush ();
swapBuffers (de);
EndPaint (Handlers).;
Рис. 21.4. Эффект красного тумана
Как видите, код не сильно отличается от предыдущего. Просто здесь добавлен туман. В примере используется туман красного цвета, поэтому по мере удаления цвет объекта становится все более красным.
Туман включается все той же функцией glEnable с параметром GL FOG. С помощью функции glFog устанавливаются параметры тумана. Эта функция может быть двух типов:
口 первый тип giFogi — в качестве второго параметра выступает целое число; 口 второй тип glFogf — второй параметр — это число с плавающей запятой. Есть еще две разновидности — glFogfv, glFogiv, но все они выполняют одну и ту же задачу, поэтому рассмотрим только одну: glFogf( pname: GLenum;
param: GLfloat);
В качестве первого параметра могут использоваться различные значения. Рассмотрим их подробно.
口 gl FOG MODE — устанавливает тип тумана. В этом случае второй параметр может содержать одно из следующих значений: GL LINEAR, GL_exp или GLEXP2.
П GL FО G DENSITY — устанавливает плотность (густоту) тумана. В этом случае второй параметр — целое число, указывающее плотность тумана. По умолчанию это 1,0.
П GLFOG START или GL_FOG_END — начало и конец действия тумана. До начального значения тумана нет. После конечного туман настолько плотный, что ничего не видно. Второй параметр — целое число, устанавливающее соответствующее значение.
口 GL_FOG_工ndex — индекс цвета тумана, только если вы используете палитру.
О GL_FOG_COLOR — цвет тумана. В качестве второго параметра выступает массив из четырех чисел, символизирующих значения красного, зеленого, голубого цвета и прозрачность. У нас в программе этот массив объявлен так:
fogColor : Array[0..3] ofGLfloat;
Вот и все. Внимательно взгляните на код примера, и все будет ясно. После рисования не забывайте отключать туман glDisable (GL FOG) и тест глубины glDisable (GL_depth_test). Все это нужно делать после рисования. Сначала включили все это, нарисовали и потом отключили. Включение теста глубины и тумана очень сильно сказывается на производительности, поэтому лучше отключать их, когда они не нужны, чтобы не тратить процессорное время.
《 Примечание 】
На компакт-диске в папке \ Примеры \ Глава 21 \ Туман вы можете увидеть прииер этой программы.
Примитивная графика
Примитивыая фафика — это не значит, что она будет ужасно простой. Это значит, что сложные объекты будут состоять из большого количества простых подобъектов (примитивов). Здесь мы рассмотрим как можно больше примитивов, с помощью которых вы сможете строить сложные сцены. Для лучшего понимания вам придется вспомнить школьный курс геометрии (достаточно того, что вы проходили до 9 класса).
Вообще-то, вся OpenGL-фафика построена на примитивах. Самым примитивным является |%"ех(точка или вершина). С ней мы уже познакомились и умеем рисовать, поэтому рассматривать ее зде,сь нет необходимости. Линии и квадраты пропустим также, потому что мы уже очень много с ними работали. Единственное, что действительно будет новым — это треугольники и полигоны. До сих пор они не использовались, хотя и являются основой OpenGL, да и всей современной компьютерной графики.
Вся графика в библиотеке OpenGL просчитывается и рисуется с помощью треугольников. Когда вы просите нарисовать прямоугольник, то он разбивается на два треугольника (любой прямоугольник можно разделить на два треугольника) и затем уже рисуются они. Это связано с 3D-ускорителями и принципами их работы. В настоящее время появляются ускорители, которые работают по другому принципу, но большинство их все-таки строит сцены по старинке.
Полигоны — множество точек, которые замкнуты между собой линиями. Практически любую фигуру можно назвать полигоном. Треугольник — это полигон из замкнутых трех точек. Прямоугольник — это полигон из замкнутых четырех точек.
Рассмотрите рис. 21.5. Здесь представлены различные фигуры, но все они состоят из примитивов. Верхние фигуры сделаны из линий, средние из треугольников, а нижние из квадратов.
Рис. 21.5. Примитивы
Давайте рассмотрим каждый примитив подробнее. Но для начала взглянем на полный код программы, рисующей эту сцену. Можете взять код из пре-
дыдущего раздела и подкорректировать процедуру FormPaint так, как это показано в листинге 21.5.
Листинг 21.5. Рисование примитивов
procedure TForml. FormPaint (Sender : TObj ect); var
ps: TPaintStj^ict;
begin
//Начало рисования и очистка буферов
BeginPaint(Handle,ps);
glClearColor(1,0.5,0.5,1);
giciear (GL_COLOR_BUFFERJIT);
/ /Устанавливаю шириьту линии glLineWidth(5);
//Рисование линий glBegin(GL^LINES);
glColor3f (0.0, 1.0, 0.0);
glVertex3f(-2.7,1.7,0);
glVertex3f (-2.7,1,0);
glColor3f (0.0, 0.0, 1.0);
glVertex3f (-2.3,1.7,0);
glVertex3 f (-2.3,1,0);
glEnd;
//Рисование замкнутых линий glBegin (GL_LINE_STRIP);
glColor3f (0.0, 1.0, 0.0);
glVertex3f(-l,1.7,0);
glVertex3f(-1,1,0);
glColor3f (0.0, 0.0, 1.0);
glVertex3f(~0.5,1.7,0);
glVertex3f(-0.5,1,0);
glEnd;
/ / Рисование замкнутых лзгний с вазу, ыканием в начало glBegin (GL_LINE_LOOP);
glColor3f (0.0, 1.0, 0.0); glVertex3f(0.5,1.7,0);
glVertex3f(0.5,1,0);
glColor3f (0.0, 0.0, 1.0); glVertex3f(1,1.7,0); glVertex3f(lf1,0);
glEnd;
/ / Треугольники
glBegin(GL_TRIANGLES);
glColor3f (0.0, 1.0, 0.0); glVertex3f(-2.7,0.7,0);
glVertex3 f(-2.7,0.0,0);
glVertex3f (-2.5,0.4,0); glColor3f (0-0, 0-0, 10); glVertex3 f(-2.3,0.7,0); glVertex3 f(-2.3,0.0,0);
glVertex3£(-2.1,0.4,0); glEnd;
/ / Замкнутые треугольники glBegin (GL_TRIANGLE_STRIP); glColor3f (0.0, 1.0z 0.0); glVertex3£(-0.7,0.7,0); gTVertex3 £(—0.7,0.0,0); glVertex3 £(-0.5,0.4,0); glColor3f (0.0, 0.0, 1 0); glVertex3f(-0.3,0.7,。); glVertex3 f(-0.3,0.0,0); glVertex3 £(-0.1,0.4,0)?
glEnd;
/ / Замкнутые треу го льники glBegin (GL_TR 工ANGLE_FAN);
glColor3f (0.0, L。,0.0) glVertex3f(1.3,0.7,0);
glVertex3f(1.3,0.0,0);
glVertex3f(1.5,0.4f 0);
glColor3f (0.0, 0.0, 1.0);
glVertex3f (1.7,0.7,0);
glVertex3f(1.7,0.0,0);
glVertex3f(1.9,0.4,0); glEnd;
// Квадраты
glBegin(GL_QUADS);
glColor3f (0.0, 1.0, 0.0); glVertex3f(-2.7,-1.7,0); glVertex3f (-2.7, -1.0,0); glVertex3f(-2.4,-1.0,0); glVertex3f(-2.4,-1.7,0);
glColorff (0.0, 0.0, 1.0); givertex3f(-2.3,-1.7,0); glVertex3f(-2.3,-1.0,0); glVertex3f(-2.0,-1.0,0); glVertex3f(-2.0,-1.7,0);
glEnd;
/ / Замкнутые квадраты glBegin (GL__QUAD_STRIP);
glColorf f (0.0, 1.0, 0.0); glVertex3f(-0.7, -1.7,0);
glVertex3f(-0.7,-1.0,0); glVertex3f (-0 .5, -1.0, 0); glVertex3f (-0.5, -1.7,0);
glColor3f (0.0, 0.0, 1.0); glVertex3f(~0.3,-1.7,0); glVertex3f(-0.3,-1.0,0); glVertex3f(-0.1,-1.0,0); glVertex3f(-0.1,-1.7,0);
glEnd;
//Полигон
glBegin (GL_POLYGON);
glColor-3 f (0.0, 1.0, 0.0); glVertex3f (1.3,-1.7,0);
glVertex3f(1.3,-1.0f0);
glVertex3f(1.1,-1.4,0);
glColor3f (0.0, 0.0, 1.0);
glVertex3f(1.7,-1.0,0);
glVertex3f(1.7,-1.7,0);
glVertex3f(工.9,-1.4,0);
glEnd;
glFlush();
swapBuffers(de);
EndPaint(Handle,ps);
end;
Теперь рассмотрим код этой процедуры по частям. Первое, с чем нам предстоит познакомиться, — это процедура glLineWidth, которая задает ширину линий. Все линии, нарисованные в дальнейшем, будут рисоваться с указанной шириной.
glLineWidth(5);
Процедура giLinewidth влияет только на рисование линий. На квадраты и треугольники она не влияет, потому что они рисуются закрашенными и линии у них не видны.
Теперь рассмотрим первый примитив из двух линий: glBegin(GL_LINES);
glColor3 f (0.0, 1.0, 0.0);
glVertex3f(-2.7,1.7,0);
glVertex3f(-2.7,1,0);
glColor3f (0.0, 0.0, 1.0);
glVertex3f(-2.3,1.7,0);
glVertex3 f (-2.3,1,0);
glEnd;
glBegin с параметром GL_L工NES открывает набор линий. Здесь рисуются две параллельные линии разного цвета. Вы можете нарисовать одну или даже 10. Между glBegin (GL_LINES) и glEnd должно быть четное количество вершин. Если будет нечетное, то последняя будет просто отбрасываться н не повлияет на сцену.
Цвет задается уже знакомой процедурой glColor3f, у которой три параметра: значение красного, зеленого и синего цвета. Значения каждой составляющей изменяется от 0 до 1.
glBegin (GL_LINE_STRIP);
glColor3f (0.0, 1.0, 0.0);
glVertex3f(-1f1.7,0);
glVertex3 f(-1,1,0);
gicoior3f (0.0, o.o, 1.0);
glVertex3 f(-0.5,1.7,0);
glVertex3f(-0.5,1,0);
glEnd;
glBegin с параметром GL LINE STRIP открывает набор линий с продолжением. Это значит, что конец первой линии будет являться началом второй, а конец второй будет являться началом третей. Здесь может быть любое количество вершин. Почему? Да потому, что если вы зададите три параметра, то нарисуется две линии между первой и второй точкой и между второй и третьей. Так что количество вершин тут может быть любое.
glBegin(GL_LINE_LOOP);
glColorff (0.0, 1.0, 0.0);
glVertex3f(0.5,1.7,0);
glvertex3 f(0.5,工,0);
glColor3f (0.0, 0.0, 1.0);
glVertex3f(工,1.7,0);
glVertex3f(1,1,0);
glEnd;
giBegin с параметром GL_line_loop открывает набор замкнутых линий. Этот примитив очень похож на предыдущий, с одной только разницей — последняя вершина замкнется с первой.
Теперь перейдем к треугольникам. Для наглядности посмотрите на примитивы, расположенные на рисунке в центре.
glBegin(GL_TRIANGLES);
gicolor3f (0.0, 1.0, 0.0);
glVertex3f(-2.7,0.7,0);
glVertex3 f(-2.7,0.0,0);
givertex3 f(-2.5,0.4,0);
glColor3f (0.0, 0.0, 1 0);
glVertex3 f(-2.3,0.7#0);
glVertex3 f(-2.3,0.0,0);
glVertex3 f (-2.1,0.4,0);
glEnd;
giBegin с параметром GL TRI ANGLES рисует треугольники. Количество вершин должно быть кратно трем, лишние вершины отбрасываются.
giBegin(GL_TRIANGLE_STRIP);
glColor3f (0.0, I .0, 0.0);
glVertex3f (-0.7,0.7,0);
glVertex3f(-0.7,0.0,0);
glVertex3f(-0.5,0.4#0);
glColor3f (0.0, 0.0, 1.0);
g1Vert ex3 f (-0.3,0.7,0),-.
glVertex3f(-0.3,0.0,0);
glVertex3f(-0.1, 0.4,0);
glEnd;
glBegin с параметром GL_TRIANGLES_STRI P рисует треугольники с замкнутыми вершинами. Последняя вершина первого треугольника является первой вершиной второго треугольника.
glBegin (GL_TRIANGLE_FAN);
glColor3f (0.0, 1.0, 0.0);
glVertex3f(1.3,0.7,0);
glVertex3f(1.3,0.0,0);
glVertex3f(1.5z0.4,0);
glColor3f (0.0, 0.0, 1.0);
glVertex3f(1.7,0.7,0);
glVertex3f(1.7,0.0,0);
glVertex3f(1.9,0.4,0);
glEnd;
giBegin с параметром gl__triangle_fan рисует треугольники с замкнутыми гранями. Последняя грань первого треугольника является первой гранью второго треугольника. Количество вершин может быть любым (не меньше трех), потому что последняя грань хранит в себе уже две точки, и с прибавлением еще одной получается новый треугольник.
Теперь перейдем к квадратам. Здесь будем писать сокращенно, потому что код идет однообразный.
glBegin(GL_QUADS);
Процедура giBegin с параметром GL QUADS рисует квадраты. Количество вершин должно быть кратно 4.
glBegin (GL_QUAD_STRIP);
Процедура giBegin с параметром GL_QUADS_STRIP рисует квадраты с общей гранью. Последняя грань первого квадрата является первой для второго. Количество вершин должно быть больше или равно 4 и кратно 2.
glBegin (GL_POLYGON);
Процедура giBegin с параметром GL POLYGON рисует полигон. Это просто набор вершин, объединенных между собой.
Примечание
Честно сказать, пример не очень удачный, но попробуйте сами поработать с вершинами. Подвигайте их в разные стороны и посмотрите на результат.
Вот и все. Теперь вам не трудно будет нарисовать достаточно сложную сцену. Самое главное, чтобы хватило воображения.
В этом примере вы также можете заметить, что менять цвет вершин можно во время рисования вершин примитива между glBegin и glEnd. В этом случае вершины будут получать разный цвет, а грани и плоскости между вершинами будут плавно менять цвет от одного к другому. С помощью такого нехитрого приема можно получить достаточно сложные сцены.
Приме 吧 нне
На компакт-диске в папке \ Примеры \ Глава 21 \ Примитивы вы можете увидеть пример этой программы.
Генерация собственных примитивов, масштабирование, перемещение объектов
Представим, что нам надо нарисовать стол. В этом случае нам надо нарисовать четыре ножки, которые выглядят как кубы. Вроде ничего сложного, но куба среди примитивов OpenGL нет. Поэтому придется каждый куб рисовать из шести прямоугольников. И так четыре раза. Не легче ли один раз создать ножку и использовать ее в своей программе сколько угодно раз? Легче, причем OpenGL дает такую возможность.
Для реализации задуманного будем использовать пример из предыдущего раздела главы, изменив немного код отображения. Для события onCreate напишите код, представленный в листинге 21.6.
Листинг 21.6. Создание объектов
glNewList (1,GL_COMPILE);
glColor3f (0.0, 1.0, 0.0);
glBegin (GL__QU7KD_STRIP);
glVertex3f(1.25,1.25,1);
glVertex3f(1.25,-1.0,1);
glVertex3f(-1,1.25,0);
glVertex3 f(-1,-1.0,0);
glEnd;
glColor3f (1.0, 1.0, 0.0);
glBegin(GL_QUAD_STRIP);
glVertex3f(1.0,1.0f0);
glVertex3f(1.0,-1.25,0);
glVertex3f(-1.25,1.0f1);
glVertex3f(-1.25,-1.25,1);
glEnd;
glEndList;
Функция glNewList создает новый список (объект). Он работает как Begin, а концом для него служит glEndList. Все, что находится между этими двумя функциями, — это вершины, или стороны создаваемого нового объекта. В нашем случае — это простые два квадрата, которые пересекают друг друга (как в разд. 21.3).
Теперь разберемся с параметрами функции giNewList. В качестве первого параметра выступает цифра, которая идентифицирует новый объект. Это своеобразный номер объекта или, можно сказать, — **числовое имя**, потому что здесь можно ставить только целое число от 1 до 255. По этой цифре мы будем в дальнейшем обращаться к этому объекту. Таким образом, для каждого объекта в сцене должен быть указан свой уникальный номер. Второй параметр — константа, которая указывает, как будет создаваться объект. Она может:
口 GL COMPILE — создать объект;
口 GL_COMPILE_AND EXECUTE - СОЗДаТЬ Объект И вывести На Экран.
В данном случае мы только создаем, а выводить будем при рисовании.
Теперь скорректируем событие OnPaint. Новый вариант вы можете увидеть в листинге 21.7.
Листинг 21.7. Новая функция отображения
procedure TForml. FormPaint (Sender: TObject);
var
ps:TPaintStruct;
begin
Beg inPaint (Handle, ps);
glClearColor(lf0.5,0.5,l);
glClear (GL_COLOR_BUFFER_BIT ОГ GL_DEPTH_BUFFER_BIT);
glRotated(45,1,0,0);
glEnable (GL_DEPTH_TEST);
glDepthFunc (GL__LESS);
glCallList(1);
glTranslatef(2,2,0);
glCallList(1);
glTranslatef(-4, -4,2);
glCallList(1);
glFlush();
swapBuffers(de);
EndPaint(Handlerps);
end;
Здесь теперь вместо рисования пересекающихся квадратов просто вызывается созданный объект с помощью glCallList. В качестве параметра выступает номер объекта (мы создавали объект 1, значит, и здесь должна быть единица). Делаем это три раза в разных позипиях, чтобы пример был более наглядным.
Пример готов, можно запустить его и посмотреть на результат.
Вы наверно уже заметили тут новую процедуру glTranslatef. Пока давайте ее оставим в покое. Попробуйте запустить пример и разобраться, как работает остальной код. Относительно процедуры glTranslatef вам достаточно знать, что она как бы перемещает фокус ввода в другое место. Мы рисуем первую фигуру, затем фокус ввода перемещается в другое место и там рисуется новая фигура.
Теперь немного улучшим наш пример. В разделе private главной формы TForm объявим несколько переменных: private
X,Y,Z:real;
RX,RY,RZ:real;
Переменные х, Y и z будут отвечать за текущую позицию объектов в пространстве. Если переменная х равна 20, значит, сцену нужно сдвинуть на 20 единиц по оси X. Переменные RX, RY и RZ будут отвечать за угол отображения по осям. Если переменная RX = 10, значит, сцену нужно повернуть на 10° по оси X. Получается, что, изменяя эти переменные, мы будем изменять позицию сцены (перемещать и вращать все фигуры). В событии one reate мы должны присвоить всем,зтмм переменным 0. После этого создадим новое событие OnKeyDown (в этот обработчик попадают события
от нажатых клавиш на клавиатуре) и напишем в нем код, представленный в листинге 21.8.
Листинг 21.& Обработка событий клавиатуры
procedure TForml.FormKeyDown (Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if Key =VK_R工GHT tbs
X:=X+0.I;
if Key 士VKJEFT then X:=X-0.1;
if Key =VK_UP then
Y:=Y+0.1;
if Key =VK_DOWN then
Y:=Y-0.1;
if Key =VK_INSERT tbs RY:=RY-2;
if Key =VK_JDELETE then RY:=RY+2;
i£ Key =VK__HOME then RX:=RX-2;
if Key =VK_END then RX:=RX+2;
FormPaint(nil);
end;
Здесь вылавливаются разные события от нажатия клавиш на клавиатуре. Так, по нажатии клавиши <т> мы увеличиваем переменную х на 0,1. Получается, что стрелками мы будем двигать сцену, а клавишами 〈Insert〉, <Delete>, <Home> и <End> — будем вращать сцену.
И, наконец, событие onPaint скорректируем так, как это показано в листинге 21.9.
Листинг 21 9 Рис «ванне сцены
procedure TForml.FcxmPaint (Sender: TObject);
var
ps:TPaintStruct;
begin
BeginPaint (Handlers);
glClearColor(1,0.5,0.5,1);
glClear (GL_COLOR_BUFFER_BIT 0r GL_DEPTH_BUFFER_JBIT);
gl PushMatrix;
glRotated(45,1,0,0);
glTranslatef(X,Y,Z);
glRotated(RX, 1,0,0);
glRotated (RY, 0,1 f 0);
glRotated(RZ, 0,0,1);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glCallList(1);
glTranslatef(2,2,0);
glCallList(1);
glTranslatef(-4,-4,2);
glCallList(1);
glPcpMatrix;
glFlush();
swapBuffers(de);
EndPa int(Handle,ps);
end;
Можно запустить программу и посмотреть на результат (рис. 21.6). Попробуйте понажимать клавиши <Insert>, <Delete>, <Home> и <End>. Если результат удовлетворил, то давайте перейдем к рассмотрению новых функций. С функцией glRotated МЫ уже ЗНЯКОМЫ, ЭТО ПОВОРОТ сцены. glTranslatef — перемещает сцену по координатам, указанным в скобках(x,y, Z), и здесь вы видите уже реальное этому подтверждение.
Рис. 21.6. Результат работы программы
На функциях glPushMatrix и glPopMatrix остановимся еще раз. Их назначение —сохранить и восстановить текущую матрицу перемещений.
Вспомните пример (см. разд. 21.2), когда мы полностью перекрывали/сво- рачивали окно и снова показывали его. В этом случае сцена изменялась. Это происходило из-за того, что прорисовка экрана осуществлялась заново, а мы использовали функцию поворота glRotated. Когда программа запускалась, то происходил вызов функции giRotated и сцена поворачивалась на 45°. После того как мы перекрыли окно и снова его показали, сцена прорисовалась и повернулась еще на 45° (всего уже 90°). Как избавится от этого эффекта? Для этого надо заключить все повороты (giRotated), перемещения (glTranslatef), масштабирование (clScale) и рисование соответствующих объектов между функциями glPushMatrix и glPopMatrix. Первая сохраняет текущую матрицу поворотов, перемещений и масштабирование. После вызова glPopMatrix матрица восстановится в состояние, которое было до вызова glPushMatrix.
В нашем случае мы сохраняем нулевую матрицу (потому что еще не производилось никаких действий до вызова glPushMatrix). Потом рисуем объекты и в конце восстанавливаем нулевую матрицу. Если перед вызовом glPushMatrix поместить giRotate (45,1, о, 0) (поворот на 45°), то сохранится
матрица, в которой есть только этот поворот. После восстановления в матрице опять будет только этот поворот. Все действия по изменению текущей точки рисования между giPushMatrix и glPopMatrix будут отменены, но объекты, заключенные в giPushMatrix и giPopMatrix, прорисуются правильно, с учетом изменений.
Г Примечание ;
На компакт-диске в папке \ Примеры \ Глава 21 ХСписки вы можете увидеть пример этой программы.'
Примитивы библиотеки GLU
В стандартном OpenGL нет таких очень важных примитивов, как сфера или конус. Конечно, вы в любой момент можете нарисовать сферу из множества точек или треугольников, но это будет громоздко и тяжело, особенно тем, у кого проблемы с геометрией. Есть выход проще — для OpenGL имеется надстройка, которая называется GLU. Находится она в файле glu32.dll. Вот именно с ней мы сейчас и будем знакомиться.
Рис. 21.7. Окно программы отображения примитивов GLU
На рис. 21.7 вы можете увидеть результат работы программы, которая сейчас буцет расематриваться. Самое интересное, что ее код буцет даже меньше, чем код программы из предыдущего примера.
Рассмотрим исходную программу более подробно. Ее кед приведен в листинге 21.10.
Листинг 21.10. Программа отображения примитивов GLU
type
TForml = class (TFprm)
private
{ Private declarations }
DC:HDC;
hrc:HGLRC;
quadObj : PGLUquadr i cCbj ;
procedure PixelFormat;
public
{ Public declarations } end;
va.r
Forml : TForml;
inplementat icn
{$R *.DFM}
procedure TForml. FormPaint ( Sender : TObject);
var
ps : TPaintStruct ;
begin
BeginPaint (Handlers);
glClearColor(1,0.5,0.5,1);
glClear (GL_COLOR_BUFFER_BITor GL_DEPTH_BUFFER_BIT);
glPushMatrix;
glRotated (45,1,0,C);
gluQuadricDrawStyle (quadObj , GLU_FILL); glColor3f(I,0,0);
724
gluSphere (quadObj , 1, 100, 10);
glTranslate!(1.5,工,1);
gluCylinder (quadObj , 0, 0.2, 1,
30,
10);
gliiDisk (quadCbj, 0.2, 0.3, 20,
glTranslatef(-3,0,0);
2);
glucylinder (quadObj , 0, 0.2, 1,
30,
10);
gluDisk (quadCbj, 0.2, 0.3, 20, glPopMatrix;
2);
glFlush();
swapBuffers(de);
EndPaint (Handle ,ps);
end;
procedure TFonnl.FcrmCreate(Sender: TObject); begin
DC: =GetDC (Handle);
PixelFozma t;
hre : =wglCreateCcntext (DC);
wglMakeCurrent (DC,hre);
quadObj : =gluNewQuadric ();
end;
В ЛИСТИНГе He Показан текст трех функций FormResize, FormDestroy И PixelFormat, потому что они абсолютно такие же, как и во всех предыдущих примерах.
В самом начале объявляется переменная quadobj типа PGLUquadricObj.
U Примечание~
Если вы не используете заголовочные файлы gl и glu (которые можно найти на диске), а меняете их на один файл opengl.pas (изстандартной поставки Delphi), то у вас эта переменная будет ™па GLUquadricObj (без буквы "рн).
По событию OnCreate мы инициализируем эту переменную (одна из последних строчек Кеда) quadobj : =gluNewQuadric ();.
В процедуре FormPaint задаются параметры рисования объекта gluQuadricDrawStyie. В качестве первого параметра выступает созданная нами переменная, с которой будет ассоциироваться "тип рисования".
Второй параметр — непосредственно тип. Он, в свою очередь, может иметь параметры:
GLU_FILL — объект полностью закрашивается (см. рис. 21.7);
О GLU LINE — объект рисуется линиями (рис. 21.8);
О GLU_s工lhouette — рисуется только силуэт объекта (рис. 21.9);
GLU_P。工NT — объект рисуется точками (рис. 21.10).
Рис. 21.8. Рисование В виде GL J LINEРис. 21.8. Рисование В виде GL J LINEРис. 21-9. РисованиеВ виде GLU_ SILHOUETTEРис. 21-9. РисованиеВ виде GLU_ SILHOUETTEРис. 21.10. Рисование В виде GLU JOINTРис. 21.10. Рисование В виде GLU JOINT
Рис. 21.8. Рисование В виде GL J LINE
Рис. 21.8. Рисование В виде GL J LINE
Рис. 21-9. Рисование
В виде GLU_ SILHOUETTE
Рис. 21-9. Рисование
В виде GLU_ SILHOUETTE
Рис. 21.10. Рисование В виде GLU JOINT
Рис. 21.10. Рисование В виде GLU JOINT
После всего этого можно смело рисовать объекты библиотеки GLU. В этом примере мы использовали три из них, поэтому только их и рассмотрим.
Объект glusphere — первый параметр это GLU-объект (в нашем случае quadobj), второй — радиус, третий — количество частей вокруг оси Z, четвертый — количество частей вдоль оси Z.
П Объект gluCylinder — первый параметр такой же, как и у glusphere, второй — маленький радиус, третий — большой радиус, четвертый — количество частей вокруг оси Z, пятый — количество частей вдоль оси Z.
О Объект gluDisk — первый параметр такой же, как и у glusphere, второй — внутренний радиус, третий — внешний радиус, четвертый — количество частей вокруг оси Z пятый — число колец (в принципе, это все те же части).
Рис- 21.11. Сцена с объектами при использовании маленького количества частей
Что такое — ''количество частей вокруг и вдоль оси Z”? Чем больше этих частей будет сформировано, тем более гладким будет объект. Однако в этом случае потребуется больше ресурсов для расчета сцены.
На рис. 21.11 показана сцена, в которой очень сильно уменьшено количество этих частей. Как видите, фигуры получились с абсолютно незакругленными и с грубыми углами.
(Совет )
Старайтесь по возможности уменьшать количество частей, но при этом качество не должно ухудшаться.
К Пртуючанм 】
На компакт-диске в папке \ Примеры \ Глава 21 \ GLU вы можете увидеть пример этой программы.
Текстуры
В этом разделе нам предстоит изучить достаточно сложный материал — текстуры. Мы научимся их загружать и "надевать** на объекты любой формы.
Рис. 21.12. Результат работы примера
Посмотрите на рис. 21.12. Здесь вы можете увидеть результат работы программы, которая сейчас будет рассматриваться. В качестве примера мы используем сферу, на которую надели текстуру. Если вы запустите пример, то сможете увидеть все прелести текстуры. Для этого сфера сделана врашаю- шейся, чтобы вы могли разглядеть ее с разных сторон.
В качестве текстуры используется иконка, назначенная главному окну программы. Поэтому надо знать, как превращать иконки в формат BMP.
Давайте сначала рассмотрим функцию рисования. Ее код вы можете увидеть в листинге 21.11.
Листинг 21.11. Функция рисования сцены
procedure TForml.FormPaint(Sender: TObject);
var
ps:TPaintStruct;、
begin
BeginPaint(Handle,ps);
glClearColor(0,0,0,I);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glPushMatrix;
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MInJfILTER,GL_NEAREST); qlTexParameteri(GL_TEXTURE_2 D f GL_TEXTURE_MAG_FILTER,GL_NEAREST); glTexEnvf (GL_TEXTURE_ENV f GL_TEXTURE_ENV_MODE, GL_DECAL);
glTex工mage2d(GL_TEXTURE_2D,0,3,TextureW,TextureH,0,GL_RGB, GL__UNSIGNED_BYTE, Texture^);
glEnable (GL_TEXTURE_GEN_S);
glEnable (GL_TEXTURE_GEN_T);
glEnable (GL_TEXTURE_2D);
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_N工CEST);
gluQuadr icTexture(quadObj,GL_TRUE);
glTranslatef(TXf TYf 0);
glRotatef(RX,1,0,0);
glRotatef(RY,0,1,0);
24 Зак 942
glRotatef(RZ,О,С,工);
gluSphere{quadObj,2.7, 15 f 10);
TX:=TX+SX;
TY:=TY+SY;
if (TX<-10) or (TX>10) then
sx:=-sx;
if (TY<-10) or (TY>10) then
SY:=-SY;
glDisable(GL_TEXTURE_2D);
g1PopMatrix;
glDisable(GL_DEPTH_TEST);
glFlush();
swapBuffers(de);
EndPaint(Handle,ps);
end;
Посмотрите внимательно на эту процедуру. Большая ее часть должна быть вам уже понятна.
giT exParameteri — устанавливает параметры текстуры. Первый параметр —тип, который может быть GL_texture_1D или GL texture_2D. Второй параметр — символическое имя свойства, параметры которого нужно установить. Мы можем эти параметры изменять: gl_texture_min_f ilter, GL_TEXTURE_MAG_F ILTER, GL_TEXTURE_WRAP_S, GL_TEXTURE__WRAP_T И gl_texture_border_color. Третий параметр — значение, которое мы хотим установить указанному свойству в предыдущем параметре. Может не совсем понятно, но сейчас все встанет на свои места. Давайте рассмотрим каждый параметр в отдельности.
О GL_TEXTURE_MIN_FILTER — устанавливает тип функции, которая будет использоваться при уменьшении текстуры. Если вы решили изменить этот параметр, то должны Gl_texture_min_filter указать вторым в функции giTexParameteri. В этом случае третий параметр сможет принимать значения:
GL_NEAREST — возвращает значение элемента текстуры, который является самым близким;
GL L 工NEAR — возвращает среднее значение из четырех ближайших к
центру тестируемого пиксела;
GL_NEAREST_M工PMAP—NEAREST — ВыбираетблИЖЭЙШИЙ MIPMAP ИДЭЛЬШе использует тип NEAREST;
GL_LINEAR__MIPMAP__NEAREST — Выбирает ближайший MIPMAP И дальше использует тип LINEAR;
GLNEAREST—M工 PMAP_LINEAR — ВыбираетДВИ MIPMAP ИДЗЛЫИе ИСПОЛЬЗуеТ ТИП NEAREST
GL_L工NEAR_MIPMAP_L工NEAR — ВыбираетДВЗ MIPMAP ИДЭЛЬШв ИСПОЛЬЗувТ ТИП LINEAR.
GL_TEXTURE_HAG_FILTER GL_TEX十UREJIAG_F工LTER — устанавливает функцию, используемую при увеличении текстуры. Если вы решили ее изменить, ТО ДОЛЖНЫ уСТЗНОВИТЬ GL_TEXTURE_MAG_F工LTER вторым Параметром в glTexParameteri, з третий сможет принимэть знэчения:
GL_NEAREST — ТОЖС, ЧТО И В ПредыдущемСВОЙСТВе;
GL_LINEAR — ТОЖе, ЧТО И В ПредыдущемСВОЙСТВе.
□ GL TEXTURE WRAP S — устзнзвливает повторения текстуры по координате s. Он может принимать значения:
GL CLAMP — запрещает повторение текстуры (одна ее копия будет надета на весь объект);
GL REPEAT — разрешает повторение текстуры.
口 GL_TEXTURE_WRAP_T - ТО Же самое, ЧТО И GL_TEXTURE_WRAP_S. ТОЛЬКО ДЛЯ координаты т. Возможные значения третьего параметра те же, что и для GL_TEXTURE_WRAF_S.
□ GL TEXTURE BORDER COLOR — устанавливает цвет обрамления текстуры. При этом параметре третий сможет принимать значение массива из четырех знзчений, которые интерпретируются как цвет (RGBA). Пор умолчанию (0,0,0,0).
Теперь перейдем к функции glTexEhvf. Она устанавливает параметры среды текстуры. Первый параметр должен быть GL TEXTURE ENV. Второй должен быть GL TEXTURE—ENV MODE. Третий МОЖеТ ПрИНИМЭТЬ значения GL—MODULATE, GL DECAL или GL BLEND. В примере используется второй параметр, а вы можете попробовать другие и посмотреть на результат. Вы должны увидеть разницу и решить, что лучше подходит для данной задачи.
Функция glTexImage2d устанавливает 2D-текстуру. Все объекты, которые рисуются после вызова этой процедуры, будут охвачены этой текстурой. Первый параметр должен быть GL TEXTURE 2D. Второй параметр — уровень детализации текстуры (0 — основной). Третий — количество цветовых компонентов в текстуре (может быть 1, 2, 3 или 4). Далее идет ширина и высота. После этого следует обрамление (может быть 0 или I). В примере используется 0. чтобы не было никаких обрамлений у текстуры. Далее — формат, в котором хранятся
цветовые данные в массиве. Здесь могут быть значения: GL__color_index, GLARED, GLGREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_LUM工NANCE ИЛИ glluminancE—Alpha. П редпосл ед ни й параметр — тип, в котором хранятся цветовые данные. Он может быть gl__unsigned__byte, gl_byte, gl_bitmap, GL_UNSIGNEI)_SHORT, GL„SHORT, GL_UNSIGNED__INT, GL_INT ИЛИ GL_FLOAT. Последний параметр — указатель на массив текстуры.
Надо заметить, что в качестве последнего параметра должна передаваться реальная ссылка на текстуру. Прежде чем вызывать glTexImage2d, текстура должна быть уже загружена и подготовлена в этой переменной. Чуть позже мы рассмотрим, как загружается текстура.
После всех этих настроек можно включить текстуру. Это делается с помощью кода:
glEnable (GL_TEXTURE_GEN_S);
glEnable (GL_TEXTURK_GEN_T);
glEnable (GL_TEXTUREL_2D);
Первые две функции необязательны. Они просто включают использование определенных функций текстуры. Здесь они показаны только для примера, и в большинстве случаев не используются. Последняя непосредственно включает показ текстуры. Теперь все объекты, которые мы станем рисовать, будут обтянуты выбранной текстурой. Это будет происходить, пока мы не ОТКЛЮЧИМ Текстуру С ПОМОЩЬЮ glDisable (GL_TEXTURE_2D).
Все остальное должно быть понятно, потому что там вдет простой вывод на экран сферы. Давайте еще рассмотрим процедуру для: загрузки текстуры из системной иконки (см. листинг 21.12).
Листинг 21.12. Загрузка текстуры
procedure TForml.GetTexture;
var
BMInfo: TBitmapInfo;
I,Imagesize:Integer;
Temp:Byte;
Bitmap:Graphics.TBitmap;
Templxnage: TBitmap;
MemDC: HDC;
begin
/ /Подготавливаются переменные для работы с картинками.
Bi 5ар: =TBi tmap. Crea te;
TempTmage : =TBitmap. Create;
Tempimage. Width: =32;
Tempimage.Height:=32;
//Прорисовывается системная иконка (Icon) в Tenplmage.
/ /Если надо использовать текстуру из ВМР-файла,
/ /можно его напрямую передать в Temp Image.
//Для этого достаточно написать Temp Image. LoadFromFile ( ' filename.bmp');
Teroplmage.Canvas.Draw(0,0, Icon);
/ /Определяются ближайше размеры текстуры, которые воспримет OpenGL.
Bi tmap. Width: =KoundUpToPowerOf 2 (Templmage. Width);
Bitmap.Height: =RoundUpToPower0f2 (Templmage.Height);
/ /Копируется текстура из Temp Image в Bitmap с учетом размеров, //воспринимаемых QpenGL
Bitmap.Canvas.CcpyRect (Rect (0,0,Bitmap.Width,Bitmap.Height), Templmage.Canvas, Rect(0,0,32,32));
//Создается контекст в памяти.
MemDC: =CreateCornpatibleDC(0);
try
with bitmap do
begin
IniageSize: =bitmap. Width*bi tmap. Height;
TextureW : =bitmap. Width;
TextureH : =bi tmap. Height ;
/ /Вьщеляется память для переменной Texture, в которой будет //хранитьсяттреобразованная текстура. Обратите внимание, как она //объявлена.
Ge tMem(Texture,ImageSize*3);
MemDC : =Crea t eCorapa t ibl eDC (0);
FillChar (BMlnfo,SizeOf (BMInfo) ,0);
//Заполняется BMrnfo информацией о текстуре.
With BMInfo. tmiHeader do
begin
biSize: =sizeof (TBitmapInfoHeader);
biBitcount:=24;
biWidth: =bitmap. Width;
biHeight : =-bitmap. Height;
bi Planes :=1;
biCompression : =BI„RGB;
end;
//Копируются даьные из Bitmap' в Texture
GetDIBits (MemDCfВitmap. Handle, 0,bitmap. Height, Texture , BMInfo, DIB^RGB_COLORS);
{$RT
//Запускается цикл, в котором меняются местами цветовые
//компоненты красного и голубого т.е. идет перевод из RGB в BGR
for i:=0 to ImageSize-1 do
begin
Temp : =ТехШгеЛ [i*3];
Texture*4 [ 1*3] : =ТехСигеЛ[1*3+2];
Texture74 [1*3+2]: =Temp;
end;
{$R-}
glPixelStorei (GL_l№ACK^ALIGNMENT,0);
glPixelStorei (GL_UNPACK_ROW__LENGTH/0);
glPixelStorei (GL_UNPACK_SKIP_ROWSfO);
glPixelStorei (GL__UNPACK一SKIP_P工XELS,O);
end;
finally
//Освобождается все ненужное.
Bitmap.Free;
Temp Image. Free;
De let eDC (MemDC);
end;
end;
Формат изображения нужного OpenGL очень похож на BMP. Только в BMP используется цвет RGB (красный, зеленый, голубой), а в OpenGL используется BGR (голубой, зеленый, красный). Это значит, что нам нужно каждый третий байт цвета поменять местами с первым.
У картинки должно быть определенное отнотеш^зн車雖把価超口明ъв廳90爸е (ширина = 2-п4" а высота = Тт * где знак Л'
2 я это 2 в степени “). Двойку нужно прибавлять при использовании обрамлений. Можно использовать небольшую функцию, которая поможет в определении этих размеров:
function TForml.RoundUpToPowerOf2(Value: Integer): Integer;
va.r
LogTwo : Extended;
begin
LogTwo:=log2 (Value);
if Trunc (LogTwo) < LogTwo then
Result: =Trunu (Power (2,Trunc(LogTwo)+1))
else
Result:=value;
end;
工^ Примечание
На компакт-диске в папке \ Примеры \ Глава 21 \ Texture вы можете увидеть пример этой программы.
Освещение
Мы практически подошли к концу изучения OpenGL. Все основы рассмотрены. Осталось рассказать только про освещение. Освоив эту тему, вы сможете сами создавать полноценную 3D-анимацию, которая невозможна без света. Посмотрите на рис. 21.13 и вы увидите, как свет преобразует сцену. Простая сфера, которая выглядела раньше достаточно плоской, вдруг приобретает истинную глубину.
Без освещения сцены трудно назвать трехмерными, потому что конус выглядит как треугольник, а сфера как круг. Без освещения объект выглядит монотонно и его глубина практически незаметна.
Как и все остальные возможности, освещение включается с помощью процедуры glEnable с параметром GL LIGHTING. По умолчанию освещенность отключена, но когда вы ее включите, то рассеянный свет примет значение (0.2, 0.2, 0.2, 1). Это практически минимальная освещенность. Максимальная равна (1, 1, 1, 1).
Параметры освещения задаются с помощью процедуры glLightModel. Для нее нужны два параметра:
параметр света, который нужно изменить;
значение.
Рис. 21.13. Пример освещения
Если вы будете вызывать эту функцию как glLxghtModelf или glLightModeli (разница в последних буквах), то первый параметр сможет принимать значенияgl_l I ght__node l_local_v i ewerили
gl__liGht_model__two_s I de (объект освещается с обеих сторон), а второй gl true или gl_false. Первый параметр — свойство, которое мы хотим изменить, а второй — значение, которое может быть истиной (gl true) или ложью (GLFALSE).
ЕСЛИ ФУНКЦИЯ будет выглядеть как glLightModelfv ИЛИ glLightModeliv, ТО первый параметр сможет выглядеть как gl_light__model_ambient, GL__LIGHT__MODEL_LOCAL_VIEWER ИЛИ GL__LIGHT__MODEL_TWO_SIDE, а ВТОрОЙ Параметр —массив значений. Первый параметр процедуры — свойство, которое мы хотим изменить, а второй — значение.
Вот как будет выглядеть установка значения рассеянного света в синий цвет: const
airibient : Array [0. .3] of GLFloat 二(0.0, 0.0, 1.0f 1.0);
begin
glLightModelfv(GL_LIGHT_MODEL_AMBIENTz airibient);
• nd;
Прежде чем приступить к дальнейшему изучению осве щен пости, необходимо понять, как работать с материалами. Освещенность объекта зависит от материала объекта. В этом компьютерная графика ничем не отличается от реального мира. Например, металл имеет менее освещенную поверхность из-за его меньшей отражающей способности, полимеры тоже имеют свои особенности в зависимости от состава. Материал может как отражать, так и излучать свет.
Свойства материала устанавливаются с помощью процедуры glMaterialfv. Для нее нужно задать три параметра.
О Первый параметр — это грань, к которой будет применен материал. (gl_back — задняя грань, GL_FONT — передняя грань и GL__FRONT__AND_BACK - обе Грани).
□ Второй параметр — свойства материала. Может быть:
GL—AMB工ENT — рассеянный свет;
GL DIFFUSE - рЗССеЯННЫЙ СВСТ;
GL SPECULAR — отраженный свет;
GL EMISSION — излучаемый свет;
GL SHININESS — степень отраженногосвста;
GL AMBIENT and diffuse — оба рассеянных света.
О Третий параметр — значение цвета в формате RGBA.
Пока с теорией закончим и перейдем к примеру. Посмотрите на листинг 21.13. На нем приведен код функции рисования сцены с учетом освещения.
Листинг 21.13, Функция рисования сцены
procedure TForml.FormPaint(Sender: TObject);
const
emission : Array [0. .3] of GLFlcat 二 (0.0, 0.0, 1.0, 1.0);
airibient : Array [0. .3] of GLFloat 二 (0.0, 0.0, 1.0, 1.0);
diffuse : Array [0. .3] of GLFloat 二(1.0, 0.0, 0.0, 1.0);
pos : Array [0. .3] of GLFloat = (5.0, 10.0, -20.0, 1-0);
shiness : GLFloat 二 70.0;
var
ps:TPaintStruct;
begin
BeginPaint(Handle,ps);
glClearColor(I,0.5,0.5*1);
glClear (GL__COLOR_BUFFER_BIT or GL__DEPTH_BUFFER_BIT);
//Включается освещение
glEnable (GL_LIGHTING);
glEnable (GL__LIGHT0);
//Задается позиция источника света
glLightfv(GL_LIGHT0r GL^POSITION, @Pos);
giPushMatrix;
glRotated(45f1,0,0);
i
/ /Синий материал для сферы
glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSIONr ©emission );
glMaterialfv(GL_FRONT_^AND_BACK, GL„AMBIENT, ©ambient); gXMaterialfv (GL__FRONT_^ANDJBACK, GL_SHININESS, @shiness );
gluQuadricDrawStyle (quadObj , GLU_FILL); glColor3f(1,0,0); gluSphere (quadObj,1, 100, 10);
/ /Сине—красный материал для конусов
glMaterialfv (GL_FR0NT_7^D_BACK/ GL_EMISSION, ©emission ); glMaterialfv(GL_FRONT__AND_BACK, GL__AMBIENT, @dif fuse);
giMaterialfv(GL_FRONT_^AND_BACK, GL_DlFFUSEf @dif fuse ); glMaterialfv (GL__FRONT_AND_B/^K, GL_SPECULAR, @dif fuse); glMaterialfv (GL_FRONT_AND_BACK, GL__SHININESS, Qshiness );
glTranslatef(1.5,1,1);
gluCy 1 inder (quadObj , 0f 0.2, 1, 30, 10);
gluDisk (quadObj, 0.2, 0.3, 20, 2);
glTranslatef(-3,0,0};
gluCy 1 inder (quadObj , 0, 0.2, 1, 30, 10);
gluDisk (quadObj, 0.2, 0.3, 20, 2);
glPcpMatrix;
glFlush();
swapBuf fers(de);
EndPaint(Handle,ps);
end;
Единственное, что мы не рассмотрели, так это функцию gl Light fv. Она устанавливает параметры света и требует три параметра.
Номер источника света. Нумеруются: gl_lighto, gl_light1, gl_light2 и так до gl_l工GHT9. Как мы уже знаем, источник включается с помощью glEnable(gl_lighto), причем в скобках указан номер источника.
Параметр, который нужно изменить: gl_position (позиция источника) ИЛИ GL_SPOT_DIRECTION (ПОЗИЦИЯ ТОЧКИ, В КОТОРУЮ светит ИСТОЧНИК).
П Значение устанавливаемого параметра.
工—ПрумечанмГ 】
На компакт-диске в папке \ Примеры \ Глава 21 \ Освещение вы можете увидеть пример этой программы.
21.9. Заключение
На этом можно считать, что наше знакомство с OpenGL закончено. Конечно же, можно бы расширить это знакомство еще больше и написать целую книгу, но это никому не нужно. Здесь сделана попытка показать вам все основные функции работы с графикой при помощи библиотеки OpenGL. Для совершенствования своих знаний вам нужно только взять примеры и попробовать в них использовать другие параметры, которые были описаны, но не задействованы в программах.
ЛЛЛЛ
ЛЛ
ЛЛ
Глава 22
OLE, COM, ActiveX
Технология OLE — это лучшее изобретение Microsoft, с ее точки зрения. По мнению многих программистов — это правда, но только на 10 процентов. Технология OLE включает в себя элементы ActiveX, которые чем-то похожи на компоненты Delphi, но только требуют неоправданных затрат, связанных с регистрацией в системе и с контролем версий.
Некоторые программисты не любят элементы ActiveX и стараются использовать их только в крайнем случае. Но все же иногда приходится сталкиваться с ситуацией, когда они необходимы.
Единственное преимущество ActiveX — компоненты, оформленные в таком виде, будут работать в программах, написанных с использованием различных, языков программирования. Если компоненты Delphi можно использовать только в Delphi, в крайнем случае в C++ Builder или Kylix (обе — разработки фирмы Borland), то ActiveX однозначно работают везде. Ваши компоненты смогут использовать и в Visual C++, и в Visual Basic, и во многих других программах, поддерживающих ActiveX.
Многие программисты используют такой подход, когда разрабатываются VCL-компоненты. После этого, в случае необходимости, их можно преобразовать в форму ActiveX, причем это делается с помощью Delphi за пять минут.
Теория OLE
Технология OLE (Object Link and Embedding — или внедрение и связь объектов) является стандартом Windows и обеспечивает связывание и встраивание объектов на основе технологии COM (Component Object Model). С момента выпуска первой версии OLE 1.0, технология претерпела большие изменения, и старая аббревиатура OLE уже не отражает действительности. Теперь эти три буквы используют только по привычке.
СОМ — это спецификация, созданная для описания структуры СОМ- объектов. СОМ -объекты могут использоваться в любых языках программирования, вне зависимости от того, какая программная среда применялась при их
создании. Помимо компиляторов, с COM-объектами могут работать очень много крупных программных пакетов, например все те же Word и Excel.
Объекты OLE могут запускаться как в отдельном окне, так и внутри окна вашего приложения (первая версия позволяла запускать объекты только в отдельном окне). Таким образом, можно получать доступ к чужим приложениям и использовать их в своих целях. В гл. 15, когда описывался пример отчетности в Excel, мы уже немного познакомились с технологией OLE. Тоща запускалась программа Excel с помощью функции CreateOleObject и потом обеспечивался к ней доступ. Затем мы выводили отчет в стороннюю программу.
Когда OLE-объект запускается внутри вашего окна, то часть меню и панелей заменяется на те, что используются в программе, которая загружается.
Прежде чем говорить о чем-то дальше, давайте разберем работу с OLE на маленьком примере. Запустите Delphi и создайте новый проект. Установите на форму один лишь компонент OleContainer с вкладки System палитры компонентов. Теперь дважды щелкните левой кнопкой мыши внутри окна компонента (или щелкните правой кнопкой и в появившемся меню и выберите пункт Insert Olyect). В результате проделанных действий перед вами откроется окно, показанное на рис. 22.1.
Рис. 22.1. Выбор объекта OLE
В этом окне в списке Тип объекта найдите строку — Рисунок PaintBrush. Выделите эту строку. Если установить флажок В виде значка, то на форме вы потом увидите только значок объекта, а по двойному щелчку кнопки мыши будет запускаться выбранное приложение (PaintBrush). Если флажок не установлен, то приложение встраивается прямо в окно.
Попробуйте запустить программу и дважды щелкнуть внутри компонента OLEContainer. Компонент будет взят в рамку, и вы сможете рисовать в нем так же, как в самом PaintBrush.
Вот таким нехитрым способом мы встроили стороннее приложение в свою программу. Но у него нет ни меню, ни панелей. Для добавления меню достаточно установить на форму компонент MainMenu. В него не надо добавлять никаких пунктов, достаточно просто установить: Если у формы есть главное меню, то OLE-компонент буцет автоматически встраиваться в него. Запустите программу и убедись, что меню появляется.
Теперь измените свойство Align у компонента OLeContainerl на aLClient, чтобы растянуть его по всей форме. Запустите программу и посмотрите на результат. Теперь ваше приложение превратилось чуть ли не в полноценный PaintBrush. Единственное, что отличает его — заголовок окна и иконка Delphi.
・『Пример работы c OLE・『Пример работы c OLEПравка Вид Рисунок Палитра СправкаПравка Вид Рисунок Палитра Справка"-/Г7г*\но-"-/Г7г*\но-Рис. 22.2. Результат работы встроенного OLE-объектаРис. 22.2. Результат работы встроенного OLE-объекта
・『Пример работы c OLE
・『Пример работы c OLE
Правка Вид Рисунок Палитра Справка
Правка Вид Рисунок Палитра Справка
"-/Г7г*\но-
"-/Г7г*\но-
Рис. 22.2. Результат работы встроенного OLE-объекта
Рис. 22.2. Результат работы встроенного OLE-объекта
Примечание — >
На компакт-диске в папке \ Примеры \ Глава 22 \ OLE вы можете увидеть пример этой программы.
Теперь поговорим более подробно о меню. Попробуйте создать в главном меню два пункта Файл и Правка. У обоих пунктов можете добавить подпункты. Теперь запустите программу и посмотрите на результат. Когда вы пытаетесь активизировать OLE-объект, то меню Файл останется ваше, а аналогичное, из программы PaintBiush, исчезает. К тому же у вас получилось два пункта (включая Правка). В чем же эффект? Пункты меню, у которых в свойстве Grouplndex стоит четное значение (О, 1, 4), — остаются неизменными. Пункты меню, у которых Groupindex нечетное, — заменяются аналогичными из OLE-объекта.
Попробуйте поставить у пункта Правка в свойстве Groupindex значение 1 и запустите программу. Теперь при активизации OLE-объекта пункт меню Правка будет заменен аналогичным из объекта.
Внимание
Обязательно учитывайте эффект замены пунктов меню при написании программ, работающих с OLE.
Написанная программа называется Контейнер OLE〉а программа, которую мы привязываем, называется Объект OLE.
При работе с OLE выделяются два способа связи.
О Связывание объекта OLE с контейнером. В этом случае результирующий файл сохраняется отдельно и должен быть создан до того, как контейнер обратится к нему. Контейнер только ссылается на файл, но не хранит в себе никаких данных. Главное преимущество такого способа — к одному документу может ссылаться множество контейнеров, и при изменении документа все контейнеры получают эти изменения.
口 Встраивание объектов. В этом случае созданный документ хранится в контейнере, и другие приложения не могут получить к нему доступ. В этом случае данные хранятся как часть приложения.
OLE-контейнер
Мы уже написали небольшой пример работы с контейнером OLE (oleContainer), но задействовали только малую часть его возможностей. Сейчас нам предстоит познакомиться с основными свойствами и методами компонента, а также написать более сложный пример, иллюстрирующий его возможности.
Давайте, как всеща, начнем наше рассмотрение со свойств компонента OleContainer, которые могут понадобиться при работе с OLE-объектами.
口 AllowinPlace — если это свойство равно true, то OLE-объект будет создаваться в компоненте, иначе будет запускаться как отдельное приложение.
AutoActivate — определяет способ активизации OLE-объекта. Здесь возможны следующие значения:
aaDoubleCLick — активизация объекта будет происходить по двойному щелчку кнопки мыши;
aaGetFocus — активизация при получениифокуса;
aaManual — активизация вызовом соответствующей функции;
copyOnSave — если это свойство равно true, то при попытке со冲ане- ния создается временный файл, который сжимается для экономии места на диске.
П iconic — если это свойство равно true, то в окне контейнера будет отображаться иконка объекта, иначе — сам объект.
П Linked — если объект связанный, то здесь true.
П OleClassName — здесь хранится имя OLE-объекта.
П OleObject — здесь хранится ссылка на сам OLE-объект.
П OleObj ectlnterfасе — здесь хранится ссылка на интерфейс OLE-объекта.
П Modified — если объект изменен, то это свойство принимает значение true.
П Newlnserted — если объект заново создан командой меню Insert Object, то это свойство равно true.
П OleStreamFormat — если это свойство равно true, то при со冲аненип будет использоваться старый формат OLE 1.0. Это необходимо, если какое-то программное обеспечение не умеет работать с новым форматом.
口 SizeMode — управляет размером объеьсга.
П smAutoSize — размер выбирается автоматически.
О smCenter — ПО центру.
П sinClip ——показывается объект реальных размеров (отображается та часть, которая поместилась в окно).
П smScale — объект масштабируется.
П smstretch — объект растягивается.
Теперь посмотрим на основные методы компонента OLeContainer:
П Change工conDialog — показать окно смены иконки.
П close — закрыть OLE-объект.
П сору — копировать объект в буфер обмена.
П CreateLinkToFile — создать ссылку на файл OLE-объекта.
□ CreateObject — создать в контейнере OLE-объект. Тут два параметра — имя объекта и булево значение, указывающее на необходимость создания объекта в виде иконки.
О CreateObjectFromFile — создать объект из указанного файла. Тут два параметра — имя файла и булево значение, указывающее на необходимость создания объекта в виде иконки.
О DoVerb — передать объекту OLE запрос на выполнение каких-либо действий.
口 InsertObjectDialog — показать окно вставки нового объекта.
О LoadFromFile — загрузить объект из файла. В качестве единственного параметра нужно указать имя файла.
О LoadFromStream — загрузить из потока. В качестве единственного параметра нужно указать поток.
口 objectPropertiesDialog — показать окно свойств объекта.
口 Paste — вставить из буфера обмена.
口 PasteSpecialDialog — показать специальное окно вставки из буфера обмена.
口 Run — запустить объект.
口 SaveAs Document — сохранить объект в виде OLE-документа. В качестве единственно го параметра нужно указать имя файла.
О SaveToStream — сохранить в поток. В качестве единственного параметра нужно указать поток.
С учетом всего сказанного давайте попробуем улучшить пример, написанный в предыдущем разделе главы. Откройте его, добавьте одну панель TToolBar и поместите на нее пять кнопок: Вставить объект. Свойства объекта, Вставить из файла. Открыть объект. Сохранить объект. Закрыть объект.
Форму получившейся программы вы можете увидеть на рис. 22.3. Можете создать точно такую же форму, а можете ее реализовать по-своему, это зависит от ваших желаний.
Для события Onclick кнопки Вставить объект напишите следующий код:
procedure TForml.InsertButtonClick(Sender: TObject);
begin
OleConta iner1.InsertObjectDialog;
end;
Теперь, после нажатия этой кнопки, программа будет отображать уже знакомое вам окно (см. рис. 22.1), с помощью которого можно будет сменить данный OLE-объект на другой.
Рис. 22.3. Форма будущей программы
Для события Onclick второй кнопки (Свойства объекта) напишем код:
procedure TForml.PropertiesButtonClick(Sender: TObject);
begin
OleContainerl.Obj ectPropertiesDialog;
end;
Этим кодом мы будем отображать окно свойств объекта. Пример такого окна вы можете увидеть на рис. 22.4. В принципе, туг не так уж и много параметров, которые вы можете изменить. На вкладке Просмотр можно изменить только масштаб (если объект поддерживает масштабирование) или изменить значок.
Следующей идет кнопка Вставить из файла. Для события onclick этой кнопки напишем код:
procedure TForml. InsertFromFileToolButtonClick(Sender: TObject);
begin
if OpenDialogl.Execute then
OleContainerl・ CreateObjectFromFile(OpenDialogl.FileName, false); end;
Вот здесь добавлено окно открытия файла OpenDialog. В первой строке мы показываем это окно, и если пользователь выбрал файл, то во второй строке создаем объект на основе выбранного файла. Попробуйте запустить приложение и открыть файлы типа BMP, DOC или XLS-файл. Как только вы нажмете кнопку Открыть, так сразу перед вами появится OLE-объект,
соответствующий выбранному файлу. Если вы выберете файл типа DOC, то запустится Word.
Рис. 22.4. Форма будущей программы
Для события onclick кнопки Сохранить пишите следующее:
procedure TForml.OpenButtonClick(Sender: TObject);
begin
OleContainerl.LoadFromFile(*ole.dat');
end;
Здесь мы отБсрываем объект из указанного файла. В качестве имени файла используется 'ole.dat'. Вы же можете добавить на форму компонент OpenDialog, чтобы пользователь сам мог выбирать имя файла, который надо открывать.
Для события onclick кнопки Сохранить файл напишем код:
procedure TForml.SaveButtonCLick(Sender: TObject);
begin
OleContainerl.SaveToFile(*ole.dat'); end;
Здесь сохраняется объект в тот же файл — ole.dat. Опять можно дать возможность пользователю выбирать имя файла с помощью компонента OpenDialog.
Внимание
Файл, созданный OLE-объектом, несовместим с форматом самой программы объекта. Это значит, что ole.dat——это не BMP-картинка, хотя и BMP — родной формат для программы PaintBrush. Таким образом, вы не сможете открыть файл ole.dat с помощью приложения PaintBrush.
Для события onclick кнопки Закрыть запишем следующее:
procedure TForml.CloseButtonClick(Sender: TObject);
begin
OleContainerl.Close;
end;
Здесь мы закрываем запущенный OLE-объект. Если пользователь дважды щелкнул по контейнеру, то OLE-объект запускается, и закрыть его можно с помощью этой кнопки.
К Примчанме 工
На компакт-диске в папке \ Примеры \ Глава 22 \ OLE1 вы можете увидеть пример этой программы.
Создание собственного окна вставки OLE-объекта
Использование стандартного окна — это хорошо, но можно создать и свое окно. Тем более, что это не так уж и сложно, заодно и вспомним, как работать с реестром.
Внимание
Если вы пока не думаете создавать собственные окна, прочтение этого раздела является обязательным, потому что здесь будут показаны некоторые приемы работы с реестром, о которых ранее не рассказывалось.
Откроем предыдущий пример и создадим в нем новую форму. На ней обязательно должен присутствовать компонент TListBox с именем OLEitemsListBox и две кнопки (Да и Отмена). Эту форму вы можете увидеть на рис. 22.5.
В разделе public объекта формы нужно объявить одну переменную Programs ID,
Имеющую ТИП TStrings:
public
{ Public declarations }
ProgramsID:TStrings;
Рис. 22.5. Форма окна загрузки OLE-объектов
Почему мы объявляем именно в разделе public? В этом параметре будет храниться список найденных идентификаторов, по которым мы потом будем создавать OLE-объект. К переменной Programs ID придется обращаться из основной формы, поэтому переменная должна быть доступна и* находиться в разделе public.
Ее нужно проинициализировать по событию onCreate формы:
procedure TlnsertOLEForm.FormCreate(Sender: TObject);
begin
Prog ramsID:=TSt ringList.Create;
end;
Уничтожаться переменная будет по событию OnDestroy:
procedure ТIns ertOLE Form.FormDestroy(Sender: TObject);
begin
ProgramsID. Free;
cad;
Теперь создаем обработчик события Onshow, тде будет происходить загрузка из реестра списка доступных в системе OLE-объектов. Здесь должен быть код, представленный в листинге 22.1.
I Листинг 22.1. Обработчик события OnStiow
procedure TlnsertOLEForm.FormShow(Sender: TObject);
var
i:Integer;
CLSID:String;
Keys:TStrings;
reg:TRegistry;
begin
Keys:=TStringList.Create;
ProgramsID.Clear;
OLEItemsListBox. I terns. Clear;
reg:=TRegistry.Create;
reg.RootKey:=HKEY_CLASSES_ROOT;
reg.OpenKey(1\1, false);
reg.GetKeyNames(Keys);
for i:=0 to Keys.Count-1 do
begin
reg.CloseKey;
reg.OpenKey(Keys.Strings[i], false);
if not reg.KeyExists(* Insertable*) then continue;
OLEItemsListBox.Items.Add(reg.Readstring(1 *));
reg.OpenKey('CLS工 D', false);
CLSID:=reg.ReadStr ing(*•);
reg.CloseKey;
if reg.OpenKey(* CLSID\'+CLSIDr false) then
begin
if reg.OpenKey('Progid', false) then
ProgramsID.Add(reg.Readstring(*'))
else
ProgramsID.Add(*[Нет идентификатора программы]*)
end;
end;
Keys.Free;
end;
В первой строке инициализируется переменная Keys, которая имеет тип TStrings. Это будет список строк, в котором будут храниться все ключи раздела реестра с описанием OLE-объектов.
Во второй строке очищаем список переменной ProgramsID, потому что там может что-то остаться после предыдущего вызова окна.
В следующей строке очищаем компонент TListBox.
Все, приготовление окончено. Теперь нужно проинициализировать переменную reg, т. е. открыть реестр. Как вы помните, реестр открывается на разделе hkey current user, а информация об установленных OLE-объектах находится в разделе hkey_clas ses_root т Именно поэтому мы тут же меняем имя раздела, чтобы перейти в нужный.
В следующей строке открываем подраздел — \. В качестве второго параметра метода открытия раздела (орепкеу) стоит значение false, что говорит о том, что если нужный раздел не существует, то его не надо создавать. В принципе, такой ситуации не может быть, потому что даже в минимальной установке Windows этот раздел существовать будет. Вследствие этого второй параметр не очень важен.
В следующей строке получаем список подразделов (с помощью метода GetKeyNames) текущего раздела. Результат буцет записан в переменную Keys. Теперь можно запускать цикл и проверять все найденные ключи на соответствие подключаемым OLE-объектам.
Внутри цикла сначала закрываем текущий раздел и после этого открываем очередной раздел из списка Keys. В открытом разделе проверяем наличие ключа Insertable. Если его нет, то вызывается continue, чтобы прервать цикл и перейти на следующий элемент. Если такой ключ есть, то читаем тип текущего OLE-объекта и добавляем его в список ListBox:
OLE Items Li st Box. Items .Add (reg. Readstring (•*));
Далее необходимо прочитать идентификатор объекта, по которому потом мы будем создавать выбранный объект. Для этого открываем раздел clsid, в котором хранится уникальный номер объекта, и читаем этот номер.
После прочтения: CLSID закрываем текущий ключ и открываем раздел с именем ключа. Если он открыт, то происходит попытка чтения идентификатора. Если он прочитан, то добавляем идентификатор в список Programs ID, иначе добавляем строку — "Нет идентификатора программы". В принципе, при нормальном раскладе все должно быть хорошо. Единственная ситуация, когда идентификатор не буцет найден, — это в случае, если OLE-объект установлен в системе или удален из нее неправильно.
После перебора всех строк списка мы уничтожаем переменную Keys.
Теперь переходим к основному окну и здесь для: события Onclick кнопки Вставить объект напишем следующий код:
procedure TForml.InsertButtonClick(Sender: TObject);
begin
InsertOLEForm.ShowModal;
if InsertOLEForm.ModalResult=mrOK then
OleContainerl. CreateObject (InsertOLEForm. Programs ID. Strings [
InsertOLEForm. OLEItemsListBox. Itemindex], false);
В первой строке мы показываем созданное окно. Если пользователь нажал кнопку ОК (свойство ModalResult равно mrOK), то надо выполнить следующую строку, в которой создается OLE-объект с помощью вызова метода createobject контейнера. В качестве параметра мы указываем идентификатор из списка ProgramslD выделенной в списке строки.
InsertOLEForm. OLE Items Li st Box. Itemindex— указывает на Выделенную строку в списке.
InsertOLEForm. ProgramslD. Strings [строка] — здесь Хранится идентификатор.
В принципе, программа примера готова. Единственный ее недостаток — нет проверки на ошибки. Возможны ситуации, когда в системе есть объекты с испорченными записями (не прав ильная установка или удаление записей), в этом случае программа может зависнуть. Самый простой способ устранения этого недостатка — поставить вызов функции Createobject в блоке try. . . except. . . end для: исключения: подобных ошибок. Однако у нас нет проверки на выделение элемента. Возможно, что пользователь увидит окно и нажмет ОК, но при этом ни одна строка списка не будет выделена. В этом случае, перед загрузкой, желательно проверять параметр InsertOLEForm.OLEItemsListBox.Itemindex На правильное значение. ЕСЛИ свойство Itemlndex больше или равно нулю, то одна из строк выделена, если равно -1, то выделенных строк в списке нет, а пользователь нажал ОК.
[Пр.г/ючание^^Д
На компакт-диске в папке \ Примеры \ Глава 22 \lnsertDialog вы можете видеть пример этой программы.
Элементы управления ActiveX
Теперь разберемся с элементами управления ActiveX. Единственный компонент, который многие программисты используют регулярно, — Internet Explorer. Да, это тоже компонент ActiveX, который использует Windows, хотя вы и видите его в виде программы. Именно потому, что этот компонент встроен в Windows и присутствует на всех машинах, его используют достаточно часто.
Запустите Delphi. Перейдите на вкладку Internet палитры компонентов. Здесь должен быть компонент WebBrowser (он должен быть последний). Если у вас верен兄 Delphi меньше, чем пятая, то этого компонента может и не быть. Он может отсутствовать, и если вы отказались устанавливать компоненты Internet (по умолчанию они ставятся).
Если компонента WebBrowser нет, то его надо установить для: дальнейшей работы. Выберите пункт меню Import ActiveX Control из меню Component. Перед вами должно открыться окно, как на рис. 22.6.
ImportActiveXImportActiveX
ImportActiveX
ImportActiveX
Import Active J
Microsoft DDS [Version 2.0),
Microsoft DireclAnimation Media Conlrols (Version 1D)
Microsoft FlexGrid Control 6.0 (SP3) [Version 1 .0)1
Microsoft Forms 2.0 Object Library (Version 2.0)
Microsoft HTML Obed Librafy (Version 4.0]
Microsoft Internet Transfer Control 6.0 (SP4) (Version 1 .0) 刁 |
D:\WIND0WS\System32\shdocvw. dB
Palette page:Unit dir name:Search p*hPalette page:Unit dir name:Search p*hAdd.. Г fiemove I |
Palette page:
Unit dir name:
Search p*h
Palette page:
Unit dir name:
Search p*h
TWebBrowset_V1 TWebBrowserTWebBrowset_V1 TWebBrowsera
TWebBrowset_V1 TWebBrowser
TWebBrowset_V1 TWebBrowser
33〔ActiveX
3
3
[ЁАРгод|^гтГр5е$\ВоНап5\^jJ !
RqELPH叭LbSQELPH小BinSQELPHIJVmpor」j
Install…Create link j Cancel . : Help
Рис. 22.6. Окно установки компонента ActiveX
В списке выбора этого окна (сверху) найдите строку Microsoft Internet Controls (Version 1.1). Вер си 兄 может отличаться, но это не важно. Теперь нажмите кнопку Install. Можно было бы нажать кнопку Create Unit, чтобы создать модуль с описанием ActiveX, но это излишне, потому что по нажатии кнопки Install этот модуль создается автоматически.
Перед вами откроется окно выбора пакета, показанное на рис. 22.7.
Окно выбора пакета очень похоже на то, которое мы видели при установке компонентов VCL. Точнее сказать, это то же самое окно. Только здесь чаще всего вы не выбираете имя файла (он будет создан из компонента ActiveX). Самое главное — выбрать пакет, в который будет происходить установка. На вкладке Into new package вы можете создать новый пакет точно так же, как и при создании пакетов для VCL-компоненте в. Пакеты
одни и те же, разницы никакой, поэтому в каждом из них могут быть как компоненты VCL, так и ActiveX.
Рис.'22.7. Окно выбора пакета
Выберите пакет и нажмите ОК. После этого появится запрос на его компиляцию. Delphi откомпилирует необходимые файлы и установит компонент для работы с браузером.
Следующим появится окно, которое сообщит об успешной установке нового компонента. Нажмите ОК и закройте все, что открыл Delphi. Для этого выберите Close All из меню Ffle. Теперь у вас есть компонент WebBrowser, только он расположен на странице ActiveX палитры компонентов.
Мы будем использовать Microsoft Internet Controls, т. е. движок установленного на вашем компьютере IE (Internet Explorer). Эго значит, что ваш браузер будет подвержен всем нарушениям в работе, что и соответствующий ему движок. Единственное, что может вас здесь успокоить, — это то, что интерфейс не будет сложным. Он будет таким, какой вы захотите, потому что сделан вашими руками.
Теперь перейдем к программированию.
Создайте новый проект и сразу измените заголовок и иконку. Установите на форму наш компонент WebBrowser (он находится на вкладках Internet или ActiveX палитры компонентов). В результате у вас появится белый квадрат с именем WebBrowser 1. После этого установите на форму CoolBar, который находится на вкладке Wn32 палитры компонентов. Эта панель должна выровняться по верхнему краю формы. Теперь выделите web Browseri и перейдите в объектный инспектор. Щелкните по свойству Align и в выпадающем списке выберите aiciient. Компонент WebBrowser должен растянуться на все свободное место формы.
Теперь установите на coolBarl (его недавно установили на форму) панель ToolBar из вкладки Wn32 и СошЬоВох из вкладки Standard палитры компонентов. Все это вы должны установить именно на cooiBarl, иначе будет
получен некрасивый набор компонентов. После этого нужно выделить CoolBarl и перейти в объектный инспектор. Здесь нужно изменить строку AutoSize на true (по умолчанию она false).
Если все действия выполнили правильно, то теперь выделите ComboBoxl (выпадающий список) и переходите в объектный инспектор. Здесь нужно выделить вБсладку Events и дважды щелкнуть кнопкой мыши по строке OnKeyDown, чтобы создать обработчик этого события. Как и раньше, Delphi создаст процедуру обработчика. Она будет вызываться каждый раз, когда вы будете вводить какую-нибудь букву в ComboBox. В этом обработчике со бытия напишите следующий код:
procedure TForml.CoroboBoxlKeyDown (Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key= VK_RETURN then
WebBrowser1.Navigate(ComboBoxl.Text); end;
Здесь мы проверяем, если переменная Key равна vk_return (т. е. если нажата Клавиша <Enter>), ТО выполняется действие — WebBrowserl. Navigate (ComboBoxl.Text). По существу происходит вызов метода Navigate нашего браузера и в качестве параметра указывается текст компонента СотаЬоВох. Метод Navigate заставляет браузер открыть указанную страничку.
Если вы устанавливали компонент WebBrowser как ActiveX и он не стоял у вас изначально, то возможно, что при компиляции Delphi сделает ссылку на недостаточность параметров. Он может запросить три или более дополнительных параметра типа OleVariant. В этом случае объявите три переменные такого типа:
procedure TForml.ConiboBoxlKeyDown(Sender: TObject; var Key: Word;
var
pl.p2,p3:OleVariant;
begin
if Key= UK—RETURN then
WebBrowserl.Navigate(ComboBoxl.Textr pl, p2z p3); end;
Можно объявить одну переменную и подставить ее в метод Navigate три раза.
Нажмите клавишу <F9>, и ваша программа должна начать работу. Введите какой-нибудь адрес в строку ComboBox и нажмите <Enter>. Если вы правильно ввели адрес, то в WebBrowserl через несколько минут должен появиться указанный сайт.
Давайте доработаем этот пример. Щелкните по TooiBari и снова переходите в объектный инспектор. Здесь нужно изменить свойства Autosize, ShowCaption и Flat на true (все они по умолчанию равны false). Теперь щелкните правой кнопкой мыши по TooiBari и из появившегося меню выберете пункт New Button. На компоненте TooiBari должна появиться новая кнопка с именем ToolButtonl. Выделите ее и в объектном инспекторе поменяйте свойство caption на Открыть. Создайте еще насколько кнопок с заголовками: Назад, Вперед, Стоять, Обновить и Печать. Полученная в результате этих действий форма показана на рис. 22.8 (в ней еще добавлены картинки к кнопкам).
Рис. 22.8. Форма будущей программы
Теперь установите на форму openDialog из вкладки Dialogs палитры компонентов. Дважды шелкние по кнопке Открыть, и Delphi автоматически создаст процедуру, которая будет вызываться при нажатии этой кнопки. В этой процедуре нужно написать следующий код:
procedure TForml.ToolButtonlClick(Sender: TObject);
begin
if OpenDialogl.Execute then
begin
WebBrowser1.Navigate(OpenDialogl.FileName);
ComboBoxl.Text:-OpenDialogl.FileName;
end;
end;
Здесь мы заставляем наш браузер загрузить открытый файл и 6 строке CoinboBox отображаем его имя, чтобы пользователь знал, какая страница сейчас загружена.
Теперь вы можете запустить программу и открыть с помощью этой кнопки любой файл на диске.
Теперь заставим работать остальные кнопки. Дважды щелкните по кнопке Назад. Какой будет результат, вы уже догадались. В созданной заготовке процедуры напишите код:
procedure TForml.ToolButton2Click(Sender: TObject);
begin、
WebBrowserl.GoBack;
end;
Структура процедуры прозрачна. Мы просто заставляем WebBrowserl переходить на предыдущую страницу. Повторите те же действия для кнопки Вперед, чтобы создать процедуру обработчика onclick. Напишите для нее следующий код: procedure TForml.ToolButton3Click(Sender: TObject); begin
WebBrowserl.GoForward; end;
Для кнопки Стоять напишите код:
procedure TForml.ToolButton4Click(Sender: TObject); begin
WebBrowserl. Stop; end;
Для кнопки Обновить:
procedure TForml.ToolButton5Click(Sender: TObject);
begin
WebBrowserl.Refresh;
end;
И, наконец, для кнопки Печать:
procedure TForml.ToolButton6Click(Sender: TObject);
var
PostData, Headers:OLEvariant;
begin
WebBrowserl • ExecWB (OLECMDID_PRINT, OLECMDEXECOPT_DODE FAULT, PostData, Headers);
Здесь только одна строка кода. В этой строке посылается команда через OLE ядру IE. Просто скопируйте ее один к одному в исходный код. Это особенности браузера, в которые вникать просто нет смысла, все равно такое вы наверно нигде больше не увидите.
Теперь можете нажать клавишу <F9> и программа должна запуститься. Попробуйте поработать с ней. Закройте браузер. Осталось сделать последние штрихи.
Для начала установите на форму компонент status В аг с вкладки ^1п32 и измените у него свойство Simple Panel в true (по умолчанию fal-se). Теперь выделите WebBrowserl и щелкните по вкладке Events в объектном инспекторе. Двавды щелкните по строке onStatusTextChange и напишите в созданной процедуре следующее:
procedure TForml .WebBrowserlStatusTextChange (Sender: TObject;
const Text: WideString);
begin
StatusBarl.SimpleText:=Text; end;
Здесь мы присваиваем переменную Text (в ней хранится текст подсказки), которую получили в качестве параметра обработчика, в StatusBarl. Теперь вы сможете видеть подсказки в строке состояния.
Давайте добавим еще индикатор загрузки. Для этого установите на форму компонент ProgressBar с вкладки VWn32. Измените у него свойство Align на alBottom, чтобы он находился вдоль нижней границы формы. Снова выделите WebBrowser! и щелкните по вкладке Events в объектном инспекторе. Двавды щелкните по строке onProgressChange и напишите в созданной процедуре следующий код: procedure TForml.WebBrowserlProgressChange(Sender: TObject; Progress, ProgressMax: Integer);
begin
ProgressBarl.Max:=ProgressMax;
ProgressBarl.Position:=Progress; end;
Здесь созданному компоненту ProgressBarl (индикатор затрузки) присваиваются максимальное значение (ProgressMax) и текущее значение (progress), которые получены в качестве параметров.
Можете пользоваться полноценным браузером в свое удовольствие. Конечно же, это не все возможности, которые можно получить, используя компонент WebBrowseri. Сюда еще очень многое можно добавить — главное, чтобы хватило воображения и умений.
Примечание ;
На компакт-диске в папке \ Примеры \ Глава 22 \WebBrawser вы можете увидеть пример этой программы.
Модель СОМ
Модель COM (Component Object Model — объектная модель компонентов) это независимая от языка программирования спецификация объектов. В спецификации СОМ -объекты называют интерфейсами (потому что у них есть небольшие отлнчия:), но мы булем использовать понятие "объект". Да и нет смысла вводить новое понятие из-за того, что кому-то из Microsoft захотелось выделиться.
Прежде чем приступать к рассмотрению модели, необходимо рассмотреть пару определений. Взгляните на приложение, написанное в предыдущем разделе (браузер Internet). Там мы писали программу, которая использует компонент ActiveX ядра IE. Так вот, наше приложение называется Бслиент- ским, а ядро IE, к которому мы подбслючились, называется СОМ-сервером. Вы должны обязательно понимать эту разницу, когда будете читать остальной материал этой главы.
工 npMivieuaHne
Самое большое отличие объектов СОМ в том, что они реализовываются в виде отдельных файлов. Когда вы хотите использовать этот объект, то должны загрузить этот файл объекта.
Файл с объектом называют сервером СОМ. Такие серверы могут быть выполнены в виде динамических библиотек DLL или исполняемых файлов в формате EXE. Если сервер реализован в виде динамической библиотеки, то его называют внутренним. Ну, а если в виде запускного файла, то локальным (внешним). Локальный сервер функционирует в своем собственном адресном пространстве. Внутренний сервер загружается в адресное пространство Бслиентского процесса (вашей программы). COM-серверы могут быть также и удаленными, когда они выполняются на другой машине. В этом случае используется расширенная спецификация DCOM (Distributed COM — распределенный COM).
Клиентское приложение, которое хочет загрузить СОМ-сервер, никогда не задумывается о том, где находится сам сервер. Приложение может узнать реальное его расположение (через реестр Windows). Но это ему ненужно. За все отвечает операционная система. Ей необходимо только передать GUID (globally unique identifier — глобально уникальный идентификатор) сервера, который нам нужен, и ОС все остальные функции инициализации выпол
нит самостоятельно.
Что значит GUID? Это номер, который СОМ-объект получает на этапе проектирования. Он генерируется случайным образом, и никакие два объекта не смогут иметь одинаковые GUID.
工 Примечание 彳
За уникальность GUID отвечал сам Билл Гейтс, но при этом делал оговорку, что вероятность совпадения дух номеров GUID все-таки есть, хотя и ничтожно мала. А что же будет, если все же эта вероятность сработает? Неужели вместо запрашиваемого ядра IE мы увидим ядро Excel? Здесь трудно что-либо предположить и надо надеяться, что ничего страшного все же не произойдет.
Мы не будем дальше вести разговор о структуре технологии СОМ, потому что она достаточно сложна и для разработки приложений абсолютно не требуется. Лучше остановимся на практике ее использования.
Все объекты и модели СОМ происходят от IUnknown. Как видите, имена объектов СОМ начинаются с буквы / (имена объектов Delphi начинаются с 7), символизируя слово interface. Объект lunknown действует так же, как TObject для объектов Delphi. Это основа, от которой происходят все остальные объекты. В нем реализуются основные методы, которые могут понадобиться впоследствии для других объектов. В модели СОМ — это три метода.
口 Query Inter face — метод служит для получения информации о встроенных в СОМ объектах или интерфейсах. Когда вам нужно загрузить какой-нибудь объект, то с Помощью этого метода проверяется его доступность. Если метод подтверждает возможность использования указанного объекта, то его можно загружать.
П _AddRef и Release — эти два метода используются для создания и уничтожения объекта. Объекты СОМ похожи на динамичес кие библиотеки. В память может быть загружена только одна версия, и все клиенты будут обращаться к ней. Для реализации всего этого необходимо контролировать —сколько клиентов подключено к объекту, чтобы знать, когда можно уничтожать их из памяти.
Когда мы загружаем объект, то вызывается метод __AddRef, который увеличивает внутренний счетчик на единицу. При следующем обращении к объекту снова вызывается этот метод и снова счетчик увеличивается на 1. Когда какое-то приложение отключилось от объекта (вызван метод Release), счетчик уменьшается на 1, и если он равен 0, то объект можно выгружать из памяти. В противном случае с ним еще кто-то работает и выгрузка невозможна.
Объект lunknown объявлен в Delphi следующим образом:
IUnknown 疋 interface
[*{00000000-0000-0000-С000-000000000046}1]
25 Зак 942
Acbve FormAcbve FormActive ServerObjectActive ServerObjectCOM ObjectCOM Object争COM+ EventObjecl争COM+ EventObjecl0COM+Subscfipti-0COM+Subscfipti-圍Property Page圍Property Page国TransactionalObjecl国TransactionalObjecl
Acbve Form
Acbve Form
Active Server
Object
Active Server
Object
COM Object
COM Object
争
COM+ EventObjecl
争
COM+ EventObjecl
0
COM+Subscfipti-
0
COM+Subscfipti-
圍
Property Page
圍
Property Page
国
TransactionalObjecl
国
TransactionalObjecl
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function JtddRef: Integer; stdcal1;
function _Release: Integer; stdcal1;
end;
Как видите, в первой строке идет имя нового интерфейса и после знака "равно" ставится ключевое слово interface. Во второй строке, в квадратных скобках, нужно указывать GUID интерфейса.
ССовет )
Никогда не пишите GUID вручную. Когда вы будете писать собственные СОМ- объекты, то этот уникальный номер будет генерировать ОС.
В примере показан объект IUnknown только для того, чтобы вы смогли увидеть самый простейший COM-объект. Теперь давайте посмотрим, как создаются СОМ-объекты на практике.
Для создания СОМ-объекта в Delphi нужно выбрать команду меню File I New | Other. Перед вами откроется уже знакомое окно создания нового проекта. Здесь перейдите на вкладку ActiveX и посмотрите, что доступно (рис. 22.9). Тут достаточно много разных типов СОМ-объектов, и мы не будем рассматривать их все.
New Items
IntraiWeb I WebServices | Business j WebSnap | Web Documents | New ActiveX J Multitier ] Projectl ) Forms | Dialogs | Projects ]
卷电费
ActiveX ActiveX Library Automation
ControlObject
Type Library
Г СояГ СояГ Inherit ГГ Inherit Г
Г Соя
Г Соя
Г Inherit Г
Г Inherit Г
CancelCancel
Cancel
Cancel
Help
Рис. 22.9. Окно создания COM-объектов
Примечание
Тема COM — это отдельный разговор, и если вам захочется узнать больше, то желательно купить отдельную книгу. Если вы не будете связывать свою жизнь только с СОМ, то информации, которая вам здесь дается, будет достаточно для написания приложений средней тяжести, потому что Delphi прячет большую часть рутинной работы, которая связана с программированием СОМ- объектов. Таким образом, использование прямого программирования практически ненужно, оно здесь рассматриваться не будет.
Пример создания ActiveX-форм
Первое, с чем мы познакомимся — Active Form. Это форма, на которой могут располагаться различные компоненты и элементы управления. Такая форма может быть встроена в любую другую программу, поддерживающую СОМ- технологию или даже опубликоавна в сети Internet. Все это мы рассмотрим на практике в этом разделе главы.
Формы Active Form — это файлы с расширением осх, представляющие собой самый настоящий СОМ-объект. Большинство элементов управления получают расширение осх, чтобы отличать эти файлы от массы других.
Рис. 22.10. Свойства нового ActiveX
Для начала создадим новый проект. Для этого выберите пункт меню File | New I Other и в появившемся окне перейдите на вкладку ActiveX. Здесь выберите пункт ActiveForm и нажмите кнопку ОК. Перед вами должно от-
крыться окно, показанное на рис. 22.10. В нем вы должны указать следующие параметры:
New ActiveX Name — имя вашей ActiveX-формы;
Г Совет )
Постарайтесь дать здесь разумное имя, потому что оно будет потом использоваться для отображения в системе. Не очень приятно будет смотреть на компонент с именем ActiveFormX.
口 Implementation Unit — имя исполнительного модуля;
口 Project Name — имя проекта (постарайтесь дать разумное имя, потому что так будет называться файл);
口 Threading Model — для нас достаточно здесь значения по умолчанию;
Make Control Licensed — создать лицензию для компонента;
П Include Version Information — включить в компонент информацию о версии; □ Include About Box — включить окно О программе.
Давайте поменяем только имя компонента (simpleActiveX) и проекта (simpleActiveProj 1). checkbox оставим без изменений, потому что не хочется иметь контроля версии или окна О программе. Нажмите кнопку ОК и перед вами появится привычная визуальная форма, на которой можно располагать свои собственные компоненты. Смело располагайте на ней компоненты VCL и работайте, как с привычным проектом. Поставим на форму ТОЛЬКО КОМПОНеНТ TEdit.
Визуальная часть пусть останется за вами, потому что она не должна вызывать особых проблем. Давайте лучше рассмотрим, из чего состоит наш проект. Откройте менеджер проектов и посмотрите на его содержимое. Здесь в дереве находится наш проект с именем SimpleActiveProj 1 .осх, который состоит из двух файлов:
П SimpleActivelmpl 1 — файл, в котором хранится наша форма;
П SimpleActiveProj 1 .pas— файл с описанием возможностей нашего проекта.
(Совет )
Желательно не изменять эти файлы без особой на то надобности. Здесь очень много достаточно сложных алгоритмических конструкций, поэтому лучше оставить их создание на совести Delphi.
После того как вы установили все компоненты, откомпилируйте проект, нажав сочетание клавиш <Ctrl>+<F9>. Откомпилированный OCX-файл готов. Теперь надо зарегистрировать его в системе, чтобы можно было затем его протестировать. Мы уже знаем, что при вызове COM-объектов вся информация о них находится в реестре и все необходимое попадает туда при регистрации. В связи с этим, выберите register ActiveX Server из меню Run.
Для регистрации можно также выполнить в командной строке следующую команду:
Regsvr32.ехе ИмяФайла
Теперь можно переходить к тестированию. В качестве тестирования создадим HTML-странцу и попробуем загрузить форму с помощью IE. Для этого выберите Web Deployment Option из меню Project, и перед вами откроется окно публикации компонента в сети Internet, как это показано на рис. 22.11.
Рис. 22.11. Окно публикации компонента в Интернете
Рассмотрим каждый параметр в отдельности:
口 Target dir (Full path of the deployed OCX) — директория, в которую попадет скомпилированный файл OCX;
口 Target URL (Virtual path of the deployed OCX) — адрес в Internet, откуда будет загружен OCX (когда пользователь будет загружать страничку, то компонент будет считываться именно с этого адреса);
HTML dir (Full path of the deployed HTML file) — директория, в которую попадет шаблон HTML-файла, который потом можно использовать для публикации;
О Use CAB file compression — упаковать OCX-файл в САВ-архив (необходимо указывать только если объект создавался именно для публикации
сети, т. е. компонент сжимается, за счет чего увеличивается скорость его загрузки по сети);
口 Auto increment release number — автоматически увеличивать номер релиза. Заполните поля и нажмите кнопку ОК. Заполнение полей Target dir. Target URL и HTML dir является обязательным. Я советую вам выбирать Target dir и HTML dir отличную от той, в которой находится ваш проект. Эти директории нужно указать реальные, а вот адрес в Internet можно указывать любой. Если компонент уже зарегистрирован в системе, то этот адрес не понадобится. Браузер будет обращаться к этому адресу только в том случае, если в системе не найден указанный компонент. В нашем случае мы уже произвели регистрацию в системе и можем использовать компонент по своему усмотрению.
Теперь выберите пункт меню Web Deploy, и Delphi создаст необходимый для тестирования HTML-файл. Запустите этот файл и вы увидите окно, показанное на рис. 22.12.
! <2 D:\ActiveFormProjLhtm - Microsoft Internet Explorer - [Автономная работа]НВ В ।
Рис. 22.12. Результат запуска HTML-файла
Как видите, достаточно просто создать маленькое приложение, которое сможет работать в сети Internet прямо внутри браузера IE. Только учтите, что если вы собираетесь публиковать свои файлы в Internet, то не каждый пользователь сможет воспользоваться услугами программы. В защите ActiveX очень много дыр, поэтому эти компоненты по умолчанию не могут загружаться из сети на компьютеры пользователей. Чтобы пользователь смог увидеть вашу программу, он должен понизить уровень безопасности в своем IE до минимума и разрешить загрузку ActiveX по сети.
Благодаря такой плохой защищенности компоненты ActiveX не получили должного распространения. Никто не хочет понижать безопасность своего компьютера и надеяться на добропорядочных программистов. яТаких программистов не так уж и много, поэтому лучше лишний раз перестраховаться и не включать ActiveX.
И все же, в локальных сетях ActiveX используют в общем-то часто и много, особенно в России. У таких форм достаточно много преимуществ и они позволяют решить ряд проблем.
工 Примечание
Централизованное обновление. Когда программист внес в свой продукт обновление, то он должен установить его на все машины, работающие с его программой. А если таких компьютеров много? Можно пытаться известить своих пользователей по почте или каким-либо другим способом. А если неизвестны все пользователи? Вот тут уже задача усложняется. В данном случае достаточно только изменить OCX-файл и передать его на сервер, откуда IE получит этот файл (параметр Target URL окна Web Deployment Option). При следующем запуске IE сам загрузит обновленный файл и установит его.
Есть еще один способ протестировать пример. Для этого выберите Import ActiveX Control из меню Component и вы увидите окно, через которое мы устанавливали компонент ActiveX. Найдите в верхнем списке имя компонента и нажмите Install. Delphi установит этот компонент, и его иконка появится на вкладке ActiveX палитры компонентов. Теперь вы сможете устанавливать его на любую форму и использовать как простой компонент.
К Примечание 工
На компакт-диске в папке \ Примеры \Глава 22 \ ActiveForm вы можете увидеть пример этой программы.
Создание компонентов ActiveX
Вы уже наверно убедились на практике, что ActiveX — это достаточно сложная для понимания и разработки технология. Она является продолжением развития OLE, которая была пополнена технологией СОМ и получила
более емкое название — ActiveX. Под этим термином скрывается несколько самостоятельных технологий:
формы ActiveForm — мы с ними познакомились в самом начале;
элементы управления;
П библиотеки;
серверы автоматизации;
П страницы свойств.
Все это объединяется под одним термином СОМ. С формами мы уже познакомились, теперь предстоит познакомиться со вторым СОМ — элементами управления.
Для разработки элементов управления на основе ActiveX нужно иметь достаточно много знаний и навыков. Фирма Borland упростила эту технологию, как для понимания, так и для разработки, создав свою надстройку — Delphi ActiveX (часто сокращается до DAX). Благодаря DAX вы можете любой компонент Delphi превратить компонент ActiveX и использовать его в любой другой среде разработки. Вот именно этим мы и займемся. Сейчас рассмотрим, как создать компонент ActiveX с использованием Delphi и надстройки DAX. Хотя присутствие DAX вы даже не заметите.
Выберите команду меню File | New | Other и на вкладке ActiveX выберите ActiveX Control. По нажатии кнопки ОК перед вами должно открыться окно ActiveX Control Wizard, показанное на рис 22.13.
Рис. 22.13. Мастер создания компонента ActiveX
Давайте рассмотрим содержимое окна ActiveX Control Wizard.
口 VCL Class Name — имя компонента, который мы хотим превратить в ActiveX. Выберите из списка TPanel.
О New ActiveX Name — имя ActiveX-компонента (оставьте по умолчанию).
口 Implementation Unit — имя модуля (оставьте по умолчанию).
О Project Name — имя проекта (оставьте по умолчанию).
П Threading Model — модель потока.
П Make Control Licensed — лицензия компонента. Включайте только в том случае, если захотите продавать свой компонент.
О Include Version Information — включить информацию о версии.
□ Include About Box — добавить окно О программе.
После нажатия кнопки OK Delphi создаст шаблон для нового компонента, в котором уже готовы к использованию все методы и свойства компонента TPanel. Весь исходный код, реализующий компонент, будет находиться в модуле Panel I mpll.pas. Перейдите в него с помощью менеджера проектов и посмотрите на содержимое.
Здесь много процедур и функций, начинающихся словами Get_ или set . Зачем они нужны? В ActiveX нет свойств или переменных, к которым можно было бы обращаться напрямую, как мы это делали с компонентом Delphi. Весь доступ к свойствам происходит через процедуры или функции. Поэтому, чтобы прочитать заголовок панели, Delphi создал функцию Get caption, а чтобы изменить заголовок на новый — Set_Caption (const Value: WideString). Раньше для этого нам достаточно было просто обратиться к свойству caption компонента панели. Сейчас такой трюк не проходит. Немного позже мы напишем собственную реализацию изменения заголовка.
Вся информация о методах компонента находится в библиотеке типов. Чтобы ее увидеть, выберите пункт меню Type Library (библиотека типов) из меню View (рис. 22.14).
Когда вы выбираете какой-нибудь элемент библиотеки, в правой стороне окна появляется нескол ько вкладок. На вкладке Attributes вы можете видеть: П Name — имя объекта;
口 GUID — уникальный номер в библиотеке (не менять);
口 Version — версия;
П LCID — идентификатор языка;
П Help String — краткое описание;
口 Help File — имя файла помощи, связанного с объектом;
П Help Context — идентификатор контекста справки.
囲 PanelXControll.tlb囲 PanelXControll.tlba■0▲4窗奉*i伞■"心才 奇 州a■0▲4窗奉*i伞■"心才 奇 州+ 閹 IPaneX* "《X IPaneKEvents 疝卜PaneN里办 T xAl'gnment+ ・* TxBevelCut + A yxB orderStyle + ф TxDiagMcxle+ 閹 IPaneX* "《X IPaneKEvents 疝卜PaneN里办 T xAl'gnment+ ・* TxBevelCut + A yxB orderStyle + ф TxDiagMcxleRags ] Text |jPareKContfoll|{1474B3E2D13D-48CC-8BAO-8UF5Rags ] Text |jPareKContfoll|{1474B3E2D13D-48CC-8BAO-8UF5Рис. 22.14. Библиотека типовРис. 22.14. Библиотека типов琢ГЮ伙 gngl]琢ГЮ伙 gngl]Attributes I usesName:GUID:Version:iCID:Attributes I usesName:GUID:Version:iCID:
囲 PanelXControll.tlb
囲 PanelXControll.tlb
a■0▲4窗奉*i伞■"心才 奇 州
a■0▲4窗奉*i伞■"心才 奇 州
+ 閹 IPaneX
* "《X IPaneKEvents 疝卜PaneN
里办 T xAl'gnment
+ ・* TxBevelCut + A yxB orderStyle + ф TxDiagMcxle
+ 閹 IPaneX
* "《X IPaneKEvents 疝卜PaneN
里办 T xAl'gnment
+ ・* TxBevelCut + A yxB orderStyle + ф TxDiagMcxle
Rags ] Text |
jPareKContfoll
|{1474B3E2D13D-48CC-8BAO-8U
F5
Rags ] Text |
jPareKContfoll
|{1474B3E2D13D-48CC-8BAO-8U
F5
Рис. 22.14. Библиотека типов
Рис. 22.14. Библиотека типов
琢ГЮ伙 gngl]
琢ГЮ伙 gngl]
Attributes I uses
Name:
GUID:
Version:
iCID:
Attributes I uses
Name:
GUID:
Version:
iCID:
Вот некоторые из флагов, которые вы тоже можете здесь увидеть:
口 None — флаги отсутствуют;
□ Restricted — запретить использование библиотеки в средах программирования макросов;
口 Control — в библиотеке находится компонент ActiveX;
О Hidden — библиотека скрыта от пользователей;
П Displnterface — доступ к свойствам и методам производится только через интерфейс Tdispatch;
口 Nonextensible — если выделен, то реализация интерфейса IDispatch (основной интерфейс ActiveX) будет включать только те свойства и методы, которые показаны в реализации;
口 Dual — методы и свойства интерфейса передаются и через IDispatch и таблицу виртуальных методов;
口 OLE Automation — используются только совместимые с автоматизацией типы данных;
口 Source — указывает, что возвращаемое значение типа VARIANT, являющееся источником событий;
О Bindable — свойство поддерживает связывание данных;
口 Request Edit — свойство поддерживает сообщение onRe quest Edit.
工 Примечание-^
Программа-клиент может получить доступ к интерфейсам (объектам) через специальный интерфейс IDispatch, либо через таблицу виртуальных методов. Интерфейс IDispatch позволяет использовать свойства и методы объектов через уникальный идентификатор DispiD.
Давайте создадим собственный метод. Дл兄 этого вьвделите ветвь iPanelx и щелкните мышью по кнопке New Metod на панели инструментов. Delphi создаст новое объявление метода Methodl. Переименуйте его в setcap. 'Delphi создаст процедуру setcap, которая будет является реализацией метода Set cap. Перейдите на вкладку Parametrs (рис. 22.15) и добавьте новый параметр для процедуры с именем caption с типом LPSTR, и Modifier равным — [in]. Для модификатора (Modifier) можно указывать одновременно И [in], И [out].
応 PanelXControl l.tlb
A♦0▲4雄伞®A♦0▲4雄伞®弁翁 ▼圉后总拗弁翁 ▼圉后总拗
A♦0▲4雄伞®
A♦0▲4雄伞®
弁翁 ▼圉后总拗
弁翁 ▼圉后总拗
Attributes Paiameters | Rags ) Text ] Return Type: |HRESULT'- —..)Attributes Paiameters | Rags ) Text ] Return Type: |HRESULT'- —..)
Attributes Paiameters | Rags ) Text ] Return Type: |HRESULT'- —..)
Attributes Paiameters | Rags ) Text ] Return Type: |HRESULT'- —..)
吟 Locked
® Locked
岭 PaentCotar
好 ParentColof
•峙 ParentCiGD
,ParentCiGD
叮 Visible
i * Visible 礴 DoubleBuffered 。DoubleBulfered
S 礴 VisibleD ockChenlCoui 磽 Cursor
i 守 Cursor
*EEggg
⑵ MyPiop 導 MyiRrap IPanet^Evenls
Paneth
T xARgnmenl
TxBevelCut
A TxBaderStyle
A TxDranMndp二J
Рис. 22.15. Установка параметров на вкладке Parametrs
Чтобы изменить Modifier, нужно дважды щелкнуть по нему кнопкой мыши.
В результате появится окно, показанное на рис. 22.16. Здесь вам доступны:
О In — означает, что метод является процедурой и используется для установки значений;
口 Out — говорит о том, что метод будет считывать значение компонента;
□ RetVal — метод будет возвращать значение.
Рис. 22.16. Изменение параметра Modifier
Теперь перейдите с помощью Менеджера проектов в модуль PanelImpll.pas, найдите процедуру Setcap и напишите в ней следующее:
procedure TPaneIX.SetCap(Caption: PChar);
begin
FDelphiControl.Caption:=Caption;
end;
FDelphiControl указывает на компонент, с которым мы работаем. В данном случае это TPanel. У него мы изменяем свойство caption на то значение, которое указано в качестве параметра в нашей процедуре Setcap. Чтобы вы лучше понимали, перейдите в начало модуля и посмотрите на объявление type: type
TPaneIX 二 class(TActiveXControl, IPaneIX) private
{ Private declarations }
FDelphiControl: TPanei;
Здесь можно увидеть, что переменная FDelphiControl имеет тип т?апе1, т. е. панели, из которой мы формируем компонент ActiveX. Через эту переменную мы можем обращаться к методам и свойствам привычной нам панели.
С помощью setcap мы изменяем свойство caption у TPanel, т. e. наш метод делает то же, что и set caption.
Рис. 22.17. Создание свойства
Теперь создадим свойство. Щелкните мышью по кнопке New Property и выберите пункт Read | Write (рис. 22.17).
Delphi создаст два метода — один для чтения свойства, а другой для записи. Переименуйте их в МуРгор. В модуле Panel Impl 1 у вас появятся две новые процедуры: function TPanelX.Get_MyProp: Integer; begin
end;
И
procedure TPanelX.Set_MyProp(Value: Integer); begin
end;
Можете их реализовать. Зарегистрируйте новый компонент в системе (Run I Register ActiveX Server). Теперь можете установить его на палитру компонентов (Component | Import ActiveX Control) и протестировать. После регистрации компонент будет практически не виден на палитре, потому что у него не будет иконки. Чтобы найти созданную панель, перейдите на вкладку ActiveX и проведите указателем мыши по палитре компонентов. Самой правой окажется Panelx.
Для теста нужно выполнить следующие действия:
Создайте новый проект.
Поставьте на форму новый компонент.
Поставьте кнопку и для ее события onclick напишите код:
procedure TForml .ButtonlClick(Sender: TObject);
begin
PanelXl.SetCap( 1 привет * );
end;
工 Примечание"
На компакт-диске в папке \ Примеры \ Глава 22 \ Control вы можете увидеть пример этой программы.
С СовётЗ
На компакт-диске в папке "Документация" вы можете найти документ под названием Программирование PWS.doc. В нем описывается процесс написания приложений для WEB-сервера. Эти приложения пишутся на основе технологии СОМ. Даже если вы не будете писать свои программы под WEB-сервер, документ может оказаться полезным и в будущем может пригодиться.
Глава23Буфер обменаГлава23Буфер обмена喻喻
Глава23
Буфер обмена
Глава23
Буфер обмена
喻
喻
Кнопки Копировать и Вставить есть практически в любом полноценном приложении. Вы тоже, очевидно, захотите использовать их возможности в своих программах. В связи с этим в данной главе рассматриваются вопросы, касающиеся организации переноса данных между приложениями.
Если в вашей программе есть строки ввода TEdit, то они уже умеют работать с буфером обмена. Попробуйте щелкнуть правой кнопкой мыши в любом месте такой строки, и перед вами откроется меню, в котором есть все необходимые пункты. ОС Windows очень много делает самостоятельно и нам не приходится заботиться о многих мелочах.
С другими компонентами Windows дело обстоит сложнее. Тут нам придется немного поработать на клавиатуре. Но эта работа не так уж сложна, и вы убедитесь в этом, когда закончите чтение этой главы.
Буфер обмена
и стандартные компоненты Delphi
Большинство компонентов Delphi уже готовы к работе с буфером обмена. В основном это касается тех компонентов, которые содержат какие-либо данные, которые пользователь может поместить в буфер обмена. Если возможность копирования в буфер необходима, то у компонента должны быть методы с именами:
□ CutToClipboard — вырезать в буфер обмена;
П CopyToCl ipboard — копировать в буфер обмена;
口 Paste FromC 1 opboa rd — вставить из буфера обмена.
Давайте посмотрим на эти методы в действии.
Создайте новое приложение и поместите на его форму три кнопки: Вырезать, Копировать и Вставить. По центру окна растяните компонент тмешо. Полученную форму можно увидеть на рис. 23.1.
Рис. 23.1. Форма будущей программы
Теперь создадим обработчики событий для кнопок. Для события onclick кнопки Вырезать напишите следующий код:
procedure TForml.ToolButtonlClick(Sender: TObject);
begin
Memol.CutToClipboard;
end;
Здесь вызывается один метод CutToClipboard компонента Memol. Этот метод вырезает вьвделенный текст в буфер обмена.
Для события onclick кнопки Копировать запишем код:
procedure TForml.ToolButton2Click(Sender: TObject);
begin
Memol•CopyToClipboard;
end;
В этой процедуре происходит копирование вьвделенного фрагмента текста в буфер обмена с помощью метода CopyToClipboard.
Ну, и для кнопки Вставить напишем вызов метода РаsteFromClipboard компонента Memol в процедуре, вызываемой по событию Onclick. С помощью этого метода все содержимое буфера обмена будет вставлено в компонент Memol.
Теперь попробуйте запустить приложение и скопировать какой-нибудь текст в буфер обмена с помощью нашей кнопки Копировать. После этого нажмите на кнопку Вставить, и скопированный текст появится в текущей позиции курсора. Если вы скопируете в буфер какое-нибудь изображение из любого графического редактора и попытаетесь вставить его в компонент Memol, то ничего не произойдет. Система сама следит за форматом данных, хранимых
в буфере обмена. Чуть позже мы рассмотрим, как сделать так, чтобы кнопка Вставить была активна только тогда, когда в буфере есть данные и они соответствуют нужному формату.
Попробуйте щелкнуть правой кнопкой мыши внутри компонента Memol. Перед вами так же должно появиться всплывающее меню с пунктами, которые необходимы для работы с буфером обмена.
工 Примечание ]
На компакт-диске в папке \ Примеры \ Глава 23 \ Memo Clipboard вы можете увидеть пример этой nporpaMivbi.
Объект Clipboard
Для работы с буфером обмена в Delphi есть объект clipboard. Несмотря на то, что это объект, его не надо инициализировать (как TApplication и TPrinter), а можно использовать без всяких подготовительных действий. Достаточно только подключить в разделе uses модуль clipbrd и объект становится доступным.
У этого объекта не так уж много свойств и методов. Рассмотрим их на практике. Для начала модернизируем пример, написанный в предыдущем разделе главы.
Откройте предыдущий пример и сразу добавьте в раздел uses модуль Clipbrd. Теперь по событию Onclick для кнопки Копировать напишите следующий код:
Clipboard.SetTextBuf(PChar(Memol.SelText));
Здесь мы используем метод SetTextBuf объекта clipboard. Этот метод копирует текст, переданный в качестве параметра, в буфер обмена. В качестве параметра мы передаем выделенный в компоненте Memol текст — Memol. SelText. Единственное, что здесь необходимо учесть, это то, что обязательно надо привести текст к типу PChar.
Для события Onclick, связанного с нажатием кнопки Вырезать, напишем следующий код:
Clipboard.SetTextBuf(PChar(Memol.SelText));
Memol.SelText:= 一;
Вырезать — значит скопировать текст в буфер и потом удалить его из компонента Memol. Именно это мы и делаем. В первой строке текст копируется, а во второй строке мы обнуляем (удаляем) выделенный текст.
Для события onclick кнопки Вставить пишем следующий код:
Memol.SelText:=Memol・ SelText+Clipboard.AsText;
Свойство AsText объекта clipboard указывает на содержимое буфера обмена в виде текста. В этом коде мы ирнбавляем это содержимое к вьвделенно- му тексту и вставляем это все в выделенный текст. Таким нехитрым способом реализована вставка.
Попробуйте запустить пример и убедиться, что все работает.
Теперь рассмотрим еще несколько интересных методов объекта clipboard.
□ Assign — назначить в буфер обмена объект, совместимый с TPersistent. К таким объектам относятся картинки тImage, б качестве единственного параметра нужно указать объект, содержимое которого нужно скопировать в буфер обмена.
П clear — очистить содержимое буфера обмена.
□ HasFormat — проверяется, какого типа данные хранятся в буфере обмена. Возможны следующие типы данных:
cf_text — буфер содержит текст;
CF_bitmap — буфер содержит картинку;
С f_metafi LE pi ст — буфер содержит векторную картинку;
CF P 工CTURE —буфер содержит объект типа Tpicture;
CF COMPONENT — буфер содержит компонент.
Давайте добавим в пример одну кнопку, по нажатии которой будет выводиться сообщение, показывающее тип хранящихся в буфере обмена данных. Для: события, связанного с нажатием этой кнопки, напишем код, приведенный в листинге 23.1.
Листинг 23.1. Получение информации о содержимом буфера обмена
procedure TForml.InfoButtonClick(Sender: beginif Clipboard.HasFormat(CF_TEXT) then Application.MessageBox('Буфер содержитif Clipboard.HasFormat (CF_BITMAP) then Application.MessageBox(rБуфер содержитif Clipboard. HasFormat (CF_METAFILEPICT) Application.MessageBox ('Буфер содержит1 Внимание ”);procedure TForml.InfoButtonClick(Sender: beginif Clipboard.HasFormat(CF_TEXT) then Application.MessageBox('Буфер содержитif Clipboard.HasFormat (CF_BITMAP) then Application.MessageBox(rБуфер содержитif Clipboard. HasFormat (CF_METAFILEPICT) Application.MessageBox ('Буфер содержит1 Внимание ”);TObject);TObject);текст*, *Внимание!*);текст*, *Внимание!*);картинку', 'Внимание! thenкартинку', 'Внимание! thenвекторную картинку',векторную картинку',
procedure TForml.InfoButtonClick(Sender: begin
if Clipboard.HasFormat(CF_TEXT) then Application.MessageBox('Буфер содержит
if Clipboard.HasFormat (CF_BITMAP) then Application.MessageBox(rБуфер содержит
if Clipboard. HasFormat (CF_METAFILEPICT) Application.MessageBox ('Буфер содержит
1 Внимание ”);
procedure TForml.InfoButtonClick(Sender: begin
if Clipboard.HasFormat(CF_TEXT) then Application.MessageBox('Буфер содержит
if Clipboard.HasFormat (CF_BITMAP) then Application.MessageBox(rБуфер содержит
if Clipboard. HasFormat (CF_METAFILEPICT) Application.MessageBox ('Буфер содержит
1 Внимание ”);
TObject);
TObject);
текст*, *Внимание!*);
текст*, *Внимание!*);
картинку', 'Внимание! then
картинку', 'Внимание! then
векторную картинку',
векторную картинку',
if Clipboard.HasFormat (CF_PICTURE) then
/^plication.MessageBox (1 Буфер содержит объект типа TPicture* r •Внимание!*);
if Clipboard.HasFormat (CE\_COMPONENT) then'
* Внимание ”);* Внимание ”);Application.MessageBox(*Буфер содержит компонент*,
* Внимание ”);
* Внимание ”);
end;
С помощью следующих двух методов объекта ТС lipboa rd вы можете поместить в буфер компонент или текст.
SetComponent — назначить в буфер обмена компонент. В качестве единственного параметра нужно указать компонент, который надо скопировать в буфер.
SetTextBuf — текстовый буфер обмена. В качестве единственного параметра нужно указать буфер PChar.
Примеианк1е、
На компакт-диске в папке \ Примеры \ Глава 23 \ Clipboard вы можете увидеть призер этой программы.
Картинки и буфер обмена
Давайте теперь разберемся, как работать с изображениями в буфере обмена. Как вы знаете, у нас основным компонентом для работы с картинками является тImage. Давайте напишем пример, в котором будет копироваться и вставляться изображение из буфера обмена в компонент Тimage.
Использование буфера обменаИспользование буфера обменаРис. 23.2. Форма будущей программыРис. 23.2. Форма будущей программы
Использование буфера обмена
Использование буфера обмена
Рис. 23.2. Форма будущей программы
Рис. 23.2. Форма будущей программы
Создайте новый проект и поместите на форму две кнопки Копировать и Вставить. По центру окна расположите компонент Timage, который будет хранить изображения. Форму вы можете увидеть на рис. 23.2.
По событию Onclick для кнопки Копировать напишем следующий код:
procedure TForml.CopyButtonClick(Sender: TObject);
begin
Clipboard.7\ssign (Image 1. Picture);
end;
Здесь методом Assign устанавливается в буфер обмена содержимое свойства picture компонента image 1. Свойство picture имеет тип TPicture, который среди своих предков имеет объект TPersistent. Если вы помните назначение этого объекта, то должны знать, что он позволяет использовать метод Assign. Посмотрите на рис. 23.3, на котором показана иерархия объекта Т Picture. Это копия картинки из файла помощи Delphi.
Hierarchy
TObject
I
TPersistent
I
TmterfacpdPersiq2m
J
[Picture
Рис. 23.3. Иерархия объекта TPicture
Теперь посмотрим на код, который надо написать для события, связанного с нажатием кнопки Вставить:
procedure TForml.PasteButtonClick(Sender: TObject);
begin
Imagel. Picture. As sign (Clipboa rd);
end;
Здесь мы копируем с помощью метода Assign содержимое буфера обмена в СВОЙСТВО Picture.
В принципе, пример готов и на этом можно было бы остановиться. Но давайте разберемся еще с тем, как сделать так, чтобы кнопка Вставить была доступна только в том случае, если в буфере обмена находится именно картинка. Для этого сначала перейдем в раздел private и запишем там следующее:
private
{ Private declarations }
FC1ipboardOwner:HWnd;
procedure WMDrawClipboard(var Msg: TWMDrawClipboard);
message WM_DRAWCLIPBOARD;
Здесь объявлена переменная FclipboardOwner типа HWnd. Это тип, который используется для идентификации окна. Вспомните, каждый раз, когда нам нужно было получить или передать указатель на окно, мы использовали свойство Handle. Вот это свойство имеет тип HWnd. В приведенном выше коде явно объявлена переменная такого же типа, чтобы появилась возможность работать с окнами.
Так же объявлена процедура WMDrawClipboard с одним лишь параметром типа TWMDrawClipboard. Это цроиедура — обработчик события WM_DRAWCLIPBOARD. Об этом говорит соответствующая надпись после объявления процедуры и точки с запятой. Там стоят ключевое слово message и имя сообщения, на которое должна откликаться процедура. Вот таким нехитрым способом можно написать обработчик системного сообщения, которого нет в Delphi.
К Примечанме 】
Имена всех сообщений Windows вы может найти в папке Delphi (подпапка — Source \ Rtl \Win), в файле Messages.pas. Такие имена всегда начинаются с приставки wm_.
Чтобы создать обработчик события onPaint без использования объектного инспектора и готовых шаблонов, нужно написать следующую строку: procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
Здесь объявлена процедура WMPaint. В качестве параметра она принимает сообщение типа TWMPaint, через которое передаются необходимые для рисования данные. После процедуры написан тип сообщения, на которое должна реагировать процедура.
Сама процедура представлена в листинге 23.2.
Листинг 23.2. Обработчик события прорисовки объекта
procedure Объект.WMPaint(var Message: TWMPaint); var
SaveIndex: Integer;
DC: HDC;
PS: TPaintStruct;
C: TCanvas;
begin
DC :二 Message.DC;
if DC - 0 then DC := BeginPaint (Handle, PS);
SaveIndex :二 SaveDC(DC);
C :二 TCanvas.Create;
c.Handle:=DC;
Здесь можно рисовать
RestoreDC(DC, Savelndex);
end;
В самом начале получаем указатель на контекст рисования Widows. Он находится в свойстве DC объекта сообщения message, который мы получили в качестве параметра. Потом проверяем, если контекст равен нулю, то получаем его с помощью функции BeginPaint. В первом параметре необходимо задать указатель на окно, в котором мы будем рисовать.
После этого сохраняем контекст с помощью SaveDC. Результатом нам вернется индекс сохраненного контекста. После рисования восстановим его с помощью RestoreDC, у которой два параметра — контекст рисования и индекс сохраненного контекста.
В принципе, этого достаточно, если вы умеете рисовать с помощью функций Win API. Для улучшения в процедуре введен код преобразования контекста рисования в знакомый нам объект TCanvas. После этого можно использовать переменную с, как объект холста.
Это небольшое отступление, чтобы вы хорошо поняли, как самостоятельно делать обработчики сообщений. Теперь рассмотрим обработчик события wm_drawclIpboarD. Он будет вызываться каждый раз, когда изменилось содержимое буфера обмена. Давайте добавим его в наш проект. Для этого в разделе private добавьте следующее объявление процедуры: procedure WMDrawClipboard(var Msg: TWMDrawClipboard);
message WM_DRAWCLIPBOARD;
Теперь нажмите сочетание клавиш <Ctii>+<Shift>+<C>, и Delphi создаст пустую заготовку описанной процедуры. В ней напишем следующий код:
procedure TForml.WMDrawClipboard(var Msg: TWMDrawClipboard);
begin
SendMessage (FC1 ipboardOwner, WM_DRAWCLIPBOARD, 0, 0);
Msg.Result :二 0;
ClipboardChanged;
end;
В первой строчке мы посылаем сообщение владельцу буфера обмена типа WM DRAWCLIPBOARD. Во второй строчке обнуляем результат переменной Msg, которую мы получили в качестве параметра.
В последней строке вызываем процедуру ClipboardChanged. Она должна выглядеть так, как это показано в листинге 23.3.
Листинг 23.3, Процедура! которая вызывается при изменении содержимого буфера
procedure TForml.ClipboardChanged;
var
I: Integer;
begin
PasteButton.Enabled := False;
for I := 0 to Clipboard. FormatCount - 1 do
begin
if Cl ipboard. Has Format (CF__BITMAP) then
begin
PasteButton.Enabled := True;
Break;
end;
end;
•end;
Для начала мы делаем кнопку Вставить неактивной. Потом запускаем цикл от 0 до количества форматов в буфере обмена Clipboard. Formatcount. Внутри цикла происходит проверка. Если формат соответствует CF__bitmap, то кнопку Вставить можно делать активной и прерывать цикл проверки.
И последнее, что надо сделать в программе, — это сформировать обработчик события onshow для нашей главной формы. В нем напишите следующий код:
procedure ТForml.FormShow(Sender: TObject);
begin
FC1ipboardOwner := SetClipboardviewer(Handle);
ClipboardChanged;
end;
В первой строке вызывается функция Set Clipboardviewer. Она устанавливает указанное в качестве параметра окно (главное окно) в системе в качестве наблюдателя за буфером обмена. Вспомните, что именно этому наблюдателю мы посылаем сообщение SendMessage в процедуре WMDrawClipboard. После этого, как только буфер изменится, окну будет отправлено соответствующее сообщение, и оно будет зафиксировано процедурой WMDrawCiipb oard.
Во второй строке вызываем процедуру Cl ipboardChanged, чтобы при старте программы произошла проверка — находится там картинка или ее нет. Если этой проверки не производить, то программа после запуска еще не будет знать, что находится в буфере. Это буцет до тех пор, пока он не изменится и программа не получит соответствующего сообщения.
Попробуйте запустить свой вариант программы и последить за кнопкой Вставить. Запустите любые другие программы и попробуйте в них поместить в буфер данные разного типа. Как только вы поместите туда картинку, так сразу же программа среагирует на это и сделает кнопку Вставить активной.
工 Прммдчанме ;
На компакт-диске в папке \ Примеры \ Глава 23 \ Image вы можете увидеть пример этой программы.
Создание собственного формата для работы с буфером
Представьте себе ситуацию, когда в программе есть какой-то объект и нужно дать пользователю возможность копировать его в буфер, а затем вставлять в нужное место. Стандартные форматы данных для буфера се_техт, CF BITMAP, CF METAFILEPICT и так далее не подходят, но данные копировать надо. Программисты с такой ситуацией встречаются практически в каждой своей программе. В таких случаях нужно создать свой собственный формат данных, с которым и будет работать буфер обмена.
Язык Delphi — это объектный язык. По условиям объектного программирования, чтобы добавить новые возможности к уже существующему объекту, нужно создать его потомка. В данном случае мы по идее должны вывести потомка из ТС lipboa rd и наделить его уникальными возможностями по форматированию нужных нам данных. В данном случае это будет не совсем правильно. Объектное пр ограммир ование хорошее дело, но только когда оно в меру. В следующем примере нам не понадобится наследственность и мы не будем использовать технологию родителей и потомков.
Итак, создайте новый проект и сразу же создайте в нем новый модуль (File I New | Unit). Delphi в этом случае создаст пустой модуль, который мы сохраним под именем ClipboardFormatUnit. Результат — модуль, имеющий следующий код:
unit С i ipboardFormatunit ;
interface
iirpl ementat ion
end.
После ключевого слова interface добавляем раздел type, объявляем структуру TLineData И НОВЫЙ объект TLineClipboard (ЛИСТИНГ 23.4).
Листинг 23.4. Структура и объект для работы с буфером
type
TLineData=record
Name:StringflOO];
LastName:String[100];
Bothday:String[10];
Age:Integer;
Telephone:String[15];
end;
TLineC1ipboard=class public
LineData:TLineData;
procedure CopyToC1ipboard;
procedure Paste FromClipboard;
end;
Структура TLineData состоит из пяти полей. Именно эту структуру мы и будем помещать в буфер обмена. Как вы уже поняли, объект clipboard не может работать со структурами. В связи с этим сейчас напишем модуль, с помощью которого научим его это делать.
После структуры вдет объявление нового объекта. Новый объект описываем в программе, определяя там все его методы и свойства. Чаще всего за нас это делал Delphi. Обратите внимание на то, что он объявлен как простой объект без каких-либо родителей(TLineClipboard=class). Несмотря на это, он будет иметь родителя — TObject, потому что все объекты должны иметь родителя, и если ничего не указано, то будет использоваться базовый объект TObject. У нового объекта будут только одно свойство типа структуры TLineData и два метода для копирования и вставки данных в буфер обмена.
Теперь после раздела type напишем var и опишем одну переменную: var
CF_PERSONDATA: word;
В этой переменной будет храниться указатель на зарегистрированный формат для буфера обмена. Давайте не будем откладывать это дело на потом, а
сразу же реализуем регистрацию в системе нового формата. Для этого в конце модуля перед последним end запишем:
initialization
CF_PERSONDATA:=RegisterClipboardFormat (*CF_PDATA,);
end.
Здесь мы объявили блок initialization, который всегда выполняется автоматически при обращении к модулю или любому его с одержи мому. В этом блоке присваиваем переменной CF PERSONDATA результат выполнения функции RegisterClipboardFormat. Функция регистрирует новый формат для буфера обмена с именем, указанным в качестве единственного параметра. Результат выполнения функции — число, идентифицирующее зарегистрированный формат данных. После этого данный формат можно увидеть в свойстве Formats объекта clipboard.
Вот теперь перейдем к реализации функций записи и чтения данных из буфера. Копирование, на первый взгляд, реализуется довольно сложно. Но это только на первый взгляд. Посмотрите на листинг 23.5, где показан код процедуры копирования.
I Листинг 23 5 Процедура копирования в буфер обмена
procedure TLineClipboard.CopyToClipboard;
var
DatarTHandle;
DataPtr:Pointer;
begin
//Выделяем память под данные
Data: =GlobalAlloc (GMEMJMOVEABLE, SizeOf (LineData));
try
DataPtr:=GlobalLock(Data);
Move (LineData, DataPtr^, SizeOf (TLineData)).
/ /Заполняем буфер обмена
Clipboard.Open;
Clipboard.SetAsHandle(CF_PERSONDATA, Data);
Clipboard. AsText :=Line Data. Name+# 13# 10+LineData. LastName+#13# Юч-
Line Dat a .Bothday+#13#10+IntToStr(LineData.Age)+#13#10+
LineData.Telephone;
Clipboard.Close;
GlobalUnlock(Data);
except
GlobalFree(Data);
end;
end;
В процедуре объявлены две переменные:
Data — в эту переменную мы будем выделять память для хранения структуры, которую надо будет поместить в буфер обмена;
口 DataPtr — указатель на выделенную для переменной Data память.
В самом начале процедуры мы присваиваем переменной Data результат вызова функции Global Alloc. Эта функция выделяет нужную область памяти в общем массиве глобальной памяти. При этом у нее имеется несколько параметров.
Параметры (флаги) выделяемой памяти. Здесь указан флаг GMEM_MOVEABLE, который указывает на то, что память может быть перемещаемой. Например, когда ОС не хватает оперативной памяти, то она может выгрузить некоторую часть кода на диск. Затем, по мере надобности, загрузить его обратно память.
П Размер выделяемой памяти. Здесь мы указываем размер sizeOf структуры LineData.
Следующим этапом блокируем выделенную память с помощью функции GlobalLoc и получаем указатель на выделенную память, который сохраняется в переменной DataPtr.
У нас есть выделенная память, и мы ее временно заблокировали для работы. Теперь мы должны скопировать в эту память структуру, которая должна быть помещена в буфер обмена. Для этого пользуемся процедурой Move, которая копирует данные из одного участка памяти (первый параметр — структура LineData) в другой участок (второй параметр — выделенная память DataPtr). Третий параметр — это размер копируемых данных. Здесь указывается размер нашей структуры.
Память подготовлена и в нее занесены данные для буфера обмена. Теперь можно приступать к передаче этих данных в буфер. Для начала буфер надо открыть (open) для записи. Следующим этапом мы заносим в буфер данные в виде структуры (зарегистрированного нами формата CF PERSONDATA). Для этого используется метод SetAsHandle. У этого метода два параметра:
О тип заносимых данных;
П данные.
Дальше, заносим данные в виде текста, чтобы программы, которые не знают о существовании нашего собственного формата, могли прочитать их в виде текста. Для этого текст надо занести в свойство AsText объекта clipboard. В это свойство заносим все поля структуры, разделенные символами #13# 10. Символ #13 означает конец строки, а #10 означает перевод каретки на новую строку. Получается, что каждый параметр будет занесен в виде отдельной строки. Чуть позже вы увидите, как это выглядит на практике.
Данные занесены, можно уничтожать все, что больше не потребуется. Для начала закрываем буфер обмена с помощью вызова метода close объекта clipboard. Потом вызываем метод Globalunlock, который разблокирует память. Уничтожать выделенную память (вызов Global Free) нужно, только если во время записи произошла какая-то ошибка. Поэтому вызов этой процедуры поместили в блоке except.. end. Если ошибок не было, то память должна остаться целой и невредимой, потому что там хранится структура.
Вот теперь разберемся с кодом, который получают данные из буфера обмена. ВзГЛЯНИТе на процедуру PasteFromClipboard (ЛИСТИНГ 23.6).
Тистинг 23.6. Процедура вставки данных
procedure TLineClipboard.PasteFromClipboard; var
Data:THandle;
DataPtr:Pointer;
begin
Data: =C1 ipBoard. GetAsHandle (CF^PERSONDATA);
if Data=0 then exit;
DataPtr:=GlobalLock(Data);
Move (DataPtr74, LmeData, sizeOf (TLineData));
GlobalUnlock(Data);
end;
В первой строке кода мы пытаемся получить данные из буфера обмена с помощью функции GetAsHandle. Этой функции нужно передать формат, в котором мы хотим получить данные. Если значение, которое нам вернула GetAsHandie, равно нулю, то данных в буфере нет, или они имеют несовместимый формат. В этом случае процедура прерывает свое выполнение.
Если все нормально, то в переменной Data будет указатель на блок памяти с данными буфера обмена. Для их получения блокируем память и пользуемся уже знакомой процедурой Move, для копирования данных из памяти буфера обмена (первый параметр) в структуру Line Data (второй параметр).
Данные получены, можно разблокировать память с помощью процедуры GlobalUnlock.
Все. Модуль готов. Теперь переходим к написанию основной программы, которая будет использовать созданный нами формат данных для копирования и вставки через буфер обмена. У нас уже создан новый проект, в котором мы написали модуль ClipboardFormatUnit. Перейдите в основное окно и поместите на форму следующие компоненты:
П две кнопки Копировать и Вставить;
П компонент stringGrid (в нем нужно изменить свойство goEditing в разделе option на true, чтобы мы могли редактировать ячейки в сетке);
П Мето — используется для отображения содержимого буфера в виде текста.
Эту форму вы можете увидеть на рис. 23.4. Для реализации кнопок можно использовать компонент ToolBar, чтобы программа выглядела более эстетично и красиво, но вы можете поступить и по-другому.
Рис. 23.4. Форма будущей программы
Сразу же добавьте в раздел uses наш модуль ciipboardFormatunit, чтобы вы могли использовать новый формат из главной формы.
Теперь создадим обработчик события onclick для кнопки Копировать и напишем в нем код, представленный в листинге 23.7.
procedure TForml.CopyButtonClick(Sender: TObject);
var
LineClipboard:TLineClipboard;
begin
LineClipboard:=TLineC1ipboard•Create;
LineClipboard.LineData.Name:=StringGridl.Cells[0, StringGridl.Row];
LineClipboard.LineData.LastName:=StringGridl.Cells[1, StringGridl.Row];
LineClipboard.LineData.Bothday:=StringGridl.Cells ⑵ StringGridl.Row];
LineClipboard.LineData.Age:^StrToInt(StringGridl.Cells[3, StringGridl .Row]);
LineClipboard.LineData.Telephone:=StringGridl.Cells[4, StringGridl•Row];
LineClipboard.CopyToClipboard;
LineClipboard.Free;
end;
В разделе var объявлена одна переменная типа TLineClipboard — объект для работы с буфером обмена, который был написан в модуле ClipboardFormatUnit. В первой строке кода мы инициализируем эту переменную.
Потом заполняем структуру LineData объекта Line Clipboard данными из выделенной строки сетки. По завершении этого процесса копируем данные в буфер обмена с помощью вызова метода СоруТоClipboard объекта LineClipboard.
Данные скопированы, значит, объект уже не нужен, и его можно уничтожать. Для этого вызываем метод Free.
Для события Onclick кнопки Вставить напишем следующий код (листинг 23.8).
Листинг 23a Вставка данных из буфера
procedure TForml.PasteButtonClick(Sender: TObject); var
LineClipboard:TLineClipboard;
begin
LineClipboard:=TLineClipboard.Create;
if Clipboard. Has Format (CF_PERSONDATA) then
begin< .
LineClipboard.PasteFrcmClipboard;
StringGridl.Cells[0, StringGridl.Row]:=LineClipboard.LineData.Name;
StringGridl.Cells[1, String-
Gricll -Row] : =LineC lipboa rd. LineData. LastName;
StringGridl.Cells[2r StringGridl.Row]:=LineClipboard.LineData.Bothday;
StringGridl.Cells[3, String-
Gridl.Row] :=IntToStr(LineClipboard.LineData.Age);
StringGridl.Cells[4, String-
Gridl •Row]:=LineClipboard.LineData.Telephone;
end;
LineClipboard.Free;
Memol.Lines.Clear;
Memol. PasteFroinClipboard;
end;
Опять же, здесь объявлена переменная LineClipboard, которая инициализируется в первой строке кода. После этого проверяем, если буфер обмена содержит информацию в формате CF PERSONDATA (это созданный нами формат), то мы читаем буфер с помощью метода PasteFromClipboard. После этого заполняем поля текущей строки из структуры LineData объекта Lined ipboard.
В самом конце процедуры очищаем компонент Memol и заставляем его с помощью метода PasteFromClipboard прочитать данные из буфера. Этот компонент не знает о существовании нашего формата и читает данные из буфера обмена как текст (это его родной формат). Получается, что мы увидим в компоненте то, что мы записали в свойство AsText объекта clipboard. Одновременно со вставкой в компонент stringGrid происходит вставка текста буфера в компонент Мето.
工 Примечание 】
На ком пакт-диске в папке \ Примеры \ Глава 23 \ New Format вы можете увидеть пример этой программы.
””
”
”
Глава 24
Дополнительная информация
Мы уже знаем достаточно много о программировании в Delphi, но очень мало говорили об оболочке, в которой программируем. В большинстве книг именно с нее начинается обучение. Очевидно, что это не совсем правильно. Нет смысла обучать человека тому, чего он абсолютно не собирается применять, потому что не знает, зачем и когда. Сначала нужно заинтересовать его и показать, как применять свои знания, что мы и делали на протяжении всей книги. Теперь же, когда накоплено достаточно знаний для написания собственных проектов, пора узнать, как работать с оболочкой Delphi, как тестировать и отлаживать написанные приложения.
В программах регулярно возникают ошибки даже у именитых профессионалов. Для выявления их необходимо много сил и терпения. В связи с этим в данной главе рассматриваются вопросы, связанные с отладкой и тестированием программ.
Тестирование и отладка
Для начала разберемся с отладкой программ. Отладка — это пошаговое выполнение команд программы. В этом режиме каждая строчка кода выполняется по команде, и сразу после ее выполнения Delphi останавливает ее работу и ждет следующей команды. Коша программа остановлена на определенном шаге выполнения, вы можете просмотреть значения всех переменных и даже изменить их значения.
Точка прерывания отладки — строка кода, на которой программа должна остановить свое выполнение и перейти в Delphi для продолжения выполнения в пошаговом режиме.
Для того чтобы поставить точку прерывания, нужно выделить строку и нажать клавишу <F5>. Эта строка должна окраситься в красный цвет. Если она сразу или после компиляции оказалась другого цвета, то на этой строке программа не может останавливаться. На рис. 24.1 вы можете видеть пример строки, на которой установлена точка прерывания.
26 Зак 942
Рис. 24.1. Точка прерывания
Слева от строки может стоять синяя точка. Это делается в том случае, когда строка может стать точкой прерывания. Если такой точки нет, то и прерывания не может быть. Эти точки видны не всегда. Они могут пропадать и появляться только после очередной компиляции программы.
Если вы внесли какие-то изменения в код или добавили строку, то напротив этой строки не буцет никаких точек, даже если здесь точно программа может остановить свое выполнение. Оболочка Delphi просто не знает о том, что делает этот код. Очередная проверка произойдет только во время следующей компиляции, и только после нее будут выставлены правильные точки прерывания.
Давайте напишем маленький пример и попробуем разобраться с отладкой на нем. Создайте новое приложение и установите на форму одну кнопку. По событию onclick, связанному с её нажатием, напишите следующий код:
procedure TForml.ButtonlClick(Sender: TObject);
var
j:Integer;
begin
i:—10;
j:=20;
i:=i+j г
end;
Теперь откомпилируйте программу (<Ctd>+ <F9>). После компиляции вы увидите три сообщения типа Value assigned to *i* never used (рис. 24.2). Эти сообщения говорят о том, что значения, присвоенные указанным переменным, не используются. В таких случаях Delphi оптимизирует код программы, и раз значения не используются, то и незачем компилировать этот код. •
Рис. 24.2. Список сообщений внизу окна редактора кода
Обратите внимание, что слева от строк кода не появились синие точки. Это значит, что мы не, сможем установить на них прерывания. Точнее сказать, сможем, но они не будут работать. Это связано с тем, что Delphi оптимизировал эти строки, потому что они явно не влияют на ход программы. Мы производим расчеты, но они никуда не выводятся. Таким образом, только по синим точкам вы можете проверить, какой код был оптимизирован.
Чтобы Delphi не выдавал подобных сообщений и не оптимизировал код, вы можете отключить оптимизатор. Для этого нужно выбрать команду меню Project I Options и в появившемся окне на вкладке Compiler убрать флажок
Optimization (рис. 24.3). В этом случае точки появятся на всех строках кода, и даже на тех, которые не несут смысла.
Рис. 24.3. Окно настроек проекта, где отключается оптимизатор
Но такое делать не желательно, потому что оптимизация — это очень хорошая и удобная вещь. По сообщениям можно определить ненужные переменные (объявленные, но не используемые) и найти участки кода, где логика нарушена. Возможно, что вы присвоили значение одной переменной, а используете потом другую. Такую ошибку легко найти по сообщению оптимизатора.
Давайте откорректируем код и приведем его к виду:
procedure TForml.ButtonlClick (Sender: TObject);
var
i, j :Integer;
begin
1Ю;
j:=20;
i : =i+j;
if i>0 then exit;
end;
Здесь всего лишь добавлена проверка переменной ±. Если она больше нуля, то произойдет выход из процедуры. Этот код тоже не влияет на ход выполнения программы, потому что даже если i будет меньше нуля, процедура все равно заканчивается и произойдет выход. Но если теперь откомпилировать программу, то сообщений не будет и слева от строк кода появятся синие точки. Это потому, что мы уже используем переменную i (в условии if), а значит, оптимизатор не имеет права не компилировать этот код и его уже имеет смысл отлаживать.
Установите точку останова (клавиша <F5>) на первой строке нашей процедуры, после Бслючевого слова begin. Теперь запустите программу, и как только вы нажмете на кнопку, то выполнение остановится и управление получит Delphi. Чтобы продолжить выполнение программы до следующей строки, нужно нажать клавишу <F8>.
Если выделенная строка — ваша процедура или функция, то можно нажать Бславишу <F7>, чтобы отладчик перешел внутрь этой процедуры и продолжил ее выполнение построчно. Если вы нажмете <F8>, то отладчик выполнит процедуру без входа в нее и перейдет дальше. В этом случае нельзя посмотреть, что происходит внутри процедуры.
Если вы хотите, чтобы отладочный режим закончился и программа продолжила выполняться самостоятельно, нажмите <F9>. После этого программа продолжит свое выполнение с последней точки останова, которая была реализована. Если вы хотите совсем остановить работу программы, то нажмите сочетание клавиш <Ctrl>+<F2> или выберите нз меню Run пункт Program Reset, и программа будет выгружена из памяти.
(Ъовет )
Если вы отлаживаете приложение, которое работает с таблицами или базами данных через BDE, то не рекомеццуетс只 прерывать его выполнение, потому что в памяти остается незакрытая сессия связи с BDE, и повторный запуск приведет к ошибке. Это связано с тем, что старая сессия связи не закрыта, а для новой невозможно выделить память. Поэтому всегда завершайте подобные программы корректно.
Когда вы хотите посмотреть, какое значение хранится в переменной, то надо ее выделить и нажать Бславиши <Ctrl>+<F7> или выбрать Evaluate | Modify из меню Run. Перед вами откроется окно, показанное на рис. 24.4. Если вы видите, что значение неправильное, и хотите его изменить, то в строке New value нужно ввести другое значение и нажать кнопку Modify. Чтобы перечитать значение переменной, нужно нажать кнопку Evaluate.
В окне Evaluate/Modify можно вводить не только переменные, но и целые выражения. Например, в предыдущем примере поставьте точку прерывания на первой строке кода процедуры ButtonlClick. Теперь запустите программу и нажмите кнопку. Выполнение программы должно прекратиться и
управление перейдет в Delphi. Нажмите <F8>, чтобы выполнить первую строку кода и перейти на вторую. Текущая строка должна быть выделена синим цветом. Теперь нажмите сочетание клавиш <Ctrl>4-<F7> и в строке Expresdon окна Evaluate/Modify введите выражение — i+Ю. Нажмите <Enter> или кнопку Evaluate, чтобы просчитать это выражение. В списке Result должно появиться значение 20, потому что переменная i равна 10 плюс еще 10. Получаем результат 20.
Рис. 24.4. Окно просмотра значений переменной
Введите в строку Expression просто переменную i. Пересчитайте ее значение нажатием кнопки Evaluate. Далее, в строке New value введите значение 15 и нажмите кнопку Modify. Теперь переменная i должна равняться 15. Проверьте это нажатием кнопки Evaluate.
Попробуем ввести в строке Expression выражение i=lo. Здесь используется операция сравнения переменной i и числа 10. Если переменная равна 10, то мы должны увидеть в списке Result значение true, иначе — false.
Вы можете просматривать не только переменные, но и свойства объектов. Наберите в строке Expression следующий код: Forml. width. Здесь Forml — это имя формы, и мы хотим просмотреть ее ширину. Нажмите <Enter> и вы увидите интересующее значение. Хотя у нас в коде нет обращения к этому объекту, мы можем просмотреть его значения, потому что форма существует в памяти и в данный момент работает. Некоторые свойства будут все же недоступны из-за оптимизатора, но в большинстве случаев вы сможете увидеть или изменить значения объектов.
Теперь надо рассмотреть понятие видимости переменных. Остановите работу программы. Для этого нажмите сочетание клавиш <Ctrl>+<F2>, чтобы
программа выгрузилась без сохранения данных, или продолжите программу нажатием клавиши <F9> для окончания отладочного режима и закройте окно программы. Снова запустите программу нажатием клавиши <F9>. Теперь нажмите кнопку в окне программы. В результате Delphi прервет ее выполнение, произведя переход в отладочный режим. Выделите переменную i и нажмите <Ctii>+<F7>, чтобы увидеть значение переменной. Вместо ее зна-
■ inaccessible
чения в списке Result вы увидите следующий текст — Variable Т
here due to optimization (Переменная i недоступна в этом месте, потому что оптимизирована). В данном случае программа остановила свое выполнение на строке:
i:=10;
Здесь мы присваиваем переменной i значение 10. До этого кода переменная не имеет значения, поэтому мы и увидели такую надпись. Попробуйте выполнить эту строку, нажав клавишу <F8>, и снова посмотрите значение переменной i. Теперь значение равно 10.
Попробуйте выполнить процедуру до самого конца. Остановитесь на следующей строчке:
if i>0 then exit
Теперь посмотрите значение переменной j. Вы снова должны увидеть сообщение о том, что переменная оптимизирована и не имеет значения. Это связано с тем, что, начиная с текущей строчки кода и до конца процедуры, уже нет обращений к значению переменной. Значит, это значение не нужно и Delphi его снова оптимизировала.
Есть еще один способ увидеть текущее значение переменной. Вы должны также выделить эту переменную и нажать клавиши <Ctrl>+<F5> или выбрать Add Watch из меню Run. Переменная будет добавлена в специальное окно Watch, в котором будет постоянно отображаться ее текущее значение (рис. 24.5).
Рис. 24.5. Окно наблюдения за переменной Watch
Если вы сделали все правильно, то программа у вас должна быть остановлена на последней строке кода процедуры. Нажмите <F9>, чтобы продолжить ее выполнение, и вы увидите окно своей программы. Снова нажмите клавишу, и программа снова остановит выполнение на первой строчке процедуры (если вы не сняли точку останова). Добавьте переменную i в окно Watch.
Попробуйте дважды щелкнуть по строке с переменной i в окне Watch, и перед вами откроется окно параметров просмотра (рис. 24.6). В этом окне вы можете сделать достаточно много настроек, но самое интересное — это центр окна. Здесь находится большое количество элементов — RadioButton. Выделяя один из них, вы выбираете тип переменной. В зависимости от выбранного типа вы будете по-разному видеть ее в окне Watch. Но чаще всего вы не будете изменять эти настройьси. Здесь достаточно значений по умолчанию.
Рис. 24.6. Окно настройки переменной для просмотра в наблюдателе Watch
Теперь попробуйте расположить окно Watch так, чтобы оно всегда было на экране и не мешало видеть код программы. Выполните построчно код программы, нажатием клавиши <F8> и понаблюдайте за изменением значения переменной i. После каждого шага значение будет автоматически перечитываться.
(Сов#)
Нельзя отлаживать приложения, использующие DirectX или OpenGL Эти приложения вообще не рекомендуется запускать из оболочки Delphi, а отлаживать запрещено. При наступлении критической ситуации происходит автоматическое переключение в Delphi, и это может вызвать срыв в работе ОС.
Если вам нужно узнать, как выполняется определенный код, то в это место лучше вставить вывод на экран (средствами DirectX или OpenGL) каких- либо сообщений или просто воспроизведение звукового сигнала. Отладка в графических программах — это отдельный разговор, и тут нужно действовать в зависимости от ситуации. Универсальных и одинаково эффективных способов для всех случаев жизни нет.
工 Примечание ]
На компакт-диске в папке \ Примеры \Глава 24 \ Отладка вы можете увидеть пример рассмотренной здесь программы.
Работа с редактором
Теперь мы познакомимся с некоторыми приемами по работе с редактором кода Delphi. В этой части вы узнаете, как работать с закладками, как быстро создавать переменные, процедуры и функции и как искать нужный код. Если у вас маленькая программа и модуль состоит из нескольких строк, то тут не будет проблем даже с существующими знаниями, потому что найти что- то нужное не так уж и сложно. А что, если проект большой и модуль состоит из 1000 строк? Вот тут возникает множество проблем, с которыми надо бороться. А ведь и 1000 строк не предел. В программах средней и большой сложности можно найти модули с более чем 5000 строк.
Закладки
Установите курсор на какую-нибудь строку текста (выделять не надо) и нажмите сочетание клавши <С1г1>+<8Ый>+<цифра>. Эти клавиши поставят закладку на строке, а точнее сказать на выделенной позиции (строка плюс колонка). Теперь перейдите в другое место и нажмите одновременно клавишу <Ctrl> и ту же цифру. После этого вы вернетесь в строку, ще была закладка.
Коща вы устанавливаете закладку на строку, то слева от строки кода появляется изображение кубика с цифрой внутри. Цифра указывает на номер закладки, и именно эта цифра в сочетании с <Ctrl> моментально перенесет вас в позицию, ще была установлена заьсладка. Пример редактора кода с закладкой вы можете увидеть на рис. 24.7.
Заьсладки можно устанавливать и из контекстного меню. Щелкните правой кнопкой мыши в редакторе кода и в появившемся меню наведите курсор на пункт Toggle Bookmarks. Откроется подменю, в котором вы можете увидеть изображения кубиков с номерами и подписи. Выбирая любой из пунктов, вы можете установить соответствующую закладку.
Если вы установили закладку с номером 1 на одну строку и потом устанавливаете на новую строку закладку с этим же номером, то из предыдущего места
расположения закладка снимается и переносится в новое место. Чтобы просто снять закладку, достаточно перейти на ее строку и повторно установить там закладку с тем же номером. В этом случае закладка просто исчезнет.
Рис. 24.7. Окно редактора кода с закладкой
Копирование строк
Установите курсор в начало строки текста. Выделите строку с помощью клавиш <Shift>+<J>. Это выделит всю строку. Теперь, удерживая <Ctrl>, нажмите клавиши <К>, а затем <С> (буквы латинские). В результате произойдет копирование вьщеленной строки без использования буфера обмена (clipboard) в место чуть ниже копируемой.
Code Explorer
Теперь посмотрим, как быстро создавать переменные. В окне кода (слева), находится вытянутое окно Code Explorer, в котором вы можете видеть дерево из трех пунктов:
П TForml — в этой ветке находится описание всех компонентов, установленных на форме;
口 Variables/Constants — здесь хранятся переменные и константы;
□ Uses — здесь хранится список модулей, подключенных к профамме.
Давайте попробуем создать новую глобальную переменную в разделе var. Для этого щелкните правой кнопкой мыши по пункту Variables/Constants и выберите в появившемся меню пункт New. В дереве будет создан пункт, в котором нужно ввести следующий текст:
Н: Integer;
Таким образом, мы создали переменную н в разделе var, и при этом не имеет значения, в какой строчке кода мы находимся в данный момент. На рис. 24.8 вы можете увидеть создание нового элемента.
Рис. 24.8. Создание переменной в разделе var с помощью Code Explorer
Такой прием очень удобен, когда у вас очень большой модуль и не хочется переходить в самое начало, а потом возвращаться назад.
Точно так же можно создавать не только переменные, но и процедуры и функции для объекта главной формы. Для этого нужно щелкнуть правой кнопкой мыши по ветке TForml (имя объекта вашего окна).
Редактор кода
Если вы находитесь в каком-то методе, то для того, чтобы перейти на его объявление, нажмите клавиши <Ctrl>+<Shift>+<T>. Чтобы перейти от объявления к реализации самой функции, нажмите <Ctrl>+<Shift>+<i>.
Когда вы пишете название какою-то стандартного объекта Delphi или его свойства, можно написать только часть его имени, а потом нажать клавиши <Ctrl>+<Пробел>. Вы увидите выпадающее окно, в котором перечислены все свойства, методы, а также переменные и любые ключевые слова, начинающиеся с введенного вами текста. Попробуйте набрать в редакторе буквы ”Wi” и нажать клавиши <СЫ>+<Пробел>. В выпадающем списке первым пунктом буцет стоять width. Если вам нужно именно это свойство, то выберите его, и Delphi автоматически допишет начатое вами слово.
Используйте эту возможность, когда вы пишете длинные названия свойств или методов. Напишите только начало, а потом произведите выбор из списка нужного варианта. Если список большой и вы сразу не видите нужного слова, то продолжайте писать. По мере набора букв список будет уменьшаться. Таким образом вы уменьшаете вероятность возникновения ошибки при наборе длинных имен.
Чтобы сдвинуть выделенный код вправо, нажмите клавиши <Ctrl>+ +<Shift〉+<1>. Попробуйте выделить несколько строк и нажать это сочетание клавиш. Количество отступов во всех выделенных строках увеличится. Таким образом вы отодвигаете их от левого края. Чтобы сдвинуть строки обратно, нужно нажать клавиши <Ctrl>+<Shift>+<U>.
Когда вы работаете с кодом, то в окне видите только о пределенную его часть, в зависимости от размеров окна. Если вам надо увидеть код, расположенный чуть ниже, то можно сдвинуть экран с помощью полосы прокрутки. Но в этом случае надо использовать мышь. А можно нажать клавиши <Ctrl>+<T> ИЛИ <Ctrl>+<J>. В этом случае экран прокручивается, а курсор не двигается, как будто вы используете полосу прокрутки.
Создание программ инсталляции
Когда ваша программа готова, пора задуматься о том, как бы ее установить на компьютер пользователя. Если программа состоит только из одного файла, то никаких проблем. А что, если программа состоит из множества файлов? В этом случае используют программы установки, которые, запускаясь, копируют все необходимое на компьютер клиента. Именно этот способ желательно вам использовать. В этом разделе мы познакомимся с программой InstallShield Express, которая упрощает процесс создания программ установки.
■ П 巳竺ечание
Программу InstallShield Express чаще всего можно найти на том же диске, что и Delphi. Она ин стал л ируется отдельно, и если вы еще не установили ее, то сделайте это сейчас. Если вы устанавливаете ее с диска Delphi, то у вас будут некоторые ограничения в работе, о которых будет сказано позже. Отдельный продукт этой программы более функционален и ни в чем не ограничен.
Запустив программу, вы увидите окно, показанное на рис. 24.9. Главное окно содержит файл помощи, в котором показано, как работать с программой. В левой части окна расположено дерево, в котором можно выбирать нужные для настройки инсталлятора разделы. В центре окна будет отображаться информация, найденная по выбранному разделу. Если у вас нет проблем с английским, то вы сможете разобраться по файлу помощи, но если проблемы есть, то лучше почитать этот раздел книги, потому что здесь будут описаны все необходимые для создания собственных программ инсталляции.
Рис. 24.9. Главное окно программы InstallShield Express
Для создания нового проекта выберите из меню File пункт New. Перед вами откроется окно создания нового проекта. В этом окне нужно указать лишь путь проекта и имя файла. У имени файла должно быть расширение ism, и старайтесь ему задавать вполне осмысленное имя, т. к. оно будет использоваться в качестве имени проекта. Указав путь и имя проекта, нажмите кнопку ОК.
Qi Your Project Name-1 - InstallShield Express - Borland Limited Edition
File Edit View Go Build Tools Help
.D 户 y :雨盾" j q т йик a ^\^\ -儉
I Organize Your Setup
XInstallShield Today-Help•Е О Organize Your Setup,固 General Information!• "G Features.程 Setup Types如 Upgrade PathsВ © Specify Application Data戸 Ries邑 Files and Features 芯L Objects/Merge Modules'•兩 DependenciesВ ❸ Configure the Target System ® Shortcut^/Foldersi . _缈Г RegistryXInstallShield Today-Help•Е О Organize Your Setup,固 General Information!• "G Features.程 Setup Types如 Upgrade PathsВ © Specify Application Data戸 Ries邑 Files and Features 芯L Objects/Merge Modules'•兩 DependenciesВ ❸ Configure the Target System ® Shortcut^/Foldersi . _缈Г RegistryOrganize Your SetupOrganize Your SetupThe first step in building anything is to lay a solid foundation. The base of your setup is formed by specifying application information through the General Information view and creating features in the Features view.The first step in building anything is to lay a solid foundation. The base of your setup is formed by specifying application information through the General Information view and creating features in the Features view.И General InformationИ General InformationThe General Information view is where you set global project properties such as the application name and support information.Most of the Dre口erties in this view are 产The General Information view is where you set global project properties such as the application name and support information.Most of the Dre口erties in this view are 产
XInstallShield Today
-Help
•Е О Organize Your Setup
,固 General Information
!• "G Features
.程 Setup Types
如 Upgrade Paths
В © Specify Application Data
戸 Ries
邑 Files and Features 芯L Objects/Merge Modules
'•兩 Dependencies
В ❸ Configure the Target System ® Shortcut^/Folders
i . _缈Г Registry
XInstallShield Today
-Help
•Е О Organize Your Setup
,固 General Information
!• "G Features
.程 Setup Types
如 Upgrade Paths
В © Specify Application Data
戸 Ries
邑 Files and Features 芯L Objects/Merge Modules
'•兩 Dependencies
В ❸ Configure the Target System ® Shortcut^/Folders
i . _缈Г Registry
Organize Your Setup
Organize Your Setup
The first step in building anything is to lay a solid foundation. The base of your setup is formed by specifying application information through the General Information view and creating features in the Features view.
The first step in building anything is to lay a solid foundation. The base of your setup is formed by specifying application information through the General Information view and creating features in the Features view.
И General Information
И General Information
The General Information view is where you set global project properties such as the application name and support information.
Most of the Dre口erties in this view are 产
The General Information view is where you set global project properties such as the application name and support information.
Most of the Dre口erties in this view are 产
ReadyReady
Ready
Ready
Рис. 24.10. Главное окно программы после создания нового проекта
Теперь главное окно изменится и примет вид, показанный на рис. 24.10. Дерево слева, где были пункты помощи, пополнилось новыми пунктами. Выбирая эти пункты, вы будете указывать параметры будущего инсталлятора. Давайте рассмотрим эти пункты более подробно.
Organize Your Setup (Организовать вашу установку) — в этом разделе вы найдете подпункты основных параметров программы установки. Если щелкнуть кнопкой мыши по этому пункту, то в центре окна вы увидите исчерпывающую информацию о подпунктах, что в них хранится и для чего они предназначены. Далее пойдет описание подпунктов этого раздела.
口 General Information — здесь вы должны указать основные сведения о себе, как о разработчике, указать свой сайт в сети Internet и контактную информацию (рис. 24.11). Вы можете изменит здесь следующие основные свойства:
Author — здесь нужно ввести имя автора программы (введите свое имя или название своей компании);
Authoring Comments — комментарии автора;
Subject — здесь указывается имя программы, которую нужно инсталлировать;
Product Name — имя продукта;
Author
Authoring Comments
—
Subject
jYour Product Name
Keywords
Installer; MSI; Database
Product Name
Default
Display Icon
Product Vfersion
j 1.00.0000
IN5TALLDIR
[ProgramFi esFolder]\Your Company Name^
PuNisher/Product URL
http: //www. yourcompany. com
Product Update URL
http:丹www. yourcompany. com
Publisher
Support Contact
Your Company Name
Рис. 24.11. Свойства пункта General Information
Display Icon — иконка программы;
Product Version — версия продукта;
INSTALLDIR — директория, в которую будет установлена программа (по умолчанию используется директория [ProgramFilesFolder] \ Your Company Name \ Default);
工 Приглечамме〉
[ProgramFilesFolder] указывает на то, что программа должна устанавливаться в папку Program Files на компьютере клиента, после чего указываются подкаталоги этой папки. В качестве примера предлагается ввести имя компании (Your Company Name).
Publisher/Product URL и Product Update URL — адрес в сети Internet, по которому можно найти программу и обновления;
Publisher — здесь опять указывается название компании;
Support Contact — контактная информация со службой поддержки (здесь указывается почтовый адрес службы поддержки — ваш e-mail).
(Совет )
Остальное— специфичные настройки, которые используются редко. Желательно не указывать в качестве службы поддержки свой телефон или реальный почтовый адрес, используйте только e-mail.
П Features — следующий раздел, в котором вы устанавливаете возможности инсталлятора. Если вы выберете этот раздел, то в центральной части окна увидите информацию, показанную на рис. 24.12.
Слева находится дерево, в котором перечислены разные возможности инсталлятора. По умолчанию создана только одна возможность — Always
feature is a placeholder forfeature is a placeholder for
feature is a placeholder for
feature is a placeholder for
Install (Инсталлировать всегда). Щелкните правой кнопкой мыши по верхнему элементу или нажмите клавишу <Ins>, чтобы создать новую возможность. Назовите ее — Organizer. Допустим, что мы создаем программу установки какой-нибудь программы. Все файлы этой программы поместим в пункт Always Install. У инсталлятора будет и дополнительная возможность — органайзер, который пользователь должен иметь возможность выбирать — устанавливать или нет.
Features VI&WFeatures VI&W曰 Г . Features
Features VI&W
Features VI&W
(^Always Install
Use the Features view to create and modify t^stu and associate properties with them. Right-click on the topmost item of the Features explorer to add a new feature. Press FS to refresh the Features view.
、 e default feature Always;tal! cannot be renamed orre:, ved from your project,you add anysubfeatures to it. This
Рис. 24.12. Свойства пункта Features
Выбирая одну из возможностей, вы можете указать ее свойства:
Description — описание типа установки;
Required — обязательность типа;
Visible — видимость.
Для пункта Always Install здесь указывается невидимость, потому что будут указываться пункты, которые должны инсталлироваться всегда. Для пункта Organizer, который должен быть виден в окне выборочной установки, здесь нужно ставить Visible and Collapsed. Это для того, чтобы пользователь мог выбирать, устанавливать ему дополнительную возможность (органайзер) или нет.
□ Setup Types — типы установки. Если выбрать этот пункт, то в центре окна вы увидите еще одно окно, которое показано на рис. 24.13. В левой его части находится список разных типов установки: Typical (Типичная), Minimal (Минимальная), Custom (Выборочная). У каждого пункта есть флажок, который вы можете либо устанавливать, либо снимать, в зависимости от того, нужен этот тип установки или нет. По умолчанию во всех типах флажки установлены. Для нашего примера оставим все так, как
есть, пусть будет три типа установки. Для небольших проектов чаще всего достаточно одного типа установки, при котором инсталлируется сразу все. В этом случае оставьте только Typical.
Setup Type Descriptor:Setup Type Descriptor:All program Features will be inslalled. (Requires the most disk space.)All program Features will be inslalled. (Requires the most disk space.)Features installed for Typical setup type:Features installed for Typical setup type:Выделяя в левом списке тип установки, вы можете в правом списке указывать то, что должно у стан авлив аться в данном случае. При выборе Typical должно устанавливаться все, поэтому в левом списке оставляем выделенными все пункты. При выборе типа Minimal должен устанавливаться необходимый минимум, т. е. органайзер не обязателен и с него снимаем флажок. При выборе Custom, опять же, оставляем все выделенное, чтобы пользователь сам мог убрать то, что ему надо.
Setup Type Descriptor:
Setup Type Descriptor:
All program Features will be inslalled. (Requires the most disk space.)
All program Features will be inslalled. (Requires the most disk space.)
Features installed for Typical setup type:
Features installed for Typical setup type:
丁 益 Always Install 9 MP OfQanizer
Clear AllClear AllSelect All
Clear All
Clear All
Select the setLp types you want to offer using the checkboxes in the left tree. When a setup i:ype is highlighted, use the checkboxes in the right tree to select the features associated with that 5etUD tvDe. Setuo tvoes will be disoJaved on the 5etw Tvdcs dialoo in the order thev aDDear
, —— ■■■■!■- - -,—■— I
Рис. 24.13. Свойства пункта Setup Types
Specify Application Data — это следующий раздел, в котором нужно указать, какие файлы нужно у стан авлив ать на компьютер клиента. Этот раздел состоит из целого рада пунктов.
口 Files — здесь указывается, какие файлы нужно устанавливать на компьютер клиента. Если выбрать этот пункт, то в центре окна вы увидите его свойства (рис. 24.14).
Сверху вы можете выбирать в выпадающем списке тип установки. У нас должно быть там два пункта: Always Install и Organizer.
Чуть ниже слева находится список дисков и папок вашего компьютера. Выбирая папку, вы справа будете видеть файлы этой папки. Перейдите в
27 Зак 942
директорию, где находятся файлы программы, для которой вы создаете программу установки.
Feature: |Always InstallFeature: |Always InstallSource computers folders-IJ 1i MO 3DSMAX3 ffl-C] Archivei l+l Cj Borland l+l CD cd I+l CD Distr W О Images\ i M til JBuifder35 i H UJ neti由占JekaSource computers folders-IJ 1i MO 3DSMAX3 ffl-C] Archivei l+l Cj Borland l+l CD cd I+l CD Distr W О Images\ i M til JBuifder35 i H UJ neti由占Jeka闺叫咏普回纟阂一圣闺叫咏普回纟阂一圣I m J туреXML Document, XML DocumentПриложение Mi..I m J туреXML Document, XML DocumentПриложение Mi..2T д'4ечнь1Й fxcy...2 Компонент при. “2T д'4ечнь1Й fxcy...2 Компонент при. “Modified '02.09.2002 tO:,..08.08.2002 15:...17.07.200211:...15.03.2002 11:...22.05.2001 10:...Modified '02.09.2002 tO:,..08.08.2002 15:...17.07.200211:...15.03.2002 11:...22.05.2001 10:...1 XML Document2. XML Document1... Приложение1 XML Document2. XML Document1... Приложение02,09.200210:...02.09.2002 lOi...]02,09.200210:...^U02,09.200210:...02.09.2002 lOi...]02,09.200210:...^U
Feature: |Always Install
Feature: |Always Install
Source computers folders
-IJ 1
i MO 3DSMAX3 ffl-C] Archive
i l+l Cj Borland l+l CD cd I+l CD Distr W О Images
\ i M til JBuifder35 i H UJ net
i由占Jeka
Source computers folders
-IJ 1
i MO 3DSMAX3 ffl-C] Archive
i l+l Cj Borland l+l CD cd I+l CD Distr W О Images
\ i M til JBuifder35 i H UJ net
i由占Jeka
闺叫咏普回纟阂一圣
闺叫咏普回纟阂一圣
I m J туре
XML Document
, XML Document
Приложение Mi..
I m J туре
XML Document
, XML Document
Приложение Mi..
2T д'4ечнь1Й fxcy...
2 Компонент при. “
2T д'4ечнь1Й fxcy...
2 Компонент при. “
Modified '
02.09.2002 tO:,..
08.08.2002 15:...
17.07.200211:...
15.03.2002 11:...
22.05.2001 10:...
Modified '
02.09.2002 tO:,..
08.08.2002 15:...
17.07.200211:...
15.03.2002 11:...
22.05.2001 10:...
1 XML Document
2. XML Document
1... Приложение
1 XML Document
2. XML Document
1... Приложение
02,09.200210:...
02.09.2002 lOi...]
02,09.200210:...^U
02,09.200210:...
02.09.2002 lOi...]
02,09.200210:...^U
Destination computers foldersDestination computers foldersOestinatoo cf^i^&fs fgOestinatoo cf^i^&fs fg
Destination computers folders
Destination computers folders
Oestinatoo cf^i^&fs fg
Oestinatoo cf^i^&fs fg
|以起一…产 J *11$ [©* Modified
21
Рис. 24.14. Свойства пункта Files
Внизу слева можно видеть окно, в котором мы должны выбирать папку на компьютере клиента (в которую нужно устанавливать файлы). Щелкните в этом окне правой кнопкой мыши по пункту Destination Computer (Компьютер, на который будет производиться установка) и в появившемся меню выберите пункт Show Predefined Folder. Затем выберите пункт [INSTALLDIR]. В дереве этого окна появится соответствующий пункт. В этот пункт надо переместить основные файлы, которые требуется ско- пир9вать в папку, куда будет устанавливаться программа. Если какие-то файлы должны быть помещены в папку Windows, то щелкните в этом окне правой кнопкой мыши по пункту Destination Computer и в появившемся меню выберите пункт Show Predefined Folder и затем выберите пункт [WindowsFolder]. В созданный- пункт дерева можно переносить файлы, которые должны быть в папке Windows.
Теперь в выпадающем списке Features (сверху окна) выберите пункт Organizer. Снова выберите в левом нижнем окне пункт [INS1ALLDIR] и перенесите в него файлы органайзера.
工 Примечание
Во время создания списка устанавливаемых файлов вы не можете создавать поддиректории, если пользуетесь ограниченной версией с диска Delphi. Все файлы могут копироваться только в папку, которую вы выбрали в качестве установочной. Дерево каталогов копируемых файлов запрещено.
口 Files and Features — здесь вы можете просмотреть информацию о копируемых файлах в виде списка (рис. 24.15).
Featu»es
Files
Always Install
Name
1 UnkTo
I Modified
忘 Organizer
ljournal.mdb Jlogo.bmp _Jsclad.mdb
1tipsofday.dat Ь']шаблон.тбЬ
£|midas.dll "^Ireasvr 32.exe 二35clad.exe
D:\Hetpeka D:\net\Jeka D:\net\Jeka D:\net\Jeka D:\net\Jeka D:\net\Jeka D:\net\Jeka D:\net\Jeka
17,07.2002 11:...
15.03.2002 11:...
17.07.2002 11:...
21.06.2002 11:...
17.07.2002 9:5...
22.05.2001 10:...
23.03.2000 4:0...
17.07.2002 1:4...
——■
1
Рис. 24.15. Свойства пункта Files and Features
П Objects/Merge Modules — здесь вы можете указать, какие модули нужно перенести на компьютер клиента. После выделения этого пункта перед вами откроется список установленных на вашем компьютере модулей, которые можно перенести на компьютер клиента (рис. 24.16).
Допустим, что ваша программа использует базы данных MS Access. В этом случае на компьютере клиента должна быть установлена надстройка DAO. Ваша программа установки может автоматически перенести эту надстройку на компьютер клиента. Достаточно только найти ее в списке левого верхнего окна и поставить напротив этой строки галочку. Программа сама определит файлы, которые надо скопировать на компьютер клиента, и при установке внесет необходимые изменения в реестр.
Если вам необходимо установить на компьютер клиента какой-нибудь компонент ActiveX, то опять же его можно найти в этом списке. Созданная программа установки сама зарегистрирует этот компонент во время инсталляции, и вы избавитесь от всех проблем по установке и регистрации ActiveX-компонентов.
InstallShield Objects/Merge Modules
Cond市onal Installation
П 芯 CMDialog ActiveX Control DLL上.
口 芯 DatabaseRTL
□勘 DatcbeseVCL
□齒 Data5nmp
О 芯 DataSnapConnertion
П 芯 DataSnapCorba
1 J ПйЬя^пяпГпЬ.1
1「
痉 © Always Install
• 口 G Organizer
Description of DAO360
Help
Name
DAO360
File Name
d: \Prog 'am Files\Install5hield\Insta1l5hield 一
Express Boriand Limited
Edition\pbjects\DAO360. Msm
Author
InstallShield Software Corporation「|
Select which feature, or features, you want to associate this module with. If 1 the selected feature is installed! the module will also — be installed. Merge modules replace the concept of objects fnimrl in рлг1||эг wprcinrj^ nf
Рис. 24.16. Свойства пункта Objects/Merge Modules
Configure the Target System — это следующий раздел, в котором вы можете указать, какие изменения надо произвести на компьютере клиента.
П Shortcuts/Folders — здесь мы указываем ярлыки, которые нужно создать в программном меню для вызова программ. Выделите этот пункт и увидите в правой половине окна информацию, о пределяющую эти действия (рис. 24.17).
b 4d Shortcuts
В Ml (Taskbar
日 ЗВ Start Menu
I 嗡 Programs Menu 唱 Startup
SendTo
Desktop
Programs Menu
Right-click on Programs Menu to design a shortcut or program folder that will be created on the target system's Programs menu when this feature is selectedforinstallation.
Рис. 24.17. Свойства пункта Shutcuts/Folders
Если вы хотите создать ярлык для вызова программы из меню Пуск, то выделите пункт Program Menu и щелкните по нему правой кнопкой мы-
ши. В появившемся меню выберите пункт New Folder, чтобы создать отдельную папку для своей программы. Теперь выделите эту папку и щелкните правой кнопкой мыши по ней. Здесь нужно выбрать пункт New Shortcut, чтобы создать ярлык для программы.
Выделив имя созданной иконки, вы увидите следующие ее свойства:
Description — описание программы;
Feature — котда создавать ярлык (по умолчанию стоит Все1да, но вы можете выбрать пункт Organizer, если иконку нужно создавать, когда пользователь требует установку органайзера);
Arguments — здесь указываются параметры, которые нужно передавать программе;
Target — файл, который надо запускать при выборе этой иконки (здесь нужно указывать имя запускного файла относительно компьютера клиента, например, [INSTALLDIR] \ о項anizer.exe);
Icon File — файл иконки для ярлыка;
Icon Index — если у программы есть своя иконка, то вы можете указать ее индекс (например, если здесь указать 0, то будет использоваться первая иконка программы, которая установлена по умолчанию);
Run — здесь указываются параметры запуска программы (по умолчанию стоит нормальный запуск — Normal Window);
Working Directory — рабочая директория программы (здесь так же нужно указывать директорию относительно компьютера клиента (например, [1NSTALLDIR]).
Registry — здесь указываются изменения, которые надо произвести в реестре. Окно разделено на две части: в верхней вы видите свой текущий реестр, снизу вы видите изменения, которые надо произвести.
Для создания нового параметра, который нужно буцет создать на машине клиента, вы должны щелкнуть правой кнопкой мыши по нужному разделу и в появившемся меню выбрать пункт Key для создания нового ключа. В этом ключе вы можете создать еще один ключ или параметр, снова щелкнув правой кнопкой мыши.
口 ODBC Resources — здесь вы можете увидеть в виде дерева, все установленные ODjBC-драйверы. Эти драйверы используются для доступа к базам данных через компоненты ADO. В примерах этой книги использовался MSTe^-драйвер, который относится к DAO, но вам могут понадобиться и ODBC-драйверы. Если нужен такой драйвер, найдите его в левом верхнем окне и поставьте флажок напротив его имени.
ODBC ResourcesODBC ResourcesFeaturesFeatures巳野 ODBC ResourcesH 国 Drivers and D5Ns•口而而Ei .... : да Localserver巳野 ODBC ResourcesH 国 Drivers and D5Ns•口而而Ei .... : да LocalserverSQL ServerSQL Serveri В „ Я Microsoft Access Driver (*.n•• U 項 Базаданных MSAcces:..® ECDCMuaci Я Microsoft Text Driver <*.txt:i В „ Я Microsoft Access Driver (*.n•• U 項 Базаданных MSAcces:..® ECDCMuaci Я Microsoft Text Driver <*.txt:PropertiesProperty WalueDriverE:\WINNT\5ystem32\5QLSRV32.c.SetupE: \WINNT\5ystem32\5QL5RV32. ?SQLLevei 1FiieUsage 0DriverODBC 03.50ConnectFun YYYAPILevel 2ID \.11 »PropertiesProperty WalueDriverE:\WINNT\5ystem32\5QLSRV32.c.SetupE: \WINNT\5ystem32\5QL5RV32. ?SQLLevei 1FiieUsage 0DriverODBC 03.50ConnectFun YYYAPILevel 2ID \.11 »** ® Always Instalf.忘 Organizer** ® Always Instalf.忘 OrganizerODBC HelpODBC HelpThis view lists all of the ODBC drivers, data source names — (DSNs), and translators registered on the development system. Check the box next to each .,, yQU wani to include in your setup.This view lists all of the ODBC drivers, data source names — (DSNs), and translators registered on the development system. Check the box next to each .,, yQU wani to include in your setup.
ODBC Resources
ODBC Resources
Features
Features
巳野 ODBC Resources
H 国 Drivers and D5Ns
•口而而E
i .... : да Localserver
巳野 ODBC Resources
H 国 Drivers and D5Ns
•口而而E
i .... : да Localserver
SQL Server
SQL Server
i В „ Я Microsoft Access Driver (*.n
•• U 項 Базаданных MSAcces:
..® ECDCMuac
i Я Microsoft Text Driver <*.txt:
i В „ Я Microsoft Access Driver (*.n
•• U 項 Базаданных MSAcces:
..® ECDCMuac
i Я Microsoft Text Driver <*.txt:
Properties
Property Walue
DriverE:\WINNT\5ystem32\5QLSRV32.c.
SetupE: \WINNT\5ystem32\5QL5RV32. ?
SQLLevei 1
FiieUsage 0
DriverODBC 03.50
ConnectFun YYY
APILevel 2
ID \.11 »
Properties
Property Walue
DriverE:\WINNT\5ystem32\5QLSRV32.c.
SetupE: \WINNT\5ystem32\5QL5RV32. ?
SQLLevei 1
FiieUsage 0
DriverODBC 03.50
ConnectFun YYY
APILevel 2
ID \.11 »
** ® Always Instalf.忘 Organizer
** ® Always Instalf.忘 Organizer
ODBC Help
ODBC Help
This view lists all of the ODBC drivers, data source names — (DSNs), and translators registered on the development system. Check the box next to each .,, yQU wani to include in your setup.
This view lists all of the ODBC drivers, data source names — (DSNs), and translators registered on the development system. Check the box next to each .,, yQU wani to include in your setup.
Рис. 24.18. Выбор ODBC-драйвера
Как только вы выбрали ODBC-драйвер, сразу активизируются правое верхнее и левое нижнее окна. В правом верхнем вы можете указать, в каких типах установки нужно устанавливать этот драйвер. В левом нижнем видны свойства драйвера.
П INI File Changes — указываются, какие изменения нужно произвести в INI-файлах. Сейчас вся информация хранится в реестре и должна храниться именно там. Поэтому мы не будем останавливаться на этом пункте и пойдем дальше.
File Extensions — здесь вы указываете; какие расширения нужно зарегистрировать под программу. Выберите этот пункт и слева появится окно, показанное на рис. 24.19.
Чтобы создать новое расширение, щелкните правой кнопкой по пункту File Extensions в дереве слева. В результате появится новый пункт, в котором надо ввести имя расширения. В правой половине окна вы должны в свойстве File указать имя программы, которая должна запускаться. Вы также можете указать дополнительны^ аргументы программы и иконку, которая буцет отображаться для всех файлов этого типа.
Customize the Setup Appearance — в этом разделе находятся пункты настроек, в которых вы можете указать, как должна выглядеть программа инсталляции.
П Dialogs — здесь вы указываете, какие диалоговые окна должны быть видны во время инсталляции. Выберите этот пункт, и вы увидите окно, по-
казанное на рис. 24.20. В левом верхнем поле находится дерево, в котором перечислены все доступные диалоговые окна. В левом нижнем поле можно увидеть примерный внешний вид выделенного окна. В правом верхнем поле будут отображаться свойства, которые вы можете изменить.
Slash Bitmap — если напротив этого пункта установлен флажок, то вначале загрузки буцет отображаться рисунок. В свойстве splash Bitmap этого окна вы можете указать картинку, которая должна отображаться в окне.
Рис. 24.19. Окно зарегистрированных расширенийРис. 24.19. Окно зарегистрированных расширений
Рис. 24.19. Окно зарегистрированных расширений
Рис. 24.19. Окно зарегистрированных расширений
Н ZJ Dialogs」二! Splash Bitmap【5口 Install Welcome&口 LicenseAgreemertП 二J ReadmeЙ 口 Customer Information□ Destination FokierDatabase FolderН ZJ Dialogs」二! Splash Bitmap【5口 Install Welcome&口 LicenseAgreemertП 二J ReadmeЙ 口 Customer Information□ Destination FokierDatabase FolderGlobal Dialog SettingsGlobal Dialog SettingsGlobal Oaiog Image<ISProductFolder>\l<e<3cfeai O^og Banner<ISProductFolder >\ReGlobal Oaiog Image<ISProductFolder>\l<e<3cfeai O^og Banner<ISProductFolder >\Re
Н ZJ Dialogs
」二! Splash Bitmap
【5口 Install Welcome
&口 LicenseAgreemert
П 二J Readme
Й 口 Customer Information
□ Destination Fokier
Database Folder
Н ZJ Dialogs
」二! Splash Bitmap
【5口 Install Welcome
&口 LicenseAgreemert
П 二J Readme
Й 口 Customer Information
□ Destination Fokier
Database Folder
Global Dialog Settings
Global Dialog Settings
Global Oaiog Image<ISProductFolder>\l<e
<3cfeai O^og Banner<ISProductFolder >\Re
Global Oaiog Image<ISProductFolder>\l<e
<3cfeai O^og Banner<ISProductFolder >\Re
Рис. 24.20. Окно регистрации нового расширенияРис. 24.20. Окно регистрации нового расширения
Рис. 24.20. Окно регистрации нового расширения
Рис. 24.20. Окно регистрации нового расширения
Install Welcome — окно приглашения в программу установки. Это окно невозможно отключить, и оно обязательно должно присутствовать в
программе установки. Вы можете только изменять свойства окна: Bitmap image (картинка, которая будет отображаться в окне), show Copyright (показывать строку Copyright), Copyright Text (текст строки Copyright).
License Agreement — окно лицензии. У этого окна два свойства: Banner Bitmap (картинка иконки). License File (файл с текстом лицензии).
Readme — окно с дополнительной текстовой информацией об устанавливаемой программе. У этого окна есть два свойства: Banner Bitmap (картинка иконки) и Readme File (файл с текстом).
Customer Information — окно, в котором пользователь должен вводить данные о себе (имя, название компании). У этого окна много свойств и все они достаточно интересны.
О Banner Bitmap — карТИНКЯ ИКОНКИ.
О show serial Number — показывать строку для ввода серийного номера продукта.
О Serial Number Template — шаблон серийного номера. Здесь вы можете указать шаблон, по которому будет вводиться серийный номер. Например, серийный номер состоит из двух чисел (каждое по три цифры) между которыми стоит знак тире. В этом случае шаблон будет выглядеть так — ###-###. Если в начале должны быть какие-то обязательные буквы, например, sernum, то шаблон можно записать как — sernum###-###).
О serial Number validation DLL — здесь можно указать DLL-файл, который будет отвечать за проверку правильности введенного серийного номера.
О validate Function — здесь указывается функция из библиотеки, которая будет проверять серийный номер.
О success Return value — значение, которое должно возвращаться при положительном результате проверки.
О Retry Limit — количество попыток регистрации, которые пользователь может выполнить.
О show All users Option — показать возможность выбора, для каких пользователей будет доступна программа. В Windows NT/2000/XP есть возможность давать доступ к программе только тому пользователю, который ее устанавливает, или всем пользователям компьютера.
Destination Folder — показывать окно выбора папки, в которую надо устанавливать программу.
Database Folder — окно выбора папки, куда будут устанавливаться базы данных.
Setup Туре — окно выбора типа установки. В этом окне пользователь сможет выбирать, какая установка ему нужна: типичная, минимальная или выборочная. Если у вас используется только один тип установки, то нет смысла отображать это окно.
Custom Setup — окно, в котором будут отображаться компоненты вашей программы, если пользователь выбрал выборочную установку. У этого окна в свойстве show change Destination установите true. В этом случае пользователь сможет менять директорию установки.
Ready to Install — окно, предупреждающее о начале копирования файлов.
Setup Progress — показывать окно хода установки. Если у этого окна в свойстве show Progress Ваг установить false, то пользователь не будет видеть Progress Ваг, в котором отображается ход установки.
• Setup Complete Success — окно окончания установки. У этого окна целая куча интересных свойств:
О Banner Bitmap — картинка ИКОНКИ;
О show Launch Program — показывать возможность запуска программы по окончании процесса установки;
О Program File — имя файла программы, которую надо запустить;
О Command Line Parameters — параметры Командной СТрОКИ, КОТОрые надо передать программе;
О show Readme — показывать текстовый файл с дополнительной информацией;
О Readme file — текстовый файл с дополнительной информацией.
Define Setup Requirements and Actions — в этом разделе делаются последние настройки программы установки.
Requirements — здесь можно указать ОС, тип процессора, количество памяти и другие параметры, необходимые программе. Программа установки при старте будет проверять эти параметры, и если что-то не совпадает, то установка не произойдет.
Prepare For Release — в этом разделе вы создаете программу установки.
口 Build You Release — здесь вы должны выбрать носитель, на котором будет распространяться программа. В зависимости от типа носителя программа установки может отличаться. Если вы выбрали дискеты по 1,44 Мбайт, то Install Shield автоматически разобьет инсталляцию по файлам такого размера. Для запуска процесса создания программы установки нужно нажать клавишу <F7> или выбрать из меню Build пункт Build.
О Test You Release — здесь можно протестировать созданную программу установки.
口 Distribute You Release — этот пункт позволяет скопировать программу установки на носитель.
24.4. Как писать и распространять Shareware-программы
Теперь у вас есть необходимые для написания собственной программы знания и остается только узнать, как писать программы и что именно надо писать. Самое главное, при написании программы, это не идея. А что же можно назвать главным фактором успеха программного продукта? Вот именно это мы постараемся сейчас выяснить.
Какую программу все же написать? Для этого не надо далеко идти. Просто подумайте, что вам нужно? Если вы работаете с базами данных, то займитесь их написанием. Если вы работаете с графикой, то напишите простенькую программу, которая будет делать несколько эффектных действий. При этом ваша будущая программа должна удовлетворять нескольким критериям.
□ Программа должна быть нужна вам. Если она не нужна даже разработчику, то ею не будет пользоваться никто. Если вы дизайнер, то кто, как не вы, лучше знает, что нужно настоящему художнику?
П Программа должна быть уникальна. Если в вашей программе есть какая- то изюминка, то она точно найдет своего пользователя.
口 Программа должна быть простой в использовании. Не стоит загромождать ее лишними действиями. Если вы гений в графических фильтрах, то напишите простенький plug-in к Photoshop, но не надо писать целый графический редактор ради одного эффекта. Это только усложнит конечный продукт и отпугнет потенциальных пользователей. Программа должна выполнять только самые необходимые действия и содержать как можно меньше лишних функций. Поэтому не стоит встраивать в графический редактор текстовый процессор. Такие большие продукты уже есть, и вы все равно не сможете с ними конкурировать. Чем проще пользоваться программой, тем больше у нее шансов. Вот еще один пример: CyD GIF Studio Pro — отличный GIF-редактор, но в нем очень много функций, которыми не все пользуются, поэтому многие переходят на более дорогую программу, но выполняющую только самое необходимое. Это принцип жизни всех американцев и европейцев. Рассмотрим еще один пример. Linux — дешевая и сложная ОС, поэтому она не получила распространения. Windows простая и дорогая ОС, но используется на большинстве компьютеров. Парадокс, но это так.
П Интерфейс должен быть удобным и симпатичным. Цвета желательно выбирать не сильно яркие, чтобы они не резали глаз. На счет цвета можно посоветовать использовать только системные, чтобы они изменялись в зависимости от выбранной в ксШпьютере цветовой схемы. Все часто используемые функции нужно выносить из меню на панель, чтобы можно было получить к ним быстрый доступ. Посмотрите на все программы Windows и старайтесь не .стильно выделяться, а то это иногда шокирует, и люди не очень охотно используют такие вещи.
П Документация — она должна быть полной и желательно с конкретными примерами. Пользователи любят, когда описаны конкретные действия при работе с программой. Не надо ограничиваться простым описанием возможностей. Постарайтесь дать как можно больше информации и конкретных примеров.
Этот список можно продолжать бесконечно, но остановимся на сказанном. На протяжении всей этой главы мы еще вернемся к уже описанным требованиям и рассмотрим еще некоторые вещи.
Прежде чем писать программу, поставьте перед собой конкретные цели. Главное, чтобы цель была достижима, причем в кратчайшие сроки. Не надо ставить перед собой задачу написать текстовый редактор, потому что их уже достаточно, вы не напишете лучше, чем Microsoft, но даже если это и так, то пока вы будете писать, Microsoft выпустит уже десять новых версий. Вы не сможете угнаться за. гигантами. Поэтому делайте свою цель менее фантастической.
Программу желательно сделать маленькой. Для этого есть несколько причин.
□ Пользователи не любят слишкам сложные программы. Об этом мы уже говорили и рассмотрели несколько примеров.
П Первая версия программы должна быть готова максимум через несколько месяцев. Если вы затянете написание программы на год, то через этот период может пропасть необходимость в ней или кто-то уже реализует вашу идею, и вы потратите время зря.
Именно поэтому ставьте реальные и быстро достижимые цели.
После того как вы поставили перед собой цель, начинайте писать программу. Во время написания нужно четко придерживаться поставленной цели. Не в коем случае нельзя обращать внимание на такие соображения, как: "Надо бы добавить вот такую возможность". Если вы хоть раз обратите на это внимание и начнете выполнять все предполагаемые доработки, то вас затянет рутинная работа, которой не буцет конца. Вы будете вечно добавлять новые возможности, а программа выйдет в свет с большим опозданием. Лучше записывайте появляющиеся мысли на бумагу и реализовывайте их по мере возможности в следующих версиях программы.
Когда вы закончите выполнять первоначальный план, то протестируйте готовую программу и начинайте заниматься вопросами ее распространения (об этом мы поговорим чуть позже). На своем сайте можете указать все, что накопилось за время написания программы (то, что вы собираетесь добавить), и определить, что это можно увидеть в следующей версии программы. Потенциального пользователя такая постановка вопроса заинтересует, и он запомнит ссылку на вашу страничку в Internet среди "Избранных':
Пока все пользователи работают с первой версией программы, надо бы начать доработки, оформляя новую версию.
Постарайтесь хорошо тестировать свою работу. Для этого можно набрать группу тестеров среди пользователей. Раздайте им бесплатные лицензии, они все равно не заплатят (в России еще не привыкли платить за программы). Чем больше буцет ошибок в программе, тем хуже будут к вам относиться.
工 Пр-мечанне 】
Если вы сильно запустили свои программы, и пользователи потеряли к ним интерес, то вам понадобится несколько дней, чтобы исправить все ошибки и годы, чтобы снова завоевать доверие. Выводы делайте сами.
Когда ваша программа готова, ее надо где-то разместить, чтобы потенциальные клиенты могли ею воспользоваться. Для этого можно использовать серверы, которые бесплатно предоставляют место для ваших WEB-страниц. Конечно же, это не самый лучший шаг, потому что в цивилизованных странах не очень любят пользоваться такими страницами. Но для вас этого будет достаточно. Все равно вы сможете заработать свои деньги, даже при использовании бесплатного хостинга (бесплатной услуги размещения файлов на WEB-сервере).
(Совет )
Желательно выбирать сервер за границей, чтобы ничего в его адресе не указывало на страну происхоадения программы. Желательно, чтобы никто не знал, что вы русский. Ваш почтовый ящик тоже должен быть не в зоне RU. Очевидно, что WEB-страница должна быть сделана без ошибок в английском языке. Если ошибки есть, то ваши шансы заработать падают на 99%.
Если вы не можете писать на английском, то сделайте перевод с помощью любого электронного переводчика и отправляйтесь на адрес http:// members.home.net/djosbome 1 /. Здесь вам за небольшую плату исправят все ошибки. Воспользуйтесь этим и не жалейте сотню долларов. Потратьте их, и вы уже через месяц сможете ощутить живую прибыль, если программа чего- то стоит.
При распространении программы желательно использовать платный и заграничный хостинг, потому что российское происхождение программы и бес-
платный хостинг могут плохо повлиять на продажи. Если с хостингом еще можно повременить, то происхождение программы надо прятать изначально.
После того как только она будет опубликована в глобальной сети, ее надо начать рекламировать.
("Совет
Желательно зайти на адрес http://download.cnet.com/. Там найдите ссылку Submint file. Перед вами появится окно для регистрации, после чего вы сможете добавлять свои программы в каталог сайта. Только тут есть одна проблема. Добавлять вы можете, но отображаться они не будут, потому что этот сервис платный. Самая дешевая регистрация одной программы стоит $ 79 (на момент написания книги). Вы кончено же можете обратиться и к другим каталогам программ, но опыт показывает, что за последние 6 лет не продалась ни одна копия программы, которая не была бы отображена на Download .сот.
Только на Download.com вашу программу будут скачивать по 100 копий в сутки. Не думайте, что на этом сервере будет располагаться сам файл. Его будут скачивать с вашей страницы, а Download.com хранит только описание и ссылку на файл, который надо скачивать.
Если вы все правильно сделали, и программа оказалась на Download.com, то через некоторое время другие архивы программ будут сами делать ссылку на вашу страницу и файл с программой. Так что можно не тратить свои усилия, связанные с регистрацией программы на всех серверах. Тем более что только Download.com дает ощутимый трафик для такой работы, остальные смогут дать только 5— 10 чтений в день.
Программа готова, выложена в Internet и ее скачают. Возможно, что ее скоро захотят купить. Вы должны заранее приготовиться к этому моменту. Самому получать деньги от клиентов не получится. Как же тогда решить эту проблему? Есть несколько серверов, которые предоставляют услуги по оплате счетов через Internet, почту и даже телефон. Вы просто регистрируетесь у них, и они сами будут получать деньги от клиентов. За свои услуги они берут немного, всего 9-15 процентов. Не надо жадничать, это действительно очень мало. Воспользуйтесь этими услугами. Европейцы и американцы доверяют таким сервисам и без проблем показывают им свои кредитные карточки, потому что знают, что сервисы защищены. Незнакомой компании никто и ничего показывать не будет.
(Совет.)
Здесь можно посоветовать использовать www.regnow.comв Он очень удобен, а главное — прост в использовании. При его использовании после регистрации к вам приходит письмо, в котором находятся логин и пароль доступа. Когда вы войдете в свою зону, сможете легко разобраться с работой сервера. Просто попробуйте, там все понятно, если вы хоть немного понимаете английский язык или умеете пользоваться переводчиками.
Глава 25
Сплошная практика
Мы уже знаем достаточно много о том, как программировать в Delphi и теперь вы готовы к самостоятельному изучению тонкостей этого программирования. Здесь будут рассмотрены несколько полезных примеров, чтобы вы лишний раз могли понять логику, которой надо придерживаться при составлении программ.
Те примеры, которые мы рассматривали на протяжении всей книги, поучительны и удобны в образовательных целях. Но для полной готовности к реальной жизни надо еще немного попрактиковаться, потому что рассмотренные программы были небольшими (в целях экономии места в книге) и теперь надо научиться весь рассмотренный материал собирать в единое целое.
Достаточно много дополнительной информации вы найдете на диске к этой книге. Читать с монитора, как известно, не так уж удобно. Но в этом случае книга будет не слишком дорогой. иБиблия Delphi** должна стать доступной каждому. В ней сделана попытка дать максимум информации и заполнить компакт-диск не какими-либо, а именно необходимыми для работы данными. Таким образом, не поленитесь и после прочтения книги обязательно загляните в директорию \ Документация.
Создание Screen Saver
Если вы до сих пор считаете, что Delphi это язык, предназначенный для работы с базами данных, то сейчас мы окончательно опровергнем это мнение. Давайте рассмотрим, как при помощи Delphi можно написать Screen Saver зьфа- на. Самое главное, что никаких особых усилий для этого не понадобится.
Создайте новый проект приложения. Для того чтобы Delphi мог создать Screen Saver зкрана, вы должны шделать несколько установок для своей формы.
□ Перед объявлением типов вставте строку кода:
{$D SCRNSAVE Saver}
Здесь saver — имя проекта, его надо сохранить под именем Saver.dpr.
□ Свойство формы Borderstyle поменяйте на bsNone. Это означает, что у формы не должно быть никаких заголовков и обрамлений.
О Все параметры в Border icons установить в false.
□ Свойство формы windowstate поменяйте на wsMaximized, чтобы окно появлялось максимизированным на весь экран.
□ Создайте событие onKeyPress и вставьте туда всего лишь одну процедуру —close () (закрытие хранителя зкрана по нажатии любой клавиши).
П Для события FormActivate значения Left и тор нужно установить в ноль.
Теперь Delphi будет компилировать исполняемый файл, совместимый с хранителем экрана. Что такое Screen Saver? Это та же программа, только с расширением- scr. Таким образом, остается одна деталь — изменить расширение на scr. Вы можете после компиляции программы переименовать файл в *.scr или возложить этот труд на Delphi: Для этого необходимо выбрать пункт Option из меню Project и на вкладке Application в строке Target file extension написать scr. В этом случае Delphi сам подставит расширение.
Подготовительные работы закончены. Можно приступать к написанию хранителя экрана. Вы уже знаете достаточно много и сможете сами написать что-нибудь интересное. Здесь будет рассмотрен простейший пример.
Для примера понадобится объявить три переменные в разделе private: private
{ Private declcLrations }
BGbi tmap: TBi tmap;
DC : hDC;
Backgroundcanvas : TCanvas;
Затем, в обработчике события oncreate формы, напишите код, представленный в листинге 25.1.
Листинг 25.1. Действия в момент создания формы
procedure TForml.FormCreate(Sender: TObject);
begin
BGbi tmap: =TBi tmap. Create; / /Инициализация
/ / Выставляем размеры картинки как у экрана
BGbitmap.Width :二 Screen.Width;
BGbitmap.Height :二 Screen.Height;
DC := GetDC (0);
BackgroundCanvas :二 TCanvas.Create;
BackgroundCanvas.Handle := DC;
BGBitmap.Canvas.CopyRect(Rect (0, 0, Screen.Widthf Screen.Height), BackgroundCanvas,
Rect (0, 0, Screen.Width, Screen.Height));
Вас kgroundCanv^s.Free;
randomize;
end;
Что происходит в этой процедуре? В самом начале мы инициализируем переменную BGbitmap и устанавливаем ей размер как у экрана. Объект Screen — это объект, который создается автоматически, при старте программы, и содержит в себе информацию о нем. В свойствах width и Height находятся ширина и высота экрана.
Потом в переменную DC заносится указатель на контекст вывода эьфана. На первый взгляд это происходит не явно. На самом деле функция GetDC возвращает указатель на контекст указанного в качестве параметра устройства или формы. Если указать 05 то GetDC вернет указатель на контекст воспроизведения эьфана. Если вы захотите получить контекст воспроизведения своей формы, то должны написать GetDC (Handle). В качестве параметра выступает Handle (указатель) окна. Но вы в таком виде не будете никогда использовать GetDC, потому что у вас уже есть контекст воспроизведения формы —ЭТО Canvas, а указатель на него — Canvas. Handle.
Дальше создается BackgroundCanvas. Это TCanvas, который будет указывать на зкран (рабочий стол). Мы делаем данное действие только для удобства. Для рисования можно было бы использовать DC, но все же TCanvas более понятен, и мы с ним достаточно подробно разобрались в главе, которая посвящена графике.
С помощью следующей строки сохраняем копию экрана:
BGBitmap.Canvas.CopyRect(Rect (0, 0, Screen.Width, Screen.Height),
Вас kgroundCanvas,
Rect (0, 0, Screen.Width, Screen.Height));
Затем уничтожаем указатель на контекст экрана BackgroundCanvas.Free, потому что больше он нам не понадобится.
Самая последняя функция — randomize, которая инициализирует таблицу случайных чисел. Если вы этого не сделаете, то после запроса у системы случайного числа вам вернется значение из текущей таблицы, которая не
всегда удачна. Вам не надо будет видеть таблицу случайных чисел, она прекрасно будет работать без вашей помощи.
В обработчике события onDestroy мы уничтожаем картинку: procedure TForml.FcrmDestroy(Sender: TObject); begin
BGBitmap. Free;
end;
Последняя функция — это обработчик таймера, который мы должны поставить на форму:
procedure TForml.TimerlTimer (Sender: TObject);
const
DrawColors: array[0. .7] of TColor = (clRed, clBlue, clYellow, clGreen, clAqua, clFuchsia, cLMaroon, clSilver);
begin
BGBitmap.Canvas.Pen.Color: =DrawColors [random(7)];
BGBitmap. Canvas. MoveTo (random (Screen. Width), random {Screen. Height)); BGBitmap. Canvas. LineTo (random (Screen. Width), random (Screen. Height)); Canvas. Draw(0,0,BGBitmap);
end;
В разделе констант надо объявить массив DrawColors, который хранит восемь цветов. Объявление происходит следующим образом:
Имя_Массива : array [ Индекс_первсго_значения .. Индекс_после,цнего_значения] of Тип_значения_Массива 二 (Перечисление_значений)
В перечислении_значений ДОЛЖНО быть определено (Индекс_последнего_ значения - индекс_первого_значения + 1) параметров. В нашем случае это — (7 - 0 + 1) = 8 значений цвета.
Дальше заменяем цвет у контекста рисования формы BGBitmap.Canvas.Pen.Color случайным цветом ИЗ массива DrawColors. Функция random возвращает число из таблипы случайных чисел, при этом, это число будет больше нуля и меньше значения, переданного в качестве параметра. Это значит, что random (7) вернет случайное число от 0 до 7. С помошью BGBitmap. Canvas. MoveTo мы перемещаемся в случайную точку внутри картинки BGBitmap И С ПОМОЩЬЮ BGBitmap. Canvas. LineTo рисуем ИЗ ЭТОЙ ТОЧКИ В НОВУЮ ТОЧКУ ЛИНИЮ. Canvas. Draw (О, О, BGBitmap) ВЫВОДИТ наше произведение на экран.
Попробуйте запустить пример, и вы увидите, как экран засыпается линиями со случайными координатами. Но если вы протестируете пример, то заметите несколько недостатков.
口 Если скопировать Screen Saver экрана в системную директорию (windows или winnt) и попытаться установить этот Screen Saver, то будут заметны блики.
口 При нажатии на кнопку Настроить запускается Screen Saver, а не окно настройки.
Как же нашему хранителю экрана узнать, что надо отображать окно настройки, а не запускаться самому? Очень просто. При старте хранителя экрана в командной строке передаются параметры. Они могут быть следующими:
П /s — надо запустить Screen Saver экрана;
□ /р — Screen Saver экрана должен отображаться в окне свойств экрана на рисунке монитора (рис. 25.1);
О /с:ххххх — нужно отобразить окно настроек (здесь после двоеточия, вместо хххх стоит число).
Рис. 25.1. Окно настройки хранителя экрана
Количество параметров, переданных программе, можно узнать, вызвав функцию ParamCount. Эта функция вернет только количество параметров. Сами параметры можно получить с помощью функции Paramstr. Этой функции нужно только передать номер параметра, который мы хотим получить. Чтобы перебрать все переданные программе параметры, можно использовать следуюший код: var
i:Integer;
begin
for i:=0 to ParamCount-1 do
Переменная:=ParamStr(i);
end;
Теперь забудем на минуту про параметры и для начала избавимся от бликов. Для этого по событию OnCreate в самом начале программы, присваиваем ширине и высоте окна значения 0, чтобы окно было минимальным. В этом случае окно не будет мерцать, потому что его просто нет на экране из-за нулевых ширины и высоты.
Width:=0;
Height:=0;
Теперь вернемся к параметрам. По событию onshow для главной формы напишем код, представленный в листинге 25.2.
I Листинг 25.2 Обработка параметров, переданных программе
procedure TSaverForm.FormShow(Sender: TObject);
begin
if ParainCoLLnt>0 then
begin
if ParamStr(1) = */p * then
begin
Close;
exit;
end;
if ParamStr (1)⑵廿小then
begin
OptionsForm.ShowModal;
Close;
exit;
end;
end;
Timerl. Enabled: =true;
Width: =Screen. Width;
Height:=Screen.Height;
end;
В самом начале процедуры происходит проверка — если количество параметров больше нуля, то нужно будет проверить, что передали. Если параметр равен /р, то мы просто будем закрывать Screen Saver экрана и ничего отображать не будем. Если параметр равен /с:хххх, то будем отображать окно настроек. Но здесь с проверкой небольшое затруднение, ведь мы еще не знаем, какое число будет вместо хххх. Поэтому просто проверяем второй символ параметра, и если он равен букве с, то значит это /с.
Окно свойств сделаем простейшим, в нем всего лишь запрашивается пароль для хранителя экрана. Саму реализацию работы с паролем мы не будем рассматривать, потому что здесь все просто. Вам нужно сохранять этот пароль в реестре, а при попытке выхода из программы запрашивать ввод пароля. Если пользователь ввел правильный пароль, то Screen Saver экрана можно закрывать. Попробуйте всю эту логику написать сами. Если что-то не получится, то на диске можно найти пример, в котором реализовано все необходимое.
Теперь вернемся к обработчику события onshow. После проверки всех параметров, делаем таймер (Timerl) активным. Если у вас на форме стоит активный таймер (свойство Enabled равно true), то сделайте его неактивным.
После активизации таймера определяем ширину и высоту окна как значения ширины и высоты всего экрана, чтобы окно раскрылось на весь экран.
Остальной код практически не изменился, за исключением того, что вы должны добавить проверку пароля. Здесь можно посоветовать еще добавить возможность управления частотой таймера в окне настроек, но это уже по желанию.
Примечание
На компакт-диске в папке \ Примеры \ Глава 25 \ Screensaver вы можете увидеть пример этой программы.
Компоненты в runtime
Сейчас мы рассмотрим маленький пример, который ответит сразу на два вопроса: как создавать компоненты во время выполнения программы и как ими управлять. Что такое runtime? Ваша программа может находиться в двух состояниях — designtime (время создания проекта) и runtime (время выпол-
нения проекта). В этом случае будем создавать компоненты не путем рисования на форме, а путем использования программного кода во время выполнения профаммы.
Создайте новый проект и установите на его форму два компонента TLabel. Все остальное будем программировать. Для начала в разделе private объявите переменную CompList типа TList. Tlist — это объект-контейнер, который может хранить несколько других объектов. Точнее сказать, он хранит только ссылки, но это не главное. Главное — TList позволяет хорошо управлять хранящимися в нем объектами.
Для события oncreate напишите следующий код:
procedure TForml.FormCreate(Sender: TObject);
begin
CompList:=TList.Create;
end;
Здесь мы инициализируем переменную CompList с помощью объекта TList. Во время инициализации выделяется память под эту переменную. Сразу же для события onDestroy напишем следующую процедуру:
procedure TForml.FormDestroy(Sender: TObject);
begin
CompList.Free;
end;
Здесь мы освобождаем выделенную переменной CompList память.
Теперь создадим обработчик события, возникающего при нажатии кнопки мыши — OnMouseDown. По нажатии кнопки мыши мы будем создавать на форме новый компонент тPanel. Давайте в нем напишем код, показанный в листинге 25.3.
Листинг 25.3. Создание компонентов в runtime
procedirre TForml. FonriMouseDown (Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
TempPanel: TPanel; / /Объявляем переменную для панели begin
/ /Создаем панель. В скобках у Create указан будущий владелец
TempPanel:=TPanel.Create(Forml);
TempPanel. Left: =X; //Устанавливаем левую и правую координаты -
TempPanel.Тор: =Y;//в X и Y позицию, где нажата, кнопка, мыши
TeinpPanel. Width: =20; //Устанавливаем ширину
TempPanel.Height:=20; //Устанавливаем высоту
/ /Далее устанавливаем обработчик нажатия на эту панель
TempPanel.OnMouseDown:=PanelMouseDown;
/ /Добавляем панель в контейнер ConpList (CompLi s t. Add)
/ /и сохраняем результат в TempPanel.Tag
ТетфPanel. Tag: =CompList. Add (TempPanel);
Forml. InsertControl (TempPanel) ; //Вставляем панель на форму end;
Для начала вспомним, что это за свойство Tag у компонента TPanel. Tag — это просто целое значение, которое вы можете использовать по своему усмотрению. Именно этим свойством мы и будем часто пользоваться во время программирования нашего примера.
Теперь разберем написанный выше код. В разделе var объявим одну переменную TempPanel типа TPanei. Это временная переменная, в которой буцет инициализироваться новая панель. В первой же строчке кода обработчика инициализируем эту переменную, как панель. В качестве параметра методу Create передается имя объекта, который буцет являться родителем создаваемого компонента. Мы передаем нашу главную форму, потому что компонент буцет размещаться именно на ней.
На следующем этапе устанавливаем левую и правую позиции панели, как координаты места указателя мыши, где произошел щелчок ее кнопки (х и Y, которые нам переданы в обработчике, указывают на точку, в которой была нажата кнопка мыши). Далее происходит установка ширины и высоты панели. Занесем туда значение 20.
Теперь об обработчике события TempPanel. OnMouseDown. Здесь указываем имя функции PanelMouseDown. Но такой функции нет среди стандартных и в нашем проекте также. Поэтому мы должны ее создать самостоятельно. Как это сделать эффективно?
Мы создаем обработчик для TPanei, поэтому временно поставьте один экземпляр панели на форму в произвольное место..
Создайте для него обработчик на OnMouseDown и переименуйте его в Pane iMous е Down.
Напишите нужный текст (его рассмотрим позже), и можно удалять временно созданный на форме экземпляр TPanei.
Таким образом, вы можете быть уверены, что ошибок не будет, потому что Delphi сам пропишет функцию PaneiMouseDown, где надо, и укажет все необходимые параметры.
Если захотите объявлять эту функцию самостоятельно, то напишите в разделе private следующий код:
procedure Pane IMouse Down (Sender: TObj ect; Button: TMous eButt on;
Shift: TShiftState; X, Y: Integer);
Объявлять можно и до private, там, 1де Delphi объявляет обработчики событий, но желательно этого не делать. Теперь опишем саму функцию:
procedure TForml . PanelMouseDown (Sender: TObject; Button: TMouseButton;
Shift : TShiftstate; x, Y : Integer);
begin
end;
(Совет)
Обязательно нужно следить, чтобы количество и тип параметров точно совпадали с необходимыми. У каждого обработчика свои параметры и при объявлении процедуры вручную нужно отнсюиться к этому вопросу очень внимательно. Именно поэтому вам необходимо использовать первый способ с временной панелью, когда Delphi сам создаст обработчик для одной панели, а вы будете использовать его для других.
Теперь панель готова, и ее надо сохранить в контейнере CompList. Для этого нужно выполнить метод Add контейнера и в качестве параметра передать ему нашу панель:
CompList. Add (TempPanel)
Этот метод добавит панель' в контейнер и вернет индекс компонента в контейнере. Этот индекс мы сохраняем в свойстве Tag панели Temp Panel.
工 Примечаний、
Свойство Тад абсолютно не влияет на сам компонент, но нам этот индекс пригодится для дальнейшей работы.
Теперь давайте посмотрим на функцию PaneiMouseDown, которая должна быть следующей:
procedure TForml . PanelMouseDown (Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Labell.Caption:=IntToStr(TPanel(CompList.Items[TPanel(Sender). Tag]).Left);
Label2.Caption:=IntToStr(TPanel(Sender).Left);
end;
Здесь две строки. Обе они выполняют одно и то же действие, но по- разному. Эти строки записывают в свой TLabel левую позицию панели, по которой произведен щелчок мышью.
Первая строка, чтобы получить левую позицию панели, использует CompList, а вторая работает с панелью напрямую. Рассмотрим сначала вторую строку. В ней основным является выражение TPanel (Sender) . Left. Sender — передается процедурой-обработчиком Pane iMouseDown. В ней записан указатель на объект, который сгенерировал событие OnMouseDown. В нашем случае это будет указатель на панель, по которой вы щелкнули мышью. Так как мы точно уверены, что это панель, то так и показываем ТРапе 1(Sender). Этим МЫ приводим Sender, который имеет ТИП Tobject, К TPanel, и теперь вы можете использовать все свойства и методы панели (для примера нам достаточно свойства Left).
Если бы мы знали точное имя панели, то этого писать не пришлось бы. Но это невозможно, потому что все создаваемые в runtime панели (а их можно создать любое количество) у нас используют один обработчик, вызываемый по щелчку кнопки мыши, и мы не знаем, по какой именно панели был произведен щелчок. Получив значение левой позиции, мы переводим значение левой позиции (целое число) в строку с помощью intToStr.
Первой строка очень похожа на вторую, только внутри TPanel о мы используем не Sender, a CompList. Items [TPanel (Sender) - Tag], T. e. значение ИЗ контейнера. Чтобы получить первое значение из контейнера, нужно написать CompList. Items [0], ДЛЯ второго — CompList. Items [1 ], ДЛЯ Третьего — CompList. Items [2] и т. д. Но по какой именно панели произведен щелчок? Чтобы это узнать, запишем TPanel (Sender) .Tag, т. е. получаем свойство Tag (там хранятся индексы панели) панели сгенерировавшей событие. Далее все происходит так же.
Запустите пример и пощелкайте мышью по форме. При каждом щелчке будут создаваться панели. Потом попробуйте пощелкать по самим панелям. На двух TLabel будут появляться значения левой позиции панели, по которой вы щелкали.
Теперь давайте рассмотрим еще несколько интересных свойств и методов, которые есть у контейнера Tlist.
□ count — в этом свойстве хранится информация о количестве элементов в контейнере.
□ items — здесь хранятся ссылки на элементы контейнера. Для доступа к ссылкам нужно написать Items [Индекс элемента ].
Clear — ОЧИСТИТЬ СПИСОК.
П Delete — удалить элемент из списка. В качестве единственного параметра нужно указать индекс удаляемого элемента.
П Exchange — поменять в контейнере местами два элемента. Здесь два параметра будут индексами меняемых местами компонентов.
О First — получить указатель на первый элемент списка. Это то же самое, что и записать items [0].
indexof — получить индекс указанного в качестве параметра объекта. Допустим, что вы знаете объект (тPanel) и хотите узнать, под каким индексом он расположен в контейнере. В этом случае можно написать следующий код: CompList. indexOf (Panell). Если такой панели не найдено в списке, то будет возврашено значение — 1, иначе — правильный индекс указанной панели.
П insert — вставить новый элемент. У этого метода два параметра — индекс, под которым надо вставить элемент, и сам элемент.
Last — получить последний элемент списка. Это то же самое, что использовать свойство items [Count - 1].
П Move — переместить элемент в новое место. У метода два параметра — индекс элемента, который надо переместить, и индекс, который должен получить элемент.
П Раск — уничтожить и освободить память. При удалении элементов из контейнера они просто помечаются как нулевые. При выполнении этого метода все нулевые элементы уничтожаются, и занятая ими память освобождается.
П Remove — удалить элемент. В качестве параметра нужно указывать элемент, КОТОРЫЙ надо удалить, например, CompList. Remove (Panell).
Давайте добавим в наш пример возможность удаления панели с формы и из контейнера. Это будет происходить по щелчку правой кнопки мышки в области компонента.
Добавьте в конец процедуры PanelMouseDown следующий код:
if Eutton^mbRight then
begin
index:=TPanel(Sender).Tag;
TPanel(CompList.Items[index]).Free;
CompList.Delete(index);
for i : =index to CompList. Count -1 do
TPanel (CorrpList. Items [i]) .Tag: =TPanel (CompList.Items [i]) .Tag-1;
end;
В разделе var этой процедуры нужно объявить две переменные index и i. Обе они будут числами типа integer.
Теперь разберем код. Сначала мы проверяем, если нажата правая кнопка мыши, то нужно удалить компонент, по которому произвели щелчок кнопкой мыши. Для этого сначала сохраняем индекс компонента TPanel(Sender) .Tag в переменной index. Эго необходимо, потому что после уничтожения компонента TPanei (Sender) не будет существовать, и мы не сможем получить доступ к его свойству Tag.
Следуюшим этапом происходит удаление. Сначала мы уничтожаем сам компонент —— TPanel (compList. items [index]) . Free, а после ЭТОГО уничтожаем ссылку на него в контейнере CompList. Delete (index). Вроде бы все удалили, но программа после этого будет работать неправильно. Допустим, что у нас в контейнере было 5 элементов, и мы удалили третий. По идее в контейнере должны остаться элементы с индексами 1, 2, 4, 5, а 3 должен отсутствовать. Но реально индексы перестроятся, и мы увидим индексы 1, 2, 3, 4. Если мы попытаемся оставить все так, как есть, то программа будет нестабильна. Если вы щелкнете по последней созданной панели, то программа обратится к элементу в контейнере под номером 5, потому что в свойстве Tag панели находится цифра 5. Но такого элемента не существует, и произойдет ошибка. Поэтому надо подкорректировать свойства Tag у всех панелей, начиная с удаляемой. Для этого запускаем цикл от индекса удаляемой панели до последнего элемента списка и уменьшаем их свойство Tag:
for i: = index to CompXist . Count -1 do
TPanel (CompList-Items [i] ) .Tag:=TPanel(CompList .Items [i]) . Tag-1;
Вот теперь у нас в контейнере будут элементы с индексами от 1 до 4 и у всех панелей на форме в свойстве Тag будут правильные значения.
Примечание
На компакт-диске в папке \ Примеры \ Глава 25 \ Runtime вы можете увидеть пример этой программы.
25.3. Тест на прочность
У пользователей очень часто возникают вопросы, связанные с написанием программы теста. Видимо преподаватели в институтах начинают любить компьютеры и используют их для тестирования знаний учеников. Но т. к. в наших школах есть проблемы с программным обеспечением, то преподаватели дают задания своим ученикам-программистам написать какой-нибудь определенный тест.
В связи с этим в данном разделе будет рассмотрен процесс разработки программы-теста. В этой программе будет множество интересных приемов программирования, и для обучения такая программа будет просто идеальной.
Итак, нам придется написать две программы:
□ редактор тестов, В ней будут создаваться тесты и заполняться вопросы, на которые надо будет отвечать, а также варианты правильных ответов;
П программа тестирования. Это оболочка, которая будет загружать созданные тесты, и в ней пользователь должен будет отвечать на появляющиеся вопросы.
Самое простое решение — использовать базы данных. Но для них нужны специальные надстройки (BDE, DAO и т. д., в зависимости от базы), что нежелательно. В данном случае можно и даже нужно обойтись без этого.
Итак, начнем мы с редактора, потому что сначала нужно научиться создавать тесты, а потом уже будем их отображать и работать с ними. Для начала создадим главную форму, как это показано на рис. 25.2. Здесь у нас главное окно, в котором есть главное меню программы, панель кнопок быстрого вызова команд ToolBar и строка состояния, которая будет отображать подсказки. Попробуйте' создать что-то подобное.
Рис. 25.2. Главная форма редактора теста
На форме созданы следующие кнопки (и соответствующие пункты меню):
口 Создать;
□ Открыть;
О Настройки программы; 口 Помощь;
□ Сохранить;
П О программе;
И Печать;
П Выход.
У каждой кнопки
в свойстве Hint указана соответствующая ей подсказка.
которая должна появляться в строке состояния при наведении указателя мыши на эту кнопку. Чтобы эта подсказка появлялась еще и рядом с кнопкой, установите свойство showHint у главной формы в значение true.
Первым делом давайте сразу же сделаем возможность отображения подсказок Hint в строке состояния. Для этого в разделе private опишите новую пропедуру:
private
{ Private declarations }
procedure ShowHint (Sender: TObject);
Теперь нажмите сочетание клавиш <Ctrl>+<Shift>+<C>, чтобы создать заготовку для этой процедуры. В ней нужно написать следующее:
procedure TTestEditorForm.ShowHint(Sender: TObject);
begin
StatusBar.Panels.Items[0].Text := Application.Hint; end;
Здесь мы отображаем текст текущей подсказки, который находится в свойстве Hint объекта Application на нулевой панели строки состояния. Пусть строка состояния состоит из двух панелей.
Теперь сделайте главную форму многодокументной. Для этого в свойстве FormStyle НУЖНО установить значение fsMDlForm.
Можете сразу же создать окно О программе. Здесь это описываться не будет, потому что фантазия у каждого программиста работает по-разному, ну, а пример окна вместе с исходным кодом вы сможете увидеть на диске.
Создайте сразу обработчик события onclick для кнопки выхода и напишите там вызов метода close, чтобы по нажатии этой кнопки наша программа закрывалась. ПотЪм эту же процедуру-обработчик нужно назначить пункту меню Выход.
На этом первые приготовления закончены. Теперь двинемся дальше. Создайте новую форму (установите ее имя — NewTestForm), которая будет отображаться по нажатии кнопки Создать. В этом окне нужно расположить строку ввода имени теста и выпадающий список для выбора типа теста. Как это сделать, показано на рис. 25.3.
Рис. 25.3. Окно создания нового теста
У выпадающего списка измените свойство style на csDropDownList для того, чтобы пользователь мог только выбирать уже имеющееся значение в списке и не мог вводить новое. В свойстве items (список элементов) у нас будет только одна строка — Вопрос - варианты ответов. В принципе, можно было бы и не делать этот выпадающий список, раз там только один элемент, но он установлен, чтобы вы потом могли расширить возможности программы.
Для кнопок Да и Нет установите только свойство ModaiResuit в значения mrok и mrCancel соответственно.
У самой формы измените свойства:
□ Position на рома inFormCentег, чтобы окно показывалось по центру главного окна;
口 Borderstyle на bsSingle, чтобы пользователь не мог изменять размеры окна.
Эти свойства мы будем менять у всех форм, которые будут отображаться модально. Кнопки Да и Нет у таких окон тоже всеща будут иметь указанные выше значения свойства ModaiResuit, поэтому мы больше не будем останавливаться на этих вещах.
Теперь возвращаемся в главную форму и по событию Onclick для кнопки Создать пишем код отображения окна NewTestForm. Это окно нужно отображать как модальное.
К ITpuMg^a^ie 】
На компакт-диске в папке \ Примеры \ Глава 25 \ Testi \ Редактор вы можете увидеть исходный код уже написанного примера.
Теперь создадим окно формирования нового теста вопросов и ответов. Для этого создайте новую форму и установите у нее следующие свойства: 口 Caption — тест Вопрос - варианты ответа;
П Formstyle - f sMDichiid (это у нас будет дочернее окно);
О Name — QuestionResultFoinii.
По событию OnClose пишем следующий код:
procedure TQuestionResultForm.FormClose(Sender: TObject;
var Action: TCloseAction);
begin
Action:-caFree;
QuestionResultForm:=ni1;
end;
Здесь в первой строке устанавливаем переменной Action (эту переменную мы получаем в качестве параметра) значение caFree, чтобы окно могло закрыться. Дочерние окна по умолчанию не закрываются, а сворачиваются. Во второй строке обнуляем переменную Que s t ionRe suit Form, которая указывает на объект окна.
Дочерние окна при старте автоматически становятся видимыми и отображаются в рабочем поле главного окна. Нам этого не надо, потому что данное окно должно появляться только после того, как пользователь нажмет кнопку создания нового теста и подтвердит его создание. Для того чтобы отключить форму от автоматического создания, мы должны войти в свойства проекта (Prefect | Options) и в появившемся окне переместить имя формы QuestionResuitForm из списка Auto-create forms в список Available forms.
Теперь перейдем к дизайну формы создания теста. Ее вид вы можете увидеть на рис. 25.4. В центре окна находится компонент Treeview, растянутый по левому краю, и Listview, растянутый по всему оставшемуся контуру (clClient). Сверху окна расположена панель Toolbar со следующими кнопками:
П Создание вопроса;П Удаление;
П Редактирование;П Выход.
Выделите компонент Listview и установите у него следующие свойства:
П GridLines :=truej
口 ViewStyle:=vsReport;
П Name:=ResultView.
После этого дважды щелкните мышью по свойству columns и в появившемся окне создайте две колонки с именами Верно и Вариант ответа. У второй колонки установите свойство Autosize в значение true.
Рис. 25.4. Форма окна создания теста
Теперь объявим в разделе type следующую структуру:
PQue s tion=Z4TQue s t ion;
TQuestion=record
Name: String[255];
Resultcount:Integer;
ResiltText: array[0..10] of String[255];
Re s i1tValue: array[0..10] of boolean; end;
У структуры TQuestion есть следующие поля:
□ Name — здесь будет храниться вопрос;
口 ResultCount — количество вариантов ответов;
П ResiltText — массив из строк для десяти вариантов ответов;
П Resiltvalue — массив из булевых переменных, указывающих, какие ответы верные.
Тут же объявлена переменная pQuestion, которая является указателем на Структуру TQuestion.
Теперь в разделе public объявим следуюшие переменные: public
{ Public declarations } ProjectName:String[255];
QuestionList:TList;
В переменной projectName будем хранить имя проекта. Список QuestionList будет использоваться для хранения структур типа рQuestion. Этот список должен инициализироваться по событию onshow и уничтожаться по событию OnClose. Как это делать, вы уже должны знать.
Далее создаем форму для редактирования элементов, которую мы будем отображать на экране по нажатии кнопок Создать вопрос и Редактировать вопрос. Результат работы вы можете увидеть на рис. 25.5.
Рис. 25.5. Форма окна ввода вопросов
Здесь находится строка TEdit для ввода текста вопроса и TCheckListBox для ввода вариантов ответа. По нажатии кнопки Добавить будем показывать окно ввода нового варианта ответа:
procedure TEditQuest ionForm.NewResultButtonClick(Sender: TOb^ertK;
var
Str:String;
begin
Str:-'1;
if Iriput Quer^ ' Новый ответ ' , * Введите текст твета: * , Str) then ResultListBox.Items.Add(Str);
end;
Здесь мы объявили одну строковую переменную. В первой строке кода ей присваивается пустая строка. Затем на экране отображается стандартное диалоговое ОКНО ВВОДа Текстовой строки С ПОМОЩЬЮ функции inputQuery.
28 Зак. 942
У этой функции три параметра:
П текст, который будет отображаться в заголовке окна;
П текст, который будет отображаться в окне рядом со строкой ввода;
□ строковая переменная, через которую мы можем передать значение по умолчанию и получить результат ввода.
Если функция возвращает true, то пользователь после ввода нажал кнопку ОК, и в этом случае мы добавляем введенный текст в список ответов ResultListBox.
Для события onclick, связанного с нажатием кнопки Удалить, напишем следующий код: procedure TEditQuestionForm.SpeedButton丄Click(Sender: TObject); begin
if Resu丄匸ListBox. Itemlndexo-l then
ResultListBox.Items.Delete(ResultListBox.Itemlndex); end;
В первой строке кода мы проверяем свойство Itemindex, которое указывает на выделенный элемент в списке. Если оно не равно —1, значит, в списке есть выделенный элемент, и мы его должны удалить. Для этого выполняем ResultListBox. Items.Delete, указывая в качестве параметра выделенную строку.
Теперь вернемся к нашему окну QuestionResultForm. Здесь создадим обработчик события onclick для кнопки создания нового вопроса. В нем нужно написать код, представленный в листинге 25.4.
Листинг 25.4. Создание нового вопроса
procedure TQuestionResultForm.NewButtcnClick(Sender: TObject);
var
NewQuest:PQuestion;
i:Integer;
begin
//Очищаем содержимое окна. EditQuestionForm
Edit Que st ionForm. ResultListBox. Items. Clear;
EditQuestionForm.QuestionEdit.Text:=*';
/ /Отображаем окно на. экране
EditQuestionForm. ShowModal;
if EditQuestionForm.ModalResultOmrOK then exit;
/ /Создаем в памяти новую структуру
NewQuest:=New(PQuestion);
NewQuest.Name:=EditQuest ionForm.QuestionEdit.Text;
NewQuest. ResultCount: =Edi tQue st ionForm. ResultListBox. Items. Count;
/ / Добавляем в структуру варианты ответов
for i:= О to NewQuest.ResultCount-1 do begin
NewQuest.ResiltText[i]:=EditQuestionForm.ResultListBox.Items.Strings[i];
NewQuest.ResiItValue[i]:=EditQuestionForm.ResultListBox.Checked[i]; end;
QuestionList .Add (NewQuest);
/ /Добавляем новый элемент в дерево воцросов
with QuestionTreeVie-w. Items .Add (nil, NewQuest.Name) do begin
Iraagelndex: =0;
Data:-NewQuest;
end;
end;
В самом начале очищаются элементы управления окна EditQuestionForm. Потом отображаем это окно, и если пользователь ввел название вопроса и нажал 0K то нужно обработать введенную информацию. Для начала выделяется память под переменную NewQuest. Эта переменная объявлена как PQuestion, а это указатель на структуру TQuestion. Как вы знаете, любые указатели создаются пустыми, и чтобы они на что-то указывали, им надо выделять память. Выделяем память с помощью функции New. Этой функции нужно передать в качестве параметра тип данных, под который надо выделять память. Мы указываем наш указатель pQuestion, по которому функция определит, сколько памяти надо выделить. Результат выполнения функции —указатель на выделенную память, который мы сохраняем в переменной NewQuest.
Если нужно уничтожить выделенную память, надо вызвать процедуру Dispose и передать ей переменную, которую нужно уничтожить, например, Dispose (NewQuest). Однако на данный момент не надо уничтожать эту переменную, потому что она потом будет использоваться, и мы ее добавляем в СПИСОК QuestionList типа TList.
После того как выделена память для структуры NewQuest, мы заполняем ее поля в зависимости от введенной пользователем информации. Как только все заполнено, добавляем структуру в список: QuestionList.Add(NewQuest);
После этого происходит самое интересное. Мы должны создать новый элемент в дереве вопросов. Для этого выполняется следующий год:
with QuestionTreeView. Items .Add (nil, NewQuest.Name) do
begin
ImageIndex:=0;
Data:=NewQuest;
end;
Давайте разберем этот код по частям. В первой строке в структуру дерева добавляется новый элемент с помощью вызова функции QuestionTreeView. items .Add. В качестве параметров методу Add нужно передать указатель на родительский элемент в дереве и текст элемента. В качестве родительского элемента передаем nil, потому что мы будем создавать дерево без вложенных элементов. В качестве текста элемента передаем текст вопроса.
Выполненный метод Add возвращает указатель на созданный элемент. И тут у нас стоит оператор with, который заставляет выполнить следующие действия с указанным объектом. Получается, что действия, следующие между begin и end, будут выполняться с созданным элементом дерева вопросов. А там выполняются два действия:
Imagelndex: =0 — индексу иконки присваивается значение 0.
к Внимание /
Давайте установим на форму список картинок imageList, загрузим туда несколько картинок и укажем этот список в свойстве Images нашего дерева. Получается, что в этом коде мы назначаем элементу первую картинку из созданного списка.
Data : =NewQuest — свойство Data элемента дерева.
Внимание /
Это такое же свойство, как Тад у всех компонентов. Оно также не влияет на работу компонента и его элементов и может использоваться в наших собственных целях. Это свойство имеет значение указателя, и мы можем в него вносить любые указатели. Мы присваиваем в это свойство указатель на структуру NewQuest, которая связана с созданным элементом.
Теперь создадим обработчик события OnChange для нашего дерева. В нем нужно написать содержимое листинга 25.5.
Листинг 25.5. Обработчик События OnChange
procedure TQuestionResultForm.QuestionTreeViewChange(Sender: TObject;
Node: TTreeNode); var
i:Integer;
begin
//Очищаю список
Resultview. Items .Clear;
/ /Если не выделен элемент, то выход
:f Nod6=nil then exit;
//Запускаю цикл, по которому заполняются данные списка.
for i:=0 to PQuestion(node.Data).ResultCount-1 do
with Resultview.Items.Add do
begin
Caption:=PQuestion(node.Data).ResiltText[i];
if PQuestion(node.Data).ResiltValue[i]=true then begin
Subl terns. Add (*Да *);
ImageIndex:=2;
end
else
begin
Sub I terns .Add (* Нет');
Image Index: =1;
end;
end;
end;
Это событие reнерируется всякий раз, когда пользователь выбрал какой- нибудь элемент. По выбору вопроса мы должны заполнить ответы в списке Listview. Но прежде чем заполнять, очищаем список, потому что он уже мог быть заполненным данными другого вопроса.
В обработчик события передается параметр Node типа TTreeNode, который указывает на выделенный элемент. Второй строчкой кода происходит проверка, если выделенный элемент равен nil (ничего не выбрано), то нужно выходить из процедуры.
Чтобы получить доступ к структуре pQuestion, в которой хранятся данные об ответах выделенного вопроса, мы должны обратиться к свойству Data выделенного элемента Node. Как вы помните, в это свойство мы поместили указатель на структуру PQuestion. Но программа не может знать, какого типа этот указатель, поэтому мы должны явно указывать это — PQuestion(node.Data).
Далее, запускается цикл от 0 до количества вариантов ответов в данном вопросе PQuestion (node. Data) .ResultCount минус 1. Внутри цикла выполняем код Resultview. Items .Add, который создает очередной элемент списка. Здесь метод Add также возвращает указатель на созданный элемент, вместе с которым и будет выполняться дальнейший код (об этом говорит оператор with). А внутри кода выполняются несколько действий.
□ Заполняется заголовок элемента Caption.
口 Если PQuestion (node. Data) . ResiltValue [il равно true, T. e. Ответ верный, то добавляем дочерний элемент (текст этого элемента будет отображаться во второй колонке списка) Subitems .Add (1 Да 1) и пРисваиваем иконке индекс 2. Иначе текст дочернего элемента будет равен нет и иконка будет иметь индекс единицы.,
Таким образом, внутри цикла будут обработаны все варианты ответов и все они будут добавлены в список. Попробуйте сейчас запустить программу и создать пару вопросов и посмотреть, как все будет выглядеть. Окно созданной программы вы можете увидеть на рис. 25.6.
Рис. 25.6. Рабочее окно программы
Здесь осталось рассмотреть код, по которому мы будем отображать окно, показанное на рис. 25.6. Для этого надо скорректировать обработчик события onclick для кнопки создания нового проекта теста:
procedure TTestEditorForm.NewButtonCLick(Sender: TObject); begin
NewTestForm. ShowModal;
if NewTestForm.ModalResultOmrOK then exit;
if NewTestForm.TestTypeBox.ItemIndex=O then
begin
QuestionResultForm:=TQuestionResultForm. Create (Owner);
Quest ionResultForm. Pro j ectName: =NewTestForm. TestNameEdit. Text;
QuestionResultForm.Caption:=Quest ionResult Form.Caption+* : *
+QuestionResultForm.ProjectName;
end;
end;
В первой строке этого кода мы показываем окно создания нового проекта. Если пользователь выбрал первый тип теста "Вопрос — варианты ответа" (в примере он будет описан как единственный), то создается окно, в котором мы создаем вопросы. Потом сохраняем имя выбранного проекта и изменяем заголовок окна.
Обработчики события кнопок Редактировать и Удалить вопросы мы рассматривать не будем, а только рассмотрим их код с комментариями. Вы должны разобраться с этим кодом самостоятельно (листинг 25.6).
I Листинг 25.6. Редактирование вопросов
procedure TQuestionResultForm.EditButtonClick(Sender: TObject);
var
i:Integer;
begin
/ /Здесь QuestionTreeView. Selected указывает на. выделенный элемент
//в дереве. Если он равен nil, то ничего не выделено, и нужно выйти
if QuestionTreeView.Selected^nil then exit;
/ / Заполняем компонент Ques tionEdi t в окне редактирования вопросов
EditQuestion
Form. QuestionEdit.Text:=PQuestion(Que st ionT гееView.Selected.Data).Name;
//Очищаем список вариантов ответов в окне редактирования вопросов
EditQuestionForm.ResultListBox.Clear;
for i:=0 to PQuestion(QuestionTreeView.Selected.Data).ResultCount-1 do begin
/ /Заполняем список вариантов ответов в окне редактирования вопросов Edit Ques tionForm, Result Li stBox. Items. Add (
PQuestion(QuestionTreeview.Selected.Data).ResiltText[i]);
//Если ответ верный, то ставим галочку
if PQuestion(QuestionTreeview.Selected.Data).ResiltValue[i]=true then EditQuestionForm.ResultListBox.Checked[i]:=true;
end;
/ /Отображаем окно редактирования воттроса.
EditQuestionForm. ShowModal;
if EditQuestionForm.NodalResultOmrOK then exit;
/ /Записываем информацию обратно в структуру
PQu.es-
tion (QuestionTreeView.Selected.Data) .Name: =EditQuestionForm.QuestionEdit.
Text;
PQuestion(QuestionTreeview.Selected.Data) .Resultcount:=
EditQuestionForm.ResultListBox.Items.Count;
for i:= 0 to PQuestion(QuestionTreeview.Selected.Data).ResultCount-1 do begin
PQuestion(QuestionTreeview.Selected.Data)•ResiltText[i]:=
EditQuestionForm.ResultListBox.Items.StringsR J;
PQuestion(QuestionTreeview.Selected.Data).ResiltValue[ij:= EditQuestionForm. ResultListBox.Checked[i];
end;
/ /Вызываем процедуру QuestionTreeViewChange, которая должна, обновить
//информацию в Resultview. Первый параметр нас не интересует, а второй
/ /14Ы обязаны указать, потому что внутри процедуры Qu.estionTreeViewCha.nge
/ /ю используем его. Здесь указывается вьщеленный элемент.
QuestionTreeViewChange(nil, QuestionTreeView.Selected);
end;
Единственное, что здесь нужно отметить, так это то, что обращение к структуре, связанной с элементом, происходит через свойство Data выделенного элемента. Как мы уже знаем, там хранится указатель на структуру. То же самое можно было бы сделать, обращаясь через контейнер (в данном случае это буцет не так удобно). Но все же это возможно, и на всякий случай рассмотрим пример, как можно обратиться к свойству Name:
PQuestion (QuestionList [QuestionTreeView. Selected. Index]) .Name
Здесь используется контейнер QuestionList. В квадратных скобках у него указывается индекс элемента из контейнера, который нужен. Мы указываем индекс ВЫДелеННОГО В дереве Элемента QuestionTreeview. Selected. Index.
Для события onclick кнопки Удалить вы должны написать код, представленный в листинге 25.7.
Листинг 25.7. Удаление вопроса
procedure TQuestionResuitForm.DeleteButtonClick(Sender: TObject); var index, i:Integer;
begin if QuestionTreeview.Selected=nil then exit;
//Подтверждение удаления
i f Appl icati on. Me s sage Box (PCha r (* Вы действительно хотите удалить QuestionTreeview.Selected.Text), 1 Внимание!!!*,
MB_OKCANCEL+MB_ICONINFORMATION) OidOk then Exit;
/ / Сохраняем индекс вьщеленноро элемента.
index:=Que st ionTreeView.Selected.Index;
//Удаляем вьщелейный элемент из дерева
QuestionTreeview.Items.Delete(QuestionTreeview.Selected);
/ /Удаляем из контейнера.
Quest ionList.Delete(Index);
end;
Примечание ;
На компакт-диске в папке \ Примеры \ Глава 25\Test2 \ Редактор вы можете увидеть исходны йкодуже написанного при мера.
Сохранение и загрузка теста
Наша программа умеет создавать тесты. Пора бы ее научить и сохранять их, и тем более загружать потом созданные проекты для редактирования. Добавим кнопки открытия и сохранения проекта из дочернего окна в основное.
工 Пршиечание
Сохранение легче сделать внутри дочернего окна, а открытие — в главном. Но это нарушает законы наследования (правила оформления программ). Поэтому придется идти не совсем простым путем, потому что мы должны писать программы правильно и хорошо разобраться с тем, как работать с дочерним и окнами.
Для события Onclick, связанного с нажатием кнопки Сохранить проект, напишем следующий код:
procedure TTestEditorForm. SaveButtonClick (Sender: TObject);
begin
/ /Если активное дочернее окно равно нулю (нет активных окон) , то выход if ActiveMDIChild=ni1 then exit;
//Если окно имеет имя QuestionResultForm, то это
//Вопрос — варианты ответов, и вызываем для с охранения
//процедуру SaveTestl .
if ActiveMDIChild.Name=* QuestionResultForm1 then
SaveTestl;
end;
Свойство ActiveMDlChild всегда указывает на активное в данный момент дочернее окно. Прежде чем использовать это свойство, его желательно сравнивать со значением nil, потому что в данный момент может вообще не быть ни одного дочернего окна. В этом случае при обращении к свойству может произойти "критическая** ошибка, потому что произойдет попытка чтения по несуществующему указателю.
Процедуру SaveTestl вы можете увидеть в листинге 25.8.
Листинг 25.8. Процедура сохранения теста
procedure TTestEditorForm.SaveTestl;
var
fs: TFileStream;
i:Integer;
Str:String[5];
begin
//Если у активного окна в свойстве FileName ц7cHо, / /то нет имени файла и нужно вызвать обработчик события
//меню "Сохранить как. . . ", чтобы появилось окно ввода
//имени файла
if TQuestionResultForm(ActiveMDIChild) . FileName=,' then begin
S aveAsMenuClick(nil);
exit;
end;
/ /Создаем новый файл. Если он уже существовал, то его
//содержимое будет уничтожено
fs:=TFileStream. Create (TQuestionRe suit Form (ActiveMDIChild) . FileName, fmCreate);
//Сохраняем в начале файла текст "Тест", чтобы по нему потом //определить, к чему относится дгснный файл.
Str: = *Тест*;
fs.Write(Str, SizeOf(Str));
//Сохранить имя проекта
fs.Write (TQuestionResultForm(ActiveMDIChild) .ProjectName, sizeof (TQuestionResultForm(ActiveMDIChild) . ProjectName));
try
/ /Сохранить количество воттросов
fs.Write(TQuestionResuitForm(ActiveMDIChild).QuestionList.Count, sizeof(TQuestionResultForm(ActiveMDIChild).QuestionList.Count));
/ / Запускаем цикл, в котором с охраняются все вопросы.
for i:=0 to TQuestionResultForm(ActiveMDIChild).QuestionList.Count-1 do
fs.Write (PQuestion(TQuestionResultForm(ActiveMDIChild.) .QuestionList [i] ) Л, sizeof(TQuestion));■
finally
//Закрыть файл
fs.Free;
end;
end;
Здесь все очень просто и с кодом можно разобраться по комментариям. Единственное, на что здесь надо обратить внимание, это то, что структура
PQuestion находится в динамической памяти, поэтому при сохранении _л Если этого знака не указать, то в
нужно указывать знак разыменования .」'
файле сохранится адрес структуры, а не сама структура. В этом случае при чтении данных из файла будет прочитан адрес, но по этому адресу ничего интересного не будет, потому что после первой же перезагрузки программы память очистится и сама структура уничтожится. В связи с этим для сохранения данных по адресу, а не самого адреса, нужно указывать знак 八
Обработчик события для пункта меню Сохранить как еще проще:
procedure TTestEditorForm.SaveZ\sMenuClick(Sender: TObject);
begin
if SaveDialogl.Execute then
begin
TQuest ionResultForm (ActiveMDIChiId) . FileName: =SaveDialogl. FileName;
SaveButtonClick(nil);
end;
end;
Здесь мы отображаем окно выбора имени файла. Если пользователь что-то выбрал, то сохраняем имя файла в свойстве FileName активного окна и вызываем обработчик события кнопки Сохранить, где происходит сохранение.
Теперь посмотрите на обработчик события onclick для кнопки Открыть проект (листинг 25.9).
Листинг 25.9. Обработчик для открытия файла
procedure TTestEditorForm.OpenButtonClick(Sender: TObject); var
fs:TFileStream;
i, Count:Integer;
Ptr:String[5];
New。—' : PQuestion;
baqip
//П ж^чать окно открытия файла
if not ^>penE)ial)ql.Execute then exit;
'< .'ткрмть файл длн чтения
t $: -「卜】i tXr recm.Create(OpenDialog].FileName, fmOpenRead);
/ /Перейти в начало файла и прочитать заголовок
fs • Seek (0, soFroniBeginning); fs.read(Str, SizeOf(Str));
//Если заголовок равен тексу "Тест", значит это "Вопрос -
//варианты ответов" if Str=*TecT* then begin
//Создать новое окно теста
■QuestionResultForm:^TQuestionResultForm.Create(Owner);
//Сохранить имя открытого файла в объекте окна
QuestionResult Form. FileName: =OpenDialogl ・ Filename;
/ /Прочитать имя проекта
fs. Read (Que stionResuit Form. ProjectName, sizeof(QuestionResuitForm. ProjectName));
try
//Прочитать количество вопросов fs.Read(Count, sizeof(Count));
//Запустить цикл чтения воттросов for i:=0 to Count-1 do begin
//Создаем новую структуру в памяти для воттроса NewQuest:=New(PQuestion);
//Читаю структуру
fs.Read(NewQuestЛ, sizeof(TQuestion));
/ / Добавляем структуру в контейнер
■QuestionResultForm. QuestionList.Add(NewQuest);
/ /Создаем новый элемент в дереве
with QuestionResultForm. QuestionTreeView. Items .Add (nil, NewQuest. Name) do
begin
ImageIndex:=0;
Data:=NewQuest; end;
end;
finally
//Закрываю файл
fs.Free;
end;
end;
end;
В чтении файла так же ничего сложною нет. Все очень похоже на запись и со всеми методами вы уже должны быть знакомы. Здесь так же читаются данные в указатель на структуру pQuestion, поэтому при чтении нужно рас … с … с 7 c 4 人 чтобы данные записались по адресу, а не зыменовывать указатель NewQuest人, 八 户
6 адрес, т. е. в памяти, на которую указывает указатель.
Примечание
На компакт-диске в папке \ Примеры \ Глава 25 \Test3 \ Редактор вы можете увидеть исходный код уже написанного примера.
Вот на этом редактор можно считать законченным. Хотя еше не реализованы обработчики события для кнопок Печать и Свойства проекта. Но свойства проекта нам не нужны, а вот печать оставим вам для самостоятельной работы. Попробуйте добавить эту кнопку в структуру нашего проекта.
Тестер
Теперь напишем программу тестирования, которая будет загружать наши проекты, отображать вопросы и собирать статистику правильных ответов. Для этого будет использоваться отдельная программа.
Создайте новый проект и установите на форму ряд компонентов (форму вы можете увидеть на рис. 25.7).
口 Панель ToolBar с тремя кнопками Открыть, Запустить и Выход.
口 Компонент staticText, где будут отображаться вопросы. В свойстве Name укажите QuestionLabel И СВОЙСТВО AutoSize установите В false.
口 Список checkListBox, в котором будут отображаться варианты ответов. В Свойстве Name укажите QuestionCheckList.
口 Кнопка Дальше.
Рис. 25.7. Форма будущей программы
В разделе type объявите структуру TQuestion такого же вида, как и в редакторе вопросов. Количество и размерность полей структуры должны быть одинаковыми, потому что мы будем использовать ее для загрузки данных из файла.
Внимание
Если какое-то поле будет отличаться от остальных, то при загрузке произойдет ошибка.
type
PQuestion=ATQuestion;
TQues tion=record
Name: String[255];
Resultcount:Integer;
ResiltText: array[0..10] of String[255];
Res i 1 tValue: array[0..10] of boolean;
end;
В разделе private объявите следуюшие переменные:
private
{ Private declarations }
QuestionList:TList;
Question, QuestionNumber, FalseNumber: Integer;
FileName:String;
Рассмотрим, для чего нужны эти переменные.
О QuestionList — здесь будет храниться список вопросов, как и у редактора вопросов.
口 Question — будет отображать текущий вопрос, на который отвечает испытуемый.
П Questi onNumbe г — здесь будет храниться количество вопросов, на которые уже даны ответы. Нам же надо иметь счетчик, после которого тест должен закончиться.
П FalseNumber — определяет количество неправильных ответов.
Теперь создадим обработчик события Onshow для главной формы. В этом обработчике нужно инициализировать список QuestionList:
procedure TTestForm. FormShow (Sender: TObject);
begin
QuestionList:=TList.Create;
end;
По событию onDestroy мы должны уничтожить этот объект:
procedure TTestForm. FormDestroy (Sender: TObj ect);
begin
QuestionList.Free;
end;
Теперь для кнопки открытия напишем следующий код:
procedure TTestForm.OpenButtonClick(Sender: TObject);
begin
//Показать окно открытия файла
if not OpenDialogl.Execute then exit;
FileName:=OpenDialogl.Filename;
RunButton.Enabled:=true;
end;
В первой строке кода отображаем окно открытия файла. Если пользователь нажал на кнопку Отмена, то происходит выход из процедуры. Иначе в переменной FileName сохраняется имя выбранного файла. В принципе, этого можно было и не делать, потому что имя файла останется в свойстве OpenDialogl. Filename. Но здесь все же определена отдельная переменная, в которой будет храниться имя файла (надеяться на свойства компонента диалогового окна не стоит).
В последней строке делаем кнопку Запустить (RunButton) доступной. Кстати, на форме эта кнопка должна быть недоступной, чтобы при старте программы пользователь не мог ее нажать, пока не выберет файл.
Теперь напишем обработчик события onclick для кнопки Запустить:
procedure TTestForm.RunButtonClick(Sender: TObject);
begin
LoadFile;
Que s t i onNumbe r:=0;
FalseNumber:=0;
NextButton.Enabled:=true;
NextQuestion;
end;
В первой строке вызываем процедуру LoadFile, которую напишем чуть позже, и она будет загружать список вопросов из выбранного файла проекта. Почему мы должны загружать вопросы каждый раз при старте программы? Да потому, что тест будет происходить следующим образом:
Из списка вопросов случайным образом выбирается первый попавшийся вопрос.
Пользователь отвечает на него, и мы удаляем этот вопрос из списка. Таким образом, в следующий раз, когда будет выбираться вопрос из списка, мы будем точно уверены в том, что в списке вопроса, на который уже отвечал пользователь, нет.
При следующем старте теста список вопросов инициализируется заново (мы снова загружаем весь список) и все вопросы возвращаются на свои места.
После загрузки вопросов обнуляем все переменные и делаем доступной кнопку NextButton (это кнопка Дальше, по нажатии которой будет выбираться следующий вопрос). При старте программы кнопка Дальше должна быть недоступной.
В последней строке мы вызываем процедуру NextQuestion, которая и буцет выбирать случайный вопрос и отображать его в окне программы.
Теперь посмотрим на процедуру загрузки вопросов — LoadFiie. Она идентична уже написанной процедуре загрузки в программе редактора вопросов, и ее код можно увидеть в листинге 25.10.
Листинг 25.10. Загрузка файла вопросов
procedure TTestForm.LoadFiie;
var
fs:TFileStream;
i, Count:Integer;
Str:String[5];
ProjectName:String[255];
NewQuest:PQuestion;
begin
QuestionList.Clear;
/ /Открыть файл для чтения
fs:=ТFilestream.Create(FileName, fmOpenRead);
/ /Перейти в начало файла и прочитать заголовок
fs.Seek(0,soFromBeginning);
fs.read(Str, SizeOf(Str));
//Если заголовок равен тексу "Тест", значит это "Вопрос - //варианты ответов"
if Str='TecT‘ then
begin
〃加обитать имя ттроекта
fs.Read(ProjectName, sizeof(ProjectName));
Caption:=ProjectName;
try
//Прочитать количество вопросов fs.Read(Countr sizeof(Count));
//Запустить цикл чтения вопросов
for i:=0 to Count-1 do begin
/ /Создаю новую структуру в памяти для вопроса
NewQuest: =New (PQuestion);
//Читаю структуру
fs.Read(NewQuestл, sizeof(TQuestion));
/ /Добавляем структуру в контейнер
QuestionList.Add (NewQuest);
end;
finally
//Закрываю файл
fs.Free;
end;
end;
end;
Тут все должно быть понятно, но на всякий случай весь программный код помечен подробными комментариями.
Теперь посмотрим на процедуру NextQuestion, которая должна случайным образом выбирать вопрос из списка. Эту процедуру вы можете увидеть в листинге 25.11.
Листинг 25.11. Выбор следующего вопроса
procedure TTestForm.NextQuestion;
var
i:Integer;
begin
Randomize;
Question:=Random(QuestionList.Count-1);
QuestionLabel.Caption:=PQuestion(QuestionList[Question]).Name;
QuestionCheckList.Items.Clear;
for i:=0 to PQuestion(QuestionList[Question]).ResultCount-1 do
QuestionCheck
List . Items.Add(PQuestion(QuestionList[Question]).ResiltText[i]);
Inc(Ques tionNumber);
end;
В первой строке процедуры мы вызываем процедуру Randomize, которая инициализирует таблицу случайных чисел. Если вы опустите вызов этой процедуры, то ничего страшного не произойдет, и когда вы будете запрашивать случайное число, то оно будет случайным, но все же лучше инициализировать таблицу. Просто в этом случае считается, что эффективность получения действительно случайного числа будет наилучшей.
Во второй строке вызываем функцию Random, которая возвращает случайное число. Ей нужно передать в качестве параметра максимально допустимое число. Мы передаем QuestionList. Count-1, т. е. количество вопросов в нашем списке. Функция вернет случайное число от 0 до указанного числа. Сохраняем это число в переменной Question.
В следующей строке кода показываем в компоненте QuestionLabel вопрос. Затем очищаем список ответов в компоненте QuestionCheckList и заполняем его вариантами ответов, относящихся к данному вопросу. В последней строке кода увеличиваем переменную QuestionNumber, в которой у нас хранится количество вопросов, на которые уже был дан ответ.
Для события onclick, связанного с кнопкой Далее, напишите код, представленный в листинге 25.12.
Листинг 25.12. Обработчик события для кнопки Далее
procedure TTestForm.NextButtonClick(Sender: TObject);
var
OK:Boolean;
i:Integer;
begin
OK:=true;
for i:=0 to PQuestion(QuestionList[Question]).ResultCount-1 do
if PQu.es-
tion(QuestionList[Question]).ResiltValue[i]OQuestionCheckList.Checked[i] then
OK:=false;
if OK=false then
Inc(FalseNumber);
//Удаление вопроса из списка
QuestionList.Delete(Question);
if QuestionNumber<5 then
NextQuestion
else
begin
Application.MessageBox(PChar(*Вы закончили тест с количеством ошибок 二 ■ +
IntToSt г (FalseNumber)), * Внимание !!!*);
NextButton.Enabled:=false;
end;
end;
В первой строке кода логической переменной ок присваивается значение true. В этой переменной мы будем хранить состояние результата ответа. По умолчанию будем считать, что ответ правильный, поэтому и устанавливаем значение true.
Далее запускается цикл от 0 до количества вариантов ответов в списке. Внутри цикла сравниваем значение правильных ответов с состоянием свойства checked компонента QuestionCheckList. Если что-то не совпадает, то тестируемый где-то ошибся и нужно переменной ок присвоить значение false, т. е. ответ неверный. После цикла происходит проверка, если переменная ок равна false, то увеличиваем счетчик неправильных ответов FalseNumber на единицу.
Все, текущий вопрос нам в списке больше не нужен. Его нужно удалить, чтобы он больше не появился, когда мы будем случайным образом получать следующий вопрос.
Далее происходит проверка, если количество отображенных вопросов меньше 5, то выбираем следующий вопрос (вызываем процедуру NextQuestion), иначе отображаем сообщение с состоянием пройденного теста и делаем кнопку Далее недоступной.
Как видите, наш тест состоит из 5 вопросов, и если вам нужно больше, то можете увеличить это значение. В редакторе вопросов есть кнопка свойств, по нажатии которой можно отображать окно свойств проекта. Можно сделать возможность выбирать количество вопросов, на которые должен ответить испытуемый в этом окне. Потом эти свойства можно сохранить в файл проекта и загружать в программе теста. Но все это делать мы не будем, потому что у вас уже есть достаточно знаний, чтобы попробовать все это проделать само стоятел ьно.
К Прмнечанме 】
На компакт-диске в папке \ Примеры \ Глава25 \ Test4 \ вы можете увидеть исходный код уже написанного примера.
Приложение
Описание компакт-диска
Примеры, которые приведены в данной книге, можно загрузить с прилагаемого компакт-диска. Примеры находятся в папках, названных в соответствии с нумерацией глав, к которым они относятся: Примеры\Глава5, Приме- ры\Глава6,…,Примеры\Глава25 (табл. Ш).
Таблица П1. Структура компакт-диска
Папка
Содержание
\DLL
Динамические библиотеки для OpenGL и Midas, необходимые для работы некоторых примеров, рассматриваемых в книге
\ Programs
Программы HeaderConvert — для конвертирования заголовочных файлов C++ в Delphi, ASPack — для сжатия исполняемых файлов, OpenGL — установка пакета OpenGL на ваш компьютер
'Документация \3аголовочные файлы
\Картинки
\Компоненты
Дополнительная документация к книге
Заголовочные файлы ACM, DirectX и OpenGL
Картинки и иконки для кнопок программ
Компоненты для улучшения внешнего вида программ и облегчения программирования
'Примеры
Примеры из книги. Все разделено по папкам в соответствии с названиями глав
Большинство примеров из книги можно запускать прямо с компакт-диска.
工 Примечание 】
Для редактирования исходного кода необходимо переписать папку с исходным кодом на локальный диск компьютера.
Предметный указатель
А
c
ACM-Manager 673
ActionMainMenuBar 346
Action Manager 346 ActionToolBar 346
ActiveControl 124
ActiveMDIChild 212
Add 157, 235
AddAlias 542
AddStandardAIias 541
AddStrings 157
ADOConnection 432, 435
Align 124
AlphaBlend 124, 134
AlphaBlendValue 125
Anchors 125
And 185
Append 157
Arc 379
Arrangelcons 212
ASC 438
Assign 157
AssignPrn 409
AsString 439
AutpScroll 125, 144
AutoSize 125
AVI-файл 305
Cancel 484
Canvas.TextOnt 372
CanvasSetAngle 372
Caption 127
CaretPos 155
Cascade 212
Case 146
Check Box 158
Clear 157
ClientHeight 127
ClientWidth 128
Close 142
CloseFile 409
Color 128
ColorToRGB 375
Combo Box 165
СОМ-объекты 758
СОМ-сервер 758
Constraints 128
continue 180
copy 189
Copy FromC 1 i pboard 503
Count 156, 233
CsDropDown 167
CsDropDownList 167
CsOwnerDrawFixed 167
CsOwne rD raw Variable 167
в
CsSimple 167
Ctl3D 129
Cursor 129
Begin Doc 415
BeginUpdate 327
CutFromCIipboard 503
Bevell nner 159
BevelOuter 159
Border Icons 125
BorderStyle 126
BorderWidth 127 break 180 Brush 357
D
DateToStr 237
DAX 766
DBGrid 436
D BLookupComboBox 454
DefaultColimnHeight 289
Default Col imn Width 289
Delete 155, 157, 190, 472
DESC 439
DirectX 581
DisplayOption 289
H
Height 132
Hint 132
HorzScrollBar 132
Dispose 247 DLL 577 DockSite 129 DragKind 130 DragMode 130
I
Icon 132 if 184 Include 261
E
IndexOf 157
InputQuery 317
Edit 472
Enabled 130
End Doc 416
Equals 157
Insert 157, 450, 472
IntToStr 85, 168, 176, 236
Invalidate 367
Itemindex 163
Events 136
Exchange 157
Exclude 261
Execute 382, 570
к
KeyOption 290
F
L
FileExists 153, 246, 313
FileName 305
FillRect 4B6
Filter 461
FindClose 251
FindNext 251
First 472
FixedColor 289
FixedCols 289
FloatToStr 239
Font 131, MB
Format Float 239
FormStyle 131
Last 472
Left 148
Length 97, 188
■ Lines 151
LineTo 360
Li st Box 162
ListView 837
LoadFromFile 153, 157, 233
M
MDIChildCount 212
M DI Children 212
G
M e rgeC h a nge Log 544
Message Box 456
GDI 354
Get 157
GetAliasNanies 541
GetBitmap 300
GetRVahie 376
GotoNearest 539
GU1D 758
Move 157
MoveTo 360
N
Name 133
Next 212, 472
о
ReadString 253
Reel 278, 379
OLE-объект 750, 751
OnChenge 165
OnClick 141, 144
OnMouseMove 144
OnPaint 355
OnShow 153, 246
OpenGL 581, 691
Open Key 252
Rectangle 355
Refresh 478
Repaint 361, 367, 376
repeat 179
Resume 572, 573
Rewrite 409 round 181, 239 runtime 827
or 185
p
s
SaveToFile 157, 235
PaintTo 415
Panel 159
ParentFont 133
PasswordChar 150
Pen 357
Pixel 378
Play 638
pointer 241
PopupActionBarEx 351
PopupMenu 351
Position 133, 167
Post 451, 472
Prev 472
Previous 212
Printers 406
Priority 573 procedure 104
Screen Saver 822
ScrolBar 167
Seek 229
Sender 142
set of 260
Set Focus 450
SetLength 97
SH Get File Info 323
SizeOf 694
SndPlaySouiid 632
SQL 465
Stop 639
Strings 154, 156, 233, 290
StrToDate 237
StrToFloat 239
StrTolnt 176, 236
StrTolntDef 236
Suspend 573
Q
QRE^r 527
Quick Reports 517
Quick Report 507
QuotedStr 462
Synchronize 571
T
TADOTable 437, 461
Tag 134
TAnimate 305
R
TBitBtn 265
TButton 139
TDrawGrid 273
RadioBox 159, 169
RadioButton 161 random 145 ReadBool 253 Read Integer 253
TEdit 149
Terminate 572
Text 157
TextHeight 379
TextWidth 379
Tile 212
TImage 382
Title Captions 289
TLabel 148
TListView 319
TMaskEdit 272
TMediaPlayer 635
TMemo 151
TMonthCalendar 308
Top 134, 148
TPageControl 298
TPrQgressBar 302
TQuickRep 518
Transparent 148
TransparentColor 134
TransparentColorVal ue 134
TRect 379, 381
TreeView 308, 314, 837
tru... except 192
TSpeed Button 265
TSplitter 288
TStaticText 289
TStatusBar 336
TStream 261
TStringGrid 273
TStrings 156
TTabControl 292
TThread 570
TTrackBar 300
TValueListEditor 289
u
U ndo LastChange 544
V
Value List Editor 291
VertScrollBar 134
Visible 134
w
WaveOntOpen 642
WaveOutPrepareHeader 644 wDay 333 wHour 333 Width 134
WindowState 134 wMilliseconds 333 wMinute 333 wMonth 333 Write 230 writeln 409 wSecond 333 wYear 333
X
XPManifest 351
Глобальные переменные 244дДинамические массивы 222Динамический запрос 4703Закладка 499Запись данных 668Глобальные переменные 244дДинамические массивы 222Динамический запрос 4703Закладка 499Запись данных 668
Глобальные переменные 244
д
Динамические массивы 222
Динамический запрос 470
3
Закладка 499
Запись данных 668
Глобальные переменные 244
д
Динамические массивы 222
Динамический запрос 470
3
Закладка 499
Запись данных 668
Б
Базы данных 424
Библиотека GLU 722
Блок-схема 26
в
Вещественные типы данных 91
Воспроизведение звукового файла 632
Вытесняющая многозадачность 567
Вычисляемое поле 527
TObject 612TPersistent 612TWinControl 613Оператор if 98Отладка программы 791пПалитра компонентов 137Перевод каретки 786Плеер 635Поток 566Процедура 59, 329рРазыменование 850Реляционная база данных 425, 465TObject 612TPersistent 612TWinControl 613Оператор if 98Отладка программы 791пПалитра компонентов 137Перевод каретки 786Плеер 635Поток 566Процедура 59, 329рРазыменование 850Реляционная база данных 425, 465
TObject 612
TPersistent 612
TWinControl 613
Оператор if 98
Отладка программы 791
п
Палитра компонентов 137
Перевод каретки 786
Плеер 635
Поток 566
Процедура 59, 329
р
Разыменование 850
Реляционная база данных 425, 465
TObject 612
TPersistent 612
TWinControl 613
Оператор if 98
Отладка программы 791
п
Палитра компонентов 137
Перевод каретки 786
Плеер 635
Поток 566
Процедура 59, 329
р
Разыменование 850
Реляционная база данных 425, 465
и
Индексы 426
К
Клиент 545
Клиент-серверная: архитектура 553 база данных 424, 545
Комментарии 82 Компоненты 67 Конец строки 786 Константы 102 Контейнер OLE 742
Л
Локальные переменные 244
м
Массив 99, 220
Метаданные 489
Методы 119
TMediaPlayer 640 полей 481
Многозадачность 566
Многомерные массивы 225
Многопоточность 262
Многоуровневая система 553
Множества 259
Мультимедиа 635
О
Объект 62
OLE 742
TButtonControl 613
TComponent 612
TcustomControl 613
T GraphicControl 613
Свойства TMediaPlayer 640
Сервер 545
Сетевая база данных 424
т
Таблица 425
Текстуры 726
Тип данных PChar 100
У
Указатели 241
Условный переход 58
Ф
Функция 61, 107, 329
ц
Целые типы данных 90
Цикл 30, 173
www.bhv.iuwww.bhv.iu
www.bhv.iu
www.bhv.iu
Книги издательства "БХВ-Петербург"в продаже:
Магазин "Новая техническая книга": СПб., Измайловский пр., д. 29, тел. (812) 251-41-10
Отдел оптовых поставок: e-mail: opt@bhv.spb.su
Внесерийные книги
Андрианов В., Соколов А. Автомобильные охранные системы. Справочное пособие
272 с.
Богданов-Катьков Н. Струйные принтеры для дома и офиса
224 с.
Бурлаков М. Macromedia Flash MX 2004: сборка видеоклипов
720 с.
Бутиков Е. Оптика: Учебное пособие для студентов физических специальностей вузов, 2-е изд.
480 с.
Быков А. и др. ADEM CAD/CAM/TDM. Черчение, модернизация, механообработка (+CD-ROM)
320 с.
ГасфилдД. Строки, деревья и последовательности в алгоритмах
654 с.
Гольдштейн Б. Стек протоколов ОКС7. Подсистема ISUP: Справочник
480 с.
Гольдштейн Б. Интерфейсы V5.1 и V5.2. Справочник
288 с.
Гольдштейн Б. Системы коммутации
318 с.
Гольдштейн Б. Call-центры и компьютерная телефония
372 с.
Гурова А. Герои меча и магии. По мотивам одноименной компьютерной игры
320 с.
Живайкин П. 600 звуковых и музыкальных программ
624 с.
Зыль С. Операционная система реального времени QNX: от теории к 叩актнке (+CD-ROM)
192 с.
Зыль С. Операционная система реального времени QNX: от теории к практике, 2-е изд. (+ прил. на CD)
192 с.
Идельсон А., Кохась К. Задачи Санкт-Петербургской олимпиады школьников по математике 2003 год
224 с.
Карпюк В. MS Windows ХР Professional. Опыт сдачи сертификационного экзамена 70-270
528 с.
Коновалов Д. Знакомый английский
64 с.
Корнеев В., Киселев А. Современные микропроцессоры, 3-е изд.
448 с.
Малыхина М. Базы данных: основы, проектирование, использование
512 с.
Мещеряков М. Linux: инсталляция и основы работы (+CD-ROM)
144 с.
Палмер М., Синклер Р. Б. Проектирование и внедрение компьютерныхсетей. Учебный курс. — 2-е изд.
240 с.
Петелин Р. Adobe Audition. Обработка звука для цифрового видео (+CD)
400 с.
Петров Ю. П. Новые главы теории управления и компьютерных вычислений
192 с.
Пирогов В. Ю. Ассемблер для Windows. — 2-е изд.
656 с.
Пирогов В. Ю. Ассемблер. Учебный курс. — 2-е изд., перераб. и доп.
1056 с.
Пог Д. MS Windows ХР Home Edition: недокументированные возможности
768 с.
Попов А. Администрирование Windows с помощью WMI и WMIC (+CD-ROM)
752 с.
Попов С. Аппаратные средства 1\лультимедиа. Видеосистема PC400с.
Правин О. Правильный самоучитель работы на компьютере, 2-е изд.496с.
Прохоров А. Интернет: как это работает280с.
Роб П. Системы баз данных: проектирование, реализация и управление, 5-е изд. 299 с.
Роб П. Системы баз данных: проектирование, разработка и использование1200 с.
Робачевский А. Операционная система UNIX528с.
Романовский И. Дискретный анализ, 3-е изд.320с.
Скляров Д. Искусство защиты и взлома информации288с.
Соколов А., Андрианов В. Альтернатива сотовой связи:448с.
транкинговые системы
Соломенчук В. Интернет: поиск работы, учеба, гранты288с.
Соломенчук В. Как сделать карьеру с помощью Интернета416с.
Соломенчук В., Соломенчук П. Железо ПК 2004368с.
Суворов К. А., Черемных М. Н. Справочник Delphi. Базовые классы576с.
Успенский И. Интернет как инструмент маркетинга256с.
Феличи Д. Типографика: шрифт, верстка, дизайн360с.
Фленов М. Библия Delphi (+CD)880с.
Фленов М. Программирование в Delphi глазами хакера (+CD-ROM)368с.
Фрей Д. AutoCAD и AutoCAD LT для начинающих680с.
Частиков А. Архитекторы компьютерного мира384с.
Чеппел Л., Титтел Э. TCP/IP. Учебный курс (+CD-ROM)976с.
Шарыгин М. Сканеры и цифровые камеры384с.
издательство koi ьютерной литературы "Бхв-петергург
ЗНАКОМЬТЕСЬ,ЗНАКОМЬТЕСЬ,НОВАЯ СЕРИЯНОВАЯ СЕРИЯСНСАПМИНСНСАПМИНСИСТЕМНЫЙАДМИНИСТРАТОРСИСТЕМНЫЙАДМИНИСТРАТОР
ЗНАКОМЬТЕСЬ,
ЗНАКОМЬТЕСЬ,
НОВАЯ СЕРИЯ
НОВАЯ СЕРИЯ
СНСАПМИН
СНСАПМИН
СИСТЕМНЫЙ
АДМИНИСТРАТОР
СИСТЕМНЫЙ
АДМИНИСТРАТОР
БХВ-Петер^ургЗНАКОМЬТЕСЬ, НОВАЯ СЕРИЯДля сютег/-ых ад\/ т страторов, ГГ-менад» : m инженеров.БХВ-Петер^ургЗНАКОМЬТЕСЬ, НОВАЯ СЕРИЯДля сютег/-ых ад\/ т страторов, ГГ-менад» : m инженеров.Книги этой серии помогут в совершенстве овладеть современными компьютерными технологиями, выбрать оптимальные и эффективные решения при проектировании и внедрении сетей, обеспечить их информационную безопасность и квалифицированное администрирование.Книги этой серии помогут в совершенстве овладеть современными компьютерными технологиями, выбрать оптимальные и эффективные решения при проектировании и внедрении сетей, обеспечить их информационную безопасность и квалифицированное администрирование.
БХВ-Петер^ург
ЗНАКОМЬТЕСЬ, НОВАЯ СЕРИЯ
Для сютег/-ых ад\/ т страторов, ГГ-менад» : m инженеров.
БХВ-Петер^ург
ЗНАКОМЬТЕСЬ, НОВАЯ СЕРИЯ
Для сютег/-ых ад\/ т страторов, ГГ-менад» : m инженеров.
Книги этой серии помогут в совершенстве овладеть современными компьютерными технологиями, выбрать оптимальные и эффективные решения при проектировании и внедрении сетей, обеспечить их информационную безопасность и квалифицированное администрирование.
Книги этой серии помогут в совершенстве овладеть современными компьютерными технологиями, выбрать оптимальные и эффективные решения при проектировании и внедрении сетей, обеспечить их информационную безопасность и квалифицированное администрирование.
Санкт-Петербург, 19СЮ05, Измайловский пр., 29тел.: +7(812) 251-4244; 251-6501e-mail: info@bhv. ruwww.bhv.ru
ЗНАКОМЬТЕСЬ, НОВАЯ РТМ СЕРИЯ ■ i J
ПРОФЕССИОНАЛЬНОЕ ПРОГРАММИРОВАНИЕ
валн/ыциро—, F】a6or#tE 府 ПО»
ЗНАКОМЬТЕСЬ
новая JTTT1
СЕРИЯ -汕
Санкт-Петербург, 19CXD05, Измайловский пр., 29 тел.: +7(812) 251-4244; 251-6501 e-mail: info@bhv.ru www.bhv.ru
(ниги этой серии помогут изучить временные технологии программирования I выбрать оптимальные и эффективные ешения при разработке программ.
J . ^iSMMIHfa
Магазин-сшюн
"НОВАЯ ТЕХНИЧЕСКАЯ КНИГА,
190005, Санкт-Петербург, Измайловский пр” 29
В магазине представлена литература покомпьютерным технологиямрадиотехнике и электроникефизике и математике
экономике
медицине
и др.
Низкие цены
Прямые поставки от издательствЕжедневное пополнение ассортимента
Подарки и скидки покупателям
Магазин работает с 10.00 до 20.00без обеденного перерывавыходной день - воскресенье
Тел.: (812)251-41*10, e-mail: trade@techkn$ga.com
БИБЛИЯ
Delphi
Вы хотите научитьэся быстро и качественно писать свои собственные программы? Вы уже начали изучать программирование, но многие вещи Вам еще непонятны или Вы хотите повысить свой уровень? Тогда эта книга для Вас. В ней Вы найдете полную информацию по программированию - от самых основ до примеров построения конкретных приложений. Прочитав ее, Вы научитесь создавать программы, которые помогут Вам в работе, учебе и повседневной жизни.
Электронная справочная информация и статьи, содержащиеся на компакт-диске к книге, послужат Вам дополнительным источником знаний в процессе дальнейшего обучения. Особого внимания заслуживают расположенные на CD изображения и компоненты, которые помогут сделать Ваши программы более элегантными и привлекательными.
Компакт-диск содержит исходные коды программ, дополнительную справочную информацию, offline-версию сайта автора (www.vt-online.ru), а также готовые изображения и компоненты.Компакт-диск содержит исходные коды программ, дополнительную справочную информацию, offline-версию сайта автора (www.vt-online.ru), а также готовые изображения и компоненты.ПРОГРАММИРОВАНИЕ МОЖЕТ БЫТЬ ДОСТУПНО КАЖДОМУ!
Компакт-диск содержит исходные коды программ, дополнительную справочную информацию, offline-версию сайта автора (www.vt-online.ru), а также готовые изображения и компоненты.
Компакт-диск содержит исходные коды программ, дополнительную справочную информацию, offline-версию сайта автора (www.vt-online.ru), а также готовые изображения и компоненты.
Фленов Михаил Евгеньевич, автор множества статей в журналах ("Chip-Россия", " Игромания", "Хакер") и книги "Программирование в Delphi глазами хакера", которая уже через неделю после выхода стала бестселлером и подаялась на первые строчки во всех рейтингах интернет-магазинов. В течение года в журнале "Хакер" автор вел разработанную им рубрику по программированию.
КАТЕГОРИЯПРОГРАММИРОВАНИЕ
УРОВЕНЬ ПОЛЬЗОВАТЕЛИНАЧАЛЬНЬМ^СРЕДНИЙ
ISBN 5-94157-456-8