ОСНОВЫ ПРОГРАММИРОВАНИЯ В DELPHI
2 УДК ББК О-75 Составители: Е.С. Гарбузняк, преп. Л.Ф. Кардаш, преп. Рецензенты: В.Е. Фёдоров, канд. экон. наук, доц., каф. прикладной информатики, филиал ПГУ им. Т.Г. Шевченко в г. Рыбница Л.Я. Козак, канд. техн. наук, доц., каф. физики, математики и информатики, филиал ПГУ им. Т.Г. Шевченко в г. Рыбница О-75 Основы программирования в Delphi: Практикум / Сост.: Е.С. Гарбузняк, Л.Ф. Кардаш. Рыбница, с. (в обл.) Практикум содержит практические работы по дисциплине «Информатика и программирование», позволяющие изучить основные приёмы алгоритмизации и программирования в интегрированной среде Delphi, принципы разработки, отладки и тестирования программ. Практикум предназначен для студентов II курса направления «Прикладная информатика». Может быть использован студентами других направлений. УДК ББК Рекомендовано Научно-методическим советом ПГУ им. Т.Г. Шевченко Е.С. Гарбузняк, Л.Ф. Кардаш, составление, 2015
3 ОГЛАВЛЕНИЕ ВВЕДЕНИЕ. 5 ПРАКТИЧЕСКАЯ РАБОТА ЗНАКОМСТВО С ИНТЕГРИРОВАННОЙ СРЕДОЙ ПРОГРАММИРОВАНИЯ DELPHI ПРАКТИЧЕСКАЯ РАБОТА РАБОТА С ОСНОВНЫМИ ТИПАМИ ДАННЫХ В DELPHI. ИСПОЛЬЗОВАНИЕ ПОДПРОГРАММ ПРАКТИЧЕСКАЯ РАБОТА РАБОТА СО СТРОКАМИ В DELPHI. УПРАВЛЕНИЕ ЦИКЛАМИ ПРАКТИЧЕСКАЯ РАБОТА НАСТРОЙКА ВНЕШНЕГО ВИДА ФОРМЫ. РАБОТА С ПАНЕЛЯМИ ПРАКТИЧЕСКАЯ РАБОТА СТАНДАРТНЫЕ ДИАЛОГОВЫЕ ОКНА WINDOWS. РАБОТА С МЕНЮ ПРАКТИЧЕСКАЯ РАБОТА СОЗДАНИЕ ПАНЕЛИ ИНСТРУМЕНТОВ. ОРГАНИЗАЦИЯ МЕХАНИЗМА ДЕЙСТВИЙ. ИСПОЛЬЗОВАНИЕ ТЕХНОЛОГИИ MDI ПРАКТИЧЕСКАЯ РАБОТА РАБОТА С ФАЙЛАМИ ПРАКТИЧЕСКАЯ РАБОТА ГРАФИЧЕСКИЕ ВОЗМОЖНОСТИ DELPHI ПРАКТИЧЕСКАЯ РАБОТА МУЛЬТИМЕДИА ВОЗМОЖНОСТИ DELPHI 3
4 ПРАКТИЧЕСКАЯ РАБОТА ДИНАМИЧЕСКИЕ МАССИВЫ В DELPHI. СОЗДАНИЕ ПРОСТОЙ БАЗЫ ДАННЫХ ПРАКТИЧЕСКАЯ РАБОТА ВВЕДЕНИЕ В БАЗЫ ДАННЫХ ПРАКТИЧЕСКАЯ РАБОТА МЕХАНИЗМ DRAG-AND-DROP ПРАКТИЧЕСКАЯ РАБОТА АВТОМАТИЗАЦИЯ ACTIVEX РЕКОМЕНДУЕМАЯ ЛИТЕРАТУРА
5 ВВЕДЕНИЕ Практикум составлен в соответствии с рабочей программой дисциплины «Информатика и программирование» и призван помочь студентам в приобретении знаний и умений, необходимых в учебной и будущей профессиональной деятельности. Практикум, состоящий из тринадцати работ, представляет собой практический курс освоения основных приёмов алгоритмизации и программирования в интегрированной среде Delphi, принципов разработки, отладки и тестирования программ. Каждая работа включает теоретическую часть, практическую часть, контрольные вопросы и индивидуальные задания. В теоретической части даны краткие сведения, необходимые для получения практических навыков программирования в Delphi, в практической части основные приёмы работы в среде программирования и задания, в которых приведены подробные инструкции по её применению. Практикум позволяет осваивать возможности среды программирования Delphi, сочетая изучение теоретического материала с практическими заданиями, что является наиболее эффективным способом изучения нового материала. Разработанная серия вопросов важна для закрепления и проверки материала. Индивидуальные задания направлены на усвоение пройденного материала, приобретение практических умений и навыков, на активизацию учебной деятельности студентов. Порядок расположения материала соответствует последовательности его изучения. Практикум предназначен для студентов II курса направления «Прикладная информатика». Может быть использован студентами других направлений. 5
6 ПРАКТИЧЕСКАЯ РАБОТА 1. ЗНАКОМСТВО С ИНТЕГРИРОВАННОЙ СРЕДОЙ ПРОГРАММИРОВАНИЯ DELPHI Цель познакомиться с основными правилами работы в интегрированной среде программирования Delphi, выработать практические навыки работы в ней. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ 1. Интерфейс среды программирования Delphi Delphi интегрированная среда для разработки приложений (Integrated Development Environment IDE) на языке Object Pascal. Это среда, в которой есть всё необходимое для проектирования, запуска и тестирования приложений и где всё нацелено на облегчение процесса создания программ. Delphi мощная система визуального объектноориентированного проектирования, позволяющая решать множество задач, в частности: создавать законченные приложения для Windows самой различной направленности, от вычислительных и логических до графических и мультимедиа; быстро создавать профессионально выглядящий оконный интерфейс для любых приложений; создавать мощные системы работы с локальными и удалёнными базами данных; создавать справочные системы для разработанных приложений и многое другое. Для запуска Delphi необходимо выполнить одно из действий: дважды щёлкнуть на ярлыке на рабочем столе; выполнить команды Program Files Borland Delphi Bin и запустить файл delphi32.exe; выполнить команды Пуск Все программы Borland Delphi Delphi. При загрузке Delphi 7 появляется главное окно среды разработки (рис. 1.1), состоящее из пяти областей: 6
7 Рис Главное окно Delphi 1. Главное окно Delphi (Delphi Project). Оно состоит из трёх основных частей: главного меню, панели инструментов и палитры компонентов. Главное меню и левая панель инструментов предназначены главным образом для создания, сохранения, открытия, компиляции и запуска на выполнение приложений. В палитре компонентов библиотеки визуальных компонентов (Visual Component Library VCL) содержатся визуальные (видимые пользователю) и не визуальные компоненты (они явным образом не видны пользователю). Палитра компонентов содержит ряд страниц, закладки которых видны в её верхней части. 2. Конструктор (окно) формы (Form). В это окно в процессе написания программы помещаются необходимые компоненты для создания интерфейса программы. Причём при выполнении программы помещённые компоненты будут иметь тот же вид, что и на этапе проектирования. Для переключения между конструктором форм и редактором кода используется клавиша F Редактор кода (окно программы, модуль, Unit). Окно модуля программы предназначено для просмотра, написания и редактирования текста (кода) программы. При первоначальной 7
8 загрузке в окне текста программы находится текст, содержащий минимальный набор операторов для нормального функционирования пустой формы в качестве Windows-окна. При помещении некоторого компонента в окно формы текст программы автоматически дополняется описанием необходимых для его работы библиотек стандартных программ (раздел uses) и типов переменных (раздел type). 4. Инспектор объектов (Object Inspector). Он предназначен для управления объектами проекта и состоит из двух вкладок Свойства (Properties) и События (Events). Страница свойств Инспектора объектов показывает свойства того объекта, который в данный момент выделен. Если щёлкнуть на окне пустой формы, то на странице свойств Инспектора объектов можно увидеть свойства формы. Эти свойства можно изменять. Например, если изменить свойство Caption (Надпись) формы, написав в нём «Моя форма», эта надпись появится в полосе заголовка формы. Страница событий составляет вторую часть Инспектора объектов. На ней указаны все события, на которые может реагировать выбранный объект. Например, если необходимо выполнить какие-то действия в момент создания формы, то следует выделить событие OnCreate. Рядом с именем этого события откроется окно с выпадающим списком. Если в приложении уже существуют какие-то обработчики событий, то при событии OnCreate можно использовать один из них, для чего следует выбрать необходимый обработчик из выпадающего списка. Если же надо написать новый обработчик, то следует сделать двойной щелчок на пустом окне списка. 5. Дерево объектов (Object TreeView). Здесь можно увидеть, какой именно объект в данный момент является текущим. Это окно будет особенно полезно, когда на форме появится много компонентов. 2. Обзор палитры компонентов Delphi 7 Среда разработки Delphi ориентирована, прежде всего, на создание программ для Windows. При этом большое внимание уделяется возможности визуальной разработки приложений с помощью большого набора готовых компонентов Delphi, позволяющих избежать ручного кодирования. Компоненты Delphi 8
9 охватывают практически все аспекты применения современных информационных технологий. Конечно, для работы в Delphi необходимо изучить базовые компоненты, которые требуются при подготовке практически любого приложения. Основные компоненты Delphi находятся на первых четырёх вкладках (рис. 1.2): 1. Standard. 2. Additional. 3. Win System. Рис Основные вкладки с компонентами Delphi Их названия всплывают в виде подсказок при наведении мышки на пиктограммы. Чтобы перенести компонент на форму, нужно щёлкнуть по нему левой кнопкой мыши (при этом выбранный компонент выделяется) и затем щёлкнуть в том месте формы, где его предполагается разместить. В дальнейшем компоненты можно свободно перетаскивать по форме мышкой, конструируя нужный интерфейс. Если, щёлкнув по компоненту, необходимо отказаться от его переноса на форму, следует щёлкнуть по стрелке, расположенной слева на вкладке. Выделение компонента снимется. Изучение Delphi начинается со страницы палитры компонентов Standard. На этой странице расположены стандартные для Windows интерфейсные элементы, такие как главное и всплывающее меню, кнопка, однострочный и многострочный редакторы, переключатели, метки, списки и некоторые другие компоненты, применяющиеся наиболее часто при создании приложений. Ниже в таблице (табл. 1.1.) представлен список компонентов страницы Standard. 9
10 Пиктограмма Имя MainMenu PopupMenu Label Edit Memo Button CheckBox RadioButton ListBox ComboBox ScrollBar GroupBox RadioGroup Panel ActionList Компоненты страницы Standard Назначение Таблица 1.1 Главное меню. Компонент способен создавать и обслуживать сложные иерархические меню. Всплывающее меню. Это меню появляется после нажатия на правую кнопку мыши. Метка. Используется для размещения коротких сообщений в виде статического текста. Строка ввода. Предназначена для ввода пользователем текстовой информации в виде одной строки. Многострочный текстовый редактор. Используется для ввода пользователем и отображения многострочного текста без функций форматирования. Командная кнопка. Используется для реализации в программе команд с помощью обработчика события OnClick этого компонента. Независимый переключатель. Используется его свойство Checked (Отмечено), имеющее значения True или False, меняющееся при щелчке мышью. Зависимый переключатель. Используется для выбора только одного из нескольких вариантов. Для этого компонент объединяется как минимум с одним или несколькими такими же компонентами в группу. Список выбора. Содержит список предлагаемых вариантов и позволяет контролировать текущий выбор. Выпадающий список выбора. Представляет собой комбинацию компонентов Edit и ListBox. Полоса прокрутки. Представляет собой вертикальную или горизонтальную полосу, управляющую визуальным представлением компонентов, не помещающихся целиком в окне программы. Контейнер группы компонентов. Используется для группировки нескольких связанных по смыслу компонентов. Группа зависимых переключателей. Содержит специальные свойства для обслуживания нескольких связанных между собой зависимых переключателей. Панель. Этот компонент, как и GroupBox, служит для объединения нескольких компонентов. Содержит внутреннюю и внешнюю кромки, что позволяет создавать эффекты «вдавленности» и «выпуклости». Список действий. Служит для реакции программы на действия пользователя, связанные с выбором одного из группы однотипных управляющих элементов. 10
11 На страницу Additional помещены дополнительные компоненты, без которых сегодня трудно представить программу для Windows: кнопки с дополнительными свойствами, таблицы, компоненты для размещения изображений и многие другие, представленные в таблице (табл. 1.2) ниже. Пиктограмма BitBtn Имя SpeedButton StringGrid DrawGrid Image Shape Bevel ScrollBox CheckListBox Splitter StaticText Chart Компоненты страницы Additional Назначение Таблица 1.2 Командная кнопка. Отличается от стандартной кнопки Button возможностью отображения пиктограммы. Пиктографическая кнопка. Используется для быстрого доступа к опциям Главного меню. Таблица строк. Этот компонент обладает мощными возможностями для представления текстовой информации в табличном виде. Таблица изображений. Этот компонент используется для представления изображений в табличном виде. Рисунок. Компонент для отображения изображений, в том числе пиктограмм и метафайлов. Фигура. С помощью этого компонента можно вставить на форму правильную фигуру: прямоугольник, эллипс, окружность. Кромка. Служит для выделения отдельных частей формы трёхмерными рамками и полосами. Панель с полосами прокрутки. В отличие от компонента Panel, автоматически вставляет полосы прокрутки, если размещённые на нём компоненты отсекаются его границами. Список множественного выбора. Отличается от компонента ListBox наличием рядом с каждой опцией независимого переключателя типа CheckBox, облегчающего выбор сразу нескольких опций. Граница. Этот компонент создаёт границу между двумя видимыми компонентами и даёт возможность пользователю перемещать её. Статический текст. Отличается от компонента Label наличием собственного Windows-окна, что позволяет обводить текст рамкой или выделять его в виде «вдавленной» части формы. Диаграмма. Этот компонент облегчает создание специальных панелей для графического представления данных. 11
12 Страница Win32 содержит компоненты, представляющие собой интерфейсные элементы для 32-разрядных операционных систем Windows 95/98/NT. Использующие эти компоненты программы выглядят в стилистике последних версий операционных систем Windows. Ниже в таблице (табл. 1.3.) представлен список компонентов страницы Win32. Пиктограмма Имя TabControl PageControl ImageList RichEdit TrackBar ProgressBar UpDown HotKey Animate Компоненты страницы Win32 12 Назначение Таблица 1.3 Набор закладок. Каждая закладка представляет собой поле с надписью и/или текстом. Выбор закладки распознаётся программой и используется для управления содержимым окна компонента. Набор панелей с закладками. Каждая панель может содержать свой набор интерфейсных элементов и выбирается щелчком по связанной с ней закладке. Набор рисунков. Представляет собой хранилище для нескольких рисунков одинакового размера, например, пиктограмм для кнопок. Многострочный редактор форматированного текста. В отличие от компонента Memo, может изменять такие характеристики текста, как шрифт, цвет, выравнивание и т.д. (формат rtf). Регулятор. Используется для управления значениями некоторых величин в программах. Например, с его помощью удобно изменять громкость звучания мультимедийных устройств. Индикатор процесса. С помощью этого компонента можно отображать ход исполнения длительного процесса, например, копирование данных. Цифровой регулятор. Две кнопки этого компонента служат для увеличения (верхняя) или уменьшения (нижняя) связанной с компонентом числовой величины. Для отображения этой величины умеет ассоциироваться с компонентом Edit. Управляющая клавиша. Компонент служит для ввода управляющих кодов (F1, Ctrl + Shift и т.д.) Мультипликатор. Предназначен для отображения движущихся изображений (видеоклипов). Имеет ограничение: не может сопровождать видеоклип звуком. Как и компонент ProgressBar, применяется для сопровождения длительных процессов.
13 DateTimePicker MonthCalendar TreeView ListView HeaderControl StatusBar ToolBar CoolBar PageScroller Селектор времени/даты. Этот компонент предназначен для ввода или отображения времени или даты. Календарь. Служит для отображения календаря и выбора даты или диапазона дат. Дерево выбора. Представляет собой совокупность связанных в древовидную структуру пиктограмм. Обычно используется для просмотра структуры каталогов и других подобных элементов, связанных иерархическим образом. Панель пиктограмм. Организует просмотр нескольких пиктограмм и выбор нужной. Компонент способен располагать пиктограммы в вертикальных или горизонтальных рядах и показывать их в крупном или мелком масштабе. Управляющий заголовок. Представляет собой горизонтальную или вертикальную полосу, разделённую на ряд смежных секций с надписями. Размеры секций можно менять на этапе работы программы. Обычно используется для изменения размеров столбцов или строк в разного рода таблицах. Панель статуса. Предназначена для размещения разного рода служебной информации в окнах редактирования. Пример нижняя часть рамки окна текстового редактора Word. Инструментальная панель. Этот компонент служит контейнером для командных кнопок BitBtn и способен автоматически изменять свои размеры и положение при добавлении или удалении кнопок. Инструментальная панель. В отличие от ToolBar, используется как контейнер для размещения стандартных интерфейсных компонентов Windows таких, как Edit, ListBox, ComboBox и т.д. Прокручиваемая панель. Служит для размещения узких инструментальных панелей. При необходимости автоматически создаёт по краям панели стрелки прокрутки. На странице System представлены компоненты, которые имеют различное функциональное назначение (например, Timer очень важный в любой программе компонент), в том числе компоненты, поддерживающие стандартные для Windows технологии межпрограммного обмена данными OLE и DDE. Ниже в таблице (табл. 1.4.) представлен список компонентов страницы System. 13
14 Пиктограмма Timer Имя PaintBox MediaPlayer OleContainer DDEClientConv DDEClientItem DDEServerConv DDEServerItem Компоненты страницы System Назначение Таблица 1.4 Таймер. Этот компонент служит для отсчёта интервалов реального времени. Имеет ограничение: не может обрабатывать интервалы менее 55 миллисекунд. Окно для рисования. Создаёт прямоугольную область, предназначенную для прорисовки графических изображений. Мультимедийный проигрыватель. С помощью этого компонента можно управлять различными мультимедийными устройствами. OLE-контейнер. Служит приёмником связываемых или внедряемых объектов. DDE-связь. Совместно с DDEClientItem используется для создания клиентской программы в DDE-связи. DDE-тема. Определяет тему DDE-связи в клиентском приложении. DDE-связь. Совместно с DDEServerItem используется для создания серверной программы в DDE-связи. DDE-тема. Определяет тему DDE-связи в серверном приложении. ПРАКТИЧЕСКАЯ ЧАСТЬ 1. Создание проекта В Delphi программный код всегда привязывается к какомулибо событию, которое является сигналом к началу работы алгоритма. Основополагающей идеей программирования в интегрированной среде Delphi является следующее: программы управляются событиями. Реализация этой идеи предусматривает этапы: 1. Проектирование экранной формы. 2. Установка событий, которые будут происходить в работающем приложении. 3. Программирование действий, связанных с событиями. 14
15 1.1. Сохранение проекта Разрабатываемое в среде визуального программирования Delphi приложение называется проектом. Проект совокупность файлов разных форматов, из которых создаётся программа. В качестве примера создадим простое приложение, которое позволит: 1. Ввести температуру, выраженную в градусах по шкале Цельсия. 2. Преобразовать введённое значение в градусы, выраженные в шкалах Реомюра и Фаренгейта. Создание нового проекта следует начинать с выбора в меню команды File (Файл) New (Новый) Application (Приложение). Целесообразно сразу сохранить создаваемый проект в отдельной папке, например, Delphi_lab. При сохранении проекта среда Delphi создаёт несколько файлов. Одни из них содержат описание проекта в целом, другие описания программных модулей и форм. Для сохранения проекта нужно воспользоваться одним из способов: выбрать в меню команду File (Файл) Save All (Сохранить Всё); нажать горячие клавиши Shift + Ctrl + S; щёлкнуть по пиктограмме Save All (Сохранить Всё) на панели инструментов. В появившемся диалоговом окне (рис. 1.3) «Сохранить Unit1 как» ("Save Unit1 As") следует: 1. Выбрать созданную папку или создать новую. 2. Заменить имя программного модуля Unit1 на уникальное более информативное, например, для рассматриваемого примера можно задать имя Grades_.pas. При этом расширение *.pas при задании имени модуля указывать необязательно, оно добавляется автоматически. Затем Delphi с помощью ещё одного диалогового окна (рис. 1.4) «Сохранить Project как» ("Save Project As") запросит имя проекта, которое по умолчанию имеет имя Project. Его также следует заменить на уникальное (например, Grades), но без символа «_», так как имена проекта и программного модуля не должны совпадать. 15
16 Рис Диалоговое окно сохранения модуля Рис Диалоговое окно сохранения проекта 16
17 1.2. Создание экранной формы Форма основная единица визуального программирования, графическое представление окна Windows приложения вместе с его содержанием (один проект может использовать несколько форм). Форма служит основой программы. Каждая форма в период выполнения программы соответствует отдельному окну. Работа над новым проектом начинается с создания стартовой формы окна, которое появляется при запуске приложения. Стартовая форма создаётся путём назначения свойств формы, которые определяют её внешний вид: положение на экране, текст заголовка, размер формы, вид рамки и т.д. Как указывалось ранее, свойства перечислены на вкладке Свойства (Properties) окна Инспектора объектов и их свойств. В левой колонке перечислены имена свойств, в правой их значения. Можно вносить изменения, вводя новые значения в правую колонку. Щёлкните на вкладке Свойства (Properties) и с помощью полосы прокрутки просмотрите все свойства формы. Если попробовать выбирать различные свойства, можно заметить различия в способах отображения их значений. По умолчанию Delphi в качестве заголовка окна формы проекта Grades использует Form1. Целесообразно изменить его на уникальное имя. Для этого нужно: 1. Щёлкнуть на вкладке Свойства (Properties) в верхней её части. 2. В Инспекторе объектов (Object Inspector) найти свойство Caption (Заголовок). 3. Щёлкнуть на этом свойстве для его активизации. 4. Набрать желаемый текст, например, «Переводы градусов». Заголовок окна формы изменяется уже в процессе ввода. Можно не завершать ввод нажатием клавиши Enter (в этом случае поле ввода будет подсвечено ещё раз). Размеры формы в пикселях можно установить, используя свойства Width (Ширина) и Height (Высота). При выборе другого свойства, например, Color (Цвет фона формы), после его значения выводится кнопка раскрывающегося 17
18 списка. Щелчок на ней обеспечит появление списка доступных значений этого свойства, из которых можно выбрать нужное. У формы и у других объектов могут быть вложенные свойства. Перед их названиями стоит знак «+». После двойного щелчка на имени вложенного свойства появляется список уточняющих свойств. При этом знак «+» заменяется на. В поле значения свойства Font (Шрифт) расположена кнопка с тремя точками. После её нажатия появляется стандартное диалоговое окно выбора шрифта и его характеристик. Перечисленные свойства определяют внешний вид формы. Внутри же кода Object Pascal обращение к форме для управления ею осуществляется по имени, заданному свойством Name (Имя). Активизируйте это свойство и задайте ему значение GradeC. Чтобы не потерять выполненные назначения, сохраните проект: File (Файл) Save (Сохранить). Задайте значения следующим свойствам формы, как указано в таблице ниже (табл. 1.5). Таблица 1.5 Значения свойств формы примера «Переводы градусов» Свойство Обозначение Значение Имя формы Name GradeC Заголовок Caption Переводы градусов Ширина Width 380 Высота Height 300 Цвет фона Color clbtnface Шрифт Font.Name MS Sans Serif Размер шрифта Font.Size 8 Для того, чтобы запустить форму (программу), необходимо воспользоваться одним из способов: нажать клавишу F9; выбрать команду Запуск Запуск (Run) в меню; щёлкнуть на кнопку с зелёным треугольником на панели инструментов. На экране появляется изображение обычного окна Windows с заданными выше свойствами (рис. 1.5). Можно изменять размеры окна и перемещать его по экрану стандартным способом. Для завершения программы нужно воспользоваться одним из способов: 18
19 дважды нажать на кнопку системного меню (значок левее заголовка формы); нажать клавиши Alt + F4; щёлкнуть на кнопке закрытия в правом верхнем углу окна. Рис Экранная форма Приложение следует всегда закрывать, чтобы вернуться к режиму кодирования (программирования) Компоненты формы Начнём программирование приложения с создания интерфейса. В Delphi такие элементы интерфейса, как командные кнопки, поля редактирования, управления, находящиеся на форме, называются компонентами. В приложении форма и её компоненты рассматриваются как объекты, над которыми производятся различные действия. Поэтому большая часть работы по созданию и компоновке программ в Delphi сводится к: 1. Выбору компонентов. 2. Размещению компонентов на форме с помощью мыши. 3. Определению свойств компонентов. Чтобы разместить на форме компонент, необходимо на палитре компонентов щёлкнуть на кнопке с нужной пиктограммой, затем щёлкнуть в той точке формы, где будет находится правый верхний угол компонента. В результате этих действий на форме появится компонент стандартного размера. Программа перевода градусов из одной шкалы в другую должна получать от пользователя исходные данные градусы Цельсия. Как известно, в Windows данные вводятся в поля редактирования с клавиатуры. Поэтому на форму нужно поместить 19
20 компонент управления и редактирования Edit, находящийся на вкладке Standard (рис. 1.6). Рис Палитра компонентов страницы Standard Установите курсор на компонент Edit (пиктограмма с двумя маленькими буквами "ab"). Этот компонент позволяет пользователю прочитать или записать одну строку текста (по умолчанию длиной до 255 символов). Щёлкните левой клавишей мыши на указанной пиктограмме. Визуально пиктограмма станет «углублённой». Далее переместите указатель мыши в верхний левый угол окна формы и снова щёлкните левой клавишей мыши. Компонент появится на форме (рис. 1.7). Рис Размещение компонента на форме Очистите поле компонента с помощью свойства Text, после чего компонент на форме будет выглядеть следующим образом (рис. 1.8): Рис Очищенное поле компонента Edit Размер компонента можно менять, используя маркеры выделения: маленькие квадратики, окружающие компонент (см. рис. 1.7). Для этого необходимо выполнить следующие действия: 20
21 1. Щёлкнуть на изображении компонента. 2. Установить курсор на одном из его маркеров. 3. Нажать левую клавишу мыши. 4. Удерживая её нажатой, изменить положение границы компонента. 5. Отпустить клавишу мыши. При этом Delphi отображает текущие размеры компонента. Чтобы изменить местоположение компонента, нужно: 1. Установить курсор мыши на компоненте. 2. Нажать её левую клавишу. 3. Удерживая её нажатой, перемещать компонент. В процессе перемещения Delphi будет указывать текущие значения верхнего левого угла компонента (свойства Left и Top). Кроме компонента Edit, окно формы должно содержать поясняющий текст рядом с полем редактирования. Текст, расположенный на форме, называется меткой (Label) или этикеткой. Метка в палитре компонентов представлена пиктограммой с буквой "А". Добавьте четыре метки на форму (рис. 1.9). Первая будет задавать текст над полем редактирования. Остальные три послужат для вывода результатов. Рис Размещение на форме группы меток Для размещения на форме нескольких компонентов одного типа следует во время выбора компонента из палитры нажать клавишу Shift, и кнопка палитры останется визуально «углублённой». Затем, разместив нужное количество компонентов, следует щёлкнуть на стрелке выбора в палитре компонентов (крайняя слева). 21
22 После размещения меток на форме первой из них задайте значение свойства Caption «Задайте градусы по шкале Цельсия». Введённое значение отобразится на форме (рис. 1.10). Рис Изменение значения свойства Caption для первой метки Размер метки устанавливается автоматически, если свойство AutoSize (Автоматический подгон размера) имеет значение True (Истина). Если же текст должен занимать несколько строк, то надо вручную установить размер метки, а значения свойств задать: AutoSize (Автоматический подгон размера) False (Ложь), WordWrap (Перенос слов) True (Истина). Добавьте на форму командную кнопку (на её пиктограмме изображено "OK"). Поместите её в правый нижний угол (рис. 1.11) и установите свойство Caption «Перевести». Рис Добавление на форму командной кнопки 22
23 1.4. Событие и обработчик события После создания интерфейса необходимо написать программу, которая выполнит запланированные действия: 1. Введёт исходные данные. 2. Сделает преобразования. 3. Выведет результат. Следует иметь в виду, что после того, как программа для Windows загрузилась в память и выполнила некий код инициализации, она ничего не делает. Ничего не произойдёт, пока не случится некоторое событие (event). Поэтому вместо перечня инструкций (операторов), принятого при линейном программировании, программа на Delphi содержит набор алгоритмов, определяющих её действия для различных событий. Внешний вид создаваемой формы должен подсказывать, как работает приложение. Очевидно, что пользователю нужно ввести температуру в градусах Цельсия в поле редактирования и щёлкнуть на кнопке «Перевести». Щелчок левой клавишей мыши на изображении кнопки «Перевести» это пример события в Windows. Событием называется то, что происходит во время выполнения приложения. В Delphi у стандартных событий есть стандартные имена. Щёлкните на вкладке События и с помощью полосы прокрутки просмотрите все стандартные события формы. Например, событие, соответствующее одному щелчку клавишей мыши, имеет имя OnClick, а двойному щелчку мыши OnDblClick. Реакцией на событие должно быть какое-либо действие. Так, реакцией на событие OnClick, произошедшее на кнопке «Перевести», должны быть вычисления в соответствии с формулами перевода градусов из одной шкалы в другую. Реакция на событие в Delphi реализуется как процедура обработки этого события. Такая процедура называется обработчиком события. Таким образом, задачей разработчика в Delphi является определение множества событий для поставленной задачи и создание соответствующих процедур-обработчиков событий. Для написания кода обработчика нужно выделить объект (кнопка «Перевести»). Далее на вкладке События (Event) окна Инспектора объектов (Object Inspector) выбрать событие OnClick и дважды щёлкнуть на правом столбце указанного события. В результате открывается окно редактора кода с именем и макетом процедуры-обработчика события (рис. 1.12). 23
24 Рис Вид окна редактора кода при обработке события Delphi автоматически образует имя процедуры обработки события как комбинацию двух частей, разделённых точкой. Первая часть идентифицирует форму, содержащую объект, для которого создаётся обработчик, вторая идентифицирует сам объект и событие. Для нашего примера имя процедуры состоит из имени формы GradeC, имени командной кнопки «Перевести» Button1 (имя установлено по умолчанию в свойстве Name объекта), имени события Click (без букв On). Автоматически на странице События (Events) появится сформированное имя процедуры в строке события OnClick. В окне редактора кода после заголовка процедуры нужно объявить используемые переменные: procedure TGradeC.Button1Click(Sender: TObject); var Celsius: Real; // градусы по Цельсию Fahrenheit: Real; // градусы по Фаренгейту Reaumur: Real ; // градусы по Реомюру 24
25 В разделе var описаны три переменные Celsius, Fahrenheit и Reaumur, которые обозначают градусы по шкалам Цельсия, Фаренгейта и Реомюра соответственно. Между операторными скобками begin и end в разделе операторов следует написать операторы языка Object Pascal, реализующие процедуру обработки события. Для перевода градусов из одной шкалы в другую следует использовать соотношения: 1 0 R = 5/4 0 C (ноль градусов совпадает у обеих шкал); 1 0 F = 5/9 0 C (ноль градусов соответствует 32 0 F); C = F. Код процедуры для события Click кнопки «Перевести» выглядит так, как показано ниже на рисунке (рис. 1.13). Рис Код процедуры для события Click кнопки «Перевести» 25
26 Исходные данные программа получает из поля редактирования обращением к свойству Text. Это свойство содержит строку символов, поэтому получить действительное число можно с помощью функций StrToFloat или Val. Celsius := StrToFloat(Edit1.Text); Значение температуры проверяется на значение нуля по Цельсию в соответствии со шкалой по Фаренгейту: if Celsius <= then При введении значения температуры ниже C в программе необходимо предусмотреть соответствующее сообщение. Для этого используется процедура, параметром которой является выражение строкового типа: ShowMessage('Температура'+Edit1.Text + ' градусов Цельсия не существует); В коде свойства меток Caption заданы как пустые строки. Label2.Caption := ' '; Label3.Caption := ' '; Label4.Caption := ' '; Присвоение пустых значений этому свойству сделано для того, чтобы очистить поля этих меток после предыдущего запуска расчёта перевода градусов. Если введённое значение температуры выше абсолютного нуля, то вывод информации осуществляется путём присвоения свойству Caption соответствующих меток вычисленных значений в виде текстов сообщений (для этого используется обратная функция FloatToStr). Одна метка дублирует заданное значение по шкале Цельсия. Label2.Caption := Edit1.Text + ' градуса(ов)' + ' по шкале Цельсия равны; Две другие метки применяются для вывода результатов вычислений. Предварительно по формулам вычисляются значения температур для шкал Фаренгейта и Реомюра. Каждый результат в 26
27 коде представлен в виде двух строк. Для перехода на другую строку служит код #13: Fahrenheit := 1.8 * Celsius + 32; Label3.Caption := ' ' + FloatToStr(Fahrenheit) + ' градусам' + #13 + по шкале Фаренгейта'; Reaumur := 4/5 * Celsius; Label4.Caption := ' ' + FloatToStr(Reaumur) + ' градусам' + #13 + 'по шкале Реомюра'; В коде программы используется комментарий (две наклонные черты), действие которого распространяется до конца строки. В конце программы Edit очищается: Edit1.Text := ' '; Ряд свойств устанавливается не только на этапе проектирования, но и во время выполнения программы. Можно изменять цвет фона и цвет символов при выполнении программы. Для этого в ветви else сразу после begin напишем два оператора присваивания: Label2.Color := clblack; // цвет фона Label2.Font.Color := clred; // цвет символа Для того, чтобы переместить кнопку в левую часть окна приложения и изменить её размеры, после первого перевода градусов следует написать такие операторы в конце процедуры: Button1.Left := 80; Button1.Height := 50; Button1.Width := 150; 2. Запуск приложения из среды Delphi. Компиляция. Ошибки, предупреждения и подсказки После завершения набора кода и сохранения проекта приложение можно откомпилировать. Запустить программу на компиляцию можно: выбрав в меню Проект (Project) команду Компилировать Grades (Compile Grades); нажав клавиши Ctrl + F9. 27
28 В результате на экране появится окно «Компиляция» ("Compiling"). В разделе Готово: (Done) появляется либо текст «Откомпилировано» (Compiled), либо сообщение о наличии ошибок. Если при компиляции обнаружены ошибки (рис. 1.14), то в окне «Компиляция» указываются: 1. Советы (Hints). 2. Предупреждения (Warnings). 3. Ошибки (Errors). Рис Окно компиляции проекта при наличии ошибок При наличии ошибок процесс компиляции останавливается и в нижней части окна выводятся сообщения об ошибках (рис. 1.15). Рис Окно редактора кода программы при наличии ошибок 28
29 Этих сообщений не меньше двух, так как второе содержит информацию о невозможности генерации исполняемой программы (фатальная ошибка Fatal Errors). Пример фатальной ошибки отсутствие символа «;» в конце оператора: Edit1.Text := ' '. При этом строка с ошибкой всегда подсвечивается, что позволяет быстрее её найти и исправить. Предупреждение менее серьёзное событие по сравнению с ошибкой, но и оно может в итоге привести к неправильным результатам. Выделение оператора в качестве комментария // Celsius := StrToFloat(Edit1.Text); приведёт к предупреждению, которое также отображается в нижней части окна редактора кода программы (рис. 1.16). Рис Окно редактора кода программы при наличии предупреждения По смыслу это означает, что переменной не присвоено начальное значение. В этом случае программа будет откомпилирована, но выдаст неверные результаты. Подсказка обычно указывает на то, что какое-либо место в программе можно улучшить. Например, если в программе описана 29
30 переменная www, которая использоваться не будет, то в нижней части окна появится соответствующая подсказка (рис. 1.17). Рис Окно редактора кода программы при наличии подсказки После устранения всех ошибок при успешной компиляции приложения появляется соответствующее сообщение «Откомпилировано» (рис. 1.18). Рис Окно Компиляция при успешной проверке на ошибки После компиляции в левой части окна редактора кода программы появятся синие точки, обозначающие работающую строку программы (рис. 1.19). 30
31 Рис Окно редактора кода программы при успешной компиляции Пустые строки и строки описания переменных точками не обозначаются, так как никаких действий не выполняют. Синими точками отмечены также начало (begin) и конец (end) логического блока, поскольку при этом выполняется ряд специфических действий. Если в программе нет синтаксических ошибок, компилятор создаст в каталоге, где находится файл проекта, файл приложения (с именем, как у файла проекта). Этот файл можно будет позже запустить непосредственно из Windows. Запустить приложение можно и не завершая работу с Delphi. Для этого следует выполнить одно из трёх действий: нажать клавишу F9; выбрать команду Запуск (Run) в пункте Главного меню Запуск; щёлкнуть левой клавишей мыши на кнопке с зелёным треугольником на панели инструментов. В результате начала работы приложения для переводов градусов появляется экранная форма с расположенными на ней компонентами (рис. 1.20). 31
32 Рис Окно запущенного приложения После задания температуры в поле ввода и нажатия кнопки «Перевести» на форме в полях меток будут размещены результаты перевода (рис. 1.21). При этом введённое в поле ввода значение очищается. Рис Окно приложения после ввода значения градусов Окно приложения ведёт себя, как обычное окно Windows. Его можно перемещать по экрану, изменять его размеры, развернуть на весь экран и т.д. 32
33 После запуска программы окно Инспектора объектов (Object Inspector) и их свойств исчезает перед появлением формы «Переводы градусов». Когда программа активна (рис. 1.22), в заголовке главного окна Delphi появляется дополнительное слово [Запущено] (Running). Рис Вид главного окна Delphi после запуска программы Во время выполнения программы могут возникнуть ошибки времени выполнения. В примере такая ошибка может возникнуть при задании градусов по шкале Цельсия значением, в котором целая часть от дробной части отделена точкой (а не запятой). Если, например, задать значение « », то в этом случае будет выдано соответствующее сообщение «Недопустимое значение данных с плавающей точкой» (рис. 1.23). Это связано со стандартной установкой. Для продолжения работы программы необходимо в окне с сообщением об ошибке нажать кнопку "OK", а затем выполнить команду Запуск (Run) и восстановить окно Grades. 33
34 Рис Пример ошибки времени выполнения После этого появится другое окно (рис. 1.24), в котором ошибка описана в упрощённом виде. Рис Окно вывода сообщения об ошибке времени выполнения после повторного запуска Можно нажать кнопку "OK" и ввести новые данные (если ошибка понятна) либо прекратить вычисления, завершив программу стандартным образом. Ошибка времени выполнения также может возникнуть, например, в операторе Reaumur := 4/5*Celsius; при ошибочной замене операции умножения делением, т.е. Reaumur := 4/5/Celsius; при Celsius = 0. В этом случае возникает ошибка «Деление на ноль данных с плавающей точкой» и появится окно с сообщением и рекомендациями по дальнейшим действиям (рис. 1.25). Рис Окно ошибки времени выполнения при делении на ноль 34
35 При возникновении ошибки времени выполнения целесообразно воспользоваться командой Сброс программы (Program Reset) из меню Запуск (Run) или комбинацией клавиш Ctrl + F2. КОНТРОЛЬНЫЕ ВОПРОСЫ 1. Чем является Delphi? 2. Какой язык используется при программировании в Delphi? 3. Что необходимо выполнить для запуска Delphi? 4. Из чего состоит Главное окно Delphi? 5. Перечислите основные вкладки Delphi. 6. Перечислите основные компоненты страницы Standard. 7. Перечислите основные компоненты страницы Additional. 8. Перечислите основные компоненты страницы Win Перечислите основные компоненты страницы System. 10. В чём заключается основополагающая идея программирования в среде Delphi? 11. Что называется проектом? 12. Как создать новый проект в среде Delphi? 13. Перечислите способы сохранения нового проекта. 14. Что называется формой? 15. В чем разница между формой, стартовой и экранной формами? 16. Перечислите основные свойства формы. 17. Каким образом можно изменить заголовок окна формы проекта? 18. Перечислите способы запуска формы (программы). 19. Перечислите способы завершения программы. 20. В чём заключается работа по созданию и компоновке программ в Delphi? 21. Как изменить размер компонента на форме? 22. Как изменить местоположение компонента на форме? 23. Как разместить на форме несколько компонентов одного типа? 24. Что называется событием? 25. Что называется обработчиком события? 26. Из каких частей состоит имя процедуры обработки события? 35
36 27. Для чего в коде программы используются комментарии и как они обозначаются в среде Delphi? 28. Как проверить проект на наличие ошибок? 29. Когда в проекте появляется предупреждение? 30. Для чего в проекте используется подсказка? 31. Когда могут возникнуть ошибки времени выполнения? 32. Что рекомендуется сделать при возникновении ошибки времени выполнения? ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ 1. Запустите интегрированную среду программирования Delphi одним из известных способов и внимательно изучите содержимое экрана. 2. Внимательно изучите компоненты основных вкладок среды программирования Delphi. 3. Создайте новый проект. 4. Измените название заголовка окна формы на «Моя первая программа», используя соответствующее свойство. 5. Измените имя формы на «MyForm», используя соответствующее свойство. Проверьте, что произошло в Дереве объектов после изменения имени объекта. 6. Выполнив предыдущих 2 шага, сделайте выводы об особенностях этих свойств формы. 7. Добавьте на форму компонент для размещения текста и с помощью соответствующего свойства напишите приветствие «Hello, world!». 8. Измените размеры и цвет сообщения, разместив его в центре формы. 9. Добавьте на форму командную кнопку, разместив её ниже приветствия. 10. С помощью соответствующего свойства измените надпись на кнопке на «Приветствие». 11. Создайте обработчик события нажатия на кнопку «Приветствие». 12. В процедуре обработки события (нажатия на кнопку) в соответствующем месте введите следующий код: ShowMessage('Hello, world!'); 36
37 13. Сохраните проект. 14. Проверьте проект на наличие ошибок. 15. Запустите приложение. Проверьте, что произойдёт при нажатии на кнопку. 16. Сравните способы вывода приветствия «Hello, world!» на форму. 17. Завершите работу программы. 18. Закройте проект.
38 ПРАКТИЧЕСКАЯ РАБОТА 2. РАБОТА С ОСНОВНЫМИ ТИПАМИ ДАННЫХ В DELPHI. ИСПОЛЬЗОВАНИЕ ПОДПРОГРАММ Цель научиться работать с целыми и вещественными типами данных в Delphi, изучить способы обработки статических многомерных массивов, а также научиться использовать подпрограммы при написании приложений в Delphi. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ 1. Типы переменных В любом языке программирования приходится использовать переменные. При загрузке программы компьютер вначале считывает все необходимые данные в оперативную память, после чего уже имеет возможность работать с ними. Переменная это ячейка оперативной памяти, которая может хранить данные какого-то одного типа. Переменная похожа на ячейку в MS Excel, там тоже в ячейке можно указать нужный формат данных. Если переменная имеет тип данных «строка», то в неё нельзя записать число. А в переменную с типом «целое число» нельзя записать вещественное число. Каждая переменная имеет уникальное имя. Для компьютера имя переменной это адрес ячейки памяти, где хранятся данные. Присваиванием имён (идентификаторов) переменным занимается программист. Имена переменным в Delphi даются по определённым правилам: 1. Имя переменной может содержать любое количество английских букв, цифр и знака подчёркивания. Другие символы недопустимы. 2. Первым символом обязательно должна быть буква. 3. В Delphi нет разницы, какие буквы даются переменным большие или маленькие. То есть, myperem, MyPerem, MYPEREM это одна и та же переменная. Переменную можно назвать одной буквой. Однако уже через месяц или раньше очень трудно будет вспомнить, зачем нужна была переменная F и какие данные в ней хранятся. Поэтому 38
39 переменным следует задавать осмысленные имена и сочетать большие буквы с маленькими для разделения на слова. Хорошие примеры MinZarplata или Glav_Param. Каждая переменная имеет свой тип. Тип переменной обязательно нужно указывать, потому что разные типы переменных занимают разный размер и компьютеру нужно знать, сколько байт в оперативной памяти требуется отвести под указанную переменную. Создание переменной состоит из двух этапов: 1. Объявление переменной (указываются имя и тип переменной). Переменная объявляется в специальном разделе var. 2. Присвоение переменной какого-то значения. Объявление переменной выглядит так: var Peremennaya1: Real; Peremennaya2, Peremennaya3: Integer; Как видно из примера, вначале указывается имя переменной, затем после двоеточия указывается тип переменной. Если нужно объявить несколько переменных одного типа, их имена разделяются запятыми. В приведённом примере была объявлена одна вещественная переменная типа Real и две целые переменные типа Integer. Присваивать значение переменным можно неоднократно. Переменная потому и называется так, что её значение в процессе работы программы может меняться. Примеры присвоения значений переменным: A := 10; B := 20.35; C := 'Это строка'; D := True; A := ; Механизм присвоения значения работает следующим образом: вначале рассчитывается значение в правой части команды, то есть после знака «:=». Затем результат этого значения записывается в переменную. В последней строке примера использовалось выражение « ». Вначале получается результат, в данном случае он равен 7. Затем этот результат записывается в переменную. 39
40 В дальнейшем имя переменной можно использовать в различных выражениях, например: A1 := 3; A2 := A1 + 7; A1 := A1 + 1; В первой строке в переменную записано число 3. Вторая строка содержит выражение, результатом которого будет число 10. В третью строку в переменную A1 будет записано 4: вначале рассчитывается результат правой части команды, где в переменной A1 ещё старое значение, затем он записывается в эту же переменную, изменяя её значение. Ниже в таблице (табл. 2.1) перечислены основные типы переменных. Название типа Integer Real String Boolean Описание Целое число Вещественное число Строка Логический тип Основные типы переменных Пояснения Таблица 2.1 Переменная может содержать только целые числа, как со знаком, так и без знака. Переменная может принимать в качестве значения целые и дробные числа, со знаком и без знака. Переменная может хранить любые символы и наборы символов. В переменную String можно записать до 2 Гб символов. Булева (логическая) переменная может иметь значение либо False (Ложь), либо True (Истина). На самом деле, типов переменных значительно больше, и по мере усложнения программ эти типы будут изучаться. 2. Целочисленные типы данных Целое число это число, не имеющее запятой. Число может быть беззнаковым (положительным) и со знаком минус (отрицательным). Тип Integer основной тип целых чисел. Действительно, этот тип приходится использовать чаще всего, однако существуют и другие типы целых чисел. Ниже в таблице (табл. 2.2) представлены целочисленные типы данных. 40
41 Таблица 2.2 Целочисленные типы данных Размер Название Диапазон возможных значений занимаемой типа памяти Примечание Integer байта Знаковое Cardinal байта Без знака ShortInt байт Знаковое SmallInt байта Знаковое LongInt байта Знаковое Int байт Знаковое Byte байт Без знака Word байта Без знака LongWord байта Без знака Любая программа работает так: когда программа запускается, она считывается в оперативную память компьютера. Туда же считываются и данные, с которыми программа работает. Только после этого программа начинает выполняться. Поэтому оперативную память следует экономить везде, где только можно. Это позволит увеличить скорость работы программы. Чаще всего при создании приложений используются переменные типа Integer, это самый распространённый тип целых чисел, он подходит почти для всех расчётов. Однако бывают моменты, когда не нужно такого большого диапазона значений. Например, при использовании переменной для счётчика какого-то цикла, который будет длиться, к примеру, от 1 до 100 не имеет смысла использовать тип Integer, поскольку у оперативной памяти будет занято 4 байта. 4 байта это немного, но в большой программе переменных будет очень много, и если все они будут тратить память попусту, то такая программа будет непрофессиональной. Поэтому следует научиться тратить столько байт оперативной памяти, сколько нужно. Не зря ведь придумали столько типов. Если заранее известно, что в переменной будут числа от нуля и выше, то нет никакой необходимости брать знаковый тип, ведь отрицательным число всё равно не будет. 3. Вещественные типы данных Вещественное число это число с запятой, после которой идут десятичные значения. Ещё говорят, что они имеют плавающую точку. Некоторые начинающие программисты 41
42 считают, что лучше такой тип переменных использовать всегда, даже при обработке целых чисел. Это большое заблуждение. Операции над числами с плавающей точкой отнимают у процессора гораздо больше времени и требуют больше памяти. Компьютер воспринимает вещественное число как два целых и делает двойную работу при обработке чисел до запятой и после неё. Однако иногда бывает необходимо использовать именно такой тип данных. К примеру, если нужно поделить одно целое на другое. Хорошо, если это будет «4/2», результат тоже будет целым 2. А если «4/3», тогда результатом будет 1,3333 и тут без вещественного числа не обойтись. А ведь заранее неизвестно, какие числа будет делить пользователь, поэтому лучше сразу иметь в виду, что результат может быть не целым числом. Как и целые, вещественные числа имеют несколько типов, которые представлены ниже в таблице (табл. 2.3). Таблица 2.3 Вещественные типы данных Количество Размер Название Диапазон возможных значений значащих занимаемой типа цифр памяти Real * * байт Real 5.0 * * байт Single 1.5 * * байта Double 5.0 * * байт Extended 3.6 * * байт Comp байт Currency байт Третий столбец таблицы указывает количество максимально значащих цифр. Цифры, которые выходят за этот предел, будут игнорироваться. Тут важно помнить, что вещественные числа не равны целым. То есть, число 3,0 не будет равно 3. Чтобы сравнить эти числа, следует округлить вещественное число. Наиболее удобным для использования в программах Delphi является тип Real. Ему эквивалентен тип Double, но в будущем это может быть изменено. Вычисления с дробными числами выполняются приближённо, за исключением типа Currency (финансовый), который предназначен для минимизации ошибок округления в бухгалтерских расчётах. 42
43 4. Статические массивы Массив это структура данных, доступ к элементам которой осуществляется по номеру (или индексу). Все элементы массива имеют одинаковый тип. Описание массива имеет вид: type имя_типа_массива = array [диапазон] of тип_элемента; Диапазон определяет нижнюю и верхнюю границы массива и, следовательно, количество элементов в нём. При обращении к массиву индекс должен лежать в пределах этого диапазона. Массив из ста элементов целого типа описывается так: или type TMyArray = array [ ] of Integer; type TMyArray = array [0.. 99] of Integer; После задания типа массива TMyArray можно описать переменные этого типа: var A, B: TMyArray; Вместо присвоения типа можно явно описать переменные как массивы: var A, B : array [1..100] of Integer; Для доступа к элементу массива нужно указать имя массива и индекс элемента в квадратных скобках. В качестве индекса может выступать число, идентификатор или выражение, значение которых должно укладываться в диапазон, заданный при описании массива: var N: Integer; begin N := 65; A[5] := 101; A[N] := 165; A[N + 3] := 200; B := A; end; 43
44 Иногда требуется узнать верхнюю или нижнюю границу массива. Для этого служат встроенные функции: High() возвращает число, являющееся верхней границей массива; Low() возвращает число, являющееся нижней границей массива. В скобки нужно подставить массив, границы которого требуется узнать. Массивы могут иметь несколько измерений, перечисляемых через запятую. Например, таблицу из четырёх столбцов и трёх строк можно описать в виде массива с двумя измерениями: type MyTable = array [1.. 4, 1.. 3] of Integer; var X: MyTable; Y: Integer; begin Y := X[3, 2]; end; Теперь в результате операции присвоения Y будет равен 7. При объявлении переменной двухмерного массива вначале задается диапазон индексов строк, затем через запятую диапазон индексов столбцов. Работа с такими массивами ничем не отличается от одномерных массивов, только в качестве индекса необходимо указать сразу два элемента строку и столбец. Многомерный, например, двумерный массив можно описать как массив массивов: type TMyArray = array [1.. 4] of array [1.. 3] of Integer; Результат будет аналогичен предыдущему примеру. Для Delphi двухмерные массивы не предел. Можно объявлять и трёхмерные, и четырёхмерные, вот только обрабатывать такие массивы будет сложней с каждой новой размерностью. Следует также отметить, что каждое измерение многомерного массива может иметь свой собственный тип, не обязательно целый. Вышеописанные массивы, у которых количество элементов неизменно, называют статическими массивами. В Delphi можно использовать и динамические массивы, количество элементов в 44
45 которых допускается изменять в зависимости от требований программы. Работа с динамическими массивами рассмотрена в практической работе 10. ПРАКТИЧЕСКАЯ ЧАСТЬ 1. Работа с целыми и вещественными типами данных 1.1. Преобразование типов данных Изучим работу с целыми и вещественными типами на практике. Для этого создадим простую программу, которая делит одно целое число на другое. Результат будет выводиться как вещественное число. Откройте Delphi, создайте новый проект. На форму необходимо поместить три компонента Label, три компонента Edit и одну кнопку, чтобы получилось так, как показано ниже на рисунке (рис. 2.1). Рис Внешний вид формы Чтобы выполнить одинаковую операцию над несколькими компонентами сразу, их можно выделить один за другим, удерживая клавишу Shift. Например, если таким образом выделить все три компонента Edit, то затем можно разом очистить их свойство Text. 45
46 Сохраните проект под именем MyCalc. Затем дважды щёлкните по кнопке, чтобы создать обработчик нажатия на кнопку. Перед begin процедуры следует создать раздел var и объявить там три переменных: var Perem1, Perem2: Integer; Perem3: Double; Затем вернитесь в тело процедуры (между командами begin и end) и присвойте целым переменным введённые пользователем значения. Здесь нужно понять одну важную вещь. Пользователь будет вводить значения в компоненты Edit, и там они будут храниться в свойстве Text в виде строкового типа данных. Строку нельзя будет присвоить переменной какого-либо другого типа данных, поэтому присвоение Perem1 := Edit1.Text; //ошибочное присвоение несовместимость // типов данных будет ошибочным. Разница довольно существенная: даже если пользователь вводит, казалось бы, целое число, например, 123, то компьютер видит строку символов '123', а вовсе не число. Выход преобразовать один тип данных в другой, в данном случае строку в целый тип. Преобразовать строку в целый тип можно с помощью функции: StrToInt('String'); В качестве параметра (в скобках) указывается строка. Функция преобразует её в целое число и вернёт его как результат. Примеры использования функции (эти примеры не нужно вводить в редактор кода): var s: String; i: Integer; begin s := '1234'; i := StrToInt(s); //параметр строковая переменная i := StrToInt( ); //параметр строка i := StrToInt(Edit1.Text); //параметр свойство Text //компонента Edit, имеющее строковый тип end; 46
47 Как видно из примера, имеются несколько возможностей передать в функцию строку. В первом случае преобразования передаётся строковая переменная s, в которой хранится строка '1234'. Функция преобразует эту строку в целое число, и в результате в переменную i попадёт уже число Во втором случае передаётся строка '123456', а в переменную i попадает преобразованное из этой строки число. В третьем случае в качестве параметра передаётся тот текст, который пользователь ввёл в поле ввода Edit1. Здесь следует сделать оговорку. Функция сработает правильно, если пользователь ввёл туда целое число. В противном случае возникнет ошибка. Пользователь личность непредсказуемая, поэтому перед преобразованием типов следует всегда делать проверку: действительно ли в поле ввода имеются только цифры от 0 до 9, нет ли там случайно буквы или запятой. Такую проверку вы научитесь делать позднее. Пока что нужно следить, чтобы в полях ввода были только целые числа. Вернитесь к программе. Сразу после begin присвойте целым переменным значения, которые ввёл пользователь: Perem1 := StrToInt(Edit1.Text); Perem2 := StrToInt(Edit2.Text); В третью, вещественную переменную, необходимо записать результат деления первого числа на второе. Тут может крыться ещё один «подводный камень»: если во второе поле пользователь ввёл число 0, то получится деление на 0, а на ноль делить нельзя. Если же попробовать это сделать, то компьютер, в лучшем случае, зависнет. Здесь опять следует делать проверку на правильность введённых данных, ставить, как говорят, «защиту от дураков». Подробнее такие проверки будут рассмотрены в практической работе 7. А пока просто наберите этот код: If Perem2 = 0 then begin //если это ноль, то: ShowMessage('На ноль делить нельзя!'); //выводит сообщение Edit3.Text := '0'; //как результат записывается ноль end else begin //иначе: Perem3 := Perem1 / Perem2; //делим Edit3.Text := FloatToStr(Perem3); //преобразуем вещественное // в строку и записываем результат end; 47
48 Следует обратить внимание на предпоследнюю строку. Функция FloatToStr() в качестве параметра принимает вещественное число и возвращает это же число в виде строки. Например, в результате преобразования s := FloatToStr(123.45); переменной s будет присвоена строка '123.45', которую затем уже можно будет вывести пользователю в качестве результата. В нашем примере результат деления двух целых чисел преобразуется в строку и выводится в поле Edit3. Следует заметить, что в качестве параметра можно передавать не только значение, но и выражение. Например, если указать Edit3.Text := FloatToStr(Perem1 / Perem2); то надобность в использовании вещественной переменной Perem3 отпадает. Попробуйте, как работают оба варианта Использование подпрограмм Иногда бывает необходимо выполнять часть кода неоднократно. Этот самый код выносят в отдельную подпрограмму процедуру или функцию. Процедура подпрограмма, которая выполняет какие-то действия и которую можно вызвать из другого места программы. После выполнения процедуры выполнение программы продолжается с того места, откуда она была вызвана. Процедура является самостоятельной частью программы, и её можно вызвать в любой момент, чтобы выполнить какие-то действия. Чтобы процедуру можно было вызвать из программы, её необходимо объявить выше того места, где будет осуществляться её вызов. Синтаксис процедуры приведён ниже: procedure NameProc(Param: Тип); var //объявление переменных(необязательно) begin //тело процедуры end; 48
49 Осуществить вызов процедуры можно, просто указав её имя. Проверьте это на практике. Вернитесь к проекту и выше процедуры обработки кнопки создайте такую процедуру: procedure Soobshenie; begin ShowMessage('Ошибка! На ноль делить нельзя!'); end; В данной процедуре не используются переменные, поэтому раздел var отсутствует. Всё, что делает созданная процедура, выводит сообщение о том, что на ноль делить нельзя. Обратите внимание, что если нет входящих параметров, то скобки указывать необязательно. Снова перейдите в процедуру обработки нажатия кнопки и вместо вывода сообщения, что на ноль делить нельзя, произведите вызов процедуры: Soobshenie; Сохраните проект, скомпилируйте его и посмотрите, что получилось. Не забудьте, что вводить в Edit1 и Edit2 можно только цифры от 0 до 9. Теперь подробнее о параметрах. Параметры это входные данные. То есть можно вызвать процедуру и задать ей нужные данные. Параметры процедуры записываются в круглых скобках с указанием типа. Если параметров нет, скобки можно не ставить. Пример процедуры с параметрами (этот пример не нужно вводить в редактор кода): procedure Primer(a, b: Integer); begin a := a * b; end; Обратите внимание, что обязательно нужно указывать тип параметров. После этого процедуру можно вызвать, указав ей, какие цифры нужно перемножить. Примеры вызова процедуры: Primer(10, 20); //передаём целые числа Primer(a, 100); //передаём переменную a с целым числом, и целое число Primer(c, d); //передаём две переменных с целым числом 49
50 Следует сказать об области видимости переменных. Переменные бывают глобальными и локальными. Глобальные переменные видны во всей программе, а локальные переменные создаются внутри процедуры, в разделе var, и видны только в этой процедуре. Локальные переменные создаются в памяти в то время, когда процедура начинает работу, и уничтожаются, когда процедура закончила работу. Таким образом, можно сделать две или более процедур и указать в них переменные с одинаковым именем, поскольку это будут разные переменные. Функция это такая же подпрограмма, как и процедура. Отличие функций от процедур в том, что они не просто выполняют какие-то действия и расчёты, но и могут возвращать результат определённого типа. Поскольку они возвращают результат, необходимо указать тип этого результата. Синтаксис функции приведён ниже: function NameFunc(Param: Тип): Тип_возвращаемого_значения; var //объявление переменных (необязательно) begin //тело функции Result := результат вычислений; end; Следует обратить внимание на два момента: после имени функции и параметров в круглых скобках после двоеточия указывается тип возвращаемого значения. Кроме того, в каждой функции по умолчанию имеется переменная Result, которая имеет тот же тип, что и тип возвращаемого значения. Эту переменную специально объявлять не нужно, она отвечает за результат. В Delphi этой переменной можно присваивать значение неоднократно. Результатом будет последнее присвоенное значение. Есть ещё один способ вернуть из функции результат вычислений: использовать переменную с таким же именем, как и имя функции. Эту переменную объявлять не нужно. В нашем примере, строка Result := результат вычислений; будет полностью идентична строке NameFunc := результат вычислений; 50
51 Какой из способов использовать решайте сами, оба способа правильны. Снова вернитесь к программе и для закрепления знаний добавьте в неё функцию. Функция также должна быть описана выше того места, где будет осуществляться её вызов. Можно создать её между созданной ранее процедурой Soobshenie и процедурой нажатия на кнопку. function Delenie(a, b: Integer): Real; begin Result := a / b; end; Замените ту строку, где в третью переменную записывается результат деления первых двух, на вызов функции и передачу ей этих двух чисел: Perem3 := Delenie(Perem1, Perem2); Конечно, эти примеры примитивны. Реально функции и процедуры выполняют гораздо более важные вещи, чем деление одного числа на другое. Со временем разрабатываемые приложения будут содержать множество таких функций и процедур. Следует отметить, что событие в Delphi это процедура, которой передаётся управление в случае, если произошли запрограммированные изменения. Событие означает, что какой-то компонент, которому было назначено событие, изменился. События могут быть самыми разными изменение текста в поле Edit, нажатие кнопки мыши (или клавиши) или просто курсор мыши оказалась над компонентом. Улучшите пример, введите в него событие. Выделите компонент Edit1. Задайте ему событие OnChange, которое происходит всякий раз при изменении текста в этом компоненте. Представьте себе пользователя, работающего с программой. Ему будет приятно, если он начнёт менять текст в первом поле, а остальные поля автоматически очистятся, чтобы быть готовыми для новых расчётов. После выделения компонента Edit1 перейдите в Инспекторе объектов на вкладку Events (События). 51
52 Дважды щёлкните по событию OnChange (Изменение). Создастся процедура обработки события, и откроется Окно редактора кода. Там следует вписать две строки: Edit2.Clear; Edit3.Clear; Вставьте ещё одну «защиту от дураков», ведь третье поле нужно только для результата. Если пользователь начнёт там вводить данные, то страшного ничего не произойдёт, поскольку в расчётах это поле не участвует, но всё равно плохо, когда программу используют неправильно. Выделите компонент Edit3. На вкладке Properties (Свойства) найдите свойство ReadOnly (Только для чтения) и вместо False (Ложь) поставьте True (Истина). Всё, теперь пользователь не сможет вводить данные в это поле, только программа сможет выводить в него результат. Сохраните проект, выполните Run и убедитесь в этом. 2. Обработка статических массивов 2.1. Одномерные массивы Рассмотрим простой пример работы с одномерным массивом, где всем элементам массива присваивается значение строки. Создайте новое приложение с одной командной кнопкой «Обработка одномерного массива» на форме и присвойте этот код обработке кнопки: var a: array [0.. 9] of String; i: Integer; s: String; begin for i := 0 to 9 do begin a[i] := 'Строка ' + IntToStr(i); s := s + a[i] + #13 + #10; end; //for ShowMessage(s); end; //конец процедуры В данном примере с помощью цикла for элементу массива a[i] присваивается строка «Строка 0». Причём номер строки 52
53 меняется при каждом проходе цикла. Затем полученная строка добавляется в строковую переменную s. Также были добавлены и символы перехода на новую строку, чтобы в конце вывести всё это с помощью процедуры ShowMessage(). Сохраните проект под именем MyArrays, скомпилируйте его и посмотрите, что получилось Многомерные массивы В сохранённом проекте с обработкой одномерного массива добавьте на форму ещё одну командную кнопку «Обработка двухмерного массива» и присвойте этот код обработке кнопки: var a: array [0.. 9, 0.. 4] of String; i, j: Integer; s: String; begin for i := 0 to 9 do for j := 0 to 4 do begin a[i, j] := 'Строка ' + IntToStr(i) + ' Столбец ' + IntToStr(j); s := s + a[i,j] + #13 + #10; end; //for ShowMessage(s); end; //конец процедуры Как видите, теперь приходится использовать два цикла for: один для строк и один для столбцов. Вначале выполняется цикл для первой строки и управление передаётся на вложенный цикл. Там происходит обработка массива от первого до последнего столбца первой строки. Затем программа возвращается в первый цикл, прибавляет к счётчику единицу и происходит такая же обработка для второй строки от первого до последнего столбца. И так далее, пока не будет обработан последний столбец последней строки. Сохраните проект, скомпилируйте его и посмотрите, что получилось. Трёхмерные массивы обрабатывать ещё сложней. Такая таблица будет выглядеть уже как кубическая. Объявляться трёхмерный массив будет так: a: array [0.. 9, 0.. 4, 0.. 7] of String; 53
54 Для циклической обработки трёхмерного массива потребуется уже три цикла for. Как будет выглядеть четырёхмерный массив, графическим образом вообще невозможно описать. Несмотря на то, что есть возможность работать хоть с десятимерным массивом, надобность более, чем в трёхмерном, в практике почти не встречается. Чаще всего приходится работать с одно- и двухмерными массивами Сетка строк StringGrid На практике познакомимся с сеткой строк StringGrid. Создайте новое приложение, установите на форму компонент StringGrid с вкладки компонентов Additional. Рядом находится ещё сетка DrawGrid. Разница между ними небольшая, но первую используют чаще. Сетка StringGrid самостоятельно прорисовывает данные в ячейках, а при использовании сетки DrawGrid эти данные придётся прорисовывать самостоятельно. Сетку DrawGrid обычно используют для прорисовки графики, и нужда в ней возникает намного реже, чем в StringGrid. Cетка StringGrid очень похожа на ту, что используется в MS Excel. Пока ещё она пуста. Выделите её и изучите следующие свойства: 1. BorderStyle отвечает за стиль обрамления. Может иметь только два значения: с обрамлением и без него. 2. ColCount количество колонок в сетке. По умолчанию их DefaultColWidth ширина колонок по умолчанию. Всем колонкам устанавливается одинаковая ширина. Однако во время работы программы ширину каждой колонки можно будет изменить программно. 4. DefaultDrawing прорисовка данных по умолчанию. Если стоит значение True, то компонент сам будет отображать введённые данные, иначе это придётся делать программисту. 5. DefaultRowHeight высота строк по умолчанию. Установлено 24 пикселя, но этот размер великоват, поэтому строки получаются такими высокими. 6. FixedCols количество фиксированных колонок. Они выделяются серым цветом и всегда первые. Это свойство можно назвать заголовком строк. Практически не бывает необходимости делать более одной такой колонки. По умолчанию как раз одна колонка и есть, попробуйте сделать две, а затем верните одну. 54
55 7. FixedRows количество фиксированных строк. По умолчанию тоже одна, и работает так же, как и FixedCols. Как правило, эта строка служит заголовком колонок. 8. GridLineWidth толщина разделительных линий. Попробуйте поставить ноль линии исчезнут. Верните единицу. 9. Options самое главное свойство компонента. Оно содержит много настроек, которые раскроются, если щёлкнуть по плюсу слева от названия свойства. Эти дополнительные свойства имеют следующие назначения: gofixedvertline прорисовка вертикальных линий у фиксированных ячеек. По умолчанию True. gofixedhorzline прорисовка горизонтальных линий у фиксированных ячеек. govertline прорисовка вертикальных линий у всех остальных (нефиксированных) ячеек. gohorzline прорисовка горизонтальных линий у нефиксированных ячеек. gorangeselect разрешение выделять несколько ячеек. Не работает, если включен элемент goedit. godrawfocusselect разрешено выделять ячейку, которая находится в фокусе ввода. gorowsizing разрешено изменять высоту строки перетаскиванием мышью. gocolsizing разрешено изменять ширину колонки перетаскиванием мышью. gorowmoving можно ли мышью перемещать строки на другое место. gocolmoving можно ли мышью перемещать колонки на другое место. goediting можно ли редактировать сетку, то есть вводить данные с клавиатуры. Игнорируется, если включён элемент gorowselect. gotabs можно ли переключаться на другие ячейки с помощью клавиши Tab. gorowselect выделяется вся строка. Если равно False, то только одна ячейка. goalwaysshoweditor если True, то редактировать ячейку можно сразу при выделении. Если False, то для редактирования нужно нажать Enter или F2. 55
56 gothumbtracking разрешена ли прорисовка данных в ячейках при прокрутке. Если нет, то данные будут обновлены после прокрутки. 10. RowCount количество строк в сетке. Остальные свойства практически ничем не отличаются от других компонентов. Итак, приступите к примеру. По умолчанию даётся слишком большая высота строк, её следует уменьшить до 16 (свойство DefaultRowHeight). Должно быть 5 строк и 5 колонок, причём фиксированными будут по одной колонке и одной строке. Далее, пользователь должен иметь возможность вводить данные в ячейки, поэтому в разделе Options в свойстве goediting установите True. Теперь создайте для формы событие OnShow и впишите следующий код: begin //заполняем значениями первую колонку: StringGrid1.Cells[0,1] := 'Иванов'; StringGrid1.Cells[0,2] := 'Петров'; StringGrid1.Cells[0,3] := 'Николаев'; StringGrid1.Cells[0,4] := 'Бонд'; //заполняем значениями первую строку: StringGrid1.Cells[1,0] := 'Год рожд.'; StringGrid1.Cells[2,0] := 'Место рожд.'; StringGrid1.Cells[3,0] := 'Прописка'; StringGrid1.Cells[4,0] := 'Семейное положение'; //меняем ширину колонок StringGrid1.ColWidths[4] := 120; StringGrid1.ColWidths[3] := 90; StringGrid1.ColWidths[2] := 90; end; Как видите, обращение к отдельным ячейкам здесь точно такое, как к двухмерному массиву. Первым индексом служит строка, вторым колонка. Нумерация индексов начинается с нуля, поэтому верхняя левая ячейка будет иметь индекс [0, 0]. Свойство ColWidths[i] устанавливает ширину колонки с индексом i. Сохраните проект, скомпилируйте его и посмотрите результат. Если сетка слишком маленькая или наоборот, слишком 56
57 большая, то измените её размер, чтобы все колонки умещались в сетке. Улучшите пример, добавив маску для ввода даты к столбцу «Год рождения». Делается это совсем просто выделяете сетку, переходите на вкладку Events и генерируете событие ongeteditmask. Это событие происходит, когда пользователь редактирует сетку. Там вписываете только одну строку: if ACol = 1 then Value := ' г.' Если посмотреть на параметры этого события, то можно увидеть, что в него передаются такие параметры, как ACol и ARow. Это индекс текущей колонки и текущей строки. Параметр Value содержит текст маски. То есть, если пользователь редактирует колонку с индексом 1, это вторая колонка, где указывается год рождения, здесь устанавливается маска ввода даты. Сохраните проект как EditGrid, скомпилируйте его и посмотрите результат. Работа с сеткой не такая уж сложная. Принцип работы такой сетки заключается в том, что вы заполняете сетку данными, как правило, заголовками строк и столбцов, а затем позволяете пользователю их редактировать. После чего введённые пользователем данные можно переписать в двухмерный массив, или в массив с записями, а затем сохранить в файл. КОНТРОЛЬНЫЕ ВОПРОСЫ 1. Что называется переменной? 2. Чем является имя переменной для компьютера? 3. Какие существуют правила по заданию имени переменной? 4. Для чего нужны типы данных? 5. Как объявить переменную в Delphi? 6. Как присвоить значение переменной? 7. Перечислите основные типы данных. 8. Перечислите целочисленные типы данных. 9. Почему при задании типа переменной важно выбирать правильный тип данных? 10. Перечислите вещественные типы данных. 57
58 11. На что указывает количество максимально значащих цифр в вещественных типах данных? 12. В каких случаях следует преобразовывать тип данных? 13. Что называется массивом? 14. Перечислите способы объявления переменной массива. 15. С помощью каких встроенных функций можно узнать верхнюю или нижнюю границу массива? 16. Как объявить переменную многомерного массива? 17. В чем разница между статическими и динамическими массивами? 18. Когда и для чего используется компонент StringGrid? 19. В чём разница между компонентами StringGrid и DrawGrid? 20. Перечислите основные свойства компонента StringGrid. 21. Для чего используют подпрограмму? 22. Какие бывают виды подпрограмм? 23. Что называется процедурой? 24. Как объявить процедуру? 25. Как осуществить вызов процедуры из программы? 26. Что называется параметром? 27. Какие виды переменных существуют? 28. Где используются локальные переменные? 29. Где используются глобальные переменные? 30. Что называется функцией? 31. Как объявить функцию? 32. Как осуществить вызов функции из программы? 33. В чём заключаются отличия функции от процедуры? ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ 1. Откройте созданный ранее проект MyCalc. 2. Добавьте на форму кнопку, измените надпись на «Умножить первое число на второе». Создайте для этой кнопки функцию умножения двух чисел по аналогии с делением. 3. Добавьте на форму кнопку, измените надпись на «Сложить числа». Создайте для этой кнопки функцию сложения двух чисел по аналогии с делением. 4. Добавьте на форму кнопку, измените надпись на «Вычесть второе число из первого». Создайте для этой кнопки функцию вычитания двух чисел по аналогии с делением. 58
59 5. Добавьте на форму кнопку, измените надпись на «Вычесть первое число из второго». Создайте для этой кнопки функцию вычитания двух чисел по аналогии с делением. 6. Сохраните проект, скомпилируйте его и проверьте результат. 7. Закройте проект MyCalc. 8. Откройте созданный ранее проект EditGrid. 9. Добавьте маску для ввода места рождения к столбцу «Место рождения», чтобы при редактировании соответствующих ячеек появлялось «г.». 10. Добавьте маску для ввода адреса прописки к столбцу «Прописка», чтобы при редактировании соответствующих ячеек появлялось «г.». 11. Добавьте маску для ввода семейного положения к столбцу «Семейное положение», чтобы при редактировании соответствующих ячеек появлялось «женат». 12. Добавьте маску для ввода семейного положения к столбцу «Семейное положение», чтобы при редактировании соответствующих ячеек появлялось «не женат». 13. Сохраните проект, скомпилируйте его и проверьте результат. 14. Закройте проект EditGrid. 59
60 ПРАКТИЧЕСКАЯ РАБОТА 3. РАБОТА СО СТРОКАМИ В DELPHI. УПРАВЛЕНИЕ ЦИКЛАМИ Цель научиться работать со строками в Delphi, а также изучить способы управления циклами. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ 1. Строковые типы данных В практике программирования чаще всего приходится работать со строками. Будь то обычный Edit или Memo, там используются строки. И даже если используется Edit, то, чтобы запросить у пользователя число, он все равно вводит его в качестве строки, и его требуется преобразовать в числовой тип, чтобы работать с ним, как с числом. Строка это фактически массив символов. Рассмотрим следующий пример: var s: String; a: array [1..7] of Char; begin s := 'Привет!'; ShowMessage(s); a[1] := 'П'; a[2] := 'р'; a[3] := 'и'; a[4] := 'в'; a[5] := 'е'; a[6] := 'т'; a[7] := '!'; ShowMessage(a); ShowMessage(s[1]); ShowMessage(a[1]); end; //Результат: вывод строки "Привет!" //Результат: вывод строки "Привет!" //Результат: вывод буквы "П" //Результат: вывод буквы "П" К сожалению, нельзя просто так взять и присвоить массиву строку это будет ошибкой, поэтому a := s не скомпилируется. Однако после того, как в массив посимвольно введён текст, это 60
61 получается уже полноценной строкой, поэтому компилятор разрешит директиву ShowMessage(a); и программа отработает без ошибок. Разумеется, неразумно использовать массив символов там, где проще использовать переменную типа String. Этот пример показывает, что строка это тот же массив символьного типа. Тип TStrings присутствует во многих компонентах. TStrings это тот же массив, только не типа Char (символ), а типа String (строка): a: array [1..10] of String; Однако, хотя TStrings по своему образу похож на такой массив, он имеет массу дополнительных возможностей, поэтому его используют везде, где встаёт речь о работе с набором строк. 2. Списки строк В Delphi существуют 2 вида списков строк: список выбора и выпадающий список. Для работы со списком выбора используют компонент ListBox, а для работы с выпадающим списком компонент ComboBox. Списки выбора хранят в себе какие-то списки и дают пользователю возможность выбирать одну из указанных строк. Часто их применяют в окнах с заданными параметрами, чтобы пользователь мог выбрать один из параметров. Чтобы получить доступ к строкам списка выбора, нужно воспользоваться свойством Items компонента ListBox. Это свойство имеет тип TStrings (строки). Выпадающие списки часто встречаются при работе с Windows и различными программами. Чтобы получить доступ к строкам выпадающего списка, нужно воспользоваться свойством Items компонента ComboBox. Это свойство также имеет тип TStrings (строки). Действуют и работают выпадающие списки также, как и списки выбора, с той только разницей, что выглядят они подругому и не позволяют выбрать несколько элементов сразу, как это можно сделать, используя компонент ListBox. 61
62 3. Управление циклами Операторы цикла позволяют организовать многократное повторение одной и той же последовательности действий. В Delphi существуют 3 вида циклов: for.. do, while.. do, repeat.. until. Оператор цикла с параметром (простой оператор цикла) применяется, когда известно количество повторений цикла. Он записывается так: for счётчик := выражение1 to выражение2 do действие; Счётчик это переменная, которая должна быть объявлена перед логическим блоком, в котором оператор цикла расположен, и её тип должен относиться к одному из перечислимых типов, обычно Integer. Выражение1 и выражение2 могут быть как константой или идентификатором, так и вызовом функции. Действие один или несколько операторов Delphi. Если это группа операторов, то они должны быть заключены в логические скобки begin/end. В начале работы оператора переменная-счётчик получает значение выражения1. Если при этом значение счётчика окажется меньше или равно значению выражения2, то выполняются операторы, входящие в действие. Это один цикл. Затем переменная-счётчик принимает значение, следующее за текущим, и начинается новый цикл, то есть сравнение счётчика и выражения2, выполнение действия, и так далее до тех пор, пока значение переменной-счётчика не превысит значение выражения2. Возможна работа оператора цикла, при котором переменнаясчётчик будет не увеличиваться, а уменьшаться. В этом случае ключевое слово to заменяется на downto: for счётчик := выражение1 downto выражение2 do действие; Соответственно, выражение1 должно быть больше или равно выражению2. Оператор цикла с предусловием (условный оператор цикла) удобно использовать в том случае, когда количество повторений заранее не известно. Его синтаксис следующий: 62
63 while условие do тело цикла; Этот цикл будет выполняться до тех пор, пока истинно условие (логическое выражение, возвращающее значение типа Boolean). При этом если это выражение сразу равно False, тело цикла не будет выполнено ни разу. Нужно очень внимательно следить за написанием условия и контролем завершения цикла, так как в результате ошибки цикл while будет повторяться бесконечное количество раз, что приведёт к «зацикливанию» и «зависанию» программы. Оператор цикла с постусловием (условный оператор повторения) сначала выполняет тело цикла, а затем уже проверяет выполнение условия: repeat тело цикла until условие; Таким образом, этот вариант цикла гарантирует, что тело цикла будет выполнен по крайней мере один раз. Он будет выполняться до тех пор, пока условие не станет истинным (True). Это единственный оператор Delphi, в котором тело цикла не требуется заключать в логические скобки begin/end. Начало и конец тела цикла определяются по ключевым словам repeat и until. В Delphi циклами можно ещё и управлять: прерывать их и принудительно переходить на новый виток цикла. Для этого служат директивы Break и Continue. Break прерывание цикла. Если внутри цикла встретится такой оператор, происходит немедленный выход из цикла. Как правило, этот оператор используют совместно с управляющей структурой if, например: if a <> b then break; Следовательно, если возникнет какое-то недопустимое для цикла условие, всегда есть возможность прервать цикл досрочно. Continue прерывание текущего шага цикла. В отличие от Break, Continue не прекращает цикл вовсе, а лишь прерывает дальнейшую обработку этого шага цикла, после чего цикл сразу начинается со следующего шага. Способ применения такой же, как у Break. 63
64 ПРАКТИЧЕСКАЯ ЧАСТЬ 1. Работа с компонентом ListBox Создайте новое приложение, установите на форму один компонент ListBox и под ним один Edit для вывода той строки, которую выбрал пользователь. Затем дважды щёлкните по свойству Items компонента ListBox. Откроется редактор строк. Наберите в нём названия операционных систем: MS-DOS Windows 3.10 Windows 95 Windows 98 Windows ME Windows 2000 Windows XP Windows Vista Unix Linux OS/2 Каждое название операционной системы должно быть на отдельной строке. Нажмите "ОК", чтобы сохранить результат. После этого форма будет выглядеть так, как показано ниже на рисунке (рис. 3.1). Рис Внешний вид формы Создайте для компонента ListBox обработчик события OnClick. Это событие срабатывает всякий раз, когда пользователь выберет одну из строк в указанном списке. 64
65 В обработчике событий напишите только одну строку: Edit1.Text := ListBox1.Items.Strings[ListBox1.ItemIndex]; Эта строка присваивает свойству Text компонента Edit1 тот текст, который хранится в выбранной строке списка ListBox1. Свойство Items имеет своё свойство TStrings, которое представляет собой массив из строк списка. Указав индекс массива, можно получить доступ к нужной строке. В примере в качестве индекса указано свойство ListBox1.ItemIndex, которое имеет тип Integer и возвращает элемент выбранной строки в массиве строк. Кстати, если поставить точку и откроется список, то там этого свойства не будет, как это можно сделать с другими свойствами, так что его необходимо писать вручную. Сохраните проект в новую папку как MyListBox, скомпилируйте и посмотрите, как он работает. Кроме того, существует возможность программно добавлять в ListBox новую строку или удалять выбранную. Добавьте на форму ещё две кнопки и напишите на них: «Добавить строку», «Удалить строку». Добавляться будет строка, написанная в компоненте Edit, а удаляться выделенная. Для кнопки «Добавить строку» впишите: ListBox1.Items.Add(Edit1.Text); а для кнопки «Удалить строку»: ListBox1.Items.Delete(ListBox1.ItemIndex); Сохраните проект, скомпилируйте и посмотрите, как он работает. Часто бывает необходимым предоставить пользователю возможность множественного выбора. Пользователь, удерживая клавишу Ctrl, щелкает по строкам и выбирает нужные. Для этого у ListBox имеется свойство MultiSelect (Множественный выбор). По умолчанию оно равно False, то есть запрещает пользователю выбрать несколько строк. Создайте ещё одно приложение, в котором будет продемонстрирована возможность множественного выбора. Установите на форму один ListBox и один Memo, ниже кнопку с надписью «Копировать». При нажатии на кнопку будет 65
66 осуществлён поиск у ListBox выделенных строк и копирование их в Memo. На рисунке (рис. 3.1.) в левой части установлен компонент ListBox, а в правой Memo. Рис Внешний вид приложения, реализующего множественный выбор там: Войдите в редактор строк компонента ListBox и напишите Строка 1 Строка 2 и так далее, пока не заполните ListBox так, чтобы его не нужно было прокручивать. Установите свойство MultiSelect у ListBox в True. Не забудьте очистить компонент Memo с помощью соответствующего свойства. Теперь для кнопки «Копировать» напишите следующий код: procedure TForm1.Button1Click(Sender: TObject); var i: Integer; begin Memo1.Clear; for i := 0 to ListBox1.Items.Count 1 do if ListBox1.Selected[i] then Memo1.Lines.Add(ListBox1.Items.Strings[i]); end; // очистка Memo 66
67 Вначале очищается компонент Memo, чтобы пользователь мог несколько раз опробовать выбор и копирование строк и строки в Memo не скапливались. Затем создаётся цикл, чтобы обрабатывать ListBox построчно. Первая строка всегда имеет нулевой индекс, а свойство Count во всех объектах TStrings возвращает количество строк в объекте. Так как индексы начинаются не с 1, а с 0, то вычитается единица. В цикл помещён условный оператор if. Если условие верно, то есть строка выделена, то производится добавление строки в Memo. За это отвечает свойство Selected (Выделено) с указанием индекса строки. Если оно равно True, то строка в данный момент выделена. Сохраните проект как MyMultiSelect, скомпилируйте и посмотрите, как он работает. 2. Работа с компонентом ComboBox Создайте новое приложение, добавьте на форму один ComboBox и один Edit (рис. 3.3.). Рис Внешний вид формы Вызовите редактор строк ComboBox (свойство Items), впишите несколько городов: Москва Санкт-Петербург Киев Минск Ташкент Душанбе Свойство ItemIndex, которое указывает индекс выделенной строки, по умолчанию установлено в 1. Это означает, что ни одна 67
68 строка не выбрана. Если установить его в 0, то в поле ввода текста появится первая строка списка. Оставьте 1, чтобы строк не было видно. Создайте обработчик события OnChange для компонента ComboBox и там напишите только одну строку: Edit1.Text := ComboBox1.Items.Strings[ComboBox1.ItemIndex]; Можно заметить, что это почти точная копия работы с ListBox. Сохраните проект в новую папку как MyComboBox, cкомпилируйте и проверьте, как он работает. 3. Использование оператора Continue Рассмотрим работу специальной команды для управления циклом Сontinue на практическом примере. Нужно разделить число 10 на число от 3 до 3 включительно и результат вывести в ListBox. Поскольку выводить нужно также целые числа, следует использовать функцию Round(), которая принимает вещественное число, округляет его до ближайшего целого и это целое возвращает. Также известно, что на ноль делить нельзя, поскольку возникнет ошибка. Для того, чтобы не допустить это деление, необходимо прервать этот шаг цикла с помощью Сontinue. Создайте новое приложение. Установите на форму ListBox, а под ним кнопку «Подсчитать» (рис. 3.4). В обработчике события нажатия на кнопку напишите следующий код: procedure TForm1.Button1Click(Sender: TObject); var i, r: Integer; begin for i := 3 to 3 do begin if i = 0 then begin ListBox1.Items.Add('На ноль делить нельзя!'); Continue; end; //if r := Round(10/i); ListBox1.Items.Add('10/' + IntToStr(i) + '=' + IntToStr(r)); end; //for end; 68
69 Рис Внешний вид формы В тот момент, когда счётчик i станет равным 0, выполнится тело условия if, то есть будет выведено сообщение «На ноль делить нельзя!», и после оператора Continue цикл сразу перейдёт на новый виток, пропустив деление. Сохраните проект в новую папку как MyContinue, cкомпилируйте и посмотрите, как он работает. КОНТРОЛЬНЫЕ ВОПРОСЫ 1. Что представляет собой строка? 2. В чём разница между типами String и TStrings? 3. Какие виды списков строк существуют в Delphi? 4. Какое свойство позволяет получить доступ к строкам списка? 5. Как осуществить множественный выбор в списке выбора? 6. Какие существуют особенности задания значений свойству ItemIndex? 7. Для чего используются операторы цикла? 8. Какие виды циклов существуют в Delphi? 9. Когда используется оператор цикла for.. do? 10. Когда используется оператор цикла while.. do? 11. Когда используется оператор цикла repeat.. until? 12. Какие способы управления циклами существуют в Delphi? 69
70 ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ 1. Откройте созданный ранее проект MyContinue. 2. Добавьте на форму ниже кнопку с надписью «Подсчитать 2». Используя код из кнопки «Подсчитать», создайте для новой кнопки событие, чтобы при нажатии на неё программа делила число 5 на число от 5 до 5 включительно. Результат вывести в ListBox. 3. Добавьте на форму ниже кнопку с надписью «Подсчитать 3». Используя код из кнопки «Подсчитать», создайте для новой кнопки событие, чтобы при нажатии на неё программа умножала число 2 на число от 10 до 0 включительно. Лишнее из кода убрать. Результат вывести в ListBox. 4. Добавьте на форму ниже кнопку с надписью «Подсчитать 4». Используя код из кнопки «Подсчитать», создайте для новой кнопки событие, чтобы при нажатии на неё программа складывала число 3 с числом от 5 до 5 включительно. Лишнее из кода убрать. Результат вывести в ListBox. 5. Добавьте на форму ниже кнопку с надписью «Подсчитать 5». Используя код из кнопки «Подсчитать», создайте для новой кнопки событие, чтобы при нажатии на неё программа вычитала число 3 из числа от 0 до 10 включительно. Лишнее из кода убрать. Результат вывести в ListBox. 6. Сохраните проект, скомпилируйте его и проверьте результат. 7. Создайте новый проект. 8. Расположите на форме компонент Memo и кнопку с надписью «Подсчитать» аналогично проекту MyContinue (см. рис. 3.4). 9. Реализуйте задачу деления числа 10 на число от 3 до 3 включительно, аналогично проекту MyContinue, но результат необходимо вывести в Memo. 10. Сохраните проект как MyContinueMemo, скомпилируйте его и проверьте результат. 11. Сравните результаты работы проектов MyContinue и MyContinueMemo. 70
71 ПРАКТИЧЕСКАЯ РАБОТА 4. НАСТРОЙКА ВНЕШНЕГО ВИДА ФОРМЫ. РАБОТА С ПАНЕЛЯМИ Цель научиться работать с такими панелями в Delphi, как Panel, GroupBox и RadioGroup, а также использовать их при настройке внешнего виды формы. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ 1. Компонент Panel Компонент Panel находится на вкладке Standard и применяется для улучшения внешнего вида формы, а также для группировки нескольких компонентов. Этот компонент по своим свойствам немного напоминает форму. Если установить несколько других компонентов на Panel, то при смещении панели будут смещаться и компоненты, установленные на ней. 2. Компоненты GroupBox и CheckBox GroupBox находится на вкладке Standard и предназначен для группировки схожих по смыслу компонентов. Этот компонент во многом похож на обычную панель. Текст из свойства Caption выходит не посреди панели, а вверху, создавая таким образом заглавие этой панели. При перемещении GroupBox будут перемещаться и все компоненты, расположенные на нём. Часто с GroupBox используется компонент CheckBox независимый переключатель. Этот компонент используется для включения/выключения каких-либо опций или для индикации состояния. На форме может быть несколько этих компонентов, и каждый можно включать и выключать независимо друг от друга. 3. Компонент RadioGroup RadioGroup находится на вкладке Standard и предназначен для группировки так называемых радиокнопок. Включённой может быть лишь одна такая радиокнопка. 71
72 ПРАКТИЧЕСКАЯ ЧАСТЬ 1. Работа с компонентом Panel Изучим компонент Panel на практике. Создайте новый проект и установите на форму один компонент Panel. По умолчанию свойству Caption панели присваивается то же имя, что и свойству Name, то есть прямо по центру панели выходит текст "Panel1". Очистите свойство Caption панели, просто удалив этот текст и ничего туда не вписывая. У компонента есть и другие полезные свойства. Свойства семейства Bevel отвечают за внешний вид компонента: 1. BevelIner и BevelOuter имеют одинаковые значения и отвечают за то, как панель будет выглядеть: выпуклой или вогнутой. Попробуйте изменить значения этих свойств. 2. BevelWidth указывает ширину оборки панели. Попробуйте изменить значение на 5. Панель выглядит экзотично, но не профессионально, лучше у этого свойства оставлять значение по умолчанию. Ещё одно очень важное свойство Align (Выравнивание). Это свойство часто используют не только у панелей, но и у многих других компонентов. Свойство Align имеет несколько значений: AlBottom указывает, что панель будет занимать весь низ формы. Когда размеры формы меняются, меняется и размер панели, но она по-прежнему занимает весь низ. AlClient указывает, что панель занимает всё пространство формы. Если установить панель, растянуть её по всему верху, а затем установить ещё одну панель и указать значение alclient, то вторая панель займёт всё оставшееся место. AlCustom указывает пользовательские настройки. При изменении размеров формы такая панель останется, как при разработке дизайна. AlLeft занимает всю левую часть формы. AlNone выравнивания нет. Работает практически как AlCustom. AlRight занимает всю правую часть формы. AlTop панель вытягивается по всей верхней части формы. 72
73 Укажите у свойства Height (Высота) панели значение 40. Свойство Top установите в 0. Тем самым панель стала прижатой к самому верху формы. Чтобы растянуть панель по всему верхнему краю, свойству Align нужно присвоить значение altop. Если пользователь станет менять размеры формы, сжимая окно или наоборот, растягивая, панель всё равно будет занимать весь верх. Устанавливая на эту панель кнопки, можно получить типичную панель инструментов. Установите на панель три кнопки, одну рядом с другой. На кнопках должен быть текст: «Сохранить», «Загрузить» и «Очистить». На оставшееся внизу место поместите компонент Memo. Дважды щёлкните по свойству Lines, чтобы вызвать редактор текста, и удалите из Memo весь текст. Свойству Align компонента Memo присвойте значение alclient, чтобы растянуть Memo по всему оставшемуся месту. Вот и всё, теперь есть панель инструментов и рабочая область редактора текстов. Сохраните проект как MyPanel, скомпилируйте его и посмотрите, как меняются размеры панели и Memo при изменении размера окна. 2. Работа с компонентами GroupBox и CheckBox Создайте новое приложение. Сделаем полезную утилиту, меняющую вид формы в зависимости от настроек. Разделив форму на 2 части, в левой части установите компонент GroupBox, присвоив его свойствам Left и Top значение 6, чтобы прижать его к верхнему левому краю окна. В свойстве Caption этого компонента напишите текст "BorderIcons". Поскольку обрамление компонента вплотную подходит к тексту заголовку, с дизайнерской точки зрения будет нелишним добавить пару пробелов перед текстом и столько же после него. BorderIcons свойство формы, программирующее её внешний вид. Это свойство имеет 4 значения, каждое из которых можно включить, либо выключить. Таким образом, нам нужно 4 классических «флажка», в которые можно будет поставить «галочку» или убрать её. Роль таких флажков выполняет 73
74 компонент CheckBox. Установите 4 компонента CheckBox прямо на панель GroupBox, один над другим (рис. 4.1). Рис Внешний вид формы Измените свойство Caption этих компонентов, написав там, соответственно, "bisystemmenu", "biminimize", "bimaximize" и "bihelp". Свойство Checked компонента CheckBox показывает включен ли компонент, другими словами, установлена ли в нём «галочка». У формы по умолчанию первые три значения включены, также сделайте и здесь: у первых трёх компонентов CheckBox установите свойство Checked в True. 3. Работа с компонентом RadioGroup Добавьте в правой части формы (см. рис. 4.1) компонент RadioGroup. В свойстве Caption установите текст "BorderStyle", не забывая про пробелы до и после текста. Устанавливать на этой панели радиокнопки значительно легче, чем кнопки CheckBox. Выделите эту панель и дважды щёлкните по её свойству Items. Откроется редактор текста, такой же, как у Memo. В этом редакторе нужно написать названия кнопок, каждую кнопку обязательно следует писать на отдельной строке. Создайте следующие кнопки: bssizeable bsdialog 74
75 bsnone bssingle bssizetoolwin bstoolwindow Как только будет нажато "ОК" и редактор будет закрыт, на панели RadioGroup появятся описанные кнопки. Ни одна из них не имеет включённого вида. Можно включить только одну такую кнопку из списка. Компонент RadioGroup имеет свойство ItemIndex, которое указывает, какая кнопка в данный момент включена. По умолчанию ItemIndex равен 1. Поскольку нумерация кнопок начинается с нуля, значение 1 означает, что ни одна кнопка не включена. Установите значение 0, включив тем самым первую кнопку (на форме одноименное свойство включено по умолчанию). Ниже установите кнопку, написав на ней «Применить». Всё, с дизайном окончено, переходим к программированию кнопки. Сохраните проект в новую папку: свойству Name формы дайте имя fmain, модулю Main, всему проекту в целом FormViewer. Создайте для кнопки «Применить» процедуру обработки нажатия кнопки. Процедура имеет следующий вид: procedure TfMain.Button1Click(Sender: TObject); begin //обрабатываем компонент BorderIcons if CheckBox1.Checked then fmain.bordericons := fmain.bordericons + [bisystemmenu] else fmain.bordericons := fmain.bordericons [bisystemmenu]; if CheckBox2.Checked then fmain.bordericons := fmain.bordericons + [biminimize] else fmain.bordericons := fmain.bordericons [biminimize]; if CheckBox3.Checked then fmain.bordericons := fmain.bordericons + [bimaximize] else fmain.bordericons := fmain.bordericons [bimaximize]; if CheckBox4.Checked then fmain.bordericons := fmain.bordericons + [bihelp] else fmain.bordericons := fmain.bordericons [bihelp]; //обрабатываем компонент BorderStyle case RadioGroup1.ItemIndex of 0: fmain.borderstyle := bssizeable; 75
76 1: fmain.borderstyle := bsdialog; 2: fmain.borderstyle := bsnone; 3: fmain.borderstyle := bssingle; 4: fmain.borderstyle := bssizetoolwin; 5: fmain.borderstyle := bstoolwindow; end; //case end; Вначале обрабатывается первый флажок CheckBox: if CheckBox1.Checked then fmain.bordericons := fmain.bordericons + [bisystemmenu] else fmain.bordericons := fmain.bordericons [bisystemmenu]; Если этот флажок включён, его свойство Checked вернёт Истину, и тогда к свойству формы BorderIcons будет добавлена константа [bisystemmenu]. Важно одно если прибавлять эту константу, то включать это значение следует в свойство BorderIcons. Пример взят из встроенного справочника Delphi. Поставьте мигающий курсор внутри слова BorderIcons и нажмите Ctrl + F1. Выйдет контекстный справочник с этой командой. Если будет предложено выбрать между CLX и VCL, следует выбрать библиотеку визуальных компонентов VCL (Visual Components Library). Далее щёлкните по ссылке "Delphi example" (Пример для Delphi). В показанном в справке примере отключается кнопка «Развернуть окно». Таким же образом обрабатываются остальные три компонента CheckBox. Далее идёт обработка панели RadioGroup. Поскольку только одна кнопка может быть включена, то свойство ItemIndex этой панели может иметь только 6 значений по количеству кнопок: от 0 до 5. В данном случае удобней всего использовать конструкцию case (оператор выбора). В зависимости от значения ItemIndex присваивается одноименное включённой кнопке значение свойству BorderStyle формы. Сохраните пример, скомпилируйте и посмотрите, как работает программа. В зависимости от выбранных значений после нажатия кнопки «Применить» меняется внешний вид формы. В некоторых случаях её размеры можно менять, раздвигая мышью, в некоторых нет. В случае, если BorderStyle равен значению 76
77 bsnone, пропадает и оборка окна, и верхняя системная строка формы. Поэкспериментируйте. Эта утилита может оказаться полезной в дальнейшем, если необходимо заранее посмотреть, какие настройки выбрать, чтобы окно программы выглядело именно так. Например, чтобы нельзя было менять его размеры, чтобы отсутствовали кнопки «Свернуть» и «Развернуть» и т.д. КОНТРОЛЬНЫЕ ВОПРОСЫ 1. Для чего в Delphi используются панели? 2. Какие виды панелей существуют в Delphi? 3. Для чего используется компонент CheckBox? 4. Перечислите основные свойства компонента Panel. 5. Перечислите основные свойства компонента GroupBox. 6. Перечислите основные свойства компонента CheckBox. 7. Перечислите основные свойства компонента RadioGroup. 8. Какое свойство формы отвечает за настройку её внешнего вида? ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ 1. Откройте проект MyPanel. 2. Посмотрите, как будет меняться расположение панели, если выбирать разные значения свойства Align. 3. Откройте проект FormViewer. 4. Объясните, почему для обработки компонента CheckBox используется условный оператор if. Каковы особенности работы этого оператора? 5. Объясните, почему для обработки компонента RadioGroup используется оператор выбора case. Каковы особенности работы этого оператора? 6. Поэкспериментируйте с настройками внешнего вида формы, выбрав различные комбинации значений компонентов CheckBox и RadioGroup. Объясните полученные результаты, используя справку интегрированной среды программирования Delphi. 77
78 ПРАКТИЧЕСКАЯ РАБОТА 5. СТАНДАРТНЫЕ ДИАЛОГОВЫЕ ОКНА WINDOWS. РАБОТА С МЕНЮ Цель познакомиться со стандартными диалоговыми окнами в Delphi, освоить основные приёмы работы с меню, а также научиться создавать модальное окно. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ 1. Стандартные диалоговые окна В Delphi имеется десять компонентов, находящихся на странице Dialogs палитры компонентов и реализующих диалоги общего назначения. Эти диалоги используются многими Windowsприложениями для выполнения таких операций, как открытие, сохранение и печать файлов, поэтому их часто называют стандартными. Например, текстовый процессор Microsoft Word использует большинство из перечисленных выше диалогов. Более того, поскольку стандартные диалоги определяются средой Windows и чаще всего используется локализованная версия этой операционной системы, то диалоги оказываются русифицированными, несмотря на то, что сама среда Delphi не локализована. Для использования стандартного диалога соответствующий ему компонент должен быть помещён на форму, а его свойствам установлены нужные значения. После этого следует связать вызов диалога с каким-либо событием. Чаще всего таким событием является выбор пункта меню или нажатие кнопки. Для вызова любого стандартного диалога используется метод Execute функция, возвращающая логическое значение. При закрытии диалога кнопкой "ОК" (или "Open", или "Save") функция Execute возвращает значение True, a при отмене диалога значение False. Проверить стандартный диалог можно уже на этапе разработки приложения. При выборе команды Test Dialog (Проверить диалог) контекстного меню или двойном щелчке на компоненте стандартного диалога он открывается и работает так же, как и при выполнении приложения. После закрытия 78
79 стандартного диалога он возвращает через свои свойства значения, выбранные или установленные в процессе диалога. Например, при открытии файла возвращаемым значением является имя открываемого файла (OpenDialog1.FileName), а при выборе цвета новый цвет (ColorDialog1.Color) Компоненты выбора открываемого и сохраняемого файлов В Delphi для выбора открываемого файла используется компонент OpenDialog, а для сохраняемого файла SaveDialog. Эти компоненты имеют идентичные свойства и отличаются только внешним видом. Компонент OpenDialog реализует диалог открытия файла. При запуске этого диалога появляется окно, в котором можно выбрать имя открываемого файла. В случае успешного закрытия диалога (нажатием кнопки "Open") в качестве результата возвращается выбранное имя файла. Компонент SaveDialog предлагает стандартный диалог сохранения файла, который отличается от диалога открытия файла только своим заголовком. Основными свойствами компонентов OpenDialog и SaveDialog являются следующие: 1. FileName типа String указывает имя и полный путь файла, выбранного в диалоге. Имя файла отображается в строке редактирования с названием «Имя файла» и является результатом диалога. 2. Title типа String задаёт заголовок окна. Если свойство Title не установлено, то по умолчанию используется заголовок "Open" для OpenDialog и заголовок "Save" для SaveDialog. 3. InitialDir типа String определяет каталог, содержимое которого отображается при вызове окна диалога. Если каталог не задан, то отображается содержимое текущего каталога. 4. DefaultExt типа String задаёт расширение, автоматически подставляемое к имени файла, если пользователем расширение имени не указано. 5. Filter типа String задаёт маски имён файлов, отображаемых в раскрывающемся списке под названием "Files of type". В окне диалога видны имена файлов, которые совпадают с указанной маской. По умолчанию значением Filter является пустая строка, что соответствует отображению имён файлов всех типов. 79
80 6. FilterIndex типа Integer указывает, какая из масок фильтра отображается в списке. По умолчанию свойство FilterIndex имеет значение 1 и используется первая маска. 7. Options типа TOpenOptions применяется для настройки параметров, управляющих внешним видом и функциональными возможностями диалога. Каждый параметр (флажок) может быть включён или выключен. Свойство Options имеет около двух десятков параметров, наиболее важными из них являются следующие: ofallowmultiselect из списка можно выбрать одновременно более одного файла; ofcreateprompt при вводе несуществующего имени файла выдается запрос на создание файла; ofnolongnames имена файлов отображаются как короткие (не более 8 символов для имени и 3 символа для расширения); ofoldstyledialog создаёт окно диалога в стиле Windows Фильтр представляет собой последовательность значений, разделенных знаком. Каждое значение состоит из описания и маски, также разделённых знаком. Описание представляет собой обычный текст, поясняющий пользователю данную маску. Маска является шаблоном отображаемых файлов и состоит из имени и расширения. Если для одного описания приводится несколько масок, то они разделяются знаком «;». Например: OpenDialog1.Filter := 'Текстовые файлы *.txt; *.doc Все файлы *.*; В данном примере фильтр формируется с двумя масками: маской для текстовых файлов и маской для всех файлов (под текстовыми понимаются файлы типов doc и txt). Так как в раскрывающемся списке диалога отображается только описание фильтра, то для удобства целесообразно включить в описание и маску, например, следующим образом: OpenDialog1.Filter := 'Текстовые файлы *.* *.* ; файлы *.txt; *.doc *.txt; *.doc Все Фильтр обычно формируется при проектировании приложения. Для этого из Инспектора объектов щелчком в области значения свойства Filter вызывается редактор фильтра (Filter Editor). Значения фильтра вводятся построчно, при этом в левой 80
81 колонке указывается описание фильтра, а в правой соответствующая маска. Стандартные диалоги выбора имени файла для открытия или сохранения файла вызываются на экран методом Execute. Эта функция в качестве результата возвращает логическое значение, позволяющее определить, как закрыт диалог. Если пользователь в процессе диалога нажал клавишу Open или Save, то диалог считается принятым, и функция Execute возвращает значение True. Если диалог был закрыт любым другим способом, то он считается отвергнутым и функция возвращает значение False. Ниже приведён пример использования стандартного диалога OpenDialog: procedure TForm1.Button1Click(Sender: TObject); begin if OpenDialog1.Execute then Memo1.Lines.LoadFromFile(OpenDialog1.FileName); end; При нажатии на кнопку Button1 появляется диалог OpenDialog1 выбора имени файла для открытия. При выборе имени текстового файла его содержимое загружается в компонент Memo1. Следует обратить особое внимание на то, что многострочный редактор Memo позволяет работать с текстами в коде ANSI. При отмене диалога OpenDialog1 открытия файла не происходит. Компоненты OpenPictureDialog и SavePictureDialog вызывают стандартные диалоги открытия и сохранения графических файлов. Эти стандартные диалоги отличаются от OpenDialog и SaveDialog видом окон и установленными значениями свойства Filter. Так, по умолчанию свойство Filter установлено для отображения графических файлов следующих форматов: jpeg (*.jpg); jpeg (*.jpeg); битовый массив (*.bmp); пиктограмма (*.ico); метафайл расширенного формата (*.emf); метафайл (*.wmf). 81
82 1.2. Компонент выбора параметров шрифта Диалог выбора названия и других параметров шрифта обеспечивает изменение свойства Font для любого визуального компонента, обладающего этим свойством, например, формы или метки. В Delphi диалог выбора параметров шрифта реализует компонент FontDialog, основными свойствами которого являются: 1. Font типа TFont определяет параметры шрифта. Управление параметрами шрифта осуществляется через его подсвойства, наиболее важными из которых являются Name, Style, Size и Color. 2. MaxFontSize типа Integer ограничивает доступный в диалоге максимальный размер шрифта. Свойство активно, если установлен параметр FdLimitSize. 3. MinFontSize типа Integer ограничивает доступный в диалоге минимальный размер шрифта. Свойство активно, если установлен параметр FdLimitSize. 4. Device типа TFontDialogDevice указывает тип устройства, для которого устанавливается шрифт, и может принимать одно из трёх значений: fdscreen вывод на экран; fdprinter вывод на принтер; fdboth вывод на экран и принтер. 5. Options типа TFontDiaiogOptions используется для настройки отдельных параметров диалога. Свойство Options включает свыше полутора десятков параметров, к числу важнейших относятся следующие: fdeffects отображение группы переключателей атрибутов (подчёркнутый и зачёркнутый) и списка цветов (включён по умолчанию); fdlimitsize активизация свойств MaxFontSize и MinFontSize, приводящая к установлению для размеров шрифта допустимого диапазона; fdtruetypeonly отображение в списке только шрифтов TrueType; fdwysiwyg отображение в списке шрифтов, одновременно доступных и для экрана, и для принтера. Так, в приведённой строке кода: 82
83 if FontDialog1.Execute then Label1.Font := FontDialog1.Font; с помощью диалога выбора шрифтов выполняется задание шрифта надписи Label Компонент выбора цвета Диалог выбора цвета обеспечивает изменение свойства Color для любого визуального компонента, обладающего этим свойством. В Delphi диалог выбора цвета реализует компонент ColorDialog, основными свойствами которого являются: 1. Color типа TColor определяет выбранный или установленный цвет. 2. Options типа TColorDialogOptions используется для настройки отдельных параметров диалога. Свойство включает в себя следующие параметры: cdfullopen отображение дополнительной панели выбора цвета; cdpreventfullopen отключение кнопки "Define Custom Colors"; cdshowhelp отображение кнопки "Help"; cdsolidcolor задание вместо выбранного цвета ближайшего сплошного цвета; cdanycolor выбор несплошных цветов. По умолчанию все параметры выключены. Например, в строке кода: if ColorDialog1.Execute then Edit1.Color := ColorDialog1.Color; с помощью диалога выбора цвета устанавливается цвет редактора Edit1. 2. Подпрограммы, реализующие диалоги Часто формы отображают различные сообщения и требуют от пользователя ввода какой-либо информации. Такие формы часто называют диалогами. Рассмотрим ряд специальных процедур и функций, предлагаемых Delphi для отображения простых диалогов общего назначения: процедуру ShowMessage, функции MessageDlg и 83
84 MessageDlgPos, которые отображают окно (панель) вывода сообщений, а также функции InputBox и InputQuery для ввода информации Процедура ShowMessage Процедура ShowMessage(const Msg: String) отображает окно сообщения с кнопкой "ОК". Заголовок содержит название исполняемого файла приложения, а строка Msg выводится как текст сообщения. Например, если в процедуре обработки нажатия на кнопку написать: procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage('Простейшее диалоговое окно'); end; то при запуске приложения и нажатии на кнопку Button1 появится следующее диалоговое окно (рис. 5.1): Рис Простейшая панель сообщения 2.2. Функции MessageDlg и MessageDlgPos Функция MessageDlg(const Msg: String; AType: TMsgDlgType; AButtons: TMsgDlgButtons; HelpCtx: Longint):Word отображает окно сообщений в центре экрана и позволяет получить ответ пользователя. Параметр Msg содержит отображаемое сообщение. Окно сообщений может иметь различный тип и наряду с сообщением содержать картинки. Тип окна сообщения определяется параметром АТуре, который может принимать следующие значения: mtwarning окно содержит чёрный восклицательный знак в желтом треугольнике и заголовок "Warning"; 84
85 mterror окно содержит белый косой крест в красном круге и заголовок "Error"; mtinfomation окно содержит синюю букву i в белом круге и заголовок "Information"; mtconfirmation окно содержит синий знак? в белом круге и заголовок "Confirmation"; mtcustom окно не содержит картинки, в заголовке выводится название исполняемого файла приложения. Параметр AButtons задаёт набор кнопок окна и может принимать любые комбинации следующих значений: mbyes кнопка с надписью "Yes"; mbno кнопка с надписью "No"; mboк кнопка с надписью "ОК"; mbcancel кнопка с надписью "Cancel"; mbhelp кнопка с надписью "Help"; mbabort кнопка с надписью "Abort"; mbretry кнопка с надписью "Retry"; mbignore кнопка с надписью "Ignore"; mball кнопка с надписью "All". Для параметра AButtons имеются две константы mbyesnocancel и mbokcancel, определяющие предопределенные наборы кнопок: mbyesnocancel = [mbyes, mbno, mbcancel]; mbokcancel = [mbok, mbcancel]. При нажатии любой из указанных кнопок, кроме кнопки "Help", диалог закрывается, а результат (свойство ModalResult) возвращается функцией MessageDlg. Параметр HelpCtx определяет контекст (тему) справки, которая появляется во время отображения диалога при нажатии пользователем клавиши F1. Обычно значение этого параметра равно нулю. Пример использования функции MessageDlg: procedure TForm1.Button1Click(Sender: TObject); var rez: TModalResult; begin if length(edtdate.text) < 8 then begin rez := MessageDlg('Неправильная дата!' #10 #13 'Исправить автоматически?', mterror, [mbok, mbno], 0); 85
86 //#10 #13 символы перехода на следующую строку if rez = mrok then edtdate.text := DateToStr(Date); if rez = mrno then edtdate.setfocus; end; //if end; При нажатии на кнопку Button1 производится простейшая проверка даты. Код даты вводится в поле редактирования edtdate, размещённое на форме. Если длина даты меньше допустимой, выдаётся предупреждение и запрос на автоматическую коррекцию даты (рис. 5.2). Рис Окно сообщений При утвердительном ответе пользователя в поле даты записывается текущая дата, при отрицательном фокус передаётся полю ввода даты. Функция MessageDlgPos(const Msg: String; AType: TMsgDlgType; AButtons: TMsgDlgButtons; HelpCtx: Longint; X, Y: Integer): Word отличается от функции MessageDlg наличием параметров X и Y, управляющих положением диалогового окна на экране Функция InputBox Функция InputBox(const ACaption, APrompt, ADefault: String): String отображает диалоговое окно, служащее для ввода строки текста. Окно выводится в центре экрана и содержит поле ввода с надписью, а также кнопки "ОК" и "Cancel". Параметр ACaption задаёт заголовок окна, а параметр APrompt содержит поясняющий текст к полю ввода. Параметр ADefault определяет строку, возвращаемую функцией при отказе пользователя от ввода информации (нажатие кнопки "Cancel" или клавиши Esc). Пример использования функции InputBox: 86
87 procedure TForm1.Button1Click(Sender: TObject); var soname: String; begin soname := InputBox('Пользователь', 'Введите фамилию', 'Иванов'); end; Приведённая процедура отображает окно запроса на ввод фамилии пользователя (рис. 5.3). По умолчанию предлагается значение «Иванов». Рис Окно запроса на ввод фамилии 2.4. Функция InputQuery Функция InputQuery(const ACaption, APrompt: String; var Value: String): Boolean отличается от функции InputBox тем, что вместо третьего параметра (строки по умолчанию) используется параметр Value, который в случае подтверждения ввода содержит введённую пользователем строку. В качестве результата функция возвращает логическое значение, позволяющее определить, каким образом завершён диалог. Если нажата кнопка "ОК", то функция возвращает значение True, если нажата кнопка "Cancel" или клавиша Esc значение False. Например, в процедуре procedure TForm1.Button1Click (Sender: TObject); var soname: String; begin soname := 'Иванов'; InputQuery('Пользователь', 'Введите фамилию', soname); end; с помощью функции InputQuery выводится окно запроса, аналогичное тому запросу на ввод фамилии, который был приведён ранее на рис
88 3. Модальное окно Часто при создании серьёзного приложения в Delphi используется так называемое модальное окно. Модальным называется дочернее окно, которое не даёт главной форме работать, пока не закончена работа этого модального окна. 4. Меню в Delphi Меню представляет собой список объединённых по функциональному признаку пунктов, каждый из которых обозначает команду или вложенное меню (подменю). Выбор пункта меню равносилен выполнению соответствующей команды или раскрытию подменю. Обычно в приложении имеется главное меню и несколько контекстных (всплывающих или локальных) меню Главное меню Главное меню используется для управления работой всего приложения, а каждый из элементов контекстного меню служит для управления отдельным интерфейсным элементом. Пункт меню представляет собой объект типа TMenuItem. Отдельный пункт меню обычно виден как текстовый заголовок, описывающий назначение пункта меню. Пункт меню может быть выделен (маркирован) для указания на включенное состояние. Класс TMenuItem используется для представления пунктов главного и контекстных меню. Основными свойствами пункта меню являются: 1. Bitmap типа TBitmap определяет изображение пиктограммы, размещаемое слева от заголовка пункта меню. По умолчанию свойство имеет значение nil и изображение отсутствует. 2. Break типа TMenuBreak задаёт, разделяется ли меню на колонки. Свойство Break может принимать одно из трёх значений: mbnone меню не разделяется (по умолчанию); mbbreak пункты меню, начиная с текущего, образуют новую колонку; mbbreakbar пункты меню, начиная с текущего, образуют новую колонку, которая отделена линией. 88
89 3. Caption типа String содержит строку текста, отображаемую как заголовок пункта меню. Если в качестве заголовка указать символ, то на месте соответствующего пункта меню отображается разделительная линия. При этом, несмотря на отображение линии, свойство Caption по-прежнему имеет значение. 4. Checked типа Boolean определяет, является ли пункт выделенным. Если свойству установлено значение True, то пункт выделен и в его заголовке появляется специальная отметка. По умолчанию свойство Checked имеет значение False и пункт меню не имеет отметки. 5. Count типа Integer задаёт количество подпунктов в данном пункте меню. Это свойство есть у каждого пункта меню. Если какой-либо пункт не содержит подпунктов, то свойство Count имеет значение Enabled типа Boolean определяет, активен ли пункт, то есть будет ли он реагировать на события от клавиатуры и мыши. Если свойству Enabled установлено значение False, то пункт меню неактивен и его заголовок обесцвечен. По умолчанию свойство Enabled имеет значение True и пункт меню активен. 7. Items типа TMenuItems является массивом подпунктов текущего пункта меню. Каждый пункт меню, имеющий подпункты (вложенное меню), перечисляет их в свойстве Items. Это свойство позволяет получить доступ к подпунктам по их позициям в массиве: Items [0], Items [1] и т.д. 8. RadioItem типа Boolean определяет вид отметки, появляющейся в заголовке пункта. Если свойству установлено значение False (по умолчанию), то в качестве отметки используется значок в виде галочки, в противном случае пункт отмечается жирной точкой. 9. Shortcut типа TSortCut определяет комбинацию клавиш для активизации пункта меню. Определить комбинации клавиш можно также с помощью свойства Caption, но свойство Shortcut предоставляет для этого более широкие возможности. Обозначение комбинаций клавиш, установленных через свойство Shortcut, появляется справа от заголовка элемента меню. Наиболее просто задать комбинацию клавиш при конструировании через Инспектор объектов, выбрав нужную комбинацию из предлагаемого списка. Кроме того, назначить комбинации клавиш можно с помощью одноименной функции Shortcut(Key: Word; Shift: TShiftState): 89
90 TShortCut. Параметр Shift определяет управляющую клавишу, удерживаемую при нажатии алфавитно-цифровой клавиши, на которую указывает параметр Key. Если в процессе выполнения программы, например, для пункта меню mnutest требуется задать комбинацию клавиш Alt + T, то это можно выполнить следующим образом: mnutest.shortcut := Shortcut(Word( T ), [ssalt]); 10. Visible типа Boolean определяет, виден ли пункт на экране. Если свойству Visible установлено значение False, то пункт меню на экране не отображается. По умолчанию свойство Visible имеет значение True и пункт виден в меню. Основным событием, связанным с пунктом меню, является событие OnClick, возникающее при выборе пункта с помощью клавиатуры или мыши. В приложении для генерации события OnClick или для имитации выбора пункта меню можно использовать метод OnClick. Вызов этой процедуры эквивалентен выбору соответствующего пункта меню пользователем. Например, в процедуре procedure TForm1.Button1Click(Sender: TObject); begin mnulockitem.click; end; нажатие кнопки Button1 приводит к тому же эффекту, что и выбор пункта меню mnulockitem. Для создания меню при разработке приложения используется конструктор меню. Меню также можно создавать или изменять динамически непосредственно в ходе выполнения приложения. Главное меню располагается в верхней части формы под её заголовком (рис. 5.4) и содержит наиболее общие команды приложения. В Delphi главное меню представлено компонентом MainMenu Всплывающее (контекстное) меню Всплывающее меню вызывается, когда пользователь щёлкает правой кнопкой мыши по объекту: форме или какому-либо другому компоненту. 90
91 Рис Внешний вид редактора главного меню Контекстное меню в Delphi представляется компонентом PopupMenu. Его основными свойствами являются: 1. AutoPopup типа Boolean определяет, появляется ли контекстное меню при щелчке правой кнопки мыши и размещении указателя на компоненте, использующем это меню. Если свойство AutoPopup имеет значение True (по умолчанию), то контекстное меню при щелчке мыши появляется автоматически. Если свойство AutoPopup имеет значение False, то появления меню не происходит. Однако в этом случае можно активизировать меню программно, используя метод Popup. Процедура Popup (X, Y: Integer), где X и Y координаты меню относительно левого верхнего угла экрана монитора, выводит на экран указанное контекстное меню, например: PopupMenu1.Popup(200, 200); 2. Alignment типа TPopupAlignment определяет место появления контекстного меню по отношению к указателю мыши. Свойство Alignment может принимать следующие значения: paleft указатель определяет левый верхний край меню (по умолчанию); pacenter указатель определяет для меню центр по горизонтали; paright указатель определяет правый верхний край меню. 91
92 Для того, чтобы контекстное меню появлялось при щелчке на компоненте, необходимо его свойству PopupMenu присвоить в качестве значения имя требуемого контекстного меню. Пример задания контекстного меню для формы: Form1.PopupMenu := PopupMenu1; ПРАКТИЧЕСКАЯ ЧАСТЬ 1. Создание главного меню Создайте новый проект. На форме расположите компонент Memo. Выделите компонент Memo и убедитесь, что в его свойстве Align установлено значение alclient, то есть Memo растянуто на всю форму. На вкладке Standard найдите компонент MainMenu (Главное меню) и установите его на любое место формы, прямо на компонент Memo. Компонент MainMenu не визуальный, то есть пользователь всё равно не будет его видеть. Для создания меню необходимо дважды щёлкнуть по компоненту MainMenu, чтобы вызвать редактор меню. Когда редактор откроется, можно увидеть, что первый пункт меню выделен синим цветом. Пусть выделение остаётся, перейдите на свойство Caption и введите текст «Файл». После нажатия клавиши Enter в меню сформируется команда «Файл», а выделение переместится направо, к следующей команде. Щёлкните мышью немного ниже команды «Файл», чтобы выделить пункт ниже. Для пункта меню «Файл» создайте подменю, прописав следующие команды (см. рис. 5.4): «Сохранить» «Загрузить» «Очистить» «Выход» Предпоследняя команда, знак (минус), формирует в меню разделительную полосу. Как только редактор меню будет закрыт, строка с главным меню сразу появится над компонентом Memo. Щёлкните один раз по слову «Файл», и откроется подменю. 92
93 Щёлкните по команде «Сохранить», и будет создана процедура обработки этой команды. Команда «Сохранить» выглядит так: Memo1.Lines.SaveToFile('MyFile.txt'); Все остальные команды введите аналогичным образом. Команда «Выход» выглядит так: Close; //выход из программы 2. Создание всплывающего (контекстного) меню Найдите на вкладке Standard компонент PopupMenu (Всплывающее меню) и также установите его поверх компонента Memo. Редактор этого меню вызывается таким же образом, как и редактор главного меню. Во всплывающем меню, однако, только одна ветка меню, где команды указываются одна под другой. Другими словами, нет возможности делать пункты меню (Файл, Правка, Вид и т.д.) и подпункты (Файл Создать, Файл Загрузить и т.д.). Создайте следующие команды: «Сохранить» «Загрузить» «Очистить» «Выход» Чтобы создать обработчик события для команды, нужно дважды щёлкнуть по ней в редакторе всплывающего меню. Сами команды точно такие же, как и у главного меню. Напишите код для всех указанных пунктов всплывающего меню. Всплывающее меню нужно привязать к форме, само по себе оно работать не будет. Для этого необходимо выделить форму, что является непростой задачей, поскольку компонент Memo растянут на всё окно и нет возможности щёлкнуть по свободному месту формы. Форму проще всего выделить в окне Дерева объектов (Object TreeView). Если в данный момент это окно закрыто, открыть его можно командой меню View Object TreeView или 93
94 горячими клавишами Shift + Alt + F11. В этом окне легко можно выделить любой компонент, в том числе и форму. Итак, выделите форму. В окне Инспектора объектов отразятся свойства формы. Найдите свойство формы PopupMenu. Оно имеет вид списка, в котором можно выбрать то или иное всплывающее меню. Поскольку такое меню в проекте только одно, его и выберите. Сохраните проект как MyMenu, скомпилируйте его и запустите на выполнение. Щелчок правой кнопкой на любом месте формы приведёт к вызову всплывающего меню. Всплывающее меню также называют контекстными. Дело в том, что многие компоненты имеют свойство PopupMenu: редактор Memo, панели и многие другие компоненты. Можно установить несколько всплывающих меню с разными командами и привязать к различным компонентам свои собственные PopupMenu. Тогда щелчок правой кнопкой над одним компонентом приведёт к вызову одного всплывающего меню, над другим другого. 3. Создание модального окна Чтобы создать модальное окно, необходимо в этом же проекте создать новую форму. Для этого необходимо выполнить команды File New Form. В свойстве Caption новой формы напишите «О программе», форму назовите fabout и сохраните проект. Модуль новой формы, соответственно, назовите About. На форму установите компонент Label, напишите в его свойстве Caption «Программа: MyNotebook v 1.0» (рис. 5.5). Ниже установите ещё один Label. Свойство AutoSize (Автоматическое изменение размера) поставьте в False, а свойство WordWrap (Перенос слов на другую строку) в True. Здесь напишите такой текст: Программа предназначена для простого редактирования текстов. Программа позволяет сохранять текст в файл и считывать его из файла. Файл создаётся там же, откуда запущена программа, и имеет имя MyFile.txt. Ниже установите ещё один Label. Напишите в нём: «Автор: такой-то» (укажите свои собственные фамилию, имя и отчество). 94
95 Будет красиво, если текст этого компонента выйдет посередине. Чтобы добиться этого, свойство AutoSize (Автоматическое изменение размера) установите в False, свойство Aligment (Выравнивание текста) в tacenter. Ниже установите кнопку, напишите на ней "ОК" и создайте обработчик кнопки для выхода из формы. Инструкция Close главной формы закрывает всю программу, а инструкция Close модального окна закрывает только это окно. Рис Внешний вид формы «О программе» Поэкспериментируйте со свойствами Font и Color компонентов, чтобы форма выглядела красивей. Модальные окна не имеют кнопок «Свернуть» и «Развернуть», поэтому в свойстве BorderStyle формы fabout выберите значение bsdialog. А в свойстве Position (Позиция формы при её открытии) выберите pomainformcenter, чтобы форма появлялась по центру главного окна. Обратите внимание на последнее свойство. Обычно для главных окон программы это свойство устанавливают в podesktopcenter, чтобы форма появлялась по центру рабочего стола. Чтобы можно было вызывать окно модально, нужно «привязать» его к главной форме. В редакторе кодов имеется две вкладки: Main и About модули главной формы и формы «О программе». Перейдите на вкладку главного окна и нажмите F12, чтобы вызвать это окно. Выполните команду File Use Unit. Откроется окно, где можно увидеть созданную модальную форму. Выделите её и нажмите "OK". 95
96 Если посмотреть код главной формы, под разделом implementation можно увидеть такую картину: implementation uses About; Delphi вставила инструкцию, при которой главная форма использует всё, что описано в коде модального окна. Можно было набрать всё это вручную, однако во избежание ошибок рекомендуется пользоваться командой File Use Unit. Тогда код точно будет введён без ошибок и именно в то место, которое нужно. В главной форме снова откройте редактор главного меню, дважды щёлкнув по компоненту MainMenu. Выделите пункт справа от «Файл» и в свойстве Caption напишите «Справка», а в пункте ниже напишите «О программе» (рис. 5.6). Рис Редактор главного меню При разработке меню не забывайте о стандартах, принятых в Windows. Никто не запрещает размещать подраздел «О программе» в пункт главного меню «Файл» или какой-нибудь другой. Однако пользователь будет искать его именно в разделе «Справка». Не заставляйте пользователя привыкать к другим стандартам, если хотите, чтобы написанные программы пользовались спросом. 96
97 Напишите код вызова модального окна. Дважды щёлкните по подразделу меню «О программе», чтобы создать процедуру обработки этого подраздела. Там введите инструкцию: fabout.showmodal; Помните, что вызывается форма модального окна fabout, поэтому нужно обращаться именно к ней. Сохраните проект, скомпилируйте программу и посмотрите результат. КОНТРОЛЬНЫЕ ВОПРОСЫ 1. Для чего в Delphi используются стандартные диалоговые окна? 2. Какие виды диалогов существуют в Delphi? 3. Какие действия необходимо выполнить для использования стандартного диалога? 4. Какой метод используется для вызова любого стандартного диалога? 5. Перечислите основные свойства компонентов выбора открываемого и сохраняемого файлов. 6. Что представляет собой фильтр компонентов OpenDialog и SaveDialog? 7. Что представляет собой маска, используемая в фильтре? 8. Чем отличаются компоненты OpenPictureDialog и SavePictureDialog от компонентов OpenDialog и SaveDialog? 9. Перечислите основные свойства компонента выбора параметров шрифта. 10. Перечислите основные свойства компонента выбора цвета. 11. Какие существуют специальные подпрограммы, предлагаемые Delphi для отображения простых диалогов общего назначения? 12. В каких случаях используют процедуру ShowMessage? 13. В каких случаях используют функцию MessageDlg? 14. В каких случаях используют функцию MessageDlgPos? 15. В каких случаях используют функцию InputBox? 16. В каких случаях используют функцию InputQuery? 17. Какое окно называется модальным? 97
98 18. Что представляет собой меню в Delphi? 19. Какие виды меню обычно используются в приложении? 20. Для чего используется главное меню? 21. Для чего используется контекстное меню? 22. Что представляет собой пункт меню? 23. Перечислите основные свойства пункта меню. 24. Каким компонентом в Delphi представлено главное меню? 25. Каким компонентом в Delphi представлено всплывающее меню? 26. Перечислите основные свойства контекстного меню. 27. Как создать обработчик события для команды всплывающего меню? 28. Как осуществляется связь модального окна с главным окном приложения? 29. Как осуществить вызов модального окна? ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ 1. Создайте новый проект. 2. Реализуйте пример, показанный в пункте 1.1 теоретической части работы. Проверьте, как работает компонент OpenDialog. 3. Реализуйте пример, показанный в пункте 1.2 теоретической части работы. Проверьте, как работает компонент FontDialog. 4. Реализуйте пример, показанный в пункте 1.3 теоретической части работы. Проверьте, как работает компонент ColorDialog. 5. Реализуйте пример, показанный в пункте 2.1 теоретической части работы. 6. Реализуйте пример, показанный в пункте 2.2 теоретической части работы. 7. Поэкспериментируйте с возможными значениями параметра АТуре для функции MessageDlg. Как будет выглядеть окно при каждом выбранном параметре? 8. Поэкспериментируйте с возможными значениями параметра AButtons для функции MessageDlg. Как будет выглядеть окно при каждом выбранном параметре? 98
99 9. Реализуйте пример, показанный в пункте 2.3 теоретической части работы. 10. Реализуйте пример, показанный в пункте 2.3 теоретической части работы, с помощью функции InputQuery. 11. Сравните результаты работы функций InputBox и InputQuery, реализующих одну и ту же задачу. 12. Сохраните проект как MyDialogs, скомпилируйте его и проверьте результаты.
100 ПРАКТИЧЕСКАЯ РАБОТА 6. СОЗДАНИЕ ПАНЕЛИ ИНСТРУМЕНТОВ. ОРГАНИЗАЦИЯ МЕХАНИЗМА ДЕЙСТВИЙ. ИСПОЛЬЗОВАНИЕ ТЕХНОЛОГИИ MDI Цель изучить способы организации панели инструментов, научится пользоваться механизмом действий, освоить создание MDI-окон. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ 1. Способы организации панели инструментов Что такое панель инструментов? Во многих программах есть панели, на которых установлены кнопки с рисунками. Иногда кнопки содержат текст, чаще содержат только изображение. При наведении указателя мыши на такую кнопку спустя некоторое время выходит подсказка о том, что это за кнопка. Как правило, кнопки панели инструментов дублируют команды главного и (или) всплывающего меню, облегчая пользователю работу с программой. В Delphi существуют два способа организации панели инструментов: 1. Организация простой панели инструментов с помощью компонентов Panel и SpeedButton, когда на панели инструментов нужно расположить лишь несколько кнопок. 2. Организация перемещаемой панели инструментов с помощью компонентов ControlBar и ToolBar, когда панель можно двигать, отрывать от формы и делать из неё отдельное окно. Особенности работы с компонентом Panel были рассмотрены в предыдущей работе. Ниже рассмотрены особенности работы с компонентами SpeedButton, ControlBar и ToolBar Кнопка SpeedButton Прежде всего, кнопка SpeedButton отличается от других кнопок тем, что не имеет фокуса ввода. При работе программы один из компонентов имеет фокус ввода, когда он выделен. Если это компонент для ввода текста (Edit, Memo), то пользователь сразу может вводить текст. Если это кнопка, то пользователь может 100
101 нажать Enter, что будет равносильно нажатию на кнопку мышью. Кроме того, клавишей Tab можно перемещать фокус ввода от одного компонента к другому, порядок выделения компонентов определяется их свойством TabOrder. А кнопка SpeedButton фокуса ввода не имеет, её нельзя выделить клавишей Tab, а если щёлкнуть по ней мышью, то фокус ввода вернётся к тому компоненту, в котором был до этого. Основными свойствами кнопки SpeedButton являются: 1. Glyph позволяет загрузить на кнопку изображение. 2. Flat позволяет задать выпуклость кнопке, если значение False. 3. Hint позволяет сформировать подсказку для кнопки. 4. ShowHint выводит подсказку на экран при наведении указателя мыши на компонент Панели ControlBar и ToolBar Компонент ControlBar работает как панель, но он позволяет панелям инструментов перемещаться внутри себя по желанию пользователя. Компонент ToolBar это панель инструментов, которая состоит из кнопок ToolButton. Однако первоначально компонент ToolButton на палитре инструментов отсутствует, поэтому кнопку можно загрузить, щёлкнув по панели инструментов правой кнопкой мыши и выбрав команду NewButton. Кнопка ToolButton по своим свойствам схожа с кнопкой SpeedButton. Её основными свойствами являются: 1. AllowAllUp синхронизирует состояние кнопки с другими кнопками группы, если значение установлено в True (в любой момент может быть нажата только одна кнопка группы). Это свойство работает только в том случае, если свойство Grouped (Группировка) у кнопки также установлено в True. 2. Caption содержит надпись на кнопке, которая будет выходить вместе с изображением, если у самой панели инструментов свойство ShowCaptions установлено в True. 3. Down отвечает за состояние кнопки: нажата она или нет. 4. Style отвечает за стиль кнопки. Сама панель инструментов ToolBar также имеет ряд свойств, которые необходимо знать: 101
102 1. AutoSize автоматически выравнивает высоту, учитывая высоту кнопок, если выбрано значение True. 2. ButtonHeight определяет высоту кнопок, создаваемых на этой панели. 3. ButtonWidth определяет ширину кнопок на этой панели. 4. Flat если значение равно True, то кнопки выглядят современно, без выпуклостей. 5. Caption показывает название панели инструментов, которое будет видно, если снять панель инструментов с места и сделать из неё отдельное окно. 6. ShowCaptions разрешает или запрещает показ текста на кнопках. 7. List если значение равно True, то изображение прижмётся к левой границе, а текст к правой. Иначе изображение будет сверху, а текст снизу кнопки. Работает, если ShowCaptions установлен в True. 8. Enabled определяет активность/неактивность кнопки. Обычная кнопка в неактивном состоянии имеет рисунок серого тона. Кнопки имеют три варианта изображения: обычное, неактивное и когда над кнопкой располагается указатель мыши. В один контейнер ImageList невозможно загрузить изображения различных состояний кнопок. Если необходимо использовать все три состояния, то придётся устанавливать три контейнера ImageList для изображений. В каждый контейнер добавляется картинка своего состояния, важно, чтобы эти картинки имели одинаковый индекс, то есть чтобы они соответствовали друг другу по очерёдности в списке. Далее устанавливается: Images указывается контейнер с обычным изображением кнопки. DisabledImages указывается контейнер с изображениями недоступных кнопок. HotImages указывается контейнер с изображениями кнопок в момент, когда указатель мыши находится над ними. Обычно такие премудрости не нужны, достаточно указывать только одно изображение кнопок. Но знать о таких возможностях компонентов нужно. 9. DragKind может иметь два варианта: dkdrag (по умолчанию) и dkdock. Если установлено dkdrag, то панель можно будет перемещать только внутри ControlBar. Для этого нужно 102
103 указателем мыши ухватиться за вертикальную чёрточку в левой части панели и перемещать её. А вот если установить в этом свойстве dkdock, то панель инструментов можно будет снять с ControlBar, установить её внутри окна программы или даже за её пределами. 2. Механизм действий. Компонент ActionList Компонент ActionList является последним на вкладке Standard коллекции компонентов. Это невизуальный компонент, пользователь его не увидит. Назначение этого компонента организация механизма действий. Использование в приложении главного и всплывающего меню, панели инструментов позволяет сделать программу вполне профессионального вида. В этом случае у пользователя появляется возможность давать команды программе тремя разными способами. Однако организовать всё это достаточно сложно. К примеру, нужно открыть новый файл. Хорошо, если это действие ограничится одной-двумя командами. А если потребуется выполнять предварительные проверки и подготовку? Копировать одинаковое действие в три разных места (для команды главного меню, всплывающего меню и для кнопки на панели инструментов) можно, но это неудобно и неэффективно. ActionList позволяет организовать механизм действий. Для этого следует привязать к какому-нибудь действию этого компонента свои команды, изображение, подсказку, а затем присвоить это действие нужному пункту главного и всплывающего меню, кнопке на панели инструментов. И какой бы способ отдать команду пользователь не выбрал, выполнится один и тот же код, указанный в действии ActionList. Более того, этот компонент имеет собственную коллекцию часто применяемых действий. Достаточно только вставить их и переименовать на русский язык, код действий писать уже не нужно. Такой способ организации программы сэкономит много времени, особенно на сложных проектах. Суть действий компонента проста. Вначале необходимо создать действие с помощью встроенного в компонент редактора действий, присвоить действию имя, изображение (если используется компонент ImageList с коллекцией изображений), подсказку, категорию. Затем следует создать для действия событие OnExecute, где описываются все команды, которые должны 103
104 выполняться при выборе данной команды. Затем данное действие необходимо связать с нужной командой главного и всплывающего меню, с кнопкой панели инструментов. Подробней работа с компонентом ActionList будет рассмотрена в практической части работы. 3. Технология MDI MDI (Multi Document Interface) это способ создания многодокументных окон, когда новое, дочернее окно, создаётся внутри главного и не может выйти за его пределы. Такой способ считается устаревшим, однако во многих случаях он является наиболее удобным способом. Например, если нажать Пуск Выполнить и в окне для ввода команды набрать "MMC" (без кавычек, латинскими символами), то появится MDI-окно Консоли управления системой, которое находится внутри главного окна и не может выйти за его пределы. ПРАКТИЧЕСКАЯ ЧАСТЬ 1. Создание панели инструментов 1.1. Организация простой панели инструментов Создайте новое приложение. Установите на форму компонент Panel. Очистите у панели свойство Caption. Свойство Align установите в altop, чтобы она заняла весь верх формы. Свойство Height установите в 24. Перейдите на вкладку Additional и найдите кнопку SpeedButton. Установите её на панель. Чтобы прижать её к самому левому краю, свойство Left установите в 0. Теперь взгляните на свойство Height, оно равно 22. У панели это свойство равно 24. Следовательно, чтобы кнопка была посередине панели, нужно свойство Top установить в 1. Тогда сверху и снизу кнопки получится по 1 пикселю. Откройте диалоговое окно свойства Glyph у этой кнопки и выберите картинку "DoorOpen" из стандартной коллекции Delphi. 104
105 Щёлкните дважды по кнопке SpeedButton и напишите там выход из программы: Close; Сохраните проект как SimplePanel, скомпилируйте его и посмотрите, как кнопка работает. Во всех современных приложениях такие кнопки обычно выглядят более плоскими. Чтобы убрать выпуклость кнопки, измените свойство Flat на True. В свойстве Hint кнопки напишите «Выход из программы». Однако этого мало, нужно ещё разрешить подсказке выходить на экран. За это отвечает свойство ShowHint. Но, если установить это свойство в True, то только эта кнопка сможет выводить подсказку, для других кнопок его тоже придётся выставлять в True. Чтобы этого не делать, можно установить свойство ShowHint в True у родительского компонента: панели или самой формы. Тогда все компоненты, расположенные на этом объекте, будут иметь значение True в этом свойстве. Установите это свойство в True у формы. Теперь и панель, и кнопка тоже имеют True в ShowHint. И любая следующая кнопка, которая будет расположена в дальнейшем на панель рядом с первой кнопкой, также будет иметь возможность выводить подсказку. Скомпилируйте проект и посмотрите, как он работает. Кнопки SpeedButton можно группировать. Установите рядом ещё две таких кнопки. Сделайте так, чтобы они были вплотную друг к другу, но чуть поодаль от первой кнопки. На первую наложите рисунок "led2on", на вторую "led2off" из коллекции изображений Delphi (рис. 6.1). Рис Фрагмент простой панели инструментов Эти кнопки расположены рядом и имеют схожие картинки. Таким образом, пользователь уже видит, что они относятся к какому-то одному свойству. Выделите обе кнопки и установите у них свойство GroupIndex в единицу происходит группировка 105
106 кнопок в одну индексную группу. У любой из них установите свойство Down (Нажата) в True. Сохраните проект, скомпилируйте его и посмотрите, как работает индексная группа. Нажмите другую кнопку, первая отожмётся. И наоборот. Именно таким образом можно, к примеру, выбрать начертание шрифта или выравнивание абзаца в MS Word. Можно устанавливать много групп, и каждой присваивать в свойстве GroupIndex свою цифру. Ноль означает, что кнопка не принадлежит ни к какой группе. У кнопки с групповым индексом 0 просто невозможно перевести свойство Down в True Организация перемещаемой панели инструментов Создайте новое приложение. Установите на форму компонент ControlBar с вкладки Additional. Свойство Align установите в Top, а свойство AutoSize в True. Тогда компонент будет автоматически растягиваться или сужаться, когда будете двигать панели инструментов внутри него. Перейдите на вкладку Win32 и найдите там компонент ToolBar. Установите панель поверх ControlBar. При этом ControlBar сразу принял нужную высоту. Если снять выделение с этого компонента, то можно увидеть, что он имеет оборку сверху. Обычно в программах такой оборки нет, поэтому снимите её. Свойство EdgeBorders компонента раскрывается и показывает, какие оборки есть. Установите ebtop в False. Теперь панель инструментов имеет профессиональный вид, только недостаёт кнопок. Желательно и тут установить свойство AutoSize в True. Добавьте кнопки, щёлкнув правой кнопкой мыши по панели инструментов и выбрав команду NewButton. Команда NewSeparator в этом меню создаёт разделители между кнопками. Для удаления или разделения кнопки её необходимо выделить и нажать Delete. Создадим такую же панель, как в прошлом примере. После первой кнопки вставьте сепаратор, потом ещё две кнопки. Эти кнопки выпуклые, а в современных приложениях они выглядят более плоскими. Выделите саму панель и свойство Flat измените на True, тогда все кнопки на панели будут выглядеть плоскими. Установите на форму компонент ImageList и загрузите те же три картинки, что и в предыдущем примере: dooropen.bmp, led2on.bmp и led2off.bmp. 106
107 Выделите панель инструментов и в свойстве Images выберите ImageList. Картинки из списка автоматически загрузились в кнопки. Если не понравилось распределение этих картинок, их можно изменить, меняя свойство ImageIndex. Первая картинка имеет индекс 0, вторая 1, и так далее. Таким образом, можно присваивать кнопкам различные картинки из существующего списка. Выделите две последние кнопки и установите свойства Grouped и AllowAllUp в True. У первой кнопки в свойстве Caption напишите «Выход», у второй «Активна», а у третьей «Неактивна». Что будет активно или нет, не имеет значения. Выделите саму панель инструментов и установите свойство ShowCaptions в True. Кнопки становятся большими, и вместе с изображением на них выходит также и текст. В некоторых приложениях можно встретить такие панели инструментов. Снова верните это свойство в False. Чтобы вернуть кнопкам первоначальный размер, выделите первую кнопку и измените её размеры. Установите свойства Height и Width равными 23. Чтобы повторить предыдущий пример, установите у первой кнопки свойство Down в True. Если скомпилировать пример, то можно увидеть, что при нажатии на кнопку она возвращается в отжатое состояние. А как быть, если, как в прошлом примере, требуется, чтобы всегда была нажата только одна кнопка из группы? Две последние кнопки уже сгруппированы и для них указано синхронизировать состояние с другими кнопками группы. Снова выделите их, а в свойстве Style выберите tbscheck. Этот стиль позволяет кнопке оставаться в нажатом состоянии. Чтобы отжать её, нужно будет снова щёлкнуть по кнопке. Если же кнопки сгруппированы, как в данном примере, то нажатие на другую кнопку отожмёт первую. Вернитесь к свойствам панели. Чтобы кнопки были квадратными, оставляйте свойства ButtonWidth равными друг другу. В свойстве Caption панели укажите «Файл». Сохраните проект как MovePanel, скомпилируйте его и посмотрите результат. Рассмотрим ещё один пример панели инструментов. Кнопки на созданной панели можно связывать с главным или всплывающим меню. То есть, если есть главное меню, и там есть 107
108 команда «Выход», то нет необходимости писать этот же код у кнопки, достаточно связать её с компонентом главного меню. В примере код обработчика события кнопки «Выход» отсутствует. Если есть, то удалите. Установите на форму главное меню. Укажите там раздел «Файл» и подразделы «Открыть», и «Выход». Свяжите меню с ImageList и пункту «Выход» присвойте картинку dooropen.bmp. Создайте обработчик события для этого пункта и пропишите там: Close; Сохраните проект, скомпилируйте его и посмотрите, как он работает. Теперь выделите кнопку «Выход» на панели инструментов и в свойстве MenuItem выберите пункт меню, с которым нужно ассоциировать кнопку. Если всё сделано правильно, то при работе программы, если пользователь выберет команду «Выход» в меню или нажмёт кнопку «Выход» на панели инструментов, выполнится один и тот же код. Точно также кнопки можно связать с всплывающим меню, за это отвечает свойство PopupMenu, в котором можно выбрать всплывающее меню. Свойство DropDownMenu связывает кнопку со вспомогательным меню, если оно есть. 2. Создание MDI-окон. Использование ActionList Создайте новое приложение. Создадим многооконный редактор текстов, который позволит обрабатывать несколько текстовых файлов одновременно и переключаться между ними. Свойство Name формы переименуйте в fmain. В свойстве Caption напишите «Текстовый редактор MyEdit». Сохраните проект в отдельную папку, модуль назовите Main, а проект в целом MyEdit. В свойстве Position выберите podesktopcenter, чтобы форма появлялась по центру Рабочего стола. Обратите внимание на свойство FormStyle. Это свойство отвечает за стиль формы, по умолчанию установлено fsnormal (Обычное). Выберите для FormStyle значение fsmdiform (Главное MDI-окно). Для дочернего MDI-окна в дальнейшем будет выбрано значение fsmdichild. На форму установите компонент ControlBar с вкладки Additional, установите у него свойство Align равное altop, а свойство AutoSize в True. 108
109 Поверх ControlBar установите компонент ToolBar с вкладки Win32. В свойстве EdgeBorders этого компонента переведите в False подсвойство ebtop, чтобы убрать верхнюю оборку. Свойство AutoSize и Flat также установите в True. Обратите внимание на свойство ShowHint этого компонента. При значении True оно позволяет выводить подсказку для кнопок, расположенных на этой панели инструментов, при наведении на них указателя мыши. Установите True в этом свойстве. Не забывайте время от времени сохранять проект. Поочерёдно установите на форму главное меню (MainMenu), всплывающее меню (PopupMenu) и ActionList с вкладки Standard. Добавьте компонент ImageList с вкладки Win32. Установите диалоги OpenDialog и SaveDialog с вкладки Dialogs. Всё это невизуальные компоненты, поэтому их можно поместить на любое свободное место формы. Начните с компонента ImageList, так как картинки понадобятся и для обоих меню, и для панели инструментов. Команды, которые будет выполнять программа, будут следующими: открыть файл, сохранить, создать, выравнивать окна каскадом, горизонтально и вертикально. Все эти команды будут сопровождаться изображениями, поэтому подберите такие же, как на рисунке ниже (рис. 6.2), или похожие изображения. Можно также создать их самостоятельно и добавить в ImageList под теми же индексами, как на рисунке. Рис Набор изображений для приложения 109
110 Приготовление главного окна программы почти закончено. Осталось создать дочернее окно, в котором будет находиться редактор текстов. Выполните команду File New Form, чтобы создать новое окно. Свойство Name новой формы переименуйте в feditor (так будет называться редактор), в свойстве Caption напишите «Дочернее окно». Свойству FormStyle выберите значение fsmdichild. Сохраните этот модуль как Editor. Попробуйте скомпилировать проект и запустить программу на выполнение. Сразу видны недостатки: дочернее окно появляется сразу же, а при попытке его закрыть оно просто сворачивается. Исправить это можно так: закройте программу (но не проект), выберите команду меню Project Options. Откроется окно (рис. 6.3), в котором можно задавать некоторые настройки проекта в целом. Рис Настройки проекта В левой части окна, в поле "Auto-create forms" выведен список форм проекта, которые создаются автоматически. Выделите 110
111 название дочерней формы feditor, нажмите на кнопку со стрелкой вправо, чтобы переместить её в поле "Available forms". Теперь эта форма не будет создаваться автоматически, придётся делать это вручную. Нужно будет исправить и закрытие этой формы, чтобы она не сворачивалась, а действительно закрывалась. Для этого выделите дочернюю форму, создайте для неё обработчик события OnClose и впишите в этот обработчик следующий код: Action := cafree; Таким образом, при попытке закрыть форму уничтожается объект, которым эта форма является. На этой форме не потребуются ни меню, ни панель инструментов. Установите на неё только компонент Memo. Очистите весь текст в свойстве Lines, а свойство Align переведите в alclient, чтобы компонент занял всю форму. Не забудьте сохранить проект. Дочерняя форма готова, переходим снова к главной форме. Вы заметили, что не пришлось выполнять команду меню File Use Unit, чтобы главная форма видела дочернюю? Когда создаётся MDI-приложение, дочерняя форма уже будет видна из главной. Однако теперь она не создаётся автоматически, поэтому придётся все же выполнить эту команду. Выделите главную форму, выполните команду меню File Use Unit и выберите модуль Editor, чтобы его можно было запустить на выполнение из главного окна. Перейдите к компоненту ActionList. Вначале свяжите его с коллекцией рисунков. Для этого выделите компонент и в свойстве Images выберите ImageList созданного приложения. Далее дважды щёлкните по ActionList, чтобы открыть редактор действий. Появится окно редактора, разделённое на две области (рис. 6.4). Область Categories содержит категории действий, а область Actions сами действия. Для создания нового действия щёлкните правой кнопкой по области Actions и выберите команду New Action (Новое действие). По умолчанию действие имеет имя Action1. Выделите это действие и переименуйте его свойство Name в FileNew. Это действие будет открывать окно редактора с новым документом 111
112 Рис Редактор действий ActionList Чтобы настроить это действие, в свойстве Caption напишите «Новый». В свойстве Category напишите "File", в этой категории будут храниться все действия, связанные с командой меню «Файл». Сразу же созданное действие исчезло из окна создана новая категория, и действие перенесено в неё (рис. 6.5). Рис Редактирование созданного действия Выберите File в области Categories, а в области Actions снова выделите созданное действие. Вернитесь к свойству Hint строке подсказки, которая выскакивает при наведении указателя мыши на объект. Подсказка выскакивает, если свойство ShowHint объекта 112
113 установлено в True, что было сделано только для панели инструментов. Значит, подсказки будут выходить только для кнопок. В свойстве Hint напишите следующее: «Создать новый файл». Следующее свойство ImageIndex, оно показывает индекс изображения для выбранного действия. После выбора свойства откроется список картинок, из которых можно выбрать нужную. Действие подготовлено, однако оно работать просто так не будет. Чтобы заставить действие работать, нужно на вкладке Events (События) создать для него обработчик события onexecute. Щёлкните дважды по этому событию и напишите в нём следующий код: feditor := TfEditor.Create(Owner); //создание нового окна Переменная feditor была объявлена в разделе глобальных переменных дочерней формы: var feditor: TfEditor; С помощью функции Create() создаётся новый объект (форма), который присваивается переменной feditor. То есть дочерняя форма создаётся вручную. Таким же образом можно создавать в дальнейшем другие дочерние окна, указывая их идентификатор. Действие создано. Теперь создайте кнопку на панели инструментов, которая будет выполнять это действие. Выделите панель инструментов ToolBar, в свойстве Images укажите созданный ранее ImageList, чтобы панель использовала те же изображения, что и ActionList. Щёлкните по панели правой кнопкой, выберите команду "New Button". Появилась новая кнопка. Выделите её и в свойстве Action выберите созданное действие FileNew. На кнопке появилось нужное изображение, и нажатие на неё приведёт к созданию нового дочернего окна. Сохраните проект, скомпилируйте его и выполните. При многократном нажатии на кнопку, создаётся несколько дочерних окон с редактором. Закрытие дочернего окна уже не сворачивает его, а уничтожает. Привяжите это же действие к пункту главного меню. Выделите MainMenu, в свойстве Images выберите тот же 113
114 ImageList. Дважды щёлкните по главному меню, чтобы открыть редактор. Создайте категорию «Файл» и команду «Новый». В свойстве Action этой команды выберите созданное действие FileNew. Сохраните проект, скомпилируйте и запустите его. В меню появилась команда Файл Новый, которая имеет ту же картинку и выполняет то же действие, что и кнопка на панели инструментов. Проделайте все те же самые действия для всплывающего меню. Но здесь нет категорий, поэтому просто создайте команду «Новый». Не забудьте предварительно привязать к PopupMenu созданный ImageList, а к главной форме в свойстве PopupMenu само всплывающее меню. Создать новое окно несложно, сложнее открыть уже имеющийся файл. Снова откройте редактор действий ActionList. В категории File создайте новое действие, переименуйте его в FileOpen. Выделите действие, в свойстве Hint напишите «Открыть файл», в свойстве Caption «Открыть», в свойстве ImageIndex выберите подходящую картинку. В событие onexecute введите код: if OpenDialog1.Execute then begin feditor := TfEditor.Create(Owner); feditor.memo1.lines.loadfromfile(opendialog1.filename); feditor.caption := OpenDialog1.FileName; end; //if Вначале проверяется, выбрал ли пользователь файл. Если да, то создаётся новое дочернее окно. По умолчанию оно активно, поэтому к нему можно обращаться по имени переменной feditor. Выбранный файл загружается в Memo, затем в заголовок окна выводится имя файла. Создайте новую кнопку, привяжите к ней это действие. Создайте в главном меню команду Файл Открыть, привяжите это же действие. В всплывающем меню создайте команду «Открыть» и привяжите к ней это же действие. Сохраните, скомпилируйте и запустите проект, попробуйте несколько раз открывать разные текстовые файлы. Для большего эффекта можно в компонентах OpenDialog и SaveDialog создать фильтры для расширения *.txt. 114
115 Для действия FileSave (Сохранить файл) код события OnExecute будет такой: //если активного окна нет выход: if ActiveMDIChild = nil then exit; //если в строке окна есть имя файла, сохраняем в него: if Length(ActiveMDIChild.Caption) > 0 then feditor.memo1.lines.savetofile(feditor.caption) //иначе спрашиваем, куда сохранять, и сохраняем: else if SaveDialog1.Execute then begin feditor.memo1.lines.savetofile(savedialog1.filename); feditor.caption := SaveDialog1.FileName; end; //else if Как видно из кода, команда ActiveMDIChild будет содержать значение nil (ничего, пусто), если нет ни одного активного окна. Все остальные операции с этим действием создайте самостоятельно и привяжите это действие к команде главного меню Файл Сохранить, к команде всплывающего меню «Сохранить», к новой кнопке на панели инструментов. Также самостоятельно создайте действие FileExit (Выход из программы) и привяжите его к кнопке и соответствующим пунктам главного и всплывающего меню. У MDI-приложений есть несколько полезных свойств и методов. Рассмотрим некоторые из них. 1. ActiveMDIChild указывает на активное дочернее окно. Например, текст в ActiveMDIChild.Caption является заголовком активной дочерней формы. 2. MDIChildCount возвращает целое число (количество открытых дочерних окон). 3. MDIChildren позволяет получить доступ к любому, даже неактивному дочернему окну. Например, MDIChildren[1] это первое дочернее окно. В ActionList можно также добавлять и стандартные действия. Откройте редактор действий. Щёлкните по нему правой кнопкой и выберите команду "New Standard Action". Откроется список, в котором можно выбрать категорию и команды действий (рис. 6.6). Выделите категорию Window (Окно) и команды, перечисленные в этой категории. Как только будет нажата кнопка "ОК", эти действия попадут в список ActionList, в категорию Window. 115
116 Рис Выбор действий категории Window Отсортируйте команды таким образом, как показано ниже на рисунке (рис. 6.7), и для перечисленных команд укажите соответствующие изображения. Рис Новая стандартная категория По порядку, в свойствах Caption этих действий укажите: «Выравнивание каскадом», «Выравнивание горизонтально», 116
117 «Выравнивание вертикально», «Упорядочить», «Свернуть всё», «Закрыть окно». Сделайте соответствующие пункты в разделе «Окно» главного меню и в свойстве Action этих пунктов присвойте соответствующие команды. Никакого кода вводить не нужно, он уже содержится в этих командах. Сохраните проект, скомпилируйте и посмотрите, как он работает. Новые команды меню будут недоступны, пока нет открытых окон. При открытых окнах ими можно будет пользоваться. Таким образом, без использования дополнительного кода программы была организована обработка окон многооконного проекта на основе стандартных действий компонента ActionList. КОНТРОЛЬНЫЕ ВОПРОСЫ 1. Для чего предназначена панель инструментов? 2. Какие существуют способы организации панели инструментов в Delphi? 3. Чем кнопка SpeedButton отличается от других кнопок Delphi? 4. Перечислите основные свойства кнопки SpeedButton. 5. Как осуществить группировку кнопок SpeedButton? 6. Каким образом можно добавить кнопку ToolButton на панель ToolBar? 7. Перечислите основные свойства кнопки ToolButton. 8. Перечислите основные свойства панели ToolBar. 9. Какие варианты изображения имеют кнопки ToolButton? 10. Как создать разделитель между кнопками ToolButton? 11. Как кнопки на созданной панели инструментов можно связывать с главным или всплывающим меню? 12. В каких случаях следует организовывать механизм действий? 13. Опишите основные шаги работы с компонентом ActionList для организации механизма действий. 14. Что необходимо сделать для создания нового действия в ActionList? 15. Что необходимо сделать, чтобы заставить работать созданное действие? 117
118 16. Что необходимо сделать, чтобы добавить в ActionList стандартное действие? 17. Перечислите основные свойства компонента ActionList. 18. Что собой представляет технология MDI? 19. В каких случаях используется технология MDI? 20. Перечислите основные свойства MDI-приложений. ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ 1. Откройте проект SimplePanel. 2. Добавьте свою группу кнопок и присвойте в свойстве GroupIndex соответствующую цифру. 3. Сохраните проект, скомпилируйте и посмотрите, как он работает. 4. Откройте проект MovePanel. 5. Выберите компонент ToolBar. 6. С помощью изменения соответствующего свойства снимите панель инструментов с панели управления ControlBar и установите её внутри окна программы. 7. С помощью изменения соответствующего свойства снимите панель инструментов с панели управления ControlBar и установите её за пределами окна программы. 8. Сохраните проект, скомпилируйте и посмотрите, как он работает. 9. Откройте проект MyEdit. 10. С помощью компонента ActionList создайте новое действие FileExit (Выход из программы), настройте его и подготовьте для работы. 11. Привяжите новое действие FileExit к кнопке и соответствующим пунктам главного и всплывающего меню. 12. С помощью компонента ActionList создайте новое стандартное действие, используя категорию Help (Помощь), настройте его и подготовьте для работы. 13. Сделайте соответствующие пункты в разделе «Помощь» в главном и всплывающем меню и в свойстве Action этих пунктов присвойте соответствующие команды. 14. Сохраните проект, скомпилируйте и посмотрите, как он работает. 118
119 ПРАКТИЧЕСКАЯ РАБОТА 7. РАБОТА С ФАЙЛАМИ Цель изучить основные стандартные подпрограммы для работы с файлами, а также способы обработки файлов в Delphi. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ 1. Виды файлов Под словом файл в Delphi понимается область внешней памяти компьютера, которая имеет имя. Существует три вида файлов: 1. Типизированные файлы принадлежат к какому-то типу данных и могут работать только с ним. Можно использовать для создания файлов записей. 2. Текстовые файлы предназначены для работы с текстом. 3. Нетипизированные файлы предназначены для побайтовой работы с файлами любого типа. Можно использовать для копирования файла, его переноса или переименования. Файлы объявляются следующим образом: var f1: File of тип_записи; //типизированные файлы f2: TextFile; //текстовый тип файлов f3: File; //нетипизированные файлы 2. Способы обработки файлов в Delphi Работа с файлами в Delphi позволяет считывать, сохранять информацию и выполнять другие действия с файлами. Существует несколько способов работы с файлами Стандартные подпрограммы для работы с файлами Ниже в таблице (табл. 7.1) содержатся практически все процедуры и функции для работы с файлами в Delphi, как создающие, переименовывающие, удаляющие файлы и каталоги, так и подпрограммы поиска файлов, обладающих заданными характеристиками. 119
120 Таблица 7.1 Стандартные подпрограммы для работы с файлами Описание стандартной подпрограммы Назначение procedure AssignFile (var F; FileName: String); Связывает файловую переменную F с именем файла FileName. procedure ChDir (Path: String); Изменяет текущий каталог. Переменная Path задаёт путь к устанавливаемой по умолчанию папке. Закрывает файл, однако связь файловой procedure CloseFile (var F); переменной с именем файла, установленная ранее процедурой AssignFile, сохраняется. function DateTimeToFileDate (DateTime: TDateTime): Integer; Преобразует значение переменной DateTime типа TDateTime Delphi в системный формат времени создания (обновления) файла. function FileDateToDateTime (FileDate: Integer): TDateTime; function DiskFree (D: Byte): LongInt; function DiskSpace (D: Byte): Integer; function EOF (var F:): Boolean; procedure Erase (var F); function FileAge (const FileName: String): Integer; function FileExists (const FileName: String): Boolean; function FileGetDate (Handle: Integer): Integer; function FileSetDate (Handle: Integer; Age: Integer): Integer; Преобразует системный формат времени создания (обновления) файла в формат TDateTime Delphi. Возвращает объём в байтах свободного пространства на указанном диске. Переменная D номер диска (0 устройство по умолчанию, текущий диск; 1 диск А; 2 диск B; 3 диск С и т.д.). Функция возвращает значение 1, если указан номер несуществующего диска. Возвращает объём в байтах полного пространства на указанном диске. Переменная D номер диска (0 устройство по умолчанию, текущий диск; 1 диск А; 2 диск B; 3 диск С и т.д.). Функция возвращает значение 1, если указан номер несуществующего диска. Тестирует конец файла и возвращает True, если файловый указатель стоит в конце файла. При записи это означает, что компонент будет добавлен в конец файла, при чтении что файл исчерпан. Уничтожает файл F. Перед выполнением процедуры файл должен быть. Для файла FileName возвращает время его последнего обновления (в системном формате) или 1, если такого файла не существует. Возвращает True, если файл FileName существует, и False в противном случае. По заданному дескриптору файла Handle возвращает время и дату его создания (в системном формате). Если дескриптор не существует, возвращает 1. Для файла с дескриптором Handle устанавливает новое время и дату его создания Age (в системном формате). В случае удачи возвращает 0 или код ошибки в противном случае. 120
121 function FindFirst(const Path: String; Attr: Integer; var F: TSearchRec): Integer; Function FindNext (var F: TSearchRec): Integer; procedure FindClose (var F: TSearchRec); Procedure Flush(var F); procedure GetDir (D: Byte; var S: String); procedure MkDir (Dir: String); procedure Rename (var F; NewName: String); procedure Reset (var F: File[; RecSize: Word]); procedure Rewrite (var F: File[; RecSize: Word]); procedure RmDir (Dir: String); function CopyFile (OldName, NewName: AnsiChar; FileExists: Boolean): Boolean; function GetLogicalDrives: Cardinal; Возвращает в переменную F типа TSearchRec первый из файлов, зарегистрированных в указанном каталоге. Path путь поиска с маской выбора файлов; Attr атрибуты выбираемых файлов. Возвращает в переменную F следующий файл в каталоге. Переменная F должна быть предварительно инициирована обращением к функции FindFirst. Освобождает память, выделенную для поиска файлов функциями FindNext и FindFirst. Очищает внутренний буфер файла, гарантируя сохранность всех последних изменений файла на диске. Возвращает текущий каталог (каталог по умолчанию). D номер устройства (0 устройство по умолчанию, текущий диск; 1 диск А; 2 диск B и т.д.); S переменная типа String, в которую возвращается путь к текущему каталогу на указанном диске. Создаёт новый каталог на текущем диске. Dir адрес нового каталога, который не может совпадать с именем уже существующего каталога. Переименовывает, т.е. задаёт новые адрес и имя файла F. NewName строковое выражение, содержащее новые адрес и имя файла. Перед выполнением процедуры необходимо закрыть файл. Открывает существующий файл. Переменная RecSize (только для нетипизированных файлов) указывает размер блока данных. Создаёт новый файл. Переменная RecSize (только для нетипизированных файлов) указывает размер блока данных. Удаляет каталог Dir. Удаляемый каталог должен быть пустым, т.е. не содержать файлов или вложенных каталогов. Копирует файл. Эта функция не встроенная функция Delphi, а одна из API функций Windows. Возможность работать с ними программа получает после добавления в секцию uses модуля Windows. Параметры функции: OldName прежние адрес и имя файла; NewName новые адрес и имя файла; FileExists переменная, определяющая реакцию на существование файла по новому адресу (при False файл будет перезаписан, при True функция завершится ошибкой). Эта функция также из состава WinAPI функций. Она позволяет получить структуру логических дисков компьютера. 121
122 2.2. Компоненты, работающие с файлами Компоненты Delphi, которые умеют работать с файлами, читают и сохраняют своё содержимое строки типа String в файл текстового формата, это компоненты ListBox, ComboBox и Memo, расположенные на первой вкладке палитры компонентов. Ниже приведён пример приложения для работы с текстовыми файлами (рис. 7.1). Рис Приложение для работы с текстовыми файлами Каждая строка компонентов ListBox и ComboBox является объектом Items[i], а Memo Lines[i], где i номер строки, который отсчитывается от нуля. Добавление строк в компоненты выполняется методами Add и Insert: begin Memo1.Lines.Add('Первая строка'); ComboBox1.Items.Add('Первая строка'); ComboBox1.Items.Add('Вторая строка'); ListBox1.Items.Add('Первая строка'); ListBox1.Items.Add('Вторая строка'); end; Метод Add добавляет новую строку в конец. Метод Insert имеет дополнительный параметр, указывающий после какой строки разместить новую строку. Доступ к строкам осуществляется так: ComboBox1.Items[0] := 'Первая строка изменилась' ; ListBox1.Items[1] := 'Вторая строка изменилась' ; У компонента ComboBox дополнительно есть свойство Text, где, как и у компонента Edit, находится вводимый текст: ComboBox1.Text := ' Вводимый текст '; 122
123 На выделенную в данный момент строку компонента ComboBox указывает свойство ItemIndex типа Integer, то есть это номер выделенной строки. Следовательно, получить саму выделенную строку компонента ComboBox можно конструкцией S := ComboBox1.Items[ComboBox1.ItemIndex]; или пользуясь оператором присоединения: With ComboBox1 do S := Items[ItemIndex]; Чтобы по нажатию клавиши Enter в компонент ComboBox занести вводимую в строку информацию и удалять нажатием Escape, необходимо: выделить на форме ComboBox, перейти в Инспекторе объектов на вкладку Events, щёлкнуть дважды по обработчику OnKeyPress и написать: begin if Key = #13 then ComboBox1.Items.Add(ComboBox1.Text); if Key = #27 then ComboBox1.Items.Delete(ComboBox1.Items.Count 1); end; Key определённая в этом обработчике переменная, содержащая код нажатой клавиши, где #13 и #27 коды клавиш Enter и Escape соответственно. Items.Count количество содержащихся в компоненте строк. Так как отсчёт строк идёт от нуля, необходимо отнять единицу. После очередного удаления количество строк меняется, поэтому Items.Count 1 всегда указывает на последнюю строку. Последовательно нажимая Escape, можно удалить все строки. Командой ComboBox1.Items.Delete(0); можно добиться того же эффекта, только удаляться будут первые строки. Чтобы стереть все строки сразу, можно использовать метод Clear. Чтобы сохранить содержимое компонента ListBox в файл, необходимо выполнить следующую команду: 123
124 ListBox1.Items.SaveToFile(' Имя_файла.txt '); Расширение можно поставить любое по желанию, не обязательно *.txt, как и вообще без него обойтись. Но расширение *.txt позволит легко открыть файл стандартным Блокнотом, что бывает очень удобно на этапе написания программы. Для загрузки данных из файла служит метод LoadFromFile: ListBox1.Items.LoadFromFile(' Имя_файла.txt '); Если в программе не используются компоненты ComboBox, ListBox или Memo, а сохранять информацию нужно, то выбирается один из компонентов и делается невидимым. Для этого в Инспекторе объектов в свойство Visible ставится значение False Классическая работа с файлами Технология работы с файлами в системе Delphi требует определённого порядка действий: 1. Прежде всего файл должен быть открыт. Система следит, чтобы другие приложения не мешали работе с файлом. При этом определяется, в каком режиме открывается файл: для изменения или только считывания информации. После открытия файла в программу возвращается его идентификатор, который будет использоваться для указания на этот файл во всех процедурах обработки. 2. Начинается работа с файлом. Это могут быть запись, считывание, поиск и другие операции. 3. Файл закрывается. Теперь он опять доступен другим приложениям без ограничений. Закрытие файла гарантирует, что все внесённые изменения будут сохранены, так как для увеличения скорости работы изменения предварительно сохраняются в специальных буферах операционной системы. Классический способ работы с файлами в Delphi связан с использованием файловых переменных. Файловая переменная вводится для указания на файл. Делается это с помощью ключевого слова File: var F: File; 124
125 Описанная таким образом файловая переменная считается нетипизированной и позволяет работать с файлами с неизвестной структурой. Данные считываются и записываются побайтно блоками, размер которых указывается при открытии файла, вплоть от 1 байта. Но чаще используются файлы, состоящие из последовательности одинаковых записей. Для описания такого файла к предыдущему описанию добавляется указание типа записи: var F: File of тип_записи; В качестве типа могут использоваться базовые типы или создаваться свои. Важно только, чтобы для типа был точно известен фиксированный размер в байтах, поэтому, например, тип String в чистом виде применяться не может, а только в виде String[N]. Данные, считанные из файла или записываемые в файл, содержатся в обычной переменной, которая должна быть того же типа, что и файловая. Поэтому сначала в программе необходимо описать нужный тип, а затем ввести две переменные этого типа: файловую и обычную. Для текстовых файлов отдельно можно указать, что тип файловой переменной в этом случае TextFile, а тип обычной String. Для открытия файла нужно указать, где он расположен. Для этого файловая переменная должна быть ассоциирована с нужным файлом, который определяется его адресом. Адрес файла может быть абсолютным, с указанием диска и каталогов ('C:\ Мои документы\мои рисунки\filename.ini'), или относительным, тогда он создаётся в папке с *.exe файлом программы. Для задания относительного адреса достаточно указать имя файла с нужным расширением. Делается это оператором AssignFile: AssignFile(SaveF, 'C:\Мои документы\мои рисунки\filename.ini'); AssignFile(SaveF, 'FileName.ini'); Теперь файл должен быть открыт. Открытие файла оператором Rewrite приведёт к воссозданию файла заново, т.е. существующий файл будет без предупреждения уничтожен, и на его месте будет создан новый пустой файл 125
126 заданного типа, готовый к записи данных. Если же файла не было, то он будет создан. Открытие файла оператором Reset откроет существующий файл к считыванию или записи данных, и его указатель будет установлен на начало файла: Rewrite(SaveF); Reset(SaveF); Каждый из этих операторов может иметь второй необязательный параметр, имеющий смысл для нетипизированных файлов и указывающий длину записи нетипизированного файла в байтах: Rewrite(SaveF, 1); Reset(SaveF, 1); Чтение файла производится оператором Read: Read(SaveF, SaveV); Запись в файл производится оператором Write: Write(SaveF, SaveV); При этом чтение и запись производится с текущей позиции указателя, затем указатель устанавливается на следующую запись. Можно проверить, существует ли нужный файл, оператором FileExists: if FileExists('FileName.ini') then Read(SaveF, SaveV); Принудительно установить указатель на нужную запись можно оператором Seek(SaveF, N), где N номер нужной записи, который, как и почти всё в программировании, отсчитывается от нуля: Seek(SaveF, 49); //установка указателя на 50-ю запись При последовательном чтении из файла рано или поздно будет достигнут конец файла, и при дальнейшем чтении произойдёт ошибка. Проверить, не достигнут ли конец файла, 126
127 можно оператором EOF (аббревиатура End Of File), который равен True, если прочитана последняя запись и указатель находится в конце файла: while (not EOF(SaveF)) do Read(SaveF, SaveV); Следует обратить особое внимание на то, что для текстовых файлов вместо Read и Write используются операторы Readln и Writeln, умеющие определять конец строки. Оператор Truncate позволяет отсечь (стереть) все записи файла, начиная от текущей позиции указателя и до конца файла: Truncate(SaveF); В конце работы с файлом его необходимо закрыть. Это делается оператором CloseFile: CloseFile(SaveF); 2.4. Диалоги выбора файлов Диалоги выбора файла позволяют указать программе, с каким файлом будет осуществлена работа. На вкладке палитры компонентов Dialogs находятся компоненты OpenDialog и SaveDialog. Как известно из практической работы 5, все диалоги, находящиеся на этой вкладке, в том числе и диалоги выбора файла, невизуальные, т.е. при переносе их на форму в работающей программе их не видно, они видны только на этапе конструирования. Компонент OpenDialog позволяет открыть в программе стандартное Windowsокно диалога открытия файла, компонент SaveDialog окно диалога сохранения. Диалоги выбора файла сами по себе ничего не делают, а только предоставляют настройки, сделанные пользователем при выборе файла. Самый важный метод диалогов Execute. Он срабатывает в момент нажатия кнопки «Открыть» или «Сохранить» в окне выбора файла. Свойство диалогов Title позволяет записать в заголовок нужную фразу. Если оставить его пустым, то в заголовке будут стандартные «Открыть» или «Сохранить». 127
128 Свойство InitialDir позволяет в момент открытия оказаться в нужной директории. Оно доступно как на этапе конструирования приложения, так и программно. При работе с диалогами выбора можно заметить, что выбирать приходится из всех файлов в нужной директории. Удобнее видеть только, например, текстовые файлы или другой тип файлов по выбору. Для этого используются фильтры (свойство Filter у диалогов выбора). В Delphi при работе с диалогами выбора часто используется так называемый оператор присоединения with. Он предназначен для любых объектов, имеющих длинный «хвост» из свойств, которые приходится записывать многократно (например, выражение OpenDialog1.FileName). Синтаксис оператора присоединения следующий: with Объект do begin end; Свойства объекта внутри операторных скобок begin/end можно записывать непосредственно. Допускается перечислять через запятую несколько объектов. В случае, когда внутри скобок находится один оператор, они необязательны Определение размера файла Определение размера файла определение того, сколько байтов в него поместится. В качестве примера можно рассмотреть приложение, в котором на форме расположены компоненты OpenDialog и кнопка Button, по нажатию на которую и будет осуществляться выбор файла. Существует 3 способа определения размера файлов. Первый способ заключается в следующем: 1. Задаётся тип данных размером 1 байт, представляющий собой запись. 2. Определяется файловая переменная этого типа. 3. Осуществляются переходы с начала файла к концу. Сколько переходов столько и байтов в файле. В этом случае обработчик события нажатия на кнопку будет выглядеть следующим образом: 128
129 procedure TForm1.ButtonClick(Sender: TObject); type TSize = Record Bait: Byte; end; var F: File of TSize; Size: Integer; begin Size := 0; with OpenDialog1 do if Execute then begin AssignFile(F, FileName); Reset(F); while (not EOF(F)) do begin Size := Size + 1; seek(f, Size); end; //while Form1.Caption := IntToStr(Size); // Выводим информацию //о размере в заголовок формы CloseFile(F); end; //if end; Программа работает и для файлов размером до нескольких мегабайт, время ожидания результата невелико, несколько секунд. Для больших файлов ожидание затягивается, и программа выглядит «зависшей». Можно изменить программу так, чтобы она считала килобайты (заменить Seek(F, Size) на Seek(F, Size*1024)). Скорость работы возрастёт в 1024 раза и уже будет приемлемой. Второй способ заключается в том, что размер файла возвращается встроенной функцией FileSize. Сам файл достаточно описать как имеющий тип Byte. В этом случае обработчик события нажатия на кнопку будет выглядеть следующим образом: procedure TForm1.ButtonClick(Sender: TObject); var F: File of Byte; Size: Integer; begin with OpenDialog1 do if Execute then begin AssignFile(F, FileName); Reset(F); Size : = FileSize(F); 129
130 Form1.Caption := IntToStr(Size); CloseFile(F); end; //if end; // Выводим информацию //о размере в заголовок формы Третий способ работы с файлами в Delphi заключается в том, что файл рассматривается как объект. Здесь также существуют встроенные способы определения размера файла: procedure TForm1.ButtonClick(Sender: TObject); var F: TFileStream; begin with OpenDialog1 do if Execute then begin F := TFileStream.Create(Filename, fmopenread); Caption := IntToStr(F.Size); F.Free; end; //if end; Эта процедура выглядит компактнее, работает быстрее, но и требует дополнительной подготовки Поиск файлов Поиск файлов в Delphi позволяет найти файлы, подходящие по выбранным критериям: имени, размеру, дате создания и т.д. Поиск файлов может производиться как по всему выбранному диску, так и ограничиться отдельным каталогом. Поиск файлов в Delphi выполняется в три этапа. 1. Сначала находится первый файл, удовлетворяющий заданной маске. Этот поиск осуществляется с помощью функции function FindFirst(const Path: String; Attr: Integer; var F: TSearchRec): Integer. Параметр Path задаёт адрес каталога (директории), в котором производится поиск. Он должен завершаться маской имён искомых файлов, например: 'C:\Temp\*.*', '*.txt'. Символ «*» означает любое количество допустимых в имени файла символов. Если необходимо указать, что символ должен быть только один, то в маске используется символ «?». Например, маска a*.txt определяет текстовые файлы с именем любой длины 130
131 начинающиеся на a, а маска a?.txt ограничивает длину имени файлов двумя символами. Если в маске описан только файл, то поиск осуществляется только в текущем каталоге. Параметр Attr содержит набор атрибутов, которые могут учитываться при отборе файлов: fareadonly = $01 файл только для чтения; fahidden = $02 скрытый файл; fasysfile = $04 системный файл; favolumeid = $08 метка диска; fadirectory = $10 каталог (директория); faarchive = $20 архивный файл; faanyfile = $3F произвольный файл. Данные атрибуты могут иметь различные имена в различных системах. Именно такое предупреждение выдаёт Delphi при попытке использовать эти имена. Поэтому лучше использовать не наименования, а значения. Однако, тогда возникают трудности, так как впоследствии невозможно догадаться, что такое $02 или $08. Поэтому нужно вводить константы с соответствующими значениями. Указанные атрибуты имеют значение отдельных битов в результирующем числе Attr. Для задания искомому файлу набора атрибутов их нужно просуммировать: Attr := fareadonly + fasysfile + fahidden; Такой набор атрибутов заставит функцию искать только скрытые системные файлы с характеристикой «только для чтения». Результат поиска содержится в переменной F, имеющей тип TSearchRec: type TSearchRec = Record Time: Integer; Size: Integer; Attr: Integer; Name: TFileName; ExcludeAttr: Integer; FindHandle: THandle; FindData: TWin32FindData; end; Наиболее важными среди полей этой записи являются: Name имя файла; 131
132 Size размер файла в байтах; Time время создания файла в формате dos. Чтобы определить, имеет ли найденный файл нужный атрибут, используется поразрядное логическое умножение: if (F.Attr and fadyrectory) = F.Attr then S := 'Это каталог'; В данном случае имеющая нужный атрибут переменная F.Attr при поразрядном логическом умножении на него не изменяется. 2. Когда первый файл, удовлетворяющий условиям поиска, найден, вызывается функция function FindNext(var F: TSearchRec): Integer. Переменная F, в которой первая функция сохранила результат поиска, передаётся функции FindNext в качестве параметра. На основании записанной в неё информации будет продолжен поиск следующего подходящего файла. 3. Процесс поиска завершается вызовом процедуры procedure FindClose(var F: TSearchRec). Эта процедура освобождает память, которая была выделена системой для проведения процесса поиска. Функции FindFirst и FindNext возвращают значение 0, если при поиске файла не возникло ошибок и очередной файл был найден. ПРАКТИЧЕСКАЯ ЧАСТЬ 1. Работа с текстовыми файлами Создайте программу, сохраняющую своё положение на экране в момент закрытия и там же открывающуюся. Для этого нужно сохранить два параметра: значения свойств формы Left и Top. Это расстояние от левого и верхнего краёв экрана соответственно. Значения выражаются в пикселах и имеют тип Integer. Необходимо перевести эти числа в строку (тип String) с помощью оператора IntToStr. Разместите на форме невидимый ListBox, щёлкните по пустому месту формы, чтобы её свойства появились в Инспекторе 132
133 объектов, и перейдите на вкладку Events. Щёлкните по обработчику OnClose и напишите: begin ListBox1.Items.Clear; ListBox1.Items.Add(IntToStr(Form1.Left)); ListBox1.Items.Add(IntToStr(Form1.Top)); ListBox1.Items.SaveToFile('MyFormPos.txt'); end; Этот обработчик сохраняет положение формы на экране. Чтобы написать обработчик, помещающий форму на прежнее место при старте программы, необходимо использовать событие OnCreate, которое происходит в момент «создания» формы операционной системой. В этот момент и нужно присваивать ей необходимые свойства. В процедуре обработки события OnCreate напишите: begin if FileExists('MyFormPos.txt') then begin ListBox1.Items.LoadFromFile('MyFormPos.txt'); Form1.Left := StrToInt(ListBox1.Items[0]); Form1.Top := StrToInt(ListBox1.Items[1]); end; //if end; В первой строке происходит проверка на существование файла, ведь если его не будет, произойдёт ошибка. Впрочем, программа после выдачи предупреждения откроется в том месте, где была на этапе проектирования, а при закрытии нужный файл будет воссоздан. В операторных скобках begin/end содержится сам код, который будет выполнен только при наличии файла MyFormPos.txt в папке с программой, так как используется относительный путь. Можно указать конкретное местоположение, например, C:\Program Files\MyProg\MyFormPos.txt. Проверку на существование файла можно выполнить и с помощью контроля исключительных ситуаций. Если файл не существует, то произойдёт исключительная ситуация. Перехватив её с помощью специального оператора, можно избежать ошибки в случае отсутствия указанного файла. 133
134 Сохраните проект как MyFormPos, скомпилируйте и посмотрите, как он работает. 2. Работа с типизированными файлами Измените программу из первой части работы, запоминающую своё положение на экране, применив классический вариант работы с файлами. Описание переменных приведено на рисунке (рис. 7.2) ниже. Рис Фрагмент кода приложения для работы с типизированными файлами Создайте обработчик события формы OnCreate со следующим содержимым: procedure TForm1.FormCreate(Sender: TObject); begin AssignFile(SaveF, 'Init.ini'); if FileExists('Init.ini') then begin 134
135 Reset(SaveF); Read(SaveF, SaveV); Form1.Left := SaveV.X; Form1.Top := SaveV.Y; Form1.Caption := SaveV.Caption ; // переменные дополнительно //сохраняют заголовок формы end; //if end; Также создайте обработчик события OnClose со следующим содержимым: procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin Rewrite(SaveF); SaveV.X := Form1.Left; SaveV.Y := Form1.Top; SaveV.Caption := Form1.Caption; Write(SaveF, SaveV); CloseFile(SaveF); end; //Нет необходимости проверять наличие файла, //создадим его заново В данном случае файл считывается и записывается туда, куда ему указали. Но необходимо также уметь выбрать нужный файл в работающей программе. Описание этого в заключительной части работы. Сохраните проект как MyFormPosIni, скомпилируйте и посмотрите, как он работает. 3. Работа с диалогами выбора файлов Создайте новый проект, который будет иметь возможность выбора файла для загрузки в редактор Memo и сохранения после редактирования. Расположите на форму компоненты OpenDialog, SaveDialog, Memo и три кнопки Button (рис. 7.3). В свойство Caption одной из кнопок запишите «Открыть. », другой «Сохранить», третьей «Сохранить как. ». В обработчике события OnClick кнопки «Открыть. » напишите: if OpenDialog1.Execute then Memo1.Lines.LoadFromFile(OpenDialog1.FileName); 135
136 В результате выбора файла свойство FileName компонента OpenDialog получает значение полного адреса выбранного файла, который и вставляется в функцию загрузки файла компонента Memo. Рис Приложение работы с файлами с помощью диалогов выбора В данном случае выражение записывается в одну строку. Если программа использует несколько раз выражение OpenDialog1.FileName, то писать руками трудоёмко, поэтому при работе с диалогами выбора рекомендуется использовать оператор присоединения. Перепишите фрагмент загрузки файла с использованием оператора присоединения: with OpenDialog1, Memo1 do if Execute then Lines.LoadFromFile(FileName); Запись получается более компактной. Так как свойства компонентов OpenDialog и SaveDialog одинаковы, сохранение текста выглядит абсолютно аналогично. Создайте обработчик нажатия кнопки «Сохранить как. » и напишите: 136
137 with SaveDialog1, Memo1 do if Execute then begin Lines.SaveToFile(FileName); OpenDialog1.FileName := FileName; // Чтобы исправленный текст //не затёр источник end; Для кнопки «Сохранить» напишите: Memo1.Lines.SaveToFile(OpenDialog1.FileName); // Сохраняем туда, //откуда считали Сохраните проект как MyDialogFile, скомпилируйте и посмотрите, как он работает. 4. Осуществление поиска файлов Создайте новый проект (рис. 7.4), который будет производить поиск всех файлов на диске C. При этом в программе папка рассматривается как файл, внутрь поиск не идёт. Рис Приложение поиска файлов на диске Процедура, производящая поиск файлов на диске C, описана ниже. В этом приложении также следует использовать классическую схему поиска файлов: сначала находится первый файл, удовлетворяющий заданным условиям, с помощью метода FindFirst, затем остальные файлы находятся методом FindNext. В процедуре обработки нажатия кнопки «Начать поиск» напишите следующий код: 137
138 procedure TForm1.Button1Click(Sender: TObject); var FindFile: TSearchRec; begin //Первый этап поиска if FindFirst('c:\*.*', faanyfile, FindFile) = 0 //Параметры функции задают //поиск на диске с: любых файлов then begin SG.Cells[1, 0] := ' Имя файла'; //Инициация заголовка таблицы SG.Cells[2, 0] := ' Размер файла'; //Инициация заголовка таблицы SG.Cells[0, 1] := ' 1.'; //Найден первый файл SG.Cells[1, 1] := FindFile.Name; //Записываем в таблицу //имя найденного файла SG.Cells[2, 1] := IntToStr(FindFile.Size); //Записываем в таблицу // размер найденного файла SG.Visible := True; //Если первый файл найден, //показываем таблицу if (FindFile.Attr and fadirectory) = FindFile.Attr //Определяем, //это каталог или нет then SG.Cells[1, 1] := SG.Cells[1, 1] + ' каталог'; //Если найденный //файл каталог, помечаем //Второй этап поиска while FindNext(FindFile) = 0 do //Ищем другие файлы на диске, //пока не произойдёт ошибка поиска begin SG.RowCount := SG.RowCount + 1; //Добавляем строку в таблицу SG.Cells[0, SG.RowCount 1] := ' ' + IntToStr(SG.RowCount 1) + '.'; //Присваиваем ей номер SG.Cells[1, SG.RowCount 1] := FindFile.Name; //Записываем имя // файла SG.Cells[2, SG.RowCount 1] := IntToStr(FindFile.Size); //Записываем //размер файла if (FindFile.Attr and fadirectory) = FindFile.Attr //Определяем каталог then SG.Cells[1, SG.RowCount 1] := SG.Cells[1, SG.RowCount 1] + ' каталог'; //Помечаем каталог end; //while //Третий, завершающий этап поиска FindClose(FindFile); end; // if end; //Освобождаем память Сохраните проект как MyFileSearch, скомпилируйте и посмотрите, как он работает. 138
139 КОНТРОЛЬНЫЕ ВОПРОСЫ 1. Что в Delphi понимается под файлом? 2. Какие виды файлов существуют в Delphi? 3. Как объявить переменные файлового типа? 4. Перечислите стандартные подпрограммы для работы с файлами. 5. Перечислите способы обработки файлов в Delphi. 6. Перечислите компоненты, работающие с файлами. 7. Какие методы используются для добавления строк в компоненты? 8. Что необходимо выполнить для сохранения содержимого компонентов в файл? 9. Какой метод используется для загрузки данных из файла? 10. Как можно сделать компонент невидимым для пользователя? 11. В чём заключается технология классической работы с файлами в Delphi? 12. Для чего вводится файловая переменная? 13. Перечислите способы открытия файла. 14. Что необходимо выполнить для открытия файла? 15. Какие виды адреса файлов существуют? 16. Как осуществляется чтение файла? 17. Как осуществляется чтение текстового файла? 18. Как осуществляется запись в файл? 19. Как осуществляется запись в текстовый файл? 20. Как проверить, существует ли файл? 21. Как можно принудительно установить указатель на нужную запись? 22. Как можно проверить, не достигнут ли конец файла? 23. Как можно стереть все записи файла, начиная от текущей позиции указателя и до конца файла? 24. Как закрыть файл по окончании работы с ним? 25. Для чего при работе с файлами используются диалоги выбора? 26. Для чего в Delphi используется оператор присоединения? 27. Какие существуют способы определения размера файла в Delphi? 28. Перечислите этапы поиска файла в Delphi. 139
140 29. Что необходимо выполнить для задания искомому файлу набора атрибутов? ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ 1. Создайте новый проект. 2. Реализуйте пример, показанный в пункте 2.2 теоретической части работы. 3. Сохраните проект как MyTextFile, скомпилируйте и посмотрите, как он работает. 4. Откройте проект MyFormPosIni. 5. Измените код приложения так, чтобы в файле сохранялись ширина и высота формы. 6. Сохраните проект, скомпилируйте и посмотрите, как он работает. 7. Создайте новый проект. 8. Реализуйте пример, показанный в пункте 2.5 теоретической части работы, тремя способами. Для каждого способа создайте свою кнопку. 9. Сохраните проект как MyFileSize, скомпилируйте и посмотрите, как он работает. 10. Сравните результаты работы каждого способа по определению размера файла. 140
141 ПРАКТИЧЕСКАЯ РАБОТА 8. ГРАФИЧЕСКИЕ ВОЗМОЖНОСТИ DELPHI Цель изучить методы работы с графическими объектами в Delphi. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ 1. Холст Delphi позволяет разрабатывать программы, которые могут выводить графику: схемы, чертежи, иллюстрации. Программа выводит графику на поверхность объекта (формы или компонента Image). Поверхности объекта соответствует свойство Сanvas. Для того, чтобы вывести на поверхность объекта графический элемент (прямую линию, окружность, прямоугольник и т.д.), необходимо применить к свойству Canvas этого объекта соответствующий метод. Например, следующая инструкция Form1.Canvas.Rectangle (10,10,100,100); вычерчивает в окне программы прямоугольник. В свою очередь, свойство Canvas это объект типа TCanvas. Методы этого типа обеспечивают вывод графических примитивов (точек, линий, окружностей, прямоугольников и т.д.), а свойства позволяют задать характеристики выводимых графических примитивов: цвет, толщину и стиль линий; цвет и вид заполнения областей; характеристики шрифта при выводе текстовой информации. Методы вывода графических примитивов рассматривают свойство Canvas как некоторый абстрактный холст, на котором они могут рисовать (Canvas переводится как «поверхность», «холст для рисования»). Холст состоит из отдельных точек пикселов. Положение пиксела характеризуется его горизонтальной (X) и вертикальной (Y) координатами. Левый верхний пиксел имеет координаты (0, 0). Координаты возрастают сверху вниз и слева направо (рис. 8.1). Значения координат правой нижней точки холста зависят от размера холста. 141
142 Рис Холст для вывода графических примитивов Размер холста можно получить, обратившись к свойствам Height и Width области иллюстрации (Image) или к свойствам формы ClientHeight и ClientWidth. 2. Карандаш и кисть Художник в своей работе использует карандаши и кисти. Методы, обеспечивающие вычерчивание на поверхности холста графических примитивов, тоже используют карандаш и кисть. Карандаш применяется для вычерчивания линий и контуров, а кисть для закрашивания областей, ограниченных контурами. Карандашу и кисти, используемым для вывода графики на холсте, соответствуют свойства Реn (Карандаш) и Brush (Кисть), которые представляют собой объекты типа TPen и TBrush соответственно. Значения свойств этих объектов определяют вид выводимых графических элементов Карандаш Карандаш (Canvas.Pen) используется для вычерчивания точек, линий, контуров геометрических фигур: прямоугольников, окружностей, эллипсов, дуг и др. Вид линии, которую оставляет карандаш на поверхности холста, определяют свойства объекта TPen, которые перечислены ниже в таблице (табл. 8.1). 142
143 Свойства объекта TPеn Свойство Назначение Color Цвет линии Width Толщина линии Style Вид линии Mode Режим отображения Таблица 8.1 Свойство Color задаёт цвет линии, вычерчиваемой карандашом. Ниже (табл. 8.2) перечислены именованные константы (тип TColor), которые можно использовать в качестве значения свойства Pen.Color. Значения свойства Pen.Color Константа Цвет clblack Черный clmaroon Каштановый clgreen Зеленый clolive Оливковый clnavy Темно-синий clpurple Розовый clteal Зелено-голубой clgray Серый clsilver Серебристый clred Красный cllime Салатный clblue Синий clfuchsia Ярко-розовый claqua Бирюзовый clwhite Белый Таблица 8.2 Свойство Width задаёт толщину линии (в пикселах). Например, инструкция Canvas. Pen.Width := 2; устанавливает толщину линии в 2 пиксела. Свойство Style определяет вид (стиль) линии, которая может быть непрерывной или прерывистой, состоящей из штрихов различной длины. Ниже (табл. 8.3) перечислены именованные константы, позволяющие задать стиль линии. Толщина пунктирной линии не может быть больше 1. Если значение свойства Pen.Width 143
144 больше единицы, то пунктирная линия будет выведена как сплошная. Константа pssolid psdash psdot psdashdot psdashdotdot psclear Таблица 8.3 Значения свойства Pen.Style Вид линии Сплошная линия. Пунктирная линия, длинные штрихи. Пунктирная линия, короткие штрихи. Пунктирная линия, чередование длинного и короткого штрихов. Пунктирная линия, чередование одного длинного и двух коротких штрихов. Линия не отображается (используется, если не надо изображать границу области, например, прямоугольника). Свойство Mode определяет, как будет формироваться цвет точек линии в зависимости от цвета точек холста, через которые эта линия прочерчивается. По умолчанию вся линия вычерчивается цветом, определяемым значением свойства Pen.Color. Однако можно задать инверсный цвет линии по отношению к цвету фона. Это гарантирует, что независимо от цвета фона все участки линии будут видны, даже в том случае, если цвет линии и цвет фона совпадают. Ниже (табл. 8.4) перечислены некоторые константы, которые можно использовать в качестве значения свойства Pen.Mode. Константа pmblack pmwhite pmcopy pmnotcopy pmnot Таблица 8.4 Значения свойства Pen.Mode Цвет линии Чёрный, не зависит от значения свойства Pen.Color. Белый, не зависит от значения свойства Pen.Color. Цвет линии определяется значением свойства Pen.Color. Цвет линии является инверсным по отношению к значению свойства Pen.Color. Цвет точки линии определяется как инверсный по отношению к цвету точки холста, в которую выводится точка линии Кисть Кисть (Canvas.Brush) используется методами, обеспечивающими вычерчивание замкнутых областей, например, геометрических фигур, для заливки (закрашивания) этих областей. 144
145 Кисть, как объект, обладает двумя свойствами, перечисленными ниже (табл. 8.5). Свойство Color Style Свойства объекта TBrush Назначение Цвет закрашивания замкнутой области. Стиль (тип) заполнения области. Таблица 8.5 Область внутри контура может быть закрашена или заштрихована. В первом случае область полностью перекрывает фон, а во втором сквозь незаштрихованные участки области фон будет виден. В качестве значения свойства Color можно использовать любую из констант типа TColor (см. табл. 8.2). Константы, позволяющие задать стиль заполнения области, приведены ниже (табл. 8.6). Константа bssolid bsclear bshorizontal bsvertical bsfdiagonal bsbdiagonal bscross bsdiagcross Значения свойства Brush.Style Тип заполнения (заливки) области Сплошная заливка. Область не закрашивается. Горизонтальная штриховка. Вертикальная штриховка. Диагональная штриховка с наклоном линий вперёд. Диагональная штриховка с наклоном линий назад. Горизонтально-вертикальная штриховка, в клетку. Диагональная штриховка, в клетку. 3. Вывод текста Таблица 8.6 Для вывода текста на поверхность графического объекта используется метод TextOut. Инструкция вызова метода TextOut в общем виде выглядит следующим образом: Объект.Canvas.TextOut(x, у, Text); где: Объект имя объекта, на поверхность которого выводится текст; х, у координаты точки графической поверхности, от которой выполняется вывод текста (рис. 8.2); 145
146 Text переменная или константа символьного типа, значение которой определяет выводимый методом текст. Рис Координаты области вывода текста Шрифт, который используется для вывода текста, определяется значением свойства Font соответствующего объекта Canvas. Свойство Font представляет собой объект типа TFont. Ниже (табл. 8.7) перечислены свойства объекта TFont, позволяющие задать характеристики шрифта, используемого методами TextOut и TextRect для вывода текста. Свойство Name Size Style Color Таблица 8.7 Свойства объекта TFont Назначение Используемый шрифт. В качестве значения следует использовать название шрифта, например, Arial. Размер шрифта в пунктах (points). Пункт единица измерения размера шрифта, используемая в полиграфии. Один пункт равен 1/72 дюйма. Стиль начертания символов. Может быть: нормальным, полужирным, курсивным, подчеркнутым, перечеркнутым. Стиль задается при помощи следующих констант: fsbold (полужирный), fsltalic (курсив), fsunderline (подчеркнутый), fsstrikeout (перечеркнутый). Свойство Style является множеством, что позволяет комбинировать необходимые стили. Например, инструкция программы, устанавливающая стиль «полужирный курсив», выглядит так: Объект.Canvas. Font := [fsbold, fs Italic]. Цвет символов. В качестве значения можно использовать константу типа Tcolor. 146
147 Область вывода текста закрашивается текущим цветом кисти. Поэтому перед выводом текста свойству Brush.Color нужно присвоить значение bsclear или задать цвет кисти, совпадающий с цветом поверхности, на которую выводится текст. Следующий фрагмент программы демонстрирует использование функции TextOut для вывода текста на поверхность формы: with Form1.Canvas do begin // установить характеристики шрифта Font.Name := 'Tahoma'; Font.Size := 20; Font.Style := [fsltalic, fsbold]; Brush.Style := bsclear; // область вывода текста не закрашивается TextOut(0, 10, 'Borland Delphi 7'); end; После вывода текста методом TextOut указатель вывода (карандаш) перемещается в правый верхний угол области вывода текста. Иногда требуется вывести какой-либо текст после сообщения, длина которого во время разработки программы неизвестна. Например, это может быть слово «руб.» после значения числа, записанного прописью. В этом случае необходимо знать координаты правой границы уже выведенного текста. Координаты правой границы текста, выведенного методом TextOut, можно получить, обратившись к свойству PenPos. Следующий фрагмент программы демонстрирует возможность вывода строки текста при помощи двух инструкций TextOut: with Form1.Canvas do begin TextOut(0, 10, 'Borland '); TextOut(PenPos.X, PenPos.Y, 'Delphi 7'); end; 4. Методы вычерчивания графических примитивов Любая картинка, чертёж, схема могут рассматриваться как совокупность графических примитивов: точек, линий, окружностей, дуг и др. Таким образом, для того, чтобы на экране появилась нужная картинка, программа должна обеспечить вычерчивание (вывод) графических примитивов, составляющих эту картинку. 147
148 Вычерчивание графических примитивов на поверхности компонента (формы или области вывода иллюстрации) осуществляется применением соответствующих методов к свойству Canvas этого компонента Линия Вычерчивание прямой линии осуществляет метод LinеТо, инструкция вызова которого в общем виде выглядит следующим образом: Компонент.Canvas.LineTo(x, у); Метод LinеТо вычерчивает прямую линию от текущей позиции карандаша в точку с координатами, указанными при вызове метода. Начальную точку линии можно задать, переместив карандаш в нужную точку графической поверхности. Сделать это можно при помощи метода MoveTo, указав в качестве параметров координаты нового положения карандаша. Вид линии (цвет, толщина и стиль) определяется значениями свойств объекта TPen графической поверхности, на которой вычерчивается линия. Довольно часто результаты расчётов удобно представить в виде графика. Для большей информативности и наглядности графики изображают на фоне координатных осей и оцифрованной сетки Ломанная линия Метод Polyline вычерчивает ломаную линию. В качестве параметра метод получает массив типа TPoint. Каждый элемент массива представляет собой запись, поля х и у которой содержат координаты точки перегиба ломаной. Метод Polyline вычерчивает ломаную линию, последовательно соединяя прямыми точки, координаты которых находятся в массиве: первую со второй, вторую с третьей, третью с четвертой и т.д. Метод Polyline можно использовать для вычерчивания замкнутых контуров. Для этого необходимо, чтобы первый и последний элементы массива содержали координаты одной и той же точки. 148
149 4.3. Окружность и эллипс Метод Ellipse вычерчивает эллипс или окружность, в зависимости от значений параметров. Инструкция вызова метода в общем виде выглядит следующим образом: Объект.Canvas.Ellipse(x1, y1, х2, у2); где: Объект имя объекта (компонента), на поверхности которого выполняется вычерчивание; x1, y1, х2, у2 координаты прямоугольника, внутри которого вычерчивается эллипс или, если прямоугольник является квадратом, окружность (рис. 8.3). Рис Вид геометрической фигуры в зависимости от параметров метода Ellipse Цвет, толщина и стиль линии эллипса определяются значениями свойства Pen, а цвет и стиль заливки области внутри эллипса значениями свойства Brush поверхности (Canvas), на которую выполняется вывод. 149
150 4.4. Дуга Вычерчивание дуги выполняет метод Arc, инструкция вызова которого в общем виде выглядит следующим образом: Объект.Canvas.Arc(x1, y1, х2, у2, х3, у3, х4, у4); где: Объект имя объекта (компонента), на поверхности которого выполняется вычерчивание; x1, y1, х2, у2 параметры, определяющие эллипс (окружность), частью которого является вычерчиваемая дуга; х3, у3 параметры, определяющие начальную точку дуги; х4, у4 параметры, определяющие конечную точку дуги. Начальная (конечная) точка это точка пересечения границы эллипса и прямой, проведённой из центра эллипса в точку с координатами х3 и у3 (х4, у4). Дуга вычерчивается против часовой стрелки от начальной точки к конечной (рис. 8.4). Рис Дуга как часть эллипса (окружности) в зависимости от параметров метода Arc 150
151 Цвет, толщина и стиль линии, которой вычерчивается дуга, определяются значениями свойства Pen поверхности (Canvas), на которую выполняется вывод Прямоугольник Прямоугольник вычерчивается методом Rectangle, инструкция вызова которого в общем виде выглядит следующим образом: Объект.Canvas.Rectangle(x1, y1, x2, y2); где: Объект имя объекта (компонента), на поверхности которого выполняется вычерчивание; x1, y1 и х2, у2 координаты левого верхнего и правого нижнего углов прямоугольника. Метод RoundRec тоже вычерчивает прямоугольник, но со скруглёнными углами. Инструкция вызова метода RoundRec выглядит так: Объект.Canvas.RoundRec(x1, y1, х2, у2, х3, у3); где: Объект имя объекта (компонента), на поверхности которого выполняется вычерчивание; x1, y1, х2, у2 параметры, определяющие положение углов прямоугольника, в который вписывается прямоугольник со скруглёнными углами; х3 и у3 размер эллипса, одна четверть которого используется для вычерчивания скруглённого угла (рис. 8.5). Вид линии контура (цвет, ширина и стиль) определяется значениями свойства Pen, а цвет и стиль заливки области внутри прямоугольника значениями свойства Brush поверхности (Canvas), на которой прямоугольник вычерчивается. Есть ещё два метода, которые вычерчивают прямоугольник, используя в качестве инструмента только кисть (Brush). Метод FillRect вычерчивает закрашенный прямоугольник, а метод FrameRect только контур. У каждого из этих методов лишь один параметр структура типа TRect. Поля структуры TRect содержат 151
152 координаты прямоугольной области, они могут быть заполнены при помощи функции Rect. Рис Прямоугольник со скруглёнными углами Ниже в качестве примера использования методов FillRect и FrameRect приведена процедура, которая на поверхности формы вычерчивает прямоугольник с красной заливкой и прямоугольник с зелёным контуром. procedure TForm1.Button1Click(Sender: TObject); var r1, r2: TRect; // координаты углов прямоугольников begin // заполнение полей структуры // зададим координаты углов прямоугольников r1 := Rect(20, 20, 60, 40); r2 := Rect(10, 10, 40, 50); with fоrm1.canvas do begin Brush.Color := clred; FillRect(r1); // закрашенный прямоугольник Brush.Color := clgreen; FrameRect(r2); end; //with end; 4.6. Многоугольник 152 // только граница прямоугольника Метод Polygon вычерчивает многоугольник. В качестве параметра метод получает массив типа TPoint. Каждый элемент массива представляет собой запись, поля (х, у) которой содержат координаты одной вершины многоугольника. Метод Polygon
153 вычерчивает многоугольник, последовательно соединяя прямыми линиями точки, координаты которых находятся в массиве: первую со второй, вторую с третьей, третью с четвертой и т.д. Затем соединяются последняя и первая точки. Цвет и стиль границы многоугольника определяются значениями свойства Реn, а цвет и стиль заливки области, ограниченной линией границы, значениями свойства Brush, причём область закрашивается с использованием текущего цвета и стиля кисти. Ниже приведена процедура, которая, используя метод Polygon, вычерчивает треугольник: procedure TForm1.Button2Click(Sender: TObject); var pol: array[1..3] of TPoint; // координаты точек треугольника begin pol[1].x := 10; polf1].y := 50; pol[2].x := 40; pol[2].y := 10; pol[3].х := 70; pol[3].у := 50; Form1.Canvas.Polygon(pol); end; 4.7. Сектор Метод Pie вычерчивает сектор эллипса или круга. Инструкция вызова метода в общем виде выглядит следующим образом: Объект. Canvas.Pie(x1, y1, x2, y2, х3, у3, х4, у4); где: Объект имя объекта (компонента), на поверхности которого выполняется вычерчивание; x1, y1, х2, у2 параметры, определяющие эллипс (окружность), частью которого является сектор; х3, у3, х4, у4 параметры, определяющие координаты конечных точек прямых, являющихся границами сектора. Начальные точки прямых совпадают с центром эллипса (окружности). Сектор вырезается против часовой стрелки (рис. 8.6) 153
154 от прямой, заданной точкой с координатами (х3, у3), к прямой, заданной точкой с координатами (х4, у4). Рис Сектор как часть эллипса (окружности) в зависимости от параметров метода Pie 4.8. Точка Поверхности, на которую программа может осуществлять вывод графики, соответствует объект Canvas. Свойство Pixels, представляющее собой двумерный массив типа TColor, содержит информацию о цвете каждой точки графической поверхности. Используя свойство Pixels, можно задать требуемый цвет для любой точки графической поверхности, т.е. «нарисовать» точку. Например, инструкция Form1.Canvas.Pixels[10, 10] := clred; окрашивает точку поверхности формы в красный цвет. Размерность массива Pixels определяется размером графической поверхности. Размер графической поверхности 154
155 формы (рабочей области, которую также называют клиентской) задаётся значениями свойств ClientWidth и ClientHeight, а размер графической поверхности компонента Image значениями свойств Width и Height. Левой верхней точке рабочей области формы соответствует элемент Pixels [0,0], а правой нижней Pixels[ClientWidth 1,ClientHeight 1]. Свойство Pixels можно использовать для построения графиков. График строится, как правило, на основе вычислений по формуле. Границы диапазона изменения аргумента функции являются исходными данными. Диапазон изменения значения функции может быть вычислен. На основании этих данных можно вычислить масштаб, позволяющий построить график таким образом, чтобы он занимал всю область формы, предназначенную для вывода графика. Например, если некоторая функция f(x) может принимать значения от нуля до 1000 и для вывода её графика используется область формы высотой в 250 пикселов, то масштаб оси Y вычисляется по формуле: m = 250/1000. Таким образом, значению f(x) = 70 будет соответствовать точка с координатой Y = 233. Значение координаты Y вычислено по формуле Y= h f(x) * m = * (250/1000), где h высота области построения графика. Обратите внимание на то, что точное значение выражения * (250/1000) равно 232,5. Но так как индексом свойства Pixels, которое используется для вывода точки на поверхность Canvas, может быть только целое значение, то число 232,5 округляется к ближайшему целому, которым является число Вывод иллюстраций Наиболее просто вывести иллюстрацию, которая находится в файле с расширением bmp, jpg или ico, можно при помощи компонента Image, значок которого находится на вкладке Additional палитры компонентов. Иллюстрацию, которая будет выведена в поле компонента Image, можно задать как во время разработки формы приложения, так и во время работы программы. Ниже (табл. 8.8) перечислены основные свойства компонента Image. 155
156 Свойство Picture Width, Height AutoSize Strech Visible Таблица 8.8 Свойства объекта TImage Назначение Иллюстрация, которая отображается в поле компонента. Размер компонента. Если размер компонента меньше размера иллюстрации и значение свойств AutoSize и Strech равно False, то отображается часть иллюстрации. Признак автоматического изменения размера компонента в соответствии с реальным размером иллюстрации. Признак автоматического масштабирования иллюстрации в соответствии с реальным размером компонента. Чтобы было выполнено масштабирование, значение свойства AutoSize должно быть False. Отвечает за то, отображается ли компонент и соответственно иллюстрация на поверхности формы. Во время разработки формы иллюстрация задаётся установкой значения свойства Picture путём выбора файла иллюстрации в стандартном диалоговом окне, которое появляется в результате щелчка на командной кнопке Load окна Picture Editor (рис. 8.7). Чтобы запустить Image Editor, нужно в окне Object Inspector выбрать свойство Picture и щёлкнуть на кнопке с тремя точками. Рис Окно Image Editor Если размер иллюстрации больше размера компонента, то свойству Strech нужно присвоить значение True и установить значения свойств Width и Height пропорционально реальным размерам иллюстрации. 156
157 Чтобы вывести иллюстрацию в поле компонента Image во время работы программы, нужно применить метод LoadFromFile к свойству Picture, указав в качестве параметра имя файла иллюстрации. Например, инструкция Form1.Image1.Picture.LoadFromFile('e:\temp\bart.bmp'); загружает иллюстрацию из файла bart.bmp и выводит её в поле вывода иллюстрации (Image1). Метод LoadFromFile позволяет отображать иллюстрации различных графических форматов: bmp, wmf, jpeg (файлы с расширением jpg). 6. Битовые образы При работе с графикой удобно использовать объекты типа TBitMap (битовый образ). Битовый образ представляет собой находящуюся в памяти компьютера, и, следовательно, невидимую графическую поверхность, на которой программа может сформировать изображение. Содержимое битового образа (картинки) легко и, что особенно важно, быстро может быть выведено на поверхность формы или области вывода иллюстрации (Image). Поэтому в программах битовые образы обычно используются для хранения небольших изображений, например, картинок командных кнопок. Загрузить в битовый образ нужную картинку можно при помощи метода LoadFromFlie, указав в качестве параметра имя bmp-файла, в котором находится нужная иллюстрация. Например, если в программе объявлена переменная pic типа TBitMap, то после выполнения инструкции pic.loadfromfiie('е:\images\aplane.bmp'); битовый образ pic будет содержать изображение самолёта. Вывести содержимое битового образа (картинку) на поверхность формы или области вывода иллюстрации можно путём применения метода Draw к соответствующему свойству поверхности (Canvas). Например, инструкция Image1.Canvas.Draw(x, у, bm); 157
158 выводит картинку битового образа bm на поверхность компонента Image1 (параметры х и у определяют положение левого верхнего угла картинки на поверхности компонента). Если перед применением метода Draw свойству Transparent объекта TBitMap присвоить значение True, то фрагменты рисунка, окрашенные цветом, совпадающим с цветом левого нижнего угла картинки, не будут выведены через них будет проглядывать фон. Если в качестве «прозрачного» нужно использовать цвет, отличный от цвета левой нижней точки рисунка, то свойству TransparentColor следует присвоить значение символьной константы, обозначающей необходимый цвет. 7. Мультипликация Под мультипликацией обычно понимается движущийся и меняющийся рисунок. В простейшем случае рисунок может только двигаться или только меняться. Как было описано выше, рисунок может быть сформирован из графических примитивов (линий, окружностей, дуг, многоугольников и т.д.). Обеспечить перемещение рисунка довольно просто: надо сначала вывести рисунок на экран, затем через некоторое время стереть его и снова вывести этот же рисунок, но уже на некотором расстоянии от его первоначального положения. Подбором времени между выводом и удалением рисунка, а также расстояния между старым и новым положением рисунка (шага перемещения), можно добиться того, что у наблюдателя будет складываться впечатление, что рисунок равномерно движется по экрану. Для обеспечения периодического вызова какой-либо процедуры создаваемой программы часто используется невизуальный компонент Timer (Таймер), который находится на вкладке System палитры компонентов. Свойства компонента Timer, перечислены ниже (табл. 8.9). Свойство Name Interval Enabled Таблица 8.9 Свойства объекта TTimer Назначение Имя компонента. Используется для доступа к компоненту. Период генерации события OnTimer. Задается в миллисекундах. Разрешение работы. Разрешает (значение True) или запрещает (значение False) генерацию события OnTimer. 158
159 Компонент Timer генерирует событие OnTimer. Период возникновения события OnTimer измеряется в миллисекундах и определяется значением свойства Interval. Следует обратить внимание на свойство Enabled. Оно дает возможность программе «запустить» или «остановить» таймер. Если значение свойства Enabled равно False, то событие OnTimer не возникает. ПРАКТИЧЕСКАЯ ЧАСТЬ 1. Работа со стилями заполнения областей В качестве примера создайте программу «Стили заполнения областей», которая в окно (рис. 8.8) выводит восемь прямоугольников, закрашенных чёрным цветом с использованием разных стилей. Рис Окно программы «Стили заполнения областей» Создайте новый проект. Ниже представлен листинг программы, который нужно написать в редакторе кода. Сохраните проект как MyBrushStile, а модуль программы как MyBrushStile_. unit MyBrushStyle_; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls; type TForm1 = class(tform) 159
160 procedure FormPaint(Sender: TObject); private < Private declarations>public < Public declarations ) end; var Form1: TForm1; implementation // перерисовка формы procedure TForm1.FormPaint(Sender: TObject); const bsname: array[1..8] of String = ('bssolid', 'bsclear', 'bshorizontal', 'bsvertical', 'bsfdiagonal', 'bsbdiagonal', 'bscross', 'bsdiagcross'); var x, y: Integer; // координаты левого верхнего угла прямоугольника w, h: Integer; // ширина и высота прямоугольника bs: TBrushStyle; // стиль заполнения области k: Integer; // номер стиля заполнения i, j: Integer; begin w := 40; h := 40; // размер области(прямоугольника) у := 20; for I := l to 2 do begin х := 10; for j := 1 to 4 do begin k := j + (i 1) * 4; // номер стиля заполнения case k of 1: bs = bssolid; 2: bs = bsclear; 3: bs = bshorizontal; 4: bs = bsvertical; 5: bs = bsfdiagonal; 6: bs = bsbdiagonal; 7: bs = bscross; 8: bs = bsdiagcross; end; //case // вывод прямоугольника Canvas.Brush.Color := clgreen; // цвет закрашивания зелёный Canvas.Brush.Style := bs; // стиль закрашивания 160
161 Canvas. Rectangle (x, y, x + w, y + h); // вывод названия стиля Canvas.Brush.Style := bsclear; Canvas.TextOut(x, y 15, bsname[k]); // вывод названия стиля x := x + w + 30; end; у := y + h + 30; end; //for j end; //for i end. Сохраните проект, скомпилируйте и посмотрите, как он работает. 2. Работа с координатной сеткой В качестве примера создайте программу «Координатная сетка», которая на поверхность формы выводит координатные оси и оцифрованную сетку (рис. 8.9). Рис Окно программы «Координатная сетка» Создайте новый проект. Ниже представлен листинг программы, который нужно написать в редакторе кода. Сохраните проект как MyGrid, а модуль программы как MyGrid_. unit MyGrid_; interface uses Windows, Messages, SysUtils, Classes, 161
162 Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class (TForm) procedure FormPaint(Sender: TObject); private < Private declarations >public < Public declarations >end; var Form1: TForm1; implementation procedure TForm1.FormPaint(Sender: TObject); var x0, y0: Integer; // координаты начала координатных осей dx, dy: Integer; // шаг координатной сетки (в пикселах) h, w: Integer; // высота и ширина области вывода координатной сетки х, у: Integer; lx, ly: Real; // метки (оцифровка) линий сетки по X и Y dlx, dly: Real; // шаг меток (оцифровки) линий сетки по X и Y cross: Integer; // счётчик неоцифрованных линий сетки dcross: Integer; //количество неоцифрованных линий между оцифрованными begin х0 :=30; у0 := 220; // оси начинаются в точке (40, 250) dx := 40; dy := 40; // шаг координатной сетки 40 пикселов dcross := 1; // помечать линии сетки X: // 1 каждую; // 2 через одну; // 3 через две; dlx := 0.5; // шаг меток оси X dly := 1.0; // шаг меток оси Y, метками будут: 1, 2, 3 и т. д. h := 200; w := 300; with forml.canvas do begin cross:=dcross; MoveTo(x0, y0); LineTo(x0, y0 h); // ось X MoveTo(x0, y0); LineTo(x0 + w, y0); // ось Y // засечки, сетка и оцифровка по оси X x := x0 + dx; lx := dlx; repeat MoveTo(x, y0 3); LineTo(x, y0 + 3); // засечка cross := cross l; if cross = 0 then // оцифровка 162
163 begin TextOut(x 8, y0 + 5, FloatToStr(lx)); cross := dcross; end; //if Pen.Style := psdot; MoveTo(x, y0 3); LineTo(x, y0 h); // линия сетки Pen.Style := pssolid; lx := lx + dlx; x := x + dx; until (x > x0 + w); // засечки, сетка и оцифровка по оси Y y := y0 dy; ly := dly; repeat MoveTo(х0 3, у); LineTo(х0 + 3, у); // засечка TextOut(х0 20, у, FloatToStr(lу)); // оцифровка Pen.Style := psdot; MoveTo(х0 + 3, у); LineTo(x0 + w, у); // линия сетки Pen.Style := pssolid; y := y dy; ly := ly + dly; until (y < y0 h); end; end; end. //with Особенность приведённой программы заключается в том, что она позволяет задавать шаг сетки и оцифровку. Кроме того, программа даёт возможность оцифровывать не каждую линию сетки оси х, а через одну, две, три и т.д. Сделано это для того, чтобы предотвратить возможные наложения изображений чисел оцифровки друг на друга в случае, если эти числа состоят из нескольких цифр. Сохраните проект, скомпилируйте и посмотрите, как он работает. 3. Работа с ломанной линией 3.1. График изменения величины В качестве примера использования метода Polyline ниже приведена процедура, которая выводит график изменения некоторой величины. Предполагается, что исходные данные находятся в доступном процедуре массиве Data (тип Integer). 163
164 procedure TForml.Button1Click(Sender: TObject); var gr: array[1..50] of TPoint; // график ломаная линия x0, y0: Integer; // координаты точки начала координат dx, dy: Integer; // шаг координатной сетки по осям X и Y i: Integer; begin х0 := 10; у0 := 200; dx :=5; dy := 5; // заполним массив gr for i := l to 50 do begin gr[i].x := x0 + (i l) * dx; gr[i].y := y0 Data[i] * dy; end; //for // строим график with forml.canvas do begin MoveTo(x0, y0); LineTo(x0, 10); // ось Y MoveTo(x0, y0); LineTo(200, y0); // ось X Polyline(gr); // график end; //with end; Сохраните проект как MyGraph, скомпилируйте и посмотрите, как он работает Замкнутый контур В качестве примера использования метода Polyline для вычерчивания замкнутого контура ниже приведена программа, которая на поверхности диалогового окна, в точке нажатия кнопки мыши, вычерчивает контур пятиконечной звезды (рис. 8.10). unit MyStars_; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(tform) procedure FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); private < Private declarations >public < Public declarations >164
165 end; var Forml: TForml; implementation // вычерчивает звезду procedure StarLine(x0, y0, r: Integer; Canvas: TCanvas); // x0, y0 координаты центра звезды //r радиус заезды var р: array [1.. 11] of TPoint; // массив координат лучей и впадин a: Integer; // угол между осью ОХ и прямой, соединяющей // центр звезды и конец луча или впадину i: Integer; begin а := 18; // строим от правого гор. луча for i := l to 10 do begin if (i mod 2 = 0) then begin // впадина p[i].x := x0 + Round(r/2 * cos(a * pi/180)); p[i].y := y0 Round(r/2 * sin(a * pi/180)); end //if else begin // луч [i].x := x0 + Round(r * cos (a * pi/180)); [i].y := y0 Round(r * sin(a * pi/180)); end; //else a := a + 36; end; //for p[ll].x := p[l].x; // чтобы замкнуть контур звезды Canvas.Polyline(р); // начертить звезду end; // нажатие кнопки мыши procedure TForm1. FormMouseDown (Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Button = mbleft // нажата левая кнопка? then Form1.Canvas.Pen.Color : = clred else Form1.Canvas.Pen.Color := clgreen; StarLine(x, y, 30, Forml.Canvas); end; end. 165
166 Цвет, которым вычерчивается звезда, зависит от того, какая из кнопок мыши была нажата. Процедура обработки нажатия кнопки мыши (событие MouseDown) вызывает процедуру рисования звезды StarLine и передаёт ей в качестве параметра координаты точки, в которой была нажата кнопка. Звезду вычерчивает процедура StarLine, которая в качестве параметров получает координаты центра звезды и холст, на котором звезда должна быть выведена. Сначала вычисляются координаты концов и впадин звезды, которые записываются в массив р. Затем этот массив передаётся в качестве параметра методу Polyline. При вычислении координат лучей и впадин звезды используются функции sin и cos. Так как аргумент этих функций должен быть выражен в радианах, то значение угла в градусах домножается на величину pi/180, где pi это стандартная именованная константа, равная числу π. Рис Построение звезды как замкнутой области Сохраните проект как MyStars, скомпилируйте и посмотрите, как он работает. 4. Построение графика функции В качестве примера создайте программу «График функции», которая, используя свойство Pixels, выводит в окно график функции у = 2 sin(x) e x/5 (рис. 8.11). Для построения графика используется вся доступная область формы, причём если во время работы программы пользователь изменит размер окна, то график будет выведен заново с учётом реальных размеров окна. 166
167 Рис Построение графика заданной функции Создайте новый проект. Ниже представлен листинг программы, который нужно написать в редакторе кода. Сохраните проект как MyGrFunc, а модуль программы как MyGrFunc_. unit MyGrFunc_; interface Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type TForm1 = class(tform) procedure FormPaint(Sender: TObject); procedure FormResize(Sender: TObject); private public end; var Forml: TForml; implementation // Функция, график которой надо построить function f(x: Real): Real; 167
168 begin f := 2 * Sin(x) * exp(x/5) ; end; // строит график функции procedure GrOfFunc; var x1, x2: Real; // границы изменения аргумента функции y1, y2: Real; // границы изменения значения функции х: Real; // аргумент функции у: Real; // значение функции в точке х dx: Real; // приращение аргумента l, b: Integer; // левый нижний угол области вывода графика w, h: Integer; // ширина и высота области вывода графика mx, my: Real; // масштаб по осям X и Y х0, у0: Integer; // точка начало координат begin // область вывода графика l := 10; // X координата левого верхнего угла b := Forml.ClientHeight 20; //У координата левого верхнего угла h := Forml.ClientHeight 40; // высота w := Forml.Width 40; // ширина x1 := 0; // нижняя граница диапазона аргумента х2 := 25; // верхняя граница диапазона аргумента dx := 0.01; // шаг аргумента // найдем максимальное и минимальное значения // функции на отрезке [x1, x2] y1 := f(x1); // минимум y2 := f(x1); //максимум x := x1; repeat y := f (х); if у < y1 then y1 := y; if у > у2 then y2 := y; х : = x + dx; until (x >= х2); // вычислим масштаб my := h/abs(y2 yl); // масштаб по оси Y mx := w/abs(x2 xl); // масштаб по оси X х0 := 1; у0 := b Abs(Round(y1 * my)) ; with form1.canvas do begin // оси MoveTo(l, b); LineTo(l, b h); MoveTo(x0, y0); LineTo(x0 + w, y0); 168
169 TextOut(l + 5, b h, FloatToStrF(y2, ffgeneral, 6, 3)); TextOut(l + 5, b, FloatToStrF(y1, ffgeneral, 6, 3)); // построение графика x := xl; repeat y := f(x); Pixels[x0 + Round(x * mx), y0 Round(y * my)] := clred; x := x + dx; until (x >= x2); end; //with end; procedure TForm1.FormPaint(Sender: TObject); begin GrOfFunc; end; // изменился размер окна программы procedure TForm1.FormResize(Sender: TObject); begin // очистить форму forml.canvas.fillrect(rect(0, 0, ClientWidth, ClientHeight)); // построить график GrOfFunc; end; end. Основную работу выполняет процедура GrOfFunc, которая сначала вычисляет максимальное (у2) и минимальное (y1) значения функции на отрезке [x1, x2]. Затем, используя информацию о ширине (Form1.ClientWidth 40) и высоте (Form1.ClientHeight 40) области вывода графика, вычисляет масштаб по осям X (mх) и Y (mу). Высота и ширина области вывода графика определяются размерами рабочей (клиентской) области формы, т.е. без учёта области заголовка и границ. После вычисления масштаба процедура вычисляет координату у горизонтальной оси (у0) и вычерчивает координатные оси графика. Затем выполняется непосредственное построение графика. Вызов процедуры GrOfFunc выполняют процедуры обработки событий OnPaint и OnFormResize. Процедура TForm1.FormPaint обеспечивает вычерчивание графика после появления формы на экране в результате запуска программы, а также после появления формы во время работы программы, 169
170 например, в результате удаления или перемещения других окон, полностью или частично перекрывающих окно программы. Процедура TForm1.FormResize обеспечивает вычерчивание графика после изменения размера формы. Сохраните проект, скомпилируйте и посмотрите, как он работает. Приведённая программа довольно универсальна. Заменив инструкции в теле функции f(х), можно получить график другой функции. Причём независимо от вида функции её график будет занимать всю область, предназначенную для вывода. Рассмотренная программа работает корректно, если функция, график которой надо построить, принимает как положительные, так и отрицательные значения. Если функция во всём диапазоне только положительная или только отрицательная, то в программу следует внести изменения. 5. Работа с иллюстрациями Создайте программу «Слайд-проектор», использующую компонент Image для просмотра иллюстраций, которые находятся в указанном пользователем каталоге. Диалоговое окно программы приведено ниже (рис. 8.12). Рис Форма приложения «Слайд-проектор» 170
171 Создайте новый проект. Ниже представлен листинг программы, который нужно написать в редакторе кода. Сохраните проект как MySlider, а модуль программы как MySlider _. unit MySlider _; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls, Menu type TForm1 = class(tform) Image1: ТImage; Button1: TButton; procedure FormActivate(Sender: TObject); procedure ButtonlClick(Sender: TObject); private < Private declarations >public < Public declarations >end; var Form1: TForm1; asearchrec: TSearchRec; apath: String; // каталог, в котором находятся иллюстрации afile: String; // файл иллюстрации iw, ih: Integer; // первоначальный размер компонента Image implementation $R *.DFM> // изменение размера области вывода иллюстрации // пропорционально размеру иллюстрации procedure Scalelmage; var pw, ph: Integer; // размер иллюстрации scalex, scaley: Real; // масштаб по Х и Y scale: Real; // общий масштаб begin // иллюстрация уже загружена, получим её размеры pw := Form1.Image1.Picture.Width; ph := Form1.Image1.Picture.Height; if pw > iw // ширина иллюстрации больше ширины компонента Image then scalex := iw/pw // нужно масштабировать else scalex := 1; if ph > ih // высота иллюстрации больше высоты компонента then scaley := ih/ph // нужно масштабировать else scaley := 1; // выберем наименьший коэффициент 171
172 if scalex < scaley then scale := scalex else scale := scaley; // изменим размер области вывода иллюстрации Form1.Image1.Height := Round(Form1.Image1.Picture.Height*scale) Form1.Image1.Width := Round(Form1.Image1.Picture.Width*scale); // так как Strech = True и размер области пропорционален // размеру картинки, то картинка масштабируется без искажений end; // вывести первую иллюстрацию procedure FirstPicture; var r: Integer; // результат поиска файла begin apath := 'f:\temp\'; r := FindFirst(aPath + '*.bmp', faanyfile, asearchrec); if r = 0 then begin // в указанном каталоге есть bmp-файл afile := apath + asearchrec.name; Form1.Image1.Picture.LoadFromFile(aFile); // загрузить иллюстрацию ScaleImage; //установить размер компонента Image r := FindNext(aSearchRec); // найти следующий файл if r = 0 then // ещё есть файлы иллюстраций Forml.Button1.Enabled := True; end; //if end; // вывести следующую иллюстрацию Procedure NextPicture(); var r: integer; begin afile := apath + asearchrec.name; Forml.Image1.Picture.LoadFromFile(aFile); Scalelmage; // подготовим вывод следующей иллюстрации r := FindNext(aSearchRec); // найти следующий файл if r<>0 then // больше нет иллюстраций Form1.Button1.Enabled := False; end; procedure TForml.FormActivate(Sender: TObject); begin Image1.AutoSize := False; // запрет автоизменения размера компонента Image1.Stretch := True; // разрешим масштабирование // запомним первоначальный размер области вывода иллюстрации 172
173 iw := Image1.Width; ih := Image1.Height; Button1.Enabled := False; FirstPicture; end; // сделаем недоступной кнопку «Дальше» // вывести первую иллюстрацию //щелчок на кнопке «Дальше» procedure TForm1.Button1Click(Sender: TObject); begin NextPicture; end; end. Программа выполняет масштабирование выводимых иллюстраций без искажения, чего нельзя добиться простым присвоением значения True свойству Strech. Загрузку и вывод первой и остальных иллюстраций выполняют соответственно процедуры FirstPicture и NextPicture. Процедура FrirstPicture использует функцию FindFirst для того, чтобы получить имя первого bmp-файла. В качестве параметров FindFirst передаются: имя каталога, в котором должны находиться иллюстрации; структура AsearchRec, поле Name которой, в случае успеха, будет содержать имя файла, удовлетворяющего критерию поиска; маска файла иллюстрации. Если в указанном при вызове функции FindFirst каталоге есть хотя бы один bmp-файл, значение функции будет равно нулю. В этом случае метод LoadFromFile загружает файл иллюстрации, после чего вызывается функция ScaleImage, которая устанавливает размер компонента пропорционально размеру иллюстрации. Размер загруженной иллюстрации можно получить, обратившись к свойствам Form1.Image1.Picture.Width и Form1.Image1.Picture.Height, значения которых не зависят от размера компонента Image. Сохраните проект, скомпилируйте и посмотрите, как он работает. 6. Использование битовых образов Создайте программу «Два самолёта», которая демонстрирует использование битовых образов для формирования изображения из нескольких элементов. 173
174 Создайте новый проект. Ниже представлен листинг программы, который нужно написать в редакторе кода. Сохраните проект как MyAplanes, а модуль программы как MyAplanes_. unit MyAplanes_; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type TForml = class(tform) procedure FormPaint(Sender: TObject); private < Private declarations >public < Public declarations >end; var Forml: TForm1; sky, aplane: TBitMap; // битовые образы: небо и самолёт implementation ($R *.DFM> procedure TForm1.FormPaint(Sender: TObject); begin // создать битовые образы sky := TBitMap.Create; aplane := TBitMap.Create; // загрузить картинки sky.loadfromfile('sky.bmp'); aplane.loadfromfile('aplane.bmp') ; Form1.Canvas.Draw(0, 0, sky); // отрисовка фона Form1.Canvas.Draw(20, 20, aplane); // отрисовка левого самолёта aplane.transparent := True; // теперь элементы рисунка, цвет которых совпадает с цветом // левой нижней точки битового образа, не отрисовываются Form1.Canvas.Draw(120, 20, aplane); // отрисовка правого самолета // освободить память sky.free; aplane.free; end; end. После запуска программы в окне приложения (рис. 8.13) появляется изображение летящих на фоне неба самолётов. Фон и 174
175 изображение самолёта битовые образы, загружаемые из файлов. Белое поле вокруг левого самолёта показывает истинный размер картинки битового образа aplane. Белое поле вокруг правого самолёта отсутствует, так как перед его выводом свойству Transparent битового образа было присвоено значение True. Рис Приложение «Два самолёта» 7. Создание мультипликации Создайте программу «Движущаяся окружность», которая демонстрирует движение окружности от левой к правой границе окна программы. Создайте новый проект. Ниже представлены диалоговое окно программы (рис. 8.14) и листинг программы, который нужно написать в редакторе кода. Сохраните проект как MyMoveCircle, а модуль программы как MyMoveCircle _. Рис Форма приложения «Движущаяся окружность» unit MyMoveCircle _; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls; type TForm1 = class(tform) Timer1: TTimer; procedure Timer1Timer(Sender: TObject>; procedure FormActivate(Sender: TObject); 175
176 private < Private declarations >public < Public declarations >end; implementation var Form1: TForml; x, y: Byte; // координаты центра окружности dx: Byte; // приращение координаты x при движении окружности // стирает и рисует окружность на новом месте procedure Ris; begin // стереть окружность Form1.Canvas.Pen.Color: = Form1.Color; Form1.Canvas.Ellipse(x, y, x + 10, y + 10); x := x + dx; // нарисовать окружность на новом месте Form1.Canvas.Pen.Color := clblack; Form1.Canvas.Ellipse(x, y, x + 10, y + 10) ; end; // сигнал от таймера procedure TForm1.Timer1Timer(Sender: TObject); begin Ris; end; procedure TForm1.FormActivate(Sender: TObject); begin x := 0; y := 10; dx := 5; Timer1.Interval := 50; // период возникновения события OnTimer 0.5 сек Form1.Canvas.Brush. Color := Forml.Color; end; end. Основную работу выполняет процедура Ris, которая стирает окружность и выводит её на новом месте. Стирание окружности выполняется путём перерисовки окружности поверх нарисованной, но цветом фона. 176
177 Для обеспечения периодического вызова процедуры Ris в форму программы добавлен невизуальный компонент Timer (Таймер), значок которого находится на вкладке System палитры компонентов. Добавляется компонент Timer к форме обычным образом. Однако, поскольку компонент Timer является невизуальным, т.е. во время работы программы не отображается на форме, его значок можно поместить в любое место формы. Событие OnTimer в рассматриваемой программе обрабатывается процедурой Timer1Timer, которая, в свою очередь, вызывает процедуру Ris. Таким образом, в программе реализован механизм периодического вызова процедуры Ris. Обратите внимание на то, что переменные х, у (координаты центра окружности) и dx (приращение координаты х при движении окружности) объявлены вне процедуры Ris, т.е. они являются глобальными. Поэтому следует выполнить их инициализацию (в программе инициализацию глобальных переменных реализует процедура FormActivate). КОНТРОЛЬНЫЕ ВОПРОСЫ 1. Как осуществляется вывод графики в приложении Delphi? 2. Какое свойство в Delphi соответствует поверхности объекта для вывода графики? 3. Что необходимо выполнить, чтобы вывести на поверхность объекта графический элемент? 4. Что представляет собой холст в Delphi? 5. Как можно получить размер холста? 6. Какие свойства соответствуют карандашу и кисти в Delphi? 7. Для чего в Delphi используется карандаш? 8. Перечислите основные свойства объекта TPеn. 9. Перечислите возможные значения свойства Pen.Color. 10. Перечислите возможные значения свойства Pen.Style. 11. Перечислите возможные значения свойства Pen.Mode. 12. Для чего в Delphi используется кисть? 13. Перечислите основные свойства объекта TBrush. 14. Какой метод используется для вывода текста на поверхность графического объекта? 177
178 15. Перечислите основные свойства объекта TFont. 16. Как осуществляется вычерчивание графических примитивов на поверхности компонента? 17. Какой метод осуществляет вычерчивание прямой линии? 18. Для чего используется метод MoveTo? 19. Какой метод используется для вычерчивания ломанной линии? 20. Какой метод позволяет начертить окружность или эллипс? 21. Какой метод используется для вычерчивания дуги? 22. Какой метод позволяет начертить прямоугольник? 23. Какой метод позволяет начертить прямоугольник со скруглёнными углами? 24. Для чего используется метод Polygon? 25. Какой метод вычерчивает сектор эллипса или круга? 26. За что отвечает свойство Pixels? 27. Когда можно задать иллюстрацию, которая будет выведена в поле компонента Image? 28. Для чего используется объект типа TBitMap? 29. Для чего предназначено свойство Transparent объекта TBitMap? 30. Что понимается под мультипликацией? 31. Для чего используется компонент Timer? ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ 1. Создайте новый проект. 2. Реализуйте примеры, показанные в пункте 3 теоретической части работы. 3. Сохраните проект как MyTextOut, скомпилируйте и посмотрите, как он работает. 4. Создайте новый проект. 5. Реализуйте пример, показанный в пункте 4.5 теоретической части работы. 6. Сохраните проект как MyRect, скомпилируйте и посмотрите, как он работает. 7. Создайте новый проект. 8. Реализуйте пример, показанный в пункте 4.6 теоретической части работы. 178
179 9. Сохраните проект как MyPoligon, скомпилируйте и посмотрите, как он работает. 10. Откройте проект MyGrFunc. 11. Внесите в код программы изменения, которые позволят построить график функции, отрицательной во всём диапазоне. 12. Сохраните проект как MyGrFuncNegative, скомпилируйте и посмотрите, как он работает. 13. Внесите в код программы MyGrFunc изменения, которые позволят построить график функции, положительной во всём диапазоне. 14. Сохраните проект как MyGrFuncPositive, скомпилируйте и посмотрите, как он работает.
180 ПРАКТИЧЕСКАЯ РАБОТА 9. МУЛЬТИМЕДИА ВОЗМОЖНОСТИ DELPHI Цель изучить компоненты Delphi для создания мультимедийных приложений. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ Большинство современных программ, работающих в среде Windows, являются мультимедийными. Такие программы обеспечивают просмотр видеороликов и мультипликации, воспроизведение музыки, речи, звуковых эффектов. Типичными примерами мультимедийных программ являются игры и обучающие программы. Delphi предоставляет в распоряжение программиста два компонента, которые позволяют разрабатывать мультимедийные программы: Animate обеспечивает вывод простой анимации (подобной той, которую видит пользователь во время копирования файлов); MediaPlayer позволяет решать более сложные задачи, например, воспроизводить видеоролики, звук, сопровождаемую звуком анимацию. 1. Компонент Animate Компонент Animate, значок которого находится на вкладке Win32 (рис. 9.1), позволяет воспроизводить простую анимацию, кадры которой находятся в AVI-файле. Рис Значок компонента Animate 180
181 Хотя анимация, находящаяся в AVI-файле может сопровождаться звуковыми эффектами (это можно проверить, например, при помощи стандартной программы Проигрыватель Windows Media), компонент Animate обеспечивает воспроизведение только изображения. Для полноценного воспроизведения сопровождаемой звуком анимации следует использовать компонент MеdiaPlayer. Компонент Animate добавляется к форме обычным способом. После добавления компонента к форме следует установить значения его свойств. Свойства компонента Animate перечислены ниже (табл. 9.1). Свойство Name FileName StartFrame StopFrame Activate Color Transparent Repetitions Таблица 9.1 Свойства компонента Animate Назначение Имя компонента. Используется для доступа к свойствам компонента и управлением его поведением. Имя AVI-файла, в котором находится анимация, отображаемая при помощи компонента. Номер кадра, с которого начинается отображение анимации. Номер кадра, на котором заканчивается отображение анимации. Признак активизации процесса отображения кадров анимации. Цвет фона компонента (цвет «экрана»), на котором воспроизводится анимация. Режим использования «прозрачного» цвета при отображении анимации. Количество повторов отображения анимации. Следует ещё раз обратить внимание, что компонент Animate предназначен для воспроизведения AVI-файлов, которые содержат только анимацию. При попытке присвоить записать в свойство FileName имя файла, который содержит звук, Delphi выводит сообщение о невозможности открытия указанного файла ("Cannot open AVI"). Чтобы увидеть, что находиться в AVI-файле (анимация и звук или только анимация), нужно из Windows раскрыть нужную папку, выделить AVI-файл и из контекстного меню выбрать команду Свойства. Компонент Animate позволяет использовать в программах стандартные анимации Windows. Вид анимации определяется значением свойства СommonAVI. Значение свойства задаётся при помощи именованной константы. Ниже (табл. 9.2) приведены 181
182 некоторые значения констант, вид анимации и описание процесса, для иллюстрации которого используется эта анимация. Таблица 9.2 Значения свойства СommonAVI Значение Анимация Процесс AviCopyFiles AviDeleteFile AviRecycleFile Копирование файлов. Удаление файла. Удаление файла в корзину. 2. Компонент MediaPlayer Компонент MediaPlayer, значок которого находится на вкладке System (рис. 9.2), позволяет воспроизводить видеоролики, звук и сопровождаемую звуком анимацию. Рис Значок компонента MediaPlayer В результате добавления компонента MediaPlayer на форме появляется группа кнопок (рис. 9.3), подобных тем, которые можно видеть на обычном аудио- или видеоплеере. Назначение этих кнопок пояснено ниже (табл. 9.3). Рис Компонент MediaPlayer 182
183 Таблица 9.3 Кнопки компонента MediaPlayer Кнопка Обозначение Действие Воспроизведение btplay Воспроизведение звука или видео. Пауза btpause Приостановка воспроизведения. Стоп btstop Остановка воспроизведения. Следующий btnext Переход к следующему кадру. Предыдущий btprev Переход к предыдущему кадру. Шаг btstep Переход к следующему звуковому фрагменту. Назад btback Переход к предыдущему звуковому фрагменту. Запись btrecord Запись. Открыть/Закрыть bteject Открытие или закрытие CD-дисковода компьютера. Свойства компонента MediaPlayer приведены ниже (табл. 9.4). Свойство Name DeviceType FileName AutoOpen Display VisibleButtons Таблица 9.4 Свойства компонента MediaPlayer Назначение Имя компонента. Используется для доступа к свойствам компонента и управлением работой плеера. Тип устройства. Определяет конкретное устройство, которое представляет собой компонент MediaPlayer. Тип устройства задаётся именованной константой: dtautoselect тип устройства определяется автоматически; dtvaweaudio проигрыватель звука; dtavivideo видеопроигрыватель; dtcdaudio CD-проигрыватель. Имя файла, в котором находится воспроизводимый звуковой фрагмент или видеоролик. Признак автоматического открытия сразу после запуска программы файла видеоролика или звукового фрагмента. Определяет компонент, на поверхности которого воспроизводится видеоролик (обычно в качестве экрана для отображения видео используют компонент Panel). Составное свойство. Определяет видимые кнопки компонента. Позволяет сделать невидимыми некоторые кнопки. Наличие у компонента MediaPlayer свойства Visible позволяет скрыть компонент от пользователя и при этом применять его для воспроизведения звука без участия пользователя. Звуковые фрагменты находятся в файлах с расширением wav. Например, в каталоге C:\Winnt\Media можно найти файлы со стандартными звуками Windows. 183
184 Помимо воспроизведения звука, компонент MediaPlayer позволяет просматривать видеоролики и мультипликации, представленные как AVI-файлы (AVI сокращение от Audio Video Interleave, что переводится как чередование звука и видео, т.е. AVIфайл содержит как звуковую, так и видеоинформацию). ПРАКТИЧЕСКАЯ ЧАСТЬ 1. Работа с компонентом Animate В качестве примера создайте программу «Просмотр анимации», которая демонстрирует использование компонента Animate для отображения в диалоговом окне программы анимации. Вид формы программы приведён ниже (рис. 9.4), а значения свойств компонента Animate1 в таблице (табл. 9.5). Рис Форма программы «Просмотр анимации» Значение свойств компонента Animate1 Свойство Значение FileName bart.avi Active False Transparent True Таблица
185 Создайте новый проект. Ниже представлен листинг программы, который нужно написать в редакторе кода. Сохраните проект как ShowAVI, а модуль программы как ShowAVI_. unit ShowAVI_; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComCtrls, ExtCtrls; type TForm1 = class(tform) Animate1: TAnimate; // компонент Animate Button1: TButton; // кнопка «Пуск-Стоп» Button2: TButton; // следующий кадр Button3: TButton; // предыдущий кадр RadioButton1: TRadioButton; // просмотр всей анимации RadioButton2: TRadioButton; // покадровый просмотр procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); procedure RadioButton1Click(Sender: TObject); procedure RadioButton2Click(Sender: TObject); private < Private declarations >public < Public declarations ) end; var Form1: TForm1; CFrame: Integer; implementation 185 // форма // номер отображаемого кадра // в режиме покадрового просмотра // к следующему кадру procedure TForm1.Button2Click(Sender: TObject); begin if CFrame = 1 then Button2.Enabled := True; if CFrame < Animate1.FrameCount then begin CFrame := CFrame + 1; // вывести кадр Animate1.StartFrame := CFrame; Animate1.StopFrame := CFrame; Animate1.Active := True; if CFrame = Animatel.FrameCount // текущий кадр последний then Button2.Enabled:=False; end; //if
186 end; // к предыдущему кадру procedure TForm1.Button3Click(Sender: TObject); begin if CFrame = Animate1.FrameCount then Button2.Enabled := True; if CFrame > 1 then begin CFrame := CFrame 1; // вывести кадр Animate1.StartFrame := CFrame; Animate1.StopFrame := CFrame; Animate1.Active := True; if CFrame = 1 // текущий кадр первый then Form1.Button3.Enabled := False; end; //if end; // активизация режима просмотра всей анимации procedure TForml.RadioButtonlClick(Sender: TObject); begin Buttonl.Enabled := True; //доступна кнопка «Пуск» // сделать недоступными кнопки покадрового просмотра Form1.Button3.Enabled := False; Form1.Button2.Enabled := False; end; // активизация режима покадрового просмотра procedure TForm1.RadioButton2Click(Sender: TObject); begin Button2.Enabled := True; // кнопка «Следующий кадр» доступна Buttons.Enabled := False; // кнопка «Предыдущий кадр» недоступна // сделать недоступной кнопку Пуск вывод всей анимации Buttonl.Enabled := False; end; // пуск и остановка просмотра анимации procedure TForm1.ButtonlClick(Sender: TObject); begin if Animate1.Active = False // в данный момент анимация не выводится then begin Animate1.StartFrame := l; // вывод с первого Animate1.StopFrame := Animate1.FrameCount; // по последний кадр Animate1.Active := True; Button1.Caption := 'Стоп'; RadioButton2.Enabled := False; end //then 186
187 else begin Animate1.Active:=False; Button1.caption:='Пуск'; RadioButton2.Enabled:=True; end; //else end; // анимация отображается // остановить отображение end. После запуска программы в форме выводится первый кадр анимации. Программа обеспечивает два режима просмотра анимации: непрерывный и покадровый. Кнопка Button1 используется как для инициализации процесса воспроизведения анимации, так и для его приостановки. Процесс непрерывного воспроизведения анимации инициирует процедура обработки события OnClick на кнопке «Пуск», которая присваивает значение True свойству Active. Эта же процедура заменяет текст на кнопке Button1 с «Пуск» на «Стоп». Режим воспроизведения анимации выбирается при помощи переключателей RadioButton1 и RadioButton2. Процедуры обработки события OnClick на этих переключателях изменением значения свойства Enabled блокируют или, наоборот, делают доступными кнопки управления: активизации воспроизведения анимации (Button1), перехода к следующему (Button2) и предыдущему (Button3) кадру. Во время непрерывного воспроизведения анимации процедура обработки события OnClick на кнопке «Стоп» (Button1) присваивает значение False свойству Active и тем самым останавливает процесс воспроизведения анимации. Сохраните проект, скомпилируйте и посмотрите, как он работает. 2. Работа с компонентом MediaPlayer 2.1. Воспроизведение звука В качестве примера создайте программу «Звуки Microsoft Windows», которая демонстрирует использование компонента MediaPlayer для воспроизведения звуковых фрагментов, находящихся в wav-файлах. Вид формы программы приведён ниже на рисунке (рис. 9.5). 187
188 Рис Форма программы «Звуки Microsoft Windows» Значения изменённых свойств компонента MediaPlayer1 приведены ниже (табл. 9.6), значения остальных свойств оставлены без изменения. Таблица 9.6 Значение свойств компонента MediaPlayer1 Свойство Значение DeviceType DtAutoSelect FileName C:\Winnt\Media\3вук Microsoft.wav AutoOpen True VisibleButtons.btNext False VisibleButtons.btPrev False VisibleButtons.btStep False VisibleButtons.btBack False VisibleButtons.btRecord False VisibleButtons.btEject False Помимо компонента MediaPlayer, на форме находится компонент ListBox и два компонента Label, первый из которых используется для вывода информационного сообщения, второй для отображения имени wav-файла, выбранного пользователем из списка. Создайте новый проект. Ниже представлен листинг программы, который нужно написать в редакторе кода. Сохраните проект как WinSound, а модуль программы как WinSound_. 188
189 unit WinSound_; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, MPlayer; type TForm1 = class(tform) MediaPlayerl: TMediaPlayer; // медиаплеер Label1: TLabel; // информационное сообщение ListBox1: TListBox; // список wav-файлов Label2: TLabel; // выбранный из списка файл procedure FormActivate(Sender: TObject); procedure ListBoxlClick(Sender: TObject); procedure MediaPlayerlClick(Sender: TObject; Button: TMPBtnType; var DoDefault: Boolean); private < Private declarations >public < Public declarations >end; const SOUNDPATCH = 'с:\winnt\media\'; // положение звуковых файлов var Form1: TForm1; implementation procedure TForm1.FormActivate(Sender: TObject); var SearchRec: TSearchRec; // структура, содержащая информацию о файле, //удовлетворяющем условию поиска begin Form1.MediaPlayer1.Play; // сформируем список wav-файлов, //находящихся в каталоге c:\winnt\media if FindFirst(SOUNDPATCH+'*.wav', faanyfile, SearchRec) = 0 then begin // в каталоге есть файл с расширением wav добавим имя в список Form1.ListBox1.Items.Add(SearchRec.Name); // пока в каталоге есть другие файлы с расширением wav while (FindNext(SearchRec) = 0) do Form1.ListBox1.Items.Add(SearchRec.Name); end; //if end; // щелчок на элементе списка procedure TForm1.ListBoxlClick(Sender: TObject); 189
190 begin // вывести в поле метки Label2 имя выбранного файла Label2.Caption := ListBox1.Items[ListBox1.itemlndex]; end; // щелчок на кнопке компонента MediaPlayer procedure TForm1.MediaPlayerlClick(Sender: TObject; Button: TMPBtnType; var DoDefault: Boolean); begin if (Button = btplay) and (Label2.Caption <> '') then begin // нажата кнопка Play with MediaPlayerl do begin FileName := SOUNDPATCH + Label2.Caption; // имя выбранного файла Open; // открыть и проиграть звуковой файл end; //with end; //if end; end. Работает программа следующим образом. После появления диалогового окна воспроизводится звук Microsoft. Пользователь может из списка выбрать любой из звуковых файлов, находящихся в каталоге C:\Windows\Media, и после щелчка на кнопке «Воспроизведение» услышать, что находится в этом файле. Воспроизведение звука сразу после запуска программы активизирует процедура обработки события OnFormActivate путём применения метода Play к компоненту MediaPlayer1 (действие этого метода аналогично щелчку на кнопке «Воспроизведение»). Эта же процедура формирует список wav-файлов, находящихся в каталоге C:\Winnt\Media. Для формирования списка используются функции FindFirst и FindNext, которые, соответственно, выполняют поиск первого и следующего (по отношению к последнему, найденному функцией FindFirst или FindNext) файла, удовлетворяющего указанному при вызове функций критерию. Обеим функциям в качестве параметров передаются маска wav-файла (критерий поиска) и переменная структура SearchRec, поле Name которой в случае успешного поиска будет содержать имя файла, удовлетворяющего критерию поиска. Щелчок на элементе списка обрабатывается процедурой TForm1.ListBox1Click, которая выводит в поле метки Label2 имя файла, выбранного пользователем (во время работы программы 190
191 свойство ItemIndex содержит номер элемента списка, на котором выполнен щелчок). В результате щелчка на одной из кнопок компонента MediaPiayer1 активизируется процедура TForm1.MediaPiayer1Сlick, которая проверяет, какая из кнопок компонента была нажата. Если нажата кнопка «Воспроизведение» (btplay), то в свойство FileName компонента MediaPiayer1 записывается имя выбранного пользователем файла, затем метод Open загружает этот файл и активизирует процесс его воспроизведения Сопровождение звуковым сигналом В качестве примера создайте программу «Фунтыкилограммы», которая пересчитывает вес из фунтов в килограммы и сопровождает выдачу результата звуковым сигналом. В случае, если пользователь забудет ввести исходные данные или введёт их неверно, программа выведет сообщение об ошибке, также сопровождаемое звуковым сигналом. Вид формы приложения приведён ниже (рис. 9.6). Рис Форма программы «Фунты-килограммы» Значения изменённых свойств компонента MediaPlayer1 приведены ниже (табл. 9.7). Значение свойств компонента MediaPlayer1 Свойство Значение Name MediaPiayer1 DeviceType dtautoselect FileName с : \winnt\media\ding.wav AutoOpen True Visible False 191 Таблица 9.7
192 Создайте новый проект. Ниже представлен листинг программы, который нужно написать в редакторе кода. Сохраните проект как FuntToKg1, а модуль программы как FuntToKg1_. unit FuntToKg1_; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, MPlayer; type TForm1 = class(tform) Edit1: TEdit; // поле ввода веса в фунтах Button1: TButton; // кнопка «Пересчёт» Label2: TLabel; // поле вывода результата Label1: TLabel; // поле информационного сообщения MediaPlayer1: TMediaPlayer; // медиаплеер procedure Button1Click(Sender: TObject); private < Private declarations >public < Public declarations >end; var Form1: TForm1; implementation + ' кг. '; except on EConvertError do // ошибка преобразования begin // определим и проиграем звук «Ошибка» Form1.MediaPlayer1.FileName := 'c:\windows\media\chord.wav'; Form1.MediaPlayer1.Open; Form1.MediaPlayer1.Play; 192 // звуковой сигнал
193 ShowMessage('Ошибка! Вес следует ввести числом.'); Form1.Edit1.SetFocus; // курсор в поле ввода восстановим звук Forml.MediaPlayer1.FileName := 'c:\windows\media\ding.wav'; Forml.MediaPlayer1.Open; end; //except end; //try end; end. Сохраните проект, скомпилируйте и посмотрите, как он работает Просмотр видеороликов и анимации В качестве примера для просмотра содержимого AVI-файла создайте программу «Использование MediaPlayer», которая в результате щелчка на командной кнопке воспроизводит на поверхности формы простую сопровождаемую звуковым эффектом мультипликацию вращающееся по часовой стрелке слово Delphi. Вид формы приложения приведён ниже (рис. 9.7). Рис Форма программы «Использование MediaPlayer» Значения изменённых свойств компонента MediaPlayer1 приведены ниже (табл. 9.8). Значение свойств компонента MediaPlayer1 Свойство Значение Name MediaPlayer1 FileName delphi.avi DeviceType dtavivideo AutoOpen True Display Panel1 Visible False Таблица
194 Компонент Panel1 используется в качестве экрана, на который осуществляется вывод анимации, и его имя принимается в качестве значения свойства Display компонента MediaPlayer1. Поэтому сначала к форме лучше добавить компонент Panel, а затем MediaPlayer. Такой порядок создания формы позволяет установить значение свойства Display путём выбора из списка. Следует особо обратить внимание на то, что размер области вывода анимации на панели определяется не значениями свойств Width и Height панели (хотя их значения должны быть как минимум такими же, как ширина и высота анимации). Размер области определяется значением свойства DisplayRect компонента MediaPlayer. Свойство DisplayRect во время разработки программы недоступно (его значение не выводится в окне Object Inspector). Поэтому значение свойства DisplayRect устанавливается во время работы программы в результате выполнения следующей инструкции: MediaPlayer1.DisplayRect := Rect(0, 0, 60, 60). Чтобы получить информацию о размере кадров AVI-файла, надо, используя возможности Windows, открыть папку, в которой находится этот файл, щёлкнуть правой кнопкой мыши на имени файла, выбрать команду Свойства. В появившемся диалоговом окне выводится подробная информация о файле, в том числе и размер кадров. Создайте новый проект. Ниже представлен листинг программы, который нужно написать в редакторе кода. Сохраните проект как MyVideo, а модуль программы как MyVideo_. unit MyVideo_; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, MPlayer, StdCtrls, ExtCtrls; type TForm1 = class(tform) Label1: TLabel; // информационное сообщение Panel1: TPanel; // панель, на которую выводится анимация Button1: TButton; // кнопка "OK" MediaPlayer1: TMediaPlayer; // универсальный проигрыватель procedure ButtonlClick(Sender: TObject); procedure FormCreate(Sender: TObject); 194
195 private < Private declarations ) public < Public declarations >end; var Form1: TForm1; implementation ($R *.DFM> procedure TForm1.ButtonlClick(Sender: TObject); begin MediaPlayer1.Play; // воспроизведение анимации end; procedure TForm1.FormCreate(Sender: TObject); begin // зададим размер области вывода анимации на поверхности формы MediaPlayer1.DisplayRect := Rect(0, 0, 60, 60); end; end. Процесс воспроизведения анимации активизируется применением метода Play, что эквивалентно нажатию кнопки "Play" в случае, если кнопки компонента MediaPlayer доступны пользователю. Сохраните проект, скомпилируйте и посмотрите, как он работает. КОНТРОЛЬНЫЕ ВОПРОСЫ 1. Какие два компонента Delphi позволяют разрабатывать мультимедийные приложения? 2. Какие действия необходимо выполнить, чтобы увидеть, что находиться в AVI-файле: анимация и звук или только анимация? 3. Для чего предназначен компонент Animate? 4. С какими видами файлов работает компонент Animate? 5. Перечислите основные свойства компонента Animate. 6. Какие действия необходимо выполнить, чтобы использовать в программах стандартные анимации Windows? 195
196 7. Для чего предназначен компонент MediaPlayer? 8. С какими видами файлов работает компонент MediaPlayer? 9. Перечислите назначение кнопок компонента MediaPlayer. 10. Перечислите основные свойства компонента MediaPlayer. 11. Где можно найти файлы со стандартными звуками Windows? ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ 1. Откройте проект ShowAVI. 2. Измените программу так, чтобы использовалась стандартная анимация Windows «Копирование файла». 3. Измените программу так, чтобы использовалась стандартная анимация Windows «Удаление файла». 4. Измените программу так, чтобы использовалась стандартная анимация Windows «Удаление файла в корзину». 5. Сохраните проект, скомпилируйте и посмотрите, как он работает. 6. Откройте проект WinSound. 7. Измените программу так, чтобы в результате её работы использовались звуки, хранящиеся на диске D. 8. Сохраните проект, скомпилируйте и посмотрите, как он работает. 9. Откройте проект MyVideo. 10. Проверьте, как будет работать программа, если поменять файл с мультипликацией. 196
197 ПРАКТИЧЕСКАЯ РАБОТА 10. ДИНАМИЧЕСКИЕ МАССИВЫ В DELPHI. СОЗДАНИЕ ПРОСТОЙ БАЗЫ ДАННЫХ Цель научится управлять размерностью и обработкой динамических массивов, создавать простую базу данных с помощью динамических массивов, записи и типизированного файла, а также изучить компонент Delphi для задания маски ввода данных. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ 1. Динамические массивы Динамическими называются массивы, при объявлении которых размер не указывается. Во время выполнения программы такие массивы могут изменять длину. Объявляются динамические массивы обычным образом, в разделе переменных: var DynAr: array of Integer; В данном примере объявляется массив целых чисел, однако длина при этом не указывается. В дальнейшем с таким массивом можно выполнять различные действия: увеличивать или уменьшать его длину, узнавать текущую длину, узнавать низшее и высшее значения диапазона массива, присваивать значения любому из его элементов, или наоборот, считывать эти значения, записывать значения массива в файл и считывать их из файла. Для работы с динамическими массивами существуют стандартные подпрограммы: 1. SetLength(массив, длина) устанавливает указанную длину у массива. Например: SetLength(DynAr, 10); В результате динамический массив DynAr содержит 10 элементов. Обратите внимание, что элементы динамических массивов всегда начинаются с нуля. 197
198 Если написать конструкцию SetLength(DynAr, 8); то длина массива уменьшится на 2 элемента. Если эти элементы содержали какое-то значение, они будут уничтожены. 2. Length(массив) показывает количество элементов в динамическом массиве. 3. Low(массив) указывает низший индекс динамического массива (как правило, это ноль). 4. High(массив) указывает высший индекс динамического массива. Ниже приведён пример использования стандартных подпрограмм для работы с динамическим массивом DynAr: SetLength(DynAr, 10); Low(DynAr); High(DynAr); //установили размер в 10 элементов //вернёт 0, как низший элемент //вернёт 9, как высший элемент Эти подпрограммы удобно использовать в цикле обработки каждого элемента массива: for i := Low(DynAr) to High(DynAr) do DynAr [i] := значение; Однако перед такими операциями нужно установить какойто размер массива, иначе произойдёт ошибка. Следующий пример проверяет размер массива. Если у массива нет индексов, он устанавливает один индекс: if Length(DynAr) = 0 then SetLength(DynAr, 1); Часто бывает необходимо добавить один элемент к массиву. Это можно сделать следующим образом: SetLength(DynAr, Length(DynAr) + 1); В качестве устанавливаемого размера вначале узнаётся текущий размер массива, а затем добавляется к нему единица. Получилось, что размер массива DynAr увеличен на 1 элемент. Этих функций вполне достаточно для работы с динамическими массивами, однако перед применением их на 198
199 практике следует знать, как работа с динамическими массивами выглядит с точки зрения компьютера. Когда объявляется динамический массив, под него память ещё не выделяется, а просто указывается тип данных, чтобы компьютер знал размерность под каждый элемент. Когда устанавливается размерность динамического массива функцией SetLength(), компьютер выделяет память под указанное количество элементов. При этом имя массива служит указателем на первый элемент массива в памяти. Компьютеру всё равно, какие данные хранятся в элементах, его задача выделить для этого достаточно памяти. Поэтому удалить какой-то элемент внутри массива не так просто. 2. Компонент MaskEdit Компонент MaskEdit подобен обычному Edit, но предназначен для задания маски ввода данных. Очень часто возникает необходимость задать правильный формат для ввода пользователем каких-то данных, и тут MaskEdit будет незаменима. Самое интересное свойство этого компонента EditMask, которое позволяет открыть редактор маски (рис. 10.1). Рис Редактор маски компонента MaskEdit В строке "Input Mask" можно задать нужный формат (маску). Например, необходимо создать маску для ввода телефонного номера. Тогда в строке "Input Mask" следует написать строку « ». Цифра 9 указывает, что в этом месте должна быть любая цифра. 199
200 В поле "Sample Mask" приведены типы масок и примеры формата. Выбрав здесь нужный тип, можно сразу же задать маску для ввода. В строке "Test Input" можно увидеть, как будет происходить ввод данных, то есть можно протестировать ввод. Задать маску можно не только выбором её типа, но и указав её самостоятельно. Поле "Character for Blanks" указывает, какой символ будет указываться в месте ввода. По умолчанию это знак подчёркивания. То есть, если оставить этот символ и выбрать, например, тип Date, то пользователь увидит. Здесь есть один минус: маску для ввода можно задать, однако проверка на правильность не производится, то есть пользователь может вписать « », а 20- го месяца не бывает. Поэтому проверку правильности нужно также проводить самостоятельно. Ещё одно интересное свойство для компонентов Edit и MaskEdit свойство PasswordChar (символ ввода пароля). По умолчанию он равен «#0», то есть никакого символа. Если же указать символ «*», как это принято по умолчанию в Windows, то во время работы программы все символы, которые будет вводить пользователь, в этой строке будет отображаться как звёздочки. Причём на сам текст это не окажет никакого влияния, просто его не будет видно в момент ввода. ПРАКТИЧЕСКАЯ ЧАСТЬ В качестве примера работы с динамическими массивами, записями и типизированным файлом создайте программу «Персональный телефонный справочник». Создайте новый проект. Расположите на форме 7 компонентов Label, 2 компонента ComboBox, 1 компонент MaskEdit для ввода номера телефона и 4 компонента Edit для ввода пользовательских данных. Верхний ComboBox будет нужен для выбора телефона из списка, чтобы сразу же высвечивались данные этой записи. Кроме того, внизу расположите 2 командные кнопки с надписями «Добавить телефон» и «Выйти из программы». Вид формы программы приведён ниже (рис. 10.2). В компоненте MaskEdit создайте маску для ввода телефона, наподобие указанной в рисунке, но с кодом своего города. 200
201 Рис Форма приложения «Персональный телефонный справочник» Проверьте свойство TabOrder всех компонентов для ввода, они должны идти один за другим при нажатии клавиши Tab. Свойство TabOrder показывает индекс компонента на форме. Тот компонент, у которого TabOrder равен 0, при открытии формы будет иметь фокус ввода, то есть будет выделенным. Когда пользователь нажмет клавишу Tab, выделение перейдёт к компоненту с TabOrder равным 1, и так далее. С помощью этого свойства можно указывать очерёдность выделения компонентов, которая, как правило, идёт сверху вниз и слева направо. Разумеется, такие компоненты, как Label, фокуса ввода не имеют и у них отсутствует свойство TabOrder. Форму переименуйте в fmain, модуль будет называться Main а проект MySprav. Названия компонентов оставьте по умолчанию, их не так много. Установите форму в стиль bsdialog, а позицию по центру экрана. Прежде всего, нужно объявить глобальную запись для хранения необходимых данных, а также глобальный массив элементов типа этой записи. Массив будет использоваться для сбора данных, записи их в файл и считывания из файла. Он должен быть глобальным, чтобы можно было работать с ним из всех процедур. Поэтому выше раздела implementation напишите такой код: 201
202 type mytfsprav = record TelNum: String[15]; Mobil: Boolean; Imya: String[20]; Otchestvo: String[20]; Familiya: String[20]; Adres: String[50]; end; //record var fmain: TfMain; sprav: array of mytfsprav; //номер телефона //мобильник да, нет? //имя владельца телефона //отчество //фамилия //адрес //объявляем динамический массив записи Указано немало полей в записи. Однако, не все из них будут обязательны для заполнения. Есть данные запишутся, нет поля можно оставить пустыми. Фактически, необходимыми записями являются только две: номер телефона и имя его владельца. Номер телефона нужен, потому что это телефонный справочник, и нет смысла в записи, если не указать там поле с номером телефона. Имя тоже необходимо, ведь зачем в файле номер телефона, если неизвестно, чей это номер. Создайте обработчик событий для кнопки «Выйти из программы»: Close; Далее обработайте кнопку «Добавить телефон». В самом начале ещё нет записей, поэтому первым делом пользователь введёт телефонные номера. procedure TfMain.Button1Click(Sender: TObject); var i: Integer; //для счётчика записей begin //если номера телефона нет, выходим: if MaskEdit1.Text = '8(374)- - - ' then begin //здесь введите свой код //города ShowMessage('Впишите номер телефона!'); MaskEdit1.SetFocus; Exit; end; //if //если имени нет, выходим: 202
203 if Edit1.Text = '' then begin ShowMessage('Впишите имя владельца телефона!'); Edit1.SetFocus; Exit; end; //if //действительно ли пользователь хочет сохранить телефон? if Application.MessageBox('Вы уверены, что хотите сохранить этот телефон?', 'Внимание!', MB_YESNOCANCEL + MB_ICONQUESTION) <> IDYES then Exit; //проверяем номер телефона на совпадение с имеющимися номерами, //если там есть записи. //Если такая запись уже есть, сообщаем об этом и выходим из процедуры: if length(sprav) > 0 then for i := Low(sprav) to High(sprav) do if sprav[i].telnum = MaskEdit1.Text then begin ShowMessage('Такой номер уже есть!'); Exit; end; //if //добавляем новый элемент к массиву: SetLength(sprav, Length(sprav) + 1); //записываем новый телефон в список: sprav[high(sprav)].telnum := MaskEdit1.Text; if ComboBox2.ItemIndex = 0 then sprav[high(sprav)].mobil := True else sprav[high(sprav)].mobil := False; sprav[high(sprav)].imya := Edit1.Text; sprav[high(sprav)].otchestvo := Edit2.Text; sprav[high(sprav)].familiya := Edit3.Text; sprav[high(sprav)].adres := Edit4.Text; //очищаем поля на форме: MaskEdit1.Text := '8(374)- - - '; ComboBox2.ItemIndex := 0; Edit1.Text := ''; Edit2.Text := ''; Edit3.Text := ''; Edit4.Text := ''; // здесь укажите ваш код города ShowMessage('Телефон ' + sprav[high(sprav)].telnum + ' добавлен!'); //обновим ComboBox с телефонами: ChangeCombo; end; 203
204 Процедуры ChangeCombo ещё нет. Она предназначена для того, чтобы пользователь мог выбрать нужный телефон из списка, а процедура обновляет список. Поскольку эта процедура должна быть описана выше того места, где она используется, то её необходимо описать самой первой, после директивы компилятору : procedure ChangeCombo; var i: Integer; //счётчик для цикла begin //если массив пустой выходим: if Length(sprav) = 0 then Exit; //если что-то есть, то сначала очистим ComboBox: fmain.combobox1.items.clear; //затем добавим в него каждый номер телефона из массива: for i := 0 to High(sprav) do fmain.combobox1.items.add(sprav[i].telnum); end; Сохраните проект и скомпилируйте его, впишите пару телефонов и убедитесь, что они попадают в ComboBox. Программа уже работает с динамическим массивом и сохраняет в него данные. Однако пока данные хранятся только в памяти, нужно научить программу сохранять их в файл. Создайте для формы обработчик событий OnDestroy, то есть, когда форма разрушается (закроется), данные попадут в файл. Файл будет создаваться, если его не было, или перезаписываться, если он уже был. procedure TfMain.FormDestroy(Sender: TObject); var f: File of mytfsprav; i: Integer; begin //создаем или перезаписываем файл: try AssignFile(f, 'mysprav.dat'); Rewrite(f); //записываем все данные архива: for i := Low(sprav) to High(sprav) do Write(f, sprav[i]); finally 204
205 CloseFile(f); end; // try end; Файл объявлен такого же типа, какой имеет массив. Поскольку необходимо записать не одну запись, а целый массив таких записей, то в цикле от первого до последнего элемента массива была использована следующая функция: Write(f, sprav[i]); Эта функция записывает в файл f запись нужного типа sprav[i]. В примере в этой строке сохраняется только один элемент массива, все его поля, после чего указатель перемещается к концу. В следующем проходе цикла сохраняться будет уже следующий элемент массива, и так далее, увеличивая файл. Обратите внимание, что в качестве файла указано только имя и расширение, следовательно, он будет создаваться в той же папке, откуда запущена программа. Можно использовать примеры из прошлых практических работ и изменить имя файла в функции AssignFile(), указав там и адрес программы. Сохраните пример и скомпилируйте его. Впишите пару записей и выйдите из программы. Посмотрите в директорию, там должен появиться файл справочник. Программа уже умеет сохранять данные в файл, теперь необходимо загружать их из файла. Создайте для формы обработчик OnCreate и впишите туда код: procedure TfMain.FormCreate(Sender: TObject); var f: File of mytfsprav; begin try AssignFile(f, 'mysprav.dat'); Reset(f); //считываем все данные в архив: while not Eof(f) do begin //добавляем новый элемент массива SetLength(sprav, Length(sprav) + 1); Read(f, sprav[high(sprav)]); end; //while finally CloseFile(f); 205
206 end; // try //обновим ComboBox с телефонами: ChangeCombo; end; Обратите внимание, что заранее неизвестно, сколько всего записей, поэтому использовать цикл for нельзя. Зато можно использовать while. Функция Eof(f) вернёт истину, когда будет достигнут конец файла f. И каждый раз для новой записи добавляется один элемент к массиву. Это следует сделать раньше, чем будет считываться запись, потому что, если для записи не выделена память, то некуда будет считывать новую запись и программа выдаст ошибку. В конце опять происходит вызов процедуры ChangeCombo для добавления в ComboBox всех телефонов из массива. Сохраните проект, скомпилируйте его и проверьте работу. В ComboBox должны загружаться все телефоны из файла. Однако программе не хватает «изюминки»: необходимо, чтобы данные записи отображались на форме, когда пользователь выберет тот или иной телефон в ComboBox. Выделите этот компонент и напишите для него обработчик событий OnChange: procedure TfMain.ComboBox1Change(Sender: TObject); begin MaskEdit1.Text := sprav[combobox1.itemindex].telnum; if sprav[combobox1.itemindex].mobil then ComboBox2.ItemIndex := 0 else ComboBox2.ItemIndex := 1; Edit1.Text := sprav[combobox1.itemindex].imya; Edit2.Text := sprav[combobox1.itemindex].otchestvo; Edit3.Text := sprav[combobox1.itemindex].familiya; Edit4.Text := sprav[combobox1.itemindex].adres; end; Всё, программа готова. Это, конечно, не полноценная база данных, но тем не менее такой приём программирования будет полезен при сохранении небольшого количества данных, например, для пользовательских настроек при многопользовательской программе. А для хранения больших объёмов данных желательно использовать полноценные базы данных. 206
207 КОНТРОЛЬНЫЕ ВОПРОСЫ 1. Какие массивы называют динамическими? 2. Как объявить динамический массив? 3. Перечислите стандартные подпрограммы работы с динамическими массивами. 4. С какого значения всегда начинаются элементы динамических массивов? 5. Как работа с динамическими массивами выглядит с точки зрения компьютера? 6. Для чего предназначен компонент MaskEdit? 7. Перечислите основные свойства компонента MaskEdit. 8. Для чего используется свойство TabOrder? ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ 1. Создайте новый проект. 2. Установите на форму компонент Edit. В свойстве Font выберите шрифт Windings, а в свойстве PasswordChar укажите «#74». 3. Сохраните проект как MyMaskEdit, скомпилируйте и посмотрите, как он работает. 4. Откройте проект MySprav. 5. Сделайте маску для ввода номеров мобильного телефона в соответствии с существующими в вашем виде мобильной связи. 6. Сохраните проект, скомпилируйте и посмотрите, как он работает. 207
208 ПРАКТИЧЕСКАЯ РАБОТА 11. ВВЕДЕНИЕ В БАЗЫ ДАННЫХ Цель изучить компоненты Delphi, обеспечивающие работу баз данных, научиться создавать собственные полноценные базы данных, редактировать их и создавать отчёты. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ 1. Базы данных Базы данных довольно большая и серьёзная область программирования. Ни одно предприятие, фирма или организация сегодня не обходятся без программ, обеспечивающих работу с базами данных. База данных это механизм ввода, сохранения и выборки информации по различным параметрам. В старых языках программирования и СУБД (систем управления базами данных) под термином БД (база данных) понимался файл, в котором данные хранятся в табличном представлении. Такими СУБД являются dbase, Clipper, FoxPro и т.д. Сейчас под понятием база данных понимают, как правило, папку, в которой может храниться большое количество таблиц. Причём эти таблицы обычно связаны друг с другом, и случайное удаление одной таблицы может привести к разрушению всей базы данных. Изначально среда Delphi не ориентирована на работу с базами данных, однако реализация механизмов для работы с базами данных настолько разнообразна, что делает Delphi одной из самых мощных и удобных сред разработки приложений баз данных. Среди этих механизмов можно назвать BDE, ODBC, ADO и т.д. Delphi может работать почти со всеми существующими форматами баз данных Основные понятия баз данных Основными понятиями баз данных являются: 1. Поле это столбец таблицы, общий для всех записей. Например, поле «Фамилия». 208
209 2. Ключевое поле поле, которое позволяет однозначно идентифицировать запись. 3. Запись строка таблицы, описывающая какой-то объект. Например, какой-то автор, у которого в поле «Фамилия» записано «Иванов», а в остальных полях другая информация этого автора. 4. Таблица набор записей в базе данных. 5. База данных одна или несколько таблиц, связанных друг с другом. 6. Индекс способ фильтрации данных. Если, например, сделать поле «Фамилия» индексным, то можно сортировать данные по этому полю, что обеспечивает более быстрый поиск данных Типы баз данных Базы данных могут быть трёх различных типов: 1. Локальные это приложения, использующие механизм работы с базами данных, которые находятся на этом же компьютере и не используются другими компьютерами. 2. Сетевые похожи на локальные. Сама база данных может находиться на общесетевом диске или папке. При этом различные компьютеры в организации работают с этой общей базой данных, т.е. все сотрудники организации имеют доступ к этой базе данных, вносят в неё данные, делают выборку и т.д. 3. Клиент-серверные приложения самая распространённая модель работы с базами данных. Обычно используется в крупных предприятиях, в которых требуется обрабатывать большое количество данных. На главном компьютере (сервере) хранится общая база данных. Кроме того, на нём запущена специальная программа серверная СУБД, которая получает запросы от пользователей (клиентов), обрабатывает их и возвращает результат. Такая модель снижает нагрузку на сеть и увеличивает надёжность сохранения данных. ПРАКТИЧЕСКАЯ ЧАСТЬ 1. Работа с базами данных с помощью BDE BDE (Borland Database Engine) механизм доступа к базам данных, разработанный самой корпорацией Borland. Сейчас этот 209
210 механизм считается устаревшим, однако он обеспечивает наиболее простой доступ к базам данных различных форматов. Создайте новое приложение, которое демонстрирует работу с базой данных и использование навигационных методов на демонстрационной таблице, поставляемой вместе с Delphi. Назовите форму fmain, присвойте ей стиль bsdialog и позицию по центру рабочего стола. В свойстве Caption напишите «БД Навигатор». Установите на форму панель GroupBox, в свойстве Caption которого укажите «Навигация». На эту панель установите 4 кнопки, на которых укажите «< Начало», «<< Назад», «Вперёд >>» и «Конец >». Это будут кнопки навигации (перемещения по таблице). Ниже установите ещё один GroupBox, в свойстве Caption которого напишите «Закладка». В таблице можно установить закладку на любой записи, а потом перейти на неё из любого места. В этой панели можно будет добавлять закладку, удалять её и переходить на неё. Для этого добавьте на панель 3 кнопки с надписями «Установить», «На закладку» и «Удалить». Ещё ниже поместите обычную панель и удалите из неё текст. Установите на неё компонент Label, в свойстве Caption которого укажите «Позиция:». Нужно, чтобы эта надпись всегда была посередине. Поэтому свойство AutoSize переведите в False, а свойство Alignment в tacenter. Растяните Label, чтобы метка занимала почти всю ширину панели. Здесь вместе с надписью «Позиция:» будет выходить номер записи в таблице. Перейдите на вкладку Data Controls, где содержатся компоненты отображения данных из БД. Найдите DBGrid и поместите на форму ниже панели. Сетка DBGrid очень похожа на сетку StringGrid, но предназначена для отображения и редактирования данных из таблицы БД. Свойству ReadOnly (Только для чтения) присвойте True, чтобы пользователь не мог редактировать данные демонстрационной таблицы. Чтобы получить доступ к БД, нужно на вкладке BDE найти компонент Table, который обеспечивает доступ к таблице средствами механизма BDE. Компонент не визуальный, поэтому можете расположить его на любое место, например, поверх сетки. В свойстве DatabaseName выберите демонстрационную базу данных DBDEMOS, которая поставляется вместе с Delphi. В 210
211 свойстве TableName необходимо указать саму таблицу, к которой нужно получить доступ. Выберите biolife.db. База данных, т.е. набор таблиц, уже выбрана и выбрана таблица. Однако, чтобы вывести в сетку данные из таблицы, требуется компонент управления данными таблицы. Найдите на вкладке Data Access компонент DataSource. Этот компонент обеспечивает связь базы данных из таблицы Table с навигационными компонентами, такими, как DBGrid. Расположите его рядом с Table. В свойстве DataSet компонента нужно указать, с какой именно таблицей будет осуществлена работа. В данном примере одна таблица, но ведь может быть и десяток. Выберите Table1. Выделите сетку. Ей нужно указать, какой компонент будет управлять этой сеткой. В свойстве DataSource укажите DataSource1. Всё, приготовления сделаны. Единственное, что осталось сделать открыть файл с таблицей. Выделите компонент Table1 и в свойстве Active укажите True. После этого таблицу можно будет использовать. Работа с базой данных отличается от работы с файлами другого типа. Когда программа работает, например, с текстовым файлом, она открывает этот файл, считывает его содержимое в оперативную память компьютера и закрывает его. Пользователь работает уже не с файлом, а с его содержимым в оперативной памяти. Когда он даёт команду сохранить результаты работы, программа вновь открывает файл и перезаписывает в него данные из оперативной памяти. С базами данных все иначе. Как только таблица открыта указано True у компонента Table1 в свойстве Active, или же программно дана команда или Table1.Active := True; Table1.Open; то тем самым открывается файл. Файл будет открыт на протяжении всего сеанса, и все изменения данных будут немедленно сохраняться в нём. Приведённые выше команды открытия таблицы идентичны, можно пользоваться любым из этих способов. 211
212 Если всё сделано правильно, то форма приложения будет выглядеть следующим образом (рис. 11.1): Рис Форма приложения «БД Навигатор» Для перемещения по записям таблицы существуют такие методы: 1. First переход на первую запись. 2. Last переход на последнюю запись. 3. Next переход на следующую запись. 4. Prior переход на предыдущую запись. Создайте обработчики нажатия на кнопки в панели «Навигация». В обработчике кнопки «Начало» напишите: Table1.First; 212
213 В обработчике кнопки «Назад» напишите: Table1.Prior; В обработчике кнопки «Вперёд» напишите: Table1.Next; В обработчике кнопки «Конец» напишите: Table1.Last; Сохраните проект как MyBDNavigator, откомпилируйте и посмотрите, как он работает. Указатель позиции должен перемещаться, когда пользователь нажимает на кнопки. Кроме того, перемещаться можно будет с помощью самой сетки DBGrid. Листая таблицу, можно увидеть такие данные, как Memo и Graphic, причём сами данные не отображаются. Дело в том, что таблицы имеют разные типы данных. Бывают числовые типы, строковые, типы «Дата». Обычно, в строковый тип можно ввести текст длиной не более 255 символов. Если же требуется поместить в таблицу более длинный текст, например, аннотацию к книге, то можно воспользоваться типом Memo. При этом создаётся новый файл, в котором хранится сам текст, а в таблице указана ссылка на этот текст. Длина текста в Memo неограниченна. Для отображения Memo потребуется специальный компонент. Поле Graphic хранит изображения: картинки, фотографии. Для их отображения нужен специальный компонент. Улучшим созданный пример, создав форму, в которой, как в карточке, можно видеть все данные текущей записи. Однако для начала необходимо познакомиться с DataModule. Это модуль, который не имеет формы. Его очень удобно использовать в многооконных проектах, где окна работают с общими данными. Для созданного приложения общие данные это компоненты Table и DataSource. Прежде всего, закройте таблицу, указав False в её свойстве Active. Выполните команду File New Data Module. Появится новое окно, но это не форма. Измените имя этого окна на fdm, чтобы не нужно было писать длинного обращения к компонентам. Вернитесь на главную форму и выделите компоненты Table и 213
214 DataSource. Выберите команду Edit Cut (Вырезать). Затем перейдите в окно fdm и выберите команду Edit Paste (Вставить). Если эти компоненты были в нижней части формы, то они могут оказаться вне зоны видимости окна fdm. Прокрутите окно либо увеличьте его размеры, чтобы видеть компоненты, а затем перетащите их в верхнюю часть окна. После этого можно уменьшить размеры fdm. Сохраните окно в модуле DM.pas. Перейдите на главную форму. Эта форма должна видеть окно fdm, чтобы работать с компонентами, установленными в нём, поэтому выполните команду File Use Unit и выберите DM. Выделите сетку. В её свойстве DataSource нужно выбрать используемый DataSource, который выглядит уже как fdm.datasource1. Откройте таблицу, указав True в её свойстве Active. Данные по-прежнему отображаются, хотя компоненты доступа хранятся в другом модуле. Теперь можно создать новое окно для просмотра данных. Создайте новую форму, назовите её fviewer и сохраните в модуле Viewer. Не забудьте выбрать команду File Use Unit и там DM, ведь новая форма также будет работать с компонентами Table и DataSource. В свойстве Caption напишите «Карточка». Перейдите на вкладку DataControls. Найдите там компонент DBEdit. От обычного Edit он отличается тем, что поддерживает связь с базой данных. Расположите на форму 6 таких компонентов один под другим. Ниже разместите DBMemo, а справа от DBEdit DBImage. В результате должна получиться форма, как показано ниже на рисунке (рис. 11.2). Слева от компонентов доступа к данным установите соответствующее количество Label и пропишите пояснения к полям в соответствии с рисунком. Выделите все компоненты доступа (все DBEdit, DBMemo и DBImage). Свойство ReadOnly у них установите в True, чтобы пользователь не испортил данные демонстрационной таблицы. В свойстве DataSource выделенных компонентов нужно выбрать fdm.datasource1. Теперь придётся с каждым компонентом работать отдельно. Выделите верхний DBEdit и в свойстве DataField (Поле) выберите "Species No". Все компоненты DBEdit, (сверху вниз) должны быть привязаны к следующим полям: Species No Category 214
215 Common_Name Species Name Length (cm) Length_In Компонент DBMemo должен быть привязан к полю Notes, а компонент DBImage к полю Graphic. У DBMemo установите вертикальную полосу прокрутки, чтобы можно было листать слишком большие примечания. Рис Внешний вид формы карточки Карточка готова. Осталось вызывать её из главной формы. Если ещё не выполнили для главной формы File Use Unit и не выбрали там модуль Viewer, то сделайте это сейчас. Затем выделите сетку и сгенерируйте для неё событие OnDblClick, где пропишите вызов окна карточки: fviewer.showmodal; Теперь пользователь, щёлкнув дважды по какой-либо записи, вызовет карточку, где будут отражены данные этой записи. Однако перед компиляцией проекта нужно кое-что исправить. 4 кнопки перемещения были запрограммированы, когда ещё компонент Table был на главной форме. Однако теперь он в окне DM, поэтому 215
216 добавьте fdm перед обращением к таблице во всех 4 кнопках, как в примере: fdm.table1.first; Сохраните проект, скомпилируйте и посмотрите, как он работает. Осталось научиться пользоваться закладками. Закладки (Bookmarks) позволяют сохранить положение в наборе данных, чтобы позднее можно было вернуться к этому же месту. В Delphi за это отвечает только одно свойство. Всё, что нужно сделать, это объявить переменную типа TBookmarkStr и присвоить ей положение, которое необходимо запомнить: var bm: TBookmarkStr; begin bm := Table1.Bookmark; Когда нужно вернуться на закладку, необходимо выполнить обратное присваивание: Table1.Bookmark := bm; Чтобы освободить закладку, нужно ей присвоить пустую строку: bm := ''; Таким образом, закладка реализуется переменной типа TBookmarkStr, а в компоненте Table имеется свойство Bookmark такого же типа. Вернитесь к созданному проекту и напишите код для кнопок, отвечающих за закладки. Прежде всего, переменная-закладка будет использоваться из трёх процедур по количеству кнопок управления закладкой. Следовательно, переменная должна быть глобальной. Опишите переменную в разделе глобальных переменных: var fmain: TfMain; bm: TBookmarkStr; 216
217 Кнопка «Установить» доступна, остальные кнопки не доступны, поскольку невозможно перейти на закладку или удалить её, пока эта закладка не установлена. Как только закладка будет установлена, то кнопки «На закладку» и «Удалить» должны стать активными. А вот «Установить» нет, потому что её сначала нужно освободить, удалить. Имея это в виду, напишите код для кнопки «Установить»: //получаем закладку: bm := fdm.table1.bookmark; //разрешаем или запрещаем кнопки: Button5.Enabled := False; Button6.Enabled := True; Button7.Enabled := True; // «Установить» // «На закладку» // «Удалить» Напишите код для кнопки «На закладку»: //перейти на закладку fdm.table1.bookmark := bm; Для кнопки «Удалить» напишите следующий код: //удалить закладку: bm := ''; //разрешаем или запрещаем кнопки: Button5.Enabled := True; // «Установить» Button6.Enabled := False; // «На закладку» Button7.Enabled := False; // «Удалить» Вот и вся работа с закладками Циклическая обработка таблицы Таблицу в базе данных можно обрабатывать циклически, переходя от записи к записи, пока не будет достигнут конец или начало таблицы. Для этого используются свойства BOF и EOF, которые имеют логический тип данных. Свойство BOF возвращает истину тогда, когда указатель находится на первой записи таблицы, а свойство EOF на последней. Обрабатывать циклически таблицу можно следующим образом: Table1.First; //переход на первую запись while not Table1.EOF do begin 217
218 Table1.Next; end; //while //переход на следующую запись Точно так же можно от конца таблицы переходить к началу и использовать при переходе свойство Prev (переход к предыдущей записи) Открытие и закрытие таблицы или связанных таблиц В созданном проекте MyBDNavigator во время разработки было установлено свойство Active таблицы в True. Однако нередко бывает, что открывать и закрывать таблицу приходится программным путём. Таким образом, для открытия таблицы можно использовать свойство Active или методы Open и Close, которые делают одно и то же: //открываем таблицы: Table1.Open; Table2.Active := True; //закрываем таблицы: Table1.Close; Table2.Active := False; 1.3. Свойства RecordCount и RecNo таблицы Свойства RecordCount и RecNo таблицы приходится использовать довольно часто, чтобы выяснить номер текущей записи или общее количество записей (строк) в таблице. RecordCount возвращает целое число, показывающее общее количество записей, а RecNo номер текущей записи. Улучшите проект MyBDNavigator, указав, какая запись является текущей и каково общее количество записей. Событие OnDataChange компонента DataSource возникает всякий раз, когда происходят изменения в данных таблицы. Например, перемещение от одной записи к другой. Однако, компонент Label, в который нужно прописать эти данные, находится в главном окне, а компонент DataSource в окне DM. Поэтому следует открыть модуль DM и использовать команду File Use Unit, в которой необходимо указать главное окно fmain. Выделите компонент DataSource и сгенерируйте для него событие OnDataChange. В коде этого события запишите: 218
219 fmain.label1.caption := 'Позиция: ' + IntToStr(Table1.RecNo) + ' из ' + IntToStr(Table1.RecordCount); В результате при загрузке программы должна получиться строка, наподобие такой: Позиция: 1 из 28 Теперь проект «БД Навигатор» полностью работоспособен. Сохраните проект, скомпилируйте его и посмотрите, как он работает. 2. Создание собственной таблицы с помощью Database Desktop Наиболее простой способ создать собственную таблицу воспользоваться встроенной утилитой Delphi Database Desktop. Создайте новое приложение, в котором необходимо сделать каталог книг для библиотеки. Форму сразу переименуйте в fmain, модуль сохраните под именем Main, а проект CatBooks. В свойстве Caption формы напишите «Библиотечный каталог». С помощью файлового менеджера создайте на диске D (если нет диска D, то на диске C) ещё одну папку "Data" (данные). Таким образом, адрес базы данных будет "d:\data". Запустите утилиту Database Desktop, которая находится там же, где Delphi. Эта программа не просто браузер БД, она ещё позволяет создавать таблицы и индексные файлы. Выберите команду File New Table. Появится окно, в котором можно выбрать формат создаваемой таблицы. Откройте список. Таблицы формата dbase наиболее распространены, однако они очень уязвимы и не имеют большого разнообразия типов полей. Поэтому оставьте формат по умолчанию Paradox 7. После нажатия "ОК" появится редактор полей, в котором нужно: 1. В поле "Field Name" указать название поля (колонки). 2. В поле "Type" выбрать тип поля. Для этого достаточно нажать пробел, чтобы открылся список всех полей (табл. 11.1). 3. В поле "Size" указывается длина строк в символах. Если выбрана, к примеру, дата, то это поле недоступно для редактирования, поскольку у всех записей будет фиксированная длина. 4. Поле "Key" позволяет указать ключевое поле. Для этого тоже достаточно нажать пробел. Установка на поле ключа 219
220 приводит к тому, что все записи таблицы будут сортироваться по этому полю. Например, если установить ключ у поля «Фамилия», то все записи отсортируются по фамилиям от А до Я. Если ключа нет, то записи будут в том порядке, в каком их ввели в таблицу. Alpha Типы данных таблиц Paradox 7 Тип Буква Описание A Таблица 11.1 Строка от 1 до 255 символов. Размер поля указывается в "Size". Number N Целое число от до (15 значащих цифр). $ Money $ Число в денежном формате. Short S Короткое целое от до Long Integer I Длинное целое от до Date D Дата. Time T Время с полуночи в миллисекундах. Дата и время. Memo M Строковое поле неограниченной длины. В "Size" можно указать длину от 1 до 240 символов, остальные символы хранятся в файле с таким же именем, но расширением mb. Как Memo, но также имеются дополнительные Formated F возможности: указать тип и размер шрифта, цвет Memo символов, способ оформления. Graphic G Графический файл сохраняется прямо в поле. Logical L Логический тип. ± Autoincrement + Bytes Y Двоичные данные. Binary B Целое число, увеличивающееся на единицу, при добавлении новой записи (счётчик). Двоичные данные. Как и Memo, хранятся в отдельном файле. Обычно содержат аудио- или видео-данные. Введите в созданную таблицу необходимые поля (табл. 11.2). Поля в созданной таблице Field Name Type Size Key Key1 + Avtor I Nazvanie A 100 Exemp S Cena $ Date D Prim M 200 Таблица
221 Нажмите кнопку "Save As", файл назовите books и сохраните его в папке "d:\data". Здесь будут храниться данные о книгах. Поле с автором установлено как целое. Позже будет ещё одна таблица, в которой будет вестись учёт всех авторов, и обе таблицы будут объединены. Если взглянуть на папку "d:\data", то там можно обнаружить два файла: books.bd (сама таблица) и books.mb (Memo поле таблицы). Создайте приложение, которое будет работать с этой таблицей. Главная форма уже есть, так что сразу создайте модуль данных (File New Data Module). В свойстве Name напишите fdm, а сам модуль сохраните как DM. Сразу же перейдите на главную форму и с помощью File Use Unit свяжите её с DM. Установите на форму DM компонент Table с вкладки BDE. В свойстве DatabaseName компонента впишите адрес созданной ранее базы: "d:\data". В свойстве TableName выберите созданную таблицу books.bd. Измените имя таблицы и укажите TBooks в свойстве Name компонента Table1. Так как в приложении будет более одной таблицы, желательно каждой из них дать осмысленное имя, чтобы потом не запутаться. Установите на форму компонент DataSource с вкладки Data Access. Имя компонента измените на DS1, чтобы было короче. В свойстве DataSet выберите созданную таблицу. Перейдите на главную форму. Установите главное меню и создайте такие пункты: 1. «Файл», «Выход». 2. «Редактирование» «Добавить книгу», «Добавить автора». 3. «Сортировка» «По автору», «По названию книги». 4. «Помощь» «О программе». Установите панель, свойству Align присвойте altop, чтобы она заняла весь верх, свойство Caption очистите. На панели расположите два компонента Label один под другим с надписями «Всего книг:», «На общую сумму:». Ниже установите сетку DBGrid с вкладки Data Controls. Свойство Align переведите в alclient. В свойстве DataSource выберите DS1. Свойство ReadOnly переведите в True, так как для редактирования данных будут другие инструменты, а сетка нужна только для просмотра. 221
222 Перейдите на окно DM, выделите компонент с таблицей и откройте её, установив Active в True. На главной форме сетка должна отображать данные таблицы, пока ещё пустые. Заголовки столбцов не очень привлекательны с точки зрения клиента. Поэтому выделите таблицу TBooks и дважды щёлкните. Должно появиться окно редактора полей (рис. 11.3, а). Щёлкните правой кнопкой по этому окну и выберите команду Add all fields (Добавить все поля). В окне редактора полей (рис. 11.3, б) отобразятся все поля таблицы TBooks. а б Рис Окно редактора полей компонента Table: а первоначальный вид; б после добавления полей Выделите поле "Key1". Оно нужно только как счётчик записей, так что пользователю его можно не отображать. Спрячьте его, установив его свойство Visible в False. Поле исчезло с сетки, но не потерялось, оно по-прежнему присутствует в таблице. Выделите поле "Avtor". В свойстве DisplayLabel, которое отвечает за выводимый текст названия колонки, напишите «Автор». В свойстве DisplayWidth, которое отвечает за ширину колонки, установите 5, так как здесь будут только цифры. У поля "Nazvanie" измените выводимый текст на «Название книги». У поля "Exemp" измените выводимый текст на «Кол-во экз.». У поля "Cena" измените выводимый текст на «Цена». У поля "Date" измените выводимый текст на «Дата пост.». 222
223 Наконец, поле "Prim" должно содержать аннотацию к книге, но поскольку это поле типа Memo, то в сетке оно всё равно не выйдет. Поэтому спрячьте это поле так же, как поле "Key1". Поскольку приложение должно ещё уметь подставлять фамилию автора книги вместо цифры, нужно сделать ещё одну таблицу. Снова запустите утилиту Database Desktop. Дальше всё, как и в прошлый раз, File New Table. Тип таблицы Paradox 7. Первое поле назовите "Key2", тип поля Autoincrement, Второе поле назовите "FIO", тип Alpha, размер 30 символов. Больше полей не требуется. Нажмите кнопку "Save as", имя таблице дайте avtors, а папку для таблицы "d:\data". Можно выходить из утилиты, таблица уже существует. Теперь потребуется форма для редактирования авторов, а также ещё по одному компоненту Table и DataSource. Вначале расположите эти компоненты в окно DM. Таблицу переименуйте в TAvtors, а DataSource в DS2. У таблицы в свойстве DatabaseName напишите адрес базы данных "d:\data", а в свойстве TableName выберите таблицу avtors.bd. У компонента DS2 в свойстве DataSet выберите компонент TAvtors. Создайте новую форму редактирования авторов, переименуйте её в favtors, в свойстве Caption напишите «Авторы», свойство BorderStyle переведите в bsdialog, с помощью File Use Unit добавьте к форме модуль DM. Теперь сохраните форму под именем Avtors. Установите на форму Label и напишите на ней «Впишите Фамилию И.О. автора:». Ниже добавьте Edit. Очистите у него свойство Text, а в свойстве MaxLength (Максимальная длина) укажите 30. Именно такой размер был указан у поля "FIO" при проектировании таблицы. Ниже установите две кнопки, на которых напишите «Добавить» и «Удалить текущую запись». Кнопку «Добавить» сделайте пока недоступной. Пользователю доступ к ней будет дан тогда, когда он что-нибудь введёт в поле Edit. Ещё ниже расположите сетку DBGrid, в свойстве DataSource которой нужно выбрать DS2. Всё, приготовления окончены, можно переводить таблицу TAvtors в активное состояние (Active переведите в True). Должна получиться такая форма, как показано на рисунке ниже (рис. 11.4). 223
224 Рис Внешний вид формы авторов Теперь так же, как редактировали названия полей в первой таблице, сделайте это во второй. Поле "Key2" прятать не нужно, просто в заголовке столбца укажите. В заголовке второго столбца «Фамилия И.О.». Перед тем, как продолжить создание библиотечного каталога, необходимо познакомиться с методами редактирования данных таблицы Методы редактирования баз данных Редактирование данных подразумевает под собой внесение изменений в записи, добавление или удаление записи. Если виден набор данных, это ещё не значит, что их можно редактировать. Дело в том, что база данных может быть и сетевая, а значит, её одновременно могут редактировать несколько сотрудников. Для того, чтобы они не мешали друг другу и не испортили запись таблицы при сохранении, существуют методы редактирования данных. Пример редактирования данных выглядит так: Table1.Edit; //вошли в режим редактирования Table1['FIO'] := 'Лермонтов М.Ю.'; //присвоили значение текущей записи // и полю "FIO" Table1.Post; //приняли изменения 224
225 А если требуется по всей таблице какому-то полю присвоить одно и то же значение, например, дату, то цикл будет выглядеть так: Table1.First; Table1.Edit; while not Table1.Eof do begin Table1['Date'] := StrToDate(' '); Table1.Next; end; //while Table1.Post; Основные методы таблицы Table приведены ниже: 1. Edit переключает таблицу в режим редактирования. 2. Post сохраняет результаты редактирования в таблицу. 3. Insert вставляет новую строку в месте указателя и включает режим редактирования. 4. Append вставляет пустую строку в конец таблицы, переводит указатель на неё и включает режим редактирования. 5. Cancel отменяет внесённые, но не зафиксированные изменения и отключает режим редактирования. Следует отметить, что, если изменения в запись внесены, то перемещение на другую запись вызовет безусловное сохранение изменений, как если бы был вызван метод Post. Таким образом, в предыдущем примере метод Post сохраняет изменение лишь последней записи, а все остальные записи сохранялись при вызове метода Next. Поэтому во время редактирования будьте аккуратны с использованием методов Next, Prior, First, Last они сохраняют внесённые изменения. Подводя итог, можно привести инструкцию действий, которые необходимо выполнить для редактирования одного или нескольких полей записи: 1. Вызвать метод Edit для включения режима редактирования. 2. Назначить новые значения требуемому полю или полям. 3. Вызвать метод Post или переместиться на другую запись, чтобы принять все изменения, и выключить режим редактирования. Вернитесь к проекту CatBooks к окну добавления авторов. Вначале нужно будет сделать кнопку «Добавить» доступной, когда 225
226 пользователь введёт в поле Edit какую-нибудь запись. Выделите Edit и сгенерируйте для него событие OnChange, в котором напишите: Button1.Enabled := True; Создайте обработчик нажатия кнопки «Добавить»: fdm.tavtors.append; fdm.tavtors['fio'] := Edit1.Text; fdm.tavtors.post; Button1.Enabled := False; Edit1.Text := ''; //добавляем строку в конец таблицы //присваиваем значение //принимаем изменения //снова отключаем кнопку //очищаем поле Edit Код кнопки «Удалить текущую запись» будет таким: //удаляем запись, если таблица не пуста: if not fdm.tavtors.eof then fdm.tavtors.delete else ShowMessage('Таблица пуста!'); Всё, с формой добавления авторов закончили. Однако имейте в виду, что если добавить двух авторов, то их порядковый номер будет 1 и 2. Затем, если удалить их, то следующий автор, несмотря на то, что он будет единственным, будет иметь порядковый номер 3, так как автоинкремент сохраняет последнюю цифру счётчика в таблице. С одной стороны, плохо, если авторы будут идти не по порядку. С другой стороны, представьте, что в будущем будет удалён автор под номером 4. Если сдвигать счётчик по порядку, то под номером 4 окажется другой автор. А поскольку эта таблица будет связана с таблицей книг, там окажется ошибка: книга предыдущего автора будет числиться под новым автором. Поэтому лучше, чтобы номера авторов шли не по порядку, чем сделать ошибку и вывести в таблице неверные данные. Так что будьте аккуратны при вводе данных в таблицу и старайтесь не удалять их. Теперь нужно приступить к редактированию базы данных библиотечного каталога. Для этого создайте новую форму. В свойстве Name укажите fbook, в свойстве Caption «Редактирование книги». В свойстве BorderStyle выберите bsdialog, сохраните форму, дав модулю имя Book. Подключите к ней модуль DM с помощью File Use Unit. 226
227 Расположите на форме обычную панель, в свойстве Align которой укажите altop, чтобы занять весь верх. Растяните её почти по всей форме, только чтобы внизу осталось место для двух кнопок. Свойство Caption очистите. Перейдите на вкладку Data Controls. С этой вкладки установите следующие компоненты, учитывая, что слева от них будет по одному Label с названием поля: 1 компонент DBLookupComboBox, 4 компонента DBEdit, 1 компонент DBMemo. Слева от всех компонентов, кроме DBMemo, разместите Label, и ещё один в левой части сверху от DBMemo. У самого DBMemo не забудьте в свойстве ScrollBar установить вертикальную прокрутку. В результате форма «Редактирование книги» должна выглядеть так, как показано на рисунке ниже (рис. 11.5). Рис Внешний вид формы «Редактирование книги» С помощью этой формы можно как добавлять книги, так и редактировать существующие. Поле "Key1" здесь не указано, так как это счётчик и пользователю не обязательно его видеть. 227
228 Выделите все компоненты работы с БД (все, кроме Label). Чтобы в них отображались данные выбранного поля, необходимо в свойстве DataSource указать DS1, который обеспечивает связь с таблицей книг. Требуется каждый компонент привязать к нужному полю, выбрав его в списке DataField. Для всех компонентов, кроме DBLookupComboBox, этого достаточно. Выделите DBLookupComboBox. В свойстве ListSource нужно указать тот DataSource, который управляет данными таблицы, из которой будут взяты подставляемые значения, это DS2. Этот компонент будет заполнен списком авторов в виде «Фамилия И.О.», а вместо него в основную таблицу подставлен номер этого автора поле "Key2". В свойстве ListField нужно указать поле, из которого будут взяты данные, поле "FIO". В свойстве KeyField нужно указать поле, значение которого будем подставлено в таблицу книг, это "Key2". Чтобы облегчить пользователю ввод даты, следует установить маску для ввода. Выделите поле с датой. В свойстве EditMask укажите маску для ввода даты. Обычно для дат рекомендуется устанавливать маску "99/99/9999". Эти данные желательно выводить на экран в красивом формате. Для этого служит свойство DisplayFormat. Для вывода полной даты напишите "dddddd". Маска для вывода здесь такая же, как у функции FormatDateTime. Чтобы полный формат даты помещался в сетке DBGrid, свойство DisplayWidth установите в 17 (оптимальный размер для полной даты). Сгенерируйте обработчик нажатия на кнопку «Сохранить». Там напишите следующий код: //Если изменения были, принимаем их: if fdm.tbooks.modified then fdm.tbooks.post; Close; Свойство таблицы Modified возвратит True, если данные таблицы были изменены. В этом случае методом Post принимаются эти изменения и физически записываются в таблицу. Для кнопки «Отмена» код будет другим: //Отменяем изменения: fdm.tbooks.cancel; Close; 228
229 Перейдите на главную форму и командой File Use Unit добавьте новый модуль Book. Сгенерируйте обработку команды меню «Редактирование Добавить книгу» и напишите следующий код: fdm.tbooks.append; fbook.showmodal; //добавляем запись в конец //вызываем редактор Таким образом, программа может добавлять новую запись в таблицу и редактировать её. Чтобы редактировать уже существующую запись, выделите DBGrid на главной форме и вызовите для него обработчик события OnDoubleClick, в котором необходимо вызвать редактор: fbook.showmodal; //вызываем редактор Сохраните проект, скомпилируйте его и посмотрите, как он работает. Перед тем, как продолжить работу с проектом, необходимо познакомиться с понятием индексы таблицы Индексы таблицы Первичный (основной) ключ программы служит для того, чтобы записи в таблице сортировались по возрастанию этого ключа. Если первичный ключ не указан, то данные будут выводиться в том порядке, в каком их ввёл пользователь. Однако бывает, когда необходимо отсортировать данные по одному или по другому полю. Для этого используют вторичные ключи индексы. Индексов может быть много, они могут содержать одно поле таблицы или несколько. Если указать в индексе только одно поле, то при применении индекса таблица будет отсортирована по этому полю. Если указать два индексных поля, то вначале сортировка будет произведена по первому, затем по второму полю. Впрочем, индексацию таблицы лучше изучать на примере. Если в данный момент открыта Delphi с программой CatBooks, закройте её. Поскольку нужно будет изменить структуру таблицы, а закрытие приложения не поможет, до закрытия Delphi таблица будет в использовании и изменить её структуру не получится. 229
230 Откройте утилиту Database Desktop и загрузите в неё таблицу "d:\data\books.db". Выберите команду Table Restructure. Откроется окно, в котором вводятся названия и типы полей таблицы. Перед тем, как установить индексы для полей, измените языковой драйвер таблицы. Если этого не сделать, то при использовании индексирования по текстовому полю, где содержится текст на русском языке, могут возникнуть проблемы. Для изменения языкового драйвера в поле "Table Properties" выберите Table Language, а в открывшемся окне Pdox ANSI Cyrillic. Чтобы индексы работали, необходимо иметь главный ключ, так что, если поле "Key1" не установлено как ключевое, сделайте это. Поле "Key2" во второй таблице тоже должно быть ключевым. Следует приступить к созданию индексов. Поскольку сортировка будет выполнена по полям «Автор» и «Название», то и индексов у нас должно быть два. В поле "Table Properties" выберите Secondary Indexes и нажмите кнопку "Define". Откроется окно, в левой части которого будут находиться все поля таблицы, а в правой индексные поля. Правая часть пока что пуста. Установите галочку "Maintained", без этого индексом можно пользоваться только в режиме чтения, при добавлении новой записи будет выходить ошибка. Установка этой галочки говорит о том, что в дальнейшем индексация данных будет автоматической. Выделите поле "Avtor" и кнопкой со стрелкой вправо скопируйте его в правую часть. Таким образом, это поле было указано как вторичный индекс. Нажмите на кнопку "OK". Выйдет окно с запросом имени индекса, укажите там "Sort_Avtor". Называть индексы теми же именами, что и таблица, не рекомендуется индексов может быть много, да и легче будет запутаться в одинаковых названиях. После этого, индекс Sort_Avtor должен появиться в окне ниже кнопки "Define". При выделении этого индекса станут доступными ещё две кнопки "Modify" и "Erase". Первой кнопкой можно изменить индекс, второй удалить его. Точно также сделайте другой индекс: нажмите кнопку "Define", скопируйте направо поле "Nazvanie" и сохраните индекс по имени "Sort_Nazvanie". Теперь в окне можно увидеть видеть два индекса. Сохраните таблицу и выйдите из утилиты. Посмотрите на каталог с базой данных он увеличился на 4 файла, по 2 файла на 230
231 каждый индекс. Именно поэтому базы данных в серьёзных приложениях бывают довольно большими и содержат сотни взаимосвязанных файлов. С модификацией таблицы закончено, откройте проект CatBooks. По команде меню «Сортировка По автору» напишите код: fdm.tbooks.indexname := 'Sort_Avtor'; Команда меню «Сортировка По названию книги» будет содержать следующий код: fdm.tbooks.indexname := 'Sort_Nazvanie'; Сохраните проект, скомпилируйте его и посмотрите, как он работает Подсчёт данных Проект CatBooks можно улучшить, подсчитав общее количество книг и их сумму. Для этого в модуле DM создайте переменную-закладку. Она необходима для того, чтобы после подсчёта возвращаться к записи, откуда вызвана процедура пересчёта. Переменная должна находится там же, где определены компоненты Table, потому что закладки описываются в этих модулях. Переменная должна быть глобальной: bm: TBookmarkStr; //закладка В главном модуле в разделе Private опишите процедуру подсчёта: procedure Itog; Саму процедуру подсчёта напишите в самом низу модуля: procedure TfMain.Itog; var all: Integer; summ: Real; begin //ставим закладку: DM.bm := fdm.tbooks.bookmark; //обнуляем переменные //для общ. кол-ва книг //для общ. суммы 231
232 all := 0; summ := 0; //перемещаемся от начала до конца и сохраняем результат: fdm.tbooks.first; while not fdm.tbooks.eof do begin all := all + fdm.tbooks['exemp']; summ := summ + fdm.tbooks['exemp'] * fdm.tbooks['cena']; fdm.tbooks.next; end; //while //снова переходим на закладку и убираем её: fdm.tbooks.bookmark := DM.bm; DM.bm := ''; //записываем данные: Label1.Caption := 'Всего книг: ' + IntToStr(all); Label2.Caption := 'На общую сумму: ' + FormatFloat('0,000.00', summ) + ' с.'; end; Сгенерируйте событие OnShow для главной формы и там вызовите процедуру: Itog; Также добавьте её вызов из команды меню «Редактирование Добавить книгу». Теперь при добавлении книги пересчёт будет правильный. Сохраните проект, скомпилируйте его и посмотрите, как он работает Подстановочные поля Иногда приходится добавлять поле, которого изначально в данной таблице не было. Возьмите главную форму проекта CatBooks там выходит поле с номером автора, но не с его фамилией, а пользователю номер вряд ли поможет. Значит, для его удобства нужно будет вставить поле, где бы вместо номера выходили фамилия, имя и отчество автора. Это поле нужно будет брать из таблицы авторов, подставлять нужные значения и выводить их так, будто изначально они были в первой таблице. Создание нового поля возможно только при неактивной таблице, поэтому закройте таблицу TBooks (свойство Active переведите в False). Щёлкните дважды по компоненту TBooks, который находится в модуле DM, чтобы открыть редактор полей. Щёлкните 232
233 по свободному месту редактора правой кнопкой и выберите команду New Field (Новое поле). Появится окно (рис. 11.6), где следует указать все необходимые атрибуты поля. Рис Окно создания нового поля таблицы В поле "Name" раздела "Field properties" нужно указать имя нового поля. Назовите его "Fio_Avtors". Далее, в поле "Type" нужно указать тип нового поля String (строковое поле для вывода фамилий авторов). В разделе "Field type" нужно выбрать тип поля, выберите Lookup, это значит, что данные будут взяты из другой таблицы. В поле "Key Fields" раздела "Lookup definition" нужно указать поле таблицы, откуда будут браться значения, поле "Avtor", где хранятся числа (номера авторов). В поле "Dataset" укажите таблицу, откуда будем взяты значения, таблица TAvtors. В поле "Lookup Keys" укажите начальное поле, которое хранит цифры (номера авторов), поле "Key2". В поле "Result Field" результативное поле, значения которого будут подставляться, поле "FIO". Нажмите кнопку "ОК". В редакторе полей перетащите новое поле поближе к полю "Avtor", в начало таблицы. Переведите таблицу в активное состояние. В проекте видны недостатки: заголовок подстановочного поля выходит латинскими буквами и поле "Avtor", содержащее цифры, уже не нужно. Снова вызовите редактор полей. Поле "Avtor" сделайте невидимым (Visible в False). У поля "Fio_Avtors" в 233
234 свойстве DisplayLabel укажите «Авторы». Теперь данные в сетке выходят в удобочитаемом виде. Новое поле никак не влияет на таблицы. Оно нужно только для вывода данных в нормальном виде. Сохраните проект, скомпилируйте его и посмотрите, как он работает. Для поиска нужной записи в таблице необходимо использовать фильтрацию данных Фильтрация данных Фильтрация данных предназначена для облегчения и ускорения поиска необходимой записи. Если в базе около записей, то найти нужную запись несложно путём обычного перебора записей, с первой по последнюю, либо пока не будет найдена нужная запись. А вот если записей миллион, то время поиска заметно увеличится, и тут придёт на помощь фильтрация. Когда применяется фильтр, на экран выходят только те данные, которые удовлетворяют условиям фильтра. В программе CatBooks удобней всего делать фильтр по названию книги, так как подстановочное поле использовать в фильтре нельзя, а авторы числятся как целые числа. На экран выйдут только те книги, название которых удовлетворяет условиям поиска, а среди них найти нужную книгу будет несложно. На панель главной формы установите один компонент Label, на котором напишите «Найти книгу:», и один Edit, у которого удалите текст, сделайте его немного длинней. Когда будет вводиться название книги в поле ввода Edit, на сетке будут отображаться только отфильтрованные данные. Для компонента Edit создайте событие OnChange, где напишите такой код: //если пусто не фильтруем. if Length(Edit1.Text) = 0 then begin fdm.tbooks.filtered := False; Exit; end //if else fdm.tbooks.filtered := True; //индексируем по названию, чтобы они были по алфавиту fdm.tbooks.indexname := 'Sort_Nazvanie'; //устанавливаем фильтр: fdm.tbooks.filter := 'Nazvanie >= ' + QuotedStr(Edit1.Text); 234
235 Вначале проверяется, есть ли текст. Если нет, то фильтр убирается и осуществляется выход из процедуры. Если же текст есть, то указывается, что таблица должна фильтроваться. Чтобы быстрей найти нужную книгу из списка отфильтрованных записей, делается сортировка по названию книги. Наконец, устанавливается сам фильтр. Функция QuotedStr() возвращает текст, заключённый в кавычки. Можно было бы кавычки установить самостоятельно, однако здесь есть некоторые сложности: приходится считать, сколько же кавычек нужно ставить. Дело в том, что условие фильтрации требует такой записи: Table1.Filter := 'значение'; А само значение может быть в таком виде: Nazvanie >= 'Значение' т.е. внутри строки будет ещё одна строка, которую необходимо поставить в одинарные кавычки. Чтобы внутри текста указать одинарную кавычку, её нужно написать дважды, так что строка превратиться в такой вид: fdm.tbooks.filter := 'Nazvanie >= '''+ Edit1.Text+''''; Чтобы избежать таких сложностей подсчитывания кавычек, и существует функция QuotedStr(). Сохраните проект, скомпилируйте его и посмотрите, как он работает Отчётность Для создания отчётности существует много способов. Наиболее распространённый, но и наиболее сложный вывод отчёта с помощью компонентов отчётности, таких как Quick Report. Другой профессиональный способ вывод отчёта в файл MS Excel или MS Word. Самый простой способ вывод отчёта в компонент Memo и сохранение его в файл. Для создания отчётности в программе CatBooks удобнее всего использовать последний способ. Создайте новую форму. В свойстве Name укажите fotchet, в свойстве Caption «Отчёт», а саму форму сохраните в модуле Otchet. 235
236 В верхнюю часть формы установите Memo и растяните его по всему верху (свойство Align установите в altop). В нижней части формы поместите кнопку и компонент SaveDialog. На кнопке напишите «Сохранить в файл». У Memo уберите весь текст и установите вертикальную прокрутку. Не забудьте добавить к этой форме модуль DM с помощью команды File Uses Unit, а в главной форме той же командой добавьте новое окно Otchet. По нажатию кнопки напишите такой код: if SaveDialog1.Execute then Memo1.Lines.SaveToFile(SaveDialog1.FileName); Теперь необходимо написать код, по которому Memo будет заполняться данными. Это лучше всего сделать по событию главной формы OnShow: var s: String; begin //очищаем Memo: Memo1.Lines.Clear; //устанавливаем закладку: bm := fdm.tbooks.bookmark; //готовим таблицу: fdm.tbooks.first; fdm.tbooks.filtered := False; //делаем отпервой до последней записи: while not fdm.tbooks.eof do begin //собираем данные в переменную s: s := 'Автор: '+ GetFIO(fDM.TBooks['Avtor']) + #13 + #10; s := s + 'Название: '+ fdm.tbooks['nazvanie'] + #13 +#10; s := s + 'Экземпляров: '+ IntToStr(fDM.TBooks['Exemp']) + #13 + #10; s := s + 'Стоимость: '+ FloatToStr(fDM.TBooks['Cena']) + # 13 + #10; s := s + 'Дата поставки: '+DateToStr(fDM.TBooks['Date']) + #13 + #10; s := s + ' ' + #13 + #10; fdm.tbooks.next; Memo1.Lines.Add(s); end; //while //переходим на закладку: fdm.tbooks.bookmark := bm; bm := ''; В коде указана функция GetFIO(), которой пока нет. Эта функция будет принимать номер автора и возвращать его 236
237 фамилию, имя и отчество. Создайте эту функцию выше процедуры OnShow: function GetFIO(i: Integer): String; begin Result := ''; fdm.tavtors.first; while not fdm.tavtors.eof do begin if fdm.tavtors['key2'] = i then begin Result := fdm.tavtors['fio']; Break; end; //if fdm.tavtors.next; end; //while if Length(Result) = 0 then Result := 'Автор не найден'; end; Сохраните проект, скомпилируйте его и посмотрите, как он работает. В этом проекте были использованы почти все приёмы работы с таблицами, хотя приложение, конечно, получилось не совсем профессиональным. Например, если удалить автора из таблицы авторов, то разрушится целостность таблиц: книга будет указывать автора, которого нет. В профессиональных проектах всё это учитывается, однако на это уходит гораздо больше времени и пишется гораздо больше кода. КОНТРОЛЬНЫЕ ВОПРОСЫ 1. Что называется базой данных? 2. Какие механизмы для работы с базами данных существуют в Delphi? 3. Перечислите основные понятия баз данных. 4. Перечислите типы баз данных. 5. Для чего предназначен компонент DBGrid? 6. Для чего предназначен компонент Table? 7. Для чего предназначен компонент DataSource? 8. В чём отличие работы с базой данных от работы с файлами? 9. Какие методы существуют в Delphi для перемещения по записям таблицы? 237
238 10. Для чего используются типы данных Memo и Graphic? 11. Для чего используется модуль DataModule? 12. Для чего в базах данных используют закладки? 13. Как циклически обработать таблицу? 14. Как программно открыть и закрыть таблицу? 15. Для чего предназначены свойства RecordCount и RecNo таблицы? 16. Для чего предназначена утилита Database Desktop? 17. Перечислите типы данных таблиц Paradox Как изменить заголовки полей в созданной таблице? 19. Перечислите методы редактирования данных таблицы. 20. Какие действия необходимо выполнить для редактирования одного или нескольких полей записи? 21. Для чего нужно изменять языковой драйвер таблицы? 22. Как можно изменить языковой драйвер таблицы? 23. Какое поле называется подстановочным? 24. Как создать новое поле в существующей таблице? 25. Для чего предназначена фильтрация данных? 26. Для чего предназначена функция QuotedStr()? 27. Перечислите способы создания отчётности. ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ 1. Создайте новый проект. 2. Создайте новую базу данных «Студенческий журнал». 3. Добавьте в базу данных таблицы «Данные о студентах», «Аттестация», «Посещение». 4. Создайте для этих таблиц поля в соответствии с академическим журналом группы. 5. Заполните таблицы данными. 6. Для каждой таблицы задайте методы редактирования. 7. Для каждой таблицы создайте подсчёт данных. 8. Для каждой таблицы определите индексы и возможные способы фильтрации данных. 9. Для каждой таблицы создайте отчёты. 10. Сохраните проект как MyStudentJournal, скомпилируйте его и посмотрите, как он работает. 238
239 ПРАКТИЧЕСКАЯ РАБОТА 12. МЕХАНИЗМ DRAG-AND-DROP Цель изучить возможности механизма Drag-and-Drop, а также научиться применять его при написании приложений в Delphi. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ Интерфейс переноса и приёма компонентов Drag-and-Drop появился достаточно давно. Он обеспечивает взаимодействие двух элементов управления во время выполнения приложения. При этом могут выполняться любые необходимые операции. Для того, чтобы механизм Drag-and-Drop заработал, требуется настроить соответствующим образом два элемента управления. Один должен быть источником (Source), второй приемником (Target). При этом источник никуда не перемещается, а только регистрируется в качестве такового в механизме. Бывает, что один элемент управления может быть одновременно источником и приемником. Самым простым примером использования интерфейса переноса и приёма компонентов является следующий: пользователь помещает указатель мыши на нужный элемент управления, нажимает левую кнопку мыши и, не отпуская её, начинает перемещать курсор ко второму элементу. При достижении этого элемента пользователь отпускает кнопку мыши. В этот момент выполняются предусмотренные разработчиком действия. При этом первый элемент управления является источником, а второй приемником. После выполнения настройки механизм включается и реагирует на перетаскивание мышью компонента-источника в приемник. Группа методов-обработчиков обеспечивает контроль всего процесса и служит для хранения исходного кода, который разработчик сочтёт нужным связать с перетаскиванием. Это может быть передача текста, значений свойств (из одного редактора в другой можно передать настройки интерфейса, шрифта и сам текст), перенос файлов и изображений, простое перемещение элемента управления с места на место и т.д. 239
240 Пример реализации Drag-and-Drop в Windows возможность переноса файлов и папок между дисками и папками. Можно придумать множество областей применения механизма Drag-and-Drop. Его универсальность объясняется тем, что это всего лишь средство связывания двух компонентов при помощи указателя мыши, а конкретное наполнение зависит только от фантазии программиста и поставленных задач. Весь механизм Drag-and-Drop реализован в базовом классе TControl, который является предком всех элементов управления. Любой элемент управления из Палитры компонентов Delphi является источником в механизме Drag-and-Drop. Его поведение на начальном этапе переноса зависит от значения следующего свойства: type TDragMode = (dmmanual, dmautomatic); property DragMode: TDragMode; Значение dmautomatic обеспечивает автоматическую реакцию компонента на нажатие левой кнопки мыши и начало перетаскивания. При этом механизм включается самостоятельно. Значение dmmanual (установлено по умолчанию) требует от разработчика обеспечить включение механизма вручную. Этот режим используется в том случае, если компонент должен реагировать на нажатие левой кнопки мыши как-то иначе. Для инициализации переноса используется следующий метод: procedure BeginDrag(Immediate: Boolean; Threshold: Integer = 1); Параметр Immediate при значении True обеспечивает немедленный старт механизма. При значении False механизм включается только при перемещении курсора на расстояние, определённое параметром Threshold. О включении механизма сигнализирует указатель мыши он изменяется на курсор, определённый в свойстве: property DragCursor: TCursor; Источник при перемещении курсора не изменяет собственного положения и только в случае успешного завершения переноса сможет взаимодействовать с приемником. 240
241 Приемником может стать любой компонент, в котором создан следующий метод-обработчик: procedure DragOver(Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); Он вызывается при перемещении курсора в режиме Dragand-Drop над этим компонентом. В методе-обработчике можно предусмотреть выбор источников переноса по нужным атрибутам. Если параметр Accept получает значение True, то данный компонент становится приемником. Источник переноса определяется параметром Source. Через этот параметр разработчик получает доступ к свойствам и методам источника. Текущее положение курсора задают параметры X и Y. Параметр State возвращает информацию о характере движения мыши: type TDragState = (dsdragenter, dsdragleave, dsdragmove); где dsdragenter указатель появился над компонентом; dsdragleave указатель покинул компонент; dsdragmove указатель перемещается по компоненту. Приемник должен предусматривать выполнение некоторых действий в случае, если источник завершит перенос именно на нём. Для этого используется метод-обработчик type TDragDropEvent = procedure (Sender, Source: TObject; X, Y: Integer) of object; property OnDragDrop: TDragDropEvent; который вызывается при отпускании левой кнопки мыши на компоненте-приемнике. Доступ к источнику и приемнику обеспечивают параметры Source и Sender соответственно. Координаты мыши возвращают параметры X и Y. При завершении переноса элемент управления (источник) получает соответствующее сообщение, которое обрабатывается методом type TEndDragEvent = procedure(sender, Target: TObject; X, Y: Integer) of object; property OnEndDrag: TEndDragEvent; 241
242 Источник и приемник определяются параметрами Sender и Target соответственно. Координаты мыши определяются параметрами X и Y. Для программной остановки переноса можно использовать метод EndDrag источника (при обычном завершении операции пользователем он не используется): procedure EndDrag(Drop: Boolean); Параметр Drop при значении True завершает перенос. Значение False прерывает перенос. ПРАКТИЧЕСКАЯ ЧАСТЬ Создайте программу, в которой на основе механизма Dragand-Drop реализованы передача текста между текстовыми редакторами и перемещение панелей по форме. Создайте новый проект. В свойстве формы Caption напишите "DemoDrag&Drop". Расположите на форме по 2 экземпляра компонентов Panel, Label и Edit (рис. 12.1). Рис Вид формы программы "DemoDrag&Drop" 242
244 Для однострочного редактора Edit1 определены методыобработчики источника. В методе Edit1MouseDown обрабатывается нажатие левой кнопки мыши и включается механизм переноса. Так как свойство DragMode для Edit1 имеет значение dmmanual, то компонент обеспечивает получение фокуса и редактирование текста. Метод Edit1EndDrag обеспечивает отображение информации о выполнении переноса в источнике. Для компонента Edit2 определены методы-обработчики приемника. Метод Edit2DragOver проверяет класс источника и разрешает или запрещает приём. Метод Edit2DragDrop осуществляет перенос текста из источника в приемник. Обратите внимание, что оба компонента Edit одновременно являются источниками и приемниками. Для этого каждый из них использует методы-обработчики другого, а исходный код методов настроен на обработку владельца как экземпляра класса TEdit. Форма, как приемник Drag-and-Drop, обеспечивает перемещение панели Panel2, которая выступает в роли источника. Метод FormDragOver запрещает приём любых компонентов, кроме панелей. Метод FormDragDrop осуществляет перемещение компонента. Панель не имеет своих методов-обработчиков, так как работает в режиме dmautomatic и не нуждается в дополнительной обработке завершения переноса. Сохраните проект как MyDragAndDrop, скомпилируйте его и посмотрите, как он работает. КОНТРОЛЬНЫЕ ВОПРОСЫ 1. Что обеспечивает интерфейс Drag-and-Drop? 2. Что необходимо сделать, чтобы механизм Drag-and-Drop заработал? 3. В каком классе реализован механизм Drag-and-Drop? 4. От значений какого свойства зависит поведение компонентов Delphi? 5. Какой метод используется для инициализации переноса? 6. Как определить, является ли компонент приемником? 7. Какой метод обрабатывает сообщение о завершении переноса? 8. Какой метод используется для программной остановки переноса? 244
245 ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ 1. Откройте проект MyDragAndDrop. 2. Измените программу так, чтобы панель Panel2 можно было перемещать на область панели Panel1. 3. Сохраните проект, скомпилируйте его и посмотрите, как он работает. 4. Измените программу так, чтобы однострочный редактор Edit1 стал приемником, а Edit2 источником. 5. Измените программу так, чтобы оба компонента Edit были одновременно и источниками, и приемниками. 6. Сохраните проект, скомпилируйте его и посмотрите, как он работает. 7. Измените программу так, чтобы оба компонента Panel были одновременно и источниками, и приемниками. 8. Сохраните проект, скомпилируйте его и посмотрите, как он работает.
246 ПРАКТИЧЕСКАЯ РАБОТА 13. АВТОМАТИЗАЦИЯ ACTIVEX Цель изучить основы программирования в Windows с помощью средств ActiveX-автоматизации при написании приложений в Delphi. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ В настоящее время в технологиях создания программного обеспечения видна явная тенденция к переходу на объектноориентированные принципы разработки. Эти принципы позволяют в большинстве случаев существенно упростить написание и отладку программ, хотя этап проектирования может несколько усложниться. Основные принципы объектно-ориентированной разработки предписывают рассматривать всю анализируемую предметную область как совокупность объектов, которые могут взаимодействовать между собой. Каждый объект имеет некоторый набор методов (процедур и функций) и свойств, через которые и можно взаимодействовать с этим объектом. Современные версии операционных систем от Microsoft уже являются объектно-ориентированными. Для взаимодействия между объектами фирма Microsoft разработала целый ряд стандартов, объединённых между собой под единой маркой ActiveX. Один из таких стандартов, называемый автоматизацией, позволяет одним программам (серверам автоматизации) определять список своих объектов, их свойств и методов, которые становятся доступными для внешнего использования, а другим (клиентам автоматизации) обращаться к этим объектам. ПРАКТИЧЕСКАЯ ЧАСТЬ В данной работе на примере программы Microsoft Excel будет показано, как можно из программы в Delphi запустить Excel, создать в нём новую книгу и заполнить её данными. В создаваемом приложении должна быть обеспечена возможность выбора таблицы базы данных, её просмотр в виде таблицы, а также 246
247 команда создания отчёта по данной таблице с использованием ActiveX-автоматизации. Приложение «Генератор отчётов в Microsoft Excel» будет состоять всего из одной формы, на которой необходимо разместить компоненты для выбора базы данных и таблицы, компоненты для просмотра и навигации по таблице, а также кнопки для выбора таблицы базы данных, выхода из программы и создания отчёта. Внешний вид формы приведён ниже (рис. 13.1). Рис Вид формы приложения «Генератор отчётов в Microsoft Excel» Для обращения к таблицам баз данных необходимо поместить на форму компонент Table типа ТТаblе. Значения её свойств DatabaseName, TableName и Active должны изменяться в программе в зависимости от выбора пользователя. На форму необходимо поместить компонент DataSource типа TDataSource и связать его с компонентом Table. На форму необходимо поместить также компоненты типов TDBGrid и TDBNavigator. Значение их свойств DataSource 247
248 необходимо установить равным ранее размещённому компоненту DataSource. Для того, чтобы пользователь в программе мог выбирать имена баз данных и таблиц, необходимо разместить два списка ComboBox. Список элементов первого ComboBox для выбора имени базы данных необходимо заполнить в обработчике события формы OnCreate. Для этого необходимо вызвать метод Session.GetDatabaseNames. Список элементов второго ComboBox для выбора имени таблицы необходимо формировать в ответ на изменение имени базы данных с помощью вызова метода Session.GetTableNames. При изменении имени базы данных или таблицы необходимо заново попытаться открыть таблицу или выдать сообщение об ошибке открытия. Для упрощения выбора таблицы, размещённой локально в некотором каталоге и имеющей формат DBase, FoxPro или Paradox, можно разместить на форме кнопку и диалог выбора файла. При нажатии кнопки нужно выдать диалоговое окно для выбора файла с таблицей и по его завершении занести в списки с именами базы данных и таблицы соответственно значения пути к файлу и его имя. После выполнения указанных шагов пользователь сможет выбирать произвольные таблицы и просматривать их содержимое. Перейдите к шагу создания отчёта. Для программирования с использованием автоматизации вначале необходимо установить связь с программой-сервером автоматизации и связаться с каким-то из его объектов, например, с книгой Microsoft Excel или с документом Microsoft Word. В Delphi это делается с помощью функции GetActiveOleObject для подключения к объекту уже запущенного сервера либо с помощью CreateOleObject для запуска приложения-сервера автоматизации и подключения к его объектам. Эти функции определены в модуле ComObj, поэтому перед их использованием этот модуль необходимо указать в разделе uses. Аргументом вызова этих функций является имя создаваемого объекта сервера автоматизации, например, "Excel.Application" для получения доступа к Microsoft Excel в целом либо "Word.Document" для создания в памяти временного текстового документа Microsoft Word. Если при подключении к серверу произошла ошибка, например, приложение-сервер не запущено в случае вызова 248
249 функции GetActiveOleObject либо приложение-сервер вообще не установлено на компьютере, то возникает исключение, которое необходимо в программе соответствующим образом обработать. Результатом вызова указанных функций соединения с сервером является специальный так называемый «диспетчеризуемый» интерфейс запрошенного объекта. Такие интерфейсы-диспетчеры позволяют вызвать процедуры, функции, обращаться к массивам и свойствам объектов сервера. В Delphi для упрощения доступа к объектам автоматизации такие интерфейсы лучше всего запоминать в переменных типа Variant, который позволяет хранить в себе данные разнообразных типов, в том числе и диспетчеризуемые интерфейсы объектов автоматизации. В программе «Генератор отчётов в Microsoft Excel» результат вызова функции обращения к объекту "Excel.Application" необходимо запомнить, например, в переменной Excel, объявленной как имеющей тип Variant. Детальное описание всех имеющихся методов и свойств серверов автоматизации обычно имеется в соответствующих файлах справки. Отключение от программы-сервера автоматизации производится в Delphi автоматически в тот момент, когда в переменных вариантного типа не останется интерфейсов к объектам сервера. Например, если все вариантные переменные объявлены внутри процедуры, то при выходе из процедуры эти переменные автоматически обнулятся и соединение с сервером автоматически разорвётся. Другой вариант заключается в принудительном присвоении вариантной переменной любого другого значения, например, специального значения UnAssigned. После подключения к Microsoft Excel необходимо создать новую пустую книгу с помощью вызова Excel.WorkBooks.Add. В данном случае выражение Excel.WorkBooks позволяет обратиться к списку всех открытых книг Excel, а его функция Add создаёт новую пустую книгу и возвращает её диспетчеризуемый интерфейс. Результат этого вызова можно запомнить в переменной WorkBook также типа Variant. Рабочие книги Excel состоят из листов. Новая книга обычно содержит 3 листа. Для того, чтобы передать содержимое таблицы базы данных на первый лист, необходимо обратиться к массиву листов созданной книги и запомнить первый лист в переменной Sheet с помощью вызова WorkBook.Sheets[1]. Обратите внимание, что при компиляции программы Delphi не может обеспечить проверку правильности обращения к 249
250 объектам автоматизации. Это возможно сделать только на этапе непосредственно обращения к этим объектам. Поэтому обращение к объектам автоматизации через переменные типа Variant выполняется по сути в режиме интерпретации, т.е. работает относительно медленно. Для обращения к ячейкам листа Excel необходимо использовать двумерный массив-свойство объекта-листа с помощью выражения Sheet.Cells[Row, Col], где Row и Col задают соответственно номера столбца и строки внутри листа (нумерация ведётся, начиная с 1). Далее необходимо все поля всех записей ранее выбранной таблицы базы данных скопировать в лист Excel. По окончании формирования содержимого листа необходимо сделать текущей рабочую книгу в Excel, а сам Microsoft Excel вывести на передний план. В заключение обратите внимание, что возможно обращение к программе-серверу автоматизации, размещённой на другом компьютере в сети, при этом в созданной программе практически ничего менять не надо, кроме процедуры подключения к серверу, где дополнительно надо указать имя компьютера. Ниже (рис. 13.2) приведён внешний вид запущенного приложения после того, как окно было несколько растянуто. Рис Вид запущенного приложения «Генератор отчётов в Microsoft Excel» 250
251 Ниже (рис. 13.3) приведён внешний вид сгенерированного с помощью Microsoft Excel отчёта по таблице базы данных. Рис Сгенерированный с помощью Microsoft Excel отчёт по таблице Ниже представлен листинг программы, который нужно написать в редакторе кода. Сохраните проект как MyActiveXExcel, а модуль программы как MainUnit. unit MainUnit; interface uses Windows, Messages, SysUtiis, Classes, Graphics, Controls, Forms, Dialogs, OleCtnrs, StdCtrls, Grids, DBGrids, Db, DBTables, ExtCtrls, DBCtrls; type TMainForm = class(tform) ComboBoxDatabase: TComboBox; LabelBoxDatabase: TLabel; ComboBoxTable: TComboBox; LabelBoxTable: TLabel; DBGrid: TDBGrid; DataSource: TDataSource; Table: TTable; ButtonGenerate: TButton; 251