Артем Зубов
трюки
эффекты
Артем Зубов
Москва • Санкт-Петербург • Нижний Новгород • Воронеж Новосибирск • Ростов-на-Дону • Екатеринбург • Самара Киев • Харьков • Минск
2005
ББК 32.973.23-018.2 УДК 681.3,06
391
Зубов А. 3-91 Программирование на Delphi. Трюки и эффекты (+CD). — СПб.: Питер, 2005. — 396 с.: ил. — (Серия «Трюки и эффекты»).
i ISBN 5-469-00434-1 «Программирование на Delphi», как и все издания отличной серии «Трюки и эффекты», адресована тем, кто хочет научиться делать с помощью уже знакомых программных пакетов новые, интересные вещи. В первой части книги много говорится о среде разработки Delphi (самых последних версий) и программировании на языке Object Pascal. Благодаря этому издание подходит и новичкам, и начинающим программистам. Вторая часть книги описывает удивительные возможности, скрытые в языке, и на примерах учит читателя программистским фокусам — от «мышек-невидимок» и «непослушных окон» до воспроизведения МРЗ и управления офисными программами Word и Excel из приложений Delphi.
ББК 32.973.23-018.2 УДК 681.3.06
Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав.
Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за возможные ошибки, связанные с использованием книги.
ISBN 5-469-00434-1
©ЗАО Издательский дом «Питер», 2005
Краткое оглавление Введение
9
Часть 1. Программирование в Delphi Глава 1. Общие сведения Глава 2. Интерфейс программы Глава 3. Структура программы Глава 4. Стандартные типы данных Глава 5. Операторы Глава 6. Процедуры и функции Глава 7. Свойства, методы и события Глава 8. Работа с файлами Глава 9. Техника программирования Глава 10. Компиляция и отладка программы Глава 11. Создание приложения
12 15 39 44 55 63 73 84 108 124 131
Часть 2. Трюки и эффекты Глава 12. Хитрости с мышью и клавиатурой Глава 13. Эффектные формы Глава 14. Проигрывание звуковых записей Глава 15. Трюки с Word и Excel Глава 16. Программирование для Интернета Глава 17. Вывод графики с помощью GUI Глава 18. Использование DirectX Глава 19. Реестр Windows Глава 20. Локальные сети Глава 21. Полезные и интересные программы Глава 22. FAQ и сайты о Delphi Заключение Приложение. Описание компакт-диска
174 190 208 233 242 258 294 333 349 361 386 392 393
Оглавление Введение От издательства
9 10
Часть 1. Программирование в Delphi Глава 1. Общие сведения
12
Глава 2. Интерфейс программы
15
2.1. Среда разработки 2.2. Создание приложений 2.3. Обзор компонентов
16 28 34
Глава 3. Структура программы
39
3.1. Файлы, используемые в Delphi
40
3.2. Структура PAS-файла 3.3. Требования к тексту программы
41 42
Глава 4. Стандартные типы данных
44
4.1. Простые типы 4.2. Строковые типы 4.3. Тип Variant 4.4. Объявление переменных 4.5. Определение своих типов данных 4.6. Константы
45 47 48 48 50 53
Глава 5. Операторы
55
5.1. Оператор присваивания 5.2. Операторы ввода-вывода 5.3. Оператор безусловного перехода 5.4. Операторы условного перехода
56 56 57 57
Оглавление 5.6. Циклы 5.7. Операции с числами 5.8. Основные математические функции
59 60 61
Глава 6. Процедуры и функции
63
6.1. Процедуры 6.2. Функции 6.3. Процедуры, возвращающие значение 6.4. Забавный эксперименте функцией 6.5. Перегружаемые процедуры и функции 6.6. Дополнительные сведения о процедурах и функциях
64 66 66 69 69 71
Глава 7. Свойства, методы и события
73
7.1. Свойства 7.2. Методы 7.3. События
74 81 82
Глава 8. Работа с файлами
:
84
8.1. Текстовые файлы 8.2. Типизированные файлы 8.3. Нетипизированные файлы 8.4. Удаление записей 8.5. Обработка ошибок 8.6. Процедуры и функции для работы с файлами
86 90 101 102 104 106
Глава 9. Техника программирования
108
9.1. Оптимизация программы 9.2. Комментарии 9.3. Точный подбор цвета
109 118 119
Глава 10. Компиляция и отладка программы
124
10.1. Компиляция 10.2. Отладка
125 126
Глава 11. Создание приложения
131
11.1. Компоненты и их свойства 11.2. Остроумные фразы
132 134
Оглавление 11.3. Базы с сообщениями
141
11.4. Программирование интерфейса
143
11.5. Загрузка данных
144
11.6. Интеллект программы
147
11.7. Добавление общих фраз
150
11.8. Встраивание в программу возможности сообщать о текущем времени
154
11.9. Коллекция вопросов и ответов программы
156
11.10. Полный листинг программы
161 •
,
Часть 2. Трюки и эффекты Глава 12. Хитрости с мышью и клавиатурой
174
12.1. Указатель мыши: найти и перепрятать
175
12.2. Клавиши Ctrl, Alt, Shift и кнопки мыши
182
12.3. Определяем код клавиши
184
12.4. Быстрый опрос клавиатуры
186
Глава 13. Эффектные формы
190
13.1. Формы необычного вида
191
13.2. Кнопки необычного вида
195
13.3. Способы перемещения окон
196
13.4. Прозрачные окна
197
13.5. Программы-«невидимки»
201
Глава 14. Проигрывание звуковых записей
208
14.1. Как «поселить» звук в программе
209
14.2. Проигрывание музыки вформате МРЗ
220
14.3. Видеопроигрыватель и видеокнопка
229
Глава 15. Трюки с Word и Excel
233
15.1. Технологии СОМ и OLE
234
15.2. Управление Word и Excel из другой программы
236
Оглавление Глава 16. Программирование для Интернета
242
16.1. Разрабатываем браузер
243
16.2. Трюки с браузером Internet Explorer
248
16.3. Письмецо в конверте
254
Глава 17. Вывод графики с помощью GUI
258
17.1. GUI и Canvas
259
17.2. Выбор технологии для экранных трюков
261
17.3. Вывод текста на экран
262
17.4. Загрузка изображений в ImageList
266
17.5. Эффектно выводим графику на экран
271
17.6. Рисуем на Рабочем столе
290
Глава 18. Использование DirectX
294
18.1. Что такое DirectX
295
18.2. Библиотека DelphiX
296
18.3. Компонент TDXDraw
299
18.4. Очень быстрый вывод графики на экран
301
18.5. Часы
312
18.6. Палитра
319
18.7. Программы-«заготовки»
324
18.8. Работа со звуком
:
328
Глава 19. Реестр Windows
333
19.1. Что такое реестр
334
19.2. Реестр в подробностях
337
19.3. Запись в реестр и чтение из него
337
19.4. Автоматический запуск программы
344
Глава 20. Локальные сети
349
20.1. Разновидности сетей
350
20.2. Программы для передачи сообщений по локальной сети
352
20.3. Управление другим компьютером посети
359
Оглавление Глава 21. Полезные и интересные программы
361
21.1. Работа с изображениями в формате JPEG 21.2. Хранение изображений в ЕХЕ-файле 21.3. Запуск других приложений при помощи своей программы 21.4. Эффекты с кнопкой Пуск и Панелью задач 21.5. Постоянно открывающийся лоток привода компакт-дисков 21.6. Появляющийся время от времени «Блокнот» 21.7. «Подмигивающий» автомобиль 21.8. Разрешение экрана 320x200 пикселов в Windows
362 363 365 368 369 374 377 383
Глава 22. FAQ и сайты о Delphi
386
22.1. FAQ, илиЧаВо 22.2. Сайты о Delphi
387 390
Заключение
392
Приложение. Описание компакт-диска
393
Введение Если вы ищете серьезную книгу, например о работе с базами данных, то данное издание вам вряд ли подойдет. Здесь речь идет о таких вещах, в полезности которых можно усомниться. Книга рассчитана на читателей, немного знакомых с программированием, но, впрочем, необязательно хорошо умеющих программировать. Для понимания текста достаточно уметь написать на языке Basic программу, вычисляющую, сколько будет два плюс два. Конечно, это только нижняя планка! Более опытные пользователи тоже смогут почерпнуть для себя что-то полезное. В некоторой степени именно поэтому книга разделена на две части. В первой части описано программирование на Delphi. Те читатели, которые умеют программировать на Delphi, сразу могут переходить ко второй части — непосредственно трюкам и эффектам. Теперь об обеих частях книги более подробно. Из первой можно получить общие сведения о Delphi, описание интерфейса (включая подробный обзор среды разработки и меню), а также о типах данных, операторах, процедурах и функциях. Всевозможные справочные сведения тоже собраны в первой части (так что даже опытные пользователи могут обращаться к ней, чтобы посмотреть, как вызывается функция, которой они давно не пользовались). В конце первой части приведен пример написания нормально работающего приложения Windows. Скучновато выглядит первая часть, не так ли? Но зато во второй собраны более веселые вещи: перемещающиеся кнопки, непослушные указатели мыши, звуковые эффекты, «дрессированные» Word и Excel, вывод графики (в том числе с помощью DirectX!) и прочие трюки и эффекты. К книге прилагается компакт-диск, на котором собраны все опубликованные в ней примеры. Описание компакт-диска приведено в приложении. Думаю, что если вы дошли до этих слов, то настало время переворачивать страницу и читать дальше!
От издательства Ваши замечания, предложения, вопросы отправляйте по адресу электронной почты
[email protected] (издательство «Питер», компьютерная редакция). Мы будем рады узнать ваше мнение! На web-сайте издательства http://www.piter.com вы найдете подробную информацию о наших книгах.
Часть 1 Программирование в Delphi Глава 1. Общие сведения Глава 2. Интерфейс программы Глава 3. Структура программы Глава 4. Стандартные типы данных Глава 5. Операторы Глава 6. Процедуры и функции Глава 7. Свойства, методы и события Глава 8. Работа с файлами Глава 9. Техника программирования Глава 10. Компиляция и отладка программы Глава 11. Создание приложения
12 15 39 44 55 63 73 84 108 124 131
Глава 1
Общие сведения
Глава 1. Общие сведения Наверное, стоит вкратце рассказать о Delphi — просто чтобы знать, что это вообще за язык и откуда он взялся. Конечно, данная книга — исключительно о трюках и эффектах; но ведь любая вещь выйдет лучше, если хорошо знать инструмент, с которым работаешь. А система программирования — это именно инструмент. Delphi — это не что иное, как Object Pascal. Язык Pascal достиг своей вершины в начале 1990-х годов, во времена распространения его седьмой версии. Как раз тогда началась эра визуального программирования и создания программ для появившейся и быстро распространяющейся системы Windows. Поэтому после седьмой версии языка программистам представили не восьмую версию Pascal, а первую Delphi. Проект с таким названием появился в середине 1993 года, а в 1995 году в продажу поступила первая версия Borland Delphi. . Интересна история появления именно такого названия проекта. Почему именно Delphi? Все просто: «Если вы хотите разговаривать с Оракулом, идите в Дельфы». Это, конечно, намек на систему доступа к базам данных, называемую Oracle, и город Дельфы в Греции, где и находился мифологический Оракул. Почему для создания эффектных программ очень хорошо подходит Delphi? Сравним Delphi с другими распространенными системами программирования — Visual Basic и C++. Если сравнивать Delphi, например, с C++, то преимущество первого из языков будет заключаться в том, что программировать на нем можно гораздо проще и быстрее. Зачем выполнять лишние действия и писать сложные программы, если нужно всего лишь как можно быстрее создать что-то впечатляющее? При сравнении же Delhpi с Visual Basic оказывается, что рассматриваемый в данной книге язык имеет перед ним куда более серьезное преимущество. Дело в том, что программы (исполняемые ЕХЕ-файлы), написанные на Visual Basic, получаются сами по себе небольшого размера, но для работы требуют дополнительных файлов (библиотек DLL). Поэтому даже простая программа — при условии, что ее можно будет запускать на любом компьютере, — займет около 2 Мбайт. При создании готовых программ на Delphi в ЕХЕ-файл включается уже все необходимое и внешние файлы не требуются. Поэтому размер ЕХЕ-файла обычно бывает больше созданного в Visual Basic (от 200 Кбайт), но будет работать сам по себе, если его запустить на любом другом компьютере. Для эффектов это как раз то, что нужно! Вышедшая в 1995 году Borland Delphi 1.0 стала просто революцией после седьмой версии Pascal. Но если сравнивать с нынешними версиями Delphi, то возможности Delphi 1 на сегодняшний день очень и очень скромные. Компания Borland (а позже Inprise, после чего снова Borland) постоянно выпускала новые версии Delphi, примерно по одной в год. С появлением новой тогда операционной системы Windows 95 (32-разрядной (!), в отличие от Windows 3.1) была выпущена Delphi 2, в которой были учтены возможности и стандарты новой операционной системы. Основными отличиями первой версии Delphi от второй являются поддержка 32-разрядности и новые компоненты, выполненные в стиле Windows 95. По сути,
Глава 1. Общие сведения Delphi 2 и сейчас может успешно применяться для разработки приложений, если не использовать многие современные технологии и компоненты (например Интернет, СОМ). Однако если программист хочет использовать все современные технологии, новые компоненты, более удобную среду разработки приложений, то ему приходится идти в ногу со временем и работать с последними версиями Delphi. Если пользоваться различными дополнительными компонентами, в том числе бесплатными и свободно распространяемыми (например, скачивая их из Интернета), то минимально необходимой версией стоит считать Delphi 3. Для нее в Интернете еще можно найти компоненты, а вот для Delphi 2 я не видел. В 1997 году как раз и была выпущена третья версия Delphi. Delphi 3 была ориентирована на бурно развивающуюся тогда сеть Интернет и на клиент-серверные технологии. Кроме значительно расширенного инструментария для разработки интернет-приложений, упростилось создание управляющих элементов ActiveX. Кроме того, в Delphi 3 появились новые драйверы баз данных и SQL. В Delphi 4 были введены динамические массивы, более удобная среда разработки, улучшенная отладка программ, новые компоненты и поддержка CORBA. Delphi 5 в 2000 году порадовала улучшенной средой разработки, поддержкой баз данных и многоязыковой поддержкой. Появилась система Internet Express для создания HTML-страниц. В целом Delphi 4 и 5 были не столь революционными, как три предыдущие версии Delphi. Delphi б, увидевшая свет в 2001 году, принесла с собой опять же множество новых компонентов, еще теснее интегрируясь с Интернетом. Delphi 7 порадовала разработчиков программного обеспечения небольшими улучшениями среды разработки. Более важно то, что в седьмой версии Delphi появилась полная поддержка тем Windows XP. Как в Delphi 2 были добавлены новые компоненты в стиле Windows 95, точно так же спустя шесть лет новые компоненты позволили создавать приложения в стиле новой операционной системы. И, наконец, последняя изданный момент версия Delphi — восьмая. Borland Delphi 8 предназначена для создания приложений, которые будут использоваться на платформе .NET Framework. Эта платформа предназначена для создания, развертывания и запуска web-сервисов и приложений. Конечно, не обошлось и без других нововведений. Например, среда разработки изменилась и выглядит аналогично другим продуктам Borland. Вот так до нынешнего времени развивалась очень популярная в России (и не только в России) система программирования.
Глава 2
Интерфейс программы Q Среда разработки Q Создание приложений Q Обзор компонентов
Глава 2. Интерфейс программы
2.1. Среда разработки Здесь описана среда разработки Delphi 7, потому что в последнее время я использую именно эту версию. Вообще интерфейсы всех версий Delphi очень похожи, и различия между ними минимальны. \ \ Конечно, если сравнивать Delphi 3 и 8, то на первый взгляд кажется, что разница есть. Однако если присмотреться, то можно заметить, что кнопки выглядят немного по-другому, расположены чуть иначе, но в принципе все точно так же. Интерфейс Delphi с самого начала был хорошо продуман и удобен, привыкнуть к нему можно очень быстро. Когда я начинал программировать на Delphi, то среда разработки этой системы стала мне родной почти сразу. К сожалению, сейчас я уже не помню, за какое время привык к ней, но точно за очень короткое. Привыкнуть к Photoshop было сложнее, а от Проводника Windows я вообще не в восторге, даже спустя многие годы работы на компьютере. Ну а после первой версии Delphi внесено много изменений, отточивших и без того хороший интерфейс (рис. 2.1). ' Project! File
Ed*
Search View Project
Run Component Database
Tools Window Help j j [my
3 I
f j U S * e i O ; S l i i s ^ 4 H ' Standard j Additional j Win32 j Si>stem| DataAccessj Data Controls i dbExoressj DalaSnaoi BDE
iShffiiaH !>- III i V ; H ^ ЭВ" *fe А ГЯГ §] CssJ 'ft ft p Я «™ Q. .ill D JS • s iseView -'
[7J Fmml ;5 Umll pas
Unitl
:
: ; ; : .. 4» , <ф
:
[unit U n i t l ; :interface JFotm!
IfV:
;'. Properties | Events) Г''Не1дп|""7""'Лз^5" I 'HejpConteKt 16 ' ' Г'нЫрНв'' ' . :| НфТуре j'.' Hint •EIHQfzScrollBar i; Icon • | KeyPreview ;f"Uelt
jhtConteirt liTConlroiS Н^9П?. I False il30"
[uses ': Windows, Messages, SysUtils, Variants, Classes, Graphics, С : Dialogs;
TForrnl = class (TForm) private { Private declarations } public { PwbJic declarations } end;
Name IForml I ObjectMeriulfet l3:24
AH shown
а ВУ .Й» j j aiBODK_FULL
iMotfM
;\Сос!еДР^адгат/
| gyMiao«ftWa..|[|>Delphi?
lAdobe Photo...
22:20
Рис. 2.1. Среда разработки Delphi
Среда разработки Delphi гибко настраивается программистом под себя. Все панели могут быть перемещены в любое место экрана, для этого достаточно перетащить их мышью. Но большинство разработчиков пользуются такой конфигурацией ере-
Среда разработки ды разработки, которую предлагает Borland, разве что лишь немного модифицируя ее. .
В самом верху экрана под заголовком окна Delphi находится главное меню. Все действия можно выполнять с его помощью. Кнопки, которые расположены чуть ниже, лишь дублируют основные и часто используемые функции ради удобства. Справа от кнопок быстрого вызова видна палитра компонентов (рис. 2.2). «.Delphi? .Projectl Ed* Search yiew
Project
Run Component Eatabase Tools Wjndow Help ] I (my
J | Sgj dU
Standard j Additional j Win32 j Sreleml Data Access) Data Controls) dbExoisss! DataSnaoj BDE
_ ,_ ,
;j
,
„ , . . : j ^ О atf % А П5Г g -Э l>? «• JUTS! "^ П Oil ! ; J9
Рис. 2.2. Главное меню, кнопки быстрого вызова и палитра компонентов
Главное меню Рассмотрим по порядку все 11 пунктов главного меню Delphi. •
File. Его команды следующие: • New — с помощью этого пункта можно создавать новый проект, вставлять новую форму в существующий проект, а также добавлять другие вещи (их очень много, не буду подробно останавливаться на них); • Open (рис. 2.3) — позволяет открывать любые файлы, которые поддерживаются Delphi. Естественно, чаще всего эта команда используется для открытия существующих проектов. Хотя мне кажется, что удобнее просто щелкнуть на файле с расширением DPR (файл проекта Delphi), и тогда Delphi автоматически загрузится, попутно открыв этот проект; Папка: |€S Projects
_»] jg] jgj
!й1Ищ!
HBpl
Имя Файла:
["
1ип Файлов: | Delphi file |'.pas:".bpg;".dpr:'.dpk;"dpkw) _^j
[^QrKpbffb J: Отмена
j
^правка 1 1
' — }А
Рис. 2.З. Окно открытия файлов
Open Project — пункт меню, почти такой же, как Open, только позволяет открывать не формы, PAS-файлы и т. д., а конкретно Delphi-проекты. Дублируется сочетанием клавиш Ctrl+Fll;
Глава 2. Интерфейс программы •
Reopen — быстро открывает один из проектов, с которым недавно работали, без утомительного поиска вручную в глубинах директорий;
•
Save (Ctrl+S) — сохраняет редактируемый файл. Как правило, если вы редактируете в данный момент PAS-файл, относящийся к форме, то будет сохранен только он;
•
Save As — сохраняет редактируемый файл, но при этом его можно сохранить под другим именем;
•
Save Project As — сохраняет весь проект под новым именем или в другую директорию;
•
Save All (Shift+Ctrl+S) — сохраняет все файлы проекта;
•
Close — закрывает редактируемый файл;
•
Close All — закрывает проект. Можно использовать эту команду, чтобы перейти к редактированию другого проекта без перезагрузки Delphi. Сначала закройте один проект, затем откройте другой;
•
Use Unit (Alt+Fll) — позволяет перейти к использованию нужного модуля. Если файл был закрыт, то по этой команде можно вернуться к нему;
•
Print — легко догадаться, что это печать на принтере;
•
Exit — закрытие Delphi.
Edit. Его команды: •
•
Undo (Ctrl+Z) — позволяет отменить последнее действие, выполненное в редакторе кода. Если вы переместили компонент, то эта команда его назад не вернет. Поддерживается только «откат» набранного текста в редакторе кода. Причем если вы сохранили файл, то «откат» становится невозможен; Redo (Shift+Ctrl+Z) — повторить отмененное действие;
•
Cut (Ctrl+X) — вырезать в буфер обмена выделенный объект или текст;
•
Copy (Ctrl+C) — скопировать в буфер выделенный объект или текст;
•
Paste (Ctrl+V) — вставить из буфера объект или текст, который там находится (если находится, конечно);
•
Delete (Ctrl+Delete или просто Delete) — удалить выделенный объект или текст;
•
Select All (Ctrl+A) — выделить весь текст;
•
Align To Grid — весьма полезная команда, позволяющая автоматически выровнять выделенный компонент по сетке. Если вы нечаянно сдвинули его, то после этой команды объект будет выровнен по сетке;
•
Bring To Front — если один компонент закрывает другой (обычно более новый компонент перекрывает собой добавленный на форму ранее), то этой командой можно вывести закрываемый компонент на передний план;
•
Send To Back — команда, обратная Bring To Front. Отправляет компонент на задний план;
Среда разработки •
Align — перемещает выделенный компонент в нужное место формы;
•
Size — дает возможность изменить размеры выделенного компонента;
•
Scale — позволяет изменить размер компонентов на нужное количество процентов;
•
Tab Order — редактирование последовательности обхода компонентов с помощью клавиши Tab, при нажатии которой фокус переходит с одного компонента на другой;.
•
Lock Controls — блокирует компоненты, после чего невозможно изменить их размеры и расположение. Эта команда призвана защитить расположение компонентов, которого вы добились с большим трудом, от нечаянного изменения размеров или расположения. Повторный вызов этой команды разблокирует компоненты.
Search. Его команды: •
Find (Ctrl+F) — поиск фрагмента текста в редакторе кода (рис. 2.4);
Find
JFhd in Files j
lent to find :• 0 DtionS
1 i
'
•• • •
:
pffi ~™T
• '
•
| -"Direction
•"-
~~
;;
1 <•" Forwarjj
£ase sensitive
! Г" ^{hoto words only
| j Г gackwaid
l Г" fiegutar expressbns Г Scope
1
--
;
1 r-0.igin | I ^ Etom cursor
**" fitob^
| ^ Detected text
I ^* £ntke scope
: OK
Cancel
|
::
Help
:
Рис. 2.4. Окно поиска текста
•
Find In Files — поиск в файлах;
•
Replace (Ctrl+R) — поиск и замена текста;
•
Search Again (F3) — искать текст дальше;
•
Go To Line N u m b e r (Alt+G) — перейти к строке с нужным номером.
View. С помощью данного пункта меню можно настраивать вид экрана, а также включать отображение окон и файлов проекта: • •
Project Manager (Ctrl+Alt+Fll) — открывает одноименное окно; Object Inspector — открытие окна Object Inspector или переход к нему, если окно скрыто;
•
Object TreeView (Shift+Alt+Fll) — переход к одноименному окну;
•
Browser (Shift+Ctrl+B) — показывает иерархию классов (рис. 2.5);
Глава 2. Интерфейс программы
Globals
Classes
Units
j $t llnterface
I Unknown TObject Exception TAggregatedObject TBasicActionLink
Scope I Inheritance j References j <4j Querylnteiface 4.) _AddRef <*.i „Release
твь
TButtonGlyph TChangeLink TCIassFinder t>' , TCIipboardForrnats
TConversion *,] , TCustomBucketList «ц , TCuslomlniFile TCustomVatiantType TDockZone TDragObject •«I TFiler TGIyphCache THelpViewerNode TlntConst TlnterfacedObject Т Languages TLis( Т Monitor
1L Рис. 2.5. Иерархия классов
• Code Explorer — переход к окну редактора текста программы; • Component List — окно со списком всех компонентов; • Windows List — окно со списком всех окон; •
Debug Windows— окна, с помощью которых проще отлаживать программы;
• Desktops — можно сохранить настройки интерфейса и потом вернуться к ним; • Toggle Form/Unit (F12) — переключение с окна формы на окно редактора текста программы; • Units (Ctrl+F12) — список файлов проекта. С помощью этого окна можно перейти к любому из них; •
Forms (Shift+F12) — список форм проекта, можно перейти к любой из них;
• Toolbars — включение и выключение отображения панелей на экране. Project. В этом меню собраны команды для работы с проектом: • •
Add To Project (Shift+Fll) — добавление файла в проект. С помощью этой команды можно вставить в проект, например, форму; Remove From Project — удаление файла из проекта;
• Add New Project — добавление нового проекта;
Среда разработки Add Existing Project — добавление существующего проекта (открытие); Compile (Ctrl+F9) — компиляция проекта. В результате создается исполняемый ЕХЕ-файл; Build All Projects — компиляция всех проектов; Options (Shift+Ctrl+Fll) — параметры проекта (рис. 2.6). Pioiect Uplions (or Pieject1.exe Directories/Conditionals Forms j Application
I Version Info Packages Compiler Compiler Messages 'Linker
1
i" Code generation
; г Runtime errors
\ p Optimization i Г" Slack, flames j Г Pentium-safe FDW i jg v| Record field alignment !
,
i
-
[Syntax options
i i j "* fiange checking . . : j i 7 1/0. checking ! ' i J ~ 2verf(ow checking i ... Debugging i | i У Qebug information | ! f7 iocal symbols
ip- Strict var-strings \ Г Eompiel. boolean eval: it" lyped @ operator i p Open earamelers : . i f? Hugeitfings 1 Г~ Assignable typed constants Г|а(аи§
| |7 Reference Wo i Ip^*^'"*
1
:
OK
Cancel
j
Help
Рис. 2.6. Окно параметров проекта, вкладка Compiler
Run. Запуск и отладка программы: • R u n (F9) — запуск программы на выполнение. Если были изменения в'программе после последней компиляции, то проект сначала компилируется, а потом запускается. Если после последней компиляции проекта изменений не было, то запускается уже готовый скомпилированный исполняемый файл; • Attach To Process — позволяет подключиться к выполняемому процессу и отладить его. После выбора этой команды открывается список процессов в памяти; •
Register ActiveX Server — регистрирует ActiveX-сервер в операционной системе. О том, что такое ActiveX (COM), написано в гл. 15;
•
Unregister ActiveX Server— удаляет регистрацию ActiveX-сервера из операционной системы;
•
Step Over (F8) — построчное выполнение программы. Если встречается процедура или функция, то ее построчное выполнение не производится;
• Trace Into (F7) — также построчное выполнение программы. Только если встречается процедура или функция, происходит переход в нее и продолжается построчное выполнение; •
Run To Cursor (F4) — запуск программы на выполнение и остановка на той строке, где установлен текстовый курсор. После чего можно выполнять
Глава 2. Интерфейс программы программу построчно (F7 либо F8) или же перейти к какой-нибудь другой строке. В режимах Step Over, Trace Into и R u n To Cursor можно расположить указатель мыши над любой переменной и просматривать ее значение в конкретный момент времени; •
Program Pause — пауза в выполнении программы;
•
Program Reset (Ctrl+F2) — немедленное прекращение выполнения программы и возврат к ее редактированию в Delphi;
•
Add Watch (Ctrl+F5) — открыть окно Watch List для наблюдения за переменными при выполнении программы. Используется для отладки;
• Add Breakpoint — добавить точку останова. Выполнение программы остановится на строке с такой точкой. Точку останова можно также добавить/убрать, щелкнув мышью на левом поле в редакторе текста программы. Component Его команды: •
New Component — создание нового компонента;
•
Install Component — установка компонента;
•
Import ActiveX Control — импортирование ActiveX-компонента;
•
Install Packages — установка пакета компонентов;
•
Configure Palette — настройка палитры компонентов.
Database. Команды для работы с базами данных:
,
•
Explore — открывает окно SQL Explorer;
•
SQL Monitor — открывает одноименное окно;
•
Form Wizard — мастер создания формы для получения скелета приложения, работающего с базами данных.
Tools. Его команды: •
E n v i r o n m e n t Options — открывает одноименное окно, с помощью которого можно еще более точно настроить среду разработки Delphi на свой вкус;
•
Editor Options — открывает окно с параметрами редактора текста программы. Я обычно после установки Delphi сразу же изменяю здесь один параметр: Tab Stops на вкладке Source Options. По умолчанию его значение равно восьми. Это значит, что если при редактировании текста программы нажать клавишу Tab, то курсор сразу переместится вправо на восемь символов, что очень много. Лучше задать значение равным трем или даже двум. По крайней мере мне удобно так. Попробуйте и настройте, как нужно вам;
• •
Debugger Options — параметры отладчика; Translation Tools Options — параметры мастера создания многоязыковых программ;
Среда разработки Database Desktop — открывает одноименное приложение. Это нужно для создания приложений, работающих с базами данных; Image Editor — открывает одноименное приложение (рис. 2.7), с помощью которого можно создавать значки для своих программ. Очень полезная программа. file Window Help:
сщ
j 32 к 32 16 colors
3
New...
//
r—r—»••••»*••
I I mrmmrmmum
X-009:Y:003
Рис. 2.7. Приложение Image Editor для создания значков
Window. С помощью данного пункта меню можно выводить на экран то или иное ранее открытое окно. Help. Здесь собрано все, что касается очень мощной и удобной системы помощи Delphi. В ней можно найти ответы если не на все, то почти на все вопросы, возникающие при написании программы. • Delphi Help — вызов системы помощи. При этом в открывшемся окне находятся три вкладки: Содержание, Указатель и Поиск (рис. 2.8). В особо сложных случаях, когда найти нужное слово не удается, можно пользоваться вкладкой Поиск. Иногда можно найти необходимое и на вкладке Содержание. Однако чаще всего, думаю, используется Указатель. Нужно ввести несколько начальных букв искомого слова, и если оно имеется в указателе, то можно вывести подробное описание на английском языке. Если у вас сложности с ним, то установите специальные программы-переводчики и словари. Они действительно могут помочь, и уровень программирования сразу повысится. Найти в системе помощи нужное слово можно и сразу из редактора текста программы. Для этого установите курсор в искомое слово и нажмите F1; • Delphi Tools — то же самое, только окно помощи сразу открывается на вкладке Содержание. Кроме того, с помощью меню Help можно перейти на сайты Borland и Delphi, а также посмотреть информацию о версии Delphi (пункт About).
Глава 2. Интерфейс программы Справочная система: Delphi Help Содержание Указатель j Поиск | : 1 Введите первые буквы нужного слова JT Component 2 Выберите термин или Фразу и нажмите кнопку ''Показать", lask lisl TBcd TBYTE type (assemble!) TByleAitay TCanvas.overview TChartFX Т Class Т Component branch, overview TControl (CIX). Help processing TControl [V'CL), Help processing TControl branch, overview ТСРЛР TCP/IP protocol TCP/IP, distributing applications multi-tiered appliclions TCustomVariantType, using
Рис. 2.8. Справочная система Delphi
Панели инструментов Сразу под меню располагаются панели с кнопками (см. рис. 2.2). Сюда вынесены наиболее часто используемые команды. Какую функцию выполняет каждая кнопка, легко запомнить уже через несколько часов, проведенных в среде Delphi. Но если вы забыли, то достаточно подвести указатель мыши к кнопке, и всплывающая подсказка сообщит о назначении кнопки. На всякий случай опишу, зачем нужна та или иная кнопка (применительно к интерфейсу Delphi 7). Первая панель (слева направо): • New Items — новый файл (проект, форма, компонент и т. д.); • Open — открыть файл. Если нажать на маленькую стрелку рядом с данной кнопкой, то можно перейти к одному из недавно открывавшихся файлов; • Save — сохранить файл; • Save All — сохранить все; • Open Project — открыть проект; • Add file to project — добавить внешний файл в проект; • Remove file from project — удалить файл из проекта. Вторая панель: Help contents — помощь. Третья панель: • View Unit — просмотреть список файлов проекта; • View Form — просмотреть список форм проекта;
Среда разработки • Toggle Form/Unit — переключение с окна формы на окно редактирования текста программы и обратно; • New Form — создание новой формы и автоматическое добавление ее в проект. Четвертая панель: • R u n — запуск программы. Если программа не скомпилирована, то происходит компиляция и только потом запуск; • Pause — пауза в выполнении запущенной программы; • Trace into — построчное выполнение программы, включая процедуры и функции, встретившиеся на пути процессора; • Step over — построчное выполнение программы, исключая процедуры и функции.
Палитра компонентов Палитра компонентов расположена справа от кнопок быстрого вызова и чуть ниже главного меню Delphi (см. рис. 2.2). На этой палитре расположены все компоненты, которые установлены в среде разработки и применяются при создании программ. Чтобы компоненты было проще находить, они сгруппированы на нескольких десятках вкладок. Чаще всего используются вкладки Standard, Additional, Win32, System и Dialogs. Расположенные на них компоненты применяются практически в каждой программе, остальные же компоненты более узкоспециализированные, но это не уменьшает их ценность. Подробнее о компонентах написано в разд. 2.3.
Окно Object TreeView Чуть ниже панелей инструментов в левой части экрана располагается окно Object TreeView (рис. 2.9). В нем четко видна структура приложения — какие формы и компоненты входят в разрабатываемое приложение и где располагаются. Плюс ко всему, с помощью этого окна можно активизировать любые компоненты, даже если они скрыты под другими компонентами или находятся за пределами формы. Иногда это очень удобно. Для активизации компонента достаточно щелкнуть мышкой на его названии в окне Object TreeView.
Рис. 2.9. Окно Object TreeView
Глава 2. Интерфейс программы
Окно Object Inspector Ниже окна Object TreeView находится Object Inspector (рис. 2.10). В отличие от некоторых других окон, это практически незаменимо. Именно с его помощью изменяются свойства всех компонентов в процессе визуального программирования. Кроме того, данное окно позволяет назначать событиям процедуры обработки.
jForm1
THvr
Properties ] Events |
^н^йГ™ ~~Тзт!Г HeipContext
]6
I HelpKeyword i ! HeipType JKContext iipHorzScroiiBet | (fConlrolSci n i Icon i (№ ?) •: KeyPreyiew I False i'TetTI . 11ЭО Z I Menu I Name iForml | '"ObieetMenulteil jAI'tKOWn -''Г:..
. . : ' . . . - " : " 1 - ';
Рис. 2.10. Окно Object Inspector
В окне Object Inspector есть две вкладки (Properties и Events) и раскрывающийся список, в котором можно выбрать нужный компонент. Хотя с тем же успехом его можно выбрать мышью в окне формы или в окне Object TreeView. После чего можно изменять свойства компонента. Если активна вкладка Properties, то слева находятся свойства, а справа — их значения. Чтобы изменить значение, нужно просто щелкнуть на нем мышью и набрать с клавиатуры нужное (или же выбрать мышью, если свойство предполагает выбор значений из списка). Обработчик события назначается очень просто. Делается это следующим образом. Перейдите на вкладку Events и, выбрав нужное событие, дважды щелкните мышью на пустом поле справа в этой же строке. Например, если вам нужно написать код, который будет выполняться при нажатии кнопки, то нужно активизировать эту кнопку, перейти на вкладку Events в окне Object Inspector и дважды щелкнуть напротив надписи OnClick. Откроется окно редактирования кода с уже нужной вам процедурой, в которую можно вписывать код. \
Окно редактирования вида формы Так как Delphi является системой визуального программирования, то вид окна программы можно настраивать, не представляя его в голове, а получая результат сразу же (рис. 2.11). Когда вы перемещаете компоненты с палитры компонентов в окно формы, то сразу видите, как они будут выглядеть в процессе выполнения программы.
Среда разработки
Рис. 2.11. Окно редактирования вида формы (неотъемлемая часть визуального программирования)
Окно редактирования текста программы Именно в этом окне редактируется текст программы (рис. 2.12). Окна редактирования формы и текста программы занимают центральное место в окне программы. Ш Unitl.pas Unitl junit Unitl;
j
interlace ju»» ';.] Windows, Messages, SysUtils, Variants, Classes, Graphics, С •.'.] Dialogs; I type | | TForml * class(TFor») j Buttonl: TBuccon; I GroupBox1: TGroupBox; i Button2: TButton; Label!: TLabel; \ private : :;! { Private declarations } \ public v
'i\Code/Di»jram/:
Рис. 2.12. Окно редактирования текста программы
Если вы правой кнопкой мыши щелкнете на окне редактирования формы или текста программы, то вызовется контекстное меню, позволяющее выполнять с окнами различные действия (копировать, вырезать, вставлять, изменять настройки и т. д.). Как правило, эти же действия можно выполнить и из главного меню.
Глава 2. Интерфейс программы
2.2. Создание приложений Чтобы написать программу для Windows (то есть приложение), нужно создать проект. Проект создается автоматически, когда вы запускаете Delphi. Однако его можно создать и принудительно, выбрав в меню File > New > Application. В этом случае Delphi создаст стандартное приложение Windows. Если вы хотите создать что-то менее стандартное (например, компонент или консольное приложение), то нужно нажать кнопку New Items с изображением чистого листа или выбрать в меню File > New > Other. Откроется окно с различными проектами, которые можно создавать в Delphi (рис. 2.13): Application (обычная программа для Windows), Batch File (командный файл), Component (новый компонент), Console Application (консольное приложение, которое выглядит как окно DOS), DLL Wizard (создание DLL). Выбрав нужный тип проекта, нажмите ОК, и проект будет создан.
Data Modules ] WebServices
Business ] WebSnap j Web Documents
N««
Projects j Forms j Dialogs | Projects
| ActiveX | Muttia
Batch File
OX Application
Component
Console Application
Control Panel Application
Control Panel Module
Data Module
DLLWizard
Form
Frame
Package
Г Cs».
Г hfe»
Г (its
Рис. 2.13. Создание нового проекта
После создания нового проекта Application на экране появляется пустая форма. Можно сразу изменить некоторые ее свойства в окне Object Inspector, где отображаются свойства активного в данный момент компонента. Чтобы активизировать компонент, нужно щелкнуть на нем мышью. Если щелкнуть на форме, то она станет активной и можно будет изменять ее свойства. Обычно я сразу же изменяю некоторые свойства формы. Например, если не нужно, чтобы окно разворачивалось во весь экран (а часто это как раз не нужно), то следует изменить значение свойства BorderStyle. Установив его равным bsSingle, вы не разрешите пользователю изменять размеры формы. Наоборот, если нужно, чтобы пользователь мог свободно изменять размеры формы, то задайте значение bsSizeable. Свойство BorderStyle может принимать и другие значения. Вот краткое описание всех значений:
Создание приложений • bsDialog — диалоговое окно. Используется для создания окон с вопросом или сообщением для пользователя. В таком окне присутствует только кнопка закрытия (крестик). Размеры заголовка окна нормальные; • bsNone — окно без заголовка. С помощью таких окон удобно делать заставки программ и необычные формы, например в играх, проигрывателях. Когда вы сами оформляете кнопки сворачивания, разворачивания и закрытия окна, тоже можно выбирать это свойство. Имейте в виду, что если задать значение bsNone и не предусмотреть закрытие окна (например, специальной кнопкой), то пользователь сможет закрыть такое окно, только нажав Alt+F4; • bsSingle — обычное окно, размеры которого нельзя изменять, потянув мышью за границу окна. Размеры заголовка окна нормальные; • bsSizeable — обычное окно. Отличается от bsSingle тем, что можно изменять размеры окна; • bsSizeToolWin — такое окно похоже на bsDialog, но его заголовок очень маленький. Это значение удобно использовать, когда вы создаете какую-то панель инструментов в своей программе, или для других экзотических целей; • bsToolWindow — то же самое, что и bsSizeToolWin, только пользователь не может изменять размеры окна мышью. Итак, мы выбрали bsSingle. После этого нужно установить значения свойства Borderlcons, которое отвечает за отображение кнопок в заголовке окна (сворачивания, разворачивания во весь экран). Чтобы изменить значения этого свойства, в окне Object Inspector нужно дважды щелкнуть мышью на Borderlcons. Появится возможность изменять значения, устанавливая True (истина) или False (ложь), соответственно для отображения или неотображения той или иной кнопки в заголовке окна. Перечислю возможные значения: • biSystemMenu — отвечает за отображение кнопки меню окна. Она располагается в левом верхнем углу окна; • biMinimize — кнопка сворачивания окна (рис. 2.14); • biMaximize — кнопка разворачивания окна во весь экран; • biHelp — кнопка системы помощи (значок вопроса). Еще одним свойством формы, которое лучше сразу установить, чтобы потом не забыть, является Position. Это не что иное, как размещение формы на экране. Свойство Position может иметь следующие значения: • poDefault — окно появится на экране там, где укажет операционная система; • poDefaultPosOnly — окно появится там, где оно находилось в момент разработки, но операционная система при необходимости может изменить координаты появления окна; • ро Defa u ItSizeO n ty — окно будет расположено там, где оно было в процессе разработки, но операционная система при необходимости может изменить размеры окна;
Глава 2. Интерфейс программы
|Fotm1
i'Fa
Properties | Events) j AutoSaoJ ITfue ~*1 1 AutoSize if?!5? Г BDiMode ''J MLeitToRight j iBBpideflcons I [biSystemMenu, biSvstemMeHTtue j biMiramize biMaximize I i
BprdeiSljiie BoidetWidth Caption I OientHeight j ClenfWdth (АИ shown
Рис. 2.14. Изменение значения свойства biMinimize с True на False
• poDesigned — окно появится там, где оно находилось в момент разработки приложения (компиляции); • poDesktopCenter — окно появится в центре Рабочего стола; • poMainFormCenter — если это не основное окно программы, а вызываемое, то оно будет расположено в центре основного окна; • poOwnerFormCenter — если это не основное окно программы, а вызываемое из какого-то другого окна (назовем его материнским), то расположение будет в центре материнского окна; • poScreenCenter — окно появится в центре экрана. Чтобы не забивать голову, проще запомнить лишь несколько из этих значений: poDefault, poDesktopCenter и poOwnerFormCenter. Их хватит для решения большинства задач. Я обычно использую poDesktopCenter для основного окна и poOwnerFormCenter для остальных окон приложения. Последнее свойство формы, которое лучше задавать сразу, — это WindowState. Оно показывает, как будет выглядеть окно при запуске программы. Может иметь значения wsNormal, wsMaximized и wsMinimized, что соответствует обычному, развернутому во весь экран и свернутому в область уведомлений. Мы рассмотрели все свойства формы, значения которых лучше изменить сразу, чтобы не забыть потом. Не страшно, конечно, если изменить их позже. Теперь будем добавлять компоненты на форму. На палитре компонентов множество вкладок, и все они важны (рис. 2.15)! Конечно, компоненты с вкладки Standard используются чаще остальных, но не намного. Добавить компонент на форму можно несколькими способами. Например, дважды щелкнуть мышью на компоненте на палитре, и он добавится в центр формы. После чего компонент можно перетащить мышью в нужное место или изменить его размеры, подведя указатель мыши к границе компонента. В процессе переме-
Создание приложений щения или изменения размеров компонента Delphi будет помогать всплывающей подсказкой, показывая координаты компонента. Это очень удобно, но, к сожалению, отсутствовало в начальных версиях Delphi. / Delphi 7-Project?
. . •• 1 f "1 1Й - Ш ^ Й? Af ifc :'Sj ^ Ф H Slandatdj Additional Win32 ) System] Data Access! Data Controls 1 dbEratessj PaUSnaoj BDE )jJ-L 3 : :lj : ;; 8
Гфi isl"aJ["i>-"si'T"и a ]!I U — Э !§ "" -TI l! ? ^Ш^EE?^-^^и '
Рис. 2.15. Палитра компонентов с активной вкладкой Win32
Второй способ заключается в одинарном щелчке на палитре компонентов, чтобы активизировать добавление нужного нам компонента, и затем помещении его на форму тоже щелчком мышью. В таком случае можно задавать сразу размещение и размер добавляемого компонента. Для удаления ненужного компонента с формы его нужно активизировать (щелкнуть на нем мышью), чтобы он стал выделенным (по углам компонента появятся черные квадратики), и нажать клавишу Delete. Можно выделить несколько компонентов, удерживая Shift на клавиатуре. Когда вы двигаете компонент мышью, он сдвигается сразу на 8 пикселов. Если вы изменяете размер, то он тоже уменьшается или увеличивается на-8 пикселов. Часто же необходимо более точно двигать или изменять размер. В этом случае просто держите клавишу Shift и перемещайте или изменяйте размер клавишами управления курсором. Ими же, удерживая Ctrl, можно сдвигать компонент на 1 пиксел. Конечно, компонент можно двигать, прямо указывая в Object Inspector координаты (Left и Тор), но это удобно только в некоторых случаях. Создадим простейшее приложение. В дальнейшем будет приведено множество примеров, и подробно рассматривать набор элементарных действий при создании приложения уже не будем. Первым приложением пусть будет традиционное «Hello, World!» («Привет, Мир!»). Обычно при изучении какого-нибудь языка программирования считается традицией сделать первым приложением программу «Hello, World!». Итак, форма появилась автоматически. Ее свойства мы изменили, как было рассказано выше. Теперь поместим на форму компоненты. Дважды щелкните мышью на кнопке Button на палитре компонентов, а затем дважды на этикетке Label. Теперь передвиньте компоненты так, как показано на рис. 2.16 (можете поставить их подругому, как вам больше нравится). Приступим к изменению свойств компонентов. Опять сделаем активной форму (просто щелкните мышью на любом ее месте, свободном от компонентов) и впишем в свойство Caption формы строку «Привет, Мир!». Теперь активизируем кнопку Buttonl (щелчком мыши на этой кнопке, находящейся на форме) и у нее тоже изменим значение свойства Caption на «Нажми меня, я жду!». Обратите внимание, что на кнопку не поместился весь текст, поэтому ее нужно сделать шире. Подведи-
Глава 2. Интерфейс программы те мышь к границе кнопки, чтобы курсор превратился в черную стрелку, и, нажав левую кнопку, двигайте до тех пор, пока Buttonl не станет достаточно широкой.
Рис. 2.16. Компоненты на форме
Теперь будем манипулировать этикеткой Labell. Сделаем ее активной и изменим значения некоторых свойств. Например, свойству AutoSize присвоим значение False. Это означает, что этикетка не будет изменять свой размер, подстраиваясь под текст. Если задать AutoSize равным True, то, удалив все буквы из Caption, мы добьемся того, что этикетка станет нулевой ширины. А нам это сейчас неудобно. Также изменим значение свойства Alignment с taLeftJustify на taCenter (это привязка текста внутри этикетки). В данном примере красивее, когда текст расположен по центру. Теперь изменим размер этикетки так, чтобы она по ширине примерно была равна кнопке Buttonl. Все, процесс визуальной части программирования завершен. Перейдем к наполнению программным кодом. В приложениях Windows вообще программирование сводится к обработке событий. В нашем приложении «Hello, World!» при нажатии кнопки Buttonl на этикетку Labell будет выводиться текст. Значит, нужно всего лишь написать обработку события нажатия кнопки Buttonl. Взгляните на Object Inspector. До этого мы только изменяли свойства компонентов на вкладке Properties. Но есть еще вкладка Events. Перейдите на нее, предварительно сделав активной кнопку Buttonl. В окне перечислены все события, которые могут относиться к кнопке. Нас интересует событие OnClick, которое генерируется при нажатии кнопки. Чтобы создать процедуру обработки этого события, дважды щелкните мышью на пустом поле справа от надписи OnClick (рис. 2.17). Откроется окно редактирования кода программы, причем процедура обработки события OnClick создастся автоматически: procedure TForml.ButtonlClick(Sender: TObject); begin
end;
Создание приложений
Buttonl Properties
Events
Acton OnClick | j OnContestPopij j OnDragDrap j | OnOragOvei I DnEndDockj OnEndDrag ! QnEnter i
'
1 i [
OnKeyDown | OnKeyPress I OnKeyUp ~ OnMouseDowr;
1AII shown
Рис. 2.17. Вкладка Events, событие OnClick
Между begin и end можно писать текст программы. Введем такую строку (рис. 2.18): Labell.Caption := 'Привет, Мир!';
LM1
iprocedure TForrnl.BucconlClick(Sender: TObjcct); Ibegin JLatoell.Caption : и 'Привет, М и р ! ' ; |..'J. lend;
• end.
29: 3 4 M o d i f i e d
Рис. 2.18. Набор текста программы в процедуре обработки события OnClick кнопки Button 1
Если вы не знаете операторы языков Pascal и Delphi, то просто наберите этот пример. В гл. 5 операторы рассмотрены подробнее. Написанная строка означает, что свойству Caption компонента Labell присваивается значение «Привет, Мир!». Причем происходит это не когда попало, а только если нажать кнопку Buttonl (ведь это обработка события OnClick). Теперь запустите программу на выполнение. Для этого служит кнопка Run с изображением стрелки-треугольника, направленной вправо. Можно также нажать клавишу F9 на клавиатуре. 2 Зак. 899
Глава 2. Интерфейс программы Если вы все сделали правильно, то программа запустится и появится окно с кнопкой. Нажмите ее. Появилась ли на этикетке надпись «Привет, Мир!» (рис. 2.19)? Если да, то могу вас поздравить с первой работающей программой, написанной на Delphi! W Привет, Мир!
{ Нажми меня, я жду! 11
И • Привет, Мир!
Рис. 2.19. Окно работающей программы «Привет, Мир!»
2.3. Обзор компонентов Перед перечислением компонентов скажу, что некоторые из них являются контейнерами. Это означает, что они могут размещать на себе другие компоненты. Самым главным контейнером является форма, но есть и другие. Теперь непосредственно обзор. Конечно, здесь описаны не все компоненты, а только основные. Тем не менее даже таких часто употребляемых компонентов в Delphi великое множество. Компоненты разбиты по вкладкам.
Вкладка Standard Она содержит такие компоненты: • MainMenu — данный компонент необходим для создания меню в программе. Это то меню, которое размещается под заголовком окна. Таких компонентов может быть несколько, но присоединен к форме только один (меню может быть только одно); • PopupMenu — всплывающее меню, вызываемое правой кнопкой мыши. Такие меню можно создавать в необходимом количестве и привязывать к разным компонентам;
Обзор компонентов • Label — этикетка. Самый простой компонент, который служит для вывода текста и больше ни для чего. Может быть прозрачным (свойство Transparent) и содержать несколько строк текста (свойство Wordwrap); • Edit — предназначен для ввода текста с клавиатуры. Через него, конечно, можно и выводить текст. Edit в отличие от Label не может становиться прозрачным, да и текст должен располагаться в одну строку; • Memo — простейший текстовый редактор! Просто поместив Memo на форму и запустив программу, можно вводить и редактировать текст. Несложно сохранять текст в файл и читать из файла. Поддерживаются только текстовые файлы; • Button — кнопка. Еще один незаменимый компонент; • CheckBox — флажок. Может находиться в трех состояниях: отмечен, не отмечен и не активен. Рядом с флажком можно выводить текст (вопрос или комментарий), поэтому у этого компонента есть свойство Caption; •
RadioButton — переключатель. Несмотря на слово radio в названии, к радио этот компонент не имеет никакого отношения. Это всего лишь средство для выбора чего-нибудь. Одновременно в одном контейнере может быть только один отмеченный переключатель;
•
ListBox — список текстовых строк. Можно выбирать одну или несколько строк;
• ComboBox — раскрывающийся список. В свернутом состоянии занимает мало места, но стоит на него нажать, как он разворачивается; • ScrollBar — полоса прокрутки. Может быть вертикальной или горизонтальной; • GroupBox — панель для группировки компонентов. Попросту контейнер с рамкой; •
RadioGroup — панель для отображения нескольких переключателей;
• Panel — панель, которая является контейне.ром. Может принимать вид углубления, быть выпуклой или просто внешне неотличимой от формы.
Вкладка Additional На ней есть следующие компоненты: • BitBtn — кнопка, которая может принимать несколько стандартных образов. Например, становиться кнопкой подтверждения (ОК) или отмены (Cancel); • SpeedButton — кнопка, на которую можно загрузить изображение; • MaskEdit — компонент для ввода текста по маске. Если вы хотите, чтобы пользователь ввел данные не как попало, а как задумано программистом, то MaskEdit как раз пригодится. Например, с его помощью можно вводить даты; • StringGrid — таблица, подобная создаваемой в Excel, но, конечно, проще; • Image — изображение. Можно загружать файлы с расширениями JPG, JPEG, BMP, ICO, EMF и WMF. В ранних версиях Delphi файлы JPG и JPEG можно было подгружать только в процессе работы программы, обязательно подключив модуль Jpeg;
Глава 2. Интерфейс программы • Shape — компонент для отображения разнообразных геометрических фигур (эллипсов, прямоугольников и т. д.). Может принимать различные формы; • CheckListBox — выглядит как ListBox, но в каждой строке может стоять флажок, как у компонента CheckBox; • StaticText — выполняет функцию отображения текста. Похож на компонент Edit, но не позволяет вводить текст и требует меньше ресурсов. Текст должен располагаться в одну строку; • ControlBar — панель, на которой можно разместить кнопки SpeedButton; • Labeled Edit — состоит из Label и Edit. Ничего особенного, просто удобнее использовать один этот компонент, а не Label и Edit по отдельности; • ColorBox — компонент для выбора цвета; • Chart — отображение диаграмм.
Вкладка Win32 Вот компоненты, содержащиеся на ней: • TabControl — панель с вкладками, контейнер; • ImageList — не отображается в процессе выполнения программы. Нужен для хранения изображений; • Rich Edit — дальнейшее развитие компонента Memo. Может работать не только с текстовыми файлами, но и с файлами RTF. Позволяет изменять шрифт отдельно для каждого'символа; • ProgressBar — показывает процесс выполнения чего-либо в виде полоски из квадратиков; • UpDown — привязывается к текстовому полю и позволяет изменять числовые значения с помощью кнопок «больше» и «меньше»; • DateTimePicker — календарь. Можно использовать как для наглядного отображения, так и для удобного изменения дат; • StatusBar — строка состояния, располагающаяся внизу окна. В ней удобно размещать какие-нибудь сообщения. Похожая строка есть, например, в Microsoft Word; • ToolBar — специальная панель инструментов для кнопок, находящаяся под главным меню окна.
Вкладка System
-
Рассмотрим компоненты, находящиеся на этой вкладке: • Timer — таймер. Не отображается в процессе выполнения программы и создает события OnTimer с указанной периодичностью; • MediaPlayer — проигрыватель звука. Его можно сделать невидимым, но звук все равно будет. Может воспроизводить и видеоролики.
Обзор компонентов
Вкладка Dialogs Пропустим несколько вкладок с компонентами, предназначенными для работы с базами данных и другими серьезными вещами, такими как SQL. Описание этих компонентов — тема отдельной книги. На вкладке Dialogs находятся все стандартные диалоговые окна Windows: открытия и сохранения файла, выбора цвета, печати и т. д. Имеет смысл использовать именно их, а не выдумывать свои, потому что стандартные компоненты лучше отлажены и пользователю привычнее. Конечно, если вы пишете игру или еще какую-нибудь программку, упор в которой сделан на оригинальное оформление, то можно создать и свои окна для открытия и сохранения файлов. В случае же обычных программ лучше использовать стандартные окна: • Open Dialog — выбор открываемого файла; • SaveDialog — окно сохранения файла; •
OpenPictureDialog — открытие изображения;
• SavePictureDialog — сохранение изображения; •
FontDialog — выбор шрифта;
• ColorDialog — выбор цвета; • PrintDialog — выбор принтера и параметров печати. Можно не заботиться о том, как выводить на печать, на какой принтер, а просто вызвать перед печатью это окно. Пользователь все выберет сам, и после отправки на печать все будет печататься так, как он выбрал. Получается, что автоматически поддерживаются и сетевые принтеры; •
PrinterSetupDialog — настройка печати;
•
FindDialog — стандартное окно поиска фрагмента текста;
• ReplaceDialog — поиск и замена фрагментов текста. /
Вкладка Win 3.1 Эта вкладка досталась в наследство от Delphi 1. Компоненты на ней выглядят как стандартные Windows 3.1. Не знаю, зачем могут пригодиться данные компоненты, возможно, только для совместимости или для очень специфических задач: •
DriveComboBox — предназначен для выбора активного диска;
• DirectoryListBox — список для выбора директории. При этом нужно подставлять диск, выбранный в DriveComboBox; • FileListBox — выбор файла или отображение списка файлов. При этом нужно подставить директорию, выбранную в DirectoryListBox; • FilterComboBox — установка фильтра (маски), по которой будут отображаться файлы.
Глава 2. Интерфейс программы Обычно эти четыре компонента используются вместе. Они вполне заменяются компонентами OpenDialog и SaveDialog.
Вкладка Samples Компонент Gauge похож на ProgressBar (с вкладки Win32), но более функционален. Имеет настолько много настроек, что можно превратить обычный прямоугольный указатель количества выполненных процентов в нечто полукруглое и двухцветное.
Дополнительные вкладки Вы можете устанавливать дополнительные компоненты. Их нужно или написать самостоятельно, или купить, или, например, скачать из Интернета. Часто компоненты создают свои вкладки на палитре компонентов. Так что если установить много нового, то количество вкладок может существенно увеличиться.
Глава 3
Структура программы G Файлы, используемые в Delphi Q Структура PAS-файла 01 Требования к тексту программы
Глава 3. Структура программы
3.1. Файлы, используемые в Delphi Пользователи, которые программировали в DOS, например на Pascal, привыкли, что самая простая программа хранится в одном файле. В Delphi, даже если вы только что создали проект и не написали ни строки кода, у вашей программы уже будет несколько исходных файлов. По мере роста приложения и добавления в проект новых форм количество файлов тоже увеличивается. Все исходные файлы программы, которую вы создаете, можно разделить на две части: файлы проекта и файлы форм. Файлы проекта создаются в самом начале, когда формируется проект. Имена таких файлов совпадают с именем проекта, а по расширению можно судить о предназначении файла: • DPR (Delphi Project) — основной файл проекта. В нем содержится текст программы — инструкции, которые, как правило, запускают другие модули; • DOF (Delphi Option File) и CFG (project ConFiGuration file) — файлы конфигурации проекта. Здесь содержатся текущие параметры проекта, такие как версия Delphi, настройки компилятора и компоновщика, директорди, директивы компилятора и параметры командной строки. Чтобы изменить параметры, хранящиеся в этих файлах, нужно открыть окно Project Options, выбрав в меню Project > Options; • RES — хранит информацию о версии (если используется) и значок программы. Файл также может содержать и другие ресурсы, используемые в проекте. Не удаляйте этот файл, если в вашем приложении есть какие-либо ресурсы (например, звук или изображения, хранящиеся на форме или в ЕХЕ-файле). Когда вы создаете модули (добавляете формы), то исходных файлов становится еще больше. Обычно при формировании нового проекта создается и одна форма. Поэтому в проекте сразу присутствуют файлы формы, если, конечно, вы не создаете консольное приложение. Рассмотрим их: • DCU (Delphi Compiled Unit) — содержит скомпилированный код модуля (формы), что ускоряет компиляцию проекта, если в нем несколько модулей, а редактируете вы только один из них; • DFM (Delphi Form File) — хранит все, что касается формы: содержащиеся на ней компоненты и их свойства, а также свойства самой формы; • PAS — файл модуля (unit) с текстом программы на языке Object Pascal. Текст программы, который вы пишете, хранится здесь.
Структура PAS-файла Файлы с другими расширениями встречаются реже, но кратко опишу их тоже: • BPG (Borland Project Group) — файл сборки; • BPL (Borland Package Library) — это DLL, которая включает в себя компоненты VCL; • DCP (Delphi Component Package) — исходные файлы компонента. ПосЛе компиляции создается файл с расширением BPL; • DDP (Delphi Diagram Portfolio) — здесь хранится диаграмма, созданная в окне Diagram (вкладка внизу окна редактирования кода); • DPK (Delphi Package) — исходный файл проекта пакета; • DSK (Desktop) — в этом файле хранятся настройки рабочей среды Delphi; • OBJ — объектный файл, используемый в С и C++. Будет создаваться, если в окне Project Options на вкладке Linker выбрать параметр Generate С object files или Generate C++ object files (компилировать в C/C++ объектный файл).
3.2. Структура PAS-файла Прежде чем описывать непосредственно структуру программы, нужно упомянуть о том, что такое проект. Когда вы хотите создать новую программу на Delphi, то создаете новый проект. Файл проекта имеет расширение DPR. У проекта может быть несколько форм. Код (текст программы) для каждой формы сохраняется в файлах с расширением PAS. Теперь можно разобрать PAS-файл на части и посмотреть, как он устроен. Каждая программа, написанная на Delphi, состоит из одного DPR-файла и одного или нескольких PAS-файлов (их может и не быть, если в приложении не используются формы). В файле с расширением PAS можно выделить стандартные блоки. Вкратце структура такая: Заголовок Секция
интерфейсных
объявлений
uses type const
var implementation локальные объявления модуля процедуры и функции end с точкой
Глава 3. Структура программы Рассмотрим структуру PAS-файла подробнее: • -заголовок т- название модуля (например unitl). В конце должна стоять точка с запятой; • слово interface открывает секцию интерфейсных объявлений. Чтобы типы, константы, переменные, процедуры и функции были видны всем модулям в приложении, они должны быть объявлены здесь; • далее следует описание подключаемых модулей (DLL), объединенных ключевым словом uses. Когда вы создаете новый проект, Delphi автоматически подключает все минимально необходимые модули. Поэтому блок uses уже содержит много модулей. В процессе написания программы в этот блок обычно добавляются еще модули (автоматически или вписываются программистом вручную). Модули перечисляются через запятую, а после последнего должна стоять точка с запятой; • type — описание типов. Здесь описывается класс формы. Можно добавлять и свои типы (см. разд. 4.5); • описание констант и переменных. Константы объединяются ключевым словом const, а переменные — var; • implementation — ключевое слово; • описание других форм, которые вызываются из данной. Задается тоже ключевым словом uses. Здесь важно размещение uses. В данном случае оно располагается после слова implementation; • команды для компилятора, заключенные в фигурные скобки: {$R *. DFM}. Во многих случаях эти команды изменять не нужно, они появляются автоматически; • непосредственно программа, состоящая из процедур и функций; • ключевое слово end с точкой, называемое терминатором.
3.3. Требования к тексту программы В Delphi, как и в Pascal, все строки должны заканчиваться точкой с запятой (но есть исключения из этого правила). Последней строкой программы (модуля) должна быть такая: end.
Перед end; можно не ставить точку с запятой, но лучше все же добавлять после каждой строки кода. Точка с запятой не ставится только после некоторых ключевых слов: interface, implementation, var, const, type, begin и команд компилятору. Delphi не различает строчные и прописные буквы. Можете набирать текст программы так, как вам удобно, поставив на первое место удобочитаемость. Любая хорошая программа должна сопровождаться комментариями. В Delphi, конечно же, предусмотрено добавление их в текст программы.
Требования к тексту программы Существует два способа вставки комментария: • первый очень удобен для больших комментариев, а также для отключения целых частей программы (например, в процессе отладки). Комментарий заключается в фигурные скобки: (здесь находится комментарий) • второй способ больше подходит для комментирования одной строки кода. Комментарием считаются все буквы в строке, стоящие после символов //: // это тоже комментарий Delphi автоматически выделяет комментарии курсивным шрифтом и цветом, чтобы их можно было легко отличить от текста программы. Далее в книге тексты программ приведены с подробными комментариями, чтобы наглядно показать, какие строки для чего нужны.
Глава 4
Стандартные типы данных Q 01 а Q Q Q
Простые типы Строковые типы Тип Variant Объявление переменных Определение своих типов данных Константы
Простые типы В Delphi предусмотрено огромное количество типов данных. Обо всех типах можно узнать в системе помощи Delphi. Например, в окне ввода программы можно набрать слово type, установить в него курсор и нажать Ctrl+Fl (рис. 4.1). Найденные разделы 1
Выберите раздел и нажмите кнопку "Вывести '. Туре Type identity (Object Pascal Reference)
Рис. 4.1. Разделы справочной системы для слова type
Я не ставил перед собой цель подробно описать в данной книге программирование на Delphi, так как для этого пришлось бы писать отдельную книгу, более объемную, чем эта. Поэтому опишу только основные типы, которые нужно знать для чтения следующих глав.
4.1. Простые типы Рассмотрим их. • Булев тип данных — Boolean. Переменные этого типа могут иметь лишь одно из двух значений: False (ложь) или True (истина). Тип Boolean занимает в памяти 1 байт. При сравнении переменных этого типа имейте в виду, что False < True. В Delphi можно использовать и другие булевы типы, такие как ByteBool, WordBool и LongBool. Но они существуют для совместимости с другими языками, поэтому предпочтительнее использовать тип Boolean. • Целые типы. В зависимости от задачи при написании программы стоит использовать соответствующие типы. Конечно, сейчас нет такой жесткой зависимости от аппаратного обеспечения компьютера, и часто (в небольших программах) можно позволить себе расточительно использовать память — компьютеры мощные, памяти много. Но если вы пишете что-то серьезное, работая с большими объемами данных, то нужно внимательно относиться к тому, переменные какого типа вы используете, и вообще иметь привычку использовать именно те типы, которые необходимы. Разница в быстродействии будет заметна. Целые типы данных Delphi приведены в табл. 4.1. Некоторые типы данных могут различаться в разных версиях Delphi, но совместимы снизу вверх — от старых версий к новым. Проект, созданный в Delphi 3, будет выполняться в Delphi 7, но не наоборот.
Глава 4. Стандартные типы данных Таблица 4.1. Целые типы данных
Занимает Примечание байт
Минимальное значение
Максимальное значение
-128
127
1.
0
255
1
-32 768
32767
2
0
65535
2
Integer
-2 147 483 648
2 147 483 647
4
Longint
-2 147 483 648
2 147 483 647
4
В отличие от Integer этот тип не зависит от версии Object Pascal
0
4 294 967 295
4
В ранних версиях Delphi (третьей) этот тип имеет диапазон лишь от 0 до 2 147 483 647, но также занимает в памяти 4 байта
8
Отсутствует в ранних версиях Delphi
Тип Shortint Byte Smallint Word
Cardinal
I
Int64
\'
9 223 372 036 854 775 807
-9 223 372 036 854 775 808
• Действительные (вещественные) типы. При работе с дробными числами приходится использовать переменные действительных типов. Работа с ними менее удобна, чем с целыми, и вычисления выполняются медленнее. Действительные типы данных описаны в табл. 4.2. Таблица 4.2. Действительные типы данных
Тип
Порог
Максимальное значение
Количество значащих цифр
Занимает байт
Real
2.9E-39
1.7E38
11-12
6
Single
1.5E-45
3,4E38
7-8
4
Double
5.0E-324
1.7E308
15-16
8
Extended
3.4E-4932
1ДЕ4932
19-20
10
1,0
9.2E18
19-20
8
0,0001
9,2E14
19-20
8
Comp Currency
Строковые типы Формат типа Real неудобен для Intel-совместимых процессоров, поэтому данный тип лучше не использовать. Операции с переменными этого типа выполняются медленнее, чем с переменными других типов. Несмотря на кажущуюся идентичность действительных типов и математических действительных чисел, не стоит забывать, что у действительных типов в Delphi есть определенная точность, которая, увы, не бесконечна. Поэтому при вычислениях могут возникать определенные погрешности. Символьные типы. В Delphi их три: Char, AnsiChar и WideChar. Типы Char и AnsiChar идентичны и занимают в памяти 1 байт. В большинстве случаев можно использовать тип Char, но иногда может потребоваться применение WideChar. Символы в переменных этого типа занимают в памяти 2 байта и упорядочены в соответствии с международным набором символов Unicode. Первые 256 символов совпадают с символами ANSI.
4.2. Строковые типы Они предусмотрены в Delphi для работы со строками. Строковый тип — это массив символов. В Delphi все символы строки нумеруются, начиная с единицы, а в нулевой ячейке строки хранится ее длина. Стандартный строковый тип в Delphi — string. При объявлении строк можно специально ограничить максимальную длину строки. Для совместимости с ранними версиями Delphi существует тип shortstring с максимальной длиной строки 255 символов. Но имеет смысл использовать не тип Shortstring, а самый обычный string с указанием длины строки или без него. Кстати, команда компилятору {$н} указывает Delphi, какой тип строк использовать. Если написать ($н+}, то string будет совпадать с AnsiString. Этот параметр установлен по умолчанию. Если указать {$н-}, то string будет совпадать С Shortstring. К слову, в языке С структура строковых переменных немного отличается от принятой в Pascal и Delphi. Там нумерация символов строки начинается прямо с нулевой ячейки, а для обозначения конца строки служит не ее длина, а символ с кодом «О». В Delphi такие строки тоже поддерживаются. Для этого нужно объявить массив символов типа char. Для работы с API-функциями Windows необходим формат строк типа PChar. Вообще говоря, Delphi ориентирована на работу со строками типа string, но имеются средства для преобразования PChar в string и обратно.
Глава 4. Стандартные типы данных
4.3. Тип Variant В Delphi присутствует тип Variant. Переменным этого типа можно присваивать практически любые значения, Delphi автоматически перекодирует их. Например, если присвоить переменной типа Variant какое-либо число, а затем попытаться вывести его в текстовое поле, то все получится! Однако не следует злоупотреблять применением этого типа. Дело в том, что для создания эффективных и надежных программ лучше указать конкретный тип данных и производить все преобразования между типами вручную. Потому что операции с переменными типа Variant выполняются гораздо медленнее, чем, например, с переменными типа Integer или string, да и вероятность возникновения ошибки выше. Тип V a r i a n t предназначен для представления значений, которые могут изменяться и тип данных которых неизвестен. Например, Variant используется при создании приложений, Применяющих технологию СОМ (увидеть в действии технологию можно, например, открыв в браузере Internet Explorer страницу Word или таблицу Excel).
4.4. Объявление переменных В Delphi все используемые переменные следует предварительно объявить. Для этого служит ключевое слово var. Переменные могут быть видны всем процедурам и функциям, такие переменные называются глобальными. Они объявляются вне процедур и функций. Не нужно злоупотреблять такими объявлениями. Следует делать переменные, доступные во всех процедурах и функциях, только в том случае, если это действительно необходимо. Глобальные переменные должны быть описаны в блоке var до слова implementation. Объявляются переменные следующим образом: var // раздел объявления переменных Forml: TForml; // форма типа TForml m: Integer; // переменная m типа Integer s: String; // переменная s типа String Сначала располагается имя переменной, затем двоеточие и ее тип. Пробел можно ставить для удобочитаемости текста, но это необязательно. После наименования типа переменной ставится точка с запятой. Можно объявить несколько переменных одного типа, перечислив их через запятую: а, г, max, min: Integer; Массивы в Delphi объявляются так: MAP: array [1..10] of Integer;
Объявление переменных Что мне очень нравится в Delphi, так это то, что при объявлении массива не только задается размерность, но и конкретно указывается, как будет нумероваться массив. Можно нумеровать с нуля, с единицы или вообще с любого числа, хоть с 555, если так нужно и удобно программисту. В данном случае MAP — это имя массива; array — ключевое слово, сообщающее о том, что это массив; [ 1. . 1 о ] — размерность, нумерация начинается с единицы и до 10 (10 элементов массива); of Integer означает, что все элементы массива имеют тип Integer. Многомерные массивы объявляются похожим образом: MATRIX: array [1..10, 1..20, 1..16] of Integer;
Здесь объявлен трехмерный массив с именем MATRIX, все элементы которого имеют тип Integer. Строковые переменные объявляются аналогично: S, str: String;
Можно объявить строковую переменную с фиксированным количеством символов. Например, если вам- известно, что символов в строке будет точно не больше 200, то можно объявить строковую переменную так: S: String[200];
Естественно, из строк можно образовывать массивы так же, как и из обычных переменных: S: array [1..100] of String[255]; (массив из 100 строк, длина каждой — 255 символов}
Имена переменных и массивов не должны совпадать с зарезервированными словами Delphi: absolute
forward
published
abstract
index
read
assembler
message
readonly
automated
name
register
cdecl
near
resident
default
nodefault
safecall
dispid
override
stdcall
dynamic
pascal
stored
export
private
virtual
external
protected
write
far
public
writeonly
Глава 4. Стандартные типы данных
4.5. Определение своих типов данных Как и любой современный язык программирования высокого уровня, Delphi позволяет программисту определять собственные типы данных. Причем существует возможность сделать это несколькими способами. Все определяемые типы задаются в разделе type. Можно добавлять типы, например, после описания формы или до него. Посмотрите на пример. Здесь тип Col описан до типа TForml, а тип d — после. Листинг 4.1
type // описание типа Со! Col=record х: Integer; \/: Integer; end; // описание типа TForml - формы TForml = class(TForm) Button!: TButton; Editl: TEdit; Edit2: TEdit; Edit3: TEdit; procedure ButtonlCI ick(Sender: TObject); private { Private declarations } publ ic { Public declarations } end; // описание типа d d=0..9; Таким образом, все равно, в каком порядке описаны типы. Можно даже сделать еще один раздел type, если вам так удобнее. Delphi допускает несколько разделов var И type.
Если это нужно для удобочитаемости программы, то можно для каждого типа указывать свое слово type. Листинг 4,2
type Co 12=record хх: Integer; end;
Определение своих типов данных type Col=record х: Со12; у: Integer; end; Но нужно помнить, что если вы описываете тип, который использует другой тоже определяемый вами тип, то уже определенный тип, применяемый в создаваемом типе, должен располагаться выше (в тексте программы).
Тип-диапазон и перечисляемый тип Тип-диапазон объявляется так: type D = ' 1 ' . . ' 9 ' ; Таким образом, мы получили тип о, переменным которого можно присваивать значения от единицы до девяти. Вот еще пример: type M o n t h s = l . . 1 2 ;
Полученный тип Months можно использовать, к примеру, для нумерации месяцев. Тогда будет сложнее сделать ошибку, так как Delphi при всем вашем желании не даст назначить номер месяца 13 или, допустим, 0. В переменных типа Months, который мы только что придумали, допустимы только значения от единицы до 12 и никакие другие! Похожий на тип-диапазон перечисляемый тип описывается следующим образом: type D=(monday,tuesday,Wednesday,thursday,friday,Saturday,Sunday); В скобках через запятую перечислены все возможные значения переменных этого типа.
Записи и классы Очень удобными определяемыми типами являются записи. Если мы хотим хранить в одной переменной разнообразные данные (числовые, строковые и др.), то для этого как раз и нужно создать тип в виде новой записи. Листинг 4.3 type Card=record number: Integer; //номер карты fio: String[220]; // фамилия владельца age: Byte; // возраст владельца
Глава 4. Стандартные типы данных salary: Integer; //зарплата money: Int64; //деньги end; Получился новый тип с именем Card. Можно объявить переменную типа Card и хранить в ней и номер карты, и фамилию, и возраст, и зарплату, и деньги — множество самых разнообразных данных. Использование записей удобно само по себе, чтобы все структурировать, а не вспоминать, к чему относится та или иная переменная. Записи также незаменимы при работе с файлами (см. гл. 8). Разновидностью записей являются классы. Они отличаются от записей тем, что в них можно описывать не только переменные, а еще и процедуры с функциями. Примером определяемого класса является класс формы, который Delphi создает автоматически. Листинг 4.4
type TForml = class(TForm) Buttonl; TButton; Editl: TEdit; Edit2: TEdit; Edit3: TEdit; procedure ButtonlCI ick(Sender: TObject); private { Private declarations } publ ic { Publ ic declarations } end; Разумеется, можно добавлять и свои классы. В классах и записях допускается использовать массивы. Массивы объявляются точно так же, как и в разделе var. . Листинг 4.5
type Card=record number: Integer; // номер карты fio: String[220]; // фамилия владельца age: Byte; // возраст владельца salary: Integer; //зарплата
Константы money: Int64; //деньги OtherC: array [1..10] of Integer; // номера других карт end; В данном случае в запись добавлен массив OtherC из 10 элементов типа Integer.
4.6. Константы Я не совсем правильно поместил описание констант в одну главу с переменными, поскольку это все же разные вещи. Но все же я сделал это вполне осознанно, потому что константы по использованию в программировании близки к переменным (рис. 4.2).
Unitl Buctonl: TButton; procedure ButtonlClick(Sender: TObject); private
|
""3
j { Private declarations > I publi с I { Public declarations > I end;
! const I I
total_roonthes-12; alcogolics=3; |
jvar ; :
Form!: TFocml; Implement ati on
-'•22:.' 19' : • !JMoc№ed
'" [insert
j\Code/Diagtam/"
Рис. 4.2. Константы в тексте программы
Константы — это некие значения, используемые в программе, которые, как следует из названия, постоянны и заранее известны во время разработки программы. Задаются константы в разделе const, который может находиться до или после var И type. При объявлении констант не нужно указывать их тип. Просто через знак равенства пишется, чему равна константа. Листинг 4.6
const total_monthes=12; alcogol ics=3; Константы в программе можно использовать точно так же, как переменные. Конечно, нельзя присвоить константе значение, а также использовать ее как счетчик цикла.
Глава 4. Стандартные типы данных Часто константы используют для величин, которые после компиляции программы не нужно изменять, но которые могут изменяться при написании или модернизации программы. Допустим, в константе можно хранить номер версии программы. Тогда в тексте программы всегда используется константа, а чтобы изменить номер версии, достаточно просто исправить одно число в объявлении константы. Листинг 4.7
const vers=Myprog 1.0; // константа с версией программы begin // [...]
// где-то в глубине программы Label 12.Caption := vers; // вывод версии программы на экран // [...] end. Таким образом, не нужно искать в тексте программы значения, которые необходимо изменить, а следует выносить все такие вещи в раздел констант. Тогда они на виду и искать их не надо. Впрочем, что я вам рассказываю, где применять константы? Однажды вы сами придете к этому, если еще не пришли.
Глава 5
Операторы а Q Q Q Q О Q
Оператор присваивания Операторы ввода-вывода Оператор безусловного перехода Операторы условного перехода Циклы Операции с числами Основные математические функции
Глава 5. Операторы Именно операторы выполняют все действия в программе. В этой главе описаны операторы Delphi, а также некоторые встроенные функции.
5.1. Оператор присваивания
•
Оператор присваивания в Delphi состоит из знаков двоеточия и равенства, написанных один за другим. Слева от оператора присваивания пишется переменная, которой нужно присвоить значение, а справа — выражение, которое нужно вычислить. Листинг 5.1
А := 25; // переменной А присваивается значение 25 str := Париж; {переменной str присваивается строковое значение "Париж"} А := В+С+10; {переменные В и С складываются, затем к ним прибавляется 10, после чего результат помещается а А} Shark := 25*23+MoveX; При вводе выражений необходимо помнить, что они обязательно должны быть записаны в одну строку. Операции выполняются слева направо, но умножение и деление всегда до сложения и вычитания. Чтобы заставить Delphi выполнить, например, сложение раньше умножения, нужно поставить круглые скобки: а := ( 5 + 2 ) * 3 ; а := 5+2*3;
В первом случае переменной а присвоится значение 21. Во втором — сначала выполнится умножение 2 на 3, и в итоге получится 11. Кстати, строки тоже можно складывать с помощью плюса, но вычитать с помощью минуса нельзя.
5.2. Операторы ввода-вывода Очень важные в Pascal операторы ввода-вывода в Delphi не выглядят такими важными. Ведь вывод сейчас осуществляется через компоненты на форме, да и считывание данных тоже. Это все справедливо для приложений, использующих окна-формы. Если вы пишете консольное приложение (выглядит как окно DOS), то в нем можно использовать Write, WriteLn, Read, ReadLn точно так же, как в Pascal. Например, п : = Read ожидает ввода с клавиатуры значения, а затем помещает это значение в переменную п. Оператор ReadLn читает с клавиатуры строку символов, а в остальном используется как Read. Операторы W r i t e и W r i t e L n применяются для вывода значений переменных.
Операторы условного перехода Эти операторы могут пригодиться и тогда, когда нужно прочитать или записать файл. Работа с файлами рассмотрена в гл. 8, поэтому просто приведу примеры использования данных операторов. Для чтения из файла используются операторы Read и ReadLn. Листинг 5.2
Read(F,p); {чтение записи из файла F в переменную р} ReadLn(F.s); {чтение строки из текстового файла F в переменную s} Write(F.p); {запись переменной р в файл} WriteLn(F,s); {запись строки в текстовый файл}
5.3. Оператор безусловного перехода Оператор безусловного перехода goto устарел. Использовать его не рекомендуется, так как часто именно применение этого оператора ведет к ошибкам в программе и путанице, к тому же сейчас существуют другие, более совершенные методы программирования. Но знать об этом операторе безусловного перехода, несомненно, нужно. Вдруг с помощью него вы создадите что-нибудь интересное. Оператор goto перебрасывает выполнение программы в другое место. Лучше показать это на примере. Листинг 5.3 procedure TForml.GotoExample; label m; begin a := 5; b := 25; goto m; a:=3;
m: end;
Здесь процедура выполняется до оператора goto, а затем перескакивает сразу на метку т. Строка а : = 3; не выполняется. Метки доджны быть описаны в разделе label.
5.4. Операторы условного перехода Оператор if Чтобы проверить какое-то условие и выполнить ту или иную часть программы, существует оператор i f . В простейшем случае он записывается так: if a=5 then a := а*2;
Глава 5. Операторы Если условие а=5 верно (в данном случае), то выполняется действие, записанное после then. Можно выполнять сразу несколько действий, вписав их в составной оператор begin-end. Листинг 5.4 if a=5 then begin {набор действий} а := а*2; b := True; end; Таким образом, все выполняемые действия должны быть записаны между словами begin и end (составной оператор). Обратите внимание, что после begin не ставится точка с запятой, так как это продолжается один оператор (до end). А между begin и end в конце строк точки с запятой ставятся. Оператор if можно записать и так, как показано в листинге 5.5. Листинг 5.5 if a=5 then begin {набор действий} end else begin
{другой набор действий} end;
Тогда если а=5, то выполняется первый набор действий. Если же а не равно пяти, то выполняются другие действия (записанные после слова else).
Оператор выбора case Иногда бывает удобно использовать не if, а оператор case, который проверяет сразу много значений. Листинг 5.6 case A of 1: s = один; 2: s = два; 4: s = три; 5: s = четыре; 6: s = пять; 8: s = шесть; else s := ; end; // case
Циклы В приведенном примере оператор case сверяет значение переменной А с указанными вариантами. Если А=1, то выполнится первая строка, если А=4 — третья и т. д. Если же А не будет равна ни одному из значений, то выполнится s : = ' ' после else (else указывать необязательно).
5.6. Циклы В Delphi, как и полагается в приличном языке программирования, можно сделать цикл с известным количеством повторений, цикл с предусловием и цикл с постусловием.
Цикл с известным количеством повторений for Циклом с известным количеством повторений является for. Вот его пример: for i := 1 to 20 do s := s + ' ';
В цикле for указываются начальное и конечное значения переменной (в нашем случае для переменной i они равны 1 и 20 соответственно). Действия, которые нужно выполнять в цикле, указываются после do. После do, кстати, может быть и составной оператор begin-end.
Цикл с предусловием while-do Данный цикл очень часто используется, например при чтении и записи файлов. Листинг 5.7
s := ; i := 0 whi le i<20 do begin s := s + ; i := i + 1; end;
Цикл выполняется, пока выполняется условие (в данном случае i<20). Как видите, в теле цикла i каждый раз увеличивается на единицу, и как только перестанет выполняться условие i<20, цикл прекратится. К переменной s при каждом выполнении цикла прибавляется пробел. В итоге должно быть двадцать пробелов. Можно сделать бесконечный цикл, если записать заголовок цикла следующим образом: while True do. Если запустить программу с таким циклом, то она зависнет. Поэтому подобные циклы обычно пишут с выходом из тела цикла. Для выхода из цикла служит оператор Break. Оператор Continue используется, когда нужно перейти к началу цикла.
Глава 5. Операторы
Цикл с постусловием repeat-until Этот цикл очень похож на while-do, но условие ставится не в начале, а в конце цикла. Цикл выполняется, пока условие ложно (в общем, если сравнивать с whiledo, то все наоборот). Листинг 5.8 Ь
.
]
i := 0; repeat s := s + ; i := i + 1; unti I i>19;
В листинге 5.8 приведен такой же цикл, как и тот, который был реализован через while-do (см. листинг 5.7). Обратите внимание, что в этом цикле не нужно писать begin-end. Телом цикла считается то, что написано после repeat до until. Цикл выполняется, пока условие i>19 ложно. Еще одно различие циклов while-do и repeat-until в том, что первый может вообще ни разу не выполниться, если его условие будет ложно, a repeat-until хотя бы раз обязательно выполнится.
5.7. Операции с числами Операции с числами, типами и символами, а также их результаты приведены в табл. 5.1. Таблица 5.1. Операции с числами, типами и символами
Операция
Результат
Abs (X)
Возвращает абсолютное целое значение X. Проще говоря, X по модулю
X div Y
Возвращает целую часть частного от деления X на Y
X mod Y
Возвращает остаток от деления X на Y
Odd(X)
Возвращает True (если X — нечетное целое) или False (в остальных случаях)
Sqr(X)
Возвращает целый квадрат X. Проще говоря, Х*Х
Low(X)
Возвращает минимальное значение порядкового типа
High(X)
Возвращает максимальное значение порядкового типа
SizeOf (X)
Возвращает размерность типа (сколько байт занимает одна переменная этого типа)
Dec (X)
Уменьшает X на единицу
Inc(X)
Увеличивает X на единицу
Chr ( X )
Преобразует целую переменную в переменную типа Char
Основные математические функции Операция
Результат
UpCase
Преобразует строчную букву в прописную. Работает только с латинскими буквами
Random ( X )
Возвращает случайное число от нуля до (Х-1 ) . Перед запуском Random нужно выполнить Randomize. Последнее достаточно сделать один раз
Немного о том, как пользоваться Low, High, SizeOf. Нужно объявить переменную того типа, значения которого вы хотите узнать, и затем применить Low, High или SizeOf уже к этой переменной. Листинг 5.9
var
m: Integer; begin m := Low(m); // в m будет минимальное значение типа Integer end;
5.8. Основные математические функции Самые простые математические функции можно вызывать, не подключая дополнительные библиотеки (достаточно стандартных SysUtils и System, которые Delphi обычно объявляет автоматически при создании нового проекта), но некоторые функции требуют подключения библиотеки Math (uses Math). Основные математические функции приведены в табл. 5<2. Таблица 5.2. Некоторые математические (арифметические, тригонометрические) функции Функция
Результат
Cos ( X )
Возвращает косинус X
Sin ( X )
Возвращает синус X
ArcCos ( X )
Возвращает арккосинус X. Требует подключения библиотеки Math
ArcSin ( X )
Возвращает арксинус X. Требует подключения библиотеки Math
Tan ( X )
Возвращает тангенс X: Tan ( X ) := Sin ( X ) /Cos (X) . Требует подключения библиотеки Math
ArcTan ( X )
Возвращает арктангенс X
ArcTan2 ( Y , X )
Вычисляет арктангенс ( Y / X ) и возвращает угол. Значения X и Y должны находиться в пределах от -2Ы до 26/1. К тому же X не должен быть равен нулю. Получаемый результат будет от -р до р. Эта функция требует подключения библиотеки Math Окончание •&
Глава 5. Операторы Таблица 5.2. Окончание
Функция
Результат
Ехр(Х)
Возвращает значение е от числа X, где е основано на натуральном логарифме
Ln (X)
Возвращает натуральный логарифм
LoglO(X)
Вычисляет логарифм по основанию 10. Требует подключения библиотеки Math
Log2 (X)
Вычисляет логарифм по основанию 2. Требует подключения библиотеки Math
LnXPl (X)
Вычисляет натуральный логарифм от (Х+1 ) . Требует подключения библиотеки Math
LogN(Base,X)
Вычисляет логарифм X по основанию Base. Требует подключения библиотеки Math
Max (А, В)
Возвращает наибольшее число из А и В. Требует подключения библиотеки Math
Min(A,B)
Возвращает наименьшее число из А и В. Требует подключения библиотеки Math
Pi
Возвращает число л (3,1415926535897932385)
Int(X)
Возвращает целую часть числа X
Round (X)
Округляет X до ближайшего целого
Trunc(X)
Отбрасывает дробную часть числа
Для возведения числа в степень можно применять такую конструкцию: ХА = E x p ( A * L n ( X ) ) .
Если же нужно возвести число в квадрат или в третью степень, то лучше не использовать приведенную формулу, а просто умножить числа: х2 = х*х,х 3 = х*х*х.
Тогда процессору не потребуется выполнять сложные расчеты для вычисления логарифмов.
Глава 6
Процедуры и функции О G Q G Q Q
Процедуры Функции Процедуры, возвращающие значение Забавный эксперимент с функцией Перегружаемые процедуры и функции Дополнительные сведения о процедурах и функциях
Глава 6. Процедуры и функции
6.1. Процедуры Текст программы на Delphi представляет собой набор процедур и функций. Так получается из-за того, что сутью программирования для Windows является обработка событий. Каждая процедура — это обработка какого-либо события. Открылось новое окно программы, значит, произошло событие активации окна. Его можно обработать, написав соответствующую процедуру. Система Windows постоянно передает выполняющимся программам множество разнообразных событий. Структура процедуры в Delphi выглядит следующим образом. Листинг 6.1
procedure TForml. FormAct i vate( Sender: TObject); var // объявление переменных total: Integer; begin // здесь записывается программный код процедуры end; Это стандартная процедура обработки события активации формы. Разберем ее по строкам: procedure TForml . FormActivate (Sender : TObject); Все процедуры начинаются с ключевого слова procedure. Затем пишется имя процедуры и указывается, к какой форме она относится. В данном случае имя процедуры FormActivate, а относится она к форме Forml. Получается TForml . FormActivate. После имени процедуры в скобках указываются сообщаемые ей параметры: (Sender: TObject). После имени процедуры располагается раздел объявления локальных переменных: total: Integer; Это необязательный раздел. Если не нужно объявлять никакие переменные, то и писать var не надо. Локальные переменные объявляются так же, как глобальные (см. разд. 4.4): begin // здесь записывается программный код процедуры end;
Процедуры Локальные переменные будут видны только в пределах этой процедуры, другим процедурам они будут недоступны. Программный код процедуры (тело) располагается между begin и end. Все процедуры и функции должны быть объявлены. Если процедура создается автоматически средой разработки, то объявление процедуры тоже автоматически появляется там, где нужно. Если же программист создает процедуру или функцию вручную, то он сам должен позаботиться о ее объявлении. В листинге 6.2 приведен пример программы, написанной на Delphi. Обратите внимание на процедуру FormActivate и на ее объявление в классе формы. . i
Листинг 6.2
unit Unit"!; // наименование модуля interface
i •
uses // подключаемые библиотеки Windows,Messages,SysUtiIs, Classes,Graphics, Controls, Forms, Dialogs; type // объявление типов TForml = class(TForm) // класс формы // объявление процедуры procedure FormActivate(Sender: TObject); private // здесь объявляются переменные, // видимые только в этом модуле { Private declarations } publ ic // здесь указываются переменные, // видимые и в других модулях { Publ ic declarations } end; // конец объявления типов var Form"!: TForml; // форма типа TForml
implementat ion {$R *.DFM)
.
procedure TForml. FormActivate(Sender: TObject); //процедура var total: Integer; 3 3ax. 899
Глава 6. Процедуры и функции
begin // здесь записывается программный код процедуры end; end. // конец программы
Объявление процедуры или функции можно вписывать в класс формы вручную или заставить сделать это Delphi. Во втором случае правой кнопкой мыши нужно щелкнуть на заголовке написанной вами процедуры и в появившемся меню выбрать пункт Complete class at cursor. Тогда объявление процедуры автоматически появится в классе формы (в разделе private). Вписывание объявления процедуры вручную ничем не хуже, хотя, может быть, не так удобно.
6.2. Функции Функции служат для тех же целей, что и процедуры, — выполняют ряд действий. Но функции могут возвращать значение. Листинг 6.3
function TForml.TwentyFive: Integer; begin TwentyFive := 25; end; Здесь функция с именем TwentyFive возвращает значение типа Integer и всегда возвращает число 25. Она практически бесполезная, и приведена лишь для того, чтобы показать, как использовать функции в Delphi. Вызвать функцию из другой функции или процедуры можно так: total := TwentyFive;
При этом выполнится функция TwentyFive и возвращенное значение присвоится переменной total. Функции могут и не возвращать значение. Тогда они работают точно так же, как процедуры: function TForml.SomeActions;
Как видите, функция, не возвращающая значение, даже записывается так же, как процедура, различие лишь в названии — function.
6.3. Процедуры, возвращающие значение Процедуры в Delphi тоже могут возвращать значение. И даже не одно! Из-за этого, конечно, стирается грань между процедурами и функциями. В Delphi различия между процедурами и функциями главным образом есть лишь на уровне синтак-
Процедуры, возвращающие значение сиса. Существует мнение, что процедуры занимают меньше места и чуть быстрее работают. Но не думаю, что на практике эта разница будет заметна. Чтобы процедура возвращала значение, в ее заголовке перед каждой переменной, которая должна возвращать значение, нужно указывать слово var: procedure TForml.Otchet(var summa:Int64; var skidka:Int64);
Как это все работает, лучше посмотреть на примере. Некоторые мои знакомые почему-то не помнят, в каком месяце сколько дней. Напишем программу, которая будет подсказывать эти ценные сведения. Причем количество дней как раз и будет передаваться как возвращаемое в переменной значение. Создайте в Delphi новый проект: File > New > Application. На форму поместите компоненты ComboBox и этикетки Labell и Label2. В качестве значений свойства Items компонента ComboBox укажите названия месяцев: Январь, Февраль, Март, Апрель, Май, Июнь, Июль, Август, Сентябрь, Октябрь, Ноябрь, Декабрь. Именно так, не ИСкажайте. Такую программу можно реализовать разными способами. Способ, который мы применим, предполагает, что месяцы можно вписывать в любом порядке, но сами названия должны быть написаны с большой буквы без ошибок (чтобы совпадали с текстом программы). Теперь приступим непосредственно к написанию программы. Перейдите в Object Inspector на вкладку Events и дважды щелкните мышью на событии OnChange, чтобы открыть редактирование процедуры ComboBoxlChange. Из этой процедуры будем вызывать процедуру WhatMonth (именно процедуру, а не функцию), которой будем сообщать выбранный месяц, а из нее будут возвращаться два параметра: номер месяца и количество дней в нем. Эти значения будем выводить на Labell и Label2. Пишем процедуру WhatMonth (ее можно расположить до или после ComboBoxlChange). Нужно либо вписать ее в класс формы вручную, либо щелкнуть на заголовке процедуры правой кнопкой мыши и выбрать Complete class at cursor (рис. 6.1). Листинг 6.4
procedure TForml.WhatMonth.(s: String; var nm, days: Integer); // определение номера месяца и количества дней begin if з=Январь then begin nm := 1; days := 31; end; if з=Февраль then begin nm := 2; days := 28; end; if s=Mapr then begin nm := 3; days := 31; end; if з=Апрель then b e g i n nm := 4; days := 30; end; if з=Май then b e g i n nm := 5; days := 31; end; if з=Июнь then b e g i n nm := 6; days := 30; end; if з=Июль then b e g i n nm := 7; days := 31; end;
Глава 6. Процедуры и функции
if з=Август then begin nm := 8; days := 31; end; if з=Сентябрь then begin nm := 9; days := 30; end; if з=0ктябрь then begin nm := 10; days := 31; end; if з=Ноябрь then begin nm := 11; days := 30; end; if з=Декабрь then begin nm := 12; days := 31; end; end; Find Declaration Run
Component
Database
Jools
Win|
[ A i l Slandatd j Additional i Win32 j Si|
Drl*F4
£k>se Page Open F_le at Cursor
f "V {
Clrl*Enter
jj DalaSnaol BDE j-il_!«
New Edit Window
-II
Browse Symbol at Cursor Topic Search
*
»
[o] Button 1 Eljlabell
>
! procedure TForrol . UhacHoj Propeues | Events | i Actipii iSAnd-prs Г Bit* ode
I ilakLeilakfopj ' IbdLefiToRighi" " False"'''"'•;''"•'
{ГСарфп iffiConsjiaints i Cursti
;t(TSbeCore crDefault
Draglursor DlaQi^ind Diaptode Enabed
crDrag :dkOrag jdmManual {True
" ' " '
Integer) ;
I// ояределе«ие яо [begin
iif
|i£ jif iif iif iif
Январь' then begif: Февраль 1 then begi Нарт 1 then begin ; Апрель-' then begii ^| View Explorer Май 1 then begin nl Properties Июнь1 then beciin L. ..... , 31 ГЙ ....... IModiiied "
I \CodeXbiagram/
]№»•«)":' "
] ||Windo..
Рис. 6.1. Выбор в меню пункта Complete class at cursor .
Удобнее было бы применить здесь оператор case, но, увы, он не работает с переменными типа string. Обратите внимание на заголовок процедуры. В нее передается переменная s. Результат работы процедуры возвращается через переменные nm и days, поэтому перед ними стоит ключевое слово var. Процедуру будем вызывать из ComboBoxlChange. Так что подбор количества дней будет выполняться тогда, когда пользователь поменяет месяц в списке. Листинг 6.5
procedure TForml.ComboBox1Change(Sender: TObject); var k1, K2: Integer; begin WhatMonth(ComboBox1.Text,k1,k2);
Перегружаемые процедуры и функции LabeИ.Caption := Номер месяца в году + IntToStr(kl); Label2.Caption := Количество дней в этом месяце + lntfoStr(k2);
end; Значения переменных nm и days в процедуре whatMonth возвращаются в переменные kl и k2. Можете запустить программу и посмотреть, что все работает: значения подбираются в процедуре WhatMonth, передаются в ComboBoxlChange и уже оттуда выводятся на экран.
6.4. Забавный эксперимент с функцией Может быть, сделаем паузу? Да, вызывая вполне серьезную на вид функцию, будем получать совсем несерьезный результат. Создайте новый проект и поместите на форму кнопку Button 1 и этикетку Labell. В процедуре Buttoniciick (процедура обработки события OnClick кнопки Buttonl) напишите всего лишь одну строку кода: Labell.Caption := IntToStr(25); и запустите программу. Если нажать кнопку, то на этикетке появится текст «25». Теперь сделаем вид, что функция IntToStr, которая преобразует число в строку, нам не нравится, и поэкспериментируем с ней. Напишем такую функцию (не забудьте объявить ее в классе формы). Листинг 6.6
function TForml. lntToStr(i: Shortint): String; // наша собственная функция IntToStr begin IntToStr := Вигвам - индейская народная изба!;
end; Если теперь запустить программу и нажать кнопку, то вместо «25» появится совсем другое! Работает не стандартная функция Delphi, а наша.
6.5. Перегружаемые процедуры и функции Стандартную процедуру или функцию можно перегрузить. Это означает, что можно написать несколько процедур и функций с одинаковыми именами, но эти процедуры и функции будут разными, а самое главное — они будут обрабатывать разные параметры. Скажем, для обработки строковых значений будет вызываться одна функция, а для обработки числовых значений — другая, а для значений еще каких-то типов
Глава 6. Процедуры и функции (например, вещественных) — третья функция. И все эти три функции могут называться одинаково. Традиционно привожу пример. Создайте новый проект и разместите на форме три кнопки Button и одну этикетку Label. Теперь в свойствах Caption кнопок напишите следующее:дляВиШэп! — Целые числа, для Button2 —Дробные числа и для Buttons — Строки. При нажатии первой кнопки на этикетку будет выводиться целое число, при нажатии второй — дробное, а при нажатии третьей — текстовая строка. Все это будет выполнять процедура OutOnLabel. На самом деле получатся три процедуры, но все они будут называться одинаково. Напишем эти процедуры и обработку событий нажатия кнопок. Листинг 6.7
procedure TForml.OutOnLabel(s: String); // вывод на этикетку строки текста begin Label 1.Caption := s; end; procedure TForml.OutOnLabel(i: Integer); // вывод на этикетку целого числа begin Labe11.Caption := IntToStr(i); end; procedure TForml.OutOnLabel(i: Extended); // вывод на этикетку дробного числа begin Labe11.Caption := FloatToStr(i); end; procedure TForml. ButtonlCI ick(Sender: TObject); begin OutOnLabel(731); end; procedure TForml.Button2CI ick(Sender: TObject); begin OutOnLabel(255.6); end; procedure TForml.ButtonSCIick(Sender: TObject); begin OutOnLabel(Опять двадцать пять); end;
Дополнительные сведения о процедурах и функциях
6.6. Дополнительные сведения о процедурах и функциях Когда вы пытаетесь обратиться к процедуре, находящейся в другом модуле, но специально не объявляли его вручную, Delphi обязательно задаст вопрос: «Form 'FormГ references form 'Form2' declared in unit 'Unit2' which is not in your USES list. Do you wish to add it?» (рис. 6.2). В ответ на него нужно нажать кнопку Yes и запустить программу на выполнение снова. Если вы нажали Yes, то Delphi автоматически объявит форму, к которой происходит обращение, и больше этот вопрос задавать не будет. Разумеется, если вы хотите вызывать процедуру, принадлежащую одной форме (модулю), из другой формы (модуля), то ее нужно объявлять в разделе public, а не private, иначе другая форма не увидит этой процедуры. Можно вообще не объявлять процедуру или функцию ни в private, ни в public. Это неправильно, и так лучше не делать, однако работать все равно будет (в пределах одного модуля). Единственное «но» — процедура, которую вы вызываете, должна располагаться в тексте выше места вызова, иначе Delphi не сможет ее обнаружить. Пример не объявленной нигде процедуры и ее использования приведен в листинге 6.8. | File £d* Search УЮЧ
"
'
Project
Run Component
'
Qatabase Joo|s
II ! s S fii j * * О
ЩЫи» це|р I j jr
; Standard |миопа|| WMaj Sustemj Data Access I Data Controls j dbExpie») DataSnaol BDE i * '1 > ]
A.,[3T.g CD i* « Щ И ^ LJ fli LJ J3
: .' ;
--:-ШГ--|-Гч1г'Ч,
Ur*1
] Unit2 j
*• - Ч
J
; implementation
Fotm'Fomil'fefefencesfwmfofm? declared in unit UnitZ which is not in you
; Properties i I Г '! II ii if
j
:
• Щ:.:й!ШЬ-.'!->: :.:::.' . : .--.^ •••:•,•:. •• '•.••••.
Form2
Evert*
Action : АсйуеСогйо! | • '.Menu GbiectMenutiei OrAdivrrte ! DnCarftesize j OnCiick i
if s-'Иарт'
then begin nm :• 3; days :• 31; end;
Д™"П...."1!^",Ш i
31 •" 27
(ModSed ~
jinseif~
:
[Error] Unit1.pas{31): Undeclared identilier: WhalMonth' [Error] Unit1.pas(31):';' expected but 7 found [Error] Unill pas[34|: Undeclared identifier: V [Frrnrl I lniH nns(Fi1 ] I InrlprJwflH iHftntififtr' 'Fnrm?1
.; DnCloseQuefyi |[ ^QnComtfainedj -i OnCofii;r.'pnDeate I 'OnDWOdk^'"]"
K\9uM/~
i;AI«hown
И
<0
Рис. 6.2. Окно с вопросом о добавлении модуля в раздел uses
._.
_...
T
Глава 6. Процедуры и функции Листинг 6.8 procedure Unseen; // процедура, которая нигде не объявлена begin Form"!.Capt ion : = Здесь был Ва. .. Unseen!;
end; procedure TForml.ButtonlCI ick(Sender: TObject); begin Unseen; end; Процедура Unseen вызывается нажатием кнопки Buttonl. Если вы запустите такой пример, то все будет выполняться. Если же необъявленную процедуру расположить в тексте программы после Buttonlciick, то она работать не будет.
Глава 7
Свойства, методы и события Q Свойства G Методы Q События
Глава 7. Свойства, методы и события В предыдущих главах приводились примеры программ, в которых приходилось использовать события и свойства различных компонентов. Поэтому «темным лесом» эти понятия для вас, вероятно, уже не являются. Итак, каждый компонент имеет определенные свойства, которые задают его поведение и внешний вид. Методы компонента — это процедуры и функции, которые что-то делают или со своим объектом (компонентом), или с остальными. Например, метод Show формы выводит ее на экран, метод Close закрывает форму, a Print печатает на принтере. События генерируются операционной системой в результате взаимодействия программ с аппаратным обеспечением компьютера, с пользователем и между собой. Windows создает огромное количество сообщений, передающих в выполняющиеся программы сведения о событиях. Обращение к свойствам, методам и событиям объекта происходит так: • Labell. Caption — обращение к свойству Caption компонента Labell; • Formi. ShowModal — вызов метода ShowModal формы,Forml. Для обработки событий тоже можно обращаться к ним похожим образом, но в Delphi существует более удобный механизм. Чтобы создать процедуру обработки события, нужно перейти в Object Inspector на вкладку Events и назначить обработчик события двойным щелчком мыши — все очень просто.
7.1. Свойства Несмотря на то что большинство компонентов очень разные и нужны для выполнения различных задач, почти все они имеют много одинаковых свойств. С методами и событиями та же ситуация. Ниже в виде справочной информации перечислены и описаны свойства, которые встречаются у многих компонентов.
Align Привязка компонента к контейнеру, на котором он находится. Например, Labell. Align := alBottom; — привязка к нижнему краю формы. Саму форму можно привязывать к краям экрана. Возможные значения: • aLBottom — привязка к нижнему краю; • alClient — компонент занимает всю возможную площадь контейнера (формы, рамки, панели), на котором находится; • alCustom — привязка к левому верхнему углу; • alLeft — к левому краю; • alNone — нет привязки. Можно произвольно изменять размер и расположение компонента;
Свойства • alRight — привязка к правому краю; • alTop — к верхнему краю.
Alignment Привязка текста внутри некоторых компонентов, таких как Label, ScaticText, Memo, RichEdit. Возможные значения: taCenter, taLeftJustify и taRightJustify, что соответствует привязке к центру, к левому и правому краям.
AutoSize Автоматическое изменение размера компонента в зависимости от его содержимого. Например, этикетка Label или текстовое поле Edit становится равным по ширине тексту, который содержится в свойстве Caption или Text. Это свойство есть также у компонентов, предназначенных для отображения картинок. Возможные значения: False и True.
BorderStyle Это свойство есть практически у каждого компонента. Но, несмотря на одинаковое название, у разных компонентов BorderStyle отвечает за разные вещи. Свойство BorderStyle формы подробно рассмотрено в разд. 2.2. Компоненты могут иметь такие значения BorderStyle: • bsNone — означает, что рамки у компонента нет (плоский вид); • bsSingle — стандартный вид. У некоторых компонентов свойство BorderStyle предоставляет более широкий выбор значений: • sbsNone — аналогично bsNone: рамки нет, компонент имеет плоский вид; • sbsSingle — простая рамка; • sbsSunken — компонент как бы вдавлен в поверхность, на которой находится.
Caption Дословный перевод слова caption с английского — заголовок. У большинства компонентов свойство Caption как раз и выполняет функцию заголовка (например, у GroupBox). Присвоенный свойству Caption текст выводится в заголовок рамки. Однако, например, для компонента Label свойство Caption — никак не заголовок, а просто текст, который отображается на этой этикетке (Label). У кнопки Button и у многих похожих компонентов Caption тоже играет роль афиши. Свойству Caption можно присваивать любые значения типа string.
Глава 7. Свойства, методы и события
Color Цвет фона компонента. Свойству Color можно присваивать значения типа TColor. "Существуют заранее определенные стандартные цвета (табл. 7.1). Скажем, если вам нужно сделать Labell красного цвета, то совсем не обязательно долго думать и высчитывать числовой эквивалент этого цвета, достаточно присвоить этикетке значение clRed: L a b e l l . C o l o r := clRed; // делаем этикетку красной Таблица 7.1. Стандартные цвета Числовое значение
Значение TColor
Описание цвета
—
clNone
Белый в Windows 95/98/Ме, черный в Windows NT
SFFFFOO
clAqua
Ярко-голубой
$000000
clBlack
Черный
SFFOOOO
clBlue
Синий
SFOFBFF
cLCream
Цвет светлого крема
$808080
clDkGray
Темно-серый
$FFOOFF
clFuchsia
Цвет называется «фуксия», напоминает фиолетовый
$808080
clGray
Серый
$008000
ctGreen
Зеленый
$OOFFOO
clLime
Ярко-зеленый
$СОСОСО
clLtGray
Светло-серый
$000080
clMaroon
Что-то среднее между кирпично-красным и коричневым
$А4АОАО
clMedGray
Средне-серый
$СООССО
clMoneyGreen
Серо-зеленый
$800000
clNavy
Темно-синий
$008080
clOlive
Оливковый
$800080
clPurple
Пурпурный
$OOOOFF
clRed
Красный
$СОСОСО
clSilver
Серебряный
$FOCAA6
clSkyBlue
Небесно-голубой
$808000
clTeal
Сине-зеленый
$FFFFFF
clWhite
Белый
$OOFFFF
clYeUow
Желтый
Свойства
Cursor Показывает, каким будет вид указателя мыши, если подвести его к компоненту. Обычно значением этого свойства является crDefault — обычный указатель-стрелка. Возможные значения: • crAppStart — стрелка с песочными часами; • crArrow — стрелка; • crCross — крестик; • crDefault — обычный указатель по умолчанию; • crDrag — стрелка с чистым листом. Обычно используется для отображения перетаскивания; • crHandPoint — рука. Указывает на ссылку; • crHelp — стрелка со знаком вопроса; • crHourGlass — песочные часы. Означает, что программа занята выполнением какого-либо процесса; • crHSplit — две стрелки: одна влево, другая вправо; • crIBeam — курсор для ввода текста; • crMultiDrag — стрелка с несколькими чистыми листами. Используется для перетаскивания группы объектов; •
crNo — перечеркнутая окружность;
• crNoDrop — окружность, перечеркнутая в другую сторону. Показывает невозможность перетаскивания объекта; •
crSizeAU — четырехсторонняя стрелка;
• crSizeNESW — двухсторонняя стрелка вправо снизу вверх и влево сверху вниз; • crSizeNS — вертикальная двухсторонняя стрелка; • crSizeNWSE — двухсторонняя стрелка влево снизу вверх и вправо сверху вниз; •
crSizeWE — горизонтальная двухсторонняя стрелка;
• crSQLWait — песочные часы с надписью SQL; •
crUpArrow — стрелка вверх;
• crVSplit — две стрелки: одна вверх, другая вниз.
Enabled Показывает, включен компонент или нет. Свойство Enabled может принимать два значения: False и True. По умолчанию это свойство всегда равно True, то есть компонент включен. Если присвоить False, то компонент станет серого цвета и пользователю нельзя будет произвести с ним никакие действия. Тем не менее считывать и записывать свойства отключенного компонента программным способом можно.
Глава 7. Свойства, методы и события
Font Шрифт, которым выводятся надписи на компоненте. Можно устанавливать цвет символов (Font .Color), менять сам шрифт (Font .Name), его размер (Font .Size) и стиль (Font. Style). Для задания вида шрифта существует тип TFontstyle. Переменные данного типа могут принимать следующие значения: • fsBold — полужирный шрифт; • fsltalic — курсивный; • •
fsllnderline — подчеркнутый; fsStrikeOut — зачеркнутый.
Изменить значение свойства Font можно, дважды щелкнув на поле ввода данного свойства в Object Inspector. После этого откроется диалоговое окно выбора шрифтового оформления (рис. 7.1). Выбор шрифта Начертание:
Шрифт
Еазмер:
|обычньй MS Serif 'Ф МТ Exlia Small Fonts Ч? Symbol System Tahorna "Атрибуты^
курсив полужирный полужирный курсив
'" 1 -~—-
•Образец
Г" .Зачеркнутый
АаВЬАаОо
Г" Подчеркнутый ! Цдет: [••Черный
На#орсимеопов: [Кириллица
Рис. 7.1. Свойства шрифта можно изменять и таким образом Рассмотрим пример изменения свойства Font для этикетки Labell: • Labell .Font, style := [fsBold, f s l t a l i c ] ; —задание полужирного курсивного шрифта; • Labell. Font. Style : = [ ]; — установка стандартного шрифта; • Labell.Font.Style := Labell.Font.Style + [fsStrikeOut] ;—добавление к стилям шрифта зачеркнутого; • Labell.Font.Style := Labell.Font.Style + [fsBold]; — добавление полужирного шрифта; • Labell .Font .Style := Labell. Font. Style - [fsBold] ; — отмена полужирного СТИЛЯ.
Свойства
Height Высота компонента в пикселах. Для формы она задается, включая заголовок и границы окна. Чтобы указать для формы высоту непосредственно той области, где можно размещать компоненты, нужно использовать свойство ClientHeight.
Hint Содержит всплывающую подсказку (текст), которая отображается, когда указатель мыши подводится к компоненту (рис. 7.2).
: j S'andara1 ( Additional I Win32 j S wlem j Data Access j Data Controls j dbExoross j DataSnao I BDE
i ш В .С AW&S v •: * щ Ш^О: la D :j -,
ButtOfil. il Это подсказка! :
: .12
JModiiied
fl
LabeB
hnsart
;\Code/I)iagiam/
.^ Pioj.!'.'" jjBj
Рис. 7.2. Пример всплывающей подсказки
Всплывающая подсказка появится, только если значением ShowHint будет True.
Left и Тор Расположение компонента: Left показывает отступ от левого края, а Тор — от верхнего. Измеряется относительно контейнера. Для форм отсчет ведется от края экрана, а для компонентов — от края формы или рамки (зависит от того, расположен ли компонент непосредственно на форме или,. например, на GroupBox).
Глава 7. Свойства, методы и события
Name Имя компонента. По умолчанию задается стандартное. Например, для этикеток будут такие имена: Labell, Label2, Labels и т. д., а для кнопок: Buttonl, ButtonE и т. д. Но имя можно изменять.
PopupMenu Здесь указывается меню, открывающееся при щелчке правой кнопкой мыши на компоненте. Перед заданием этого свойства компонент PopupMenu уже должен находиться на форме.
ShowHint Показывает, нужно ли отображать всплывающую подсказку. Может иметь значения False и True. Если установить True, то будет отображаться всплывающая подсказка с текстом из свойства Hint. Если текст подсказки не задан (Hint=' '), то она не выводится на экран.
TabOrder Задает порядок перехода фокуса клавиатуры с одного компонента на другой. При нажатии клавиши Tab фокус с компонента, у которого значение свойства TabOrder равно нулю, переходит на компонент с TabOrder=l, затем на компонент с TabOrder=2 и т. д. по порядку. Очередность задается автоматически, в зависимости от того, какой компонент был помещен на форму раньше, но как раз с помощью свойства TabOrder эту очередность можно изменить.
Visible Может принимать два значения: False и True (по умолчанию True). Если установить False, то компонент не будет отображаться в режиме выполнения программы (рис. 7.3), в отличие от режима разработки (рис. 7.4).
Взимая кнопка ;| Винимая кнопка
I
Видимая кнопка
]
Видимая кнопка
Рис. 7.3. В режиме выполнения программы компонент не виден
Методы
Project Bun Component fiatabase looh tfndow И* II \W
Efe £d* Search View
2$ \: © -ft}
. <
;! г
Tj !3P • Ш ' & Й • Ш iS ii ^ П Standard j Additional j WM2 j System j Data Access I Data Controls ] doExoress j DataSnao I BDE I i |
да
?
l>
\ШЖ
,^ошш
ш«а;т iwsta. , ;;;;Sf щщ ШЁЭ^ * *
Ш
О Frarn! |oj Button! ;
Э Bu»0n2
Э ВиНопЗ • Э Bullon4 |S| ButlonS
'. '. '.
Видимая кнопка
: ; :
Видимая кнопка
''::'. •'.-
Видимая кнопка
1
Properties | Events | "left "7j2* ModalResuIt ; miNone Name :Button4 PaientBOiMotifrue PaientFont True PaientShowHir False PopupMenu ' "ShowHht "•]-fI|ie-;-;" fabOrder TabStop
~~~t) ', . .
J3 j True
Ш
a
!.J 9^ '. ./71"JP~ '.'."..".'.".'"".' I VisWe
jFalse
All thowi "•
'
''''"'"'
• кнопка ••Н евиаимая
| :
Видимая кнопка
i^lif^lilE-llfilo ,
J [
-41:. Ъ
JModified
jlriserl
|\CpdeXDiagrarn/
.
-il
" »j False
1
Рис. 7.4. В режиме разработки отображаются даже невидимые компоненты
Width Ширина компонента в пикселах. Для формы она задается, включая границы. Чтобы указать для формы ширину непосредственно той области, где можно размещать компоненты, нужно использовать свойство ClientWidth.
7.2. Методы
>
Общих методов значительно меньше, Чем свойств и событий. Рассмотрим их:
. • SetFocus — устанавливает фокус клавиатуры на объект;
• Update — вызывает принудительную перерисовку объекта. Похожим образом работает Repaint; • Hide — скрывает объект. Свойству Visible присваивается значение False; • show — делает объект видимым. Свойству Visible присваивается значение True. Можно использовать для вызова другой формы. Разновидность вызова другой формы — метод showModal;
Глава 7. Свойства, методы и события • функция Focused — возвращает True, если фокус клавиатуры находится на проверяемом объекте; • функция CanFocus — возвращает True, если проверяемый объект видимый (Visible=True) и активный (Enabled=True). Эта функция присутствует не у всех компонентов, а только у тех, которые могут получать фокус клавиатуры (например, Label не может).
7.3. События Как уже не раз упоминалось, программирование в Windows —. это написание процедур и функций по обработке событий. Чаще других используются следующие события: • OnActivate — генерируется при активации объекта (например, при первом вызове формы). В обработке этого события можно производить различные инициализации (а также в обработке события OnCreate); • OnClick — генерируется, когда к объекту подводят указатель мыши и нажимают ее левую кнопку; • OnClose — происходит при закрытии формы. Здесь можно размещать код, сохраняющий данные перед закрытием формы; • OnCreate — генерируется при создании объекта; • OnDblClick — его порождает двойной щелчок мышью на объекте; • OnDeactivate — происходит при деактивации объекта; • On Destroy — создается при уничтожении объекта; • OnDockDrop — генерируется, когда один объект перетаскивают на другой и отпускают кнопку мыши. Оно может быть вызвано, только если DockSite=True; • OnDockOver — происходит, когда на объект попадает другой объект, переносимый мышью. Это событие генерируется для объекта-приемника и может быть вызвано, только если DockSite=True; • OnDragDrop — используется для определения момента, когда пользователь переместил объект; • OnDragOver — используется для перетаскивания и отпускания; • OnEndDock — генерируется, когда перетаскивание завершено и кнопка мыши отпущена; • OnHide — происходит, когда объект становится невидимым; • OnKeyDown — происходит при нажатии клавиши на клавиатуре. Отличается от On Key Press тем, что происходит, когда пользователь нажал клавишу (отпускать ее необязательно — событие OnKeyDown уже произошло). Процедуре обработки события сообщается числовой код клавиши (типа Word) и информация о нажатых клавишах Ctrl, Alt, Shift и кнопках мыши (тип TShif tstate); • OnKeyPress — генерируется при нажатии клавиши. Но, в отличие от OnKeyDown, происходит только тогда, когда пользователь нажал и отпустил клавишу. Пока
События он ее не отпустил, событие On Key Press не происходит. Сообщается код нажатой клавиши (типа char), а точнее — символ, который ей соответствует; OnKeyUp — похоже на OnKeyDown, но генерируется при отпускании нажатой клавиши. Процедуре обработки события сообщаются код клавиши (типа Word) и информация о нажатых клавишах Ctrl, Alt, Shift и кнопках мыши (тип TShif tstate); OnMouseDown — происходит при нажатии кнопки мыши (причем неважно, какой). Процедуре обработки события сообщается информация о нажатых кнопках мыши, о клавишах Ctrl, Alt, Shift и координатах указателя мыши. Событие происходит независимо от того, отпустил пользователь кнопку мыши или нет; OnMouseMove — происходит при перемещении указателя мыши над объектом. Сообщаются информация о клавишах Ctrl, Alt, Shift и координаты указателя мыши; OnMouseUp — аналогично OnMouseDown, но генерируется не тогда, когда пользователь нажал кнопку мыши, а в момент ее отпускания; OnMouseWheel — генерируется при прокрутке колеса мыши. Как правило, происходят сразу два события: OnMouseWheel (во всех случаях) вместе с OnMouseWheelDown или с OnMouseWheelUp; OnMouseWheelDown — генерируется, если пользователь прокручивает колесо мыши к себе; OnMouseWheelUp — генерируется, если пользователь прокручивает колесо мыши от себя; On Paint — происходит при перерисовке объекта; OnResize — происходит при изменении размеров объекта. Неважно, делает это пользователь, допустим, мышью или программист с помощью соседней процедуры, все равно генерируется событие OnResize. В его обработке (если оно относится к форме Form) удобно пересчитывать координаты и размеры компонентов; OnShow — происходит при отображении объекта на экране.
Глава 8
Работа с файлами Q О Q Q О Q
Текстовые файлы Типизированные файлы Нетипизированные файлы Удаление записей Обработка ошибок Процедуры и функции для работы с файлами
Глава 8. Работа с файлами Чтобы создать эффектный проект, не помешает освоить работу с файлами. Причем, например, не через драйверы баз данных, а просто записать что-либо куданибудь или прочитать. Delphi позволяет выполнять такие действия. Если не касаться нестандартных способов доступа к файлам и банальной работы с базами данных, то остаются три способа доступа к файлам в Delphi: текстовые, типизированные и нетипизированные файлы. Использование процедур и функций для работы с текстовыми, типизированными и нетипизированными файлами очень похоже, различия лишь в деталях. Для работы с файлом необходимо назначить ему файловую переменную, то есть ассоциировать ее с именем файла на диске. Через эту переменную будет выполняться вся работа с файлом. Переменная назначается с помощью процедуры AssignFile, которая имеет следующий синтаксис: procedure AssignFile(var F; FileName: String); Вызов AssignFile инициализирует файловую переменную F любого типа. После вызова данной процедуры F становится ассоциированной с внешним файлом FileName до того момента, пока он не будет закрыт. Если параметр FileName не задан, то AssignFile ассоциирует F со стандартным входным или выходным файлом. Нельзя использовать AssignFile с открытыми файлами. После вызова AssignFile файл можно создать или открыть для чтения и записи. Для создания файла используется процедура Rewrite: procedure R e w r i t e ( v a r F: File [; RecSize: Word ] ) ; После вызова данной процедуры создается файл с именем, ассоциированным с указанной файловой переменной (F). В случае использования Rewrite с нетипизированными файлами можно указать длину записи (RecSize). Если длина не задана, то она автоматически устанавливается равной 128 байт. Если файл с таким же именем, как у создаваемого файла, уже существует, то Rewrite удалит его и создаст новый. Обратите внимание, что процедура Rewrite удаляет существующий файл, не задавая никаких вопросов. Программист должен сам позаботиться о том, чтобы программа спрашивала у пользователя, удалить или оставить существующий файл. Если файл с таким же именем уже открыт, то он будет закрыт и создан заново. Если создаваемый файл текстовый, то после создания он будет открыт только в режиме записи. Все остальные типы файлов (нетекстовые) после вызова Rewrite доступны как для записи, так и для чтения. Если же файл не нужно создавать, то его можно открыть процедурой Reset: procedure Reset(var F [: File; RecSize: Word ] );
•
Процедура Reset открывает существующий файл. При этом текущая позиция чтения/записи будет в начале файла. Если файл уже открыт, то после вызова Reset он будет закрыт и открыт заново.
Глава 8. Работа с файлами Как и в случае с Rewrite, при использовании Reset с нетипизированными файлами можно указать длину записи. По умолчанию она равна 128 байт. После работы с файлом его необходимо закрыть. Пока файл не закрыт, другие программы не могут с ним работать. Операционная система автоматически закрывает все открытые файлы при выходе из программы. Но все-таки пользователи должны закрывать файлы сами. Для закрытия файла в Delphi предусмотрена процедура cioseFile: procedure CioseFile(var F ) ; Она прекращает связь файловой переменной с заданным в AssignFile именем файла и освобождает файл, то есть его смогут использовать другие программы.
8.1. Текстовые файлы Открытие файла как текстового еще не значит, что он обязательно содержит удобочитаемый текст, который пользователь может просмотреть в текстовом редакторе. Вполне можно с помощью доступа к файлу как к текстовому хранить в нем совсем не текстовую информацию. Все зависит от того, что мы хотим читать и записывать и как в данном случае удобнее это делать. Если рассматривать структуру текстового файла, то он содержит символы, организованные в строки. Символы же могут быть любыми (даже невидимыми при выводе на экран). Текстовые файлы открываются и закрываются так же, как и любые другие. Но процедуру Reset в текстовых файлах можно использовать лишь для их чтения. Для записи текстового файла его нужно открыть с помощью Rewrite (в этом случае создается новый файл) или использовать процедуру Append: procedure Append(var F: Text);
Она открывает текстовый файл (именно текстовый, с другими типами файлов процедура Append не работает) для дополнения, то есть новые строки текста будут добавляться в конец файла после существующих строк. Если открывать текстовый файл с помощью Rewrite, то он будет доступен для записи, но строки будут добавляться в начало файла. Рассмотрим примеры работы с текстовыми файлами.
Формирование текстового файла Создайте новый проект, поместите на форму кнопку и дважды щелкните на ней, чтобы попасть в окно редактора кода (рис. 8.1).
Текстовые файлы
£ie Ed* Search View fjrojecl £!"" Component Eataba» look Window Це)р ! i [my
шщ
"3 i €a Щ> I
tj US * 0 i i? & ^ & & \\ <@ H S'amiwi j Additional I Wb32| Siutemi OalaAoce»! DetaContrabl dbEmmss} OataSnaol ВОЕ jjli;
(Щ .••. : •. • ;• . Buuoni i:
-H'BUtonl
iprocedure TForml .BucconiClick(Scnder: TObject) ;
Aclk>« fflAnchors ljakLeli.3kfop] ""BiDWode""'" Cared il^CaRtion. ^Constraints i Cutsw ; : :
iFalse iButtpnl i(TSi28Cpn IciDefautt
;i pragCutsor: ictDrag ii Diagkind jdkDrag ; DiagMpde idmManual" i ! ' Enabled |Тше i [Й Font [JTF'ontj IJAi'shown
iModilied::: :Olimeil"T;''r"i\CodeXPiagram/r
"" ft SI Kf
Рис. 8.1. Редактирование процедуры обработки события OnClick кнопки Buttonl Введите эту процедуру вместо пустой. Листинг 8.1
procedure TForm1.Button1CI ick(Sender: TObject);
var
F: TextFi le; begin AssignFi le(F,myf i le.txt); Rewrite(F); WriteLn(F,Привет! Это первая строка в текстовом файле); C l o s e F i le(F); end;
После запуска программы при нажатии кнопки будет создан файл m y f i l e . t x t в той же директории, где расположена запускаемая программа. В файл запишется строка текста, а затем он будет закрыт.
Добавление строк в файл Теперь можно попробовать добавить в файл строки. Сделаем для этого еще одну кнопку на форме (ButtonZ) и запишем в процедуру Button2Click такой код.
Глава 8. Работа с файлами Листинг 8.2
.
procedure TForm1.Button2CI ick(Sender: TObject); var F: TextFi le; begin AssignFi le(F,myfi le.txt); Append(F); WriteLn(F,Хлебом не корми, дай только строки добавлять!); C l o s e F i le(F); end;
Пока примеры маленькие и скучные. Когда рассмотрим большее количество процедур и функций, можно будет писать соответствующие программы.
Чтение файла И еще один пример работы с текстовыми файлами. На этот раз — чтение файла, в котором что-то написано. Поместите на форму еще одну кнопку (третью) и рядом с ней компонент Memol. В процедуру Buttons. Click введите код. Листинг 8.3
procedure TForm1.Button3CI ick(Sender: TObject); var F: TextFi le; s: String; begin AssignFi le(F,myfi le.txt); Reset(F); ReadLn(F.s); // читаем строку из файла в Memol Memol. Lines.Add(s); // добавляем строки в Memol C l o s e F i le(F); end;
Данная программа прочитает только первую строку из файла. Чтобы прочитать все строки, нужно изменить процедуру до такого вида. Листинг 8.4
procedure TForml.ButtonSCI ick(Sender: TObject); var F: TextFi le; s; String; begi n AssignFi le(F,myf i le.txt); Reset(F);
Текстовые файлы whi le not Eof(F) do begin ReadLn(F.s); // читаем строку из файла в Memo"! Memol. Li nes. Add(s); //добавляем строки в Memo!
end;
CloseFi le(F); end; Здесь можно наблюдать цикл while, условием выполнения которого является not Eof ( F ) , что в переводе на русский язык означает «не конец файла» (функция Eof (F) возвращает True, если достигнут конец файла). Пока не будет достигнут конец файла, цикл будет повторяться, читая по одной строке и добавляя ее в Memol (рис. 8.2,8.3).
Button! Button? Buttons
Рис. 8.2. Вид окна программы
Button! BultonZ "lultonT
Memol Привет! Это первая строка в текстовом Файле
Рис. 8.3. Вид окна программы в режиме выполнения
Глава 8. Работа с файлами Вообще можно за один раз прочитать текстовый файл в Memo. Для этого нужно написать всего лишь одну строку кода: Memol .Lines . LoadFromFile,( 'myf ile. txt') ;
Казалось бы, зачем читать файл по строке, если так проще? Когда необходимо прочитать текстовый файл целиком, тогда, конечно, этот простой способ подходит. Если же вам нужно выбирать по какому-либо критерию, одни строки читать, а другие пропускать, то как раз и пригодится метод чтения по одной строке. Модифицируем наш пример так, чтобы читались все строки, кроме начинающихся с символа «;». Листинг 8.5
procedure TForml. Button3CIick(Sender: TObject); var F: TextFi le;
s: String; , begin AssignFi le(F,myf i le.txt); Reset(F); whi le not Eof(F) do begin ReadLn(F.s); // читаем строку из файла в Memol if so then if s[1]=; then Continue; Memol. Lines. Add(s); // добавляем строки в Memol . end; CloseFile(F); end; После чтения строки из файла в переменную s проверяем: если первым символом строки s является точка с запятой (if s [ 1 ] = ';'), то даем команду перейти к следующей итерации цикла while (читаем следующую строку из файла). Проверка so' ' нужна, чтобы не возникла ошибка чтения файла, если вдруг встретится пустая строка. У пустой строки нет первого символа, который мы пытаемся проверить (s [ 1 ]). В текстовых файлах можно читать и записывать не только строки целиком, но - и отдельные символы. Если строки читаются/записываются с помощью ReadLn/ WriteLn, то отдельные символы — с помощью Read/Write.
8.2. Типизированные файлы Такие файлы я особенно люблю. Писал я как-то код игры и использовал типизированные файлы для удобства ее сохранения. Я создал тип Game (type Game=record), в котором хранил все данные, относящиеся к конкретной игре в текущий момент
Типизированные файлы и к определенному игроку. В общем, это все переменные, которые используются в течение игры и которые нужно сохранять и загружать. Так как все данные об игре хранятся в одной переменной типа Game (который я сам придумал), то для сохранения игры достаточно записать в SAV-файл всего лишь одну эту переменную. Ее размер, конечно, получался большой — десятки, а то и сотни килобайт, но удобство в том, что не нужно было заниматься подготовкой к сохранению, выискивая среди огромного количества те переменные, которые нужно сохранять. А потом заниматься этим же еще и при загрузке сохраненной игры. Все проще: раз — и сохранил игру в виде одной переменной, раз — и загрузил. Удобно, быстро и с минимальной вероятностью ошибиться. И все это благодаря типизированным файлам. Если использовать простейшие типизированные файлы, то тип объявлять не нужно. Например, работа с типизированным файлом типа integer выглядит так. Листинг 8.6 procedure TForml.ButtonlCIick(Sender: TObject); var F: F i l e of Integer; i, n: Integer; begin Randomize; AssignFi le(F,bredt.out); Rewrite(F); for i := 1 to 20 do begin n := Random(10); Write(F.n); end; CloseFi le(F); 5по\*/Меззаде(Файл вроде бы записан.); end; Данная процедура создает файл bredl. out, после чего генерирует 20 случайных чисел и записывает их в файл в нетекстовом виде. Естественно, при просмотре файла текстовым редактором получается ерунда. Обратите внимание на процедуру ShowMessage. Она выводит на экран окно с текстом. Это очень удобный и простой способ донести до пользователя какое-нибудь короткое сообщение. В таком виде типизированные файлы используются редко. Я не помню, чтобы мне хоть раз доводилось пользоваться ими без объявления своего собственного типа. Поэтому перейдем к использованию типизированных файлов с объявлением своего типа данных.
Глава 8. Работа с файлами Создадим новый проект (File > New > Application) и будем делать записную книгу, чтобы вносить в нее адреса друзей и знакомых (данный проект можно найти на компакт-диске, прилагаемом к книге, по адресу ExamplesXNotebook). Поместите на форму все необходимые компоненты: три этикетки (Label), три текстовых поля (Edit) и две кнопки (Button). Измените некоторые свойства формы Forml в соответствии с табл. 8.1. Таблица 8.1. Значения свойств формы Forml Свойство
Значение
Borderlcons biMaximize
False
BorderStyle ,
bsSingle
Caption
Записная книга
Position
poScreenCenter
Остальные свойства оставьте по умолчанию. В свойстве Caption компонентов Labell, Label2 и Labels укажите Фамилия, Год рождения и E-mail. Свойство Text компонентов Editl, Edit2 и Edits сделайте пустыми. Значением свойства Caption кнопки Buttonl должно быть «, а кнопки ButtonZ — ». В итоге должна получиться примерно такая форма, как показана на рис. 8.4. / Записн.чп книга
Фамилия : Год рождения ; E-mail
Рис. 8.4. Форма «Записная книга»
На всякий случай скажу, что мы изменяли и для чего. В свойстве Borderlcons формы можно установить такие параметры: отображать ли меню окна (и кнопку закрытия окна — крестик'в правом верхнем углу), разрешить ли сворачивание окна и разворачивание его во весь экран, показывать ли кнопку помощи (значок вопроса). Изменив значение свойства biMaximize на False, мы запретили разворачивание формы во весь экран, иначе она будет выглядеть не очень симпатично. BorderStyle — стиль формы. Стиль bsSingle означает, что пользователю нельзя изменять размеры формы. Caption — это текст заголовка формы, a Position указывает место, где форма будет появляться на экране (в данном случае в центре). Теперь можно писать код программы. Создадим новый тип данных, который назовем MyBook. Перейдите в окно редактирования текста программы (в его заголовке написано Unitl.pas), найдите раздел type и после слова end; добавьте новый тип данных.
Типизированные файлы Листинг 8.7 MyBook=record f i o : String[150]; year: Integer; emai I: String[150]; end; Откройте редактирование процедуры FormActivate формы Forml: щелкните на форме мышью, а затем в Object Inspector на вкладке Events дважды щелкните в пустом поле рядом со строкой события OnActivate. В редакторе текста программы откроется пустая процедура TForml.FormActivate. Она будет выполняться при активации формы, а активация происходит при запуске программы. Поэтому в начале работы нашего приложения будем проверять, существует ли файл с данными. Если его нет, то создадим пустой файл. Листинг 8.8
procedure TForml. FormAct ivate(Sender: TObject); var F: Fi le of MyBook; p: MyBook; begin // проверяем наличие файла if Fi leExists(book.dat)=False then begin AssignFi le(F,book.dat); Rewrite(F); p . f i o := ; p.year := 0; p . e m a i I := ; Write(F.p); C l o s e F i le(F); end; end; Если создается пустой файл, то в него добавляется одна пустая запись. Здесь была использована функция FileExists. Использовать ее несложно. Функции передается один параметр — имя файла. Она возвращает True, если файл с таким именем существует, и False, если его нет. Пусть сразу же после запуска программы из файла считывается первая запись и данные из нее выводятся на экран. Если файл только что создан, то одна запись все равно выведется — пустая. Для этого мы ее и создавали. Объявим глобальную переменную гее, в которой будет храниться номер текущей записи. Нужно объявить ее в разделе var выше всех процедур перед словом implementation.
Глава 8. Работа с файлами Листинг 8.9
var Form"!: TForml; гее: Integer;
implementation Добавим в процедуру FormActivate чтение первой записи.. Листинг 8.10
// читаем первую запись AssignFi l.e(F, book, dat); Reset(F); Read(F.p); CloseFi le(F); rec := 0; Здесь все просто. Открываем файл, читаем первую запись (то есть нулевую, так как в файлах нумерация записей начинается с нуля) и присваиваем переменной гее номер нулевой записи. Можете запустить получившуюся программу на выполнение (клавиша F9) и удостовериться, что файл book, dat появился в той же директории, где и программа. Напишем код вывода на экран данных из первой записи. Листинг 8.11
// выводим Edifl.Text Edit2.Text Edit3.Text
на экран данные из первой записи := p. f io; := IntToStr(p.year); := p.emai I;
Как видите, обращение к данным идет через точку. То, что мы указывали в описании типа, теперь доступно в переменной р типа MyBook, но через точку. В строке, где выводится на экран год (р. year), применена функция intToStr. Delphi не позволяет присваивать текстовым переменным числовые значения, и наоборот. Поэтому для того чтобы это сделать, нужно сначала преобразовать значения. Для преобразования из текста в число служит функция strToint, а для преобразования из числа в текст — IntToStr. Год у нас хранится в переменной year типа Integer. Чтобы вывести его как string (в текстовое поле), пришлось применить функциюIntToStr. Не забудьте, что две кнопки на форме внизу нужны, чтобы переходить от одной записи к Другой: левая позволяет листать записную книгу назад, а правая — вперед. А что делать, если листать некуда? Нужно отключить эти кнопки, изменив значение свойства Enabled кнопок Button с False на True. Мы это сделаем с помощью программирования — добавим в процедуру активации формы еще такие строки:
Типизированные файлы // включаем/отключаем кнопки if rec=0 then Buttonl.Enabled := False else Buttonl.Enabled := True;
Если запись и так нулевая, то отключаем кнопку листания назад. Если не нулевая (else), то включаем. Правую кнопку (листание вперед) отключать не будем — позже сделаем так, чтобы в случае обнаружения конца файла создавалась новая запись. Переменные для работы с файлами (F и р) сделаем глобальными, чтобы они были доступны каждой процедуре в нашей программе. В данном случае это оправдано, иначе их придется объявлять в каждой процедуре. Поэтому убираем F и р из раздела var процедуры FormActivate и записываем их рядом с объявлением переменной гее. Листинг 8.12
var Formt: TForml; rec: Integer; F: Fi le of MyBook; p: MyBook; Ч
'
'
:
;
• '
•
i mplementat ion
После всех добавлений у нас получилась такая процедура активации формы. Листинге. 13
procedure TForml. FormActivate (Sender: TObject); begin
// проверяем наличие файла
if Fi leExists(book.dat)=False then
begin
AssignFi le(F,book.dat); • Rewrite(F);
p. f io := ; p.year ;= 0; p.emai I := ; Write(F.p); CloseFi le(F);
end; // читаем первую запись AssignFi le(F,book.dat); Reset(F); Read(F.p); CloseFile(F);
rec := 0;
Глава 8. Работа с файлами
// выводим на экран данные из первой записи Edit"!.Text := p.fio; Edit2.Text := lntToStr(p. year); Edit3.Text := p.emai I ; // включаем/отключаем кнопки if rec=0 then Button"!. Enabled := F a l s e else Button"!.Enabled := True; end; Можете запустить приложение и посмотреть, что кнопка листания назад действительно стала неактивной. Теперь напишем код, который будет выполняться при нажатии кнопки листания вперед. В окне редактирования текста программы откройте процедуру click кнопки ButtonZ аналогично тому, как открывали процедуру активации формы. Для этого нужно выделить кнопку Button2 и в Object Inspector на вкладке Events дважды щелкнуть мышью на событии On Click. Можно также дважды щелкнуть на самой кнопке. Тогда сразу откроется окно редактирования текста программы с процедурой Button2Click. Первым делом при переходе к другой записи (при нажатии кнопки) нужно сохранять данные, которые есть на экране. Если пользователь изменил фамилию, год рождения или e-mail, то это автоматически запишется. Здесь нам и пригодится переменная гее, в которой хранится номер текущей записи (она в данный момент на экране). Пока у нас получилась такая процедура. Листинг 8.14
procedure TForml.Button2CIick(Sender: TObject); begin // записываем текущую информацию в файл p. f io := Editl.Text; p.year := StrTolnt(Edit2.Text); p.emai I := Edits.Text; AssignFi le(F,book.dat); Reset(F); Seek(F, rec); Write(F.p); C l o s e F i le(F); end; Приведенная в этом листинге процедура Seek устанавливает позицию чтения/ записи на нужную запись в файле. После чего эту запись можно прочитать или переписать. Процедура Seek не работает в текстовых файлах. Их можно читать и записывать только последовательно! Но сейчас мы, к счастью, работаем не с текстовым файлом, а с типизированным, поэтому и применили процедуру Seek.
Типизированные файлы Если номер текущей записи, например, 12, то Seek установит позицию чтения/записи на эту запись и ее можно будет переписать (с помощью Write) или прочитать (с помощью Read). Только будьте внимательны — записи в файлах De.lphi всегда нумеруются с нуля. Если с помощью Seek неправильно задать позицию чтения/записи, то можно переписать совсем не те записи! Но мы будем делать это правильно — зачем нам лишние проблемы? Итак, в листинге 8.14 заполняется переменная р (используя данные, содержащиеся в текстовых полях), открывается файл с данными нашей записной книги, устанавливается позиция чтения/записи, переписывается запись, и файл закрывается. Теперь нужно сделать возможным переход к следующей записи. Параллельно придется создать новую запись, так как все это привязано к одной кнопке. %
Процедура Button2Click стала такой. Листинг 8.15
procedure TForml. Button2CI ick(Sender: TObject); begin // записываем текущую информацию в файл p. f io := Edit"!.Text; p.year := StrTolnt(Edit2.Text);
. - . . . „ -r p.emai I := Edits.Text; • AssignFi le(F, book.dat); Reset(F); Seek(F, rec); Write(F.p); CloseFi le(F); // переходим к следующей записи Inc(rec); // увеличиваем номер записи на единицу AssignFi le(F,book.dat); Reset(F); if Fi leSize(F)=rec then // обнаружен конец файла, создаем новую запись begin Seek(F, rec); p. f io := ; p.year : = 0; p.emai I := ; Write(F.p); end; // читаем запись Seek(F, rec); Read(F,p); CloseFi le(F);
4 Зак. 899
Глава 8. Работа с файлами // выводим Edit"!.Text Edit2.Text Edit3.Text end;
данные на экран := p.f io; := IntToStr(p.year); := p.emai I;
Таким образом, при нажатии кнопки данные записываются в файл, а затем переменная гее увеличивается на единицу. После чего проверяем, совпадает ли номер записи с размером файла (а так как нумерация с нуля, то в случае совпадения одной записи не хватает — создаем ее). Размер файла определяется функцией FileSize, которую мы и использовали. Наконец, читаем запись и выводим ее на экран. Запустите приложение, чтобы посмотреть, что получилось. Можно вводить данные и щелкать мышью на кнопке листания вправо. В общем, работает, но есть много неудобств. Например, не показывается номер текущей записи. Из-за этого, если ничего не вводить, непонятно, листается вообще записная книга или нет. И если закрыть программу, то последняя введенная запись не сохраняется (ведь мы задали сохранение записи только при нажатии кнопки ButtonZ). Да и левая кнопка все время отключена. Нужно исправить все описанные недочеты. Сначала добавим на форму этикетку (Label), с помощью которой будет выводиться номер текущей записи. Помещаем этикетку Label4 между кнопками (рис. 8.5). * Записная книга
Фамилия Год рождения E-mail '
Рис. 8.5. Добавление этикетки LabeW между кнопками
В свойстве Caption этикетки Label4 ничего не пишем, а оставляем его пустым. Нужно изменить значение свойства AutoSize этикетки Label4 на False, а то она так и норовит изменить свой размер в соответствии с введенным в нее текстом, чего мы совсем не хотим. Нам нужно, чтобы она находилась там, куда ее поместили! И еще, изменим значение свойства A l i g n m e n t той же этикетки на taCenter, чтобы текст на ней располагался по центру. Добавим в конец процедуры FormActivate следующую строку: L a b e l 4 . C a p t i o n := I n t T o S t r ( r e c ) ;
Такую же строку введем в конце процедуры Button2Click. Запустите программу и посмотрите на получившейся результат. Теперь все намного удобнее, чем было. Стало видно, какая по порядку запись сейчас на экране (рис. 8.6).
Типизированные файлы
Рис. 8.6. Программа в режиме выполнения — при нулевой записи левая кнопка неактивна (Enabled=False)
Включим левую кнопку (Buttonl), добавив в конец процедуры Button2Click еще одну строку: if rec>0 then Buttonl.Enabled := True; Теперь стало еще лучше. Настало время написать код для Buttonl. Здесь все очень просто. Нужно лишь сохранить запись, уменьшить гее на единицу, прочитать данные с нужной позиции в файле, вывести их на экран, а также в конце процедуры отключить Buttonl (если запись нулевая) и вывести номер записи. В общем, необходимо сделать практически все то же, что и в случае с ButtonZ. Пишем похожую процедуру. Листинг 8.16
procedure TForml.ButtonlCI ick(Sender: TObject); begin // записываем текущую информацию в файл p. f io := Edit"!.Text; p.year := StrTolnt(Edit2.Text); p.emai I := Edits.Text; AssignFi le(F,book.dat); Reset(F); Seek(F, rec); Write(F.p); CloseFi le(F); // переходим к предыдущей записи Dec(rec); // уменьшаем номер записи на единицу // читаем запись AssignFi le(F,book.dat); Reset(F); Seek(F, rec); Read(F.p); CloseFi le(F); // выводим данные на экран Ed itl.Text := p. fio; Edit2.Text := IntToStr(p.year); Edit3.Text := p.emai I ; // выводим номер текущей записи
Глава 8. Работа с файлами
Label4.Caption := IntToStr(rec); // проверяем номер записи и, возможно, отключаем // 'левую кнопку if rec=0 then Button"!. Enabled := False; end; Запустив программу, вы убедитесь, что сейчас можно листать записную книгу вперед и назад и все записи сохраняются автоматически. Добавим еще сохранение последней введенной записи. Это лучше всего сделать при закрытии окна программы. Таким образом, напишем процедуру обработки события OnClose формы Forml. Аналогично созданию предыдущих процедур (с помощью Object Inspector) в редакторе текста программы открываем процедуру Formclose и вводим в нее такой код (можно скопировать его из процедуры Buttoniciick или Button2Click). Листинг 8.17 procedure TForm"!. FormClose(Sender: TObject; var Action: TCIoseAct ion); begin // записываем текущую информацию в файл p. f io := Edit"!.Text; p.year := StrTol nt(Edit2.Text); p.emai I := Edits.Text; AssignFi le(F,book.dat); Reset(F); Seek(F, rec); W r i t e ( F , p); C l o s e F i le(F); end;
Теперь при выходе из программы выполнится процедура Formclose, и последняя запись автоматически сохранится. Все, задуманный проект готов (рис. 8.7)! Получилась полноценная функционирующая, хоть и простая, записная книга, которую мы создали, используя типизированные файлы. Наша программа работает быстро и занимает мало места.
Фамилия Год рождения Е-тай
}Толян •'•;
[1961 |нет
Рис. 8.7. Получившаяся записная книга Данную программу можно улучшить. Вероятно, вы заметили, что в нескольких местах программы повторяется один и тот же код (в разных процедурах). Такой под-
Нетипизированные файлы ход неправильный. Повторяющиеся фрагменты кода лучше формировать в виде отдельной процедуры и просто вызывать ее нужное количество раз. Но об этом поговорим в гл. 9. Там же приведен полный окончательный текст нашей программы, хотя, если вы все сделали правильно, он у вас уже есть.
8.3. Нетипизированные файлы Данные файлы похожи на типизированные, но отличаются тем, что тип не указывается. Данные читаются целыми частями — по несколько сот или тысяч байт. Это, пожалуй, самый быстрый доступ к файлам. Но разбираться в этих данных придется вручную. Такого автоматического разбора по переменным, как в типизированном файле, здесь нет. Для работы с нетипизированными файлами в Delphi предусмотрены процедуры BlockRead и BlockWrite. Они используются почти так же, как Read и Write, за исключением того, что указывается не переменная, а буфер (массив) и количество записей, которые нужно прочитать или записать. Не совсем понятно? Тогда, чтобы разобраться, нужно привести пример (его можно найти на компакт-диске, прилагаемом к книге, по адресу Examples\FilesReadNWrite). Создадим новый проект. Красоту наводить не будем — в данный момент это ни к чему. Просто поместим на форму две кнопки (Buttonl и Button2) и назовем их Запись и Чтение соответственно (рис. 8.8). В процедуре Buttoniciick будем записывать нетипизированный файл, а в процедуре Button2ciick — его читать.
Рис. 8.8. Форма с двумя кнопками Листинг 8.18
procedure TForml.ButtonlCI ick(Sender: TObject); var BUF: array [1. .100000] of Byte; F: Fi le; begin AssignFile(F,block.dat); Rewrite(F,512); Bloc kWrite(F,BUF, 100); CloseFi le(F); end;
Глава 8. Работа с файлами Таким образом записывается нетипизированный файл. Файловая переменная объявляется так же, как для работы с типизированным файлом, только не указывается тип: F: File;
Открывается файл тоже аналогично. Только в процедурах Rewrite или Reset можно задать длину записи в байтах (в данном примере — 512). Если длину не указать, то она устанавливается равной 128 байт. Затем данные записываются в файл с помощью процедуры BlockWrite. В качестве параметров мы указали файловую переменную F, буфер BUF (массив, из, которого записываются данные) и количество записей (100). Имейте в виду, что если длина записи составляет 512 байт, а в BlockWrite задано 100 записей, то реально на диск из массива записывается 51 200 байт, то есть эти два параметра умножаются друг на друга. Листинг 8.19 procedure TForml. Button2CI ick(Sender: TObject); var BUF: array [1.. 100000] of Byte; F: Fi le; begin AssignFi le(F,block.dat); Reset(F,512); • BlockRead(F,BUF,100); C l o s e F i le(F); end; В приведенном листинге показано, как происходит чтение нетипизированного файла в массив. Здесь все точно так же, как при записи файла, только вместо BlockWrite используется BlockRead. В общем, использовать нетипизированные файлы несложно. Они очень полезны, когда важна скорость работы.
8.4. Удаление записей В Delphi нет готовой процедуры для удаления записей из файла. Поэтому приходится придумывать способы сделать это. Я обычно использую следующий метод. Чтобы удалить запись из файла, нужно открыть еще один файл и скопировать в него по порядку все записи из первого файла, за исключением удаляемой. Затем файлы закрывают, и второй файл переименовывают, чтобы его имя стало как у первого. В следующем примере из файла out. dat удаляется пятая запись.
Удаление записей Листинг 8.20
procedure TForml.Button2CI ick(Sender: TObject); var F1: Fi le of Integer; // файл, из которого удаляется запись р: Integer; F2: Fi le of Integer; // временный файл I : Integer; begin // открываем файл, из которого удаляется запись AssignFile(F1,out.dat); Reset(F1); // создаем временный файл AssignFi le(F2,temp); Rewrite(F2); i := -1; // счетчик записей // читаем каждую запись в цикле whi le not Eof(F1) do begin Read(F1,p); Inc(i); // увеличиваем счетчик записей // если запись не пятая, то выносим ее во временный файл if i<>5 then Write(F2,p); end;
,
// закрываем файлы CloseFile(F2); C l o s e F i le(F1); // удаляем основной файл Erase(F1); // переименовываем временный файл в основной Rename(F2,out.dat); end;
Разумеется, можно удалять запись не только по номеру, но и по любым другим параметрам (например, по ее содержимому). Можно усовершенствовать алгоритм, используя процедуру Truncate, которая удаляет все записи, начиная с текущей. Данный метод иногда медленно работает с очень большими файлами. Поэтому если размер файла, из которого нужно удалять запись, очень большой, то имеет смысл применять другой метод, который даже проще первого. Он заключается в том, что мы не удаляем запись физически, а просто помечаем ее как удаленную. Конечно, это получится только в случае использования типизированных файлов. При этом размер самого файла не будет изменяться. Например, имеем типизированный файл такого типа.
Глава 8. Работа с файлами Листинг 8.21
DelFi le=record рг: Boolean; fio: String[200]; n: Integer; end; Для обозначения того, удалена запись или нет, создано поле рг типа Boolean. При создании новой записи рг присваивается значение False. Это означает, что запись не удалена. Если ее нужно удалить, то рг просто присваивается True. Естественно, при работе с файлом нужно учитывать поле рг (если pr=True, то считать запись несуществующей). Остальные поля записи используются как обычно. В процессе работы записи просто помечаются как удаленные, а затем, по мере их накопления, выполняется какое-то действие (сжатие, индексация), вместе с которым уничтожаются помеченные записи. Кстати, именно так и удаляются записи практически во всех СУБД (системах управления базами данных). Положительная сторона данного метода еще и в том, что помеченные, но еще не удаленные окончательно записи можно восстановить! Такой метод удаления записей работает очень быстро независимо от размера файлов. Но это не совсем удаление, поэтому лучше использовать комбинацию этих двух методов.
8.5. Обработка ошибок При использовании файлов ошибки могут возникать чаще, чем при каких-либо других действиях: пользователь удалил файлы, файлы не читаются, отсутствует гибкий диск или компакт-диск в приводе и т. д. Поэтому важно правильно обработать такие ошибки. В принципе если даже не писать никакого кода для обработки ошибок, то Delphi вполне приемлемо сделает это самостоятельно. Это значит, что программа не закроется, прекратив выполнение, а просто выдаст сообщение об ошибке. Но можно сделать программу более дружелюбной по отношению к пользователю. Для обработки ошибок при работе с файлами существует функция lOResult, которая возвращает код ошибки последней операции с файлами. Если ошибка не произошла, то возвращается нуль. При использовании lOResult нужно отключать встроенный обработчик ошибок ввода-вывода. В этом деле поможет директива ($!-}. Соответственно, после вызова lOResult встроенный обработчик ошибок неплохо бы включать. Для этого существует директива {$! + }. Ошибки при работе с файлом обрабатываются следующим образом.
Обработка ошибок Листинг 8.22 procedure TForml.ButtonlCI ick(Sender: TObject); var F: F i l e ; begin
AssignFi le(F,ourfi le.dat);
{$(-} Reset(F);
{$!+} if lOResultoO then begin ShowMessage(VBbi и ах, файл открыть не удалось. ..); Exit;
end; {здесь находится блок работы с файлом} C-loseFi le(F); end;
Результат выполнения приведенной программы показан на рис. 8.9. Соответственно, таким же образом обрабатывать события можно не только при открытии файла (Rewrite, Reset), но также при его чтении и записи.
. ^ f" Ш • S3 i Pj &
.
,.
JAddKondj Win32) Swslemj Data Access! Dala Conttds j dbExaess j DataSnaoi BDE |-llt
!
V % A PET i| sj i» e ШШ^и Ш U iffi
1ШШ
ОэШ Запись
'^
Чтение
Увы и ах. Файл открыть не удалось...
скрыть не у д а л о с ь . . . ' ) ;
If "ТЯГ—!|
1
44: 1
JModfied
jinsert
|War™ng|Unil1.pas(52): Unsafe code tlockRead'
И IF Рис. 8.9. Корректная обработка ошибки
j \Code/Diaaiam/
3
Глава 8. Работа с файлами
8.6. Процедуры и функции для работы с файлами Рассмотрим их в алфавитном порядке: •
Append — открывает существующий текстовый файл в режиме добавления. Новые строки записываются в конец файла. Работает только для текстовых файлов;
• AssignFile — ассоциирует имя внешнего файла с файловой переменной. Эта процедура всегда должна предшествовать любым другим действиям с файлами; • BlockRead — читает одну или более записей из нетипизированного файла в массив; • BlockWrite — вносит одну или более записей из массива в нетипизированный файл; • Choi r — изменяет текущую директорию. По умолчанию директория для чтения или записи совпадает с той, откуда запускалась программа (ЕХЕ-файл); •
CloseFile — закрывает открытый файл. Если после вызова данной процедуры нужно выполнять какие-либо действия с файлом, то необходимо вызвать AssignFile;
• Eof — возвращает True, если обнаружен конец файла. В противном случае возвращает False; • Eoln — возвращает True, если обнаружен конец строки в текстовом файле. В противном случае возвращает False; • Erase — удаляет файл; •
FilePos — возвращает текущую позицию чтения/записи. Нумерация начинается с нуля;
• FileSize — возвращает размер файла. Эта функция не работает с текстовыми файлами; •
Flush — записывает буфер на диск при выводе текста в текстовый файл;
• Getoir — возвращает текущую директорию на выбранном диске; • lOResult — возвращает код ошибки последней операции с файлом (целое значение). Если операция была завершена успешно, то данная функция возвращает нуль; •
MkDir — создает директорию;
• Read — читает запись из файла. При использовании с текстовым файлом читает один символ, с нетипизированным — может читать несколько записей; • ReadLn — читает строку из текстового файла; • Rename — переименовывает файл; • Reset — открывает файл для чтения и записи;
Процедуры и функции для работы с файлами • Rewrite — создает и открывает новый файл; • RmDir — удаляет пустую директорию; • Seek — перемещает позицию чтения/записи в файле. Не используется с текстовыми файлами; • SeekEof — пропускает все пробелы, знаки табуляции и маркеры конца строки Eoln др маркера конца файла Eof или до первого значащего символа и возвращает True, если маркер обнаружен. Работает исключительно с текстовыми файлами; • SeekEoln — пропускает все пробелы и знаки табуляции до маркера конца строки Eoln или до первого значащего символа и возвращает True, если маркер обнаружен. Работает исключительно с текстовыми файлами; •
SetTextBuf — ассоциирует буфер ввода/вывода с текстовым файлом;
• Truncate — удаляет все записи в типизированном или нетипизированном файле, начиная с текущей позиции; • W r i t e — помещает запись в файл. При использовании с текстовым файлом записывает один символ, с нетипизированным — может вносить несколько записей; •
WriteLn — записывает строку в текстовый файл.
Глава 9
Техника программирования Q Оптимизация программы О Комментарии Q Точный подбор цвета
Оптимизация программы
9.1. Оптимизация программы Когда программист пишет текст программы, он обычно работает «на два фронта». Дело в том, что программу читает не столько пользователь, сколько компьютер. Нужно оптимизировать программу для компьютера, то есть писать ее так, чтобы она работала быстро и, по возможности, без ошибок, а также занимала меньше памяти. Делать же так, как показано в следующем листинге, не рекомендуется. Листинг 9.1
А[2] А[3] А[4] А[5]
:= := := :=
В[2]; В[3]; В[4]; В[5];
Пример хоть и абсурдный, но взят из практики. Естественно, в данном случае обязательно нужно использовать цикл. Листинг 9. 2
for i := 1 to 5 do
Когда компилятор преобразовывает текст программы в машинный код, циклы тоже организуются. В машинном коде это возможно, хотя не так явно и удобно, как предусмотрено в языке высокого уровня Delphi. Поэтому, как правило, если текст программы меньше, то и получаемый машинный код получается компактнее. Подчеркиваю, так бывает не всегда, но циклы целесообразно использовать, если однообразное действие повторяется более двух-трех раз. Иногда похожие и повторяющиеся действия удобно организовать в виде процедуры или функции. И потом вызывать ее, когда необходимо выполнить эти действия. Увеличивать переменную на единицу лучше не так: n := n + 1;
а следующим образом: Inc ( n ) ;
Соответственно, при уменьшении переменной на единицу вместо n : = n - 1 лучше применять Dec (n) . Операции Inc и Dec выполняются быстрее присваивания и занимают меньше места в памяти. В качестве объекта оптимизации можно использовать программу «Записная книга» (рис. 9.1), которую мы создавали в разд. 8.2. Рассмотрим текст этой программы и выясним, как ее можно оптимизировать.
Глава 9. Техника программирования Записная книга
Фамилия
j Майк
Год рождения E-mafl
:
[email protected]
Рис. 9.1. Записная книга, созданная ранее
'
Стандартное начало всех листингов, которое Delphi генерирует автоматически (unit, uses, класс формы), здесь и далее приводить не буду, чтобы не давать лишнюю информацию. Если вам нужны полные тексты программ, то можете найти их на компакт-диске, который прилагается к книге. Листинг 9.3
type MyBook=record f i o : String[150]; year: Integer; emai I: String[150]; end; var
Form"!: TForml; rec: Integer; F: Fi le of MyBook; p: MyBook; implementat ion {$R *.dfm} procedure TForml.FormActivate(Sender: TObject); begin // проверяем наличие файла if Fi leExists(book. dat) = False then begin AssignFi le(F,book.dat); Rewrite(F); p.fio := ; p. year := 0; p.emai I := ; Write(F, p); CloseFi le(F); end; // читаем первую запись AssignFi le(F,book.dat);
Оптимизация программы
Reset(F); Read(F.p); CloseFi le(F); rec := 0; // выводим на экран данные из первой записи Edit"!.Text := p. f io; Edit2.Text := IntToStr(p.year); Edit3.Text := p.emai I ; // включаем/отключаем кнопки if rec=0 then Button"!. Enabled := False else Button"!. Enabled := True; Label4.Caption := IntToStr(rec); end; procedure TForm1.Button2CIick(Sender: TObject); begin // записываем текущую информацию в файл p.fio := Editl.Text; p.year := StrTolnt(Edit2.Text); p.emai I := Edits.Text; AssignFi le(F,book.dat); Reset(F); Seek(F, rec); Write(F.p); CloseFile(F); // переходим к следующей записи Inc(rec); // увеличиваем номер записи на единицу AssignFi le(F, book.dat); Reset(F); if FileSize(F)=rec then // обнаружен конец файла, создаем новую запись begin Seek(F, rec); p.fio ;= ; p. year := 0; p.ema i I : = ; Write(F.p); end; // читаем запись Seek(F, rec); Read(F, p); CloseFile(F); // выводим данные на экран Editl.Text := p.fio; Edit2.Text := IntToStr(p.year); Ed its.Text := p.emai I ; Label4.Caption := IntToStr(rec);
Глава 9. Техника программирования if гес>0 then Button!.Enabled := True; end; procedure TForml.ButtonlCI ick(Sender: TObject); beg i n
// записываем текущую информацию в файл p. f io := Edit"!.Text; p.year := StrTolnt(Edit2.Text); p.emai I := Edits.Text; AssignFi le(F,book.dat); Reset(F); Seek(F, rec); Write(F.p); CloseFi le(F); // переходим к предыдущей записи Dec(rec); // уменьшаем номер записи на единицу // читаем запись AssignFi ie(F,book.dat); Reset(F); Seek(F, rec); Read(F.p); CloseFi le(F); // выводим данные на экран Ed itl.Text := p.fio; Edit2.Text := lntToStr(p. year); Ed it3.Text := p. emai I ; // выводим номер текущей записи Label4.Caption := IntToStr(rec); // проверяем номер записи и, возможно, отключаем // левую кнопку if rec=0 then Button"!. Enabled := False; end; procedure TForml. FormClose(Sender: TObject; var A c t i o n : T C I o s e A c t ion); begin
// записываем текущую информацию в файл p.fio := Edit"!.Text; p.year := StrTolnt(Edit2.Text); p.emai I := Edits.Text; AssignFi le(F,book.dat); Reset(F); Seek(F, rec); Write(F.p); CloseFi le(F); end;
end.
Оптимизация программы Обратите внимание, что текущая информация записывается в файл в нескольких процедурах. Причем фрагменты программы, выполняющие запись в файл, совершенно одинаковые! Писать программу подобным образом нерационально. Нужно выделить данные фрагменты в отдельную процедуру, а потом вызвать ее нужное количество раз. Это уменьшит размер получаемого ЕХЕ-файла и повысит удобочитаемость текста программы. Создаем процедуру TForml .WriteToFile. Листинг 9.4 procedure TForml.WriteToFi le; // записываем текущую информацию в файл begin p. f i o := Edit"!. Text; p.year := StrTolnt(Edit2.Text); p.emai I := EditS.Text; AssignFi le(F,book.dat); Reset(F); Seek(F, rec); Write(F.p); C l o s e F i le(F); end;
В процедурах Button2Click, Buttoniciick и FormClose заменяем приведенный в листинге 9.4 фрагмент на вызов процедуры WriteToFile. Программа сразу стала менее громоздкой! Не будем останавливаться на этом — оптимизировать так оптимизировать! Ведь не только запись в файл, но и чтение выполняется у нас из разных процедур. Чтение записи и вывод на экран присутствуют в процедурах FormActivate, ButtonlClick И Button2Click. Выделяем чтение из файла тоже в отдельную процедуру. Листинг 9.5 procedure TForml.ReadFromFi le; // читаем запись и выводим ее на экран
beg in // читаем запись AssjgnFi le(F, book.dat); Reset(F); Seek(F, rec); Read(F.p); CloseFi le(F); // выводим данные на экран Edit"!. Text := p. fio; Edit2.Text := IntToStr(p.year); Edit3.Text := p.emai I ;
end;
Глава 9. Техника программирования В процедурах Buttoniciick и Button2ciick заменяем приведенный выше фрагмент вызовом процедуры ReadFromFile. А в FormActivate придется заменить следующие строки. Листинг 9.6 // читаем первую запись AssignFi le(F, book.dat); Reset(F); Read(F.p); C l o s e F i le(F); rec := 0;
// выводим на экран данные из первой записи Edit"!.Text := p . f i o ; Edit2.Text := IntToStr(p.year); Edit3.Text := p.emai I; Заменяем их таким фрагментом программы. Листинг 9.7 гее := 0;
ReadFromFi le; Аналогично чтению и записи выделяем в отдельную процедуру создание новой записи. Новая запись создается только в двух процедурах программы. Но все равно организация отдельной процедуры оправдана, так как в данном случае код короче. К тому же повышается удобочитаемость текста программы пользователем. И если придется что-то изменять, то проще сделать это в одной отдельной процедуре, чем вносить одинаковые изменения в несколько фрагментов кода. Пишем процедуру создания новой записи. Листинг 9.8 procedure TForml.NewRec; // создаем новую запись begin Seek(F, rec); p. f io := ;
p. year : = 0; p.emai I : = ; Write(F.p); CloseFi le(F); end;
В данной процедуре пропущены функции открытия файла. Это сделано потому, что процедура NewRec вызывается из Button2ciick, где файл открывается с помощью Reset, а также из Activate For m, где файл создается при помощи Rewrite. Поэтому файл открывается до вызова процедуры NewRec.
Оптимизация программы Кстати, после того как в процедуре Button2Click вместо большого фрагмента программы появилась всего одна строка — вызов процедуры NewRec (листинг 9.9), — отпала необходимость в begin и end (листинг 9.10). Листинг 9.9 if Fi leSize(F)=rec then // обнаружен конец файла, создаем новую запись begin NewRec; end;
Листинг 9.10 if Fi leSize(F)=rec then // обнаружен конец файла, создаем новую запись NewRec; После проведенной нами работы возможностей для оптимизации программы практически не осталось. Может быть, можно еще что-то улучшить, но это уже не столь очевидно, как выполненные действия. Кстати говоря, неплохо бы изначально продумывать написание программы, чтобы потом не нужно было ее оптимизировать. Осталось привести полный текст программы после оптимизации. Программа стала короче, при этом понятнее и проще для модернизации, а действия выполняет те же. Листинг 9.11 type MyBook=record fio: String[150]; year: Integer; emai I : S t r i n g [ 1 5 0 ] ; end; var
Form"!: TForml; rec; Integer; F: Fi le of MyBook; p: MyBook; implementation {$R *.dfm} procedure TForml. FormAct ivate(Sender: TObject); begin // проверяем наличие файла
i
Глава 9. Техника программирования if Fi leExists(book.dat)=False then begin
AssignFile(F,book.dat); Rewrite(F); NewRec; // создаем новую запись end; гее := 0; ReadFromFi le; // включаем/отключаем кнопки if rec=0 then Button"!. Enabled := False else Buttonl.Enabled := True; Label4.Caption := IntToStr(rec); end; procedure TForm1.Button2CI ick(Sender: TObject); begin WriteToFi le; // записываем текущую информацию в файл Inc(rec); AssignFi le(F, book, dat); Reset(F); if Fi leSize(F)=rec then // обнаружен конец файла, создаем новую запись NewRec; ReadFromFi le; // читаем запись Label4.Caption ;= lntToStr(rec); if rec>0 then Buttonl.Enabled := True; end; procedure TForml.ButtontCI ick(Sender: TObject); begin WriteToFi le; // записываем текущую информацию в файл Dec(rec); ReadFromFi le; // читаем запись Label4.Caption := IntToStr(rec); // выводим номер записи // проверяем номер записи и, возможно, отключаем // левую кнопку if rec=0 then Buttonl.Enabled := False; end; procedure TForml. FormClose(Sender: TObject; var Action: TCIoseAction); begin
WriteToFi le; // записываем текущую информацию в файл end; procedure TForml.WriteToFi le; // записываем текущую информацию в файл beg in
Оптимизация программы p.f io := Edifl .Text; p.year := StrTolnt(Edit2.Text); p.emai I := Edit3.Text; AssignFi le(F,book.dat); Reset(F); Seek(F, rec); Wrtte(F.p); C l o s e F i le(F); end; procedure TForml.ReadFromFi le; // читаем запись и выводим ее на экран begin A s s i g n F i le(F, book.dat); Reset(F); Seek(F, rec); Read(F,p); C l o s e F i le(F); Edit"!.Text := p. f io; Edit2.Text := I ntToStr(p.year); Edit3.Text ;= p.emai I; end; procedure TForml.NewRec; // создаем новую запись begin Seek(F, rec); p.fio : = ; p. year := 0;
p . e m a i I := ; Write(F.p); C l o s e F i le(F);
,
end;
При создании программы не стоит забывать, что пользователи тоже будут ее читать. Если вы работаете в команде и участвуете в разработке какого-либо проекта, то ваш код точно будут читать другие пользователи. Если же, например, вы самостоятельно пишете программу на Delphi, то все равно через некоторое время будете ее просматривать. Да и непосредственно при написании программы проще, если все оформлено красиво и легко читается. Чтобы программа проще воспринималась, нужно делать отступы от левого поля. Но не как попало, а выделять отступами тело цикла, составные операторы beginend. Для повышения удобочитаемости можно также отделять оператор присваивания с двух сторон пробелами (а : = 5). Специально иллюстрировать отступы и пробелы незачем — вы можете увидеть их практически в любом листинге этой книги.
Глава 9. Техника программирования Некоторые программисты не особо задумываются над названиями переменных. Они так сосредоточены на написании программы, что для придумывания названий переменных у них не остается ни сил, ни энергии, ни фантазии. В таком случае есть один способ, помогающий создать уникальное имя переменной, нисколько не думая. Просто наберите на клавиатуре несколько любых символов. Получится что-то вроде sdhgf d — вполне подходит в качестве имени переменной. Однако не спешите радоваться! Сейчас я огорчу тех читателей, кому данный способ понравился. Переменные нужно называть так, чтобы их имена напоминали о том, для чего предназначена каждая переменная. Допустим, переменную, которая используется для суммирования, нужно назвать total, summa или sum. Тогда текст программы будет проще воспринимать. Легче будет и вспомнить, для чего нужна та или иная переменная. Таким образом, включить воображение все равно придется.
9.2. Комментарии Когда я только постигал азы программирования, то узнал, что желательно писать в тексте программы комментарии. Естественно, я отнесся к этому довольно скептически, не понимая, зачем нужны комментарии. Над программой я работаю один, следовательно, знаю и помню, что. в ней написал. Однако прошло некоторое время, и наступил момент, когда пришлось создавать новую версию какой-то программы. Тогда я и пожалел, что не пояснил комментариями фрагменты кода. Я многое забыл, и, чтобы внести изменения в программу, сначала пришлось долго в ней разбираться. С тех пор я снабжаю свои программы комментариями. И делаю это даже тогда, когда уверен, что не буду переписывать программу. Когда уверен, что не будешь вносить изменения в программу, — непременно придется это сделать! Но я вам не буду советовать писать комментарии в тексте программы. Только когда вы сами однажды обнаружите, что ничего не понимаете в программе, которую сами же написали несколько месяцев назад, тогда комментарии в программе вам будут просто необходимы! И вы будете писать их с удовольствием! Как уже говорилось в разд. 3.3, для добавления комментариев в Delphi можно использовать две косые черты //, а также фигурные скобки { ) . Короткий комментарий (строка или часть строки) проще оформить с помощью косых черточек, а длинный (несколько строк) — поместить в фигурные скобки. Комментариев в тексте программы не должно быть слишком мало или много, а ровно столько, сколько необходимо для понимания того, что выполняется в комментируемом фрагменте кода, для чего нужна та или иная переменная. Писать длинные пояснения не нужно. Рассмотрим пример программы с комментариями, необходимыми для ее понимания.
Точный подбор цвета Листинг 9.12 procedure TForm1.MakeMoney(m: Integer); 1 // создание денег с помощью чит-кода // т- количество добавляемых денег begin if lnputCode=False then Exit; // запрос чит-кода Randomize; m := Random(IOOOO); A.money := A. money + m;
;
end;
Как видите, комментариев немного, но все фрагменты, по которым могут возникнуть вопросы, снабжены комментариями. Теперь приведу пример неправильного употребления комментариев в той же процедуре. Листинг 9.13 procedure TForm1.MakeMoney(m: Integer); // процедура для создания денег чит-кодом // т- количество добавляемых денег begin if InputCode-False then Exit; {если введенный пользователем код неправильный, то выполняется выход из процедуры} Randomize; // инициализация генератора случайных чисел m := Random(IOOOO); // генерирование денег (случайное число) A.money := A.money + m; // добавление денег
end; Данный листинг перегружен комментариями. Своей функции — облегчать чтение программы — комментарии не выполняют, потому что такое нагромождение только вредит. Программист и так знает, что Randomize инициализирует генератор случайных чисел. Не нужно указывать это еще и в комментарии. Комментировать же предназначение переменных очень желательно.
9.3. Точный подбор цвета В Delphi свойству Color или переменной типа TColor в качестве значения можно передать цвет. Вообще тип TColor включает в себя числовые значения от -$7FFFFFFF-1 до $7FFFFFFF. И если вам хочется сделать компонент какого-то нестандартного оттенка, то придется поэкспериментировать с числовыми значениями. Те читатели, которые хотя бы иногда играют в компьютерные игры, наверное, знают, что во многих играх есть возможность, нажав определенную комбинацию клавиш или введя какое-то секретное слово (это называется чит-кодом), увеличить количество денег на игровом счете либо выполнить какие-нибудь подобные действия. Этот пример как раз иллюстрирует, как реализовать такой эффект.
Глава 9. Техника программирования Обычный красный цвет, который входит в стандартные (см. табл. 7.1), можно получить следующим образом: Labell.Color := clRed;
Абсолютно такого же красного цвета можно добиться, присвоив такое значение: Labell.Color := $OOOOFA; FFOOOO
Как же подобрать число, которое соответствует нужному цвету и оттенку. Конечно, можно просто подставлять разные значения одно за другим и смотреть, что получилось. Но есть более эффективный способ! Для этого нам нужны следующие программы: графический редактор Paint, встроенный в Windows Калькулятор и, естественно, Delphi. Вместо Paint можно использовать Adobe Photoshop, так как работать будем только с палитрой цветов. Заменить стандартный К а л ь к у л я т о р Windows можно любым другим калькулятором для инженерных расчетов. А вот без Delphi не обойтись. Сначала нужно визуально подобрать цвет, который мы хотим задать для Labell. Запускаем редактор Paint и открываем в нем окно изменения палитры цветов (рис. 9.2): Палитра > Изменить палитру > Определить цвет. Если же вы работаете в Photoshop, то перейдите на палитру Color (Синтез) (рис. 9.3).
Основная палитра1
вгтгтг • г г г~ г т т т
тттттттт
щлшттштг Дополнительные цвета:
г— r-~ j — — |__ ^ — | — —
i
I
1
I
I
i
ш
1
Оттенок: |[160 Красный: }5
j
Контрастно
j
Зеленый |0
аркость:.[о
Отмена }
Добавить в набор
Для получения справки выберите КОМАНДУ 'Вызов справки" из меню "Справка". :ЩП^\ @$ $ ^ О ЩШ W «9 ij B^ej gaWindo,.,j ВУМ1сго»...| 5SMicio5..
Рис. 9.2. В Paint цвет подбирается в окне «Изменение палитры»
Теперь можно представить себя художником и подобрать какой-нибудь нестандартный цвет. Не зря же существуют видеокарты с поддержкой 24-битного цвета?
Точный подбор цвета В общем, подбирайте любой цвет нужного вам оттенка. При этом обращайте внимание на три окошка, в которых записывается цифровое представление цвета. Например, если выбрать просто красный цвет, то в данных окошках будет стоять 255, 0, 0. У оранжевого цвета эти параметры другие: 255,128, 0 (рис. 9.4, 9.5). После окончательного подбора цвета данные числа нам как раз и понадобятся. .-, Adobe Photoshop Файл Редактирование Изображение
Слой Выделение Фильтр
Просмотр Окно Помощь
[Ч»рм»пД;;;;;;^| Непрозрачность: IQOtf Толщина: fs
пике. Г? Сглаживание
Стрелки: Г* В нэчэле J? 8 конце Фооиэ.
j2s
и. \
^«"«'••••«•^^<=i ШшШВВ! Reduce Graininess
Drop Shadow (full image) Vignette (full image)
lassdii Ё.I* и в •§ в .вг. • Рис. 9.3. В Photoshop цвет можно подбирать на палитре Color (Синтез) (она справа посередине) Изменение палитры Основная палитра:
• тггтг-ттт • ГГ~РГ-ИШ тттттштш ттттвттт ттяттштг Дсюоямительмые цвета.
ГГГГГПГГ
гггггггг
Опенок: ]20
J255
Контраст: |240 Цвег|3алиека gp^c,,,: [laT Добавить в набор
Рис. 9.4. Подбор оранжевого цвета в Paint
Синий: : :
Глава 9. Техника программирования
[128 JO-
Рис. 9.5. Подбор оранжевого цвета в Photoshop
Сейчас нужно преобразовать числа в шестнадцатеричное представление. Это можно сделать и вручную, написав программу для пересчета, но проще всего взять калькулятор. Однако любой не подойдет, а только преобразовывающий числа из одной системы счисления в другую. Встроенный в Windows Калькулятор выполнять эту операцию, если включить режим Вид > Инженерный (рис. 9.6). Щ Калькулятор Дравка Еиа | Справка
Hex
|Г I Sta |
(? Dec
Г Oct
<"" Bin
'FHVPj ••["' j~~ 14 |
i [
I |
MC |
II If Desrees Г Badians :
"ВжКвим>|
7 |
СЕ
8 | 9 |
4 I
5 |
i~
/ [ Мн) | 'And j
6
2
Рис. 9.6. Инженерный вид калькулятора
Допустим, в графическом редакторе мы выбрали оранжевый цвет, который характеризуется значениями 255,128, 0. Правка Дид Справка
il? Hen
<~ Dec
!Г Inv
Г Hyp
Г Oct ! j
Г Bin [
|| 0 Dwcxd
Г Woid
Backspace
C£
<~ Byte I
j C
I
.'Miidj
Рис. 9.7. Число 255 в шестнадцатеричном представлении (Hex)
Точный подбор цвета Переведем их все в шестнадцатеричное представление. Для этого введем на калькуляторе число 255 и установим переключатель, находящийся слева вверху, в положение Hex. Получился результат FF (рис. 9.7). Записываем это значение куда-нибудь или запоминаем его и преобразовываем следующее число (переключив калькулятор из режима Hex в Dec). Затем так же поступаем и с третьим числом. У нас получилось FF, 80, 00. Присвоим данные значения свойству Color. Только нужно присваивать их в обратном порядке и поставить перед ними знак $, что означает шестнадцатеричную систему счисления: Labell.Color := $0080FF; FF8000
Таким образом можно отобразить любой оттенок. По мере приобретения опыта вы будете задавать цвета даже без использования графического редактора. Допустим, могу навскидку сказать, что $229000; DD9022 — это тоже оранжевый цвет, только более бледный и немного другого оттенка, чем $FF8000.
Глава 10
Компиляция и отладка программы Q Компиляция О Отладка
Компиляция 10.1. КОМПИЛЯЦИЯ Delphi является компилирующей, а не интерпретирующей системой. Разница между этими двумя видами систем огромная. На мой взгляд, компиляторы предпочтительнее интерпретаторов, и вот почему. Когда вы создаете программу в интерпретаторе, она не переводится в машинный код, а хранится либо в виде текста программы (в одних интерпретаторах), либо в виде набора каких-то команд, как вариант, в Р-коде2 (в других интерпретаторах, например в ранних версиях Visual Basic). Такая программа преобразуется в машинный код прямо в процессе выполнения. Плюсы у такого подхода, конечно, есть, но минусы очень значительные. Например, гораздо меньшая скорость выполнения, чем у скомпилированных программ. К тому же интерпретатор должен все время находиться вместе с программой — компьютер без него не понимает программу. Совсем другое дело — компиляторы. Когда вы запускаете программу на выполнение в компилирующей системе программирования, программа сначала преобразуется в машинный код (понимаемый компьютером без каких-либо переводчиковинтерпретаторов), а потом запускается. Поэтому программа выполняется быстрее, и вместе с ней не нужно использовать интерпретатор. Чтобы в Delphi запустить компиляцию программы, нужно нажать комбинацию клавиш Ctrl+F9. При этом текст программы преобразуется в машинный код и создастся файл с расширением ЕХЕ, который можно запускать и который будет работать (независимо от того, установлена на компьютере система Delphi или нет). Различные параметры проекта (в том числе касающиеся компиляции) можно задать в окне Project Options, вызываемом командой Project > Options (или сочетанием клавиш Shift+Ctrl+Fll). На вкладке Application (рис. 10.1) можно поменять значок программы, загрузив его с помощью кнопки Load Icon. Значок должен иметь расширение ICO. Вместе с Borland Delphi поставляется большое количество значков программы, но можно создать и свой. В пакете Delphi присутствует приложение Image Editor (см. рис. 2.7), которое позволяет как нарисовать значок, так и преобразовать в него подходящий BMP-файл. Возможно, стоит заглянуть на вкладку Compiler окна Project Options (см. рис. 2.6) и изменить некоторые параметры так, как больше подходит создаваемому вами приложению. В области Code generation есть флажок Optimization. Обычно он установлен. Если при создании программы всё в порядке, то пусть данный флажок остается. Тогда приложение, скорее всего, будет занимать меньше места и работать чуть быстрее. Но если возникают непонятные и необъяснимые ошибки (в программе все правильно, а она не работает), то попробуйте отключить оптимизацию Р-код — это набор команд, понимаемых интерпретатором, который выполняет программу, преобразуя Р-код в машинный.
Глава 10. Компиляция и отладка программы (снять флажок Optimization). Иногда причина ошибок именно в ней, хотя это случается крайне редко. За всю мою практику такое было всего лишь однажды. Обычно ошибки все-таки кроются в неправильно написанной программе.
Difectoiies/Conditronals Forms
Application
r Application settings . .
j j
Compiler
Version Info |
]
Packages
Compiler Messages
-—..-.—.-;.—^,_m™™™ . ;
|
Linker
,„,„„...,..™...„™,. ,„..„.„.. ; j
л
Load Icon...
'' Output settings •" Т arget f Ue extension:
г gs«i;.
•Cancel:
-№*;
Рис. 10.1. Окно параметров проекта, вкладка Application
В области Runtime errors вкладки Compiler можно устанавливать или снимать флажки, означающие автоматическую проверку определенных типов ошибок в откомпилированной программе. По умолчанию проверяются только ошибки ввода-вывода (1-0 checking), но можно включить и контроль выхода .за пределы массивов (Range checking), и проверку переполнения переменных (Overflow checking). На вкладке Directories/Conditionals можно указать директории для разных целей. Например, директорию, в которую следует помещать созданный ЕХЕ-файл. Вкладка Version Info нужна, чтобы указать информацию О разработчике программы (то есть о себе). Введенные здесь данные отображаются в свойствах ЕХЕ-файла. Их можно посмотреть, щелкнув правой кнопкой мыши на ЕХЕ-файле и выбрав строку Свойства. Изменять эти свойства можно после установки флажка Include version information in project.
10.2. Отладка Обычно если пишут о какой-нибудь системе программирования, то, рассматривая отладку программ, говорят, что система имеет невероятно мощные и удобные средства отладки. Не хочется начинать описание отладки со стандартных слов, но именно так и придется сделать. Потому что Delphi действительно имеет мощные и удобные средства отладки программ. И с каждой новой версией все мощнее и удобнее.
Отладка Коснемся лишь основных инструментов отладки, так как полное описание их всех значительно увеличит объем книги. Самым элементарным из них является область, появляющаяся в окне редактирования текста программы при компиляции, если были найдены какие-то ошибки. Эти ошибки там и перечисляются (рис. 10.2). ^B _ Inixi
lfru'1 pas :
nit1".
' . •:
---','. ;
._'•-. /V.,
''.-:."'"- ".'•' '•"
*" - '•*. ';,
iCloseFile(F) ; lend; ;procedure Coroon; Ibegin lend;
J
lend.
J_J ' ) ' " • 52:1:2
'JModfed
{insert'
i
[\Code /Diagram/
[Warning] Unitl ,pas(35]: Unsafe code BlockWHIe1 [Warning] Unit! .pas[4E): Unsafe code BlockRead1 К (Error] Ural1.pas(52]: Undeclared identifier: •ShowMwssage1 \fait Frtnrl PinixrH HnrfRI ГпмИ nnl nnm™!S 2SiJ IS* '> !™U 2ji \BuM/ • . .•--...••:•;:.;:..; ^ ^ .'':::-':' _ _
!
.
'"
Рис. 10.2. Предупреждения (Warning) и ошибки (Error) в окне редактирования кода. Выделена строка, в которой обнаружена ошибка
Например, напишем такие строки и запустим компиляцию (Ctrl+F9). Листинг 10.1 procedure Comon; beg i n . ShowMwssage(llpo6Hafl надпись); end;
В данном случае имеется опечатка в вызове функции ShowMessage. В нижней области появились следующие надписи: [Error] U n i t l . p a s ( 2 6 ) : Undeclared i d e n t i f i e r :
'ShowMwssage'
[Fatal Error] P r o j e c t l . d p r ( 5 ) : Could not compile used unit ' U n i t l . p a s '
Первая надпись сообщает об ошибке (Error), указывает, в каком она находится файле (Unitl. pas) и строке (26), а также ее тип (undeclared identifier необъявленный идентификатор). Как видите, система не знает слова showMws sage, не нашлось его и в описаниях переменных, типов и классов. В этом случае ошибку нетрудно найти и исправить. Иногда ошибка оказывается совсем не там, куда указывает Delphi. Например, если мы объявили переменную i как string, а потом пытаемся использовать ее в цик-
Глава 10. Компиляция и отладка программы ле for, то Delphi найдет ошибку в строке цикла for. Исправлять же ее нужно в разделе объявления переменных — var. Бывают и более сложные ситуации, когда ошибку приходится искать чуть ли не полдня. При создании крупных проектов на отладку и тестирование иногда тратится не меньше времени, чем непосредственно на программирование. Есть даже такая должность — тестировщик. Занимающий ее человек тестирует написанное программистами приложение. Во второй строке (Fatal Error) компилятор сообщает, что не может скомпилировать файл Unitl .pas из-за ошибок. Кроме ошибок в этой же области выводятся предупреждения (warning). Они в отличие от ошибок не мешают полной компиляции проекта и созданию исполняемого ЕХЕ-файла. Но это не значит, что на них не нужно обращать внимания. Очень часто, анализируя предупреждения, можно понять, где в программе скрывается ошибка, или обнаружить какие-нибудь неочевидные погрешности в коде. Так что не стоит игнорировать предупреждения — мне они много раз помогали. Следующим инструментом отладки является пошаговое выполнение программы (рис. 10.3). Для его запуска нужно нажать F7. И чтобы перейти к следующей строке программы, следует также нажимать эту клавишу. В процессе построчного выполнения программы можно смотреть значения различных переменных. Для этого следует подвести указатель мыши к нужной переменной в тексте программы. На всплывающей подсказке будет написано ее значение в данный момент.
Urall lend; procedure TForml.ButtonZClick(Sender: jvar BUF: 1 array [1..100000] of Byte; F: File; jbegin UssignFile (Г, ' b l o c k . d a t ' ) ; iBlockRead(F,BUF, 100); jCloseFile(F); i end;
TObject) ;
J
end.
J 45: 1
(Modifed
Рис. 10.3. Режим построчного выполнения программы (выполняется 45 строка)
Если вам не нужно просматривать всю программу построчно, а всего лишь уточнить какой-нибудь ее момент, то можно перейти к детальному рассмотрению определенного фрагмента программы (как вариант — одной процедуры). Для этого нужно нажать клавишу F4. Данный режим похож на режим трассировки (F7), но
Отладка в этом случае программа выполняется по одной строке, начиная с той, в которой находился курсор в момент нажатия F4. Клавишу F4 можно нажимать и тогда, когда вы запустили программу в режиме трассировки (F7). Если нужно сразу перейти к какой-либо другой строке процедуры, то нажмите F4. Когда выполнение программы дойдет до этой строки, оно приостановится и будет ждать нажатия F7. Можно назначить точку приостановки выполнения программы (точку останова). Для этого щелкните мышью на вертикальной полосе слева в окне ввода текста программы. Там появится красный кружок, и вся строка выделится красным цветом (рис. 10.4). Когда вы скомпилируете программу (F9), ее выполнение приостановится при достижении этой выделенной красным цветом строки, и вы сможете проконтролировать, например, значения переменных. Unirt lend; iproceclure TFocwl. But-tonZCIick(Sender: TObject); jvar BUF: array [1..100000] of Byte; F: File; [begin JAssignFile(F,'block.dat') ; JReset(F,512); iCloseFile(F); lend;
46: 1
I
Modified
Рис. 10.4. Точка останова
Когда вы переходите в режим отладки Step Over (нажимаете F8), программа тоже выполняется по шагам, но при этом не отображается выполнение процедур и фун-х кций. Если вы точно знаете, что ошибка, допустим, не в процедуре OutGraphic, то, достигнув строки с вызовом этой процедуры, нажимайте не F7, a F8. В Delphi параметры отладки можно изменять в окне Debugger Options (рис. 10.5), которое вызывается командой Tools > Debugger Options. Например, на вкладке Event Log этого окна можно включить или отключить вывод различных сообщений программы. По-английски отладка называется debugging. Как же произошло слово debugging и почему ошибки в программе называют bug? Происходило все задолго до изобретения первых микросхем. Тогда компьютеры были построены на реле и занимали очень много места — целые залы и этажи зданий. Соответственно, для обслуживания одного такого компьютера требовался 5 Зак. 899
Глава 10. Компиляция и отладка программы штат специалистов. Обслуживание и ремонт требовались часто — детали были ненадежными. Но однажды компьютер перестал работать не из-за ненадежности деталей (как потом оказалось), и специалисты начали искать причину. Через некоторое время ее нашли — причиной оказался жук (bug), который забрался внутрь компьютера и замкнул собой один из контактов. Жука вытащили, а компьютер запустили.
General Event Log j Language Exception: j OS Exceptions | ^General
f Messages •:
--.——
------
i Jy CJeai tog on run
| ]7 Breakpoint messages
[ 17 Unlimited length
| J7 Process messages \ P' Ibread messages
[Display process info with event
• $7 Module messages i p flutput messages i Г' Window messages
UseEvent Log Colors
i Foreground; |Щ Custom...
^j
Background: JQ Window BackgT^j
integrated debugging
Рис. 10.5. Окно параметров отладчика
После обнаружения и устранения неисправности в журнале эксплуатации компьютера записали «debugging» (удаление жука). Так и повелось, что любые неисправности компьютеров, а потом и ошибки программного обеспечения по-английски стали называть bug.
Глава 11
Создание приложения U Q Q О Q Q Q Q
Компоненты и их свойства Остроумные фразы Базы с сообщениями Программирование интерфейса Загрузка данных Интеллект программы Добавление общих фраз Встраивание в программу возможности сообщать о текущем времени Q Коллекция вопросов и ответов программы Q Полный листинг программы
Глава 11. Создание приложения
11.1. Компоненты и их свойства Дришло время подвести итог первой части книги. Сделать это лучше всего, создав какой-нибудь интересный проект. Например, программу, которая может общаться с пользователем (ее можно найти на компакт-диске, прилагаемом к книге, по адресу Examples\Speaker). Начнем с создания нового проекта и изменения свойств формы (табл. 11.1). В этой главе и далее (во второй части книги) задание свойств оформлено в виде таблицы. Для масштабных проектов это нагляднее и удобнее, чем приводить значения свойств прямо в тексте. Таблица 11.1. Значения свойств формы Forml Свойство
Значение
Borderlcons biMaximize
False
BorderStyle
bsSingle
Caption
Суперговорилка
Position
poDesktopCenter
Width
522
Height
358
На форму помещаем компоненты Labell, Memol, Editl и Buttonl. Первый из них нужен для вывода замечаний программы. Когда пользователь будет набирать текст, программа с помощью Labell будет выводить остроумные выражения. Значения свойств компонента Labell задаем согласно табл. 11.2. Таблица 11.2. Значения свойств компонента LabeM Свойство
Значение
AutoSize
False
Caption Height
17
Left
0
Top
8
Width
513
В приведенной таблице (и далее в книге) значением свойства Caption является пустая строка. Это значит, что свойство нужно очистить, то есть удалить его содержимое. Компонент Memol будет использоваться для вывода сообщений — диалога между программой и пользователем. Свойству Readonly присваиваем значение True (табл. 11.3), чтобы в одно Memol нельзя было ввести текст.
Компоненты и их свойства Таблица 11.3. Значения свойств компонента Memol Свойство
Значение
Height
265
Left
0
Lines Readonly
True
TabOrder
2
Top
32
Width
513
Окно редактирования свойства Lines компонента Memol показано на рис. 11.1. 7 Suing LM Editor D lines
Рис. 11.1. Окно редактирования свойства Lines компонента Memol
Добавленный нами на форму компонент Editl предназначен для ввода сообщений пользователем. Его свойства и их необходимые значения собраны в табл. 11.4. Таблица 11.4. Значения свойств компонента Editl Свойство
Значение
AutoSize
False
Height
21
Left
0
Text TabOrder
0
Top
304
Width
417
Чтобы послать набранную в поле Editl фразу в окно Memol, нужно нажать кнопку Buttonl.
Глава 11. Создание приложения Отправить сообщение также можно, нажав клавишу Enter. Для этого значение свойства Default нужно изменить на True (табл. 11.5). Таблица 11.5. Значения свойств компонента Buttonl Свойство
Значение
Caption
Сказать!
Default
True
Height
23
Left
416
TabOrder
1
Top
304
Width
97
После задания значений всех свойств окно приложения должно выглядеть так, как показано на рис. 11.2.
Рис. 11.2. Форма Forml
11.2. Остроумные фразы Теперь можно начать наполнение созданного каркаса программы кодом. Параллельно программированию будем создавать текстовые файлы — базу данных, из которой программа будет брать фразы для общения. В качестве базы данных не всегда целесообразно использовать текстовые файлы, но в данном случае подходят именно они. Начнем с обработки вводимого пользователем текста, то есть зададим реакцию программы на процесс набора с клавиатуры. Для этого создадим текстовый файл с фразами, которые программа будет выводить в Labell. Несмотря на то что файл
Остроумные фразы имеет текстовый формат, зададим для него расширение DAT. Должен получиться файл l a b e l s f i l e . d a t следующего содержания. Листинг 11.1 Эй, хватит писать всякие нехорошие вещи. Уже экран краснеет! И набирает, и набирает... М д а . . . А мне отвечать. .. Тебя где писать учили вообще? В холме Огров? Ну, как курица лапой! Работай над почерком! Помню, сидим мы с админом, пиво пьем.. . Зависнуть, что ли? Ты пишешь какие-то глупости! Такой ерунды я уже давно не видела. Пиши, пиши! Склифосовский! Молодец! Правильно говоришь! Что вы делаете сегодня вечером? Я - Суперговорилка, а ты?
Пожалуй, достаточно. Данные фразы будут появляться не очень часто, поэтому на первое время их хватит. Разумеется, вместо приведенных выражений вы можете написать любые другие, но не более ста — на такое количество рассчитан массив phrases. Если же необходимо ввести больше остроумных фраз, то измените размерность массива. Теперь загрузим эти фразы в программу. Нужно, чтобы время от времени программа реагировала на нажатие пользователем клавиш клавиатуры и выдавала одно из выражений листинга 11.1. Реализовать это можно по-разному. Например, установить таймер и в событии OnTimer (то есть с определенной периодичностью) генерировать случайное число. Если это число совпадет с указанным, то будет выводиться фраза. Но мы сделаем немного по-другому — будем считать количество введенных пользователем символов. Если оно превысит определенную величину (например, 40 символов), то при вводе каждого нового знака будет возможно появление фразы. Когда остроумное выражение появляется, счетчик введенных символов нужно обнулить. После этого весь процесс начинается сначала. Такой способ не зависит от времени (а только от количества введенных символов) и позволяет не допускать слишком частое появление фраз. Для счетчика введенных символов зададим переменную symbols. Она должна быть объявлена вне какой-либо процедуры, чтобы быть доступной всем процедурам в модуле Unitl. Количество символов, которые нужно набрать, чтобы появилась фраза, зададим с помощью константы needsymb. Листинг 11.2 const needsyrnb=40;
var Form"!: TForml;
Глава 11. Создание приложения symbols: Integer; // счетчик символов implementation
Когда пользователь набирает в поле Editl какой-нибудь текст, при вводе каждого символа возникает событие OnChange. Поэтому считать количество символов будем в обработке этого события. При таком подходе счетчик символов будет увеличиваться и тогда, когда пользователь удаляет буквы. В этом же событии будем проверять, достигло ли количество введенных символов нужного предела, и если это произошло, то будем генерировать случайное число и при выполнении определенного условия выводить фразу. Фразу выведем в отдельной процедуре — OutPhrase. Листинг 11.3
procedure TForml.Edit1Change(Sender: TObject); var n: Integer; begin Inc(symbols); if symbols>needsymb then // введено нужное количество символов, можно выводить фразу beg i n n := Random(10); i f n=5 then begin OutPhrase; symbols := 0; end; end; end;
Если пользователь ввел нужное количество символов (задается константой needsymb), то совсем не обязательно, что будет выведена фраза. В этом случае генерируется случайное число п, и только когда оно равно пяти (п=5), выводится фраза. Таким образом, даже при достижении нужного количества символов вероятность появления фразы во время ввода каждого нового символа составляет '/)0. Перед использованием Random нужно запустить генератор случайных чисел Random! ze, иначе возвращаемые функцией Random значения всегда будут одинаковые. Random! ze достаточно запустить один раз, поэтому сделаем это в процедуре активации формы FormActivate. В этой же процедуре загрузим фразы из текстового файла. Чтобы не открывать и не читать файл каждый раз, когда нужно вывести лишь одну фразу, прочитаем файл один раз в массив, а потом по мере необходимости будем брать из массива нужную фразу по номеру.
Остроумные фразы Добавим в раздел var массив строк pjirases и переменную count, предназначенную для подсчета загруженных из файла фраз и их хранения. Листинг 11.4
var Form"!: TForml; symbols: Integer; // счетчик символов phrases: array [1..100] of String[255]; count: Integer;
Процедура FormActivate примет такой вид. Листинг 11.5 procedure TForml.FormActivate(Sender: TObject); begin Randomize; ReadLabe I Phrases; end;
Напишем процедуру чтения фраз из текстового файла в массив phrases (ReadLabelPhrases). Листинг 11.6
procedure TForml.ReadLabeI Phrases; // чтение фраз из текстового файла в массив
var F: TextFi le; s: String; begin AssignFi le(F, f i le_phrases); Reset(F); count := 0; w h i le not Eof(F) do begin , ReadLn(F.s); if s= then Continue; Inc(count); phras,es[count] := s;
end; CloseFi le(F);
end; Поясню данную процедуру. В принципе здесь все стандартно: открывается текстовый файл, который, пока не будет обнаружен конец файла, построчно читается в массив. После чтения каждой строки счетчик увеличивается на единицу. Затем файл закрывается. Перед чтением файла счетчик строк (переменная count) обнуляется. В данном примере это необязательно было делать, так как процедура
Глава 11. Создание приложения ReadLabelPhrases вызывается один раз из FormActivate, следовательно, переменная count равна нулю, поскольку ранее не использовалась. Но в целях повышения надежности программы счетчик прочитанных строк лучше все-таки обнулять на случай, если процедуру придется вызывать еще раз (такое вполне может произойти по мере совершенствования программы). Обратите внимание на строку AssignFile(F,file_phrases);
В ней имя файла задано константой, которая объявлена в разделе const. Листинг 11.7
const needsymb=40; f i le_phrases=labelsfi le.dat;
Это сделано для того, чтобы при желании легко изменить имя файла, с которым работает программа. Процедура ReadLabelPhrases и использующая ее программа могут прекрасно работать, но при этом фразы могут не выводиться. Чтобы исключить такую возможность, нужно запретить выполнение программы в случае отсутствия файла с фразами. Тогда процедуру ReadLabelPhrases нужно немного изменить. Листинг 11.8
procedure TForml. ReadLabel Phrases; // чтение фраз из текстового файла в массив
var
F: TextFile; s: String; begin AssignFi le(F, f i le_phrases);
{$!-}
Reset(F);
{$!+} if lOResultoO then
beg i n
ShowMessage(OuiM6i
count := 0; whi le not Eof(F) do begin ReadLn(F.s); i f s= then Conti nue; Inc(count); phrases[count] := s;
end;
CloseFi le(F);
end;
'
Остроумные фразы Итак, мы сделали загрузку фраз в массив при запуске программы. Теперь займемся непосредственно выводом фразы из массива, то есть процедурой OutPhrase. Листинг 11.9
procedure TForml .OutPhrase; // вывод случайной фразы в Label! var n: Integer; begin n ;= Random(count)+1; Labe 11.Caption := phrases[n]; symbols := 0; end;
В строке n := Random(count)+1; единица добавляется потому, что Random возвращает случайное число от нуля до count, а нумерация фраз в массиве у нас начинается с единицы. Можно запустить программу на выполнение и попробовать набрать что-нибудь в текстовом поле. Когда будет набрано 40 символов, при вводе каждого нового знака с вероятностью '/10 может появиться фраза. Плохо, что фраза появляется полностью, резко и неожиданно. Для придания эффектности нужно попробовать сделать так, чтобы фраза выводилась по буквам. Причем бесполезно выводить в Labell символы по одному через цикл. Все равно цикл выполнится мгновенно, и эффект появления букв с задержкой не получится. В Delphi для этих целей нужно использовать таймер. Поместите на форму компонент Timerl (рис. 11.3) с вкладки System. В режиме выполнения программы он виден не будет.
Сказать!
Рис. 11.3. Форма с добавленным на нее компонентом Timerl
Глава 11. Создание приложения Задайте значения свойств Timerl согласно табл. 11.6. Таблица 11.6. Значения свойств компонента Timerl Свойство
Значение
Enabled
False
Interval
50
От интервала зависит частота появления символов: чем он меньше, тем быстрее будут появляться символы. Свойству Enabled присваиваем значение False, то есть таймер выключен. Включать его будем только для вывода фразы, а потом опять выключать. Переписываем процедуру OutPhrase. Переменная п теперь не нужна в этой процедуре, а нужна переменная, которая будет видна и в процедуре TimerlTimer. Также нужна переменная, в которой будет храниться номер выведенного символа. Переменную с номером фразы назовем NumbF, а переменную, которая считает выведенные в Labell символы, — Outs. Объявляем эти две переменные в разделе var вне всех процедур. Листинг 11.10 var
Form"!: TForml; symbols: Integer; // счетчик символов phrases: array [1.. 100] of String[255]; count: Integer; // общее количество загруженных фраз NumbF: Integer; // номер фразы, которая выводится OutS: Integer; // номер символа, который выводится Процедура OutPhrase будет такой. Листинг 11.11 procedure TForml.OutPhrase; // вывод случайной фразы в Label"! beg i n
NumbF := Random(count)+1; OutS := 1; Label"! .Caption := ; Timerl. Enabled := True; // включение таймера .для вывода символов
end; Данная процедура всего лишь инициализирует переменные и запускает таймер. Вывод же фразы по символам — это дело процедуры TimerlTimer.
Базы с сообщениями Листинг 11.12 procedure TForml .Timer1Timer(Sender: TObject); begin
Label 1.Caption := Label 1.Caption + phrases[NumbF,OutS]; Inc(OutS); if OutS>length(phrases[NumbF]) then // остановка таймера Timerl. Enabled := False; end;
Процедура элементарная: на этикетку выводится очередной символ, после чего счетчик символов увеличивается на единицу и проверяется, все ли символы выведены. Если все, то таймер останавливается, тем самым прекращается вывод символов. Можете изменить некоторые параметры по своему желанию. Например, изменив интервал таймера с 50 на другое число, можно замедлить или ускорить вывод символов. Если константу needsymb сделать равной 20 вместо 40, то фразы будут появляться чаще. Чтобы изменить вероятность появления фраз, задайте другое число в строке п := R a n d o m ( 1 0 ) ; процедуры EditlChange.
11.3. Базы с сообщениями Наша программа пока не общается с пользователем, но уже может комментировать набор текста. Сделаем так, чтобы она реагировала на простые приветствия и вопросы. Причем программа должна распознавать построенные по-разному фразы. Создадим еще один текстовый файл, содержащий всевозможные приветствия. Вид его будет таким: • первая строка — приветствие, введенное пользователем; • вторая строка — ответ программы на приветствие. После этого идут следующие две строки и парами все остальные приветствия с ответами. Для повышения удобочитаемости разделим пары пустой строкой, а программу при чтении файла заставим пропускать пустые строки. Файл с приветствиями (privetf lie. dat) будет иметь следующее содержание. Листинг 11.13
Привет Привет!
Глава 11. Создание приложения Hel lo Привет!
How are you Все отлично! Как дела Лучше всех! Здравствуй Привет, привет! Здравствуйте Ухты, как важно... Привет! Здорово Ага, привет! Салют Привет, коли не шутишь! Добрый день Здравствуй! Добрый вечер Hello! Доброе утро Утро добрым не бывает.
Для прощальных фраз создадим похожий файл (byef ile. dat). Листинг 11.14 Пока Пока!
Счастливо Пока, пока! Удачи У какой еще дачи? До свидания Будь!
Думаю, этого достаточно, программа ведь создается для примера. Вы можете добавлять разные фразы по своему усмотрению.
Программирование интерфейса Восклицательные, вопросительные знаки и точки в конце введенных пользователем сообщений не поставлены намеренно, потому что неизвестно, как именно он наберет фразу. /
11.4. Программирование интерфейса При сравнении строк в нашей программе нужно будет использовать функцию преобразования из прописных букв в строчные, потому что введенное пользователем слово «Привет» не равно «привет». В Delphi есть функция Lowercase, которая преобразует прописные буквы в строчные, но работает только с латинскими символами (русские буквы она оставляет без изменений). Для преобразования русских прописных букв в строчные существует функция AnsiLowerCase. С латинскими буквами она, разумеется, тоже справляется прекрасно. Займемся той частью программы, которая работает при нажатии кнопки Сказать! (Buttonl). Когда пользователь нажмет ее, нужно вывести набранную им фразу в поле сообщений (Memol), преобразовать в строчные буквы и очистить строку ввода (Editl). После чего необходимо вызвать процедуру обработки сообщения пользователя. Ей займемся потом, как самой сложной. Сначала отладим механизм вывода сообщений. Листинг 11.15
i
procedure TForm1.Button1CI ick(Sender: TObject); // нажатие кнопки "Сказать!" var user: String; // сообщение пользователя comp: String; // сообщение программы begin user := Editl.Text; Memol.Lines.Add(User: + user); user : = AnsiLowerCase(user); Editl.Text := ; comp := Intel lect(user); Memol.Lines.Add(Comp: + comp); Editl.SetFocus; end; Подбор ответа на введенную пользователем фразу возложим на функцию intellect. Ей в качестве параметра нужно передавать сообщение пользователя (оно имеет тип string), а возвращаться будет ответ программы (тоже тип string). Пока сделаем так, чтобы функция intellect всегда выдавала одно и то же сообщение. Тогда будет можно запустить программу и проверить, как она работает. Листинг 11.16
function TForml.Intellect(s: String): String; // возвращает ответ компьютера // (s - исходное сообщение пользователя) begin
Глава 11. Создание приложения Intellect := He понимаю; end;
Можно запустить программу и посмотреть, что получилось. А вышло здорово! Что программе ни скажи — у нее на все готов ответ. Правда, пока ответ один и тот же; «Не понимаю». Зато все работает.
11.5. Загрузка данных Перед использованием данных, хранящихся в текстовых файлах, их (данные) желательно загрузить в память. Чтобы работать с этими файлами, зададим еще две константы, в которых будем хранить имена файлов. Листинг 11.17
const
needsymb=40; fi le_phrases=labelsfi le.dat; f i le_privet=privetfile.dat; f i le_bye=byefi le.dat; Введем тип Hesh и будем использовать его для создания массивов, в которых удобно хранить парные фразы (например, приветствие и ответ на него). Листинг 11.18 type Hesh=record s1: String[255]; // сообщение пользователя s2: String[255]; // ответ компьютера на сообщение end;
Создадим массивы privets и byes, объявив их в разделе var. Там же объявим переменные Pcount и Bcount для хранения количества загруженных приветственных и прощальных фраз. Листинг 11.19 var Form"!: TForml;
symbols: Integer; // счетчик символов phrases: array [1. . 100] of String[255]; count: Integer; // общее количество загруженных фраз NumbF: Integer; // номер фразы, которая выводится OutS: Integer; // номер символа, который выводится privets: array [1..100] of Hesh; Pcount: Integer; // количество.загруженных приветствий byes: array [1..100] of Hesh; Bcount: Integer; // количество загруженных прощальных фраз
Загрузка данных Напишем процедуры ReadPrivets и ReadByes, которые будут загружать фразы соответственно с приветственными и прощальными сообщениями в массивы privets И byes.
i
Листинг 11.20
procedure TForml.ReadPrivets; // загрузка приветствий в массив privets var F: TextFi le; s: String; mode: Integer; // режим чтения: 0 - фраза, 1 - ответ на фразу begin AssignFile(F,f i le_privet); {$!-} Reset(F); {$!+} if lOResultoO then begin 8п(мМеззаде(Ошибка открытия файла + f i le_privet); Form!. Close; end; mode := 0; Pcount := 0; whi le not Eof(F) do begin ReadLn(F.s); if s= then Continue; if mode=0 then beg i n Inc(Pcount); privets[Pcount].s1 := AnsiLowerCase(s); mode := 1; end else begin privets[Pcount].s2 := s; mode := 0; end; end; CloseFile(F); end; Процедура ReadPrivets содержит два режима чтения файла: mode=0 и mode=l. В первом случае процедура выполняется по одной ветке и прочитанная строка присваивается si (здесь же происходит увеличение счетчика Pcount). Затем режим переключается в mode=l, прочитанная строка присваивается s2, а режим переключается обратно в mode=0. Таким образом режимы чередуются при каждой
Глава 11. Создание приложения итерации цикла while. Если же встречается пустая строка, то процедура переходит к следующей итерации цикла с помощью оператора Continue. Процедура ReadByes аналогична ReadPrivets. Различаются они только названиями переменных и массивов. Листинг 11.21
procedure TForml.ReadByes; // загрузка прощальных фраз в массив byes var F: TextFi le; s: String; mode: Integer; // режим чтения: 0 - фраза, 1 - ответ на фразу beg in AssignFi le(F,fi le_bye); <$!-} Reset(F); {$!+} if lOResultoO then begin ShowMessage(Oum6Ka открытия файла + f i le_bye); Form"!.Close; end; mode := 0; Bcount := 0; whi le not Eof(F) do begin ReadLn(F.s); if s= then Continue; if mode=0 then begin Inc(Bcount);
byes[Bcount]. s1 := Ansi LowerCase(s); mode := 1; end .. else begin byes[Bcount].s2 := s; mode := 0; end; end; CloseFile(F); end; Процедуры ReadPrivets и ReadByes вызываются из FormActivate, так как все данные должны загрузиться в массивы при первом открытии окна программы. Переписываем процедуру FormActivate.
Интеллект программы Листинг 11.22 procedure TForml.FormActivate(Sender: TObject);
begi n Randomize; // загрузка данных в массивы ReadLabelPhrases; ReadPri vets; ReadByes; end;
11.6. Интеллект программы Сначала сделаем так, чтобы программа здоровалась и прощалась. Все необходимое для этого уже есть. В функции Intellect будем проверять, является ли сообщение пользователя приветственным или прощальным. Сравним введенное пользователем сообщение с каждой приветственной и прощальной фразой в массиве. При совпадении фраз нужно вывести соответствующий ответ из массива. Искать будем не по точному совпадению строк, а по вхождению одной строки в другую (на случай, если сообщение пользователя не совсем точно совпадает с фразой из массива). Если пользователь набрал, например, такое приветствие: «Привет, старая вешалка!», то при сравнении этого сообщения с фразами, которые хранятся в массиве privets, не будет найдено ни одного похожего приветствия. Если же искать по принципу вхождения одного из приветствий массива privets в сообщение пользователя, то будет найдено слово «привет», и программа поздоровается в ответ (рис. 11.4). Конечно, было бы хорошо, если бы программа поняла еще и выражение «старая вешалка» и обиделась, перестав разговаривать дня на два, но для этого нужно написать еще очень много кода, что никак не получится в рамках этой скромной книги. " Суперговорияка
Jser: Привет, старая вешалка! С тебя нанимается театр? ;отр: Привет!
Рис. 11.4. Ответ программы на приветственное сообщение пользователя
Глава 11. Создание приложения Чтобы не увеличивать листинг функции intellect, нужно выделить все проверки в отдельные процедуры и функции: проверку, является ли сообщение пользователя приветствием, — в одну процедуру, проверку прощальной фразы — в другую. Остальные проверки тоже нужно разместить отдельно, и все они должны вызываться из функции intellect. Проверку, является ли сообщение пользователя приветствием, организуем с помощью функции AskPrivet, которая будет возвращать ответ типа string. Если ответ не найден, то будет возвращаться пустая строка. Это значит, что сообщение пользователя не является приветствием, и тогда будет выполняться другая проверка. Если же функция AskPrivet определит, что сообщение пользователя — приветствие, то возвращаемым значением будет ответная фраза компьютера пользователю, и следующие проверки запускаться не будут. Листинг 11.23
function TForml.AskPrivet(s: String): String; // Проверяет, является ли сообщение пользователя (s) приветствием, // и возвращает ответ компьютера, если он найден. // Если ответ не найден, то возвращает пустую строку. var i: Integer; // счетчик в цикле n: Integer; // нужна для определения Роз sc: String; // ответ компьютера s1: String; // нужна для подбора begin // сканирование массива приветствий sc := ; for i := 1 to Pcount do begin s1 := privets[i].s1; n := Pos(s1,s); if nOO then " •"<' /// / подходящий ответ найден begin sc := privets[ i ].s2; Break; end; end; AskPrivet := sc; end; Сразу же напишем функцию AskBye, которая подбирает ответ, если сообщение пользователя прощальное. Эта функция отличается от AskPrivet только названиями переменных и массивов. Приветственные и прощальные сообщения выделены в разные функции только для того, чтобы программа знала, какую фразу выдавать, если она сама захочет поздороваться или попрощаться (я планирую добавить такую возможность позже). Конечно, можно было бы сделать все в одном тексто-
Интеллект программы вом файле и указать признак, который будет сообщать о том, какой является строка: приветственной или прощальной. Но вариант, который используется сейчас, на мой взгляд, более удобный. Листинг 11.24
function TForml.AskBye(s: String): String; // Проверяет, является ли сообщение пользователя (s) прощальным, // и возвращает ответ компьютера, если он найден. // Если ответ не найден, то возвращает пустую строку. var i : Integer; // счетчик в цикле n: Integer; // нужна для определения Роз sc: String; // ответ компьютера s1: String; // нужна для подбора begin // сканирование массива прощальных фраз sc := ; for i := 1 to Bcount do begin s1 := byes[i].s1; n := Pos(s1,s); if n<>0 then // подходящий ответ найден beg i'n sc := byes[i].s2; Break; end; end; AskBye := sc; end; Переписываем функцию Intellect (вызываем из нее AskPrivet и AskBye). Листинг 11.25
function TForml.Intellect(s: String): String; // возвращает ответ компьютера // (s - исходное сообщение пользователя) var sc: String; // ответ компьютера begin sc := AskPrivet(s); if sco then begin Intellect := sc;
Exit-;
end;
Глава 11. Создание приложения sc := AskBye(s); if sc<> then begin I n t e l l e c t := sc; Exit; end; Intellect := He понимаю; end;
Теперь функция intellect сначала пытается найти ответ, предполагая, что пользователь поздоровался, то есть вызывает функцию AskPrivet. Если функция AskPrivet не нашла в массиве приветствий ничего похожего, тб вызывается функция AskBye. Если и ей не удалось найти подходящий ответ, то функция intellect возвращает строку «Не понимаю», которая и выводится на экран. Попробуйте скомпилировать и запустить программу. Ее невозмутимости можно позавидовать. Если сказать ей: «Привет, старая тумба!», то программа не обижается, а только здоровается в ответ! Впрочем, она компенсирует это остроумными замечаниями вверху окна.
11.7. Добавление общих фраз Было бы неплохо, если бы наша программа понимала и другие фразы (например, комплименты). Для этого сформируем текстовый файл с основными фразами (base. dat), подобный тем, которые создавались в разд. 11.3. Листинг 11.26
Молодец Спасибо! Умница Я тронута! Дура А ну не дискриминируйте меня тут, инсинуанты несчастные! Дурак
Это кто, ты? Тебя зовут Это сложный философский вопрос. Как назовешь... Погода Да ничего так погодка. У природы ведь нет плохой погоды?
Добавление общих фраз
Да Ну еще бы! Нет Ну нет, так нет. Спасибо Пожалуйста! Пока этого достаточно, но содержимое данного файла лучше максимально увеличить. По ключевому слову в сообщении пользователя (например, «погода») компьютер может подобрать универсальную, подходящую по смыслу фразу (рис. 11.5).
Зависнуть что ли? Jsei: Здравствуй. Говорилка! Zomp. Привет, привет! Jsei: Как сегодня погода, ничего? Сото: Да ничего так погодка. У природы ведь нет плохой погоды?
Сказать!
Рис. 11.5. Разговор с программой о погоде
Подключим файл base. dat к программе. Для этого напишем процедуру загрузки данных в массив base. Листинг 11.27
procedure TForml. Read Base; // загрузка базовых фраз в массив base var F: TextFi le; s: String; mode: Integer; // режим чтения: 0 - фраза, 1 - ответ на фразу begin AssignFi le(F,f i le_base); {$!-} Reset(F); {$!+} if lOResultoO then
Глава 11. Создание приложения begin SnowMessage(Oiim6Ka открытия файла + f i le_base); Form"! .Close; end; mode := 0; BASEcount := 0; whi le not Eof(F) do begin ReadLn(F.s); if s= then Continue; if mode=0 then begin Inc(BASEcount); base[BASEcount]. s1 := AnsiLowerCase(s); mode ;= 1; end else • beg i n base[BASEcount].s2 :- s; mode := 0; end; end; C l o s e F i le(F); end;
• •
Объявляем константу (имя файла), массив и счетчик фраз. В раздел const добавляем строку file_base='base.dat' ;
' А в var дописываем следующие строки: base: array [ 1 . . 1 0 0 ] of. Hesh; BASEcount: Integer; // количество загруженных фраз в массиве base В FormActivate добавляем вызов процедуры ReadBase. Листинг 11.28 procedure TForml. FormActivate(Sender: TObject); begi n Randomize; // загрузка данных в массивы ReadLabe I Phrases; ReadPrivets; ReadByes; ReadBase; end;
Добавление общих фраз Конечно, нужно ввести функцию AskBase и добавить проверку, является ли сообщение пользователя базовой фразой, в функцию intellect (после проверки согласно As kBye). Листинг 11.29 sc : = AskBase(s); if sc<> then begin Intellect := sc; Exit; end;
Функция AskBase похожа на AskPrivet и As kBye. Листинг 11.30 function TForm1.AskBase(s: String): String; // Подбирает ответ компьютера из файла базовых фраз // и возвращает его, если находит. // Если ответ не найден, то возвращает пустую строку. var i : Integer; // счетчик в цикле n: Integer; // нужна для определения Pos sc: String; // ответ компьютера s1: String; // нужна для подбора begin // сканирование массива базовых фраз sc := ; for i := 1 to BASEcount do begin s1 := base[ i ].s1; n := Pos(s1,s); if n<>0 then // подходящий ответ найден begin sc := base[ i ].s2; Break; end; end; AskBase := sc; end; Между пользователем и программой уже может получиться примерно следующий диалог: User: Привет! Сотр: Привет!
Глава 11. Создание приложения User: Как жизнь? Сотр: Не понимаю User: Как дела, говорю? Сотр: Лучше всех! User: Отлично! Молодец! Сотр: Спасибо! User: Ну ладно, пока! Сотр: Пока!
Чтобы программа поддерживала более содержательную беседу, можно увеличить количество общих фраз в файле base. dat. Только необходимо вводить фразы без излишней детализации, тогда программа будет лучше находить их. Предположим, что в базе данных программы есть строка «в магазин». Если пользователь скажет: «Не хочу идти в магазин» или «Пошли в магазин за пивом», или же «Требуются грузчики для работы в магазине», то программа обнаружит эту строку и найдет ответ. На все подобные фразы можно подобрать универсальный ответ, например: «Да ну этот магазин, лучше все покупать через Интернет».
11.8. Встраивание в программу возможности сообщать о текущем времени Расширим возможности программы — научим ее отвечать на вопрос о том, который сейчас час. Для этих действий практически не нужна база с фразами, поэтому создавать и подгружать файлы не будем. Проверять, является ли сообщение пользователя вопросом о точном времени, будем с помощью функции AskTime, которая будет вызываться из функции Intellect (как и другие функции). Листинг 11.31 sc := AskTime(s); if sc<> then begin Intellect := sc; Ex i t;
end;
Функция AskTime, по возможности, будет выявлять разные вопросы, касающиеся времени: «Который час?», «Сколько времени?», «Уже поздно?» и т. д. Если программа распознает, что ей задают вопрос о времени, то будет сформирован ответ, основанный на системном времени компьютера. Причем, вариантов ответа будет несколько, чтобы создалось впечатление, что программа поддерживает диалог и отвечает по-разному.
Встраивание в программу возможности сообщать о текущем времени Листинг 11.32
function TForm1.AskTime(s: String): String; // Если пользователь задал вопрос о времени, // то сообщает время (возвращает ответ типа String). // Если ответ не найден, то возвращает пустую строку, var found: Boolean; // имеет значение True, если вопрос о времени sc: String; // формируемый ответ компьютера n: Integer; // нужна для генерации случайного числа hour, m i n , sec, msec: Word; //для преобразования времени now: TDateTime; // для определения даты begin found := False; if Роз(сколько, врем,з)<>0 then found := True; if Роз(который час,з)<>0 then found := True; if Роз(рано,з)<>0 then found := True; if Pos(no3flHO,s)<>0 then found := True; if Роз(скока врем,з)<>0 then found := True; if Роз(время сколько,s)<>0 then found := True; if Роз(времени сколько,s)<>0 then found := True; if Роз(сейчас времени,s)<>0 then found := True; if Роз(времени сейчас,s)<>0 then found := True; : if Роз(сейчас время,s)<>0 then found ;=True; if Роз(время сейчас,s)<>0 then found := True; if found=False then // вопрос не о времени sc : = else begin // вопрос о времени // определение текущего времени now := Time; DecodeTime(now, hour, m i n , sec,msec); // генерация одного из вариантов ответа n := Random(4); case n of 0: begin sc := Сейчас + IntToStr(hour) + ч. ; sc := sc + IntToStr(min) + мин.; end; // 0 1: begin sc := Сколько время? ; if hour<10 then sc := sc + 0 + IntToStr(hour) else s := IntToStr(hour); sc : = sc + :; if min<10 then sc := sc + 0 + IntToStr(min) else s := IntToStr(min); end; // 1
Глава 11. Создание приложения 2: sc := Не скажу я, который час! Вот,; 3: begin if hour<5 then sc := Ночь; if (hour>4) and (hour<12) then sc := Утро; if (hour>11) and (hour<17) then sc := День; if hour>16 then sc := Вечер; end; // 3
end; // end case end; // end e l s e AskTime : = sc; end;
В этой процедуре достаточно тщательно обрабатываются разные варианты вопросов. Можно в довольно свободной форме спросить у программы о текущем времен и — и она, скорее всего, ответит вам (рис. 11.6). " Г.чпррговорнякз Пиши, пиши! Склифосовский! User Hello! Comp: Привет! User He подскажешь ли, который час? Comp: Ночь User; А конкретнее? Скока время? Comp: Сейчас 1 ч. 49 мим.
Рис. 11.6. Программа выводит время, только если ее очень хорошо попросить
11.9. Коллекция вопросов и ответов программы Завершающим этапом создания нашей программы станет подготовка вопросов, которые будет задавать сама программа. Задавать вопросы программа будет в случае, если она не поняла сказанного пользователем. Вместо того чтобы выводить «Не понимаю», пусть лучше задает вопросы в ответ. Лучшая защита — нападение! Когда пользователь ответит на вопрос, программа должна сделать вид, что внимательно его выслушала и все поняла. Этого можно добиться с помощью отвлеченных фраз. Конечно, программа может неправильно среагировать на сообщение пользователя, но постоянно отвечать «Не понимаю» уже не будет. Если не сработала ни одна из функций, вызываемых из intellect, то будем вызывать функцию Questions. Функция же intellect примет такой вид.
Коллекция вопросов и ответов программы Листинг 11.33
function TForml.Intellect(s: String): String; // возвращает ответ компьютера // (s - исходное сообщение пользователя) var sc: String; // ответ компьютера begin sc := AskPrivet(s); if sc<> then begin Intellect := sc; Exit; end; sc ;= AskBye(s); if sc<> then begin Intellect := sc; Exit; end; sc := AskBase(s); if sco then begin I n t e l l e c t := sc; Exit; end; sc := AskTime(s); if sc<> then begin Intellect := sc; Exit; end; I n t e l l e c t := Questions; end; Нужно создать текстовый файл с вопросами (quest. dat). Только вместо формата «вопрос — ответ» его содержимое будет иметь вид «вопрос — реакция на ответ пользователя». Листинг 11.34
Как тебя зовут? Ага, понятно. Чем ты увлекаешься? У тебя широкий кругозор. Тебя устраивает твоя зарплата? Думаю, это у всех так.
'
Глава 11. Создание приложения Ты называешь свой компьютер по имени? Интересно! Тебе нравится со мной разговаривать? Я так и думала. Кто такой Иван Грозный? Понятно, что царь! Как ты думаешь, что появилось раньше - курица или яйцо? Это спорный вопрос... Какой фильм тебе нравится больше всего? А мне - «Шматрица». Какую музыку ты предпочитаешь слушать? А я разную: и классику слушаю, и рок, и популярную. Я не вижу, у тебя большой монитор? Да ну?! Я забыла, как тебя зовут? Что-то с памятью моей стало. Нужно тренировать.
Вот такой простой файл получился. Конечно, чтобы использовать программу для общения (пусть даже и шуточного), файлы с данными должны быть раз в сто больше, но не приводить же их в книге! Если есть желание, то можете дописать эти файлы сами и получить более интересную программу. Для примера же, чтобы посмотреть, как работает программа, достаточно и такого файла. Создадим процедуру загрузки файла quest. dat в массив. Сначала добавим константу с именем файла в раздел const:
/
file_quest='quest.dat' ;
В раздел var введем массив и переменную для работы с вопросами: quest: array [1..100]
of Hesh;
QUESTcount: Integer; // количество загруженных в quest вопросов
Для загрузки файла quest. dat в массив будем использовать процедуру ReadQuest. Листинг 11.35
procedure TForml.ReadQuest; // загрузка вопросов в массив quest var F: TextFi le;
Коллекция вопросов и ответов программы
s: String; mode: Integer; // режим чтения: 0 - фраза, 1 - реакция на ответ begin AssignFi le(F,fi le_quest); {$!-} Reset(F); {$!+} if lOResultoO then begin 8помМеззаде(Ошибка открытия файла + fi le_quest); Forml.Close; end; mode := 0; QUESTcount := 0; whi le not Eof(F) do begin ReadLn(f.s); if s= then Continue; if mode=0 then begin Inc(QUESTcount); quest[QUESTcount].s1 := s; mode := 1; end else begin quest[QUESTcount].s2 := s; mode := 0; end; e.nd; CloseFile(F); end; Вписываем вызов ReadQuest в процедуру FormActivate. Окончательный вид процедуры активации формы такой. Листинг 11.36
procedure TForml.FormActivate(Sender: TObject); begin Randomize; // загрузка данных в массивы ReadLabe I Phrases; ReadPrivets; ReadByes; ReadBase; ReadQuest; end;
Глава 11. Создание приложения Для работы функции Questions в разделе var модуля Unitl нужно объявить переменную quests типа string, в которой будет храниться реакция на ответ. Изначально переменной quests присваивается пустая строка. Когда программа задает пользователю вопрос, в quests помещается реакция на ответ. Если при подборе следующей фразы в quests что-нибудь содержится, то функция Questions не вызывается. Пустая строка переменной quests присваивается в начале функции intellect. Это сделано, чтобы, если quests не пригодилась, программа потом вместо следующего вопроса не выдала устаревшее содержимое quests. Нам нужно использовать содержимое quests в конце функции, поэтому оно копируется в локальную переменную questS2. Как все это выглядит, лучше посмотреть на примере. Обратите внимание на комментарии в начале и в конце функции. Листинг 11.37
function TForml. Intellect(s: String): String; // возвращает ответ компьютера // (s - исходное сообщение пользователя) var sc: String; // ответ компьютера questS2: String; // реакция на ответ пользователя begin questS2 := quests,1 // сохраняем значение, оно может // пригодиться в конце функции quests '.= ',// присваиваем переменной пустую строку sc := AskPrivet(s); if sc<> then begin Intellect := sc; Exit; end; sc := AskBye(s); i f sc<> then begin Intellect := sc; Exit; end; sc := AskBase(s); if sc<> then begin Intellect := sc; Exit; end; sc := AskTime(s); if sco then begin Intellect := sc;
Полный листинг программы Exit; end; if questS2= then Intellect := Questions e l s e Intellect := questS2; // здесь и пригодилась questS2 end; Пишем функцию Questions. Она получилась совсем небольшой. Листинг 11.38
function TForml.Quest ions: String; // выбирает вопрос пользователю var n: Integer; // нужна для генерации случайного числа begin n := Random(QUESTcount)+1; quests := quest[n].s2; Questions := quest[n].s1; end; На этом разработку программы можно считать завершенной (рис. 11.7)! Конечно, ее еще можно совершенствовать. Но оставляю это тем читателям, которых заинтересовала данная программа. Самое лучшее, что можно добавить в программу, — самообучаемость, чтобы в процессе общения с пользователем она запоминала, какие фразы говорят в определенных случаях. Пиши, пиши! Склифосовский Jset: Привет, давай поговорим! Zomp: Привет! Jser: Как ч тебя дела? lomp: Я забыла, как тебя зовут? J set: Артем -отр: Что-то с памятью моей стало. Нужно тренировать. Jser: Это точно! Мегабайт 512 тебе бы не помешали. Сото: Ты называешь свой компьютер по имени? User Нет Domp: Ну. нет, так нет. Jser Пока что ли... lomp: Пока!
Сказать!
Рис. 11.7. Полноценное общение пользователя с программой
11.10. Полный листинг программы Приведу полный листинг программы, созданной в данной главе, без дополнительных объяснений — он не такой большой. 6 Зак. 899
Глава 11. Создание приложения Листинг 11.39 unit Unit"!; interface
uses Windows,Messages,SysUti Is,Variants, Classes,Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls; type TForml = class(TForm) Memo"!: TMemo; Editl: TEdit; Button!: TButton; Label 1: TLabel; Timer"!: TTimer;
procedure Edit1Change(Sender: TObject); procedure FormActivate(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure ButtonlCI ick(Sender: TObject); private procedure OutPhrase; procedure ReadLabe I Phrases; function Intellects: String): String; procedure ReadPrivets; procedure ReabByes; function AskPrivet(s: String): String; function AskBye(s: String): String; function AskBase(s: String): String; procedure ReadBase; function AskTime(s: String): String; function Quest ions: String; i procedure ReadQuest; { Private declarations } publ ic { Publ ic declarations } end; type Hesh=record s1: String[255]; // сообщение пользователя s2: String[255]; // ответ компьютера на сообщение end; const needsymb=40; f i le_phrases=labelsfi le.dat;
'
Полный листинг программы
fi le_privet=privetfife.dat; fi le_bye=byefi le.dat; f i le_base=base.dat; f iIe_quest=quest.dat; van Form"!: TForml;
symbols: Integer; // счетчик символов phrases: array [1. .100] of String[255]; count: Integer; // общее количество, загруженных фраз NumbF: Integer; // номер фразы, которая выводится OutS: Integer; // номер символа, который выводится privets: array [1..100] of Hesh; Pcount: Integer; // количество загруженных приветствий byes: array [1..100] of Hesh; Bcount: Integer; // количество загруженных прощальных фраз base: array [1.. 100] of Hesh; BASEcount: Integer; // количество загруженных фраз в массиве base quest: array [1. .100] of Hesh; QUESTcount: Integer; // количество загруженных в quest вопросов questS: String; // здесь хранится реакция на ответ implementation {$R *.dfm} procedure TForml.Edit1Change(Sender: TObject); var n: Integer; beg i n Inc(symbols); if symbols>needsymb then // введено нужное количество символов, можно выводить фразу begin n := Random(10); if n=5 then begin OutPhrase; symbols := 0; end; end; end; procedure TForml.OutPhrase; // вывод случайной фразы в LabeM begin NumbF := Random(count)+1;
Глава 11. Создание приложения OutS := 1; Label 1.Caption : = '; Timer"!. Enabled ;= True; // включение таймера для вывода символов end; procedure TForml. ReadLabelPhrases; // чтение фраз из текстового файла в массив var F: TextFi le; s: String; begin AssignFi le(F,fi le_phrases); <$!-} Reset(F); <$!+} if lOResultoO then begin ShowMessage(Ouin6Ka открытия файла + f i le_phrases); Form"!.Close; end;
count := 0; whi le not Eof(F) do begin ReadLn(F.s); if s= then Continue; Inc(count); phrases[count] := s; end; CloseFi le(F); end; procedure TForml. ReadPrivets; // загрузка приветствий в массив privets var F: TextFi le; s: String; mode; Integer; // режим чтения: 0 - фраза, 1 - ответ на фразу begin AssignFi le(F,fi le_privet); {$!-} Reset(F); {$!+} if lOResultoO then begin ShowMessage(OuJM6Ka открытия файла + f i l e _ p r i v e t ) ; Form"!. C l o s e ; end;
Полный листинг программы
mode := 0; Pcount := 0; whi le not Eof(F) do begin ReadLn(F.s); if s= then Continue; if mode=0 then begin Inc(Pcount); privets[Pcount]. s1 := AnsiLowerCase(s); mode : = 1; end else begin privets[Pcount].s2 :=s; mode := 0; end; end; CloseFi le(F); end; procedure TForml.ReadQuest; // загрузка вопросов в массив quest var F: TextFi le; s: String; mode: Integer; // режим чтения: 0 - фраза, 1 - реакция на ответ begin AssignFi le(F,fi le_quest); {$!-}
Reset(F); {$!+} if IQResultoO then begin ShowMessage(Oum6Ka открытия файла + fi le_quest); Forml. C l o s e ; end;
mode := 0; QUESTcount := 0; whi le not Eof(F) do begin ReadLn(F.s); i f s= then Continue; if mode=0 then begin Inc(QUESTcount); quest[QUESTcount].s1 := s;
Глава 11. Создание приложения mode := 1; end else begin quest[QUESTcount].s2 := s; mode := 0; end; end; C l o s e F i le(F); end;
procedure TForml.ReadByes; // загрузка прощальных фраз в массив byes var F: TextFile; s: String; mode; Integer; // режим чтения: 0 - фраза, 1 - ответ на фразу begin AssignFi le(F,file_bye); {$!-} Reset(F); {$!+> if lOResultoO then beg i n ShowMessage(OujH6i
Полный листинг программы
procedure TForml. ReadBase; // загрузка базовых фраз в массив base var F: TextFi le; s: String; mode: Integer; // режим чтения: 0 - фраза, 1 - ответ на фразу begin AssignFi le(F, f i le_base); {$!-} Reset(F); {$!+} if lOResultoO then begin ShowMessage(Oum6Ka открытия файла + f i le_base); Forml.Close; end; mode := 0; BASEcount := 0; whi le not Eof(F) do begin ReadLn(F.s); if s= then Conti nue; if mode=0 then begin Inc(BASEcount); base[BASEcount].s1 := Ansi LowerCase(s); mode := 1; end else begin base[BASEcount].s2 := s; mode := 0; end; end; CloseFi le(F); end; procedure TForml.FormActivate(Sender: TObject); begin Randomize; // загрузка данных в массивы ReadLabel Phrases; ReadPrivets; •ReadByes; ReadBase; ReadQuest; end;
Глава 11. Создание приложения procedure TForm1.Timer1Timer(Sender: TObject); begin Labe 11.Caption := Label 1.Caption + phrases[NuiribF,OutS]; Inc(OutS);
if OutS>length(phrases[NumbF]) then // остановка таймера Timer!. Enabled := False; end;
function TForml.AskPrivet(s: String): String; // Проверяет, является ли сообщение пользователя (s) приветствием, // и возвращает ответ компьютера, если он найден. // Если ответ не найден, то возвращает пустую строку. , var i : Integer; // счетчик в цикле n: Integer; // нужна для определения Роз sc: String; // ответ компьютера s1: String; // нужна для подбора begin // сканирование массива приветствий sc := ; for i := 1 to Pcount do begin s1 := privets[i].s1; n := Pos(s1,s); if n<>0 then // подходящий ответ найден beg in sc := privets[i].s2; Break; end; end; AskPrivet := sc; end; function TForml.AskBye(s: String): S t r i n g ; // Проверяет, является ли сообщение пользователя (s) прощальным, // и возвращает ответ компьютера, если он найден. // Если ответ не найден, то возвращает пустую строку. var i: Integer; // счетчик в цикле n: Integer; // нужна для определения Pos sc: String; // ответ компьютера s1: String; // нужна для подбора begin // сканирование массива прощальных фраз sc := ;
Полный листинг программы
for i : = 1 to Bcount do beg in s1 := byes[i].s1; n •.= Pos(s1, s); if n<>0 then // подходящий ответ найден begin so := byes[i].s2; Break; end; end; AskBye := sc; end; funct ion TForml. AskBase(s: String): String; // Подбирает ответ компьютера из файла базовых фраз // и возвращает его, если находит. // Если ответ не найден, то возвращает пустую строку. var i : Integer; //счетчик в цикле n: Integer; // нужна для определения Роз so: String; // ответ компьютера s1: String; // нужна для подбора begin // сканирование массива базовых фраз sc := ; for i := 1 to BASEcount do begi n s1 := base[ i ].s1; n := Pos(s1, s); if n<>0 then // подходящий ответ найден beg i n sc := base[ i ]. s2; Break; end; end; AskBase := sc; end; function TForml. AskTime(s: String): String; // Если пользователь задал вопрос о времени, // то сообщает время (возвращает ответ типа String). //.Если ответ не найден, то возвращает пустую строку. var found: Boolean; // имеет значение True, есл^вопрос о времени sc: String; // формируемый ответ компьютера
Глава 11. Создание приложения n: Integer; // нужна для генерации случайного числа hour, mi n, sec, msec: Word; / / д л я преобразования времени now: TDateTime; / / д л я определения даты begin found := False;
if Роз(сколько врем,з)<>0 then found := True; if Роз(который час,з)<>0 then found := True; if Роз(рано,s)<>0 then found := True; if Pos(поздно,s)<X) then found := True; if Роз(скока время,s)<>0 then found := True; if Роз(время сколько,s)<>0 then found := True; if Роз(времени сколько,s)<>0 then found := True; if Роз(сейчас времени,s)<>0 then found := True; if Роз(времени сейчас,s)<>0 then found := True; if Роз(сейчас время,s)<>0 then found := True; if Роз(время сейчас,s)<>0 then found := True; if found=False then // вопрос не о времени sc : = else begin // вопрос о времени // определение текущего времени now := Time; DecodeTime(now, hour.min, sec, msec); // генерация одного из вариантов ответа' п := Random(4); case n of 0: begin sc := Сейчас + IntToStr(hour) + ч. ; sc := sc + IntToStr(min) + мин.; end; // 0 1: begin sc : = Сколько время? ; if hour<10 then sc := sc + 0 + IntToStr(hour) else s := IntToStr(hour); sc : = sc + :; if min<10 then sc := sc + 0 + IntToStr(min) else s := IntToStr(min); end; // 1 2: sc := He скажу я, который час! Вот.; 3: begin if hour<5 then sc := Ночь; if (hour>4) and (hour<12) then sc := Утро; if (hour>11) and (nour<17) then sc := День; if hour>16 then sc := Вечер; end; // 3
Полный листинг программы
end; // end case end; // end else AskTime : = sc; end; function TFormt.Questions: String; // выбирает вопрос пользователю var n: Integer; // нужна для генерации случайного числа begin n := Random(QUESTcount)+1; quests := quest[n].s2; Questions := quest[n].s1; end; function TForml.Intellect(s: String): String; // возвращает ответ компьютера // (s - исходное сообщение пользователя) var sc: String; // ответ компьютера questS2: String; // реакция на ответ пользователя begin questS2 := questS;.// сохраняем значение, оно может // пригодиться в конце функции quests '.-\ll присваиваем переменной пустую строку sc := AskPrivet(s); if sc<> then begin Intellect := sc; Exit; end; sc := AskBye(s); if sc<> then begin Intellect := sc; Exit; end; sc := AskBase(s); if sco then begin Intellect := sc; Exit; end; sc := AskTime(s); if sc<> then begin Intellect := sc;
Глава 11. Создание приложения Exit;
end; . if questS2= then Intellect := Questions else Intellect := questS2; // здесь и пригодилась questS2 end;
procedure TForml. ButtonlCI ick(Sender: TObject); // нажатие кнопки "Сказать!" var user: String; // сообщение пользователя comp: String; // сообщение программы beg i n user := Editl.Text; Memo"!. Lines. Add(User: + user); user := AnsiLowerCase(user); E d i t l . T e x t := ; comp := Intel lect(user); Memo"!. Li nes. Add(Comp: + comp); Edit1.SetFocus; end;
end.
Часть 2 Трюки и эффектьТ) Глава 12. Хитрости с мышью и клавиатурой Глава 13. Эффектные формы Глава 14. Проигрывание звуковых записей Глава 15. Трюки с Word и Excel Глава 16. Программирование для Интернета Глава 17. Вывод графики с помощью GUI Глава 18. Использование DirectX Глава 19. Реестр Windows Глава 20. Локальные сети Глава 21. Полезные и интересные программы Глава 22. FAQ и сайты о Delphi
174 190 208 233 242 258 294 333 349 361 386
Глава 12
Хитрости с мышью и клавиатурой Q U Q Q
Указатель мыши: найти и перепрятать Клавиши Ctrl, Alt, Shift и кнопки мыши Определяем код клавиши Быстрый опрос клавиатуры
Указатель мыши: найти и перепрятать
12.1. Указатель мыши: найти и перепрятать С указателем мыши хитрый программист можно делать все, что угодно. Можно, например, спрятать указатель и никому его не показывать. Зачем? Да кому он нужен — этот указатель? Прячется указатель так: Forml.Cursor := crNone;
Эта строка, естественно, сделает указатель мыши невидимым только тогда, когда он находится над формой Forml. Если на форме есть какие-то компоненты, то над ними курсор будет становиться видимым. Чтобы сделать курсор невидимым вообще над всеми компонентами формы, нужно указать следующее: Screen.Cursor := crNone;
После этого курсор станет невидимым и над любым компонентом формы, и над самой формой тоже.
Координаты указателя мыши Координаты указателя мыши удобно определять в процедуре обработки события OnMouseMove. Когда пользователь перемещает манипулятор (это в книгах так смешно иногда называют мышь) над формой, то генерируется событие OnMouseMove, и в процедуру обработки этого события передаются координаты мыши по горизонтали и по вертикали, а также информация о нажатых кнопках мыши. Координаты также передаются обработчикам событий OnMouseDown и OnMouseUp. Создадим программу «Бермудский треугольник». На самом деле ничего треугольного в ней не будет, а из «бермудского» — на форме будет «аномальная» зона, в которой будут случаться всякие абракадабры с указателем мыши. Он будет то пропадать, то внезапно появляться, иногда принимая нестандартный вид. При выводе указателя из этой зоны все будет приходить в норму. Создайте в Delphi новый проект. На форму поместите один компонент — Labell. В свойстве Caption напишите: «Осторожно! Здесь есть аномальная зона». Передвиньте этикетку Labell куда-нибудь в верхнюю часть формы. Свойства формы измените в соответствии с табл. 12.1. Таблица 12.1. Значения свойств формы Forml Свойство
Значение
Borderlcons biMaximize
False
BorderStyle
bsSingle Окончание
Глава 12. Хитрости с мышью и клавиатурой Таблица 12.1. Окончание Свойство
Значение
Caption
Бермудский треугольник
Height
375
Position
poScreenCenter
Width
545
На рис. 12.1 показана форма программы «Бермудский треугольник», которая получилась на самом деле прямоугольной.
©I
Рис. 12.1. Форма программы «Бермудский треугольник»
Теперь код программы. Сначала нужно определить координаты указателя мыши и сохранить их в переменных, доступных всем процедурам и функциям формы. Объявляем переменные mouseX и mouseY типа Integer в разделе var интерфейсной части модуля. Листинг 12.1
var Form"!: TForml; mouseX, mouseY: Integer; // координаты указателя мыши implementation
Реализуем механизм заполнения этих переменных значениями. Будем это делать в обработке события OnMouseMove, что логично. Если пользователь перемещает мышь, то координаты изменяются и сохраняются в mouseX, mouseY. Если пользователь не перемещает мышь, то незачем сохранять координаты.
Указатель мыши: найти и перепрятать Листинг 12.2 procedure TForml.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); beg i n mouseX := X; mouseY := Y; end; Теперь координаты указателя мыши хранятся в mouseX и mouseY, и их можно использовать в любой процедуре. Зададим координаты нашей «аномальной» зоны. Для этого понадобятс^ четыре константы: xl, x2, yl, у2, которые будут означать углы прямоугольной зоны. Листинг 12.3 const // координаты аномальной зоны х1=100; х2=300; у1=100; у2=300; Отслеживать, попал ли указатель мыши в «аномальную» зону, и изменять его вид, если все-таки попал, будем несколько раз в секунду при помощи события OnTimer. Для этого нужно добавить на форму компонент Timer и присвоить свойству Interval, например, значение 25. Затем опишем процедуру обработки события OnTimer. . Листинг 12.4 procedure TForml.Timer1Tiraer(Sender: TObject); begin // проверяем координаты if (mouseX>=x1) and (mouseX<=x2) and (mouseY>=y1) and (mouseY<=y2) then // аномальная зона Effects el se // указатель не в аномальной зоне Screen.Cursor :=crDefault; end; Эффекты с указателем мыши вынесены в отдельную процедуру Effects. Это сделано просто для наглядности. Листинг 12.5 procedure TForml.Effects; // изменение вида указателя
var n: Integer;
Глава 12. Хитрости с мышью и клавиатурой begin Randomize; n := Random(SO); if n=20 then // эффект beg i n n := Random(4);
if n<2 then Screen.Cursor = crNone; if n=2 then Screen.Cursor = crHourGlass; if n=3 then Screen.Cursor = crHandPoint; end; end;
После запуска программы на выполнение получится форма с «аномальной» зоной (примерно в левом нижнем углу), которой не будет видно, но при наведении на нее указателя мыши с ним будут случаться разные абракадабры. На рис. 12.2 показан результат выполнения программы, который выглядит очень просто, но таит в себе загадочные вещи.
Рис. 12.2. Результат выполнения программы
В процедуре TimerlTimer проверяется, попадает ли указатель в «аномальную» зону. Для этого используются переменные mouseX, mouseY и константы xl, x2, yl, у2, о которых мы позаботились заранее. Если попадает, вызывается процедура Effects, если же не попадает, то восстанавливается стандартный вид указателя. В процедуре Effects несколько раз в секунду генерируется случайное число. Если выпадает нужное число (с вероятностью '/50), то курсор меняет вид на один из трех: невидимый, в виде песочных часов, в виде руки. Когда мышь перемещается из «аномальной» зоны, то в процедуре TimerlTimer начинает выполняться строка восстановления стандартного вида указателя. Готовое приложение и полные листинги рассмотренного примера вы можете найти на компакт-диске, прилагаемом к книге, по адресу Examples\Bermud.
Указатель мыши: найти и перепрятать
Неприступная кнопка Существуют программы, которые эмулируют присутствие человека за компьютером. Отдельный вопрос — с какой целью используются такие программы (как правило, для обмана кого-либо или чего-либо); нам важно то, что эти программы принудительно перемещают указатель мыши. Именно этим мы и займемся, для чего напишем программу «Неприступная кнопка». Как вы думаете, что сейчас надо сделать? Естественно, создать в Delphi новый проект. Затем поместить на форму два компонента — кнопку Buttonl и этикетку Labell. Задайте форме, кнопке и этикетке свойства, показанные в табл. 12.2, 12.3 и ,12.4 соответственно. Таблица 12.2. Значения свойств формы Forml Свойство
Значение
Borderlcons biMaximize
False
Borders tyle
bsSingle
Caption
Неприступная кнопка
Height
375
Position
poScreenCenter
Width
545
Таблица 12.3. Значения свойств кнопки Buttonl Свойство
Значение
Caption
Врешь, не возьмешь!
Height
25
Left
192
Top
176
Width
153
Таблица 12.4. Значения свойств этикетки Label 1 Свойство
Значение
Caption
МЕСТО
Height
13
Left
16
Top
16
Width
38
На рис. 12.3 показана полученная форма.
Глава 12. Хитрости с мышью и клавиатурой
'• МЕСТО-
Врешь, не возьмешь!
Рис. 12.3. Форма программы «Неприступная кнопка»
Теперь пишем код, который состоит из одной процедуры — обработчика события OnMouseMove. Будем там проверять координаты указателя мыши и, если он близко подобрался к нашей кнопке, отбрасывать его назад. Листинг 12.6
procedure TForml. FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); var
x1,y1,x2,y2: Integer; // координаты "заграждения" кнопки begin // вычисляем координаты, за которые указателю нельзя заходить х1 := Butt.on1.ieft - 30; х2 := Buttonl.Left + Buttonl.Width + 30; у1 := Buttonl.Top - 30; y2 := Buttonl.Top + Buttonl.Height + 30; // сравниваем с координатами указателя мыши if (X>=x1) and (X<=x2) and (Y>=y1) and (Y<=y2) then // отбрасываем на "место" SetCursorPos(Form1.Left+Label1.Left,Forml.Top+LabelI.Top); end;
Теперь можно запустить программу и попробовать щелкнуть на кнопке мышью. Думаю, что не получится. У меня не получилось. При помощи клавиатуры нажимается, а мыши как будто приказывают вернуться на место. Сделаем программу немного интереснее: заставим кнопку перемещаться по экрану, а указатель мыши от нее так же должен будет отпрыгивать.
Указатель мыши: найти и перепрятать Чтобы кнопка «убегала», поместим на форму Timer (свойство Interval пусть будет равным 25). В процедуру активации формы вписываем инициализацию генератора псевдослучайных чисел. Еще нам нужно хранить направление, в котором кнопка «едет» в данный момент. Это будем делать в переменных dx и dy типа Integer. Их нужно объявить в интерфейсном var. В FormActivate эти переменные нужно инициализировать — задать начальные значения. Пусть по умолчанию кнопка перемещается вправо вниз, поэтому dx и dy надо сделать равными единице (эти переменные задают сдвиг кнопки по вертикали и по горизонтали). Листинг 12.7 procedure TForml.FormActivate(Sender: TObject); begin randomize; dx := 1; dy := 1; end; Случайные числа будем использовать для определения направления, куда перемещаться кнопке, а также для того, менять ли направление движения. Пишем процедуру TimerlTimer. Листинг 12.8 procedure TForml.Timer1Timer(Sender: TObject);
var n,k: Integer; //для генерации случайных чисел begin // определяем, менять ли направление движения n ;= Random(20); if n=3 then // менять (вероятность 3/20) begin k := Random(8); case k of // выясняем новое направление движения кнопки 0: begin dx := -1 ; dy := 0 ; end; 1: begin dx = -1 ; dy := - 1; end 2: begin dx = 0; dy = -1 ; end; 3: begin dx = 1; dy = -1 ; end; 4: begi n dx = 1; dy = 0; end; 5: beg in dx = 1; dy = 1; end; 6: begin dx = 0; dy = 1; end; 7: begin dx = -1 ; dy := 1 ; end; end; // case
end;
Глава 12. Хитрости с мышью и клавиатурой // двигаем кнопку But-tonl. Left : = Button"!. Left + dx; Button"!.Top := Button"!.Top + dy; // если кнопка уехала не туда, возвращаем ее на один пиксел if (Button!. Left+Button1.Width) > Forml.CI ientWidth then Buttonl.Left := Buttonl.Left - 1; if Buttonl.Left < 0 then, Buttonl.Left := Buttonl.Left + 1; if (Button"!.Top+Button1. Height) > Forml.CI ientHeight then Buttonl.Top := Buttonl.Top - 1; if Buttonl.Top < 50 then Buttonl.Top := Buttonl.Top + 1; end; Теперь запустите программу. Кнопка плавно перемещается по форме, но указатель мыши к ней все равно не может приблизиться. Можете менять скорость движения кнопки, задавая новое значение свойства Interval компонента Timer! Если сделать интервал равным 10, кнопка «мечется» из угла в угол. А если сделать интервал равным 50, то кнопка важно ползает, как трактор или как не торопящийся никуда «Роллс-ройс». Если даже вы не двигаете мышь и кнопка сама приблизится к указателю, указатель все равно «отпрыгнет» в сторону. Готовое приложение и полные листинги рассмотренного примера вы можете найти на компакт-диске, прилагаемом к книге, по адресу Examples\Nepristup.
12.2. Клавиши Ctrl, Alt, Shift и кнопки мыши Разобравшись с указателем и «загнав» его в угол, возьмемся за кнопки мыши и клавиши Ctrl, Alt и Shift. Чтобы можно было определить, нажато ли что-то из перечисленного, в процедуры обработки некоторых событий (OnKeyDown, OnKeyUp, OnMouseDown, OnMouseMove, OnMouseUp, OnMouseWheel, OnMouseWheelDown, OnMouseWheelUp) передается переменная shift типа Tshiftstate, в которой закодирована вся информация о кнопках мыши и клавишах Ctrl, Alt и Shift.
Описание типа TShiftState Тип TShiftState показывает состояние клавиш Alt, Ctrl, Shift и кнопок мыши: type TShiftState = set of (ssShift, ssAlt, ssCtrl, ssLeft, ssRight, ssMiddle, ssDouble); В зависимости от состояния клавиш Alt, Ctrl, Shift и кнопок мыши устанавливаются следующие флаги: • ssShif t — нажата клавиша Shift; • ssAlt — клавиша Alt; • ssCtrl — клавиша Ctrl;
Клавиши Ctrl, Alt, Shift и кнопки мыши i
;
• ssLeft — левая кнопка мыши; • s s R i g h t — правая кнопка мыши; • ssMiddle — средняя кнопка мыши; • ssDouble — был выполнен двойной щелчок МЫШБЮ.
Использование TShiftState Переменную типа TShiftState удобно использовать, когда нужно проверить несколько флагов. Например, был ли произведен двойной щелчок именно левой кнопкой мыши, можно узнать так: if S h i f t = [ s s L e f t ,
ssDouble]
then s h o w m e s s a g e ( ' Л е в а я к н о п к а ' ) ;
Был ли выполнен двойной щелчок правой кнопкой мыши, определяется следующим образом: if Shift=[ssRight,
ssDouble] then s h o w m e s s a g e ( ' П р а в а я ' ) ;
Проверить, нажата ли клавиша Shift (неважно, левая или правая), можно так: if Shift=[ssShift] then ShowMessage('SHIFT');
Флаги ssShif t, ssAlt, ssCtrl нужно проверять в обработчиках событий, связанных с клавиатурой (например, OnKeyDown). А флаги ssLeft, ssRight, ssMiddle, ssDouble — в обработчиках событий, связанных с мышью (например, OnMouseMove, OnMouseDown). ;
Левая и правая клавиши Shift Левая и правая клавиши Shift обычно воспринимаются системой одинаково. Однако это разные клавиши. Почему бы не проверять, какая клавиша Shift нажата? Для этого можно использовать функцию RShift, которая возвращает True, если нажата правая Shift, и функцию LShi ft, которая возвращает True, если нажата левая Shift. Листинг 12.9
function LShift: Boolean; label iss, exit; begin LShi ft := False; asm mov ah,02h int 16h and al,00000010b jnz iss jz exit end;
Глава 12. Хитрости с мышью и клавиатурой iss: LShif.t := True;
exit: end;
Листинг 12.10 function RShift: Boolean; label iss, exit; beg I n
RShift := False; asm mov ah, 02h int 16h and al,00000001b jnz iss jz exit end;
iss: RShift := True; exit: end; В этих функциях используется ассемблер. Именно таким образом можно применять ассемблер в Delphi. Рассмотрим пример использования функций LShif t и RShift в обработке события On Key Down. Листинг 12.11
•
procedure TForml. FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); beg in
if LShift=True then ShowMessage(Ha>xaTa левая SHIFT); if RShift=True then ShowMessage(Ha>xaTa правая SHIFT); end;
12.3. Определяем код клавиши Создадим программу, которая будет определять код нажатой клавиши. Использовать подобный инструмент в программировании намного удобнее, чем таблицы, книги или систему помощи. Программа маленькая, а польза от нее есть почти в каждом проекте. Создайте новый проект и поместите на форму две этикетки. Свойству Caption первой этикетки присвойте значение Код, a Caption второй просто очистите. Свойство Left у этикеток равно 80 и 160 соответственно. Остальные свойства у первой и второй одинаковы и показаны в табл. 12.5.
Определяем код клавиши Таблица 12.5. Значения свойств Label! и Label2 Свойство
Значение
AutoSize
False
Font Size
18
Height
29
Top
40
Width
73
Форме задайте свойства, указанные в табл. 12.6. Таблица 12.6. Значения свойств формы Forml Свойство
Значение
Borderlcons biMaximize
False
BorderStyle
bsSingle
Caption
Сообщалка кодов
Height
136
Position
poScreenCenter
Width
327
Для создания рассматриваемой программы не нужно писать много кода. Достаточно добавить одну строку в обработчике события OnKeyDown. Листинг 12.12 procedure TForml. FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin Label2.Caption := IntToStr(Key); end;
Вот и вся программа. Пользоваться ею гораздо удобнее, чем искать в ворохе таблиц код нужной клавиши (рис. 12.4). К тому же определяются даже коды функциональных клавиш, а также Alt, Shift, Backspace.
Код
18
Рис. 12.4. Окно полученной программы
Глава 12. Хитрости с мышью и клавиатурой Хочу заметить, что сообщаемые коды можно смело использовать при написании других программ. Готовое приложение и полные листинги рассмотренного примера вы можете найти на компакт-диске, прилагаемом к книге, по адресу ExamplesXKeys.
12.4. Быстрый опрос клавиатуры Если вы создаете что-то типа игры, то очень важно быстро обрабатывать нажатия клавиш. Рассмотрим, как это делается при помощи стандартных средств. Листинг 12.13
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; S h i f t : TShiftState); begin case Key of 27: begin { . . . } end; 32: begin { . . . } end; 112: begin { . . . } end; end; // case end; В листинге 12.13 на месте { . . . } подразумеваются действия, которые нужно выполнять при нажатии на клавишу. Такой метод не подходит для быстрых игр, даже банальный тетрис лучше не делать с помощью такого алгоритма. Я придумал другой метод, такой же простой, но работающий немного быстрее. Основан он на таймере. Самое главное, мой метод позволяет выяснять, нажаты ли сразу несколько клавиш (неплохо работает при двух-трех одновременно нажатых клавишах). Напишем программу, которая будет отображать нажатие клавиш управления курсором. Создайте новый проект, на форму поместите четыре этикетки Label. Все этикетки пусть будут размером 4 1 x 4 1 пиксел. Свойствам AutoSize нужно присвоить False. Свойствам Color — значение clGreen. Свойства Caption нужно очистить. Остальные значения свойств — разные для всех этикеток. Они представлены в табл. 12.7. Таблица 12.7. Значения свойств этикеток Label Компонент
Свойство
Значение
Labell
Left
56
Labell
Top
16
Быстрый опрос клавиатуры Компонент
Свойство
Значение
Label2
Left
8
Label2
Top
64
Labels
Left
56
Label3
Top
64
LabeU
Left
104
Label4
Top
64
Таким образом, на форме не будут располагаться никакие лишние компоненты. Свойства формы приведены в табл. 12.8. Таблица 12.8. Значения свойств формы Forml Свойство
\
Значение
Borderlcons biMaximize
False
Borders tyle
bsSingle
Caption
FastKeys
Height
150
Position
poDesktopCenter
Width
160
В этой программе будут использоваться четыре клавиши — клавиши управления курсором. Поэтому нужно объявить четыре переменные типа Boolean, которые будут играть роль флагов, то есть определять, нажата соответствующая клавиша или нет. Листинг 12.14
var
Forml: TForml; up, down, lef, right: Boolean; // флаги клавиш
implementation В обработчике события OnKeyDown, если клавиша нажата, флагу будет присваиваться True. В обработчике события OnKeyUp, если клавиша отпущена, будет присваиваться False; Листинг 12.15 procedure TForml.FormKeyDown(Sender: TObject; var Key: Word; S h i f t : TShiftState);
Глава 12. Хитрости с мышью и клавиатурой begin case Key of 38: up := True; 40: down := True; 37: lef := True; 39: right := True; end; end;
Листинг 12.16 procedure TForml. FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); begin case Key of 38: up := False; 40: down := False; 37: ief := False; 39: right := False; end; end; Кстати, для выяснения кодов клавиш я воспользовался программой, описанной в разд. 12.3, Реагирование на нажатия клавиш зададим в процедуре TimerlTimer. Причем чем меньше будет интервал у таймера, тем чутче и четче компьютер будет отзываться на нажатия клавиш. Поместите на форму компонент Timer и задайте свойству Interval значение 20. Теперь рассмотрим процедуру обработки события OnTimer. Листинг 12.17 procedure TForml.Timer1Timer(Sender: TObject); begin if up=True then Label"!. Co I or := с I Red e l s e Label 1.Color := cIGreen; if lef=True then Label 2.Co I or := с I Red else Label 2. Co I or := с I Green; if down=True then Labels.Co I or := с I Red else Labels.Color := cIGreen; if right=True then Label 4.Co I or := с I Red e l s e Label4.Color := cIGreen; end; Теперь можно запустить программу и посмотреть, что у нас получилось (рис. 12.5).
Быстрый опрос клавиатуры
Рис. 12.5. Программа для быстрого опроса клавиатуры (в данном случае опрашиваются только четыре клавиши)
Получилось, в общем-то, нормально. Компьютер довольно быстро реагирует на нажатия и всегда понимает одновременное нажатие на две клавиши и иногда — одновременное нажатие трех и более клавиш. Интервала 20 вполне достаточно. Можно поэкспериментировать, задав другие интервалы. Если поставить 50, то замедление практически не заметно, а вот если 200, то уже заметно. Если вы пишете какую-то программу, используя этот способ, подбирайте оптимальный интервал, чтобы реакция программы на действия пользователя была не замедленной. Готовое приложение и полные листинги рассмотренного примера вы можете найти на компакт-диске, прилагаемом к книге, по адресу ExamplesXKeysf ast.
•
Глава 13
Эффектные формы Q Q Q а Q
Формы необычного вида Кнопки необычного вида Способы перемещения окон Прозрачные окна Программы-«невидимки»
Формы необычного вида
13.1. Формы необычного вида «Думаете, что окна могут быть только прямоугольными? Совсем не обязательно. Окна могут иметь какую угодно форму», — сказал я, выглянув на улицу через самое что ни на есть прямоугольное окно панельного дома. Чтобы окно приняло нестандартный вид, нужно на этапе его создания установить регион. Регион определяет область окна, в которую операционная система выводит изображение. Windows не показывает те области окна, которые не входят в регион. Регион задается с помощью функции Windows API — SetWindowRgn. Рассмотрим пример создания овального окна (рис. 13.1).
Standatd I Additional I Win32! Sustem] Dala Access I Data Controls I dbExoress I DataSnao) BDE 2
3
f.IJ ' . [Э|1Г % A Rf Ц SU !x (? *§ Я ™ '"] ill; ! l.jjfj
Se.
end;
32 1
SModKied ;
Рис. 13.1. Овальное окно
Для создания овального окна нужно в обработчике события OnCreate формы написать следующие строки. Листинг 13.1
procedure TForro1.FormCreate(Sender: TObject); var hRgn: cardinal; begin
Глава 13. Эффектные формы
hRgn := CreateElI i pticRgn(0,0,Width,Height); SetWindowRgn(Hand Ie.hRgn,False); end; Здесь сначала создается регион в форме эллипса, а затем устанавливается вид окна с помощью функции SetWindowRgn. В качестве параметров функции CreateEllipticRgn нужно указать: • nLef tRec — дг-координата верхнего левого угла эллипса; • nTopRec — г/-координата верхнего левого угла эллипса; • nRightRect — л:-координата нижнего правого угла эллипса; • nBottomRect — г/-координата нижнего правого угла эллипса. Если указать эти параметры как (0,0, width, Height), то будет создано окно с эллипсом такого размера, который задан через параметры Width и Height в Object Inspector (рис. 13.2).
\'.;':- ::;::'."",..:.'..".
;•'/."• • '..-. ' : : . . . ' " ' .
'• •
••-" • '
••'• ' ' ^ЩШйШ^.. i С
•aiiiiiiiiiiilii
Рис. 13.2. Такое окно можно создать, используя Delphi
Если Width и Height будут равны, то получится, конечно же, круглое окно. Функция CreateEllipticRgn возвращает Handle на созданный регион, если завершена успешно. В случае неудачи возвращается NULL. SetWindowRgn устанавливает регион окна. Параметры функции SetWindowRgn: • HWND — Handle на что-то, для чего нужно установить регион (например, форма); • HRGN — Handle на регион (созданный предварительно); • BOOL — флаг, показывающий, перерисовывать ли окно.
Формы необычного вида Если функция выполнится успешно, то она возвращает число, отличное от нуля. Если нет, то возвращает ноль. Для задания прямоугольного региона нужно использовать функцию CreateRectRgn. Если регион, созданный с помощью этой функции, назначить форме, то получится обычная прямоугольная форма. Можно задать регион с помощью картинки в формате Bitmap. Рассмотрим пример. Листинг 13.2
procedure TForm1.CreateBWindow(AHWND: HWND; bitmap: TBitmap); var back_color: COLORREF; wnd_dc,hdc_bmp: HOC; tmp_rgn,wnd_rgn: HRGN; w i d t h , height,x,xO,y: Integer; begin width := bitmap.width; height := bitmap.height; wnd_dc := GetDC(aHwnd); hdc_bmp := CreateCompatibleDC(wnd_dc); SelectObject(hdc_bmp,bitmap.handle); back_color := GetPixel (hdc_bmp,0,0); tmp_rgn := CreateRectRgn(0,0,0, 0); wnd_rgn := CreateRectRgn(0,0,0,0); x := 0; for у := 0 to height-1 do beg i n w h i le (x < width-1) do begin whi le ((GetPixel (hdc_bmp,x, y) = back_co!or) and (x < width)) do inc(x); if(x <> width) then begin xO := x; wh i le ((GetPixel(hdc_bmp, x, y) <> back_color) and (x < width)) do i nc(x); tmp_rgn := CreateRectRgn(xO,y, x, y+1); Comb i neRgn(wnd_rgn,wnd_rgn,tmp_rgn, RGN_XOR); end; end; x := 0; end; De I eteObject(tmp_rgn); DeleteDC(hdc_bmp); SetWindowRgn(aHwnd,wnd_rgn,True); DeIeteObj ect(wnd_rgn); end; 7 Зак. 899
Глава 13. Эффектные формы Чтобы посмотреть на примере, как применять эту функцию, нужна какая-нибудь картинка, на основе которой будет создаваться регион для формы. Откройте программу Paint и задайте через меню Рисунок > Атрибуты размер картинки (желательно, чтобы он совпадал с размером предполагаемого окна). Затем нарисуйте в редакторе Paint фигуру, в форме которой должно быть окно. Я нарисовал грушу (рис. 13.3). Файл Правка Параметры Справка
Рис. 13.3. Окно программы точно будет повторять форму этого изображения
Груша получилась немного кривоватая, но на арбуз никак не похоже, тем более что важен не сам' рисунок, а то, что окно программы будет точно повторять форму изображения. Когда будете рисовать, имейте в виду, что функция CreateBWindow (см. листинг 13.2) принимает цвет пиксела в левом верхнем углу картинки за прозрачный цвет. Потом рисунок нужно сохранить на диске. В Delphi на форму помещаем компонент Image. Делаем его невидимым (свойство Visible=False) и загружаем в него полученную картинку. Затем в FormCreate задаем вызов функции CreateBWindow. Листинг 13.3 procedure TForml. FormCreate(Sender: TObject); begin CreateBWindow(Form1. Handle, Image"!. Picture. Bitmap);
end; Теперь запустите программу и посмотрите на полученную форму (рис. 13.4). Можете добавить в форму компоненты. У меня, например, получилась груша с кнопками.
Кнопки необычного вида
JUL iki!
. LlL _'. JiZiii
4 11-е 1 _d 1 нет ] 4 578 Б72 из 19 993136 k свободно : •- jl-c-l Jill нет 1 4 578 672 иэ 19 999 136 k свободно 1ЯлЯЯЯИТП'1!ПНЯЯРН51ыИ?Ш5й"Ч||^ пгаВЩяг !ТИмя Тип ; Размео S Дата 1 f Имя Тип -Размер Шага J3Ch2_07 doc ;t..[.-] t.,1. 1 iSbottle bmp L^[ch01] gCh2_08 doc iglruit bmp C][ch02] ИСИ2_03 doc IQProjecll cfg ^"$Ch2_02 doc^4Ch2 10 doc iyPioiecll dol m-WRL1S50tmp^Ch2_11 doc ijffiProjecrt dpi WChO_01 doc ж Ptojectl exe ^jChl 01 doc OPiojecll res ?FjCh1_02 doc inUnill dcu ^СЫ_03 doc pUnil! dim Ch1_04 doc if-Unill pas h1_05 doc 1_06 doc •_07 doc ~oa doc l_09 doc i1_10 doc _,ch1_11 doc MCh2_01 doc @Ch2_02 doc ®Ch2_03 doc gCh2_04 doc ®Ch2_05 doc ЩСЬ2_06 doc
bmp 368 254 22.04.200416:16 -a-
liuil
[..}
30.05.200421:53 --
- book\WORK_FILES\ExarapletVegian» F3 Просмотр
j
F4 Правка
|
F5 Копия
;
j-J i
F6 Перемет
i
F7 СоздКаг
i
F8 Удалить
i
AII+F4 Выход
Рис. 13.4. В середине экрана окно, созданное с помощью региона в форме груши
13.2. Кнопки необычного вида Регионы можно менять, конечно, не только для окон, но и для других компонентов, например для кнопок. Таким способом получают круглые, треугольные и другие кнопки. Можно, допустим, создать кнопку в форме бутылки. Допустим, можно сделать кнопку в форме бутылки пива или кефира или просто в форме шара. Кому что нравится. Функцию CreateBWindow вызываем из OnCreate формы. В качестве параметра указываем ей Handle на кнопку: CreateBWindow (BitBtnl .Handle, Imagel . Picture .Bitmap) ; Следующий листинг иллюстрирует работу с регионами. Листинг 13.4 procedure TForml. FormCreate(Sender: TObject); // в обработчике события FormCreate
var hRgn: Cardinal ; begin // задаем регион формы (эллипс)
Глава 13. Эффектные формы
fcLLJ
hRgn := CreateElI i pticRgn(0,0,Width,Height); SetWindowRgn(Handle, hRgn, False); // создаем регион кнопки (шар) hRgn := CreateElI i pticRgn(10,10,BitBtn1.Width-10,BitBtnl. Height-10); SetWi ndowRgn(B i tBtnl.Hand Ie, hRgn, Fa Ise);
end; procedure TForml. BitBtnlCI ick(Sender: TObject);
beg i n
v
// завершаем приложение Appl icat ion.Termi nate;
end; Получилась круглая кнопка на овальной форме. Если щелкнуть на кнопке, то приложение закроется. В полном виде этот пример можете найти на компакт-диске, который прилагается к книге, по адресу Examples\Regions\FlatButton. Лучшего визуального эффекта можно добиться, если на кнопку загрузить картинку, которая будет имитировать объемность кнопки — показывать, что кнопка не плоская, а шарообразная. Этот пример также содержится на компакт-диске по адресу Examples\Regions\BallButton.
13.3. Способы перемещения окон Если создать окно нестандартной формы, то, скорее всего, у него не будет строки заголовка, и, естественно, перемещать такое окно по экрану, перетягивая его за строку заголовка, не получится: Желательно, чтобы такое окно можно было перемещать мышью, «зацепившись» указателем за любую его точку. Так, например, можно передвигать окно популярной программы WinAmp. Суть способа сводится к изменению обработчика сообщения WM_NCHitTest. Листинг 13.5
procedure TForml.WMNCHitTest(var M: TWMNCHitTest);
beg i n
;
i nherited; if M.Result = h t C I l e n t then M.Result := htCaption;
end; В секцию Private declarations нужно вписать объявление этой процедуры, как показано в следующем листинге. Листинг 13.6
type TForml = class(TForm) private
Прозрачные окна { Private declarations } procedure WMNCHitTest(var M: TWMNCHitTest); message WM_NCHitTest; publ ic { Publ ic declarations } end;
Теперь, если запустить программу, можно передвигать окно мышью, захватывая его не только за строку заголовка, а за любую область. С помощью этой процедуры, мы «обманываем» систему, определяя всю поверхность окна как строку заголовка. Можно сделать и наоборот. Если в м. Result вместо htCaption (строка заголовка) поместить htciient, то система будет полагать, что строка заголовка окна — это поверхность окна, и двигать окно будет невозможно даже за строку заголовка. Рассмотрим пример. Листинг 13.7
procedure TForm1.WMNCHitTest(var M: TWMNCHitTest); begin Inherited; if M.Result = htCaption then M.Result := htCIlent; end; Запустив программу, можно убедиться, что окно передвинуть невозможно.
13.4. Прозрачные окна Можно создать такое окно, которое не будет видно на экране. На форме будут располагаться компоненты, но, если запустить такое приложение, будет создаваться впечатление, что компоненты то ли висят в воздухе, то ли находятся на Рабочем столе Windows. Попробуем воплотить в реальность такое «воздушное» окно. Если в процедуре FormCreate написать: Forml.Brush.Style := bsClear; то окно будет простой рамкой со строкой заголовка, будут видны компоненты, располагающиеся на форме. Интересный эффект получается при перетаскивании такого окна — в нем остается содержимое той области экрана, которая была в нем отображена (рис. 13.5). Чтобы получилось прозрачное окно, нужно еще добавить такую строку: Forml.BorderStyle := bsNone; Прозрачность окна не означает, что его нет на экране. В этом легко убедиться, запустив программу. Даже если щелкать мышью на областях, которые это невидимое окно «закрывает» (например, на значках на Рабочем столе), ничего не происходит — окно закрывает собой значки, несмотря на то что его не видно.
Глава 13. Эффектные формы
Стрелки: ГВнэчэ;
> Redit
Стрелки; Г" В начале f?'В конце Фоомэ.
> Rwluct Grainin*» > Drop Studow Hull nug»J > Vijnetl. (lull im«9«)
Рис. 13.5. При перетаскивании на окне осталась часть изображения, которое было под ним
Прозрачные окна имеет смысл использовать тогда, когда почти все окно закрыто какими-то компонентами или на окно выводится непрямоугольное изображение. Например, ходящие и танцующие по экрану девушки. Обратите внимание, что невидимые, нестандартные окна и кнопки потребляют намного больше ресурсов компьютера, чем стандартные средства. Поэтому, если, например, вы сделали окно в форме танка, не удивляйтесь, что такое окно будет медленно перемещаться на некоторых компьютерах. Однако практически все игры выполнены с использованием нестандартных средств, поэтому будет ли программа работать медленно — зависит от конкретной реализации. Есть более удачный способ делать прозрачные, а точнее, полупрозрачные окна. Этот способ стало возможно применять начиная с шестой версии Delphi. В класс TForm была введена поддержка полупрозрачности форм, то есть пользователю видно то, что окно должно было бы закрывать собой. Причем прозрачность можно плавно менять. Для этого были добавлены свойства AlphaBlend, AlphaBlendValue, TransparentColor, TransparentColorValue. Сразу вынужден огорчить пользователей и программистов, у которых установлена cHcreMaWindows 95 или Windows 98, а также тех, у кого маломощный компьютер. Свойства полупрозрачности работают в Windows 2000 и более новых операционных системах и не работают на компьютерах, устаревших сильнее, чем Pentium I.
Прозрачные окна Конечно, такие компьютеры устарели, но их все-таки еще очень много. Совсем не важно, на каком компьютере компилировалась программа. Будут работать эти эффекты или не будут, зависит только лишь от того, на каком компьютере и с какой операционной системой программу запускают. Однако если вы пишете программу и у вас эти эффекты не работают, то вы не сможете их нормально отладить. Итак, свойство AlphaBLend может принимать значения True и False. Соответственно, если задать True, то полупрозрачность формы включена, а если False — выключена. По умолчанию это свойство установлено в False. Насколько форма должна быть прозрачной, указывается в свойстве AlphaBlendValue. Если циклически менять значение этого свойства, то окно будет красиво плавно возникать на экране из ниоткуда или, наоборот, плавно исчезать. Если свойству AlphaBlendValue присвоить О, то окно будет полностью прозрачным. Если присвоить 255 — то полностью непрозрачным. Можно задавать прозрачность окна по определенному цвету. Для этого используются свойства TransparentColorValue и TransparentColor. Через свойство TransparentColor можно включать (True) и выключать (False) прозрачность окна по цвету. По умолчанию прозрачность выключена (False). Свойство TransparentColorValue показывает, какой цвет считать прозрачным. Если в процессе разработки программы (в окне Object Inspector) присвоить свойству AlphaBlend — True, a AlphaBlendValue, например, 128, то окно ведет себя совершенно обычным образом, но получается прозрачным даже без написания дополнительного кода. В книге не приведено изображение полученного прозрачного окна, так как черно-белая иллюстрация не может передать эффект. Готовое приложение и полные листинги рассмотренного примера вы можете найти на компакт-диске, прилагаемом к книге, по адресу Examples \Blend (NT only). Рассмотрим листинг программы, окно которой плавно исчезает с экрана при закрытии. Программа небольшая, поэтому я привожу ее целиком, абсолютно без сокращений. Все, что нужно сделать в режиме визуального программирования, это поместить на форму таймер Timerl. Можете разместить на форме также какие-нибудь компоненты, чтобы посмотреть, как выглядит исчезающая форма с компонентами. Листинг 13.8
unit Unit1; Interface
uses Windows,Messages, SysUti Is, Variants, Classes,Graphics,Controls, Forms, Dialogs, ExtCtrls;
type TForml = class(TForm)
Т i me r1: TT i me r;
Глава 13. Эффектные формы procedure Tirner1Timer(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); Private { Private declarations } Publ ic { Publ ic declarations }
end; var Form"!: TForml; Implementat ion {$R *.dfm} procedure TForml.Timer1Timer(Sender: TObject); begin Form"! . A l p h a B l e n d V a l u e := Form"!. AlphaBlendValue - 5; if Form"!. AlphaBlendValue=0 then Form"! .Close;
end; procedure TForml. FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin if Timer"!. Enabled=False then begin CanClose := False; Timer"!. Enabled := True; exit;
end; CanClose := True; end; end.
Алгоритм работы программы таков. Когда пользователь пытается закрыть окно, генерируется событие CLoseQuery и выполняется процедура FormCloseQuery, то есть запрос на закрытие окна. Если в этой процедуре переменной CanClose присвоить False, окно не будет закрыто. Мы так и делаем, присваиваем CanClose значение False, так как закрывать окно пока рано, нужно продемонстрировать эффект. Запускаем таймер, который занимается уменьшением значения свойства ALphaBlendValue. Также по таймеру проверяется, достигла ли форма полной прозрачности. Когда достигла, закрываем форму принудительно. Снова срабатывает процедура FormCloseQuery, но теперь таймер уже запущен, и CanClose устанавливается в True — приложение закрывается. Готовое приложение и полные листинги рассмотренного примера вы можете найти на компакт-диске, прилагаемом к книге, по адресу Examples YPlavnblend (NT only).
Программы-«невидимки»
13.5. Программы-«невидимки» Вам никогда не хотелось создать такую программу, которую пользователь не замечает? Программа проработала, а пользователь ничего не заметил. Если поместить программу в автозагрузку, то она будет работать при каждой перезагрузке компьютера, а пользователь ничего не заметит. Речь идет совсем не о вирусе, а например, о программе, в которую можно ввести день рождения хозяина компьютера. Эта программа при каждой перезагрузке будет проверять, совпадает ли текущая дата с датой, введенной в ее память. Если дата совпала, программа может проиграть музыку и поздравить юбиляра. Можно, конечно, сделать программу, которая будет напоминать о днях рождения друзей и знакомых. Но я сейчас говорю именно о незаметной для пользователя программе. Незаметность, конечно, относительна. Опытный пользователь рано или поздно обратит на нее внимание, а например, какой-нибудь ваш друг, который использует компьютер только для того, чтобы музыку слушать и в Quake играть, не заметит ни за что. Если написать поздравляющую программу, а потом аккуратно поставить ее ему на компьютер, то, думаю, он обрадуется, увидев, что умный компьютер не забывает о нем и поздравляет с днем рождения. Создадим такую программу. После установки эту программу нужно настроить: указать день рождения хозяина компьютера, на который вы внедряете это ценное программное обеспечение, и запустить программу вручную. После этого она добавится в автозагрузку и будет работать сама. Насчет помещения программы в автозагрузку не все очевидно. Дело в том, что некоторые операционные системы (например, Windows 2000 и Windows XP) восстанавливают системные файлы после несанкционированного доступа к.ним. По этой причине заносить программу в автозагрузку будем максимально корректным способом, чтобы наша программа работала в любой операционной системе. Также программа не должна иметь ни одной формы. Она должна работать быстро и незаметно, а значит, быть очень маленькой. Этого можно добиться, убрав из приложения поддержку форм и других стандартных средств, из-за которых ЕХЕ-файл, созданный в Delphi, не бывает меньше 200, а то и 400 Кбайт. Вы спросите: «А как же тогда поздравлять нашего друга?» Для этого можно придумать разные решения. Во-первых, создавать форму, не используя библиотеки Delphi (с помощью функций Windows API). Однако этот способ в нашем достаточно простом случае не очень уместен, к тому же можно сделать гораздо изящнее. В нашем случае лучше будем запускать, например, браузер Opera с поздравлением. Можно открывать файл с картинкой или запускать звуковой файл с поздравлением. Конкретных реализаций такого способа может быть множество. Начнем, пожалуй. Создайте новый проект в Delphi. Затем сразу нажмите кнопку со знаком минус для удаления формы из проекта (или через меню Project > Remove from Project). Выберите Unitl и, подтвердив выбор, удалите форму из проекта. После чего можно сохранить проект. Желательно не сохранять проект до удаления формы, иначе она останется со всеми остальными файлами и ее надо будет удалять
Глава 13. Эффектные формы вручную. Когда будете сохранять проект, назовите его Noform. Теперь нажмите кнопку View Unit или Ctrl+F12 или через меню View > Units выберите Projectl. В окне редактирования кода откроется текст программы. Те, кто программировал на языке Паскаль, узнают родные знакомые черты: здесь все то же самое, что и в Паскале. Программа выполняет построчно все, что расположено между begin и end. Именно здесь будем писать весь необходимый нам код. Удалите строки: Application.Initialize; Application.Run;
Должно получиться такое пустое приложение, как показано в следующем листинге. Листинг 13.9
program Projectl; uses Forms;
i
{$R *.res}
•
\
begin {Здесь будем писать программу} end. Обратите внимание на uses Forms. Модуль Forms нам не нужен — никакие формы мы вызывать не собираемся, как я уже говорил. А ЕХЕ-файл «благодаря» этому модулю получается очень большой — около 400 Кбайт. Теперь удалим uses Forms и откомпилируем программу. Фантастика! Всего 9 Кбайт! Файл уменьшился в десятки раз. Чтобы была возможность работать с некоторыми функциями, касающимися операционной системы, придется объявить модуль Windows: uses Windows. Размер получаемого ЕХЕ-файла практически не изменился. Для того чтобы поместить нашу программу в автозагрузку, придется объявить модуль Registry: uses Registry. Из-за него ЕХЕ-файл увеличится приблизительно на 100 Кбайт. Про реестр и автозагрузку читайте в гл. 20, поэтому на них я сейчас подробно останавливаться не буду. Для работы с датами нужно объявить модуль Sysutils, а для вызова других программ (нам надо будет запускать браузер, чтобы донести поздравления до взора пользователя) необходим модуль shellApi. Все эти модули, за исключением Registry, практически не увеличивают размер получаемого ЕХЕ-файла. Файл размером 120 Кбайт —вполне неплохо.
Программы-«невидимки» Теперь пишем программу. Я немного поменяю тактику — не буду объяснять по порядку каждую добавляемую в программу строку, а приведу сразу весь листинг программы. Программа специально подробно комментирована, и разобраться в ней будет легко, тем более с моими дальнейшими пояснениями. Листинг 13.10 program Noform; uses Windows, Registry, SysUtils, ShellApi; {$R «.res}
var
Regp: TRegistry; today: TDateTime; year, month, day: word; F: TextFi le; // файл с датами F2: TextFi le; // создаваемый файл-поздравление s: String; // сюда читаются строки из файла sKey: String; // для работы с реестром n: Integer; // для поиска символов в строке yearF, monthF, dayF: integer; kolvo: Integer; // номер поздравления за се'годня st: String; {рабочая переменная для работы с kolvo} begin sKey := Software\Microsoft\Windows\CurrentVersion\Run; kolvo : = 0; // помещаем программу в автозагрузку Regp := TRegistry.Create; Regp.RootKey:=HKEY_LOCAL_MACHINE; Regp.OpenKey(sKey,True); Regp.WriteStri ng(Noform,paramstr(O)); Regp. Free; // определяем текущую дату today := Date; DecodeDate(today,year,month, day); // читаем из файла даты и сравниваем их assignfi le(F,dates.txt); {$!-} Reset(F); {$!+} // если ошибки открытия файла с датами нет, // продолжаем работу if ioresult=0 then begin
Глава 13. Эффектные формы whi le not eof(F) do begin Readl_n(F, s); if s= then continue; // пустая строка, игнорируем // преобразовываем дату, записанную в строке, // в переменные типа Integer - dayF, monthF, yearF n := pos(., s); dayF := StrTolnt(copy(s, 1, n-1)); delete(s,1,n); n := pos(., s); monthF := StrTolnt(copy(s,1, n-1)); delete(s,1,n); n := pos( , s); yearF := StrTolnt(copy(s,1, n-1)); delete(s,1,n); // в s остается имя поздравляемого // сравниваем, если дата совпадает, // то выводим поздравление на экран if (year=yearF) and (month=monthF) and (day=dayF) then // поздравляем beg in // создаем файл i nc(kol vo); st := out + IntToStr(kolvo) + .html; AssignFi le(F2, st); Rewrite(F2); WriteLn(F2,); WriteLn(F2,); Wr i teLn( F2, ПОЗДРАВЛЕНИЕ<Д i 11 e>); WriteLn(F2,); W r i t e L n ( F 2 , < b o d y bgcolor="#FFFFFF">); WriteLn(F2, + s +
); WriteLn(F2,Поздравляем С ДНЕМ РОЖДЕНИЯ!!I); WriteLn(F2,); WriteLn(F2,