Самоучитель 14 по освоению Delphi8for.NET
Программирование в среде
D e l p h i
8
for
. N E T
Прочитав эту книгу, вы научитесь: • Программировать в среде Delphi на языке Pascal и Object Pascal • Создавать в своих приложениях графический интерфейс пользователя • Применять компоненты Delphi и использовать функциональные возможности системы Windows • Использовать встроенные средства среды разработки Delphi по отладке программ • Разрабатывать простейшие базы данных
ДЦДЛЕКШиКА
Г.В. Галисеев
рограммирование в среде D e l p h i 8
f o r
J E T
Г.В. Галисеев
удпЕктукд Москва • Санкт-Петербург • Киев 2004
ББК 32.973.26-018.2.75 Г15 УДК 681.3.07
Компьютерное издательство "Диалектика" Зав. редакцией А. В. Слепцов
По общим вопросам обращайтесь в издательство "Диалектика" по адресу:
[email protected], http://www.dialektika.com
Галисеев, Г.В. Г15
Программирование в среде Delphi 8 for .NET. Самоучитель. : — М.: Издательский дом "Вильяме", 2004. — 304 с.: ил. ISBN 5-8459-0644-Х (рус.) Книга представляет собой самоучитель начального уровня и посвящена набору инструментальных средств программирования Delphi 8. Если вы уже знакомы с Delphi, то помимо нового материала, в этой книге вы найдете много привычных понятий и компонентов, а если еще не знакомы, то прочитав книгу, получите начальные знания и сможете программировать в этой среде. Delphi 8 рассчитана на работу с платформой .NET, тем не менее, в ней можно использовать как привычные для среды программирования Delphi библиотеки VCL, так и библиотеки .NET. Для тех, кто еще не знаком с этой новой технологией, в самоучитель включена отдельная глава, посвященная общим принципам построения платформы .NET. Книга рассчитана на читателей, совсем незнакомых с языком программирования Delphi, но, тем не менее, имеющих общее представление о компьютерах, программировании и умеющих работать в среде Windows. ББК 32.973.26-018.2.75
Все названия программных продуктов являются зарегистрированными торговыми марками соответствующих фирм. Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или механические, включая фотокопирование и запись на магнитный носитель, если на это нет письменного разрешения издательства "Диалектика". Copyright © 2004 by Dialektika Computer Publishing. All rights reserved including the right of reproduction in whole or in part in any form.
Оглавление Введение
12
Глава 1. Знакомство со средой программирования
15
Глава 2. Знакомство с платформой .NET
38
Глава 3. Язык программирования Delphi
55
Глава 4. Разработка программ с помощью языка Delphi
97
Глава 5. Объекты в Delphi
123
Глава 6. Базовые классы языка Delphi
138
Глава 7. Продолжаем изучать графический интерфейс
155
Глава 8. Программирование с использованием графического интерфейса пользователя
188
Глава 9. Отладка программ
215
Глава Что еще надо знатьЮ.
236
Глава 11. Справочный раздел
259
Приложение. Ответы на контрольные вопросы
295
Предметный указатель
299
Содержание Введение
12
Глава 1. Знакомство со средой программирования
15
Среда программирования Рабочий стол Панели меню Окна рабочего стола
15 17 18 20
Компоненты
26
Стандартные компоненты
26
Первая программа
32
Подсказки в Delphi Справочная система Подсказки редактора кодов
3S 35 35
Примеры программ без написания кода
35
Резюме
37
Контрольные вопросы
37
Глава 2. Знакомство с платформой .NET
38
Модель СОМ
38
Решения .NET Строительные блоки .NET Языки программирования .NET Двоичные файлы .NET Промежуточный язык Типы и пространства имен .NET Общеязыковая исполняющая среда Стандартная система типов CTS Основы CLS
40 40 41 41 42 44 45 45 49
Пространства имен Важнейшие пространства имен .NET Пространства имен в Delphi Как получить информацию
49 50 51 53
Резюме
54
Контрольные вопросы
54
Глава 3. Язык программирования Delphi
55
Ключевые слова
56
Консольные приложения
57
Ключевые слова языка Delphi
60
Типы данных Простые типы Булев тип Символьный тип Перечислимый тип Вещественный тип Строковый тип Структурные типы Процедурные типы Вариантные типы
64 64 65 65 66 66 67 68 76 77
Операторы Простые операторы Структурные операторы
77 77 82
Ввод-вывод Файлы и файловые переменные Типизированный файл Текстовые файлы
87 88 91 93
Резюме Контрольные вопросы
'V,:',1 :
.•"-•;•'••-
-г: -•;•-.•
95 96
Глава 4. Разработка программ с помощью языка Delphi
97
Процедуры и функции Директивы для процедур и функций Параметры процедур и функций ,. Использование типизированных констднт
97 99 100 102
Программы и модули Программы Модули Объявления в модуле Область видимости
!
:
Простые программы •i . Профамма получения кодов клавиатуры и динамичеекие массивы Профамма шифрования
103 103 105 106 107 109 109 116
Резюме
122
Контрольные вопросы
122
Содержание
Глава 5. Объекты в Delphi
123
Основы объектно-ориентированного программирования
123
Классы Поля Области видимости Методы Свойства
124 125 125 126 130
Объекты Объявление и создание объекта
131 131
Уничтожение объекта
132
Дружественные классы
132
Интерфейсы Определение интерфейса Реализация интерфейса Наследование интерфейсов Директива implements Использование интерфейсов Резюме
133 133 134 135 135 136 137
Контрольные вопросы
137
Глава 6. Базовые классы языка Delphi
138
Класс System.Object
138
Переопределение методов класса TObject
141
Класс TControl
144
Класс TWinControl
150
Класс TForm
151
Резюме
154
Контрольные вопросы
154
Глава 7. Продолжаем изучать графический интерфейс
155
Стандартные компоненты Компонент TLabel Компонент TButton Компонент TEdit Компонент ТМето Диалоговые компоненты Компонент TCheckBox Компонент TRadioButton Компонент TRadioGroup Компонент TListBox
155 156 157 158 160 161 164 165 165 167
8
Содержание
Компонент TCheckListBox Компонент TComboBox Компонент TScrollBar Компонент TGroupBox Компонент TPanel Компонент TActionList Компонент TFrame Компонент TMainMenu Компонент TPopupMenu Компонент Tlmage Компонент TShape Компонент TScrollBox Компонент TStaticText Компонент Т Timer Компонент TPaintBox Компоненты TDriveComboBox, TDirectoryListBox, TFilterComboBox и TFile List Box
172 172 174 175 176 176 178 180 180 180 182 182 183 183 184 185
Резюме
186
Контрольные вопросы
186
Глава 8. Программирование с использованием графического интерфейса пользователя
188
Структурный стиль программирования
188
Объектно-ориентированный стиль
189
Реализация принципов ООП в Delphi
191
Повышение скорости работы
194
Разделение графической и логической составляющих программы
195
Сообщения Что такое сообщение Типы сообщений Система сообщений Delphi Специализированные записи Обработка сообщений Событие OnMessage класса TApplication Использование собственных типов сообщений Уведомляющие сообщения Использование сообщений внутри приложения Широковещательные сообщения
202 203 203 204 205 205 207 207 208 210 211
Программа шифрования
211
Резюме
214
Контрольные вопросы
214
Содержание
9
Глава 9. Отладка программ
215
Причины ошибок Синтаксические ошибки Логические ошибки
215 216 216
Защита от ошибок Директива Assert Обработчики исключений Что такое отладка Выполнение по шагам Просмотр значений Просмотр и изменение значений Просмотр и анализ кода Приемы отладки
218 219 220 225 226 228 230 м 233
г либо щелкни•ruri-| arentBiDiMode .Realizable те на пиктограмме View Form на панели View, что будет bdLeftToRight '*S a .SOiMode удобнее всего. В окне View Form (Просмотр форм) выдеbi5ystemNtenu,t*0inii . :л ш;3orderlcons 3orderWidth '• J лите нужную форму и щелкните на кнопке ОК. Если в окCaption ClientHeight 293 не View Form нет ни одной формы, значит в текущем проClientWidth 426 , -| екте форм нет. В этом случае при необходимости можно (TSizeConstrants) * "M н Constraints (TFont) a Font добавить в проект новую форму, выбрав команду главного 170 Height 1 1 HelpContext меню File^New^VCL Form (Файл *Новая * VCL-форма ). о I if HeipFile В среде отладки Delphi используется объектно-ориен(None) -*JI Icon LeFt тированный язык Delphi, представляющий собой расшиa . ' Щ ParentHDiMode rrue -^| рение стандартного Pascal. Все используемые в Delphi : ParentFont a !se •?S ShowHint ~ahz '" »*| компоненты являются объектами. С каждым объектом асTop 8 ад •434 ,-; Width социирован набор его свойств. Например, форма является a AlphaSdend False объектом с такими свойствами (Properties), как Name 255 Alphaflendvakie (Имя), Caption (Заголовок), Height (Высота), Width (Ширина), DefauRMonitor ijrrActiveFEirm ffi 1 FormStyle fsrwrmal Color (Цвет) и множеством других свойств, большинство HoreScrolBar (TControlScrdlBar) •% I El Nome Forml из которых отражены в окне инспектора объектов. При PlxelsPerlnch % создании новой формы Delphi автоматически присваивает PopuprAade pftiNone Position poCefaiAPosOnly jK ^ каждому свойству значение по умолчанию. При разработPrintScale poP/oportional I Scrcen5nap False ке программы свойства компонентов можно изменять SnapBuiffer 1С :;; • с помощью диалогового окна Object Inspector. Для этого H Tag В VertScroBar (TControBcrotteai-) '.Ш k нужно всего лишь щелкнуть на нужном свойстве кнопкой WindortState (•sWornMt ~^ H Visual мыши и заменить его текущее значение. На рис. 1.5 покаaNone Align AutoStoe False * | зан вид окна Object Inspector для формы Forml. Если окна [bi5y«emMerrtj,biMinii' Ж и Borderlcons BorderStyte bsSi2eable инспектора объектов на экране не видно, то для его отоBorderWidth 0 '' •• % бражения выберите команду View*Object Inspector или Caption Forml j j p ;АИ shown •. • . • нажмите клавишу . ~ 7 ~ .
- . . 3 - ;
D
••
• 1
:
0
:
;
(
;
Рис. 1.5. Окно Object Inspector Класс — это законченный фрагмент программы, в котором объединены поля, определяющие состояние объекта, и методы, определяющие функциональные возможности объекта. Объекты — это экземпляры класса. Образно говоря, класс является шаблоном, или прототипом объекта. На основе одного класса можно создавать сколь угодно много объектов. Свойства — это управляющие структуры, с помощью которых происходит доступ к полям объекта. Таким образом, изменять состояние объекта можно с помощью свойств.
Редактор кода Редактор кода представляет собой полнофункциональный текстовый редактор, с омощью которого можно просматривать и редактировать исходный код программы. Кроме того, редактор кода содержит многочисленные средства, облегчающие создание исходного кода на языке Delphi. И хотя редактировать исходный код можно в любом текстовом редакторе, делать это в редакторе кода значительно удобнее, так как встроенные возможности облегчают написание кода, а многочисленные подсказки помогают избежать ошибок. В главном окне каждый модуль отображается на отдельной странице, где на соответствующей вкладке находится редактор кода. Чтобы открыть модуль, выберите команду View^Units... (Просмотр^Модули...) или нажмите клавиши 22
Глава 1. Знакомство со средой программ
, а удобнее всего просто щелкнуть на пиктограмме модуля, расположенной на панели View. Затем в окне View Unit (Просмотр модуля) выделите имя нужного модуля и щелкните на кнопке ОК. На рис. 1.6 показана вкладка с редактором кодов.
;^unit Unitu -3 interface ; use» 1 Uindovis, Messages, SysUtils, Variants, Classes, Graphics, i Dialogs; TForrol • private class (TFocmJ < Prjr
•1 ч TJ"'!
;-. in
!
( Public declare:.i.or.5 end; j var L rorml: Trocal; | S inclement at ion L.
I'. | !$P '.nffittj : end.
:i
H -713
Рис. 1.6. Редактор кода и исследователь кода Совместно с редактором кода удобно использовать окно Exploring, которое можно открыть с помощью команды главного меню View^Code Explorer (Просмотр^ Исследователь кода). С помощью исследователя кода программист может легко просматривать файлы модулей. На древовидной диаграмме исследователя кода показаны все типы, классы, свойства, методы, глобальные переменные и глобальные процедуры, определенные в модуле, с которым происходит работа. В исследователе кода перечислены также все модули, подключенные к текущему модулю. Почти все окна, отображаемые на экране во время проектирования, являются стыкуемыми. Для состыковки двух окон необходимо поместить курсор мыши на заголовок окна и при нажатой кнопке мыши переместить его в другое окно. Появляющаяся рамка покажет новое расположение окна. Для расстыковки окон нужно проделать обратную операцию. Удобство состоит в том, что можно объединить несколько окон в одну группу и в дальнейшем обращаться с ней, как с единым окном. По умолчанию с окном редактора кода состыковано окно сообщений. Оно появляется автоматически, если в процессе компиляции программы были сгенерированы сообщения об ошибках или предупреждающие сообщения. Это можно легко проверить, если в стандартный код проекта по умолчанию внести какую-либо ошибку. Например, если в разделе v a r исправить Forml на Form2, то после компиляции, для чего необходимо нажать клавиши , появится окно сообщений с информацией об ошибке, а в окне редактора кодов будет подсвечена та строка, где произошла
Среда программирования
23
ошибка. Для отображения окна сообщений можно также щелкнуть правой кнопкой мыши на поле редактора кода и в контекстном меню выбрать пункт Message View (Просмотр сообщений). Если дважды щелкнуть кнопкой мыши на каком-либо сообщении, то в редакторе кода подсвечивается строка кода, породившая сообщение. Щелчок правой кнопкой мыши в окне сообщений активизирует контекстное меню этого окна, с помощью которого можно удалить или сохранить все или отдельные сообщения, а также сделать переход в окно редактора кода на строку с ошибкой. При выборе в главном меню команды Search^Find in Files... (ПоискаНайти в файлах...) в окне сообщений выводятся результаты поиска.
Окно Tool Palette Палитра компонентов представляет собой окно с перечнем доступных компонентов. В этом окне отображаются все компоненты библиотеки VCL. Компоненты библиотеки организованы в иерархическую структуру. Они служат строительными блоками графического пользовательского интерфейса любого приложения Delphi. Во время выполнения приложения компоненты VCL отображаются на экране как элементы управления — кнопки, флажки, списки, поля ввода и т.д. Элементы управления составляют подмножество компонентов VCL: каждый элемент управления является компонентом, но не каждый компонент является элементом управления. Элемент управления — это графический элемент, который размещается в форме, имеет визуальное отображение и способен обрабатывать сообщения операционной системы Windows. Компонент — это более широкое понятие, которое можно рассматривать как объект, выполняющий определенные функции.
Объекты Сейчас несколько проясним смысл слова объект. Строго говоря, объект — это экземпляр класса. А класс — это структура, в которой объединены поля (т.е. данные) и методы (процедуры и функции, с помощью которых и реализуется логика работы класса). Можно сказать, что поля определяют состояние объекта, а методы определяют функциональные возможности объекта, созданного на основе этого класса. Основная идея заключается в том, чтобы избежать повторяющегося кода и использовать уже созданные ранее фрагменты кода для выполнения необходимых функций. Поэтому класс можно рассматривать как шаблон, или прототип, на основе которого создаются объекты. В первом приближении объект, созданный на основе какого-либо класса, — это просто место в памяти, где хранятся только данные, связанные с этим объектом, а весь выполняемый код берется из класса. Таким образом, код, который может быть довольно большим, существует в единственном экземпляре. Именно к этому и стремились разработчики идеологии классов. Но основная мощь классов заключается не в этом, ведь нельзя предусмотреть нужные классы на все случаи жизни. Так вот, на основе одного класса можно создавать другие классы, внося только необходимые изменения. Код исходного класса останется неизменным, а созданный на его основе класс добавит только нужные свойства, не дублируя исходного кода. При этом вновь созданный класс будет обладать всеми возможностями исходного класса, или говоря более строго, будет наследовать возможности базового класса. Класс, от которого непосредственно создан производный класс, называют базовым классом, а иногда суперклассом. Но об этом поговорим подробнее, когда обратимся к основам объектно-ориентированного программирования. А пока объект будем рассматривать в более широком смысле этого слова как отдельный элемент программы, который может выполнять определенные задачи и свойства которого можно изменять. Объектом является не только само окно формы, но и все размещаемые в нем компоненты: Button (Кнопка), Label (Надпись), Memo (Область просмотра) и др.
24
Глава 1. Знакомство со средой программирования
Отображаемое в окне Exploring дерево объектов, так же как и инспектор объектов, необходимо использовать для повышения скорости работы и получения оперативной информации. Щелчок мыши на объекте выделит этот объект в форме, коде и инспекторе объектов.
Инспектор объектов Вкладки окна Object Inspector автоматически создаются для каждого вновь созданного в форме объекта и являются основным инструментом для настройки объектов. Необходимая вкладка выбирается из списка объектов, находящегося в верхней части окна, по имени объекта. Для этого необходимо щелкнуть на кнопке с изображением небольшой стрелки, указывающей вниз, и в появившемся списке выбрать нужный объект. После того как необходимый объект выбран, можно не только изменять и настраивать все его свойства, но и создавать обработчики событий для данного объекта, которые будут определенным образом реагировать на такие события, как, например, щелчок кнопки мыши, перемещение мыши или нажатие клавиш клавиатуры. При этом для каждого объекта в инспекторе объектов существуют две вкладки: Properties (Свойства) и Events (События). Кратко остановимся на некоторых свойствах для такого объекта, как форма. Для того чтобы создать проект по умолчанию, существует несколько способов. •
Первый способ. Выберите команду меню File^New^VCL Forms Application. Проект по умолчанию с формой и редактором кода будет создан. Это полностью работающее приложение, и результат его работы можно увидеть, если нажать клавишу . • Второй способ. Щелкните на пиктограмме проекта New Items, расположенной на панели Standard (Стандартная), и в появившемся окне выберите пиктограмму VCL Forms Application (VCL-приложение). • Третий способ. Выберите команду меню Project^Add New Project... и в появившемся окне выберите пиктограмму VCL Forms Application (VCL-приложение).
Теперь, когда создано рабочее приложение, можно изменять его параметры, что делается с помощью инспектора объектов. Если панель формы скрыта за окном редактора кодов, то переместите ее на передний план, что также можно сделать несколькими способами. •
Если некоторые элементы формы отображаются на экране, то просто щелкните на них мышью, после чего окно полностью отобразится на экране. • На панели View щелкните на пиктограмме Show Designer (Показать эскиз), что приведет к перестановке отображаемых окон. Нажатие клавиши приводит к такому же эффекту. • Выберите команду меню View=>Forms..., в результате чего появится диалоговое окно View Forms, где можно выбрать нужную форму. Теперь, когда форма находится на переднем плане, займемся ее редактированием с помощью инспектора объектов. Так как во вновь созданном проекте нет никаких объектов, кроме формы, то именно она и будет отображаться в инспекторе объектов. Выберите вкладку Properties (Свойства) и посмотрите на довольно большой перечень всех свойств. Разумеется, весь перечень не помещается на экране, и для доступа к отдельным свойствам нужно воспользоваться ползунком. Не стоит пугаться такого количества свойств, многие из них интуитивно понятны и они повторяются от объекта к объекту. Поэтому после небольшой практики вы хорошо освоите большинство свойств из этого списка. Все свойства распределены по группам, в которых они расположены в алфавитном порядке. Остановимся только на некоторых из них, например Caption (Заголовок) из
Среда программирования
25
группы Action (Поведение). По умолчанию там будет записано Formi, что и отображается в заголовке окна. Попробуйте изменить это свойство, для чего щелкните на поле, где записано Formi, и введите любое подходящее название, например строку "Мое Окно". Одновременно с набором текста будет меняться название окна формы. Или возьмем, к примеру, два свойства — Height (Высота) и Width (Ширина), которые определяют непосредственные размеры окна. Попробуйте ввести другие значения для них и сразу же увидите изменения на экране. Очень важное свойство Enabled (Доступность). Обычно оно всегда должно быть в состоянии True. Попробуйте изменить его на F a l s e и запустите проект на выполнение (клавиша или пиктограмма Run на панели Debug). Эффект будет заметен, так как вы не сможете обратиться к окну — оно будет заблокировано. Это удобно использовать в программе, когда необходимо на время заблокировать отдельные элементы интерфейса и запретить к ним доступ пользователя. Далее, с помощью свойства Icon (Пиктограмма) можно изменить пиктограмму, которая будет связана с приложением и будет отображаться рядом с заголовком окна. Пиктограмму можно нарисовать самому или скопировать. Для напоминания функций окна можно использовать свойство Hint (Подсказка). В поле для свойства Hint вводится текст, который будет отображаться на экране как подсказка при наведении на созданное окно указателя мыши. Но для того, чтобы это произошло, нужно разрешить тексту отображаться на экране, для чего существует свойство ShowHint (Показать подсказку), которое должно быть установлено в состояние True. Можно изменять статус окна и вид рамки (свойства BorderStyle и BorderWidth) или цвет окна (Color). Можно изменить вид курсора (Cursor), который появится при наведении курсора мыши на созданное окно. Обычно все перечисленные свойства присутствуют в любом объекте, и ими чаще всего приходится пользоваться. Остальные свойства рассмотрим, когда еще раз вернемся к проектированию формы.
Компоненты Компонент — это объект, представляющий собой отдельный строительный блок программы во время проектирования. Компонент является более широким понятием, чем используемое в Windows понятие элемент управления. Как и элемент управления, компонент способен принимать и обрабатывать сообщения Windows.
Стандартные компоненты Стандартными компонентами называются такие компоненты, которые наиболее часто используются для создания графического интерфейса пользователя. Из них составляют довольно сложные комбинации, которые выделяются в отдельные группы графического интерфейса. По своему внутреннему содержанию они могут быть довольно сложными. Форму тоже можно рассматривать как компонент, хотя ее пиктограмма отсутствует в палитре компонентов. Стандартные компоненты выделены в отдельную группу в окне Tool Palette. Рассмотрим несколько стандартных компонентов.
Компонент TLabel Компонент TLabel .(Надпись) используется для отображения текста в форме, прир чем этот текст нельзя изменять непосредственно через графический интерфейс пользователя, хотя в программе при необходимости можно предусмотреть изменение надписи. Рассмотрим методику работы с надписями. Выполните следующие действия. 1. Создайте новый проект типа VCL Forms Application. 2. Поместите надпись в форму. Для этого в окне Tool Palette найдите группу Standard и дважды щелкните кнопкой мыши на компоненте TLabel. Можно
26
Глава 1. Знакомство со средой программирования
щелкнуть один раз, а затем щелкнуть в нужном месте формы. При этом сразу можно задать необходимые размеры, наводя указатель мыши на ограничивающие квадраты и перемешая их при нажатой левой кнопке мыши на нужное расстояние. Чтобы удалить надпись из формы, выделите ее (щелкните на ней мышью, при этом она выделится черными квадратиками) и нажмите клавишу . Удаление можно выполнить также с помощью контекстного меню, если щелкнуть правой кнопкой мыши на объекте. Чтобы отменить выделение, щелкните кнопкой мыши в любом месте за пределами надписи. Поэкспериментируйте с размещением и удалением надписей. 3. Переместите надпись в другое место формы методом перетаскивания. Для этого установите указатель мыши на надписи, щелкните кнопкой мыши и, удерживая ее нажатой, передвиньте надпись в другое место. Когда надпись займет нужное положение, отпустите кнопку мыши. Обратите внимание на то, что при перетаскивании объекта фаницы надписи будут привязаны к разметочной сетке формы. 4. Измените значение свойства надписи Name (Имя) на MyFirstLabel (по умолчанию она называлась Labell). Для этого в инспекторе объектов щелкните на свойстве Name (Имя) и введите строку "MyFirstLabel". Убедитесь, что вы изменяете свойство надписи, а не формы (это типичная ошибка новичков). Для этого надпись в форме должна быть выделена, а в раскрывающемся списке в верхней части инспектора объектов должен быть выбран объект L a b e l l : TLabel (когда вы измените имя надписи, там будет написано MyFirstLabel: TLabel). После ввода нужного имени надписи зафиксируйте его, нажав клавишу <Enter>. 5. Измените саму надпись. Для этого выберите в инспекторе объектов свойство Caption (в данном случае можно перевести как надпись), введите новую строку "Моя первая надпись!" и нажмите клавишу <Enter>. Введенный текст появится в форме. Обратите внимание, как изменяются фаницы надписи. Это связано со свойством AutoSize (Подгонка размера). Если установить свойство AutoSize в состояние F a l s e , то автоматического изменения границ происходить не будет. Границы можно изменять вручную. Для этого выделите объект и наведите курсор мыши на одну из черных меток границы. Когда он примет вид двунаправленной стрелки, нажмите кнопку мыши и задайте необходимые размеры. 6. Измените цвет фона надписи. Для этого выберите свойство Color (Цвет), щелкните на стрелке, выберите в раскрывшемся списке желтый цвет и щелкните на нем. 7. Измените шрифт и цвет текста надписи. Для этого выберите свойство Font (Шрифт) и щелкните на кнопке с тремя точками. В поле Font измените шрифт на A r i a l , стиль на Bold I t a l i c , а размер на 32. В раскрывающемся списке выберите красный цвет и щелкните на кнопке ОК. 8. Добавьте к форме еще одну надпись. На этот раз воспользуйтесь другим методом — щелкните на компоненте TLabel, переместите указатель мыши в произвольное место формы и еще раз щелкните кнопкой мыши. При этом в форме в том месте, где находился указатель мыши, должна появиться новая надпись. 9. Измените значение свойства Name новой надписи на MySecondLabel, а значение свойства Caption — на "Моя вторая надпись!". 10. Сделайте видимой панель Align и выделите в форме надписи, для чего обведите их на форме указателем мыши при нажатой левой кнопке. Используйте различные способы выравнивания из панели Align и оцените результат. 11. Теперь выделите форму, для чего щелкните в любом месте формы за пределами надписей. 12. Измените свойства формы: свойству Name задайте значение LabelExample, а свойству Caption — значение Надпись.
Компоненты
27
13.
Итак, вы создали простое приложение, которое, правда, пока что ничего полезного не делает. Выполните его. Это можно сделать одним из трех способов: щелкнув на пиктограмме Run (Выполнить) из панели отладки, выбрав в главном меню команду Run => Run или нажав клавишу . При этом на экране должна появиться форма, как показано на рис. 1.7. 14. Щелкните на кнопке закрытия окна в верхнем правом углу и завершите приложение. Это же можно сделать и в среде Delphi, используя команду Run^Program Reset (Выполнить^Переустановка программы) или нажав комбинацию клавиш .
Моя первая надпись Моя вторая надпись
Рис. 1.7. Форма с надписями
Все слова и тексты, которые отображаются на экране, можно вводить на русском языке. Все идентификаторы, которые применяются непосредственно в программе, должны использовать только английский алфавит.
Компонент TEdit В компоненте TEdit (Поле ввода) хранится текст, который можно помещать в анный компонент как во время разработки, так и во время выполнения. Текст, видимый в поле ввода, является значением свойства Text. Свойство MaxLength (Максимальная длина) определяет максимальное количество символов в поле ввода. Если значение свойства MaxLength равно 0, то количество символов ничем не ограничено. С помощью свойства Font можно устанавливать шрифт текста. Если свойство Readonly (Только чтение) установить в True, то во время выполнения программы пользователь не сможет изменять текст поля ввода. Для лучшего понимания работы поля ввода выполните следующее. 1. Создайте новый проект типа VCL Forms Application. 2. Разместите компонент TEdit в форме. Компонент TEdit находится в группе Standard, как и надпись. Размещение также можно сделать несколькими способами. 3. Измените размер поля ввода. Для этого установите указатель мыши на одном из черных квадратиков и, удерживая кнопку мыши нажатой, переместите черный квадратик (а с ним и границу поля ввода) в нужном направлении. Установив необходимый размер, отпустите кнопку мыши. 4. Переместите поле ввода в нужное место методом перетаскивания, так же, как это делалось с надписью. 5. Установите значение свойства Name на MyText. Для этого в инспекторе объектов щелкните на свойстве Name и введите строку "MyText". Как и в случае с надписью, убедитесь, что вы изменяете свойство поля ввода, а не формы. Для этого в заголовке поля выбора в верхней части инспектора объектов должно быть написано E d i t l : TEdit.
28
Глава 1. Знакомство со средой программирования
6. Выберите в инспекторе объектов свойство Text и введите его новое значение. Нажав клавишу <Enter>, зафиксируйте введенный текст. Обратите внимание: во время ввода изменяется текст в поле ввода формы. 7. Измените цвет текста в поле ввода на синий. Для этого в инспекторе объектов щелкните на значке " + " рядом со свойством Font. При этом значок " + " изменяется на "—", и появляется список свойств объекта Font, который в данном случае сам используется как свойство. Выберите свойство Color и щелкните на стрелке, расположенной в этом поле. При этом раскрывается список доступных цветов. Найдите в нем синий цвет и щелкните на нем. 8. Выделите форму. Измените значение свойства Name формы на EditBoxExample, а значение свойства Caption — на Поле ввода. 9. Нажав клавишу , запустите разработанную программу. При этом на экране появится изображение, показанное на рис. 1.8. В отличие от надписи, текст в поле ввода можно изменять, запоминать и извлекать из буфера обмена. Но после установки значения True для свойства Readonly изменять содержимое поля ввода через графический интерфейс пользователя уже будет нельзя. Значение свойства Readonly можно менять из программы, запрещая или разрешая таким образом пользователю вводить данные. 10. Завершите приложение.
Поле ввода Рис. 1.8. Поле ввода
Компонент ТМето Компонент ТМето (Область просмотра) предназначен для вывода на экран нескольких строк текста. Свойства MaxLength, Font и Readonly области просмотра аналогичны соответствующим свойствам поЛя ввода. Свойство Text содержит весь текст области просмотра, однако это свойство доступно только во время выполнения. Свойство Lines содержит отдельные строки текста области просмотра, оно доступно как во время разработки, так и во время выполнения. Свойство Wordwrap определяет, будут ли переноситься строки, выходящие за пределы области просмотра, или они останутся невидимыми. Если вместо русского текста на экране появились произвольные символы, то нужно изменить значение свойства Charset (Набор символов) объекта Font (Шрифт). Для большинства шрифтов подходящими значениями свойства Charset являются DEFAULT_CHARSET ИЛИ RUSSIAN_CHARSET.
Чтобы лучше изучить область просмотра, выполните следующие действия. 1. Создайте новый проект типа VCL Forms Application. 2. Разместите область просмотра в форме так же, как делали ранее для надписи или поля ввода. 3. Установите подходящий размер области просмотра и переместите область просмотра в удобное место. 4. Измените значение свойства Name области просмотра на MemSample, для чего в инспекторе объектов щелкните на свойстве Name и введите строку "MemSample". Как и в случае надписи или поля ввода, убедитесь, что вы изменили свойство
Компоненты
29
области просмотра, а не формы. В раскрывающемся списке в верхней части инспектора объектов должно быть написано Memol: TMemo (после изменения имени там будет MemSample: TMemo). 5. Выберите свойство Lines и щелкните на кнопке с тремя точками — появится окно редактора строк String List Editor. Введите текст, показанный на рис. 1.9. Закончив ввод текста, щелкните на кнопке ОК.
Рис. 1.9. Текст, вводимый в редакторе строк
6. Выделите форму. Для этого щелкните левой кнопкой мыши на форме или на имени формы в раскрывающемся списке инспектора объектов. Измените значение свойства Name на MemoBoxExample, а свойства Caption — на Область просмотра. 7. Запустите программу на выполнение. На экране должно появиться изображение, показанное на рис. 1.10. Попробуйте вводить тексты различной длины. Поэкспериментируйте с режимами выделения текста, сохранения и извлечения из буфера обмена. Область просмотра
Строка 1 Строка 2 Строка 3 Строка 4 Строка 5 Строка 6
Рис. 1.10. Окно с компонентом тмешо
8. Завершите работу программы. 9. В инспекторе объектов измените значение свойства Wordwrap области просмотра MemSample на F a l s e , а значение свойства ScrollBars — на ssBoth (это свойство определяет наличие или отсутствие полос прокрутки).
30
Глава 1. Знакомство со средой программирования
10. Нажав клавишу , запустите программу еще раз. Вводите текст в области просмотра до тех пор, пока он не выйдет за правую границу. Попробуйте также добавлять новые строки, пока они не выйдут за нижнюю границу. 11. Завершите работу программы. 12. Чтобы лучше понять работу области просмотра, поэкспериментируйте с различными установками свойств Wordwrap и ScrollBars.
Компонент TButton Обычно с помощью компонента TButton (Кнопка) пользователь инициирует выполнение какого-либо фрагмента кода или целой программы. Другими словами, если щелкнуть на элементе управления TButton, то программа выполняет определенное действие. При этом кнопка принимает такой вид, будто она нажата. Кнопкам можно присваивать комбинации быстрых клавиш. Во время выполнения нажатие такой комбинации клавиш эквивалентно щелчку мыши на кнопке. Выполните следующие действия. 1. Создайте новый проект типа VCL Forms Application. 2. В инспекторе объектов измените значение свойства формы Name на B u t t o n Example, а свойства Caption — на Кнопка. 3. Поместите кнопку в форму. 4. Измените значение свойства Name кнопки на MyButton. 5. Измените значение свойства Caption кнопки на &Щелкать здесь. Обратите внимание: в надписи на кнопке буква, перед которой стоит символ "&", будет подчеркнутой. В данном случае это буква Щ. Это означает, что теперь кнопке присвоена комбинация клавиш быстрого вызова . 6. Нажав клавишу , запустите программу. При этом на экране появляется изображение, показанное на рис. 1.11. 7. Щелкните на кнопке. При этом кнопка принимает такой вид, будто она нажата. Так как с кнопкой еще не связан какой-либо код, то никакой реакции на нажатие кнопки не происходит. 8. Завершите работу программы. Если в названии кнопки, которое отображается на экране, одна из букв подчеркнута, то это значит, что кнопке присвоена комбинация быстрых клавиш. Нажатие клавиши с подчеркнутой буквой приведет к активизации кнопки, аналогично щелчку мыши на ней. Но при этом необходимо учитывать регистр и раскладку клавиатуры, что не всегда удобно.
Щелкать здесь j ' • • • , '
Рис. 1.11. Пример формы с кнопкой
В данном примере для использования комбинации быстрых клавиш клавиатура должна быть переключена на русский язык. Кроме того, должен использоваться верхний регистр (для оперативного переключения на верхний регистр необходимо нажать
Компоненты
31
клавишу <Shift> или клавишу , при этом постоянно будет включен верхний регистр, о чем говорит подсвеченная лампочка CapsLock). Что делать, если в названии кнопки должен отображаться символ "&"? Ведь если поместить его в название, то он сделает следующую букву подчеркнутой, а сам виден не будет. Чтобы решить эту проблему, используется следующее правило: символ "&" отображается в названии кнопки, если в свойстве Caption записаны два стоящих подряд символа — "&&". Например, чтобы название кнопки имело вид This & That, в свойство Caption необходимо записать строку "This && That". При этом никакая комбинация клавиш быстрого вызова кнопке не присваивается. Не бойтесь экспериментировать с этими компонентами. Попробуйте изменять другие их свойства. Худшее, что можно сделать, — это нарушить работу Delphi (что крайне маловероятно). Тогда придется всего лишь перезапустить программу, а в крайнем случае — переустановить.
Первая программа Теперь вы уже имеете необходимые знания для того, чтобы создать свою первую программу. Это будет простенькая программа, имеющая интерфейс с двумя кнопками и надписью, которую можно как выводить на экран, так и убирать с экрана. Постарайтесь как можно лучше оформить этот незамысловатый графический интерфейс. Образно можно сказать, что программу встречают по одежке, т.е. по виду интерфейса. Хорошо и гармонично оформленный интерфейс, где интуитивно понятно назначение всех его частей, внушает доверие к программе. Понятно, что невозможно только средствами фафики выразить все функциональные особенности программы, поэтому должны быть и поясняющие надписи, и появляющиеся подсказки, и файл справки. Только в самых простейших случаях, как например, первая профамма, можно этого не делать. Сейчас приступим к разработке первой профаммы, это не займет много времени. 1. Создайте проект типа VCL Forms Application. 2. Разместите на нем две кнопки и надпись. Используя способы выравнивания с панели Align или вручную, постарайтесь расположить их симметрично, выбрав подходящие размеры. 3. Назовите первую кнопку Вывод надписи, а вторую — Очистка (свойство Caption). 4. Для надписи выберите подходящий размер шрифта (выше говорилось, как это сделать). 5. Создайте обработчики событий для нажатия кнопки. 6. Для создания обработчика события нажатия кнопки необходимо в инспекторе объектов выбрать вкладку Events (События), где следует выбрать событие Onclick (Щелчок). Разумеется, инспектор объектов должен отображать настройки нужного объекта. В нашем случае это будет кнопка Buttonl с названием Вывод надписи. 7. Щелкните мышью на появившемся справа от события Onclick белом поле, и Delphi моментально перенесет вас в редактор кода, где сделает заготовку для обработчика события и установит курсор на том месте, где нужно ввести необходимый код. 8. Вручную введите следующий код. ' Labell.Caption := 'Моя первая программа'; 9. Проделайте то же самое со второй кнопкой, для которой введите такой код. L a b e l l . C a p t i o n := ' ' ;
32
Глава 1. Знакомство со средой программирования
10. Запустите программу и пощелкайте на кнопках. На рис. 1.12 показан графический интерфейс созданной программы. 11. Завершите работу программы.
оя первая программа
Рис. 1.12. Графический интерфейс первой программы Полностью листинг вашей первой программы приведен ниже. unit FirstProg; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, System.ComponentModel, Borland.Vcl.StdCtrls; type TForml = class(TForm) Buttonl: TButton; Button2: TButton; Label1: TLabel; procedure ButtonlClick(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Forml: TForml; implementation {$R *.nfm} procedure TForml.ButtonlClick(Sender: TObject); begin 1 Label1.Caption := 'Моя первая программа ; end; procedure TForml.Button2Click(Sender: TObject); begin Labell.Caption := ''; end; end. Изучив листинг, вы найдете имена созданных вами объектов и выводимых надписей. Больше об этом листинге говорить не будем, пока не познакомимся с основами объектно-ориентированного языка Delphi. Хочу только отметить, что несмотря на огромную работу, которую должен проделать компьютер по выводу на экран созданного вами окна, обработке событий (для чего необходимо взаимодействие с системой Windows по передаче сообщений), созданию кода по обработке критических ситуаций
Первая программа
33
и т.д., программа, с которой работает пользователь, очень короткая. В ней показаны только те фрагменты, в которых необходимо делать изменения. Вся остальная часть огромной программы скрыта в недрах Delphi, и вам с ней на первом этапе не придется работать. Это одно из преимуществ Delphi. В других языках программирования выводится гораздо больше кода, с которым труднее работать. Теперь несколько слов об оформлении интерфейса. Когда у вас всего три объекта, то нетрудно их располагать вручную, перемещая элементы на нужные места и задавая необходимые размеры. Но когда интерфейс достаточно загружен, и при этом установлена достаточно мелкая сетка на форме, то удобнее пользоваться палитрой способов выравнивания. Размеры сетки, как и многие другие настройки среды Delphi, можно задать в окне Options, которое можно открыть, выбрав команду меню Tools^Options.... В окне выбирается вкладка Windows Form Designer, где находится настройка Grid Size, с помощью которой и устанавливаются размеры сетки для формы. В этой же группе находятся такие настройки, как Show grid (Показать сетку) и Snap to grid (Привязка к сетке). Привязка к сетке означает, что границы всех объектов будут проходить только по сетке, что удобно для разработки аккуратно смотрящихся интерфейсов (рис. 1.13). i
3 Envrionment Opto i ns j Obe j ct Inspector : Envrionment Vara i be ls Ei Dep l hi Opto i ns j Library !1• Explorer VCL Desg i ner. В Editor Options i Source Options i Color • Display Key Mappings Code Insight ECO General options E! • HTML Designer Options
" Grd i Opto i ns ;' |7 ShowgHd
grid
Grid Size: К Code Generation Bracing Style Displays braces on the samefineas a function declaration for newly generated code.
£ Style
Displays braces on the line f oHowing a function declaration for nevrfy generated code. •: [
HTML Formatting Source Control Options ASP.NET В HTML Tidy Options
Cancel
j
Help
"
Рис. 1.13. Окно настроек Options
Об остальных настройках поговорим в тех случаях, когда будем обсуждать соответствующие темы. Хотя можете и поэкспериментировать, многие из них интуитивно понятны и не требуют дополнительного пояснения. После экспериментов нужно будет вернуть все установки в исходное состояние, так как в дальнейшем при описании работы Delphi предполагается, что все установки сделаны по умолчанию. И наконец, разберемся, что же делать с разработанной программой. Она нам может пригодиться в дальнейшем, поэтому сохраним ее. Для этого выберите команду FileOSave Project as... и сохраните проект и исходные файлы в отдельном каталоге (я предполагаю, что вы знакомы с Windows и знаете, как это сделать). При этом сохранятся файл проекта (расширение .dpr), файл формы (расширение .dfm), исходный файл (расширение .pas) и несколько других нужных для проекта файлов. После того как вы запустите проект, в этом каталоге появятся файлы с расширениями . ехе и . dcu. Это будут выполняемый файл и файл, созданный компилятором. Можете запустить выполняемый файл отдельно и убедиться, что он работает так же, как и в среде Delphi. 34
Глава 1. Знакомство со средой программирования
Подсказки в Delphi Справочная система Система Delphi содержит гипертекстовую справочную систему, с помощью которой программист может легко и быстро получить необходимую информацию о среде разработки Delphi и объектно-ориентированном языке Delphi. Для активизации справочной системы выберите команду Help^Delphi Help (Справка^Справка Delphi) или щелкните на пиктограмме Help contents (Содержание справочной системы) на обычной панели инструментов. При этом появится окно справочной системы Delphi (см. рис. 1.2). К сожалению, перевода справочной системы Delphi на русский язык пока что нет. Однако чтобы пользоваться ею, совсем не обязательно владеть английским языком в совершенстве. Даже если вы слабо знаете английский язык, все равно справочная система во многих случаях будет вам полезной.
Подсказки редактора кодов Во-первых, выделение ключевых слов в редакторе кода уже является хорошей подсказкой. Так что если вы набираете ключевое слово, а оно не выделилось жирным шрифтом, значит, оно набрано с ошибкой. Наличие отступов и выделение отдельных фрагментов кода также необходимо для лучшего восприятия программы. Во-вторых, несмотря на наличие дерева объектов и инспектора объектов, где можно получить подробную информацию об объектах, в редакторе кодов есть тоже достаточно много средств, помогающих получить информацию об объектах или найти описание некоторого объекта. Например, для получения справки о типе выделите идентификатор типа, щелкните на нем правой кнопкой мыши и выберите команду Topic Search. Delphi предоставит справку о данном компоненте. Аналогичный результат можно получить, если нажать клавишу . Подсказки могут появляться и непосредственно во время написания кода. Например, когда вы вводили код для вашей первой программы, то, наверное, обратили внимание на появляющийся перечень всех доступных методов и свойств для данного объекта, когда поставили точку после имени объекта. Если вы не обратили на это внимание, то повторите ввод еще раз и сделайте задержку после того, как набрали строку "Labell". Появится перечень доступных свойств и методов. Об остальных подсказках поговорим в дальнейшем, сейчас необходимо уяснить, что подсказок достаточно много и они приносят ощутимую пользу. Еще раз вернемся к простейшим профаммам и создадим несколько полезных приложений.
Примеры программ без написания кода Сначала создадим календарь, разработка которого займет не более минуты. 1. Создайте проект типа VCL Forms Application. 2. Разместите на нем компонент типа TMonthCalendar (Календарь), который находится в разделе Win32.
1i I
3. Разместите календарь в форме и установите подходящие размеры формы (рис. 1.14). 4. Запустите программу и испытайте работу компонента MonthCalendar.
1 8 15 22 29
5. Завершите работу программы, сохраните ее и можете ею пользоваться.
Примеры программ без написания кода
Пи
S 2
Map г 2004 г. Вт
ср
Чт
Пт
• |
т 1 103 9 16 17 23 24 30 31 02.03.2004 Сегодня: 4 11 18 25
5 12 19 26
' ,
Сб 6 13 20 27
Рис. 1.14. Интерфейс граммы "Календарь"
7 : 14 \ 1 21 .:'••.': 2
8
•
•
•
>
»
«
про-
35
Теперь создайте простейший навигатор Windows (Просмотр каталогов), для чего используйте компоненты TFileListBox, TDirectoryListBox, TDriveComboBox и TFilterComboBox, которые находятся в разделе win31. Сделайте все то же самое, как и при создании календаря, за одним исключением. В инспекторе объектов нужно установить необходимые связи. Например, для свойства FileList компонента D i r e c t o r y L i s t B o x l необходимо установить значение F i l e L i s t B o x . Его просто нужно выбрать из раскрывающегося списка, который будет создан для свойства FileList. Это же нужно сделать и с другими компонентами. Для свойства формы Caption введите значение Навигатор и можете наслаждаться навигатором по файлам Windows (рис. 1.15).
b.bdsprpt. irsrLab,cfg irsrLab.dpr -ratab.res FirstLabel.nfm FrstLabel.pas nrstProg.~nfm FristProg.~pas FirstProg.dcuil FristProg.nfm FirstProg.pas Navg i .bdsproj Navig.cfg Navg i .dpr = =
•
•
£
}
£
G
}
W
e
o
n
r
n
a
d
\
••:•
'
/
•
•
-
•
•
.
y
k
| В h: [data]
В Delphi
О Book (Р=) Programs |-Ч«.''-Д| CD Model
|AH files (*.*)
Рис. 1.15. Интерфейс программы "Навигатор"
В заключение еще раз хочу подчеркнуть преимущества Delphi. Вы уже могли убедиться, что с помощью нескольких щелчков мышью можно создать довольно сложное приложение, такое как календарь или навигатор. На разработку подобного приложения "с нуля" уйдет достаточно много времени. В Delphi таких заготовок существует достаточно много и на все случаи жизни. Можно очень быстро создать доступ к базам данных или использовать уже готовый модуль для прогулок по Internet. Наличие готовых модулей позволяет быстро создавать распределенные системы и обеспечивает работу целого предприятия. В дальнейшем мы познакомимся с некоторыми из таких модулей и будем создавать программы, складывая их из отдельных компонентов, как из кубиков. Но чтобы объединить работу отдельных компонентов, приходится вручную писать соответствующий код. Для того чтобы это делать, а самое главное, чтобы самому разрабатывать отдельные компоненты, необходимо знать язык программирования Delphi, на котором написана и сама Delphi. Поэтому все программы, разработанные в Delphi, хорошо интегрируются со средой Delphi. В следующих главах займемся изучением языка программирования Delphi.
36
Глава 1. Знакомство со средой программирования
Резюме В данной главе вы ознакомились со средой программирования Delphi. Изучая рабочий стол Delphi, вы, вероятно, убедились, что создавать графический интерфейс пользователя с помощью Delphi — не такая уж и сложная задача. Большую часть работы среда программирования делает за вас, необходимо только грамотно указывать ей, что нужно сделать. Безусловно, пока еще не все понятно, но к этим вопросам мы будем возвращаться на протяжении всей книги.
Контрольные вопросы 1. 2. 3. 4. 5. 6. 7.
Что такое среда программирования? Назовите панели меню, которые могут располагаться на рабочем столе Delphi. Какие окна располагаются на рабочем столе Delphi? Что такое свойство? Что находится на вкладках Code и Design? Что такое компонент? Какие стандартные компоненты вы уже знаете? Назовите их основные свойства.
Контрольные вопросы
37
Глава 2
Знакомство с платформой .NET В этой главе... •
Модель СОМ
• Решения .NET •
Пространства имен
•
Резюме
•
Контрольные вопросы
\Г*\\ этой небольшой главе рассмотрены основополагающие понятия .NET, которые ьЬаУ необходимо знать при программировании для платформы .NET. В начале главы представлены элементы, которые лежат в основе платформы .NET, — сборки (assemblies), промежуточный язык (IL) и компиляция в процессе выполнения (JIT). Затем будут рассмотрены средства взаимосвязи компонентов платформы .NET — общеязыковая исполняющая среда (CLR), универсальная система типов (CTS) и универсальная спецификация языка (CLS). Наконец, будет сделан общий обзор возможностей, предоставляемых библиотеками базовых классов .NET и утилит, которые помогут в работе с этими библиотеками.
Модель СОМ Компонентные технологии программирования основаны на контрактах — наборах правил, определяющих взаимодействие между отдельными компонентами приложения. Модель компонентных объектов (СОМ) была первой попыткой компании Microsoft формализовать контракты в качестве парадигмы программирования. Формализации контрактов содействовало также создание поддерживающей их платформы. Парадигма программирования СОМ основана на представлении контрактов между компонентами с помощью определений типов. До появления СОМ контракты между компонентами были представлены всего лишь как точки входа функций. В этом отношении модель СОМ была значительным прогрессом: динамическая загрузка кодов и систем типов вводится в ней естественным образом. Хотя объектная модель СОМ используется очень широко, внутреннее устройство компонентов весьма сложно. Чтобы научиться разбираться в нем, придется потратить по крайней мере несколько месяцев. Написание приложений с использованием компонентов СОМ можно упростить, используя стандартные библиотеки, например библиотеку шаблонных классов (ATL) со своим набором готовых классов, шаблонов и макросов. Язык Delphi также позволяет скрыть сложность инфраструктуры СОМ. Однако всех проблем избежать все равно не удастся.
Несмотря на сложность конструкций, модель программирования СОМ выдержала проверку временем. Она объединила уже получившие распространение концепции (инкапсуляция, полиморфизм, а также разделение интерфейса и реализации) в унифицированную технологию. Но СОМ — это не только модель программирования, но и поддерживающая ее платформа. К сожалению, платформа оказалась слабым местом СОМ. Чтобы модель СОМ стала общепринятой технологией программирования, ей не хватило именно устойчивой платформы. Для описания контрактов СОМ компания Microsoft определила и поддерживает не один, а целых два формата обмена: язык определения интерфейсов (IDL) и библиотеку типов (TLB). Каждый из них сам по себе не создает проблем, однако эти два формата не изоморфны, т.е. существуют конструкции, которые могут быть корректны в одном формате и в то же время не иметь смысла в другом. Можно было бы определить третий формат на основе объединения конструкций, поддерживаемых обоими форматами. Однако это не устраняет, как минимум, двух других критических проблем, связанных со способами описания контрактов в СОМ. Во-первых, модель СОМ даже не пытается описать взаимозависимость компонентов. Невозможно взять компонент СОМ и выяснить, какие другие компоненты необходимы для его нормальной работы. Из-за отсутствия информации о взаимозависимостях крайне трудно понять, какие DLL-библиотеки необходимы для развертывания приложения СОМ. Из-за этого также невозможно, не запустив приложение, выяснить, какие версии компонента необходимы. Во-вторых, каждый формат описания контрактов не обладает хоть какой-либо расширяемостью. Но даже если бы появился идеальный унифицированный формат описания, модель СОМ осталась бы отягощенной еще одной проблемой, связанной с функционированием контрактов. Эта проблема не имеет отношения к способам описания контрактов. Она кроется в самих контрактах. Контракт компонентов СОМ основан на описаниях типов. Используемая в контрактах система типов основана на подмножестве языка C++, гарантирующем переносимость между компиляторами. Переносимость гарантируется не только для лексики языков программирования, но и для форматов представления данных. Здесь и возникает главная проблема. В модели СОМ контракт компонента является физическим (т.е. двоичным) контрактом. Следовательно, компонент СОМ предъявляет жесткие требования к межкомпонентным вызовам. Контракт СОМ требует точной дисциплины стека, используемого при вызове методов, точного смещения каждой структуры данных, передаваемой в качестве фактического параметра метода, точной информации о механизме размещения памяти, используемом вызываемым методом, а также точного формата ссылки на объект. Таким образом, контракт СОМ — это всего лишь протокол формирования кадров стека, полностью лишенный семантики приложения. Перечисленные выше требования делают технологию СОМ трудной в использовании даже для классных программистов. Физическая природа контрактов компонентов СОМ особенно неудобна, когда дело доходит до версий компонентов. Управление версиями — задача далеко не простая, даже когда выполняются только семантические изменения. Для решения проблем, связанных с контрактами СОМ и их определением, работающие в Microsoft команды разработчиков СОМ и MTS создали новую компонентную платформу, названную COM3. Вскоре после выбора имени различные группы внутри Microsoft обнаружили, что на некоторых платформах Microsoft имя COM3 нельзя использовать в качестве имени каталога, поэтому его изменили на COR. В процессе разработки использовались и другие имена — СОМ+, Runtime, Lightning, URT. В итоге перед первым выпуском бета-версии технология была переименована в CLR (Common Language Runtime — общеязыковая исполняющая среда).
Модель СОМ
39
Решения .NET На фоне рассмотренных выше проблем возможности, предлагаемые платформой .NET, позволяют радикально облегчить жизнь программиста. Один из главных принципов .NET можно выразить так: "Изменяйте все, что хотите, используя любые средства". Платформа .NET — это совершенно новая модель для создания приложений для Windows (а в будущем, видимо, и для других операционных системам). Перечислим кратко основные положения .NET. • •
•
•
• •
Все возможности взаимодействия с существующим кодом. Двоичные компоненты СОМ отлично работают вместе с двоичными файлами .NET. Полное и абсолютное межъязыковое взаимодействие. В отличие от классического СОМ, в .NET поддерживаются межъязыковое наследование, межъязыковая обработка исключений и межъязыковая отладка. Общая среда выполнения для любых приложений .NET вне зависимости от того, на каких языках они были созданы. И что важно, для всех языков используется один и тот же набор встроенных типов данных. Библиотека базовых классов, которая скрывает все сложности, связанные с использованием вызовов API, и предлагает законченную объектную модель для всех языков программирования, поддерживающих .NET. О запутанных конструкциях СОМ можно забыть! Интерфейсы i c l a s s F a c t o r y , iunknown, код IDL и вариантные типы данных отсутствуют в программах .NET. Действительное упрощение процесса развертывания приложения. В среде .NET не надо регистрировать типы в системном реестре. Более того, .NET позволяет разным версиям одного и того же модуля DLL мирно сосуществовать на одном компьютере.
Строительные блоки .NET Технологии CLR, CTS и CLS очень важны для понимания смысла платформы .NET. С точки зрения программиста, платформу .NET вполне можно рассматривать просто как новую среду исполнения и новую библиотеку базовых классов. Среда исполнения .NET является общеязыковой исполняющей средой (CLR). Главная роль CLR заключается в том, чтобы обнаруживать и загружать типы .NET и производить управление ими в соответствии с вашими командами. CLR берет на себя всю низкоуровневую работу — например, автоматическое управление памятью, межъязыковое взаимодействие, развертывание различных двоичных библиотек с отслеживанием версий. Еще один строительный блок платформы .NET — это универсальная система типов (CTS). Система CTS полностью описывает все типы данных, поддерживаемые средой исполнения, и определяет, как одни типы данных могут взаимодействовать с другими и как они будут представлены в формате метаданных .NET. Необходимо отметить, что в языках программирования .NET не обязательно должны поддерживаться только типы данных, которые определены в CTS. Но существует набор правил — универсальная спецификация языка (CLS). Этот набор определяет подмножество общих типов данных, в отношении которых гарантируется, что они безопасны при использовании во всех языках .NET. Если вы создаете типы .NET с использованием только тех возможностей, которые разрешены в CLS, тем самым вы сделаете их пригодными для любых языков .NET. Помимо спецификаций CLR, CTS и CLS платформа .NET предоставляет также библиотеку базовых классов, доступную из любого языка программирования .NET. Библиотека базовых классов не только скрывает стандартные низкоуровневые опера-
40
Глава 2. Знакомство с платформой .NET
ции, такие как файловый ввод-вывод, обработка графики и взаимодействие с оборудованием компьютера, но и обеспечивает поддержку большого количества служб, используемых в современных приложениях. В качестве примера можно привести встроенные типы для обращения к базам данных, работы с языком XML, обеспечения безопасной работы приложения, создания приложений для работы в сети и т.д. С концептуальной точки зрения компоновка среды исполнения и библиотеки базовых классов .NET выглядят так, как показано на рис. 2.1. Библиотека базовых класов
Общеязыковая исполняющая среда (CLR) Универсальная система типов (CTS)
Универсальная спецификация языка (CLS)
Рис. 2.1. Библиотека классов и общеязыковая исполняющая среда
Языки программирования .NET Наверно, для работы с .NET будут адаптированы почти все известные языки программирования. Уже заканчиваются разработки более чем 30 языков для платформы .NET. Помимо четырех языков, поставляемых с Visual Studio .NET (C#, Visual Basic.NET, Managed C++ и JScript.NET), и уже выпущенного языка Delphi, ожидаются версии Smalltalk, COBOL, Pascal, Python, Perl и других. Необходимо отметить, что двоичные файлы .NET, для которых используются привычные расширения . d l l и .ехе, по своему внутреннему содержанию не имеют абсолютно ничего общего с обычными исполняемыми файлами. Например, DLL-файлы не предоставляют свои методы в распоряжение приложений на компьютере. В отличие от компонентов СОМ двоичные файлы .NET не описываются с помощью кода IDL и не регистрируются в системном реестре. Но самое важное отличие заключается в том, что двоичные файлы .NET не содержат зависящих от платформы команд. Содержимое двоичных файлов .NET — это независимый от платформы промежуточный язык, который официально называется промежуточным языком Microsoft (Microsoft Intermediate Language — MSIL), или просто IL.
Двоичные файлы .NET Когда с помощью компилятора для платформы .NET создается модуль с расширением . d l l или .ехе, содержимое этого модуля представляет сборку на промежуточном языке (IL). Назначение IL аналогично байт-коду Java — он компилируется в специфичные для платформы команды только при необходимости, т.е. когда к блоку команд IL обращается среда исполнения .NET.
Решения .NET
41
Помимо команд IL, двоичные модули .NET содержат также метаданные, которые подробно описывают все типы, использованные в модуле. Например, если у вас внутри сборки есть некоторый класс, то в метаданных этой сборки будет содержаться информация о базовом классе для этого класса, данные о том, какие интерфейсы предусмотрены для него, а также полное описание всех методов, свойств и событий этого класса. Во многих отношениях метаданные .NET являются значительным усовершенствованием по сравнению с классической информацией о типах в СОМ. Классические двоичные файлы СОМ обычно описываются с помощью ассоциированной библиотеки типов. Проблема с информацией о типах в СОМ заключается в том, что никто не может гарантировать вам, что эта информация окажется полной, так как за создание этих данных отвечает профаммист. В отличие от СОМ, метаданные в среде .NET автоматически генерируются компилятором, создающим приложение, и поэтому являются исчерпывающе полными. В метаданных .NET содержится информация абсолютно обо всех типах, которые используются в сборке (классах, структурах, перечислениях и т.д.), а также о каждом свойстве, методе или событии каждого типа. Еще одно отличие метаданных .NET от информации СОМ заключается в том, что в метаданных .NET перечислены все ссылки на внешние модули и сборки, которые потребуются для нормального выполнения сборки .NET. За счет этого можно считать сборки .NET полностью самодокументируемыми. В результате, например, отпадает необходимость регистрировать двоичные файлы .NET в системном реестре. Метаданные описывают не только типы, используемые в сборке, но и саму сборку. Эта часть метаданных называется манифестом. В манифесте содержится информация о текущей версии сборки, об использованных ограничениях по безопасности, о поддерживаемом языке (английском, русском и т.д.), а также список всех внешних сборок, которые потребуются для нормального выполнения профаммы. В подавляющем большинстве случаев двоичный файл .NET и сборка — это одно и то же. И если говорится о DLL-библиотеке для .NET, то понятия "двоичный файл" и "сборка" являются синонимами. Но сборка может состоять как из одного, так и из нескольких двоичных файлов. В сборке из одного файла содержатся и манифест, и метаданные, и команды IL. В сборке из нескольких двоичных файлов каждый двоичный файл называется модулем. При создании таких многофайловых сборок один из двоичных файлов должен содержать манифест сборки (в нем могут также находиться и другие данные, в том числе команды IL). Все остальные модули могут содержать только метаданные типов и команды IL. Единственная причина для создания многофайловой сборки — это большая гибкость при развертывании приложения. Например, если пользователь обращается к удаленной сборке, которая должна быть зафужена на его локальный компьютер, среда исполнения зафузит лишь те модули сборки, которые действительно необходимы. Такое решение позволит избежать ненужного сетевого трафика и увеличит скорость работы профаммы.
Промежуточный язык Рассмотрим промежуточный язык (IL) более подробно. Код IL не зависит от платформы, на которой будет производиться выполнение. При этом компилятор для платформы .NET сгенерирует код IL вне зависимости от того, какой язык профаммирования (Delphi, C#, Visual Basic.NET, Eiffel и т.д.) использовался для создания профаммы. В качестве примера мы создадим профамму сложения двух чисел. Единственное, что она у нас будет уметь делать, — складывать числа 2 и 3. Ниже приведен листинг с кодом этой профаммы на языке Delphi. Даже не зная языка Delphi, можно понять смысл этой профаммы, а на синтаксические конструкции пока не обращайте внимания. program Sum; {$APPTYPE CONSOLE} uses
42
Глава 2. Знакомство с платформой .NET
SysUtils; var X,Y,SumXY: I n t e g e r ; begin X := 2; Y := 3 ; SumXY := X + Y; Write(SumXY); Readln; end. После компиляции этой программы будет получен исполняемый файл Sum.exe. Этот файл не является обычным исполняемым файлом, а представляет сборку, которую можно выполнить только на платформе .NET. Внутри этого файла находятся манифест, необходимые библиотеки, команды IL и метаданные, описывающие программу Sum, которые легко увидеть с помощью программы i l d a s m . e x e (рис. 2.2 и 2.3). Об этом будет сказано далее.
:• MANIFEST &-9 $compiler_internal$ * 9 Borland.Delphi.System i 9 Borland.Vcl. Math it-9 Borland.Vcl.StrUtils
Щ-9 Borland. Vcl.SysConst »-9 Borland.Vcl.SysUtils ¥ 9 Borland.Vcl.Types s -II Borland.Vcl.Windovvs a--9 Sum в В Unit \ Л .class public auto ansi Ш .cctor: voidO Ш Sum : voidQ
.assembly Sum
Ы
Рис. 2.2. Компоненты программы Sum в окне программы ildasm.exe
На первом этапе вам не придется разбираться с кодом IL, поэтому не старайтесь понять, что там написано. Лишь отметьте для себя, как просто получить доступ к любой сборке и узнать все, что необходимо для понимания работы элементов сборки. При этом можете наглядно убедиться, что компилятор Delphi генерирует не зависимый от платформы набор команд, а код IL. Но почему Microsoft пошла по пути создания IL и какие преимущества IL перед обычным набором зависимых от платформы команд? Одно из преимуществ, о котором уже упоминалось, — это возможность полного межъязыкового взаимодействия. Поскольку любой код на любом языке программирования .NET компилируется в стандартный набор инструкций IL, проблем с взаимодействием между блоками кода IL не будет. При этом взаимодействие будет производиться на двоичном уровне, что является залогом высокого быстродействия. Еще одно возможное преимущество — независимость от аппаратной платформы. Можно предположить, что среда выполнения .NET будет распространена на самые разные компьютерные платформы и операционные системы (отличные от Windows), что уже произошло с языком Java.
Решения .NET
43
J
method public static void Sum() cil managed
entrypoint // Code size 52 (0x34) maxstack 3 locals init([0]int32X. [1]int32Y. [2] int32 SumXY) IL_0000: Idc.i4.1 IL_0001: stsfld bool Boriand.Deiphi.System.UnitJsConsole IL_0006: ldc.i-4.2 IL_0007: stloc.O IL_0008: Idc.i4.3 IL_0009: sfloc.1 IL_000a: Idloc.O L I_OOOb: ldloc.1 L IJDOOc: add L I_OOOd: stloc.2 IL_000e. Idsfld class Borland.Delphi .System.Text Borland.Delphi System.TextOutput:Output IL_0013: ldloc.2 IL_0014: ldc.i4.0 IL_0015: call instance class Boriand.Delphi.System.TextBorland.Delphi.System.Text::VVritelnteger(int32, int32) IL_001a: call instancevoidBoriand.Delphi.System.TextflushO IL_001f: call voidBorland.Delphi.System.Unit::@_IOTest() IL_0024: Idsfld classBorland.Delphi.Svstem.TextBor1and.Delphi.Systein.Textlnput:lnput IL_0029: call instancevoidBorland.Delphi.System.Text:ReadLn() IL_002e: call voidBorland.Delphi.System.Unit::@_IOTest() IL_0033: ret } // end of method Unit:: Sum
Рис. 2.3. Код программы sum на языке IL
Поскольку в сборках содержится независимый от платформы код IL, а выполняются в конечном итоге команды, рассчитанные на определенную платформу, то ктото должен взять на себя работу по компиляции IL в команды для нужной платформы. Этим занимается компилятор времени исполнения (ЛТ). Компилятор ЛТ входит в состав среды исполнения .NET. Используя код IL, разработчики могут не думать об особенностях архитектуры центрального процессора определенного компьютера — эти особенности будут учтены ЛТ. Откомпилированные из IL платформенно-зависимые команды компилятор ЛТ размещает в кэш-памяти, что значительно ускоряет работу приложения. Например, при первом вызове какого-либо метода ЛТ откомпилирует относящийся к этому методу код IL в платформенно-зависимые команды. При повторных вызовах этого метода ЛТ уже не будет заниматься компиляцией, а просто возьмет готовый код из кэш-памяти.
Типы и пространства имен .NET Сборка может содержать любое количество самых разных типов. В мире .NET тип — это универсальный термин, который может относиться к классам, структурам, интерфейсам, перечислениям и т.д. При создании приложения .NET необходимо организовывать взаимодействие этих типов. Например, сборка может определять класс с несколькими интерфейсами, каждый интерфейс может принимать в качестве параметров значения только определенного типа. Для задания определенного типа необходимо использовать имя, которое должно быть уникальным по отношению к другим типам. И здесь не обойтись без пространства имен, которые группируют стандартные типы и которые можно использовать при создании собственных типов. Пространство имен — это логическая структура для организации имен, используемых в приложении .NET. Основное назначение пространств имен — исключить конфликты между именами в разных сборках. 44
Глава 2. Знакомство с платформой .NET
Например, создается приложение типа VCL Forms Application, которое обращается к двум внешним сборкам. В каждой сборке есть тип с именем МуТуре. Но это абсолютно различные типы по функциональным возможностям и их никак нельзя смешивать. При написании кода можно точно указать, к какому именно типу и из какой сборки вы обращаетесь — достаточно к имени типа добавить имя соответствующего пространства имен (это может быть имя сборки).
Общеязыковая исполняющая среда После знакомства с типами, сборками, метаданными и IL можно более формализовано рассматривать среду исполнения .NET. Общеязыковая исполняющая среда (CLR) — это набор служб, необходимых для выполнения программного кода IL. К таким службам можно отнести и требуемые библиотеки. Например, если создано приложение MFC, то в качестве компонента среды выполнения потребуется весьма объемистая библиотека времени выполнения— mfc42.dll. Программы на Visual Basic привязаны к такому компоненту среды выполнения, как библиотека msvbvm60 . d l l , а программам на Java необходим большой набор файлов, входящих в состав виртуальной машины Java. Своя среда выполнения требуется и приложениям .NET. Главное отличие этой среды выполнения от всех тех, которые были перечислены выше, заключается в том, что единая среда выполнения .NET используется приложениями, написанными на любых языках программирования .NET. Сама CLR состоит из двух главных компонентов. Первый компонент — это ядро среды выполнения, которое реализовано в виде библиотеки m s c o r e e . d l l . При обращении к приложению .NET библиотека m s c o r e e . d l l автоматически загружается в память, и затем она управляет процессом загрузки в память сборки данного приложения. Ядро среды выполнения ответственно за множество задач: оно занимается поиском физического местонахождения сборки, обнаружением внутри сборки запрошенного типа (класса, интерфейса, структуры и т.п.) на основе информации метаданных, компилирует IL в платформенно-зависимые команды, выполняет проверки, связанные с обеспечением безопасности, и делает еше много другой работы. Второй главный компонент CLR — это библиотека базовых классов. Сама библиотека разбита на множество отдельных сборок, однако главная сборка библиотеки базовых классов представлена файлом m s c o r l i b . d l l . В библиотеке базовых классов содержится огромное количество типов для решения распространенных задач при создании приложения. Приложение .NET будет обязательно использовать сборку m s c o r l i b . d l l и по мере необходимости — другие сборки (как встроенные, так и создаваемые вами). На рис. 2.4 представлен путь, который проходит первичный код приложения, прежде чем выполнить какие-либо действия на компьютере.
Стандартная система типов CTS Как уже отмечалось ранее, стандартная система типов (CTS) — это всеобъемлющая спецификация, которая определяет, каким образом какой-либо тип (класс, структура, интерфейс, встроенный тип данных и т.д.) должен быть сформирован для его правильного восприятия средой выполнения .NET. Система CTS описывает синтаксические конструкции (например, перегрузка операторов), которые могут поддерживаться, а могут и не поддерживаться конкретным языком программирования .NET. Если необходимо создавать сборки, которые должны использоваться всеми языками программирования .NET, то при создании типов придется следовать правилам стандартной системы типов. Ниже рассмотрим особенности тех типов, которые соответствуют спецификации CTS.
Классы CTS Концепция классов — краеугольный камень любого объектно-ориентированного программирования. Она поддерживается всеми языками программирования .NET. Класс —
Решения .NET
45
это неограниченный набор полей, свойств, методов и событий, составляющих единое целое. В CTS предусмотрены абстрактные члены классов, что обеспечивает возможность применения полиморфизма в производных классах. Множественное наследование в CTS запрещено. Класс соответствует всем требованиям объектно-ориентированного программирования и может быть абстрактным, иметь область видимости, использовать интерфейсы, а также может быть закрытым для создания производных классов. Компилятор .NET Исходная программа
•
•
•
•
1
Сборка
"1
Ш
тшттшж Ядро исполняющей ' феды Библиотеки базовых классов
Загрузчик классов
Компилятор времени выполнения | Платформеннош' зависимые команды 1
• Выполнение приложения
1 1
Рис. 2.4. Последовательность выполнения программ в среде CRL
Структуры CTS Помимо классов в CTS также представлены и структуры. В первом приближении структуры можно рассматривать как упрощенные разновидности классов. Структуры CTS могут иметь любое количество конструкторов с параметрами (конструктор без параметров зарезервирован). С помощью конструкторов с параметрами можно установить значение любого поля структуры в момент создания этого объекта. В Delphi нет типа, соответствующего структуре платформы .NET. Все структуры CTS произведены от единого базового класса System.ValueType. Этот базовый класс определяет структуру как тип данных для работы только со значениями, но не со ссылками. В структуре может быть любое количество интерфейсов.
46
Глава 2. Знакомство с платформой .NET
Однако структуры не могут быть унаследованы от остальных типов данных и они всегда являются закрытыми — иными словами, они не могут выступать в качестве базовых с целью их наследования. Интерфейсы CTS Интерфейсы — это просто наборы абстрактных методов, свойств и определений событий. В отличие от классической технологии СОМ, интерфейсы .NET не являются производными от единого общего интерфейса, каким в мире СОМ был интерфейс iunknown. В интерфейсах самих по себе смысла не очень много. Однако если известно, что класс реализует определенный интерфейс, мы можем ожидать от этого класса функционального поведения в соответствии с возможностями этого интерфейса. При создании собственного интерфейса можно унаследовать этот интерфейс сразу от нескольких базовых интерфейсов. Члены типов CTS Ранее было отмечено, что классы и структуры могут иметь любое количество членов. Член — это либо метод, либо свойство, либо поле, либо событие. Для каждого члена в .NET существует набор характеристик. Например, любой член в .NET характеризуется своей областью видимости (public, p r i v a t e , p r o t e c t e d и т.д.). Член можно объявить как абстрактный, чтобы воспользоваться возможностями полиморфизма при работе с производными классами. Члены могут быть статическими (такие члены могут совместно использоваться всеми объектами данного класса) и обычными — принадлежащими только одному объекту данного класса. Перечисления CTS Перечисление — это удобная программная конструкция, которая позволяет объединять пары "имя-значение" в группы, к которым затем можно обратиться по имени группы. Предположим, что в программе необходимо выбирать один из трех типов отчетов — стандартный (Standard), расширенный (Enhanced) или секретный (Secure). Для этого в Delphi можно создать следующий тип: type Report = (Standard, Enhanced, Secure); и выбирать отчет, просто вводя осмысленные фразы, например: MyReport := Secure; В CTS все перечисления являются производными от единственного базового класса System.Enum. Этот базовый класс содержит множество полезных членов, которые помогут в работе с парами "имя-значение". Делегаты CTS Делегаты в мире .NET — это безопасные для типов указатели на функции. Но в отличие от других языков программирования, делегат .NET это уже не просто адрес в оперативной памяти, а класс, производный от базового класса M u l t i c a s t Delegate. Делегаты очень полезны в тех ситуациях, когда нужно, чтобы одна сущность передала вызов другой сущности. Делегаты — это краеугольный камень в технологии обработки событий в среде .NET. Встроенные типы данных CTS В среде .NET предусмотрен богатый набор встроенных типов данных, причем этот набор используется всеми языками программирования .NET. Названия типов данных в различных языках .NET могут выглядеть по-разному, но эти названия всего лишь псевдонимы для встроенных системных типов данных .NET, определенных в библиотеке базовых типов. Перечень встроенных типов данных .NET представлен в табл. 2.1.
Решения .NET
47
Таблица 2.1. Встроенные типы данных CTS Тип данных .NET
Тип в Delphi .NET
Тип в VB .NET
Тип в С#
System.Byte System.Sbyte System.Intl6 System.Int32 System.Int64 System.UIntl6 System.UInt32 System.UInt64 System.Single System.Double System.Object System. Char System.String System.Decimal System.Boolean
Byte Shortint Smallint Longint Int64 Word LongWord Single Double Object WideChar WideString Currency Boolean
Byte Short Integer Long Single Double Object Char String Decimal Boolean
byte sbyte short int long ushort uint ulong float double object Char string decimal bool
Как видно из таблицы, не все языки .NET могут работать с некоторыми встроенными типами данных CTS. Поэтому очень важно определить такой набор типов (и программных конструкций), с которым гарантированно смогут работать любые языки программирования в среде .NET. Такой набор есть, и он называется CLS. В табл. 2.2 приведено более полное соответствие примитивных типов Delphi 8 и платформы .NET. Таблица 2.2. Соответствие примитивных типов Delphi 8 и платформы .NET Категория
Тип Delphi
Тип CLR
Групповые типы
Integer Cardinal Shortlnt Smallint Integer Int64 Byte Word LongWord Single Double Extended Comp (соотв. Int64, устаревшее) Currency
System.Int32 System.UInt32 System.SByte System.Intl6 System.Int32 System.Int64 System. Byte System.UIntl6 System.UInt32 System.Single System.Double Borland.Delphi.System.Extended System.Int64
Знаковые типы
Беззнаковые типы
Вещественные типы
Decimal Real(соотв. Double) 48
Borland.Delphi.System. Currency (запись на основе Decimal) System.Decimal System.Double
Глава 2. Знакомство с платформой .NET
Окончание табл. 2.2. Категория
Тип Delphi
Символьные типы
Char _ System. Char WideChar (соотв. Char) System.Char Boolean System.Boolean ByteBool Borland.Delphi.System.ByteBool WordBool Borland.Delphi.System.WordBool LongBool Borland.Delphi.System.LongBool
Булевы типы
Тип CLR
Основы CLS Нет необходимости доказывать, что одни и те же программные конструкции в разных языках выглядят абсолютно по-разному. Например, в С# и Delphi объединение строк (конкатенация) производится с помощью оператора плюс " + " , а в Visual Basic для этой цели используется амперсант "&". Для среды выполнения .NET такая разница в синтаксисе абсолютно безразлична: все равно соответствующие компиляторы создадут одинаковый код IL. Однако языки программирования отличаются не только синтаксисом, но и возможностями. Например, в одних языках программирования разрешена перегрузка операторов, а в других — нет. Одни языки могут использовать беззнаковые типы данных, в других такие типы данных не предусмотрены. Поэтому нужны некие единые правила для всех языков .NET. Если им следовать, то программные модули, написанные на разных языках, будут нормально взаимодействовать друг с другом. Такой набор правил определен в универсальной спецификации языка (CLS). Набор правил CLS не только гарантирует нормальное взаимодействие блоков кода, созданных на разных языках, но и определяет минимальные требования, которые предъявляются к любому компилятору .NET. Необходимо помнить, что CLS — это лишь часть тех возможностей, которые определены в CTS. Правилам CLS должны удовлетворять и инструментальные средства среды разработки — если мы хотим обеспечить межъязыковое взаимодействие, они должны генерировать только такой код, который соответствует требованиям CLS. У каждого правила CLS есть название (например, CLS Rule 6). Вот пример одного из самых важных правил — правило номер 1. Правило 1. Правила CLS относятся только к частям типа, предназначенным для взаимодействия за пределами сборки, в которой они определены. Из этого правила следует, что при реализации какого-либо типа можно сколько угодно нарушать правила CLS — это ни на что не повлияет. Например, можно создать приложение .NET, которое взаимодействует с внешним миром с помощью трех классов, и в каждом из этих классов только одна функция. Правилам CLS в этом случае должны удовлетворять только три этих функции (область видимости, соглашения об именовании, типы параметров и т.д.). Во внутренней реализации этих функций, классов или приложения в целом может быть сколько угодно отступлений от правил CLS.
Пространства имен После знакомства с общеязыковой исполняющей средой .NET обратимся к особенностям библиотеки базовых классов .NET. Важность библиотек кода очевидна. Например, библиотека MFC определяет набор классов C++ для создания форм, диалоговых окон, меню, панелей управления и т.д. В результате программисты могут не заниматься созданием того, что уже давно сделано, а сосредоточиться на уникальных
Пространства имен
49
аспектах разрабатываемого ими приложения. Аналогичные средства существуют в Delphi, Visual Basic, Java и во всех остальных языках программирования. Однако в отличие от этих языков программирования, в языках для среды .NET не существует библиотеки базовых классов только для одного языка. Вместо этого разработчики используют библиотеку базовых типов для всей среды .NET, а для организации типов внутри этой библиотеки используется концепция пространств имен.
Важнейшие пространства имен .NET Эффективность работы программиста, использующего .NET, напрямую зависит от тогЬ, насколько хорошо он знаком с тем множеством типов, которые определены в пространствах имен библиотеки базовых классов. Самое важное пространство имен называется System. В нем определены классы, которые обеспечивают важнейшие функции, и ни одно приложение не обходится без использования этого пространства имен. Пространство имен — это способ организации типов (классов, перечислений, интерфейсов, делегатов и структур) в единую группу. В одном пространстве имен обычно объединяются взаимосвязанные типы. Например, в пространстве имен System.Drawing содержится набор типов, которые призваны помочь в организации вывода изображений на графическое устройство. В .NET предусмотрены пространства имен для организации типов, рассчитанных на работу с базами данных, сетью, многопоточностью, защитой данных и множества других задач. В табл. 2.3 приведены некоторые пространства имен .NET. Таблица 2.3. Пространства имен .NET Пространство имен .NET
Назначение
System
Множество низкоуровневых классов для работы с простыми типами, выполнения математических операций, сборки мусора и т.д.
System.Collections
Классы для работы с контейнерными объектами, такими как A r r a y L i s t , Queue, S o r t e d L i s t
System. Data System. Data.Common System. Data. OleDb System.Data.SqICIient
Классы для работы с базами данных
System.Diagnostics
В этом пространстве имен содержатся многочисленные типы, используемые языками .NET для трассировки и отладки программного кода
System. Drawing System. Drawing. Drawing2D System. Drawing. Printing
Типы для графического интерфейса, растровых изображений, шрифтов, пиктограмм, поддержки печати и специальные классы для вывода сложных изображений
System. 10
В этом пространстве имен объединены типы, отвечающие за операции ввода-вывода в файл, буфер и т.д.
System.Net
Это пространство имен содержит типы, используемые при передаче данных по сети
System. Reflection System. Reflection. Em it System. Runtime. InteropServices System. Runtime. Remoting System.Security
50
Классы, предназначенные для обнаружения, создания и вызова пользовательских типов во время выполнения Средства для взаимодействия с оригинальным кодом (Win32 DLL, СОМ-серверы) и типы, используемые для удаленного доступа В среде .NET средства обеспечения безопасности интегрированы как со средой исполнения, так и с библиотекой базовых типов. В этом пространстве имен находятся классы для работы с разрешениями, криптографией и т.д.
Глава 2. Знакомство с платформой .NET
Окончание табл. 2.3. Пространство имен .NET
Назначение
System.Threading
Это пространство имен для типов, которые используются при работе с потоками
System.Web
Классы, которые предназначены для использования в Webприложениях, включая ASP.NET
System.Windows.Forms
Классы для работы с элементами интерфейса Windows — окнами, элементами управления и прочими типами
System.XML
Множество классов для работы с данными в формате XML
Пространства имен в Delphi В Delphi 8 файлы проекта (программы, библиотеки или пакеты) неявно входят в пространство имен, называемое пространством имен проекта по умолчанию. Модуль может быть или членом пространства имен по умолчанию, или для него можно явно объявить пространство имен. Во втором случае пространство имен объявляется в заголовке модуля. uni t MyCompany.MyWidgets.MyUni t; Обратите внимание, что отдельные пространства имен разделяются точками. Но в данном случае точки не являются разделителями между именами, а являются частью имени модуля. Имя исходного файла для данного модуля должно быть MyCompany .MyWidgets .MyUnit .pas, а имя выходного файла, созданного компилятором, будет MyCompany.MyWidgets.MyUnit.dcuil. Точки в данном случае показывают вложенность пространств имен, а модуль MyUnit является членом пространства имен MyWidgets, которое само включено в пространство имен MyCompany. По умолчанию всегда создаются пространства имен для всех модулей проекта. Рассмотрим следующие объявления. Program MyCompany.Programs.MyProgram; Library MyCompany.Libs.MyLibrary; Package MyCompany.Packages.MyPackage; Таким образом создается пространство имен по умолчанию для проекта. Имя пространства имен получается при удалении имени модуля (правая часть) вместе с точкой. Модуль, в котором нет явного объявления пространства имен, называется групповым модулем, он автоматически становится членом пространства имен (членом группы) проекта. Например, если создать модуль как u n i t MyUnit; то компилятор автоматически включит его в пространство имен MyCompany. Programs. При этом пространство имен по умолчанию не влияет на имена исходных файлов групповых модулей. В предыдущем примере имя файла может быть MyUnit.pas, но при создании файла с расширением . d c u i l компилятор будет использовать пространство имен по умолчанию и будет создан файл с именем MyCompany.Programs.MyUnit.dcuil. Все имена не чувствительны к регистру клавиатуры, но компилятор сохранит имя в его оригинальном виде и будет использовать это имя при создании выходных файлов, сообщений об ошибках и при предоставлении информации о типах времени исполнения. В информацию о типах времени исполнения будут включены полные определения пространства имен.
Поиск пространства имен В модуле могут объявляться (подключаться) другие модули, от которых этот модуль зависит. Как и для платформы Win32, компилятор Delphi 8 для .NET должен идентифицировать эти модули. В случае с явным объявлением имен путь всегда известен, но
Пространства имен
51
если это групповой модуль, то компилятор должен определить область поиска подключенных модулей. Рассмотрим следующий пример модуля с явным объявлением пространства имен и подключенными модулями (для чего используется директива uses). unit MyCompany.Programs.Units.MyUnitl; uses MyCompany.Libs.Unit2, Unit3, Unit4; Здесь модуль MyUnitl является членом пространства имен MyCompany. Programs .Units. Модуль MyUnitl зависит от трех других модулей: модуля Unit2 в пространстве имен MyCompany.Libs и двух групповых модулей Unit3 и Unit4. Компилятор может легко установить полное имя модуля Unit2, но для нахождения полных имен модулей Unit3 и Unit4 компилятор должен произвести поиск среди соответствующих пространств имен. Для поиска используются источники: опции компилятора, объявленные в файле проекта пространства имен, пространства имен по умолчанию и, наконец, текущее пространство имен для данного модуля. В файле проекта можно дополнительно определить список пространства имен поиска для групповых модулей. Объявление пространства имен должно происходить сразу после объявления программы, библиотеки или пакета и перед всеми другими конструкциями или блоками. Пространства имен в списке разделяются запятыми и список должен заканчиваться точкой с запятой. Компилятор будет производить поиск в следующем порядке: • • • •
текущее пространство имен модуля; пространство имен по умолчанию; список пространства имен проекта; пространства имен, указанные в опциях компилятора.
Ниже приведен пример файла проекта и модуля с объявлением списка пространств имен для поиска файла. // Файл проекта. program MyCompany.Programs.MyProgram; namespaces MyCompany.Libs.UlWidgets, MyCompany.Libs.Network; // Файл модуля. unit MyCompany.Programs.Units.MyUnitl; Используя эти объявления, компилятор будет производить поиск в следующем порядке: • • • • •
MyCompany.Programs.Units; MyCompany.Programs; MyCompany.Libs.Network; MyCompany.Libs.UlWidgets; Пространство имен, указанное в опциях компилятора.
Если модуль является групповым (т.е. не имеет явно объявленного пространства имен), то поиск начинается с пространства имен по умолчанию. Подключение модулей можно производить как с полностью указанным пространством имен, так и с указанием только группового имени, при этом удостоверившись, что компилятор сможет найти модуль. В примере ниже показано, как можно подключить модули. unit MyCompany.Libs.MyUni11 uses MyCompany.Libs.Unit2, // Полностью указанное имя. UnitX; // Групповое имя. При этом в программе можно обращаться к элементам модуля как по полному имени, так непосредственно по имени элемента.
52
Глава 2. Знакомство с платформой .NET
uses MyCompany.Libs.Unit2; begin writeln(MyCompany.Libs.Unit2.SomeString); writeln(SomeString); end. При использовании полного имени необходимо включать имя целиком. writeln(Unit2.SomeString); // Ошибка! writeln(Libs.Unit2.SomeString); // Ошибка! writeln(MyCompany.Libs.Unit2.SomeString); // Правильно. writeln(SomeString); // Правильно. Ссылка только на часть пространства имен при подключении модулей также будет ошибкой. uses MyCompany; // Ошибка! Чтобы не набирать очень длинные имена, можно объявлять псевдонимы и использовать их локально в модуле. Сделать это можно так. uses MyCompany.AVeryLongNamespaceDesignation.VeryDescriptiveUnitName as aUnit; После чего на имя можно ссылаться с помощью псевдонима. Ниже приведены два эквивалентных утверждения. writeln(aUnit.SomeString); writeln(MyCoirpany.AVeryLongNamespaceDesignation.VeryDescriptiveUnitName.SomeString);
Как получить информацию Мы рассмотрим различные возможности платформы .NET с использованием пространства имен и содержащихся в них типов, но в одной книге невозможно описать все пространства имен. Необходимо уметь находить информацию о нужных типах и пространствах имен самостоятельно. Удобнее всего использовать для этого утилиту ildasm.exe, о которой уже упоминалось выше, хотя есть и другие средства получения необходимых сведений. Например, с .NET SDK поставляются утилиты ildasm, c l a s s v i e w И wincv.
Дизассемблер ildasm.exe Официальное название i l d a s m . e x e можно перевести как "утилита дизассемблирования промежуточного языка". В дальнейшем будем ее называть просто "дизассемблером". Эта утилита позволяет просмотреть содержимое любой сборки .NET (файла с расширением . d l l или .ехе). Можно изучить манифест, метаданные типов и инструкции IL. При этом все операции производятся с использованием дружественного графического интерфейса. Просто запустите i l d a s m . e x e и с помощью команды меню File^Open откройте нужную сборку. Ранее эта утилита уже использовалась для просмотра сборки Sum (см. рис. 2.2 и 2.3). Как видно, структура сборки представлена в самом обычном формате с деревом и узлами. Каждый метод, свойство, вложенный класс, как и все остальные типы, представлены специальными значками (в текстовом дампе дизассемблера эти значки будут заменены на аббревиатуры, состоящие из трех символов). Помимо просмотра информации о типах и их членах, ildasm позволяет получать сведения о командах IL, относящихся к выбранному типу. Дизассемблер ildasm обладает замечательной возможностью выгружать иерархию исследуемой сборки в текстовый файл, после чего полученный дамп можно изучать в удобной обстановке. Чтобы создать текстовый дамп, откройте нужную сборку, в меню File выберите команду Dump TreeView и укажите имя для создаваемого текстового
Пространства имен
53
файла. Обратите внимание, что графические значки будут заменены соответствующими текстовыми аббревиатурами. В файл можно выгружать не только типы и члены типов исследуемой вами сборки, но и относящийся к объектам сборки код IL. Для этого в ildasm предназначена другая команда: FileoDump. По умолчанию для дампов с инструкциями IL используется расширение * . h. В дизассемблере ildasm есть еще одна удобная возможность. С помощью ildasm можно просматривать метаданные типов, т.е. ту информацию о типах сборки, которую генерирует компилятор для среды исполнения .NET. Для просмотра метаданных типов достаточно загрузить сборку в ildasm и нажать клавиши .
Резюме В этой главе представлены начальные сведения о платформе .NET, необходимые для понимания того, как работает Delphi в этой среде. В основном в книге будет рассматриваться создание проектов типа VCL Form Application, т.е. будут использоваться привычные для Delphi библиотеки, процедуры и функции, но в конечном итоге вы научитесь создавать сборки, которые могут выполняться только в среде .NET. Представление о том, какую роль играют сборки, необходимо для понимания всего цикла создания и выполнения программы.
Контрольные вопросы 1. 2. 3. 4. 5. 6. 7. 8.
54
Назовите недостатки модели СОМ. Какие основные задачи ставились при разработке .NET? Какие технологии лежат в основе .NET? Что такое сборка? Какие расширения могут иметь файлы сборки? Что такое промежуточный язык Microsoft? Что такое универсальная спецификация языка? Как объявляется пространство имен в Delphi? Какие средства используются для получения информации о сборках?
Глава 2. Знакомство с платформой .NET
Глава 3
Язык программирования Delphi В этой главе...
• Ключевые слова Консольные приложения Ключевые слова языка Delphi Типы данных Операторы Ввод-вывод
• Резюме • Контрольные вопросы
основе языка Delphi лежит язык программирования Pascal, который показа.! себя как наиболее универсальный и легко изучаемый язык. При этом его удобно использовать для создания сложных приложений, включая работу с Internet, базами данных, приложения масштаба предприятия. За время своего развития язык Pascal постоянно совершенствовался, и на его основе создан Object Pascal — это наиболее радикальное и удачное расширение возможностей языка. Начиная с версии Delphi 7 язык Object Pascal называется язык Delphi. В этой главе познакомимся с основами языка программирования Delphi, его алфавитом и синтаксисом. Языки программирования разрабатываются для того, чтобы помочь программисту точнее и понятнее представить логику алгоритма с помощью нескольких зарезервированных слов, исходных значений (операндов) и действий над ними (операторов). Понятно, что для отображения логики алгоритма используются различные способы, например, можно все описать обычными словами или использовать уже широко применяемые математические знаки и символы. А можно использовать конструкции и синтаксис языка программирования, наиболее подходящего для этого алгоритма, благодаря которым логика алгоритма будет более понятной, а решение задачи — оптимальным. Многообразие языков программирования связано как со спецификой языка (например, на Lisp или Prolog удобнее выполнять логические операции, а на Fortran — математические расчеты), так и с разработкой новых языков, отвечающих современным потребностям и создаваемых на основе научных исследований. Среди языков программирования наиболее широкое развитие получили универсальные языки, с помощью которых удобно решать как логические задачи, так и вычислительные. При этом универсальный язык программирования должен быть удобен для обработки текстов, изображений, файлов, а составленная программа должна хо-
рошо читаться и пониматься однозначно всеми программистами, чтобы ее в дальнейшем можно было модифицировать. Delphi наилучшим образом отвечает всем этим потребностям, и его изучение начнем с ключевых слов.
Ключевые слова Ключевые, или зарезервированные слова — это как команды для компилятора, на основе которых он готовится к выполнению определенных действий. Например, ключевые слова u n i t (модуль) или programm (программа), расположенные в начале каждого модуля, предупреждают компилятор о начале программы. За словом u n i t или program обязательно должно располагаться имя модуля. Действие ключевого слова распространяется до тех пор, пока не встретится другое ключевое слово. Рассмотрим ключевые слова для консольного приложения, создаваемого по умолчанию при выборе проекта типа Console Application, с которым мы будем работать в этой главе. program P r o j e c t l ; {$APPTYPE CONSOLE} uses S y s U t i l s ; begin { TODO -oUser -cConsole Main: I n s e r t code h e r e } end. Здесь четыре ключевых слова выделены жирным шрифтом. Такое выделение ключевых слов обязательно в редакторе кодов Delphi, хотя для других редакторов это не требуется, и если вы будете писать программу в другом редакторе, то такого выделения, вероятнее всего, не будет. Первое выделенное ключевое слово — programm, которое говорит о начале программы и за которым обязательно должно располагаться имя программы. Затем идет ключевое слово uses (использовать), которое тоже обязательно встречается в любом модуле. За этим словом располагаются имена тех дополнительных модулей, которые будут использоваться в программе. В нашем случае указан только один модуль SysUtils. Это системный модуль, в который, в частности, входят подпрограммы для общения с операционной системой, без них ни одна разрабатываемая программа работать не будет. Если не указать этот модуль, то в работе программы просто не будет смысла, так как нельзя будет распределять память, открывать файлы или делать необходимые преобразования, т.е. нельзя будет использовать возможности операционной системы. Затем идет ключевое слово b e g i n (начало). Это слово говорит компилятору о начале блока выполняемых кодов. После этого слова должен располагаться текст вашей программы. Ключевое слово end. (конец) говорит об окончании блока и одновременно об окончании всей программы, так как после него стоит символ точки (.). Мы не будем обсуждать все ключевые слова, хотя их не так уж и много, удобнее изучать ключевые слова непосредственно в процессе работы с ними. Если есть желание и небольшие знания английского языка, то можно ознакомиться со всеми ключевыми словами в справочной системе, входящей с состав Delphi 8. Для этого щелкните на пиктограмме с изображением справочника на пользовательской панели либо выполните следующие команды меню: Help1^Delphi Help (или нажмите комбинацию клавиш и, перемещая подсвеченное поле, выберите пункт Delphi Help, после чего нажмите клавишу <Enter>). При этом откроется окно справочной системы, где необходимо выбрать вкладку Index и набрать строку "reserved words". Нажмите клавишу <Enter> и получите список всех разделов, содержащих определения ключевых слов. Щелчок на любом разделе приведет к дальнейшему отображению справочной информации о зарезервированных словах. Обратите внимание на директиву {$АРРТУРЕ CONSOLE}. ЭТО директива компилятора, указывающая на то, что компилятор должен создать консольное приложение.
56
Глава 3. Язык программирования Delphi
Обычно в фигурные скобки заключаются комментарии — это тот текст, на который компилятор не обращает внимания и в котором можно описывать все специфические особенности программы. Но если непосредственно за первой открывающейся фигурной скобкой идет символ " $ " , то эта конструкция будет представлять собой директиву компилятора. Теперь поговорим о консольном приложении.
Консольные приложения В первой главе, при ознакомлении с графическим интерфейсом пользователя, для демонстрации примеров использовались приложения типа VCL Form Application. Однако для изучения языка Delphi значительно удобнее использовать приложения типа Console Application (консольное приложение). Здесь не надо думать о графическом интерфейсе, а ввод и вывод на экран информации происходит значительно проще. Поэтому не надо отвлекаться на посторонние вещи, думайте только об особенностях языка. К графическому интерфейсу и приложениям типа VCL Form Application мы обязательно вернемся в следующих главах, так как они используются в подавляющем большинстве программ, написанных на Delphi. Итак, создадим консольное приложение, что делается так же, как и для всех других приложений, которые можно создать несколькими способами. Можно щелкнуть на пиктограмме New Items на панели Standard, можно использовать команды меню File1* New=>Other или последовательно нажать клавиши Ni=>0>. Затем в открывшемся окне New Item необходимо выбрать папку Delphi for .NET, чтобы дважды щелкнуть на пиктограмме Console Application. При этом будет создан новый проект с одной вкладкой редактора кодов со стандартным кодом по умолчанию (см. предыдущий раздел). Вкладки с формой создаваться не будет, так как вывод будет происходить в окно DOS. Для того чтобы лучше понять работу консольного приложения, создадим простейшую программу. Для этого дополните стандартный код, созданный по умолчанию, несколькими выражениями, как показано в приведенном ниже листинге. program P r o j e c t 2 ; {$АРРТУРЕ CONSOLE} uses SysUtils; var a: Cardinal; b : Cardinal; begin { TODO -oUser -cConsole Main : Insert code here } a := 3; b := 4; W r i t e d n t T o S t r ( a ) + ' +' + IntToStr(b) + ' = ' ) ; Write(a+b); Readln; end.
Здесь добавилось ключевое слово var, затем идут объявления переменных а и Ь, которые объявлены как целочисленные значения без знака (натуральные числа), о чем говорит слово C a r d i n a l , которое определяет тип переменной. Тип любой переменной всегда объявляется только таким образом, т.е. после имени переменной ставится двоеточие, а затем идет тип, который может быть как стандартным типом, так и созданным самим пользователем. Когда говорится об объявлении переменной, то имеется в виду, что, встретив код объявления переменной, компилятор создает эту переменную, выделяя место в памяти в соответствии с присвоенным ей типом. Во многих случаях на это место записывается нулевое значение, но лучше всегда самому присвоить вновь созданной переменной необходимое значение.
Консольные приложения
57
Подобным образом следует описать все переменные, используемые в программе, иначе компилятор не сможет разобраться в программе и выдаст сообщение об ошибке. Далее этим переменным присваиваются значения. Это делается в начале блока b e g i n . . . e n d , где размешается разрабатываемая программа. Затем идет процедура w r i t e (Записать). В данном случае необходимо вывести (записать) на экран сумму двух чисел, которым уже присвоены значения. Процедура Readln (Считать) заканчивает программу пользователя. Она необходима для того, чтобы оставить открытым окно DOS, в котором будет отображаться результат сложения (рис. 3.1). Без процедуры Readln окно DOS после вывода результата моментально закроется, так как программа будет завершена, а наличие этой процедуры требует ввода какого-либо значения, что не позволит закончить программу. Нажатия любой клавиши будет достаточно для того, чтобы программа завершилась. •Д"'-'
1
'
ii - . . Г Г ^ к . - ^ i
Рис. 3.1. Окно DOS с результатом сложения двух чисел
Теперь напишем такую же программу сложения двух чисел, в которой слагаемые можно вводить в процессе работы программы. Выглядеть она будет так. program Projectl; {$APPTYPE CONSOLE} uses SysUtils; var a: Cardinal; b: Cardinal; begin { TODO -oUser -cConsole Main : Insert code here } Write('Input a: ' ) ; Read(a); Write('Input b: ' ) ; Read(b); Write('Result: ' ) ; Writeln(a+b); Write('Press any key 1 ); Readln; Readln; end. Если посмотреть внимательнее, то структура программы не изменилась, только вместо присваивания переменным значений появились вызовы процедур Write и Read. Таким образом можно вводить необходимые значения с экрана. После появления надписи i n p u t а: необходимо ввести нужное число и нажать клавишу <Enter>. То же самое сделайте в ответ на запрос i n p u t b : . После нажатия клавиши <Enter> появится результат. В некоторых случаях вместо слова w r i t e используется слово Writeln. Суффикс In необходим, если после ввода 58
Глава 3. Язык программирования Delphi
или вывода значения должен быть сделан переход на новую строку. С аналогичной целью используется и процедура Readln, которая считывает неотображаемые символы перехода на новую строку и установки курсора в начало строки. Сделаем еще одно изменение, чтобы наша программа напоминала простейший калькулятор. program Projectl; {$APPTYPE CONSOLE} uses SysUtils; var a, b, sign: Cardinal; begin { TODO -oUser -cConsole Main : Insert code here } while a0 do begin Write('Input a: ' ) ; Read(a) ; Write('Input b: ' ) ; Read(b); Write('Operation: ') ; Read(sign); if sign=l then begin Write(' **Result: ' ) ; Writeln(a+b); end else begin Write(' **Result: ' ) ; Writeln(a-b); end end; end. Если запустить эту программу на выполнение, то можно постоянно вводить два числа и получать результат, не выходя из программы, как это было раньше. Для этого фрагменты ввода и вывода значений помещены в конструкцию, обеспечивающую циклическое повторение программы, это ключевые слова w h i l e . . .do (пока...делать). После них идет блок повторяющихся кодов, расположенных между ключевыми словами b e g i n . . .end;. После слова end стоит точка с запятой, что говорит о завершении блока, но не о завершении программы. Как видим, условие выхода из цикла определяет выражение а0, т.е. пока а не равно 0, цикл будет постоянно повторяться. Как только а станет равным нулю, управление моментально будет передано строке, находящейся за концом цикла, т.е. end.. Это конец программы. Здесь еще введена третья переменная sign. Это будет конструкция выбора арифметической операции. Если в ответ на запрос " O p e r a t i o n : " ввести 1, то это будет означать суммирование, а любое другое число вводится при вычитании — для этого создана конструкция i f . . . t h e n . . . e l s e (если...тогда...иначе). Чтобы можно было использовать отрицательные значения, я поменял тип переменных — из C a r d i n a l (натуральные) на i n t e g e r (целочисленные, допускающие отрицательные значения). Разработчики Delphi рекомендуют использовать групповые типы C a r d i n a l и I n t e g e r , так как для них оптимизированы математические вычисления, т.е. математические операции с ними выполняются быстрее, чем с другими типами. Эти типы называются групповыми потому, что они Хотя часто приходится использовать и другие типы, например, с целью экономии памяти или если необходимы вещественные числа (числа с плавающей запятой).
Консольные приложения
59
Запустите эту программу и убедитесь, что можно складывать и вычитать два числа в цикле. Выход из программы произойдет, если первому числу присвоить значение 0. Как видите, использование даже самых простейших средств позволило создать работающую и полезную программу. Постарайтесь понять всю программу. Большие и сложные приложения проектируются таким же образом, но, разумеется, в них используется больше специфических конструкций, ключевых слов и операторов, изучением которых сейчас и займемся.
Ключевые слова языка Delphi Прежде чем перейдем к дальнейшему описанию языка Delphi, формально определим несколько терминов. Во-первых, это слово "идентификатор". Идентификатор — это строка символов, используемая для именования некоторого элемента программы. Это может быть переменная, запись, функция, процедура или конструкция более высокого уровня, например сама программа. Идентификатор может иметь любую длину, однако в языке Delphi только первые его 255 символов являются значимыми (что более чем достаточно!). Идентификатор должен начинаться с буквы или символа подчеркивания (_) и не может содержать пробелов. После первого символа идентификатора можно использовать буквы, цифры и символы подчеркивания. Как и в зарезервированных словах, в идентификаторах можно использовать как строчные, так и прописные буквы (компилятор их не различает). Приведенные ниже идентификаторы означают одно и то же. CalculateValue calculateValue calculatevalue CALCULATEVALUE Ключевые слова не могут быть идентификаторами. Далее рассмотрим лексемы. Это минимальные значимые единицы текста в программе. Они представлены такими категориями, как специальные символы, идентификаторы, метки, числа и строковые константы. Программа, написанная на языке Delphi, состоит из лексем и разделителей, причем разделитель представляет собой пробел или комментарий. Две соседних лексемы, если они представляют собой зарезервированное слово, идентификатор, метку или число, должны быть отделены друг от друга одним или несколькими разделителями. В Delphi используются следующие подмножества набора символов кода ASCII. • • •
Буквы английского алфавита от А до Z и от а до z. Цифры — арабские цифры от 0 до 9. Шестнадцатеричные цифры — арабские цифры от 0 до 9, буквы от А до F и буквы от а до f. • Пробелы — символ пробела ($32) и все управляющие символы кода ASCII ($0-$31), включая символ конца строки или символ возврата каретки ($13). Это шестнадцатеричные числа, так как перед ними стоит символ доллара "$". Теперь определим смысл слова "выражение". Это фрагмент языка программирования, представляющий способ вычисления некоторого значения. И наконец, определим смысл слов "операнд" и "оператор". Операнд — часть выражения, над которым производятся операции. Например, в выражении, присваивающем А сумму в и с (А := В+С;), А, в, с являются операндами, а над значениями, представленными идентификаторами А й в , производится операция суммирования.
60
Глава 3. Язык программирования Delphi
Идентификатор — это строка символов, используемая для именования некоторого элемента программы. Лексемы — это минимальные значимые единицы текста в программе. Выражение — это фрагмент языка программирования, представляющий способ вычисления некоторого значения. Операнд — часть выражения, над которым производятся операции. Оператор — действие, которое может быть выполнено над одним или несколькими операндами. Оператор — действие, которое может быть выполнено над одним или несколькими операндами. Если обратиться к вышеприведенному примеру, то оператором является знак плюс (+). Хотя в некоторых случаях оператором можно назвать целое выражение, заканчивающееся точкой с запятой. Более правильно такие операторы надо называть структурированными операторами. Например, выражение w h i l e i : = 0 t o 10 do x := i ; можно назвать оператором, так как здесь выполняется операция цикла над переменной X. Теперь можно переходить непосредственно к ключевым словам. Обычно ключевые слова пишутся строчными буквами, но Delphi безразличен к регистру клавиатуры, поэтому можно использовать в своей программе как строчные (нижний регистр), так и прописные (верхний регистр) буквы. Я рекомендую использовать какой-то один стиль написания, например, тот, к которому вы уже привыкли. Но если вы только начинаете программировать, то лучше использовать общепринятые правила и писать ключевые слова строчными буквами. В табл. 3.1 приведен перечень всех ключевых слов с кратким комментарием. Таблица 3.1. Ключевые слова Ключевое слово
Комментарий
and
Булев оператор И
array
Массив
as
Используется при проверке соответствия типов, определяет объект как операнд
asm
Используется для выделения ассемблерного кода
begin
Начало блока
case
Оператор выбора. Используется при выборе из многих вариантов
class
Определяет тип "класс"
const
Определяет константы, т.е. неизменяемые переменные. Однако в Delphi есть режим, допускающий изменение констант в теле программы
constructor
Специальный метод класса, необходимый для создания и инициализации экземпляра класса (объекта)
destructor
Специальный метод класса, необходимый для разрушения объекта
dispinterface
Определяет тип интерфейса
div
Целочисленное деление
do
Определяет начало исполнимой части в операторах цикла, конструкции t r y . . . except и в операторе w i t h
downto
Определяет направление итерации в операторе f o r
else
Используется в операторах выбора case, условном операторе i f и в операторе проверки исключений t r y . . . e x c e p t
Ключевые слова языка Delphi
61
Продолжение табл. 3.1. Ключевое слово
Комментарий
end
Обычно используется совместно с ключевым словом b e g i n и отмечает конец блока. Также ставится в конце описания типа, например класса или записи
except
Используется в операторе проверки исключений t r y . . . except
exports
Определяет список экспортируемых процедур, функций и переменных
file
Устанавливает тип переменной как файл. Используется при работе с файлами
finalization
Определяет начало раздела, который в программе всегда выполняется последним
finally
Используется в операторе проверки исключений t r y . . . f i n a l l y
for
Используется в операторах цикла f o r . . . t o и f o r . . .downto
function
Используется при объявлении функций
goto
Переход на метку
if
Используется в операторах выбора i f . . . t h e n и i f . . . t h e n . . . e l s e
implementation
Определяет раздел реализации, в котором находятся описания процедур, функций, методов и коды разрабатываемой программы
in
После этого ключевого слова может указываться путь к необходимому модулю. Также используется при работе с множествами
inherited
Дословно можно перевести как "унаследованный". Используется при работе с классами, поддерживая возможности полиморфизма
initialization
Определяет раздел инициализации, который всегда располагается перед разделом f i n a l i z a t i o n . Если раздела f i n a l i z a t i o n нет, то раздел инициализации находится перед завершением программы. Выполняется сразу после запуска программы, перед всеми другими операторами. Обычно используется для инициализации переменных
inline
Используется при работе с ассемблерным кодом. Устаревшее ключевое слово к применению не рекомендуется
interface
Определяет тип интерфейса. Используется при опережающем объявлении интерфейса
is
Используется при проверке типов
label
Метка. Используется совместно с ключевым словом goto. Может быть выражена любым идентификатором или числом от 0 до 9999
library
Директива-напоминание или рекомендательная директива. Используется наравне с директивами p l a t f o r m и d e p r e c a t e d для напоминания об особенностях стандартных типов, методов или модулей. Во время компиляции вызывает появление предупреждающего сообщения
mod
Остаток от деления целых чисел
nil
Специальная константа, которая может быть присвоена любому указателю, после чего считается, что указатель не ссылается ни на что
not
Булев оператор отрицания
object
Используется как альтернатива слову c l a s s . Сохранено в языке для совместимости со старыми версиями. Не рекомендуется к использованию
of
Используется во многих операторах как связующее ключевое слово
or
Булев оператор ИЛИ
out
Используется при объявлении параметров процедуры, функции или метода. Предупреждает о том, что данный параметр используется только для выдачи значений
62
Глава 3. Язык программирования Delphi
Окончание табл. 3.1. Ключевое слово
Комментарий
packed
Используется для более плотного размещения данных в структурированных типах (массивы, множества, записи, файлы, классы)
procedure
Используется при объявлении процедур
program
Определяет имя программы, которое должно быть выражено идентификатором
property
Используется при объявлении свойств
raise
Используется при генерации исключений
record
Определяет тип записи
repeat
Используется в операторе цикла r e p e a t . . . u n t i l
resourcestring
Определяет раздел объявления ресурсов
sealed
Используется при объявлении класса, запрещая наследование данного класса
set
Ключевое слово для объявления множества
shl
Логический оператор сдвига влево
shr
Логический оператор сдвига вправо
string
Используется при объявлении строковых типов
then
Используется в операторах i f . . . t h e n и i f . . . t h e n . . . e l s e
threadvar
Используется при разработке многопоточных приложений
to
Используется в операторе f o r . . . t o
try
Используется в операторе проверки исключений t r y . . . f i n a l l y , t r y . . .except и в операторе выбора case
type
Определяет раздел объявления типов
unit
Модуль. Обычно это функционально законченный фрагмент программы, сохраняемый в файле с таким же именем
until
Используется в операторе r e p e a t . . . u n t i l
uses
Определяет раздел подключаемых модулей
var
Определяет раздел переменных
while
Используется в операторе w h i l e . . . do
with
Используется для определения идентификатора, который всегда записывается с другими идентификаторами. Код получается более компактным и понятным
xor
Булев оператор Исключающее ИЛИ
Есть еще несколько ключевых слов, о которых мы поговорим при изучении объектноориентированного программирования и которые могут использоваться как директивы. Именно эти ключевые слова выделяются жирным шрифтом в редакторе кода, хотя кое-что может вызвать недоумение: почему, например, слово Break, которое в других языках программирования является ключевым, в редакторе кодов не подсвечивается? В Delphi это не ключевое слово, а процедура, и для нее существует отдельное описание в библиотеке. А процедура w r i t e не описана в библиотеках, так как ее код встроен в компилятор. Сейчас мы не будем разбираться в этих тонкостях, а только отметим, что все ключевые слова обычно пишутся строчными буквами, а процедуры обычно начинаются с прописной буквы. Написание идентификаторов также можно сделать нагляднее, если использовать слова, отражающие назначение идентификатора, и начинать каждое слово с прописной буквы. Например, идентификатор для счетчика символов можно написать так: SymbolCounter.
Ключевые слова языка Delphi
63
Здесь слова использованы полностью, а разделение достигается за счет того, что второе слово начинается с большой буквы. Каждый, кто будет читать программу, где использованы подобные идентификаторы, сможет понять ее без дополнительных комментариев. Используйте это правило, и ваши программы станут понятнее и для вас самих. (К сожалению, все идентификаторы должны писаться только английскими буквами, поэтому учите английский, в настоящее время это язык общения программистов.)
Типы данных При описании переменной следует указать ее тип. Знание типа переменной необходимо для определения набора значений, которые она может принимать, и действий, которые над ней можно выполнять. Для обозначения типа используют идентификаторы. Ранее мы уже познакомились с такими типами, как натуральные и целые числа, обозначаемые идентификаторами C a r d i n a l и I n t e g e r . Все типы можно разделить на шесть основных подразделов: • • • • • •
простой тип; строковый тип; структурный (структурированный) тип; указательный тип; процедурный тип; вариантный тип.
Простые типы Простые типы определяют упорядоченные множества значений. Это может быть или множество натуральных чисел (тип Cardinal), или множество целых чисел (тип i n t e g e r ) , оптимизированных для работы с 32-разрядными приложениями, а также базовые целочисленные типы, перечисленные в табл. 3.2. Таблица 3.2. Целочисленные типы Тип
Диапазон
Формат
Integer
от -2147483648 до 2147483647
Знаковый 32 бита
Cardinal
от 0 до 4294967295
Беззнаковый 32 бита
shortint
от-128 до 127
Знаковый 8 бит
Smallint
от-32768 до 32767
Знаковый 16 бит
Longint
от -2147483648 до 2147483647
Знаковый 32 бита
Int64
о т - 2 6 3 до 2 6 3 -1
Знаковый 64 бита Беззнаковый 8 бит
Byte
от 0 до 255
word
от 0 до 65535
Беззнаковый 16 бит
Longword
от 0 до 4294967295
Беззнаковый 32 бита
Целочисленные типы можно отнести и к порядковым типам. Порядковые типы представляют собой подмножество простых типов. Все простые типы, отличные от вещественных типов, являются порядковыми и имеют следующие четыре характеристики. 1. Все возможные значения данного порядкового типа представляют собой упорядоченное множество, и каждое возможное значение связано с порядковым но-
64
Глава 3. Язык программирования Delphi
мером, который представляет собой целочисленное значение. За исключением значений целочисленного типа, первое значение любого порядкового типа имеет порядковый номер 0, следующее значение имеет порядковый номер 1 и т.д. Порядковым номером значения целочисленного типа является само это значение. В любом порядковом типе каждому значению, кроме первого, предшествует другое значение, и после каждого значения, кроме последнего, следует другое значение в соответствии с упорядоченностью типа. 2. К любому значению порядкового типа можно применить стандартную функцию Ord, возвращающую порядковый номер этого значения. 3. К любому значению порядкового типа можно применить стандартную функцию Pred, возвращающую порядковый номер предшествующего значения. Если эта функция применяется к первому значению в этом порядковом типе, то выдается сообщение об ошибке. 4. К любому значению порядкового типа можно применить стандартную функцию Succ, возвращающую порядковый номер последующего значения. Если эта функция применяется к последнему значению в этом порядковом типе, то выдается сообщение об ошибке. Например, если написать следующий оператор: W r i t e ( P r e d ( 3 ) ) ; , то на экране отобразится число 2, а оператор Write (Succ (3)),- отобразит число 4. В небольшой программе, приведенной ниже, объявлена порядковая переменная х, которая может принимать значения от 1 до 5 (две точки, использованные при объявлении, говорят о том, что переменная может принимать любое значение только из этого диапазона). program Projectl; {$APPTYPE CONSOLE} var x: 1..5; { x может принимать значения 1,2,3,4,5. } begin x := 3; Writeln(x); Writeln(Pred(x)); Readln; end. В таком виде программа будет работать хорошо, и на экран будут выведены два числа: 3 и 2. Но если сделать присваивание х:=6, то компилятор зафиксирует ошибку.
Булев тип Значения булева типа обозначаются встроенными идентификаторами констант F a l s e и True. Поскольку булев тип (Boolean) является перечислимым, то имеют место следующие отношения. F a l s e < True Ord(False) = О Ord(True) = 1 S u c c ( F a l s e ) = True Pred(True) = False
Символьный тип Множеством значений этого типа являются символы, упорядоченные в соответствии с расширенным набором символов кода ASCII. При вызове функции Ord(Ch), где аргумент Ch — значение символьного типа, возвращается порядковый номер Ch.
Типы данных
65
Строковая константа длиной 1 Любое значение символьного типа функции Chr. Например, функция символ "п", а функция W r i t e ( 0 r d (
может обозначать константу символьного типа. может быть получено с помощью стандартной вывода Write (Chr (110)),- отобразит на экран ' п ' ) ) ,- отобразит значение 110.
Перечислимый тип Перечислимые типы определяют упорядоченные множества значений через перечисление идентификаторов, которые обозначают эти значения. Упорядочение множеств выполняется в соответствии с последовательностью, в которой перечисляются идентификаторы. Все идентификаторы в списке перечислимого типа описываются как константы. Типом этой константы является описанный перечислимый тип. Порядковый номер перечислимой константы определяется ее позицией в списке идентификаторов. Первая перечислимая константа в списке имеет порядковый номер 0. Например, перечислимый тип для мастей карт должен быть описан так. type s u i t = ( c l u b , diamond, h e a r t , s p a d e ) ; Согласно определению, diamond является константой типа s u i t с порядковым номером 1. Если применить функцию Ord к значению перечислимого типа, возвратится целое число, определяющее его по отношению к другим значениям этого типа. Например, Ord(club) возвращает 0, Ord(diamond) возвращает 1 и т.д.
Вещественный тип К вещественному типу относится подмножество вещественных чисел, которые могут быть представлены в формате с плавающей запятой с фиксированным числом цифр, как показано в табл. 3.3. Таблица 3.3. Фундаментальные вещественные типы Тип Real48 Single Double Extended Comp Currency
Диапазон
Значащие цифры Размер в байтах
OT2.9*10"39flo1.7*10M OTi.5*10""5flo3.4*1038
11-12 7-8 15-16 19-20 19-20 19-20
от5.0*10' 3 2 4 до1.7*10 3 0 8
от 3.6*10'4951 до 1.Г10 4932 от -2" 63 +1 до 2 63 -1 от -922337203685477.5808 до 922337203685477.5807
6 4 8 10 8 8
Типы Real48 в среде .NET уже не используется. Тип Сотр не рекомендуется использовать в среде .NET и он сохранен только для обратной совместимости с более ранними версиями языка Delphi. Тип Real называется групповым типом для вещественных типов и в .NET он эквивалентен типу Double. Групповые типы служат для удобства использования вещественных типов и в разных версиях они могут соответствовать различным типам. Директива компилятора {$REALCOMPATIBILITY ON} преобразует тип Real в шестибайтовый тип, но лучше этими возможностями не пользоваться, так как замедлится выполнение программы. Тип Extended (расширенный) используется для получения более точных вычислений, но он плохо совмещается с различными платформами.
66
Глава 3. Язык программирования Delphi
Тип c u r r e n c y (коммерческий) используется для финансовых вычислений, так как разработан специально с целью исключения ошибок при проведении денежных расчетов. Он преобразуется в тип Decimal для среды .NET. Ниже приведены примеры объявления переменных отдельных типов. var realType: Real; curencyType: Currency;
Строковый тип Значением строкового типа является последовательность символов с динамически изменяемой длиной. Текущее значение атрибута длины можно получить с помощью стандартной функции Length, а с помощью процедуры SetLength можно установить длину строки. Основные строковые типы перечислены в табл. 3.4. Таблица 3.4. Строковые типы Тип
Максимальная длина Требуемая память
Использование
shortstring
255 символов
от 2 до 256 байт
Обратная совместимость
AnsiString
2 3 1 символов
от 4 байт до 2 Гбайт
8-разрядные символы ANSI, DBCS ANSI, MBCS ANSI и т.д.
Widestring
2 3 0 символов
от 4 байт до 2 Гбайт
Символы Unicode; многопользовательские серверы и многоязычные приложения
Тип A n s i S t r i n g часто называют длинной строкой, и он является предпочтительным для большинства приложений. Групповой тип s t r i n g используется как обозначение типа для A n s i S t r i n g . Например: var S: s t r i n g ; После такого объявления переменная s будет иметь тип AnsiString. При этом должна использоваться директива компилятора {$н+} (обычно она установлена по умолчанию). Если использовать директиву {$Н-}, то слово s t r i n g будет относиться к S h o r t s t r i n g . В .NET груповой тип string используется для обозначения типа System. String, который является классом и имеет методы для работы со строками. Отношение между любыми двумя строковыми значениями устанавливается согласно отношению порядка между значениями символов в соответствующих позициях. В двух строках разной длины каждый символ более длинной строки без соответствующего символа в более короткой строке принимает значение "больше"; например, 1 x s ' больше, чем ' х ' . Нулевые строки могут быть равны только другим нулевым строкам и являются наименьшими строковыми значениями. К символам в строке можно обращаться как к элементам массива. Ниже приведено консольное приложение с использованием строк. program Projectl; {$АРРТУРЕ CONSOLE} uses SysUtils; var SI: string; S2: string;
Типы данных
67
begin 51 := 'Это строка'; 52 := SI; Writeln(S2); Write(Length(S2)); Readln; end. Здесь объявлены две строковые переменные s i и S2. Первой переменной присвоено значение ' Это строка'. Затем значение первой переменной присваивается второй переменной и делается вывод значения второй переменной на экран. Также выводится значение длины второй строки, которое отобразит число 10. Если запустить это приложение на выполнение, то в некоторых случаях на экране вместо слов "Это строка" появятся абсолютно нечитаемые символы. Это связано с тем, что используемая DOS не русифицирована. Если создать графический интерфейс пользователя и сделать вывод через него, то все будет в порядке. Для того чтобы сделать осмысленный вывод в консольном приложении, пользуйтесь английским алфавитом, или установите программу русификации DOS. При компиляции программ с среде Delphi 8, разработанных на основе библиотек VCL, необходимо учитывать несовпадение строковых типов. В табл. 3.5 приведено соответствие строковых типов VCL и CLR. Таблица 3.5. Соответствие строковых типов VCL и CLR Категория
Тип Delphi
Тип CLR
Строки
string AnsiChar ShortString AnsiString WideString
System.String Borland.Delphi.System.AnsiChar Borland.Delphi.System.ShortString Borland.Delphi.System.AnsiString System.String
Также необходимо учитывать структуру строк, используемых на платформе .NET. Строки в .NET являются постоянными, и при конкатенации происходит не классическое объединение строк, а создание новой строки, что значительно замедляет работу программы, где производятся многочисленные операции со строками. В таких случаях лучше использовать класс S t r i n g B u i l d e r , который специально создан в .NET для работы со строками.
Структурные типы Структурные типы характеризуются методами структурирования, типами своих компонентов и имеют больше одного значения. Структурные типы могут иметь неограниченное число уровней. Слово packed (упакованный) в описании структурного типа требует от компилятора уплотнить хранимые данные, даже за счет уменьшения скорости доступа к элементам в переменной этого типа.
Тип array Тип a r r a y (массив) содержит фиксированное число элементов одного типа. Для доступа к элементам массива используются индексы. Массив может иметь неограниченное число размерностей, и для каждой размерности массива указывается число элементов. Допустимыми индексными типами являются все порядковые типы, диапазон которых не превышает 2 Гбайта. Массив может быть проиндексирован по каждой
68
Глава 3. Язык программирования Delphi
размерности всеми значениями соответствующего индексного типа, поэтому число элементов равно числу значений в каждом индексном типе. Объявление переменной МуАггау типа a r r a y может выглядеть так. var МуАггау: array[1..100] of Char; Можно создать тип двумерного массива следующим образом: type TMatrix = a r r a y [ 1 . . 1 0 ] of a r r a y [ 1 . . 5 O ] of Real; Такое объявление эквивалентно следующему: type TMatrix = array[1..10, 1..50] of Real; В данном случае удобно считать, что тип элемента в типе a r r a y также является массивом. Трехмерный массив можно описать так: array[Boolean] of a r r a y [ 1 . . 1 0 0 ] of a r r a y [ S i z e ] of Real что интерпретируется компилятором точно так же, как массив: array[Boolean, 1..10, Size] of r e a l Чтобы создать упакованный массив, необходимо написать packed a r r a y [ 1 . . 1 0 ] of packed a r r a y [ 1 . . 8 ] of Boolean ИЛИ packed
' array[1..10,
1..8]
of
•.
•
Boolean
Для доступа к элементам массива необходимо указать идентификатор массива с одним или несколькими индексами в квадратных скобках. Например, выражение Matrix[2,25] обеспечит доступ к 25-му элементу второй строки. То же самое мы получим, если напишем Matrix[2] [25]. Тот тип массива, который был рассмотрен выше, называется статический массив. Размер массива (число элементов) задан при объявлении и менять его в процессе работы программы нельзя. Но чаще всего необходимо иметь массив, размеры которого не известны до начала работы программы, причем они должны будут меняться в процессе работы. Это необходимо как для экономии памяти, так и для удобства работы, поэтому разработан тип динамический массив. Динамический массив в чем-то проще статического, так как его можно индексировать только целочисленными значениями, которые всегда начинаются с 0. Его объявление происходит следующим образом. var MyFlexibleArray: a r r a y of Real; Как видно, здесь не задаются размеры массива — они устанавливаются в процессе работы с помощью процедуры SetLength. SetLength(MyFlexibleArray, 20); Таким образом массив будет состоять из 20 элементов типа Real. При необходимости это значение можно изменить с помощью той же процедуры. Следует отметить, что статический массив работает быстрее, так как его значения размещаются в стеке, т.е. к ним обеспечивается быстрый доступ. Элементы динамического массива размещаются в куче, и доступ к ним происходит медленнее, но об этих моментах поговорим позже. Сейчас рассмотрим простую программу. program Projectl; {$APPTYPE CONSOLE} uses SysUtils; var arrStat: array [-1. . l-,--2 . . 2] of Integer; arrDyn: array of Byte; i, j: Integer; begin for i:=-l to 1 do
Типы данных
69
for j : = - 2 t o 2 do a r r S t a t [ i ] [ j ] := i + j ; for i : = - l t o 1 do for j : = - 2 t o 2 do Writeln(arrStat[i,j]); Writeln; SetLength(arrDyn, 5 ) ; Writeln(Length(arrDyn)); for i : = 0 t o 4 do arrDyn[i] := i ; for i : = 0 t o 4 do Writeln(arrDyn[i]); Readln; end. Здесь объявлены два массива: статический с элементами типа I n t e g e r и динамический с элементами типа Byte. Для статического массива заданы произвольные границы диапазонов. Сначала статический массив заполняется значениями (это циклы for), а затем эти значения выводятся на экран (тоже в цикле for). Затем то же самое происходит и с динамическим массивом. Длина динамического массива устанавливается равной 5, т.е. в нем может разместиться не более 5 элементов с индексами от О до 4. Выражение W r i t e l n (Length (arrDyn)) ; возвратит значение 5. В данном случае можно выделить еще одно преимущество языка Delphi — проверка границ диапазонов массивов. Например, в языке C++ такой проверки нет, и поиск ошибок, связанных с выходом за границы диапазона, затруднителен. Такие ошибки могут проявляться достаточно спонтанно, и их трудно связать с нарушением границы диапазона. Например, может произойти зависание программы в самом неожиданном месте. Встроенную в Delphi проверку диапазонов можно включать и отключать, для чего необходимо выполнить команду меню Projects^Options... или нажать клавиши . В открывшемся окне Project Options For (рис. 3.2) выберите вкладку Compiler и в блоке Runtime errors установите флажок для переключателя Range checking.
- Code generation —- . ; p Optmijation
{ Compe lir Messages Ln i ker Directories/Conditonals 3 Debugger Remote : • Envrionment Bo l ck
: in
•*} Record field aSgnment
: Syntax opttons ' • I? Strict var-strings P Complete boolean eval 'Emended syntax d@> operator"
; ;
Runtm i e errors : Г Range W I/O Г weiftw 1 I aging f? Debug information f? Local symbols '9 Reference info . p Definitions опт* P Assertions Г Use Debug DCUlLs
Documentation :• Г* Generate XM Documentation
Г Default
Cancel _i_i
v •;• ';
Рис. З.2. Вкладка с опциями компилятора
Теперь попробуйте ввести неправильное значение (например, сделать цикл не до 4, а до 5) и запустите программу. Появившееся диалоговое окно сообщит об ошибке.
70
Глава 3. Язык программирования Delphi
Во время проектирования проверку диапазона необходимо включать, но после отладки программы ее лучше выключить, при этом дополнительные коды проверки диапазона будут убраны и программа будет работать быстрее.
Тип record Тип r e c o r d (запись) содержит установленное число элементов или полей, которые могут быть различных типов. В описании записи указывается тип каждого поля и идентификатор, который именует поле. Ниже приведен пример объявления типа record. type TDate = r e c o r d year: Integer; month: 1. .12; day: 1..31; end; Ключевое слово r e c o r d определяет начало записи, в конце которой находится ключевое слово end. Между этими словами определены поля. В данном случае это поле y e a r типа i n t e g e r и поля с порядковыми переменными month и day, принимающие значения 1. . 12 и 1. . 31 соответственно. Этой записи присвоено имя TDate. Новый тип для даты можно объявить и так: type TDateRec = record Year: Integer; Month: (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec) ; Day: 1..31; end; и использовать ее в своих программах, поскольку датами приходится пользоваться довольно часто. Единственное отличие от более раннего примера — здесь для поля Month использован перечислимый тип. Например, можно объявить переменную типа TDateRec: var R e c o r d l : TDateRec; и присвоить ей значения следующим образом: with Recordl do begin Year := 1904; Month := J u n ; Day := 16; end; Здесь использована конструкция с ключевым словом with, за которым идет идентификатор, используемый со всеми остальными идентификаторами. Без этой конструкции присваивание необходимо делать так: Recordl.Year := 1904; Recordl.Month := Jun; Recordl.Day := 16; Таким образом, сначала идет имя переменной, а за ней, через точку, имя поля. Это менее наглядно, и код получается весьма громоздким, особенно если в записи много полей. В записи можно применить конструкцию, которая позволяет в одном поле размещать различные типы. Такую конструкцию выделяют как вариантную часть. Рассмотрим следующую программу.
Типы данных
71
program Projectl; {$APPTYPE CONSOLE} type TRecl = record first: Integer; case Integer of 1: (second: Word); 2 : (three: Char) ; end; var TestRecord: TRecl; begin TestRecord.first := 5; TestRecord.second := 110; Writeln(testRecord.first); Writeln(testRecord.three); Readln; end. Здесь объявляется новый тип TRecl типа r e c o r d , где для второго поля используется конструкция с ключевым словом case, т.е. в одном поле может размещаться либо переменная типа word, либо переменная типа c h a r (это видно из программы). После объявления переменной TestRecord типа TRecl ей были присвоены значения. Для второй переменной было введено значение 110, но оно было считано как символ, и на экране отобразилась буква "п", т.е. значение 110 было считано как буква "п". Это удобно использовать для преобразования типов, не прибегая к прямым методам приведения типов. Такой способ также используют, если необходимо сделать выбор из множества элементов. Например: type TShapeList = (Rectangle, Triangle, Circle, Ellipse, Other); TFigure = record case TShapeList of Rectangle: (Height, Width: Real); Triangle: (Sidel, Side2, Angle: Real); Circle: (Radius: Real); Ellipse, Other: (); end;
Тип class Тип c l a s s (класс) является структурой, состоящей из фиксированного числа компонентов. Каждый компонент класса является либо полем, содержащим данные строго определенного типа, либо методом (процедурой или функцией). По аналогии с описанием переменных, в описании поля указывается тип данных этого поля и дентификатор, именующий поле: по аналогии с описанием процедуры или функции, описание метода указывает заголовок процедуры, функции, конструктора или деструктора. Класс может наследовать компоненты другого класса. Если Т2 наследует Т1, то принято говорить, что Т2 является потомком или наследником Tl, a T1 является родительским для Т2. Часто говорят, что Т2 является производным от Tl, a T1 является базовым для Т2. Можно еще сказать и так: Т1 — это суперкласс для Т2, если Т2 — непосредственный наследник Т1. Наследование называют транзитивным, например, если ТЗ наследует от Т2, а Т2 наследует от Tl, то ТЗ наследует и от Tl. Но об этом поговорим подробнее, когда будем изучать методы объектно-ориентированного программирования.
72
Глава 3. Язык программирования Delphi
Тип set В данном случае, как и для приведенных выше типов, также можно говорить вместо "тип s e t " просто "множество". Этот математический термин вполне применим в энном случае, так как этот тип s e t представляет именно множество, и над ним можно производить все те же действия, что и над математическими множествами. Диапазон значений множества представляет собой мощность множества для определенного порядкового (базового) типа. Каждое возможное значение множества является подмножеством возможных значений базового типа. Но в Delphi количество значений базового типа не должно превышать 256, и порядковые значения верхней и нижней границы базового тила не должны превышать диапазона от 0 до 255. Поэтому базовый тип множества не может быть коротким целым ( s h o r t i n t ) , целым ( I n t e g e r ) , длинным целым (Longint) или словом (Word). Переменная для множества может принимать все значения множества либо не принимать ни одного. Ниже приводятся примеры объявления множества и операции над множествами. Для обозначения множества используются ключевые слова s e t of, за которыми определяется тип элементов множества. Множество можно создать так: type TSomelnts = 1..250; TIntSet = set of TSomelnts; или так: type T I n t S e t = s e t of 1..250; А объявить переменную для множества и присвоить ей значения можно таким образом: var Setl, Set2: TIntSet; Setl := [1, 3, 5, 7, 9]; Set2 := [2, 4, 6, 8, 10] Можно использовать две точки (. .) для задания диапазона. var MySet: s e t of ' a ' . . ' z ' ; MySet := [ ' a ' , ' b ' , ' c ' ] ; Еще несколько примеров корректных множеств. s e t of Byte set of (Club, Diamond, Heart, Spade) set of Char; Для проверки наличия определенного элемента в составе множества используется ключевое слово in. if 'a' in MySet then ... { что-то сделать };
Тип file Как и для остальных структурных типов, вместо слов "тип f i l e " можно говорить просто "файл". Следовательно, о переменной F i l e l типа f i l e можно сказать: "Файл F i l e l " , что обычно значительно удобнее. Создать файл можно следующим образом: var Filel: file; Это самое общее определение файла, такие файлы называют нетипизованными. Можно конкретизировать тип файла, если добавить к слову f i l e ключевое слово of, за которым определить тип компонентов файла (это тип, который записывается или
Типы данных
73
считывается при обращении к файлу). Компоненты файла могут иметь любой тип, за исключением типа f i l e (т.е. компонентами файла не могут быть сами файлы), или структурного типа, содержащего компонент с типом f i l e . В определении не устанавливается число компонентов. Например, имеем запись PhoneEntry, определяющую имя и номер телефона. type TPhoneEntry = record FirstName, LastName: string[20); PhoneNumber: string[15],• Listed: Boolean; end; Для записи этой информации в файл можно объявить (создать новый тип) файл TPhoneList (Номера телефонов) типа TPhoneEntry TPhoneList = f i l e of TPhoneEntry; или непосредственно описать файловую переменную типа TPhoneEntry. var Listl: file of TPhoneEntry; Но об этом поговорим подробнее, когда будем рассматривать вопросы вводавывода информации.
Тип pointer По-русски такой тип можно назвать указательным типом, или указателем. Он определяет значения, которые не являются непосредственно используемыми величинами, а указывают на переменные определенного типа, размещенные в памяти. Таким образом, переменная указательного типа содержит адрес переменной в памяти. Если тип, на который указывает указатель, является еще не описанным идентификатором, то он должен быть описан в той же самой части описания типов, где и указатель. Указателю можно присвоить значение с помощью процедуры New, операции присваивания ссылки (@) или функции P t r . Процедура New отводит новую область памяти в динамически распределяемой области (куче) для динамических переменных и сохраняет адрес этой области в указателе. Операция присваивания ссылки (@) ориентирует указатель на область памяти, содержащую существующую переменную, включая и те переменные, которые имеют идентификаторы. Функция P t r устанавливает указатель на определенный адрес в памяти. Ключевое слово n i l обозначает константу со значением указателя, которая ни на что не указывает (пустой указатель). Например, указатель на целочисленную переменную может быть объявлен следующим образом. type PTypelnt = лInteger; Здесь символ " л " используется для того, чтобы переменную PTypelnt объявить как указатель на целочисленное значение. Дальше можно создать переменную указательного типа, которой затем присвоить значение. var pointlnt: PTypelnt; Посмотрите, как это сделано в примере. type Pint = " I n t e g e r ; var x: I n t e g e r ; Pointerlnteger: Pint; begin
74
Глава З. Язык программирования Delphi
х := 3; Pointerlnteger := @x; {указателю присваивается адрес х } Writeln(Pointerlnteger~); Readln; end. Переменная х типа i n t e g e r получает значение 3. Указателю P o i n t e r L n t e g e r присваивается значение адреса переменной х. Затем на экран выводится выражение P o i n t e r L n t e g e r ^ , в данном случае это надо понимать как вывод значения, на которое указывает указатель P o i n t e r L n t e g e r . О том, что надо вывести значение не самого указателя, а то значение, на которое указывает указатель, говорит символ " Л " , размещенный после указателя. На экране будет отображено значение 3. Здесь пропущено начало программы, но, как вы уже имели возможность убедиться, оно одинаково для всех программ типа Console Application. В дальнейшем, если нет особой необходимости, листинги будут приводиться без начальной части. Когда объявляется новый тип, то принято идентификатор типа начинать с буквы "т". Например: TNewType. При объявлении указателя идентификатор принято начинать с буквы "р": PNewPointer. Хотя это и не обязательно, лучше придерживаться этого правила. Встроенный тип PChar обозначает указатель на строку, которая завершается нулем. Тип PChar описывается следующим образом. type PChar = AChar; Delphi для Windows поддерживает набор правил расширенного синтаксиса, облегчающих работу со строками с завершающим нулем, с помощью типа PChar. В системных модулях System и S y s U t i l s объявлены некоторые стандартные указатели (табл. 3.6), которые можно использовать в программах. Директива компилятора $т используется для контроля типа указателя, сгенерированного с помощью оператора " в " . Эта директива может иметь два состояния {$Т+} ИЛИ Таблица 3.6. Стандартные указатели Тип указателя
На какой тип указывает
PAnsiString, P s t r i n g PByteArray
AnsiString TByteArray (объявлен В SysUtils). Используется при преобразованиях типов, динамически размещая память для массивов C u r r e n c y , Double, Extended, S i n g l e
PCurrency, PDouble, PExtended, P S i n g l e Pinteger POleVariant PShortString PTextBuf PVarRec Pvariant PWideString PWordArray
Типы данных
Integer OleVariant S h o r t s t r i n g . Применяется при использовании кода с устаревшим типом P s t r i n g TTextBuf (объявлен в Sysueils). Встроенный буфер в записи TTextRec для файлов TVarRec (объявлен в System) Variant WideString TWordArray (объявлен в SysUtils). Используется при преобразованиях типов, динамически размещая память для массивов с 2-х байтовыми значениями
75
Если установлено состояние {$т-}, то результатом операции с оператором "@" будет нетипизированный указатель, который может использоваться со всеми другими типами указателей. Если установлено состояние {$т+}, тип результата будет иметь значение Л т, где т совместимо только с типом переменной.
Процедурные типы В языке Delphi процедуры и функции рассматриваются не только как части программы, которые можно выполнять с помощью вызовов, а трактуются гораздо шире: здесь допускается интерпретация процедур и функций как объектов, которые можно присваивать переменным и передавать в качестве параметров. Такие действия можно выполнять с помощью процедурных типов. В описании процедурного типа задаются параметры, а для функции еще и возвращаемое значение. По существу синтаксис записи процедурного типа в точности совпадает с записью заголовка процедуры или функции, только опускается идентификатор после ключевого слова p r o c e d u r e или function. Приведем несколько примеров объявлений процедурного типа. type Proc = procedure; SwapProc = procedure(var X, У: Integer); StrProc = procedure(S: String); MathFunc = function(X: Real): Real; DeviceFunc = function(var F: Text): Integer; MaxFunc = function(A, B: Real; F: MathFunc): Real; Имена параметров в описании процедурного типа играют чисто декоративную роль — на смысл описания они не влияют. Язык Delphi не позволяет описывать функции, которые возвращают значения процедурного типа. Результат функции должен быть указателем строкового, вещественного, целого, символьного, булевого типа или иметь перечислимый тип, определенный пользователем. Рассмотрим небольшой пример. Объявляем следующую функцию. f u n c t i o n Calc(X,Y: I n t e g e r ) : I n t e g e r ; Затем объявляем переменную F. var F: function(X,Y: Integer): Integer; И присваиваем ей ссылку на функцию. F := C a l c ; С этой переменной можно поступать точно так же, как и с остальными переменными, ее можно применять и для вызова функций. Вот как это происходит в программе. function Calc(х,у:Integer): Integer; begin Result := X + Y; end; type F = function (X,Y:Integer): Integer; var Pfunc: F; begin Pfunc := Calc; Writeln(Pfunc(2,3)); Readln; end.
76
Глава З. Язык программирования Delphi
Сначала описана функция Calc, в которой выполняется сложение двух чисел, затем объявлен процедурный тип F и создана процедурная переменная pfunc, которой в дальнейшем присваивается ссылка на функцию Calc. Вывод на экран в выражении w r i t e l n ( p f u n c ( 2 , 3)) ,- производится с помощью процедурной переменной pfunc. На экране появится цифра 5. Наверно, некоторые задумаются, зачем так усложнять программу, ведь можно непосредственно сделать вывод самой функции, не делая дополнительные преобразования. Все дело в том, что различных функций может быть сколь угодно много, и тогда для вывода каждой функции придется отдельно вызывать функцию w r i t e с соответствующими параметрами. Но присваивание функций процедурной переменной позволяет использовать только одну форму вывода W r i t e l n (pfunc (X, Y));, что делает программу более понятной, позволяет сократить ее и исключить возможные ошибки. Использование процедурных переменных позволяет во многих случаях улучшить наглядность программы и, соответственно, сделать ее более надежной.
Вариантные типы Об этих типах поговорим, когда освоимся с основными структурами языка Delphi и придем к необходимости использовать эти типы. Хотя, как можно догадаться из названия, переменные этого типа поддерживают работу с различными стандартными типами.
Операторы Операторы описывают те алгоритмические действия, которые должны выполняться над операндами. В языке Delphi оператор трактуется как встроенная функция языка.
Простые операторы Простым оператором является такой оператор, который не содержит в себе других операторов. Это может быть оператор присваивания " : = " , оператор процедуры или оператор перехода " g o t o " .
Арифметические операторы Арифметические операторы обычно используются с целочисленными или вещественными типами и включают следующие операторы: +, -, *, /, d i v и mod. Операторы, для которых необходимо два операнда, называются бинарными операторами. В табл. 3.7 описаны все бинарные операторы. Таблица 3.7. Бинарные арифметические операторы Оператор Операция
Тип операндов
Тип результата
Пример
+
integer, real
integer, real
X +Y
integer, real
integer, real
Result - 1
integer, real
integer, real
P * Rate
integer, real
real
X / 2
Сложение Вычитание * Умножение / Вещественное деление
Операторы
77
Окончание табл. 3.7 Оператор Операция
Тип операндов
div integer Целочисленное деление mod integer Остаток от целочисленного деления
Тип результата
Пример
integer
Total d i v Size
integer
Y mod 6
Операторы, для которых необходим только один операнд, называются унарными. В табл. 3.8 приведены унарные операторы. Таблица 3.8. Унарные арифметические операторы Оператор
Операция
Тип операндов
Тип результата
Пример
+ (унарный) -(унарный)
Число положительное Число отрицательное
integer, real integer, real
integer, real integer, real
+7 -x
Можно отметить следующие моменты. Результат операции х/у всегда имеет тип Extended независимо от типа используемых операндов. Для других операторов результат будет иметь тип Extended, если по крайней мере один из операндов имеет вещественный тип (Real). Результат имеет тип Int64, когда один из операторов имеет тип I n t 6 4 . В противном случае результат будет иметь тип I n t e g e r . Результат целочисленного деления х d i v у можно рассматривать как деление х/у, округленное до ближайшего целого значения вниз. Оператор mod можно представить следующим выражением: х mod у = х(х d i v у) * у. Оператор присваивания Оператор присваивания " : =" заменяет текущее значение переменной новым значением, которое определяется выражением, расположенным с правой стороны от оператора, или значение которого должно возвращаться функцией. При присваивании необходимо обеспечивать совместимость типов и тип, полученный при вычислении выражения, должен быть совместим с типом переменной, которой это значение должно быть присвоено. Начинающие программировать на языке Delphi часто вместо оператора присваивания используют оператор равенства " = " , так как сказывается привычка к математической форме записи. Обычно компилятор вылавливает эти ошибки, если нарушается логическая взаимосвязь, но это происходит не всегда. Не путайте оператор присваивания " : = " с оператором равенства "=".
Ниже приведены примеры операторов присваивания. X := Y + Z; Done := (I>=1) and ( K 1 0 0 ) ; Huel := ( b l u e , S u c c ( C ) ] ; I := Sqr(J) - I*K; Булевы операторы Все булевы операторы приведены в табл. 3.9. Все операции отвечают стандартным правилам булевой логики. Например, выражение х and у даст в результате True только в том случае, если оба операнда имеют значение True.
78
Глава 3. Язык программирования Delphi
Таблица 3.9. Булевы операторы Оператор Операция
Типы операндов Тип результата Пример
not
Отрицание
Boolean
and
Конъюнкция (логическое умножение) Boolean
or
Дизъюнкция (логическое сложение)
Boolean
xor
Разделительная дизъюнкция
Boolean
Boolean Boolean Boolean Boolean
not
(С i n MySet)
Dn a n d (Tt > 0) A or В A xor В
Необходимо отметить, что компилятор может производить как полное вычисление логического выражения, так и прекратить вычисления, если окончательный результат уже понятен. Рассмотрим следующий фрагмент. while (I =
1 = Max X У X 0 Cnt = 1
Оператор @ Оператор "@" используется для получения адреса (расположение в памяти) переменной, функции, процедуры или метода, что означает, что оператор "@" создает указатель на операнд. Необходимо придерживаться следующих равил. •
Если х — переменная, то выражение @х возвращает адрес переменной. Тип @х будет указателем, если используется директива компилятора {$Т-}, которая установлена по умолчанию. Если установить директиву {$т+}, то выражение @х будет возвращать тип л т , где т соответствует типу х. (Это различие может оказаться существенным в процессе присваивания.)
Операторы
81
•
Если F — процедура или функция, то @F всегда возвращает указатель на вход в F. Тип @F всегда является указателем. • Когда оператор @ используется с методом класса, то метод должен быть указан одновременно с именем класса. Например: @TMyClass.DoSomething, где метод DoSomething принадлежит классу TMyClass.
Оператор goto Оператор перехода goto всегда записывается одновременно с меткой, на которую необходимо перейти. goto l a b e l l ; Метка ставится перед утверждением, на которое необходимо сделать переход. Labell: statement; И она должна быть предварительно объявлена. label l a b e l l ; Метка может быть любым допустимым идентификатором или целым числом в диапазоне 0—9999. Объявление метки, отмеченное утверждение и оператор g o t o должны находиться в одном и том же блоке. Например, фрагмент программы, приведенный ниже, создает бесконечный цикл повторения процедуры Веер (Сигнал). label StartHere; StartHere: Веер; goto StartHere; Старайтесь не использовать этот оператор, так как излишнее увлечение переходами goto создает плохо читаемые программы, и поэтому их почти невозможно модифицировать. Хотя иногда без этих операторов не обойтись (например, при выходе из вложенных циклов). Приоритеты операторов Как и в математике, в языке Delphi операторы обычно выполняются не в том порядке, в каком они встречаются в выражении. Например, в выражении x+Y*z сначала выполняется умножение, а потом сложение. В данном случае было использовано правило приоритета операторов. Как и в математике, его можно изменить с помощью круглых скобок. Например, в выражении (x+Y) *z будет получен другой результат, чем ранее. Порядок приоритета следующий. • Унарные плюс " + " и минус " - " . • Умножение и деление *, /, div, mod. • Суммирование и вычитание +, -. Если последовательно встречаются операторы одинакового приоритета, то они выполняются слева направо, т.е. в порядке их следования.
Структурные операторы Структурные (или структурированные) операторы строятся из утверждений, порядок выполнения которых должен быть либо последовательным, либо определяться условной передачей управления (условные операторы), либо повторяться (операторы цикла).
82
Глава 3. Язык программирования Delphi
Составные утверждения Составные утверждения задают порядок выполнения утверждений, являющихся их элементами. Утверждения должны выполняться в том порядке, в котором записаны. Составные утверждения обрабатываются как одно утверждение, что имеет решающее значение там, где синтаксис языка Delphi допускает использование только одного утверждения. Утверждения заключаются в ограничители begin и end и отделяются друг от друга точкой с запятой. Например: begin Z := X; X : = У; У := Z; end;
Условные операторы Условные операторы позволяют выбрать для выполнения один из нескольких операторов, или не выбрать ни одного. Оператор if Синтаксис оператора i f можно представить следующим образом. if выражение then утверждение_1 else утверждение_2 В результате вычисления выражения должен получиться результат, имеющий стандартный булев тип. Если результатом выражения является значение True (Истина), то выполняется утверждение, следующий за ключевым словом then. Если результатом выражения является значение F a l s e (Ложь) и присутствует ключевое слово e l s e , то выполняется утверждение, следующее за ключевым словом e l s e . Если ключевое слово e l s e отсутствует, то никакое утверждение не выполняется. В общем случае ключевое слово e l s e связывается с ближайшим ключевым словом if, которое еще не связано с ключевым словом e l s e . Приведем два примера оператора if. if X= 0) and (I 0 do begin if Odd(I) then Z := Z * X; I := I div 2; X := Sqr(X); end; while not Eof(InFile) do begin Readln(InFile,Line); Process(Line); end; Здесь необходимо отметить ошибку, которую часто допускают программисты. Цикл while заканчивается, если условное выражение примет значение False, а цикл repeat заканчивается, если условное выражение примет значение True. Старайтесь их не путать. Для выхода из цикла while значение условного выражения должно быть False, а для выхода из цикла repeat значение условного выражения должно быть True.
Цикл for Оператор цикла с параметром (for) начинается с ключевого слова for и вызывает повторяющееся выполнение утверждения или составного утверждения, пока управляющей переменной присваивается возрастающая последовательность значений. Синтаксис оператора может быть таким: for счетчик:=начальное_значение t o конечное_значение do утверждение или таким: for счетчик:начальное_значение downto конечное_значение do утверждение
Операторы
85
В качестве управляющей переменной (счетчика) должен использоваться идентификатор переменной, который обозначает переменную, объявленную локальной в блоке, содержащем оператор for. Управляющая переменная должна иметь перечислимый тип. Когда начинает выполняться оператор for, начальное и конечное значения определяются один раз, и эти значения сохраняются в процессе выполнения оператора. Утверждение, которое содержится в теле оператора for, выполняется один раз для каждого значения в диапазоне между начальным и конечным значением. Управляющая переменная всегда инициализируется начальным значением. Когда работает оператор for, значение счетчика увеличивается при каждом повторении на единицу. Если начальное значение превышает конечное значение, то содержащийся в теле оператора for оператор не выполняется. Когда в операторе цикла используется ключевое слово downto, значение управляющей переменной уменьшается при каждом повторении на единицу. Если начальное значение в таком операторе меньше, чем конечное значение, то содержащееся в теле оператора цикла утверждение не выполняется. Если утверждение, содержащееся в теле оператора for, изменяет значение управляющей переменной, то это является серьезной ошибкой. После выполнения оператора for значение управляющей переменной становится неопределенным, если только выполнение оператора for не было прервано с помощью оператора перехода. Если принять это во внимание, то оператор for V:=Exprl t o Expr2 do Body; эквивалентен составному утверждению begin Tempi := Exprl; Temp2 := Expr2; if Tempi = Temp2 then begin V := Tempi; Body ; while V Temp2 do begin V := Pred(V); Body ; end; end; end; где Tempi и Temp2 — вспомогательные переменные, тип которых совпадает с основным типом переменной v и которые не встречаются в другом месте программы.
86
Глава 3. Язык программирования Delphi
Примеры оператора цикла с параметром. for I:=2 t o 63 do i f Data[I]>Max then Max := D a t a t I ] ; for I : = l t o 10 do for J:=l to 10 do begin X := 0; for К := 1 to 10 do X := X + Matl[I,K]*Mat2[K,J]; Mat[I,J] := X; end; for С := red to blue do Check(C);
Оператор with В операциях над записями оператор with удобно использовать для краткого обращения к полям записи. В операторе with к полям одной или нескольких записей можно обращаться, используя только идентификаторы полей. Например: with Date do if month=12 then begin month := 1; year := year + 1; end else month := month + 1; Это эквивалентно следующему. i f Date.month =12 then begin Date.month := 1; D a t e . y e a r := D a t e . y e a r + 1; end e l s e Date.month := Date.month + 1; B операторе w i t h сначала проверяется каждая ссылка на переменную с целью интерпретировать ее как поле записи. Если это так, то она всегда воспринимается именно таким образом, даже если имеется доступ к переменной с тем же именем.
Ввод-вывод Раньше уже была возможность познакомиться в вводом и выводом данных на экран. Это процедуры w r i t e и Read. Но, наверно, пока вы еще не задумывались, как это делается и есть ли общие правила ввода и вывода информации в различные устройства, включая дисковые накопители, принтеры, коммуникационные порты или, например USB. Разумеется, существует универсальная система ввода-вывода, и для ее работы необходимо создавать специальные объекты — потоки. Об этом поговорим после изучения основ объектно-ориентированного программирования, а сейчас будем использовать только понятие "файл". Файл представляет собой упорядоченную последовательность данных на внешнем носителе и создается для долговременного хранения информации. В качестве внешнего носителя могут служить разного рода диски (жесткие, гибкие, магнитооптические, компакт-диски, Zip, Jaz и т.д.), а также магнитные ленты и магнитные карты. Кроме того, что информация в файле может храниться долгое время, у файлов имеется еще одна отличительная особенность: их объем (или длина) зависит только от наличия свободного места на внешнем носителе. Если для каждого из других структурированных типов всегда точно определено, сколько элементов содержит то или иное
Ввод-вывод
87
значение, то количество элементов в файле при объявлении файлового типа не указывается. Хотя после закрытия файла точное число входящих в него элементов сохраняется в специальных таблицах. Именно в файл записываются данные при вызове функции Write, сигнатура (имя и перечень параметров) которой выглядит следующим образом. w r i t e ( [ v a r l : f i l e ; ] PI, [..., Pn] ) ; Слово "сигнатура" обычно используется для обозначения части объявления процедуры или функции, куда входят идентификатор и перечень параметров.
Как видно, среди параметров может присутствовать переменная v a r l типа f i l e (о том, что это необязательный параметр, говорят квадратные скобки) и счетное количество переменных, которое необходимо записать в этот файл, также заключенных в квадратные скобки. Но хотя бы одна переменная для записи должна присутствовать обязательно. Это так выглядит для программиста, но компилятор всегда должен подставить файл, и если программист не определил первый параметр типа f i l e , то компилятор подставит на это место идентификаторы i n p u t или Output, которые определяют клавиатуру и экран соответственно. Поэтому, какие бы параметры вы не использовали в процедурах w r i t e и Read, вывод и ввод всегда будут производиться в файл или устройство, определенное программистом.
Файлы и файловые переменные Для работы с файлами в Delphi предусмотрена возможность определять файловые типы и файловые переменные. После этого информацию, которая может потребоваться впоследствии, можно сохранить в файле. Существует три способа объявления файлового типа (или файловой переменной). F = file of A; F = Text; F = file; Эти три способа соответствуют трем видам файлов, которыми позволяет оперировать Delphi. Речь идет о типизированных, текстовых и нетипизированных файлах. В первом случае использованы ключевые слова f i l e и of, где первое слово говорит о том, что переменная F будет иметь тип f i l e , а второе слово of уточняет тип элементов файла. Если второго слова нет, то тип элементов файла считается неопределенным, и, соответственно, файл называется нетипизированным. Если при объявлении использовано слово Text, то это говорит о том, что создается специальный текстовый файл. Text — это не зарезервированное слово, а идентификатор стандартного типа данных, как, например, I n t e g e r или Real. Элементы файла могут представлять собой значения любого типа за исключением файлового либо структурированного типа, элементами которого являются файлы, т.е. существование "файла файлов" не допускается. Вот примеры объявлений файловых переменных. type TArrayl: array of Integer; TDate=record Day:l..31; {Число} Month:1..12; {Месяц} Year:1900..2010 {Год} end; var Fl: file; F2: Text;
88
Глава З. Язык программирования Delphi
F3: f i l e of I n t e g e r ; F4: f i l e of TArrayl; F5: f i l e of TDate; Здесь объявлены пять файловых переменных. Это F1 — нетипизированный файл, F2 — текстовый файл, a F3, F4 и F5 — типизированные файлы. Причем если среди типизированных файлов элементы файла F3 относятся к стандартному типу i n t e g e r , то элементы файлов F4 и F5 — к типам, объявленным пользователем (тип T a r r a y l представляет собой массив целых чисел, а тип TDate — запись). Хотя разные типы файлов имеют свои особенности, существует и нечто общее. Так, в файле в каждый момент может быть доступен только один элемент. Например, если вернуться к объявленным выше файловым переменным, то в файле F3 одновременно можно иметь доступ только к одному из целых чисел, которые составляют файл; в файле F4 — к единственному массиву; наконец, в файле F5 — к отдельно взятой записи. Кроме того, файлы всех видов завершаются маркером конца файла EoF (от английских слов ""End of File"). Также необходимо отметить, что поскольку файлы, в отличие от данных других типов, размещаются не в оперативной памяти, а на конкретном физическом носителе, то для доступа к файлам недостаточно только объявить файловую переменную. Необходимо еще связать конкретное место на физическом носителе с файловой переменной, для чего используются процедуры A s s i g n F i l e , Reset, Rewrite и некоторые другие. Об этом будет сказано в дальнейшем, а начнем с нетипизированных файлов. Нетипизированные файлы Нетипизированные файлы отличаются тем, что для них при объявлении не определяется тип элементов. Это позволяет получать доступ к файлам с любой структурой. При открытии файла (с помощью процедур Reset или Rewrite) необходимо только указать размер отдельных элементов, с помощью которых будет проводиться обмен с файлом. При этом файл трактуется как последовательность этих элементов произвольного типа. Вот как может выглядеть обращение к процедурам Reset и Rewrite для открытия нетипизированного файла. Reset(F,512); Rewrite(F,256); Здесь F — файловая переменная, имеющая тип f i l e . А в качестве второго параметра процедур Reset и Rewrite указаны размеры блоков (в байтах), с помощью которых будет происходить считывание из файла или запись в файл данных. Поскольку этот параметр имеет тип Word, его максимальная величина равна 65 535 байт. Если второй параметр процедур Reset и Rewrite не задан, то по умолчанию длина записи считается равной 128 байт. При определении длины записи следует принять во внимание, что в общем случае длина файла может оказаться не кратна этой величине — тогда последняя запись окажется неполной, и несколько последних байтов файла не будут считаны. Чтобы этого не случилось, используйте в таких случаях длину записи, равную единице. Для работы с нетипизированными файлами используются те же процедуры, что и для типизированных, за исключением Read и Write. Для обмена данными с нетипизированными файлами применяются специальные процедуры: BlockRead и BlockWrite. Процедура BlockRead Данная процедура считывает из файла указанное число блоков. При этом количество считываемых байт равно числу считываемых записей, умноженному на размер блока. Файл, из которого происходит считывание, предварительно должен быть открыт. Вот как выглядит сигнатура процедуры BlockRead. BlockRead (Var F : F i l e ; Var Buf; C o u n t : I n t e g e r [; Var R e s u l t : I n t e g e r ] )
Ввод-вывод
89
Здесь F — нетипизированная файловая переменная. Buf — переменная, ранее объявленная в программе и используемая в качестве рабочего буфера. Размер этого буфера, который предназначен для обменов, не должен быть меньше длины записи, определенной при открытии файла с помощью процедуры Reset или Rewrite. Count — выражение или значение, соответствующее количеству записей, которые следует считать из файла. Result — значение, возвращаемое процедурой и соответствующее количеству считанных записей (необязательный параметр). Количество считанных блоков (параметр R e s u l t ) может быть меньше или равно значению параметра Count. Если при вызове процедуры параметр R e s u l t не определен и количество считанных блоков окажется не равно значению параметра count, будет зафиксирована ошибка ввода-вывода. При этом должна быть установлена директива компилятора {$1+}, что обычно делается по умолчанию. По завершении действия процедуры BlockRead указатель текушей позиции файла перемещается на количество блоков, соответствующее значению параметра Result. Процедура BlockWrite Данная процедура добавляет в файл один или несколько блоков. При этом, как и для процедуры BlockRead, количество записываемых байт равно числу добавляемых записей, умноженному на размер блока. Файл, в который проводится запись, предварительно должен быть открыт. Вот как выглядит сигнатура процедуры BlockWrite. BlockWrite (Var F : F i l e ; Var Buf; Count:Integer [; Var R e s u l t : I n t e g e r ] ) ; Здесь F — нетипизированная файловая переменная. Buf — переменная, ранее объявленная в программе и используемая в качестве рабочего буфера. Count — выражение или значение, соответствующее количеству записей, которые следует добавить в файл. Result — значение, возвращаемое процедурой и соответствующее количеству добавленных блоков (необязательный параметр). Количество добавленных записей (параметр R e s u l t ) может быть меньше или равно значению параметра Count. Если при вызове процедуры параметр Result не определен и количество добавленных записей окажется не равно значению параметра Count, будет зафиксирована ошибка ввода-вывода. Копирование файлов Рассмотрим небольшую программу, копирующую один файл в другой и использующий некоторые из тех конструкций языка Delphi, о которых говорилось выше. var FromF, ToF: file; NumRead, NumWritten: Integer; Buf: array[l..2048] of Char; inFileName: String; outFileName: String; begin Write('inFileName = ' ) ; Readln(inFileName); AssignFile(FromF, inFileName); { Открыть входной файл. } Reset(FromF, 1); { Размер блока = 1. } Write('outFileName = ' ) ; Readln(outFileName);
90
Глава З. Язык программирования Delphi
begin AssignFile(ToF, outFileName); { Открыть выходной файл. } Rewrite(ToF, 1); { Размер блока = 1. } repeat BlockRead(FromF, Buf, SizeOf(Buf), NumRead); BlockWrite(ToF, Buf, NumRead, NumWritten); until (NumRead = 0) or (NumWritten NumRead); CloseFile(FromF); { Закрыть входной файл. } CloseFile(ToF); { Закрыть выходной файл. } end; end. В разделе var этой программы объявляются две переменные типа f i l e , счетчики NumRead и NumWritten, буфер Buf, представляющий массив символов и имена входного и выходного файлов inFileName и outFileName. Затем в блоке, где размещается программа, открывается входной файл, для чего имя файла, представляющее собой полный путь к файлу, связывается с переменной FromF типа f i l e в процедуре A s s i g n F i l e . Перед этим происходит ввод имени файла с помощью процедур Write (' inFileName= ') и Readln (inFileName), где сначала выводится запрос на ввод имени файла, на который нужно ввести полный путь файла. Те же самые действия необходимы для открытия выходного файла. Когда файлы открыты, в цикле r e p e a t перезаписываются данные из одного файла в другой. При этом выход из цикла может произойти по двум причинам: или считан последний символ из входного файла (NumRead=0), или нет места для записи на диске (NumWrittenoNumRead). Обратите внимание на последние процедуры C l o s e F i l e (FromF) и C l o s e F i l e (ToF). Они необходимы для закрытия файлов, чтобы операционная система смогла заполнить таблицу размешения файлов и разрешить пользоваться этими файлами другим пользователям. В данном случае они не обязательны, так как по окончании программы закрытие будет сделано автоматически, но лучше всегда делать это самому, в некоторых случаях это позволит избежать неприятностей.
Типизированный файл Итак, типизированный файл содержит совокупность элементов, принадлежащих типу, который определен в объявлении файловой переменной. Доступ к элементам файла, как правило, происходит последовательно. Можно также переместить указатель текущей позиции (с помощью процедуры seek) к любому элементу файла. Для того чтобы открыть файл как типизированный, его необходимо ассоциировать с файловой переменной, имеющей соответствующий тип. Это может быть любой из простых или структурированных типов, как стандартных, так и созданных пользователем, за исключением файлового типа. Для управления типизированными файлами подходят все процедуры, которые приведены в табл. 3.14. Таблица 3.14. Процедуры и функции д л я работы с файлами Процедура или функция Описание Append
Открывает существующий файл для добавления
AssignFile
Присваивает имя внешнего файла файловой переменной
BlockRead
Считывает один или несколько блоков из нетипизированного файла
Blockwrite
Записывает один или несколько блоков в нетипизированный файл
Ввод-вывод
91
Окончание табл. 3.14. Процедура или функция Описание chDir
Изменяет текущий каталог
CloseFile
Закрывает файл
EoF
Возвращает статус конца файла
EoLn
Возвращает статус строки текстового файла
Erase
Стирает внешний файл
FilePos
Возвращает текущую позицию типизированного или нетипизированного файла
FileSize
Возвращает текущий размер файла. Неприменима для текстовых файлов
Flush
Перезаписывает буфер в выходной файл
GetDir
Возвращает текущий каталог определенного тома
lOResult
Возвращает целочисленное значение, определяющее статус
MkDir
Создает подкаталог
Read Readln
Считывает одно или несколько значений из файла в одну или несколько переменных To же, что и Read, но дополнительно производит переход на следующую строку в текстовых файлах
Rename
Переименовывает внешний файл
выполнения последней функции ввода-вывода
Reset
Открывает существующий файл
Rewrite
Создает и открывает новый файл
RmDir
Удаляет пустой подкаталог
seek
Перемещает указатель в типизированных или нетипизированных файлах на заданный компонент. Не используется с текстовыми файлами
SeekEof
Возвращает статус конца текстового файла
SeekEoln
Возвращает статус конца строки текстового файла
setTextBuf
Присваивает буфер ввода-вывода текстовому файлу
Truncate
Обрезает типизированные или нетипизированные файлы после текущего положения указателя
write
Записывает одно или несколько значений в файл
Writeln
To же, что и w r i t e , но затем записывает маркер конца строки в текстовые файлы
Процедура Read Использование процедуры Read для типизированных файлов заключается в считывании одного или нескольких компонентов файла и присвоении считанных значений соответствующим переменным. Вот как выглядит сигнатура процедуры Read для типизированных файлов. Read[F, VI [, V 2 , . . . , Vn] ) ; Здесь F — файловая переменная, созданная с помощью конструкции f i l e of. VI - Vn — одна или несколько переменных, принадлежащих любому типу, за исключением файлового.
92
Глава 3. Язык программирования Delphi
Процедура Write Запись в файл осуществляет процедура w r i t e . Использование данной процедуры для типизированных файлов заключается в присвоении значений некоторых переменных компонентам файла. Сигнатура процедуры w r i t e для типизированного файла выглядит так. Write[F, VI [, V 2 , . . . , Vn] ) ; Здесь F — файловая переменная, созданная с помощью конструкции f i l e of. VI - vn — одна или несколько переменных, принадлежащих любому типу, за исключением файлового (эти переменные должны быть того же типа, что и компоненты файла). После записи каждой переменной (очередному элементу файла при этом присваивается значение переменной, т.е. старое значение элемента заменяется новым) указатель текущей позиции файла перемещается к следующему элементу. Если указатель находится в конце файла, то при записи очередного элемента этот элемент дополняет файл. Вот как можно скопировать файлы с помощью типизированных файловых переменных. var Fl, F2: file of Char; Ch: Char; inFileName: String; outFileName: String; begin Write('inFileName = ' ) ; Readln(inFileName); AssignFile(Fl, inFilename); Reset(Fl); Write('outFileName = ' ) ; Readln(outFileName); AssignFile(F2, outFilename); Rewrite(F2); while not Eof(Fl) do begin Read(Fl, Ch) ; Write(F2, Ch); end; CloseFile(F2); CloseFile(Fl); end. Здесь все сделано точно так же, как и в предыдущем листинге, только в цикле для выхода используется выражение not Eof ( F l ) , т.е. цикл повторяется, пока не будет достигнут конец входного файла. В той версии Delphi, с которой я работаю, ключевое слово f i l e не распознается компилятором, хотя в справочной системе приведено полное описание файлов наравне с другими конструкциями. В данной ситуации необходимо использовать потоки, в частности тип TFileStream, тем более что разработчики Delphi рекомендуют использовать этот тип как наиболее полно отвечающий всем потребностям.
Текстовые файлы Текстовые файлы представляют собой последовательность строк разной длины, состоящих из символов. Каждая строка текстового файла оканчивается маркером конца строки EoLn (от английского "End of Line"), а завершает текстовый файл, как и любой другой, маркер конца файла EoF. К элементам текстового файла возможен только последовательный доступ.
Ввод-вывод
93
Для того чтобы файл открыть как текстовый, его необходимо связать с файловой переменной, имеющей тип Text. Необходимо заметить, что использование процедур Read и Write с текстовыми файлами имеет свои особенности. Кроме того, к текстовым файлам (и только к ним) применимы процедуры ReadLn и WriteLn. Также специально для использования с текстовыми файлами предназначены процедура Append и функция EoLn. Сначала рассмотрим стандартные текстовые файлы. Стандартные текстовые файлы В предыдущих главах использовались операторы ввода-вывода, которые выглядели так: Read (А, В, С) ,• или Write (X,Y, 'Вывод на э к р а н 1 ) . Первый оператор позволяет ввести с клавиатуры значения, которые будут присвоены переменным А, В и с. Второй оператор выводит на экран значения переменных X и Y, а также строку текста. Как уже упоминалось, в языке Delphi существует два стандартных идентификатора, играющих роль текстовых файловых переменных, связываемых вместо файлов с конкретными физическими устройствами компьютера. Это идентификаторы i n p u t и Output, которые связаны с клавиатурой и экраном компьютера соответственно. Указанные файлы считаются постоянно открытыми (для них не нужно использовать процедуры Rewrite и Reset), и эти идентификаторы можно использовать в программах. Так, операторы ввода-вывода, о которых шла речь выше, можно записать и иначе. Read(Input,А,В,С); W r i t e ( O u t p u t , X , Y , ' В ы в о д на э к р а н 1 ) ;
Но указывать параметры Input или Output необязательно, они подразумеваются по умолчанию. Упомянутые стандартные файлы можно перенаправить. Для этого достаточно воспользоваться процедурой Assign, например: Assign (Output, • T e x t . t x t ' ) . Если после этого в программе вызвать процедуру Write без указания файла, соответствующая информация будет записана в файл Text. t x t . Процедура Read Процедура Read для текстовых файлов используется для считывания одного или нескольких значений файла и присвоения считанных значений переменным, указанным в процедуре. Значения могут принадлежать типам Char, s t r i n g , а также любым целочисленным или вещественным типам. Сигнатура процедуры Read для текстового файла выглядит так. Read([Var F : T e x t ; ] VI [, V 2 , . . . , Vn]); При считывании переменных типа c h a r из файла считывается символ и присваивается переменной, затем считывается следующий символ и присваивается следующей переменной — и так до тех пор, пока всем переменным типа Char, указанным при вызове процедуры Read, не будут присвоены считанные из файла значения. (При вводе с клавиатуры между вводимыми значениями требуется вводить некоторый разделитель: пробел, символ табуляции — клавиша или конец строки — клавиша <Enter>.) Если очередной считанный символ окажется маркером конца строки EoLn (при вводе с клавиатуры для этого достаточно нажать клавишу <Enter>), считывание будет продолжено из новой строки. Если очередной считанный символ окажется маркером конца файла EoF (при вводе с клавиатуры для этого достаточно воспользоваться комбинацией клавиш ), выполнение процедуры Read будет прекращено, и программа перейдет к следующему оператору. При считывании строковых переменных s t r i n g из файла будут считаны все символы до ближайшего маркера конца строки. Если длина считанной строки превзойдет допустимую для значений типа s t r i n g величину (255 символов), все ос-
94
Глава 3. Язык программирования Delphi
тавшиеся до конца строки байты отбрасываются. Затем при каждом новом обращении к процедуре Read будет считываться маркер конца строки, т.е. строка нулевой длины. Иными словами, с помощью процедуры Read не удастся считать несколько строк подряд. Для того чтобы считать несколько строковых значений, следует обратиться к процедуре ReadLn. При считывании числовых значений типа i n t e g e r или Real процедура Read будет пропускать любые пробелы, символы табуляции или маркеры конца строки, предшествующие числовой строке. Когда будет обнаружена первая значащая цифра, процедура Read начнет формировать числовое значение, пока не встретится один из перечисленных символов (пробел, символ табуляции или маркер конца строки). Считанная таким образом последовательность цифр рассматривается как символьное представление соответствующего числа, и полученное значение присваивается соответствующей переменной. Если числовая строка не соответствует ожидаемому формату, то произойдет ошибка ввода-вывода. Процедура Write Использование процедуры Write для текстовых файлов сводится к записи значений одной или нескольких переменных, указанных при вызове процедуры, в файл. Значения, о которых идет речь, могут принадлежать типам Char, S t r i n g , Boolean, а также любым целочисленным или вещественным типам. Кроме того, это могут быть значения перечислимых типов, созданных на основе char или одного из целочисленных типов. Если указатель текущей позиции расположен в начале файла, новая информация записывается поверх старой. Если же указатель находится в конце файла, то новая информация дополняет содержимое файла. Сигнатура процедуры Write для текстового файла следующая. Write([var FrText;] VI [, V2,..., Vn] ) ; Каждая из переменных или выражений VI - Vn имеет следующий формат: V[:a[:b]] Здесь V — переменная (или выражение), значение которой записывается в файл. А — спецификатор ширины поля (необязательный параметр). Если записываемое значение не заполнит всю ширину поля, оно будет слева дополнено пробелами. Если ширины поля для записываемого значения не хватит, данный параметр игнорируется. в — спецификатор количества знаков после десятичной точки. Данный параметр используется только в паре с параметром А и только в том случае, если значение v принадлежит одному из вещественных типов. Эти возможности форматирования можно использовать для получения упорядоченного вывода на экран.
Резюме В данной главе описаны все структуры языка программирования Delphi. Главу можно использовать и для знакомства с языком Delphi, и как краткий, но достаточно полный справочник. Для тех, кто совсем не имеет опыта программирования и лохо понимает принципы организации файловой системы операционных систем, возможно, будет понятен не весь материал, приведенный в этой главе, ввиду краткости изложения. Однако следует отметить, что о наиболее часто используемых конструкциях языка Delphi в дальнейшем будет рассказано подробнее, при рассмотрении фрагментов рабочих программ.
Резюме
95
Контрольные вопросы 1. Какиб типы данных предназначены для хранения чисел, а какие — для хранения символов? 2. Чем отличается оператор присваивания от оператора равенства? 3. Какое число будет выведено на экран с помощью оператора w r i t e (Pred(3)),-? 4. Какая ошибка будет зафиксирована компилятором в следующем фрагменте? var х: 1..5; begin х := 6; Writeln(x); Writeln(Pred(x)); Readln; end. 5. В чем заключается разница между статическим и динамическим массивами? 6. В чем разница между типами a r r a y и s e t ? 7. Что такое указатель? Как вы понимаете словосочетания "явный указатель" и "неявный указатель"? 8. Как можно изменить правило приоритета операторов? 9. В чем разница между неполным и полным вычислением логического выражения? 10. В приведенном ниже фрагменте программы есть серьезная логическая ошибка, которую компилятор не обнаружит. Постарайтесь найти ее. X := 0; while X O A C KVaariV arielbel > _ P . R Q b e l >JMt(j Vae b eP lo tA iytp fexSod M rnan rc T_gea t_dAn io fcap T
Description
W h e n y o u c r e a t e f o r m s in t h e F o r m d e s i g n e r a t d e s i g n t i m e , t h e y a r e i m p l e m e n t e d a s d e s c e n d a n t s o f T F o r m . F o r m s can r e p r e s e n t the application's m a i n window, or dialog b o x e s , or M D I children. A f o r m c a n contain other o b j e c t s , s u c h as T B u t t o n , T C h e c k B o x , a n d T C o m b o B o x o b j e c t s .
rgerbnstbysridrPi« Тур* [getNHtbynarwProc I vt-;
E x a m p l e s o f forms include T L o g i n D i a l o g and T P a s s w o r d Dialog objects. ]
G e p [9et*e'vbyrHneftK Type r t b n P c Type Type
Hierarchy
System.Object *>ч System-Marshal BvRefObiect - . System.CprnponentMpdel,Component *i* Borland.ycl.Cpntrols.TCpntrp! Borland Vcl.Controis.TWmControl Borland.Vcl. Forms.TScrollingWinContrpI Borland.yc|.Forms,TCustomForm
n
t
E
S : Oet*< fw «ET ДР1
Рис. 5.1. Иерархия наследования для типа TForm
Поля Поле можно также определить как переменную экземпляра, представляющую собой данные конкретного объекта. Поле в объекте подобно полю в записи языка Delphi, но в отличие от записи, к полям в объекте обычно не разрешен прямой доступ. Это способствует защите полей от случайного или предумышленного искажения. Доступ к полям происходит через свойства, которые могут выступать как фильтры и не пропускать недопустимых значений. Объявления полей происходят как объявления обычных переменных, при этом поля чаще всего размещаются в закрытом интерфейсе класса, доступ к которому ограничен. В именах полей принято ставить первую букву F (от слова Field), например: FQuantity:
Integer;.
Области видимости Язык Delphi предоставляет дополнительный контроль уровня доступа к членам классов (полям и методам) с помощью директив p r o t e c t e d , p r i v a t e , p u b l i c , p u b l i s h e d и automated, открывающих соответствующие разделы объявлений, или, как принято говорить, — интерфейсы класса. За каждой из директив может следовать любое необходимое количество объявлений полей или методов. Каждый интерфейс обеспечивает следующий уровень доступа. ш P r i v a t e (закрытый). Объявленные в данном разделе переменные и методы доступны только для того кода, который находится в блоке реализации самого объекта. Директива p r i v a t e скрывает особенности реализации объекта от пользователей и защищает члены этого объекта от непосредственного доступа и изменения извне.
Классы
125
•
P r o t e c t e d (защищенный). Члены класса, объявленные в этом разделе, доступны объектам, производным от данного класса. Это позволяет скрыть внутреннее устройство объекта от пользователя и в то же время обеспечить необходимую гибкость, а также эффективность доступа к полям и методам объекта для его потомков. • Public (открытый). Объявленные в этом разделе члены объекта доступны в любом месте программы. Конструкторы и деструкторы всегда должны быть объявлены как p u b l i c . • Published (экспортируемый). Для членов объекта, объявленных в данном разделе, в процессе компиляции будет создана информация о типах времени выполнения (RTTI). Это позволит другим элементам приложения получать информацию об элементах объекта, объявленных как published. В частности, подобная информация используется инспектором объектов при построении списков свойств объектов. • Automated (автоматизированный). Этот раздел сохранен только для обеспечения совместимости с ранними версиями Delphi.
Методы Методы представляют собой процедуры и функции, принадлежащие классу. Можно сказать, что методы определяют поведение класса. В классе всегда должны присутствовать два важнейших метода: конструктор и деструктор. При проектировании класса можно создать произвольное количество любых других методов, необходимых для решения конкретных задач. Создание метода — процесс, состоящий из двух этапов. Сначала следует объявить метод в объявлении класса, а затем создать код его реализации. Например, выполните следующее. • Создайте новый проект типа VCL Form Application. • Откройте инспектор объектов (клавиша ) и щелкните на вкладке Events (События). • Два раза щелкните на поле обработчика Onclick в разделе Input. В редакторе кодов получите следующую программу (см. листинг ниже), где в классе TForml появится процедура FormClick, а в разделе реализации — описание этой процедуры. unit Unitl; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs; type TForml = class(TForm) procedure FormClick(Sender: TObject); private { Private declarations } public { Public declarations } end; var Forml: TForml; implementation {$R *.nfm} procedure TForml.FormClick(Sender: TObject); begin
126
Глава 5. Объекты в Delphi
{ З д е с ь код о б р а б о т к и щелчка мыши. } end; end.
Таким образом вы создали пустую процедуру обработки щелчка мыши. Можете ее заполнить соответствующими кодами и получить нужный эффект. Например, можете изменить цвет фона окна на черный, для чего поместите в описание процедуры TForml.FormClick (между ключевыми словами b e g i n . . .end) следующую строку: Forml.Color := clBlack; и запустите программу (клавиша ). После того как появится окно, щелкните на нем мышью и наблюдайте результат. Типы методов Методы объекта могут быть описаны как статические ( s t a t i c ) , виртуальные ( v i r t u a l ) , динамические (dynamic) или как методы обработки сообщения (message), для чего к ним добавляются соответствующие директивы. Статические методы Статические методы работают подобно обычным процедурам или функциям. Этот тип методов устанавливается по умолчанию. Адрес такого метода известен уже на стадии компиляции, и компилятор в коде программы оформляет все вызовы данного метода как статические. Такие методы работают быстрее других, однако не могут быть перегружены в целях полиморфизма объектов. Виртуальные методы Вызов виртуальных методов из-за возможности их перегрузки немного сложнее, чем вызов статических, так как во время компиляции адрес конкретного вызываемого метода не известен. Для решения этой задачи компилятор строит таблицу виртуальных методов (VMT), обеспечивающую определение адреса метода в процессе выполнения программы. VMT содержит все виртуальные методы предка и виртуальные методы самого объекта, поэтому виртуальные методы используют несколько больший объем памяти, чем методы динамические, однако их вызов происходит быстрее. Динамические методы Динамические методы в целом подобны виртуальным методам, но обслуживаются другой диспетчерской системой. Каждому динамическому методу компилятор назначает уникальное число и использует его вместе с адресом метода для построения таблицы динамических методов (DMT— Dynamic Method Table). В отличие от VMT, DMT содержит методы лишь данного объекта, благодаря чему обеспечивается экономия используемой памяти, но замедляется вызов метода, поскольку для поиска его адреса, скорее всего, будет пересмотрена не одна DMT в иерархии объектов. Методы обработки сообщения Методы обработки сообщений предназначены для обработки приходящих сообщений, с помощью которых происходит обмен информацией в операционной системе Windows. Значение после ключевого слова message определяет сообщение, в ответ на которое вызывается данный метод. Такие методы создаются для реакции на те или иные сообщения Windows. Они никогда не вызываются непосредственно из программы. Например: type TTextBox = class(TCustomControl) private procedure WMChar(var Message: TWMChar); message WM_CHAR; end;
Классы
127
где WM_CHAR — константа, определяющая данное сообщение. Две буквы WM (Windows
Message) говорят о том, что это сообщение операционной системы Windows. В данном случае это связано с нажатием клавиш клавиатуры. Методы класса Для использования каждого метода необходимо сначала создать объект (экземпляр класса) и только потом делать вызов метода в программе, для чего используются имя объекта и имя метода, написанные через точку. Например, в ранее приведенном классе TForml вызов метода FormClick происходит как TForml .FormClick, но если перед объявлением метода поставить ключевое слово c l a s s , то этот метод может быть вызван как обычная процедура или функция без создания экземпляра класса, членом которого является данный метод. Необходимо иметь в виду, что при создании подобных методов нельзя использовать никакой информации экземпляра класса, поскольку это приведет к ошибке компиляции, так как объект не существует. Переопределение методов Переопределение методов в языке Delphi реализует концепцию полиморфизма, позволяя изменять поведение метода от наследника к наследнику. Переопределение метода возможно только в том случае, если первоначально он был объявлен с директивой v i r t u a l или dynamic. Для переопределения метода при его объявлении вместо ключевых слов v i r t u a l или dynamic следует указать ключевое слово o v e r r i d e . Ниже приведен пример переопределения методов, где метод Draw переопределен в двух унаследованных классах TRectangle и T E l l i p s e . type TFigure = class procedure Draw; virtual; end; TRectangle = class(TFigure) procedure Draw,- override; end; TEllipse = class{TFigure) procedure Draw; override; end; Здесь первым объявлен класс TFigure, который затем наследуют классы TRectangle и T E l l i p s e , что видно из объявлений классов, где за ключевым словом c l a s s в круглых скобках помещается имя родительского класса. Директива o v e r r i d e приводит к замещению строки описания исходного метода в таблице VMT строкой описания нового метода. Если объявить новые функции с ключевым словом v i r t u a l или dynamic, а не o v e r r i d e , то вместо замещения старых будут созданы новые методы. В случае переопределения статического метода новый вариант просто полностью заменит статический метод родителя. Если использовать код var Figure: TFigure; begin Figure := TRectangle.Create; Figure.Draw; // Вызывает TRectangle.Draw. Figure.Destroy; Figure := TEllipse.Create; Figure.Draw; // Вызывает TEllipse.Draw. Figure.Destroy; end;
128
Глава 5. Объекты в Delphi
то в зависимости от того, какая фигура создается, прямоугольник (TRectangle) или эллипс (TEllipse), вызываются разные методы для их прорисовки, хотя способ вызова один и тот же: F i g u r e . Draw;. Перегрузка методов Подобно обычным процедурам и функциям, методы могут быть перегружены таким образом, чтобы класс содержал несколько методов с одним именем, но с различными списками параметров (различные сигнатуры). Перегруженные методы должны быть объявлены с указанием директивы overload. Вот пример объявления класса с перегруженными методами. type TSomeClass = class procedure AMethod(I: Integer); overload; procedure AMethod(S: string); overload; procedure AMethod(D: Double); overload; end; Если не использовать ключевое слово overload, то компилятор не сможет понять, какой метод вы хотите вызвать, так как он различает методы только по именам. Однако если использовать overload, то компилятор будет производить отбор не только по имени, но и учитывать параметры, которые должны быть различными для методов с одинаковыми именами. Дублирование имен методов Иногда может понадобиться к одному из классов добавить метод, замещающий метод с тем же именем, но принадлежащий предку этого класса. В данном случае требуется не переопределить исходный метод, а полностью его заменить. Если просто добавить такой метод в новый класс, то компилятор выдаст предупреждение о том, что новый метод скрывает метод базового класса с тем же именем. Для устранения этой ошибки в новом методе укажите директиву r e i n t r o d u c e . type Tl = c l a s s procedure Test(I: Integer); overload; virtual; end,T2 = class(Tl) procedure Test(S: string); reintroduce; overload; end; В следующем коде будут вызываться различные методы. SomeObject : = Т 2 . C r e a t e ; SomeObject.Test('Hello!'); SomeObject.Test(7);
// в ы з ы в а е т с я // в ы з ы в а е т с я
T2.Test Tl.Test
Указатель Self Во всех методах класса доступна неявная переменная s e l f , представляющая собой указатель на тот экземпляр объекта, который был использован при данном вызове этого метода. Переменная s e l f передается методу компилятором в качестве скрытого параметра, например: function TCollection.Add: TCollectionltem; begin Result := FItemClass.Create(Self); end; где функция Add создает объект типа T C o l l e c t i o n l t e m для класса, на который происходит ссылка в поле FItemClass и который всегда является наследником T C o l l e c t i o n l t e m . Например, в кодах
Классы
129
var MyCollection: TCollection,MyCollection.Add переменная MyCollection передается в метод T C o l l e c t i o n l t e m . C r e a t e .
Свойства В основе свойств лежат промежуточные методы, обеспечивающие доступ к данным и коду, содержащемуся в классе, таким образом избавляя конечного пользователя от деталей реализации класса. По отношению к компонентам свойства являются теми элементами, сведения о которых отображаются в инспекторе объектов. Добавим свойство Val для закрытой переменной FVal в созданный ранее класс TForml, для чего необходимо выполнить следующие действия. • Создать приложение типа VCLForm.Application. • Для класса TFormq в закрытом интерфейсе ( p r i v a t e ) добавить следующее объявление: FVal: I n t e g e r ; . • В открытом интерфейсе ( p u b l i c ) ввести следующие коды: p r o p e r t y Val:Integer;. • Щелкнуть правой кнопкой мыши на описании класса и в появившемся контекстном меню выбрать пункт Complete class at cursor (Закончить реализацию класса) или нажать комбинацию клавиш <Shift+Ctrl+C>. • Delphi автоматически закончит за вас написание кодов. В результате будет получен представленный ниже листинг. type TForml = class(TForm) private { Private declarations } FVal: Integer; procedure SetVal(const Value: Integer); public { Public declarations } property Val: Integer read FVal write SetVal; end; var Forml: TForml; implementation {$R *.dfm} procedure TForml.SetVal(const Value: Integer); begin FVal := Value; end; В этом листинге будет закончено создание свойства val, что выражается в создании дополнительной процедуры SetVal и добавлении к свойству ключевых слов read и w r i t e , за которыми могут следовать имена функций или полей. В нашем простейшем случае с помощью свойства Val можно напрямую считать значение закрытого поля FVal или записать в него значение через процедуру SetVal. Co свойством можно обращаться как с обычной переменной, и при попытке присвоить свойству Val некоторое значение вызывается процедура SetVal, предназначенная для изменения значения поля FVal. Для считывания также можно создать функцию. Тогда объявление свойства может выглядеть так: property Val: Integer read GetVal write SetVal;
130
Глава 5. Объекты в Delphi
и после автоматического завершения реализации дополнительно появятся объявление функции и заготовка для нее в разделе реализации. Обратите внимание, что функция для получения значения поля начинается с префикса Get, а процедура для записи значения в поле начинается с префикса Set. Если будете вручную писать код для свойства, то обязательно придерживайтесь этого правила. Функция для получения значения поля через свойство начинается с префикса Get, а процедура для записи значения в поле — с префикса Set. Вся эта технология имеет два основных преимущества. Во-первых, она создает для конечного пользователя некий интерфейс, полностью скрывающий реализацию объекта и обеспечивающий контроль за доступом к объекту. Во-вторых, данная технология позволяет замещать методы в производных классах, что обеспечивает полиморфизм поведения объектов. При объектно-ориентированном подходе прямой доступ к полям объекта считается плохим стилем программирования, поскольку детали реализации объекта могут со временем измениться. Предпочтительнее работать со свойствами, представляющими собой стандартный интерфейс объекта, скрывающий его конкретную реализацию.
Объекты Как уже говорилось ранее, объекты (экземпляры класса) представляют собой сущности, которые могут содержать данные и код. Объекты Delphi предоставляют программисту все основные возможности объектно-ориентированного программирования, такие как наследование, инкапсуляция и полиморфизм.
Объявление и создание объекта Перед тем как создать объект, следует объявить класс, на основе которого этот объект будет создан. В Delphi это делается с помощью ключевого слова c l a s s . Объявления классов помещаются в раздел объявления типов модуля или программы. type TFooObject = class; Только после объявления класса можно делать объявление переменных этого типа в разделе var. var FooObject: TFooObject; Полностью объект создается с помощью вызова одного из конструкторов соответствующего класса. Конструктор отвечает за создание объекта, а также за выделение памяти и необходимую инициализацию полей. Конструктор не только создает объект, но и приводит этот объект в состояние, необходимое для его дальнейшего использования. Каждый класс содержит по крайней мере один конструктор C r e a t e , который может иметь различное количество параметров разного типа — в зависимости от типа объекта. Ниже рассматривается только простейший конструктор Create без параметров. Необходимо запомнить, что, в отличие от других языков программирования, например C++, конструкторы в языке Delphi не вызываются автоматически. Создание каждого объекта с помощью вызова его конструктора входит в обязанности программиста. Синтаксис вызова конструктора следующий. FooObject := TFooObject.Create; Обратите внимание на особенность вызова конструктора — он вызывается с помощью ссылки на класс, а не на экземпляр класса (в отличие от других методов, ко-
Объекты
131
торые вызываются с помощью ссылки на объект). Иначе просто нельзя — ведь экземпляр объекта FooObject в момент вызова конструктора еще не создан. Но код конструктора класса TFooObject статичен и находится в памяти. Он относится к классу, а не к его экземпляру, поэтому такой вызов вполне корректен. Вызов конструктора для создания экземпляра класса часто называют созданием объекта. При создании объекта с помощью конструктора компилятор гарантирует, что все поля объекта будут инициализированы. Все числовые поля будут обнулены, указатели примут значение n i l , а строки будут пусты. При создании объекта с помощью конструктора производится инициализация всех полей объекта. Числовые поля будут обнулены, указатели примут значение nil, а строки будут пусты.
Уничтожение объекта По окончании использования экземпляра объекта следует освободить выделенную для него память с помощью метода Free. Этот метод сначала проверяет, не равен ли экземпляр объекта значению n i l , и затем вызывает деструктор объекта — метод Destroy. Действие деструктора обратно действию конструктора, т.е. он освобождает всю выделенную память и выполняет другие действия по освобождению захваченных конструктором объекта ресурсов. Синтаксис вызова метода Free прост. FooObject.Free; Обратите внимание, что, в отличие от вызова конструктора, вызов деструктора выполняется с помощью ссылки на экземпляр, а не на тип. Кроме этого, старайтесь не использовать непосредственный вызов метода Destroy. Более безопасно и корректно вызвать метод Free, который только после проверки наличия объекта вызывает метод Destroy для разрушения объекта. Вызов метода Destroy для несуществующего объекта может иметь непредсказуемые последствия. Например, в языке C++ деструктор экземпляра статически созданного объекта вызывается автоматически, когда этот экземпляр покидает область видимости. В случае динамического создания экземпляра (с помощью оператора new) объект необходимо уничтожить самостоятельно, используя оператор d e l e t e . Однако в Delphi все экземпляры объекта — динамические, и программист должен удалять их сам. Необходимо уничтожать все, что было создано в программе. Исключение из этого правила составляют объекты, принадлежащие другим объектам. Этот тип объектов уничтожается автоматически. Еще одним исключением являются объекты с управляемым временем жизни, имеющие собственный счетчик ссылок (например, производные от классов T i n t e r f a c e d o b j e c t или TComObject), которые автоматически удаляются после ликвидации последней ссылки на них. Также заметьте, что нет необходимости всегда объявлять и описывать конструкторы и деструкторы. Ведь все классы языка Delphi неявно наследуют функциональные возможности базового класса TObject, причем независимо от того, указано ли это наследование явно или нет. Например, объявление type TFoo = class; полностью эквивалентно объявлению класса type TFoo = class(TObject);
Дружественные классы В языке C++ реализована концепция дружественных классов, т.е. классов, которым разрешен доступ к закрытым данным и функциям другого класса. В языке C++
132
Глава 5. Объекты в Delphi
этот механизм реализуется с помощью ключевого слова friend. Однако в языке Delphi нет ни такого ключевого слова, ни такого понятия, тем не менее подобного эффекта можно добиться, просто описав объекты в одном модуле. Описанные в одном модуле объекты имеют право доступа к закрытым данным и функциям друг друга, благодаря чему и обеспечивается "дружественность" в пределах модуля.
Интерфейсы Возможно, наиболее важным дополнением к языку Delphi стала поддержка интерфейсов, введенная в Delphi 3. Интерфейс определяет набор функций и процедур, которые могут быть использованы для взаимодействия программы с объектом. Определение конкретного интерфейса известно не только разработчику, но и пользователю, оно воспринимается как соглашение о правилах объявления и использования этого интерфейса. В классе может быть реализовано несколько интерфейсов. В результате объект становится "многоликим", являя клиенту каждого интерфейса свое особое лицо. Интерфейсы определяют, каким образом могут общаться между собой объект и его клиент. Класс, поддерживающий некоторый интерфейс, также обязан обеспечить реализацию всех его функций и процедур.
Определение интерфейса Подобно тому, как все классы языка Delphi происходят от класса TObject, все интерфейсы (явно или неявно) — это потомки интерфейса iunknown. Объявить интерфейс можно следующим образом: type interfaceName = interface (родительский интерфейс) ['{GUID}'] { Список членов } end; где (родительский интерфейс) и [' {GUID} ' ] являются не обязательными. Как видно, синтаксис определения интерфейса очень похож на синтаксис определения класса. Основное различие заключается в том, что интерфейс, в отличие от класса, может быть связан с глобальным уникальным идентификатором (GUID), который создается при нажатии клавиш и помещается на место нахождения курсора. Сам идентификатор заключается в квадратные скобки. Это связано с тем, что объявления интерфейсов происходят в соответствии со спецификацией модели компонентных объектов (COM) Microsoft. Также принято, чтобы идентификаторы для интерфейсов начинались с буквы I. Если вы поняли, как создавать классы, то объявление собственного интерфейса в Delphi не представляет особых сложностей. Ниже приведен пример объявления интерфейса, который реализует несколько методов. type IMalloc = interface(Ilnterfасе) [ ' {00000002-0000-0000-C000-000000000046}'] function Alloc(Size: Integer): Pointer; stdcall; function Realloc(P: Pointer; Size: Integer): Pointer; stdcall; procedure Free(P: Pointer); stdcall; function GetSize(P: Pointer): Integer; stdcall; function DidAlloc(P: Pointer): Integer; stdcall; procedure HeapMinimize; stdcall; end; •
Интерфейсы
133
Интегрированная среда разработки Delphi создает новый GUID, если нажать комбинацию клавиш .
Реализация интерфейса Приведенный ниже фрагмент кода демонстрирует реализацию интерфейсов i M a l l o c в классе TFooBar. Туре TFooBar = class(TInterfacedObject, IMalloc) ... { содержимое класса } end; В первой строке объявления класса, после указания базового класса, может быть перечислено несколько интерфейсов (это важно для реализации множественного наследования). Связывание функции интерфейса с определенной функцией класса осуществляется компилятором тогда, когда он обнаруживает метод класса, сигнатура которого совпадает с сигнатурой метода интерфейса. Класс должен содержать собственные объявления и реализации всех методов интерфейса, в противном случае компилятор выдаст соответствующее сообщение об ошибке, и программа не будет откомпилирована. Если класс реализует несколько интерфейсов, которые имеют методы с одинаковыми сигнатурами, то избежать неоднозначности позволяет назначение методам псевдонимов, как показано в следующем примере. type IFoo = interface [ ''{2137BF60-AA33-11DO-A9BF-9A4537A42701} ' ] function Fl: Integer; end; IBar = interface ['{2137BF61-AA33-11DO-A9BF-9A4537A42701} ' ] function Fl: Integer; end; TFooBar = class(TInterfacedObject, IFoo, IBar) // Назначение методам псевдонимов function IFoo.Fl = FooFl; function IBar.Fl = BarFl; // Методы интерфейса function FooFl: Integer; function BarFl: Integer; end; function TFooBar.FooFl: Integer; begin Result := 0; end; function TFooBar.BarFl: Integer; begin Result := 0; end; Здесь необходимо подчеркнуть, что при объявлении класса можно использовать несколько интерфейсов, тогда как наследование может происходить только от одного класса. Иными словами, используя только наследование классов, нельзя реализовать множественное наследование, или наследование от нескольких классов, что в некоторых случаях позволяет сделать программу более компактной и логичной. Но технология множественного наследования довольно сложно воспринимается разработчиками и обычно редко реализуется в программах, хотя есть и удачные примеры. Поэтому в Delphi не реализована технология множественного наследования в чистом виде, но ее можно имитировать с помощью интерфейсов.
734
Глава 5. Объекты в Delphi
Наследование интерфейсов Интерфейсы, как и классы, наследуют все методы своих предшественников. Однако в отличие от классов, интерфейсы не реализуют методы, т.е. не содержат необходимый код, определяющий функционирование метода. Когда интерфейс наследует некоторые методы, то он гарантирует, что эти методы будут переданы в класс для их реализации. Если родительский интерфейс не указан, то родительским интерфейсом будет интерфейс I l n t e r f a c e , который описан в модуле System. Для платформы Win32 в этом модуле объявлено три метода: Query-Interface, _AddRef и _Release. Однако в платформе .NET этих методов нет, и их не надо реализовывать. Интерфейс Ilnterface эквивалентен интерфейсу lUnknown. Для независимых от платформы приложений обычно используется интерфейс ilnterface, а интерфейс lUnknown применяется при разработке программ, зависимых от платформы Win32. Для идентификации интерфейсов используется глобальный уникальный идентификатор (GUID), представляющий строку, ограниченную фигурными скобками. Объявление GUID должно иметь вид [' {хххххххххххххххххххххххххххххххх} ' ], где х — шестнадцатеричные цифры. Для GUID объявлены специальные типы TGUID И PGUID В модуле system, которые можно использовать для управления GUID и которые имеют следующий вид. type PGUID = -^TGUID; TGUID = packed record Dl: Longword; D2: Word; D3: Word; D4: array[0..7] of Byte; end;
Директива implements Директива implements впервые была введена в Delphi 4. Она позволяет делегировать реализацию методов интерфейса другим классам или интерфейсам. Директива implements всегда указывается как последняя директива в объявлении свойства класса или интерфейса. type TSomeClass = class(TInterfacedObject, IFoo) function GetFoo: TFoo; property Foo: TFoo read GetFoo implements IFoo; end; В приведенном примере использование директивы implements говорит о том, что методы, реализующие интерфейс IFOO, следует искать в свойстве Foo. Тип свойства должен быть классом, содержащим методы IFoo, интерфейс IFoo или его потомков. В директиве implements можно использовать не один, а целый список интерфейсов (элементы списка должны отделяться запятыми). В таком случае класс, используемый для представления свойства, должен содержать методы, реализующие все приведенные в списке интерфейсы. Директива implements позволяет выполнять бесконфликтную агрегацию. Агрегация — это концепция СОМ, отвечающая за комбинацию нескольких классов для выполнения одной задачи. Другое немаловажное преимущество применения директивы
Интерфейсы
135
implements заключается в том, что она позволяет отложить использование необходимых для реализации интерфейса ресурсов до того момента, когда они потребуются реально. Предположим, что в реализации некоторого интерфейса необходим 1 МБ памяти для хранения растрового изображения, но этот интерфейс применяется достаточно редко. Вряд ли можно считать эффективным решение постоянно выделять 1 МБ памяти, который будет использоваться лишь время от времени. Директива implements позволяет реализовать интерфейс в отдельном классе, экземпляр которого будет создаваться только по прямому запросу пользователя.
Использование интерфейсов При использовании переменных типа интерфейс, в приложениях следует помнить о нескольких важных правилах. Во-первых, не забывайте, что интерфейс — это тип данных с управляемым временем жизни. А это означает, что он всегда инициализируется значением n i l , обладает счетчиком ссылок и автоматически уничтожается при выходе за пределы области видимости. Вот небольшой пример, иллюстрирующий управление временем жизни экземпляра интерфейса. var I: ISomelnterface; begin 111 инициализируется значением nil I := FunctionReturningAnlnterface; // Счетчик ссылок // увеличивается на 1. I.SomeFunc; // Счетчик ссылок уменьшается на 1. // Когда значение счетчика станет равным О, // объект I будет автоматически уничтожен, end; К тому же всегда нужно помнить, что переменные типа интерфейс совместимы по присвоению с классами, реализующими интерфейсы. В приведенном ниже примере корректно используется объявленный ранее класс TFooBar. procedure Test(FB: TFooBar) var F: IFoo; begin F := FB; // Корректно, поскольку FB поддерживает IFoo. Также можно отметить, что для переменной типа интерфейс может быть использован оператор преобразования типов as как вызов метода Q u e r y l n t e r f a c e , возвращающего адрес другой переменной типа интерфейс. Указанное правило можно проиллюстрировать. var FB: TFooBar; F: IFoo; В: IBar; begin FB := TFooBar.Create F := FB; // Допустимо, так как FB поддерживает IFoo. В := F as IBar; // Вызов функции F.Querylnterface для IBar. Если запрошенный интерфейс не поддерживается, то будет сгенерировано соответствующее исключение.
136
Глава 5. Объекты в Delphi
Резюме В данной главе кратко описаны основные понятия объектно-ориентированного программирования и способы их реализации с помощью классов и объектов. Рассмотрены структура класса и элементы класса: поля, методы и свойства. Также приведена информация об интерфейсах и их использовании для более гибкого построения логики программы. В дальнейшем будут раскрыты существенные детали реализации элементов класса и показаны конкретные примеры их использования.
Контрольные вопросы 1. Назовите три основных принципа объектно-ориентированного проектирования. В чем заключаются преимущества использования этих принципов? 2. Дайте определение понятиям "класс" и "объект". 3. Что такое поле? Как поле связано со свойством? 4. Что такое метод? 5. Для чего нужны перегрузка и переопределение методов? 6. Какие области видимости для полей и методов разрешены в классе? 7. Что такое конструктор и деструктор? 8. Какую область видимости должны иметь конструктор и деструктор? 9. Что такое интерфейс? Как вы понимаете его основное назначение? 10. Как с помощью интерфейсов создать множественное наследование?
Контрольные вопросы
137
Глава 6
Базовые классы языка Delphi В этой главе...
• Класс System.Object • Класс TControl • Класс TWinControl • Класс TForm • Резюме • Контрольные вопросы
режде чем перейти к рассмотрению остальных компонентов и возможностей системы Delphi 8, необходимо познакомиться с базовой иерархией классов этой системы, их наиболее важными свойствами и методами. Так как среда программирования Delphi создает программы, работающие на платформе .NET, то она должна использовать основные классы платформы .NET. Библиотеки с этими классами находятся в каталоге ...\WrNDOWS\Microsoft.NET\Framework\vl.1.4322 (номер версии может быть другой). И корневым классом, на основе которого созданы и будут вами создаваться классы, является класс System.Object.
Класс System.Object В Delphi 8 все типы данных являются производными от единого общего класса System.object, который определяет общее поведение для всех типов данных в среде .NET. Нет необходимости явно указывать класс System.Object в качестве базового — это подразумевается само собой. Однако ничто не мешает сделать это, явно указав базовый класс System.Object. TClassl = class(TObject) private protected public end; Здесь вы видите, что в качестве базового класса указан класс TObject, а не System.object. Но это одно и то же. И чтобы убедиться в этом, посмотрите модуль Borland. System.Delphi .pas — вы увидите там следующие объявления типов: type TObject = System.Object;
TCustomAttribute = S y s t e m . A t t r i b u t e ; Exception = System.Exception; TGUID = System. Guid; Здесь тип TObject введен для сохранения совместимости с программами, написанными для Delphi 7 и более ранних версий. Как и в любом другом классе, в классе System.Object существует свой набор членов. Обратите внимание, что некоторые члены определены как виртуальные и должны быть замещены в определении производного класса: // Самый верхний класс в иерархии классов .NET: System.Object namespace System { public class Object { public Object(); public virtual Boolean Equals (Object obj); public virtual Int32 GetHashCode(): public Type GetTypeO: public virtual String ToString(); protected virtual void Finalize(): protected Object MemberwiseClone();
Назначение каждого из методов описано в табл. 6.1 ниже. Таблица 6.1. Главные методы класса System.Object Метод
Назначение
EqualsQ
По умолчанию этот метод возвращает True только тогда, когда сравниваемые сущности указывают на одну и ту же область в оперативной памяти. Поэтому данный метод в его исходной реализации предназначен только для сравнения объектов ссылочных типов, но не структурных. Для нормальной работы с объектами структурных типов этот метод необходимо переопределить. Если происходит переопределение этого метода, то необходимо переопределить и метод GetHashCode ()
GetHashCode(;
Возвращает целочисленное значение, идентифицирующее конкретный экземпляр объекта данного типа
GetType()
Метод возвращает объект, полностью описывающий тот объект, из которого метод был вызван. Это метод идентификации времени выполнения (RTTI), который предусмотрен во всех объектах
ToStringf)
Возвращает символьное представление объекта в формате имя_пространства_имен.имя_класса (это называется "полностью определенное имя"). Если тип определен вне какого-либо пространства имен, возвращается только имя класса. Этот метод может быть переопределен для представления информации о внутреннем состоянии объекта (в формате "имя-значение")
Finalize()
Основное назначение этого метода — освободить все ресурсы, занятые объектом данного класса, перед удалением этого объекта
MemberwiseClone()
Этот метод предназначен для создания еще одной ссылки на область, занимаемую объектом данного типа в оперативной памяти. Этот метод не может быть переопределен. Если необходимо реализовать поддержку создания полной копии объекта в оперативной памяти, то надо реализовать в классе поддержку интерфейса i c i o n e a b l e
Класс System.Object
139
В Delphi все эти методы переопределены и написаны в соответствии с синтаксисом Delphi. В дальнейшем будем считать, что все классы языка Delphi являются производными от базового класса TObject, а следовательно, автоматически наследуют все его методы. В результате любой объект способен, например, сообщить свое имя, тип и даже указать, является ли он производным от того или иного класса. Подобное построение используется во многих современных языках программирования, таким образом программисту не нужно беспокоиться о реализации стандартных функций — все эти функции можно непосредственно использовать по своему прямому назначению. Данный класс лежит в основе всей иерархии классов Delphi. Он обладает самыми общими методами, присущими любому объекту языка Delphi: Класс TObject описывает основные принципы поведения объектов во время работы программы (создание, уничтожение, обработка событий и т.д.). Определение класса расположено в модуле System и всегда доступно компилятору. Корневой класс Delphi выглядит следующим образом: _TClass = class strict protected FInstanceTypeHandle: System.RuntimeTypeHandle; FInstanceType: System.Type; FClassParent: _TClass; protected procedure SetlnstanceType(ATypeHandle: System.RuntimeTypeHandle); procedure SetDelegator(ATypeDelegator: System.Type); public constructor Create; overload; constructor Create(ATypeHandle: System.RuntimeTypeHandle); overload; constructor Create(AType: System.Type); overload; function ClassParent: TClass; function InstanceTypeHandle: System.RuntimeTypeHandle; function InstanceType: System.Type,function Equals(AObj: TObject): Boolean; override; function GetHashCode: Integer; override; end; Пока не старайтесь понять все конструкции Delphi. Когда лучше познакомитесь с этим языком программирования, то все будет понятно, тем более что разработчики Delphi поставляют исходные тексты, которые можно найти в каталоге . . . \sourse. Для демонстрации использования методов, которые унаследованы от System.object, сделайте следующее. 1. Создайте проект типа Console Application. 2. Создайте новый класс, для чего поместите курсор в нужное место редактора кодов и затем дважды щелкните мышью на конструкции TClassl в окне Categories (рис. 6.1). Хотя можно все написать и вручную, но удобнее использовать уже готовые конструкции, которые нетрудно создать самому. 3. Объявите переменную myclass типа TClassl и при создании экземпляра класса можете выбрать необходимый метод из контекстного меню, которое появляется при вводе точки после имени (рис. 6.2). В данном случае будет только один пункт Create, который разрешен в данной ситуации. 4. Посмотрите все возможные методы и свойства, доступные для вновь созданного класса myclass, для чего наберите имя myclass и поставьте после него точку (рис. 6.3). 5. Закончите программу в соответствии с листингом, приведенным ниже, и запустите программу на исполнение. Результат работы программы показан на рис. 6.4.
140
Глава 6. Базовые классы языка Delphi
program Projectl; {$APPTYPE CONSOLE} uses SysUtils; type TClassl = class(TObject) private protected public end; var myClass: TClassl; begin { TODO -oUser -cConsole Main : Insert code here } myClass := TClassl.Create; Writelnl'Mi^i = '+myClass.Tostring); Writeln('Хэш-код = '+IntToStr(myClass.GetHashCode) Readln; end. flun Cofrponent Team Toosl Wndow Weclomefage| • Protect! j
н«^-«
3 й4
1
Дважды щелкнуть мышью здесь
Code Snp ipets U try except end; try except end; Fn i eyl end; tryfinelyend; J TCa l ssl - ca l ss() prviate protected pubcil end; j for :•• to do begni end;
Рис. 6.1. Создание класса
Переопределение методов класса TObject Методы, которые классы наследуют от типа TObject, обычно используются напрямую. Но иногда при создании собственных типов некоторые методы TObject приходится переопределять. Для примера рассмотрим класс Person, в который добавлены переменные для хранения данных об имени человека, его идентификационном номере и возрасте. Класс System.Object
141
g P r o j e c t l - em-lend Delphi 8.0for the Mtofflioft ЛЕТ Framework - ProteMl Jxbeto) :
FSe
Edit
Search
View
Preset
Run
Component
Team
Tools
Window
Help
'"[
! JDerauit Layout
••
,
* » |
,,
- »,
|propc«ties|
ProtectGroupl ofectl^Ke i References
uses SysUtils;
TClassl - class (TOtojecC) private protected public
•j
VAX rnyC 1 a s s : =begin
TC l e s s 1 ; Mana. •. 1 %f Model View j «ftjpata Exptorer 1
ntyClass : - T C l a s a l .|Create; « r i t e I n ( i n y C l a ? s . T o s Д^Я7ДуЯ| RendIn; end.
end; finely end: ) private protected publk end;
20: 22
: Insert
Рис. 6.2. Контекстное меню *: Project! - Borland Detpfct 8.0 tor the Microsoft Л И H&tnr.work - Pratectt&4s&r0i
Search Ve i » Project Run Component TeamToob Ш j 3 program Projectl; J Properties 1_ : №
Eds
Window
Heip ; ! jDefault Layout
r
ti ;
Щ \ —'Neil
jprojectl.exe • F|6S
uses ! SysUtils;
-jj
?S Remove
..
-I gS Proiett&oml 4 © Pro|ectl^w Cfe References
I
! type
•
10 .! J li ! : 14
!
П I ! i L
TClassl - class(TObject) private protected public: end;
!
: :*, 1 1-
1 var \ myClass:
.'. 0. .. 1
\ I
•
TClassl; —J ISaPttJertMan*:.,. 1 ^
"'• •
. Г
4ii
i
Search Toob>
furicton ClassType: TCtofSj function FleldAddress(const AName: string): TObject; ••&K#ijK Dispatcher Message); ' Create; •'•• functionToString: string; function Equal«(obj: TOb|ect): Boolean; := function GetHashCodc: Integer; :: function Get Type: Type; Free; 'function Class Type: T Class; • "% ' ' „ V , •%' • "'• -" ifunctton FieldAddress(const AName; string); TObject; №spatch(var Message);
- '•'* ?,«,
Model View! «^Data Explorer
myC l a s s : • TC l a s s 1 . C r e a t e ; myClass j Beadln;
i end.
Code Snippets ,"} try except end;
:
Jj try try except end; finally end; i try finely end; J TClassl - class() private protected public end; _ ifor :«to do begin end;
—
f
Рис. 6.З. Методы класса 142
Глава 6. Базовые классы языка DelDh
ел F:\Documents and Settings\GaHseyev\My Docuniients\Bt»rland 5 Имя - Projectl.TClassl Хэш-код - 1
в
Рис. 6.4. Результат работы программы Все классы в Delphi являются производными от класса TObject.
program ToStr; {$APPTYPE CONSOLE} uses SysUtils; type TClassl = class private protected public firstName, lastName, Ident: string; function ToString: string; override; // Переопределяем функцию. end; function TClassl.ToString: string; // Описываем функцию. begin Result := firstName + ' ' + lastName + + Ident; end; var Ivanov: TClassl; // Объявляем переменную, begin { TODO -ollser -cConsole Main Insert code here } Ivanov := TClassl.Create; // Создаем объект Ivanov. Ivanov.firstName := 'Иван'; Ivanov.lastName := 'Иванов'; Ivanov.Ident := '233454345220011 Writeln (Ivanov. ToString) ,Readln; end. Как видно из листинга, в данном случае переопределен метод ToString. Теперь этот метод будет возвращать не имя типа, как в предыдущем примере, а информацию о внутреннем состоянии объекта. Запустите программу на исполнение и увидите ту информацию, которую вы сами запрограммировали (рис. 6.5).
Класс System.Object
143
• H:\Gennady\Work \Deb>hi'i Delpb!8\Book\Programs\awp№\1 J.XH И в а н о в : > 3 3 4 Ь 4 3 4 Ь 2 2 0 О 1 1
i
Рис. 6.5. Вывод для метода ToString
Класс TControl Это базовый класс для всех компонентов, которые отображаются на дисплее во время выполнения программы. Визуальные компоненты являются элементами управления. Это означает, что пользователь может видеть такие компоненты на экране и при необходимости производить интерактивное взаимодействие с программой. Все элементы управления имеют свойства, методы и события, с помощью которых можно определить все аспекты их поведения, такие как положение элемента управления, вид курсора, подсказки, методы прорисовки или перемещения, реакция на команды пользователя и многое другое. Класс TControl имеет много защищенных свойств и методов, которые могут использоваться или экспортироваться производными классами. В табл. 6.2 и 6.3 приведены интерфейсы и свойства класса TControl. Таблица 6.2. Используемые интерфейсы класса TControl Интерфейс
Краткое описание
IControl
В основном предназначен для разработчиков компонентов, которым необходимо управлять базовыми возможностями
IDisposable
Выполняет задачи, связанные с освобождением, сохранением или переустановкой ресурсов
Таблица 6.3. Свойства класса TControl Свойство
Краткое описание
Action (Действие)
Действие является отдельным объектом, который связан с элементом управления. Действия позволяют централизовать в приложении все реакции на команды пользователя. Когда для элемента управления создается список действий, то в нем определяются все свойства и события для данного элемента управления (например, доступность элемента управления или реакция на щелчок мыши). Для создания централизованного списка действий во время проектирования используйте компонент T A c t i o n L i s t , который необходимо разместить в форме и дважды щелкнуть на нем для отображения окна редактора действий. Для добавления отдельных действий можно производить щелчки на соответствующих кнопках, или использовать контекстное меню, которое появляется после щелчка правой кнопкой мыши в окне редактора. Объект действия будет отображаться в инспекторе объектов
ActionLink (Связь с действием)
Это свойство используется для управления связями между элементом управления и действием, указанным в свойстве A c t i o n . Связи устанавливаются между свойствами и событиями действия, а также соответствующими свойствами и событиями элемента управления. В большинстве приложений это свойство не используется. Разработчики компонентов могут изменить это свойство в производных от T C o n t r o l классах.
744
Глава 6. Базовые классы языка Delphi
Продолжение табл. 6.3. Свойство
Краткое описание
Align (Выравнивание)
Используется для задания автоматического размещения элемента управления в форме или панели и сохранения занимаемого относительного пространства даже при изменении размеров формы, панели или компонента, который включает этот элемент управления. По умолчанию это свойство имеет значение alNone, что запрещает автоматическое размещение и элемент управления остается там, где его поместил разработчик
Anchors (Привязка)
Используется для привязки углов элемента управления к углам включающего их компонента. При этом если сделать привязку к противоположным углам (например [akLef t , akRight]), то при изменении размера компонента по соответствующей оси будут пропорционально изменяться и размеры элемента управления
AutoSize (Авторазмер)
Разрешает автоматическое изменение размеров элемента управления при изменении содержимого элемента управления. По умолчанию всегда выключен (False)
BiDiMode (Направление)
Используется для автоматического изменения направления написания, когда учитывается локализация программы. Например, в некоторых языках текст вводится справа налево
BoundsRect (Границы)
Используется для расположения углов элемента управления в пикселях относительно включающего его компонента. Утверждение R := Control.BoundsRect;
соответствует R.Left := Control.Left; R.Top := Control.Top; R. Right := Control. Left + Control.Width; R.Bottom := Control.Top + Control.Height; Caption (Заголовок)
ClientHeight (Высота клиента) ClientOrigin (Привязка клиента)
Используется для хранения строки, которая является заголовком элемента управления. Для подчеркивания отдельных символов поставьте перед ними амперсанд (&). Такой символ называется "ускорителем" и для быстрого обращения к данному элементу управления можно использовать комбинацию быстрых клавиш, состоящую из клавиши и подчеркнутого символа. Для отображения амперсанда используйте последовательность из двух амперсандов (&&). Замечание. Элементы управления отображают текст, используя или свойство Caption, или свойство Text. Это зависит от элемента управления. В большинстве случаев свойство Caption содержит текст, отображаемый в заголовке окна или надписи. Свойство Text включает текст, являющийся содержимым элемента управления Содержит высоту клиентского пространства в пикселях. Например, для формы клиентское пространство меньше высоты формы на высоту заголовка Содержит координаты х и Y верхнего левого угла элемента управления относительно экрана, х определяет горизонтальную координату, а у — вертикальную в пикселях
ClientRect (Границы клиента)
Содержит размеры клиентского пространства элемента управления. Замечание. Размеры определены для физического клиентского пространства, а не для логического, т.е. если элемент управления поддерживает прокрутку, по все, что не видно на экране, не входит в эти размеры
ClientWidth (Ширина клиента)
Содержит ширину клиентского пространства
Класс TControl
145
Продолжение табл. 6.3.
Свойство
Краткое описание
Color (Цвет)
Содержит фоновый цвет элемента управления. Если свойство ParentColor установлено (True), то изменение свойства родительского компонента автоматически изменяет значение этого свойства. Но если изменить это свойство, то свойство ParentColor примет значение F a l s e
Constraints (Ограничители)
Содержит минимальные и максимальные допустимые размеры элемента управления. Если эти значения установлены, то элемент управления может изменяться только в этих границах. Предупреждение. Не допускайте конфликтных ситуаций при установке этого свойства и учитывайте значения свойств Align и Anchors
ControlState (Состояние)
Используется для хранения различных аспектов состояния элемента управления (например, был ли произведен щелчок мышью или есть необходимость выравнивания). Свойство C o n t r o l S t a t e состоит из множества флагов: csLButtonDown — произведен щелчок левой кнопкой мыши, но действие еще не выполнено; c s C l i c k e d — то же самое, что и csLButtonDown, за исключением того, что установлен флаг c s C l i c k E v e n t s для свойства ControlStyle, означающий, что событие принято к исполнению; c s P a l e t t e — изменена системная палитра и элемент управления или производные от него элементы еще не закончили отработку этого события; csReadingState •— элемент управления считывает свое состояние из потока; csAlignmentNeeded — элемент управления должен отработать выравнивание; csFocusing — приложение отрабатывает сообщение о необходимости передать фокус элементу управления, хотя это не гарантирует, что фокус будет передан, но запрещает рекурсивные вызовы; c s C r e a t i n g — элемент управления или связанные с ним элементы создаются. Когда создание будет закончено, этот флаг сбрасывается; csPaintCopy — элемент управления был дублирован, т.е. копия элемента управления отображается на экране. Флаг c s R e p l i c a t a b l e свойства ControlStyle будет установлен после окончания дублирования; csCustomPaint — элемент управления получает сообщения о выполнении пользователем изображений на экране; csDestroyingHandle — окно, содержащее элемент управления, разрушено; csDocking — элемент управления состыкован
ControlStyle (Стиль)
Используется для установки различных атрибутов элемента управления, таких как возможность отработки щелчков мыши или фиксированные размеры. В свойстве используются следующие флаги: c s A c c e p t s C o n t r o l s — элемент управления становится родительским для всех элементов, размещенных в нем во время проектирования; csCaptureMouse — элемент управления реагирует на щелчки мыши; c s D e s i g n i n t e r a c t i v e — элемент управления во время проектирования преобразует щелчки правой кнопки мыши в щелчки левой кнопки; csClickEvents — элемент управления принимает и отвечает на щелчки мыши; csFramed — элемент управления имеет объемный вид; csSetCaption — заголовок элемента (свойство Caption) управления должен соответствовать свойству Name, если оно не было установлено явно; csOpaque — элемент управления заполняет все клиентское пространство; csDoubleClicks — элемент управления принимает и отвечает на двойные щелчки мыши. В противном случае двойные щелчки преобразуются в одинарные;
746
Глава 6. Базовые классы языка Delphi
Продолжение табл. 6.3. Свойство
Краткое описание
ControlStyle (Стиль)
c s F i x e d w i d t h — ширина элемента управления не может изменяться; c s F i x e d H e i g h t — высота элемента управления не может изменяться; csNoDesignvisible — элемент управления невидим во время проектирования; c s R e p l i c a t a b l e — элемент управления может копироваться с помощью метода PaintTo; csNoStdEvents — игнорируются стандартные события от мыши и клавиатуры, поэтому приложение может работать значительно быстрее; csDisplayDraglmage — если элемент управления связан со списком изображений, то создаются условия для удобного перемещения изображений; csRef l e c t o r — элемент управления отвечает на диалоговые сообщения Windows, сообщения о фокусировке или изменении размеров. Устанавливается, если элемент управления используется как элемент ActiveX; c s A c t i o n C l i e n t — устанавливается, если элемент управления используется как объект действия либо если установлено свойство Action, и сбрасывается, если это свойство сброшено; csMenuEvents — элемент управления отвечает на команды системного меню
Cursor (Курсор)
Разрешает изменять вид курсора, когда курсор размещается над элементом управления. Вид курсора определяется индексом глобальной переменной screen. В дополнение к этому могут использоваться созданные пользователем курсоры
DesktopFont (Шрифты рабочего стола)
Устанавливается значение True, когда элементу управления разрешается использовать шрифты пиктограмм Windows для свойства Font
DockOrientation Используется для задания положения элемента управления относительно (Ориентация стыковки) ДРУГИХ элементов, состыкованных с одним родительским компонентом. Выравнивание может происходить сверху вниз или слева направо DragCursor Используется для изменения вида курсора при перемещении курсора (Курсор перемещения) DragKind (Тип перемещения)
Используется для разрешения операций "перетащи и отпусти" или "перетащи состыкуй"
и
DragMode Разрешение на перемещение элемента управления. Можно выбрать ручной (Режим перемещения)(. Обычно при вводе текста для перехода на новую строку (к новому абзацу) используется клавиша <Enter>. Однако в диалоговых окнах Windows эта клавиша часто применяется для завершения ввода. Способ использования клавиши <Enter> определяется значением свойства WantReturns. Если оно имеет значение True, то клавиша <Enter> позволяет переходить к новой строке внутри текстовой области, в противном случае она служит для завершения ввода и перехода к следующему элементу управления, а для перехода к новой строке применяется комбинация клавиш . Аналогично, свойство WantTabs разрешает или запрещает использовать клавишу при вводе текста. Главное свойство данного компонента Lines (Строки). Оно имеет тип T s t r i n g s , в котором хранится список строк, введенных пользователем. Эти строки можно обрабатывать всеми методами, доступными в классе T s t r i n g s , например, сохранять в файле: Memol.Lines.SaveToFile('С:\Memo.TXT'); Наличие у текстовой области полос прокрутки задается с помощью свойства ScrollBars, значения которого перечислены в табл. 7.1. Таблица 7.1. Значения свойства ScrollBars Значение
Вид текстовой области
ssNone
Полосы прокрутки отсутствуют
ssHorizontal
Имеется горизонтальная полоса прокрутки
ssvertical
Имеется вертикальная полоса прокрутки
ssBoth
Имеются две полосы прокрутки
Если включена горизонтальная полоса прокрутки, значение свойства Wordwrap игнорируется. Это свойство определяет, будет ли выполняться автоматический перенос слов на новую строку при достижении правой границы области (при этом никакие символы новой строки в текст не добавляются — перенос отображается только на экране). При выделении фрагмента текста в текстовой области в свойство SelStart записывается позиция первого выделенного символа, а в свойство SelLength — число выделяемых символов. Выделенный текст доступен через свойство SelText (тип s t r i n g ) . Для выделения всего текста применяется метод S e l e c t A l l , для удаления выделенного текста — метод C l e a r S e l e c t i o n . Чтобы очистить содержимое текстовой области, используется метод c l e a r , чтобы отменить последние изменения — метод undo, а чтобы очистить буфер, хранящий историю изменений, и сделать такую отмену невозможной, используется метод Clearundo. Группа методов предназначена для работы с буфером обмена Windows. Для копирования выделенного текста в буфер обмена применяется метод CopyToClipboard, для вырезания выделенного текста — метод CutToClipboard, для вставки текста из буфера — метод PasteFromClipboard. Для этих целей можно также использовать стандартные комбинации клавиш:
760
Глава 7. Продолжаем изучать графический интерфейс
• • •
— вырезать выделенный текст в буфер; — скопировать выделенный текст в буфер; — вставить текст из буфера.
Когда в текстовой области происходит изменение текста, генерируется событие OnChange. Так же как и надпись, текстовую область можно использовать для отображения информации из программы. Чтобы это проверить, создайте приложение с двумя компонентами: ТМелю и TButton. Для обработчика события нажатия кнопки введите следующий текст: Memol.Lines.Add('Новая строка');. С той же целью можно использовать и метод Append. Запустите программу на выполнение и наблюдайте эффект. Поняв несложную процедуру вывода текста в текстовую область, используйте ее в дальнейшем. При этом можно использовать свойство Readonly, которое позволяет заблокировать все корректировки отображаемого текста. После размещения объектов в форме их можно свободно перетаскивать с помощью мыши в другое место, причем не только по одному, но и группой. Для этого надо выделить группу элементов — обведите их курсором мыши с нажатой левой кнопкой (после чего выделенные компоненты будут помечены серыми маркерами) и перемещайте группу элементов как один объект.
Диалоговые компоненты Диалоговые компоненты размещаются в разделе Dialogs и предназначены для создания графического интерфейса, позволяющего удобно вводить различную информацию. Однако сами диалоговые компоненты не имеют графического отображения при размещении в форме. Их расположение в форме не имеет принципиального значения и обозначается небольшой пиктограммой (рис. 7.5). Вызов этих компонентов происходит из программы. D
'' '
Р
. .'.:. .
.- -
Рис. 7.5. Пиктограммы диалоговых компонентов
Компонент TOpen Dialog Компонент TOpenDialog (Открыть файл) предназначен для ввода имени файла. При этом реализованы свойства для установок фильтров и начальных значений. Используя свойство Filter, можно создать фильтры для отображения файлов только с оп-
Стандартные компоненты
161
ределенньши расширениями. Щелкните на кнопке с многоточием для этого свойства и в открывшемся окне редактора введите необходимые расширения и соответствующие им описания. В свойстве Filterlndex указывается номер фильтра, который будет использоваться в диалоговом окне сразу после открытия. На рис. 7.6 изображен фрагмент листинга и диалоговое окно открытия файла. Е! implementation.
j var : f ileNarne: string; ]procedure TForml.ButtonlClick(Sender: TQbject); begin if OpenDialogl.Execute then fileName :- OpenDialogl.FileNawe; end; . end. 3
•£>«• f i -
ll й) Documents and Settings rJJInetpub | eSProO'am Files £3 second 3 WINDOWS
fie name: ....
Files of Jype:
Рис. 7.6. Вызов диалогового окна
Большое число значений для свойства Option позволяет настроить режимы выбора, предоставляя возможность, например, выбирать только одиночные файлы или группы файлов. В табл. 7.2 перечислены все доступные варианты выбора. Таблица 7.2. Варианты выбора для свойства Option Значение
Описание
ofReadOnly
При открытии диалогового окна по умолчанию устанавливается режим "только для чтения"
ofOverwritePromt
Генерирует предупреждающее сообщение, если происходит выбор файла, который уже используется
ofHideReadOnly
Удаляет флаг для установки режима "только для чтения"
ofNoChangeDir
После щелчка на кнопке ОК переустанавливает текущий каталог на тот, который был до открытия диалогового окна
ofShowHelp
Отображает кнопку Help
162
Глава 7. Продолжаем изучать графический интерфейс
Окончание табл. 1.2. Значение
Описание
ofNoValidate
Отменяет проверку символов в имени файла. Разрешает ввод имени с недопустимыми символами
of A l l o w M u l t i S e l e c t
Разрешает выбор нескольких файлов
o f E x t e n s i o n D i f f e r e n t Этот флаг устанавливается, когда расширение выбранного файла отличается от установленного по умолчанию. Если этот флаг используется, не забывайте его переустанавливать of PathMustExist
Генерирует сообщение об ошибке, если происходит выбор файла из несуществующего каталога
of FileMustExist
Генерирует сообщение об ошибке, если происходит выбор несуществующего файла (только для диалогового окна Open)
of CreatePrompt
Генерирует сообщение об ошибке, если происходит выбор несуществующего файла с запросом на создание нового файла
o f shareAware
Игнорирует запрет на совместное использование и разрешение выбора файла
of NoReadonlyReturn
Генерирует сообщение об ошибке, если пользователь выбирает файл в режиме "только для чтения"
ofNoTestFileCreate
Запрещает проверку сетевой защиты файла и доступность дисков. Используется только при сохранении файлов в совместный сетевой каталог
of NoNetworkButton
Убирает кнопку Network (открытие сетевых дисководов) из диалогового окна выбора файлов. Используется только при установленном флаге ofOldStyleDialog
of NoLongNaroes
Отображает только 8.3-символьные имена файлов. Используется при установленном флаге o f o l d s t y l e D i a l o g
ofoidstyleDialog
Создает старый стиль диалога выбора файлов
ofNoDef erenceLinks Запрещает разыменование сокращений Windows of EnableincludeNotify (Windows 2000 и более старшие.) Посылает уведомление C D N I N C L U D E I T E M при открытии каталога. У в е д о м л е н и е
посылается
для каждого элемента каталога. Можно использовать для контроля элементов каталога of E n a b l e S i z i n g
(Windows 98 и более старшие.) Разрешает изменять размеры с помощью мыши или клавиатуры. По умолчанию изменение размеров разрешено независимо от установок этой опции. Она требуется только при использовании процедуры перехвата
o f DontAddToRecent
Запрещает добавлять файл в список недавно открытых файлов
of ForceShowHidden
Гарантирует видимость скрытых файлов
Диалоговые элементы управления предоставляют стандартизированные интерфейсы Windows, с помощью которых пользователь выполняет общие для многих приложений операции — открытие файла, выбор шрифта, настройка цвета и другие. Например, компонент TOpenDialog является стандартизированным элементом управления, предназначенным для выбора файла.
Компонент TSaveDialog Компонент T S a v e D i a l o g (Сохранить файл) полностью идентичен TOpenDialog и отличается только надписями для отдельных элементов.
Стандартные компоненты
компоненту
163
Компонент TCheckBox Компонент TCheckBox (Флажок) предоставляет два варианта выбора — его можно установить или сбросить. Установленный флажок отмечен крестиком, у сброшенного флажка квадратик пустой. Пользователь может переключить флажок с помощью мыши или клавиши пробела. Для переключения с помощью клавиши пробела флажок должен иметь фокус ввода. После размещения компонента флажок в форме, заголовок к этому элементу управления можно задать в свойстве Caption. Расположение заголовка определяется свойством Alignment: значение t a R i g h t J u s t i f y означает расположение подписи справа, а значение taLef t J u s t i f y — слева. Главное свойство флажка называется Checked. Оно доступно для изменения и на этапе проектирования, и на этапе работы программы. Это свойство принимает значение True, если флажок включен, и False, если он сброшен. Некоторые флажки могут находиться в третьем, "частично установленном" состоянии, когда признак установки флажка отображается приглушенным цветом. Такое состояние позволяет сообщить пользователю о неполном соответствии указанному статусу (например, в ходе установки программ таким образом сообщается, что для установки выбраны не все доступные средства). Если для свойства AllowGrayed задано значение True, то флажок при последовательных щелчках на нем будет поочередно принимать состояния "сброшен", "установлен частично" и "установлен". Определить текущее состояние или задать новое из числа доступных можно, проверив или изменив свойство State, значения которого приведены в табл. 7.3. Чтобы реагировать на изменение состояния флажка, надо создать обработчик события Onclick (При щелчке). Таблица 7.3. Значения свойства State Значение
Состояние флажка
cbUnchecked
Сброшен
cbGrayed cbchecked
Установлен частично Установлен
Рассмотрим небольшую программу, отражающую изменения состояния флажка и вывода текущего состояния в надпись Label 1. Для этого необходимо разместить в форме надпись и флажок, установить значение свойства AllowGrayed равным True, сформировать обработчик события Onclick для флажка (вкладка Events в инспекторе объектов) и записать в нем оператор выбора текущего состояния флажка, как показано ниже. procedure TMyForm.CheckBoxlClick(Sender: TObject); begin case CheckBoxl.State of cbUnchecked: Label1.Caption := 'выключено1; cbGrayed: Labell.Caption := 'затемнено'; cbChecked: Labell.Caption := 'включено1; end end; Результат программы показан на рис. 7.7.
Рис. 7.7. Просмотр состояний флажка
164
Глава 7. Продолжаем изучать графический интерфейс
Компонент TRadioButton В отличие от флажка, компонент TRadioButton (Переключатель) предназначен для выбора одного переключателя из ряда возможных. Переключатели всегда используются группами. Когда пользователь выбирает один из них, то выделение с текущего переключателя снимается. Таким образом, в группе выделен всегда только один переключатель. При создании группы переключателей на форме надо расположить несколько компонентов TRadioButton. Программа поймет, что выделенным должен быть только один из них. Свойства переключателя аналогичны свойствам флажка. Свойство Alignment определяет положение подписи справа или слева от переключателя, а свойство Checked — состояние объекта. Методы GetChecked и SetChecked, позволяющие обращаться к свойству Checked и изменять его значение, в тексте программы явно не используются. Они предназначены для использования в производных от класса TRadioButton классах. Для отслеживания состояния конкретного переключателя необходимо обработать событие Onclick. С помощью флажков пользователь может установить один или несколько из них. С помощью переключателей пользователь может включить только один переключатель из группы. В форме достаточно разместить несколько переключателей, но после компиляции и запуска программы будет выделен всегда только один из них. Во время проектирования один из переключателей желательно включить, а все остальные по умолчанию оставить выключенными. Если требуется отслеживать состояние переключателей динамически, создайте обработчик события Onclick для каждого из них. Статус переключателя можно отобразить с помощью надписи Labell, как и в примере для флажков. procedure TMyForm.RadioButtonlClick(Sender: TObject); begin if RadioButtonl.Checked then Labell.Caption := 'Выбран переключатель 1' end; procedure TMyForm.RadioButton2Click(Sender: TObject); begin if RadioButton2.Checked then Labell.Caption := 'Выбран переключатель 2' end;
Компонент TRadioGroup Если в программе требуется использовать несколько групп переключателей (например, одну для указания предприятия, а другую для выбора), можно применить один из двух подходов. Первый состоит в выделении для каждой группы специального контейнерного объекта (панели), чтобы система Delphi могла понять, как объединяются переключатели. Второй подход состоит в использовании компонента TRadioGroup (Группа переключателей), который объединяет свойства и методы, обеспечивающие поддержку работы группы переключателей. После размещения в форме компонента TRadioGroup входящие в него переключатели задаются перечислением их названий. Эти названия вводятся в свойство Items, имеющее тип TString. Так как требуется ввести не одну строку, а несколько, для их ввода предусмотрен специальный редактор (рис. 7.8), который вызывается щелчком
Стандартные компоненты
165
на кнопке с многоточием, расположенной в поле для свойства Items. Текстовая область окна редактора предназначена для ввода названий переключателей, по одному в каждой строке. Не забывайте о возможности управления программой с помощью клавиатуры, поэтому перед некоторыми буквами в названиях можно указать символ "&", чтобы сделать эти буквы подчеркнутыми. Затем щелкните на кнопке ОК, и внешний вид объекта RadioGroupl примет вид, подобный изображенному на рис. 7.9.
Рис. 7.8. Редактор строк
Выбран Четвертый
•
Рис. 7.9. Компонент TRadioGroup
Поскольку компонент TRadioGroup представляет единое целое со своими переключателями, то и установки его свойств отличаются от компонента TRadioButton. Так, свойство Caption определяет не надпись каждого переключателя, а заголовок группы (исходно она называется RadioGroupl). Надписи теперь задаются в свойстве Items (Список строк). Свойство Columns задает число столбцов (первоначально один), создаваемых переключателями. Свойство Itemlndex содержит номер выделенного переключателя (число -1 показывает, что ни один переключатель не выбран). При вы-
166
Глава 7. Продолжаем изучать графический интерфейс
полнении программы значение этого свойства изменяется автоматически, когда пользователь выбирает один из переключателей группы. Разрешается менять его и программно: при занесении нового значения в свойство Itemlndex изменится и текущий выбранный переключатель формы. Динамически реагировать на выбор нового переключателя в группе можно с помощью обработчика события Onclick. Например, чтобы отображать с помощью надписи Label 1 название текущего переключателя, надо обратиться к свойству Items и выделить тот элемент, номер которого записан в свойстве Itemlndex. Предварительно следует проверить, есть ли выделенный переключатель (значение свойства Itemlndex не должно быть равно -1), или выбрать один из переключателей на этапе проектирования (например, присвоив свойству Itemlndex значение 0). p r o c e d u r e TMyForm.RadioGrouplClickfSender: TObject); begin i f RadioGroupl.Itemlndex>-1 then L a b e l l . C a p t i o n := 'Выбран ' + RadioGroupl.Items[RadioGroupl.Itemlndex]; end;
Компонент TListBox С помощью компонента TListBox (Список) пользователь может выбрать один или несколько его элементов. Если элементов много и они не умещаются в отведенной для них области, то автоматически добавляется вертикальная полоса прокрутки. Первоначально компонент TListBox, размещенный в форме, изображается в виде пустого квадрата, и его размеры можно изменять. Элементы списка можно добавлять или удалять динамически. Свойство Items имеет тип T S t r i n g s и содержит список строк, выводимых на экран. Во время разработки программист может редактировать свойство Items с помощью редактора списка строк. Во время выполнения элементы списка можно добавлять с помощью метода Add класса T S t r i n g s , а удалять — с помощью метода D e l e t e этого же класса. Свойство Itemlndex содержит порядковый номер выделенного элемента, для первого элемента значение Itemlndex равно 0. Если ни один элемент не выделен, то значение Itemlndex равно - 1 . Свойство MultiSelect позволяет выделить несколько элементов (строки) списка. Свойство SelCount содержит количество выделенных элементов, а свойство Selected определяет, выделен ли конкретный элемент. Свойство Sorted определяет, будут ли элементы выведены в алфавитном порядке. Свойство Style из раздела Miscellaneous задает внешний вид элементов. По умолчанию элементы выводятся в виде строк, однако, изменив свойство Style, их можно вывести в графическом виде или с разной высотой. Список может иметь несколько столбцов. Это не означает, что каждый столбец представляет собой отдельный список, просто при заполнении видимой части списка строками сверху вниз, очередная строка отображается в следующем столбце. В остальном такой список будет работать так же, как и обычный. Число столбцов задается с помощью свойства Columns. Если оно больше 0, то на каждую колонку отводится часть общей ширины списка (значение свойства Width, деленное на число столбцов). Список может принадлежать одному из пяти возможных типов для свойства Style, перечисленных в табл. 7.4. Если значение свойства Style не равно i b s t a n d a r d , программист должен сформировать для списка обработчик события OnDrawltem и самостоятельно организовать прорисовку содержимого. Такой список может содержать не только текст, но и другие объекты, например рисунки. Узнать высоту элемента в пикселях можно, обратившись к свойству ItemHeight.
Стандартные компоненты
167
Таблица 7.4. Значения для свойства Style Значение
Стиль списка
ibstandard
Стандартный список (стиль по умолчанию)
ibOwnerDrawFixed
Каждый элемент списка имеет фиксированную высоту, но способ его отображения определяется программистом
ibOwnerDrawVariable Кроме способа рисования, в тексте программы необходимо явно задавать размер каждого элемента списка (что позволяет создавать списки с элементами разных размеров) ibvirtual
Список является виртуальным, но все элементы — это строки одинаковой высоты. Число элементов списка сохраняется в свойстве Count. Необходимо установить элементы списка с помощью обработчика событий OnData. Если элементы связаны с объектами, то необходимо использовать обработчик событий OnDataobject. И наконец, нужно установить соответствие между строками и индексами для обработчика событий OnDataFind
i b v i r t u a l O w n e r D r a w Список является виртуальным, но все элементы — это строки одинаковой высоты. Как и для ibOwnerDrawFixed, высота символов определяется свойством itemHeight, а свойства символов можно установить с помощью обработчика события OnDrawItem. Как и для i b v i r t u a l , количество элементов сохраняется в свойстве Count. Также необходимо установить элементы списка с помощью обработчика событий OnData. Если элементы связаны с объектами, следует использовать обработчик событий OnDataObject. И наконец, необходимо установить соответствие между строками и индексами для обработчиков событий OnData и OnDataFind
Если высота элементов списка различна, то перед обработкой события OnDrawItem надо обработать событие OnMeasureltem. В заголовке соответствующего обработчика имеется параметр, описанный так: var Height: Integer; В этот параметр, передаваемый по ссылке, надо записать размер очередного элемента (его номер указывается в параметре index), а затем уже обработать событие OnDrawItem с учетом текущей высоты элемента. Выбирать элементы в списке можно по одному, а можно и несколько одновременно. Свойство MultiSelect (тип Boolean) позволяет выбирать несколько элементов, если его значение равно True. Выделить группу элементов можно, если при переходе к очередному элементу удерживать нажатой клавишу <Shift>. Такая возможность появляется, если в свойстве ExtendedSelect установлено значение True. Свойство SelCount (оно доступно только для чтения и не может быть изменено в тексте программы) содержит число выделенных строк списка. Чтобы определить, выделен ли конкретный элемент, надо, используя его номер, обратиться к свойству Selected, представляющему собой массив типа Boolean. Значение выражения ListBox.Selected[4] будет True, если пятый элемент (отсчет ведется с нуля) в списке выделен. Если выделение нескольких элементов не разрешено, то узнать, какой элемент выделен, можно, обратившись к свойству iteralndex, хранящему номер единственного выделенного элемента. Содержимое списка хранится в уже упоминавшемся ранее свойстве Items (список строк, класс TStrings). Строки можно составлять на этапе проектирования в специальном редакторе, аналогично заданию содержимого группы переключателей, а можно и во время работы программы с помощью метода Add класса TStrings. Например, если необходимо занести в список значения, отображаемые на надписи Labell, то в форму можно добавить дополнительную кнопку, а в обработчике щелчка на этой кнопке (событие Onclick) вызвать метод Add.
168
Глава 7. Продолжаем изучать графический интерфейс
procedure TMyForm.Button2Click(Sender: TObject); begin ListBoxl.Items.Add(Labell.Caption); end; Элементы списка можно упорядочить в алфавитном порядке, установив в свойство Sorted значение True. После этого при добавлении новых элементов они будут сортироваться автоматически. Для очистки всего содержимого списка используется метод c l e a r . Для удаления конкретного элемента служит метод D e l e t e S t r i n g . Например: ListBoxl.DeleteString(4); Еще один полезный метод, который часто используется для вызова контекстного меню конкретного элемента списка, называется itemAtPos. Он переводит координату точки внутри списка в номер элемента, в рамках которого лежит эта точка. Его удобнее всего использовать в обработчике щелчка мыши для объекта типа TListBox. В следующей программе разместите в форме три компонента: кнопку, список и строку состояния (компонент TStatusBar в разделе Win32). Для строки состояния свойство SimplePanel установите в состояние True, и для соответствующих обработчиков кнопки и списка событий наберите коды из приведенного ниже листинга. p r o c e d u r e TForml.ButtonlClickfSender: TObject); var I: Integer; begin for I:= 0 to Forml.ComponentCount-1 do
end;
ListBoxl.Items.InsertObject(0,Forml.Components[I].Name, Forml.Components[I] as TObject);
procedure TForml.ListBoxlMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X,Y: Integer); var APoint: TPoint; Index: Integer; TheObject: TControl; begin if Button=mbRight then begin APoint.X := X; APoint.Y := Y; Index := ListBoxl.ItemAtPos(APoint, True); if (ListBoxl.Items.Objects[Index] is TControl) then begin TheObject := (ListBoxl.Items.Objects[Index] as TControl); StatusBarl.SimpleText := TheObject.Name + 1 координаты (' + IntToStr(TheObject.Left) + ', ' + IntToStr (TheObject .Top) + ') '•; end else Beep ; end ; end; Соответствующее данному листингу окно показано на рис. 7.10. Нажав кнопку и затем щелкая правой кнопкой мыши на отдельных элементах, вы увидите имя элемента и его координаты в строке состояния. У функции ItemAtPos два параметра. Первый — объект типа TPoint, содержащий координаты точки. Второй параметр определяет, какое значение будет возвращено, если щелчок выполнен вне границ реально присутствующих в списке строк (это может быть только в области
Стандартные компоненты
169
за последней строкой). Если в такой ситуации в качестве второго параметра указано значение True, функция вернет значение - 1 , если F a l s e — увеличенный на единицу номер последнего элемента списка. Button! StatusBarl UstSoxl
X. • •
J-_.___
^1
Buttonl координаты (
Рис. 7.10. Отображение состояния элементов списка Рассмотрим еше одну программу, которая позволит лучше понять основные возможности компонента TListBox. В этой программе каждая строка из списка строк (свойство Items) может быть связана с определенным объектом, который также сохраняется в объекте типа TListBox. В листинге ниже для этого использован объект типа TFont, т.е. с каждой строкой будет связан определенный шрифт и поэтому каждая строка будет выглядеть по-своему, хотя этот объект может быть чем угодно, например изображением и текстовыми заметками. u n i t FListBox; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, Borland.Vcl.StdCtrls, Borland.Vcl.ExtCtrls, System.ComponentModel; type TForml = class(TForm) RadioGroupl: TRadioGroup; RadioGroup2: TRadioGroup; L a b e l e d E d i t l : TLabeledEdit; B u t t o n l : TButton; L i s t B o x l : TListBox; RadioGroup3: TRadioGroup; p r o c e d u r e B u t t o n l C l i c k ( S e n d e r : TObject); p r o c e d u r e ListBoxlDrawItem(Control: TWinControl; Index: I n t e g e r ; R e c t : TRect; S t a t e : TOwnerDrawState); procedure ListBoxlMeasureltem(Control: TWinControl; Index: I n t e g e r ; var H e i g h t : I n t e g e r ) ; private { Private declarations } public { Public d e c l a r a t i o n s } end; var Forml: TForml;
770
Глава 7. Продолжаем изучать графический интерфейс
implementation {$R *.nfm} var str: string; MyFont: TFont; procedure TForml.ButtonlClick(Sender: TObject); begin str := LabeledEditl.Text; MyFont := TFont.Create; case RadioGroupl.Itemlndex of 0: myFont.Color := clRed; 1: myFont.Color := clBlue; 2: myFont.Color := clGreen; end; case RadioGroup2.Itemlndex of 1: myFont.Style := [fsltalic]; 2: myFont.Style := [fsBold]; end; case RadioGroup3.Itemlndex of 0: myFont.Size := 8; 1: myFont.Size := 10; 2: myFont.Size := 12; end; ListBoxl.Items.AddObject(str,myFont); end; procedure TForml.ListBoxlDrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState); begin str := ListBoxl. Items [Index] ,ListBoxl.Canvas.Font := ListBoxl.Items.Objects[Index] as TFont; ListBoxl.Canvas.TextRect(Rect,str); end; procedure TForml.ListBoxlMeasureltem(Control: TWinControl; Index: Integer; var Height: Integer); begin Height := 24; end; end. Поскольку здесь используются обработчики прерываний ListBoxlDrawItem и ListBoxlMeasureltem (о которых уже упоминалось выше), для свойства Style выбрано значение ibOwnerDrawVariable, т.е. пользователь сам должен задать необходимые параметры для прорисовки изображения, в данном случае это будет строка с заданным шрифтом. Изображение рисуется на холсте (свойство Canvas), а необходимые размеры этого холста передаются через параметр Rect типа TRect (Прямоугольник). Работа происходит приблизительно так. При щелчке на кнопке Ввод в список будут занесены очередная строка и связанный с ней объект в соответствии с методом: ListBoxl.Items.AddObject(str,myFont);. Изменения в списке должны отобразиться на экране, т.е. автоматически будут вызваны обработчики событий OnMeasureltem и OnDrawltem. Эти обработчики будут вызываться при прорисовке каждой строки начиная со строки с индексом 0 до строки с максимальным индексом. При прорисовке каждой строки будет использоваться свой шрифт, записанный в связанном со строкой объекте типа TFont, как показано на рис. 7.11. Еще несколько слов о свойстве TabOrder из раздела Layout. Оно автоматически устанавливается при размещении компонентов в форме и задает порядок, в котором компоненты будут получать фокус при нажатии клавиши . Для компонента LabeledEditi его зна-
Стандартные компоненты
171
чение должно быть равно о, т.е. при открытии окна этот компонент будет иметь фокус и в него можно сразу вводить данные, не делая дополнительных щелчков мыши. Если это свойство имеет другое значение, то его необходимо установить вручную, при этом аналогичные свойства остальных компонентов будут автоматически пересчитаны. iwettyuicpO qvvertyuiopf] gwerfytMCpf] qwertyuiop[] qwertyuiop[] l*vertyuiop[] qwertyuiop[] qwertyuiop[] Шрифт
Г Красный '
••'••;•!•г Курсив