- •Windows -приложение
- •Среда программирования
- •Встроенный отладчик
- •Использование графики
- •Графические данные и палитра
- •Сохранение проекта
- •Структура приложения
- •Структура модуля
- •Простые типы
- •Символьные типы
- •Логические типы
- •Тип перечень
- •Составной оператор
- •Оператор if
- •Оператор цикла for
- •Оператор цикла while
- •Оператор цикла repeat
- •Пример приложения 6
- •Пример приложения 7
- •Статические массивы
- •Динамические массивы
- •Оператор with
- •Идентичность типов
- •Совместимость типов
- •Преобразование типов
- •Операторы обработки исключительных ситуаций
- •Рекурсия
- •Процедура exit
- •Директивы подпрограммы
- •Класс как объектный тип
- •Наследование
- •Операции is и as
- •Типы ссылки на класс
- •Типизированные файлы
- •Файлы без типа
- •Пример приложения 17
- •Компонент tmainmenii
- •Двунаправленные списки
- •Потоки данных
- •Пример приложения 22
- •Интерфейс drag and drop
- •Пример приложения 24
- •С файлами
- •Пример приложения 26
- •Программные потоки
- •Приоритеты потоков
- •Класс tthread
- •Проблемы синхронизации потоков
Операции is и as
Операции Is и As применяются к объектам. С помощью операции Is определяется, принадлежит ли данный объект указанному типу или одному из его потомков. Например, выражение AnObject is TMyClass возвращает true, если переменная AnObject совместима по присваиванию с переменной типа TMyClass. Для приведения типа какого-либо объекта применяется операция As. Например, with AnObject as TMyClass do ...
В операции As сначала проверяется совместимость типа с помощью операции Is. Если типы несовместимы, то запускается обработчик исключительной ситуации EInvalidCast. Поэтому использование следующей операции является менее надежным способом неявного приведения типа: With TMyClass (AnObject) do ...
МЕТОДЫ
Методы служат для обработки информации, содержащейся в полях. Доступ к полям может осуществляться без дополнительных (формальных) параметров. Формальные параметры у методов обычно служат для обмена информацией с другими классами.
В классе метод только объявляется. Описывается он в разделе реализации модуля (секция Implementation). При описании указывается имя класса-владельца и через точку имя метода. Например,
Procedure TMyClass.AMethod(Param:AType); Begin
end;.
114
Внутри begin ... end можно вызывать любые методы предков с указанием имени предка. Ближайший предок, имеющий такое же имя, как у данного метода, может вызываться с помощью зарезервированного слова inherited.
Методы могут синтаксически оформляться следующим способом:
-
Procedure;
-
Function;
-
Constructor - вид Procedure, служащий для построения объекта, инициализации полей и правильного вызова так называемых виртуальных методов;
-
Destructor - вид Procedure, служащий для освобождения памяти, т.е. разрушения объекта.
При построении объекта автоматически объявляется дополнительная переменная Self. Тип этой переменной совпадает с классом, породившим данный объект. Переменная Self является локальной и определена только внутри данного экземпляра класса. При необходимости внутри любого метода можно воспользоваться этой переменной.
ВИДЫ МЕТОДОВ
Методы бывают разных видов. По умолчанию методы являются статическими (Static) Методы в разных классах могут иметь одинаковые имена. При вызове методов обязательно указывается через точку имя объекта, вызвавшего данный метод. Например, пусть класс AClassType породил объект AnObject. Если вызывается метод AStaticMethod, то вызов записывается так: AnObject.AStaticMethod;.
Если в классе AClassType непосредственно такого метода нет, то вызывается ближайший по линии наследования метод с заданным именем.
Недостаток статических методов заключается в следующем. Пусть метод AStaticMethod вызывает внутри себя другой метод, например Second-Method. Пусть по линии наследования определено несколько методов с именем SecondMethod. В данном случае AStaticMethod вызовет ближайший по программному описанию SecondMethod, независимо от того объекта, который вызвал метод AStaticMethod.Таким образом, при использовании статических методов нельзя конкретно указать, какой из имеющихся по линии наследования нескольких методов с одним именем требуется вызвать.
МЕТОДЫ VIRTUAL И ПОЛИМОРФИЗМ
Особенности виртуальных (Virtual) методов рассмотрим на примере. Пусть объявлен класс AClass. Этот класс имеет потомка, который, в свою очередь, также имеет потомка, например AClass -> BClass -> CClass.
116
Пусть AClass содержит метод Drag, который служит для рисования некоторой геометрической фигуры. При этом внутри метода Drag вызывается метод Show для рисования заданной фигуры. Пусть метод show реализован в каждом из трех указанных классов для рисования точки (класс AClass), окружности (BClass) и квадрата (CClass). Метод Drag содержится только в AClass. Так как нужная фигура вызывается для рисования из метода Show, нет необходимости дублировать его в других классах. Пусть заданы три объекта: Var A:AClass; В:Bclass; С:CClass.
Пусть с помощью соответствующих конструкторов эти объекты созданы. Пусть вначале все три метода Show объявлены как статические. Запишем вызов на рисование геометрической фигуры: с. Drag;.
Рассмотрим, какая фигура изобразится в данном случае. По линии наследования от CClass, вызвавшего Drag, в классе AClass найдется метод Drag. При вызове происходит обращение к Show. В данном случае вызовется ближайший к Drag метод, находящийся в классе AClass, т.е. изобразится точка. Вызвать метод Show из класса BClass для рисования окружности или вызвать Show из CClass и нарисовать квадрат с помощью статических методов нельзя.
Итак, в данном случае ставится следующая задача. Вызов C.Drag; должен нарисовать квадрат, B.Drag; - окружность, соответственно A.Drag; -точку, т.е. метод Show для С.Drag должен вызываться из класса CClass, для B.Drag- из класса BClass, а для A. Drag- из класса AClass. Такая задача решается с помощью виртуальных методов show. Тогда вызов show будет определяться конкретным фактическим объектом.
Виртуальные методы определяются с помощью ключевого слова Virtual. По линии наследования все реализации одного и того же виртуального метода должны иметь одинаковые заголовки. С помощью зарезервированного слова Virtual определяется самая первая реализация виртуального метода, все последующие реализации переопределяются с помощью ключевого слова Override. Если заданный метод появляется далее по линии наследования без ключевого слова Override, то свойство виртуальности теряется для данного класса и всех его потомков.
Статические методы определяются на этапе компиляции, а виртуальные - при выполнении программы. Для реализации вызовов виртуальных методов в зависимости от фактического объекта строится так называемая таблица виртуальных методов. Построение этой таблицы возлагается на соответствующий конструктор.
Свойство одного и того же метода вести себя по-разному называется полиморфизмом (многоформным). Например, метод Drag, указанный в приведенном выше примере, может построить точку, окружность или квадрат. Для реализации полиморфизма используются виртуальные методы.
117
Итак, наряду с инкапсуляцией и наследованием, третьей особенностью типа класс по сравнению с другими типами является полиморфизм.
МЕТОДЫ DYNAMIC
Механизм Dynamic доступа к другим методам подобен механизму Virtual. В обоих случаях адрес нужной процедуры или функции определяется фактическим объектом. Отличие заключается в том, что в данном случае строится таблица динамических методов. Обращение к этой таблице происходит медленнее, чем к таблице виртуальных методов, зато объем программы получается меньше. Методы Dynamic целесообразно использовать, когда класс имеет множество потомков, а число переопределяемых методов небольшое.
МЕТОДЫ MESSAGE
Методы Message - обработки сообщений - представляют собой особую форму динамических методов. Обработчики сообщений всегда являются процедурами. Для ускорения поиска в таблице динамических методов после ключевого слова Message записывается константа целого типа, являющаяся индексом нужного метода. В обработчике сообщений имеется один параметр Var. Методы Message - это одно из звеньев взаимодействия компонентов Delphi с Windows. Объявление обработчика сообщений выглядит приблизительно так: Procedure Handle_WM_Paint(var Msglnfo); Message WM_Paint;.
Для поиска нужного адреса в таблице динамических методов в данном примере записана константа WM_Paint. Многие константы для Message зарезервированы за конкретными сообщениями Windows и выбирать их произвольно нельзя. Указанная константа поиска данного сообщения Windows может быть переопределена в каком-либо предке по линии наследования для данного объекта. Если константа Message не найдена, то вызывается метод обработки сообщений, заданный по умолчанию.
МЕТОДЫ ABSTRACT
Обычно методы создаются для выполнения каких-то конкретных действий. Если по какой-либо причине это выполнить не удается, метод в классе может быть зарезервирован с обязательным переопределением его в классах потомков. Такой метод помечается ключевым словом Abstract. Если абстрактный метод не переопределен, то вызов такого метода приводит к вызову специальной процедуры Abstract, которая генерирует исключительную ситуацию. Абстрактным не может быть статический метод, так как статические методы нельзя переопределять. Например,
118
Type TmyParent=class
Procedure AMethod;virtual;abstract; End; TmyClass=class(TmyParent)
Procedure AMethod;override; End;
МЕТОДЫ OVERRIDE
Как указано выше, зарезервированным словом Override помечаются переопределенные виртуальные или динамические методы (пример выше).
МЕТОДЫ CLASS
Исходное назначение методов - определять поведение экземпляров объектов какого-либо класса. В некоторых случаях необходимо иметь ситуацию, когда поведение, задаваемое для метода, не должно зависеть от реально существующего объекта. Такая ситуация возникает с методом, который, например, должен возвращать имя класса. В этом случае метод помечается ключевым словом Class. В отличие от других команд, таких, как Dynamic, Virtual и т.д., слово Class ставится перед заголовком метода, например Class Procedure AMethod;.
ПРИМЕР ПРИЛОЖЕНИЯ 14
Рассмотрим пример по использованию конструкторов и деструкторов в объектах. Кроме того, посмотрим, как можно использовать указатели на методы и методы class.
Пусть на форме динамически строится совокупность объектов типа TButton. Далее пусть эти объекты последовательно разрушаются с выдачей соответствующих сообщений о числе объектов. Рассматривать этот пример будем последовательно, вводя все новые и новые конструкции и проверяя их непосредственно в среде Delphi.
ДИНАМИЧЕСКОЕ СОЗДАНИЕ КОМПОНЕНТОВ
Пусть имеется форма, у которой есть только обработчик события Mouse Down, с помощью которого можно динамически строить кнопки, нажимая на левую клавишу мыши. Закрепляется каждая кнопка на форме в позиции координат точки, в которой была нажата клавиша мыши. Вот код данного обработчика:
procedure TForm1.FormMouseDown(Sender:TObject; Button:
TMouseButton; Shift: TShiftState; X, T: Integer); Var Btn:Tbutton;
Begin
Btn:=TButton.Create(self)
Btn.Parent:=self;
Btn.Left:=x;
Btn.Top:=y;
Btn.Width:=Width+50;
Btn.Caption:=Format('Кнопка x,y= %d,%d',[x,y]); end;.
В Delphi ключевое слово self используется, например, когда нужно явно обратиться к текущей форме в одном из ее методов.
Динамическое создание компонентов является тем случаем, когда требуется передать конструктору create параметр Owner (указать владельца создаваемого объекта), а затем присвоить то же значение свойству Parent (указать родителя). В обоих случаях нужно передавать имя текущей формы (не всегда параметры Owner и Parent совпадают, как в данном случае). Таким образом, обработчик выше будет корректно работать, если параметр self заменить на Form (пусть такое имя имеет переменная типа Form). Следует отметить, что код, приведенный выше, обычно записывается с использованием оператора with:
procedure TForm1.FormMouseDown(Sender: TObject/Button:
TMouseButton; Shift: TShiftState; X, Y: Integer); begin
with TButton.Create(self) do begin Parent:=self; Left:=x; Top:=y;
Width:=Width+50;
Caption:=Format('Кнопка x,y= %d,%d,[x,y]); end; end;.
Ниже приводится объявление модуля, используемого в данном случае:
unit priml4;
interface
uses Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls, ExtCtrls, Menus; Type
TForm1 = class(TForm)
procedure FormMouseDown(Sender: TObject;Button: TMouseButton;Shift: TShiftState; X, Y: Integer);
end; var Form1: TForm1;.
Вариант решения примера представлен на рис 34.
Рис.34
ИСПОЛЬЗОВАНИЕ КЛАССА СО СЧЕТЧИКОМ ОБЪЕКТОВ
Пусть в примере 14 будем строить кнопки некоторого собственного типа, унаследованного от типа TButton. Построим для этих целей класс, в котором предусмотрим счетчик создаваемых объектов.
Для хранения числа создаваемых объектов понадобится переменная целого типа. Объявим ее в секции implementation: var CountBtns: integer = 0;. Эта переменная содержит общую информацию о форме и представляет данные модуля, поэтому она не включена как поле в класс. Для доступа к такой информации понадобится метод класса. Теперь представим объявление нашего класса:
type
TMyButton=class(TButton) public
Constructor Create{AOwner:TComponent); override;
Destructor Destroy; override;
class function GetCount:integer; end;.
Увеличение счетчика объектов предусмотрим при вызове конструктора, уменьшение - при вызове деструктора. Для построения объектов и их разрушения, естественно, будем использовать стандартные методы. Ниже приводятся описания деструктора и конструктора:
constructor
TMyButton.Create (AOwner: TComponent); begin
inherited;
Inc(CountBtns); end;
destructor TMyButton.Destroy; begin
dec(CountBtns);
inherited; end;.
120
121
Так как переменная countBtns объявлена в секции implementation, обратиться к ней извне модуля нельзя. Только метод класса позволяет нам прочитать ее текущее значение:
class function TMyButton.GetCount: integer; begin
Result:=CountBtns; end;.
Теперь можно создавать объекты нашего нового типа TMyButton, немного изменив приведенный выше код обработчика FormMouseDown:
procedure TForm1.FormMouseDown(Sender: TObject; Button:
TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
with TMyButton.Create(self) do begin
Parent:—self;
Left:=x;
Top:=y;
Width:=Width+60;
Caption:=Format{'%d кнопка x,y= %d,%d',[GetCount,x,y]); end;.
Класс-функция GetCount может вызываться, в отличие от обычных методов, как из объекта (в обработчике FormMouseDown), так и из класса. Например, добавим таймер (компонент TTimer) на форму и обеспечим при срабатывании таймера изменение заголовка формы с помощью следующего обработчика события OnTimerr
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Caption:=Format('Кнопок на форме %d',[TMyButton.GetCount]);
end;.
В результате решения получим, например, следующий вариант (рис. 35).
Проследим за разрушением объектов на форме. Очевидно, это можно увидеть в секциях initialization и finalization, т.е. после того, как форма начнет разрушаться.
initialization
MessageBox(0,PChar(Format('На форме %d кнопок', [TMyButton.GetCount])),'Инициализация',mb_ok);
finalization
MessageBox(0,PChar(Format('На форме %d кнопок', [TMyButton.GetCount])),’Финиш’,mb_ok);.
В данном случае использовалась функция Windows API MessageBox, так как при разрушении формы доступ в секции finalization ко многим функциям Delphi невозможен. Запуская данную программу на выполнение, получим два сообщения до построения и после разрушения формы.
СОБЫТИЯ
В приложениях и компонентах событие возникает как результат послания операционной системой сообщения, что произошло некоторое действие, которое контролирует метод послания уведомления о событии. Этот метод проверяет, назначил ли пользователь соответствующий обработчик событий. Если событие произошло и обработчик назначен, то метод послания уведомления о событии вызывает соответствующий обработчик. Методы послания уведомления о событии можно переопределять, используя стандартные методы, или создавать свои собственные.
Рассмотрим вариант переопределения методов послания уведомления о событии. Такие методы реализуются в разделе Protected. Поэтому потомки тех встроенных классов, в которых реализуются такие методы, могут делать доступными их для пользователя. Например, рассмотрим, как переопределяется метод послания уведомления о событии Click для кнопки, одновременно изменяя ее заголовок на строку, в которую записано текущее время.
TtimeButton=class(Tbutton) Protected
Procedure Click; override; End;
………………………..
Procedure TtimeButton.Click;
Inherited Click;
Caption:=DateTimeToStr(Now); End;.
Рис.35
123
Методы послания уведомления о событии являются указателями на метод, которые вызываются в случае, когда компонент получает сообщение от Windows о том, что произошло некоторое событие, воздействующее на данный компонент. Обработчики событий представляют собой свойства, которые выполняют чтение и запись в указатели на метод. Например, рассмотрим фрагмент, взятый из класса TControl, содержащегося в исходном коде VCL. Имена обработчиков событий по соглашению содержат префикс On. Вначале объявляется событие; TNotifyEvent = procedure (Sender:TObject) of object;. Далее объявляется класс, содержащий поля данного типа, и обработчики событий, работающие с этими полями.
Tcontrol=class(TComponent) Private
FOnClick: TNotifyEvent;
………………….
Ptotected
Property OnClick: TnotifyEvent read FOnClick write FOnClick;
……………………………………
end;.
При создании собственных обработчиков необходимо научиться строит методы посылки уведомления о событии. Эти методы должны уметь получать и обрабатывать сообщения Windows, что возможно сделать с помощью методов вида Message.
УКАЗАТЕЛИ НА МЕТОДЫ
Этот тип данных представляет собой ссылку (указатель) на метод. Указатель на метод содержит два адреса: одна ссылка на адрес кода метода, другая - на адрес экземпляра объекта - представляет собой скрытый параметр self. Адрес self представляет собой в данном случае адрес расположения данных, принадлежащих конкретному объекту.
Указатели на методы реализуют один из принципов компонентной технологии - делегирование. Если кто-то написал класс, у которого есть поля в виде указателей на методы, например,
Туре
TNotifyEvent=procedure(Sender:Tobject) of object; TMyButton=class
OnClick: TNotifyEvent; End;
,то можно менять поведение построенных (даже скомпилированных) объектов этого типа, просто присваивая этим указателям новые методы. Например, пусть объявлено:
124
туре
TForm1=class(TForm)
Procedure OnButton1Click(Sender:Tobject); Button1:MyButton; End;.
Теперь при построении компонента на форме можно делегировать обработчик OnButton1click из TForm1 в MyButton путем следующего присваивания: MyButton.OnClick:=Form1.OnButton1click;.
ПРИМЕР ПРИЛОЖЕНИЯ 15
Продолжим рассмотрение примера 14. Попытаемся не только динамически создавать новые объекты, но и разрушать их также динамически. Выберем, что уничтожение очередного объекта будет наступать, как только пользователь нажмет на клавишу Backspace (#8). Для реализации этой идеи понадобятся указатели на методы.
В данном случае динамически созданный объект для своего уничтожения должен отслеживать нажатие клавиши #8. Для этих целей может служить событие OnKeyPress. Этому событию надо делегировать собственное событие. Прежде всего, необходимо убедиться, что оно объявлено как указатель на метод, иначе делегирование невозможно. В справочной системе Delphi можно найти следующее объявление:
TKeyPressEvent=procedure(Sender:Tobject;var key:char) of object;
Property OnKeyPress: TKeyPressEvent;.
Таким образом, для делегирования необходимо создать свою процедуру. В классе TForm1 добавим собственный обработчик: Procedure MyButtonKeyPress(Sender:Tobject; var key:Char);.
Кроме того, в класс TForm1 необходимо добавить переменную для записи того динамически построенного метода, который подлежит удалению, так как может удаляться не только текущий объект, но и предыдущий (если новые объекты не создаются). Добавим такое объявление в секцию Private ToDestroy:TMyButton;. Теперь определим новую процедуру обработки события OnKeyPress:
Procedure TForm1.MyButtonKeyPress(Sender: TObject;
var Key: Char); begin
if key=#8 then ToDestroy:=Sender as TButton;
end;.
125
Эта процедура не уничтожает, а запоминает подлежащий уничтожению объект. Чтобы она работала, необходимо при создании кнопки делегировать наш обработчик, записывая OnKeyPress :=MyButtonKeyPress ;.
Допишем еще одну строку для установки фокуса на текущий объект SetFocus;, т.е. после удаления очередного объекта необходимо переместить фокус ввода на предыдущий объект.
Удаление кнопки реализуем в обработчике onTimer. Полный текст программы приводится ниже.
unit priml5;
interface
uses Windows, Messages, SysUtils,Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls, ExtCtrls; type
TMyButton=class(TButton) public
Constructor Create (AOwner:TCoraponent) ; override; Destructor Destroy; override; class function GetCount:integer; end;
TForm1 = class(TForm) Timer1: TTimer;
procedure FormMouseDown(Sender: TObject; Button:
TMouseButton; Shift: TShiftState; X, У: Integer); procedure Timer1Timer(Sender: TObject};
private
ToDestroy:TMyButton;
Procedure MyButtonKeyPress{Sender:Tobject; var key:Char); end;
var Form1: TForm1; implementation
var CountBtns: integer =0; {$R *.dfm}
Constructor TMyButton.Create(AOwner: TComponent); begin
inherited; Inc(CountBtns) ;
end;
Destructor TMyButton.Destroy; begin
.dec(CountBtns); inherited;
end;
class function TMyButton.GetCount: integer; begin
Result:=CountBtns; end;
126
procedure TForm1.MyButtonKeyPress(Sender: TObject; var Key: Char); begin
if key=#8 then ToDestroy:=Sender as TMyButton;
end;
procedure TForm1.FormMouseDown(Sender: TObject; Button:
TMouseButton; Shift: TShiftState; X, Y: Integer);
Begin
with TMyButton.Create(self) do begin Parent:=self ;
Left:=x;
Top:=у;
Width:=Width+60;
Caption:=Format('%d кнопка x,y= %d,%d', [GetCount,x, y]); OnKeyPress:=MyButtonKeyPress; SetFocus; end; end;
procedure TForm1.Timer1Timer(Sender: TObject); begin
if Assigned(ToDestroy) then begin SelectNext{ToDestroy,false,true); ToDestroy.Free;
ToDestroy:=nil;
end ;
Caption: =Format {'Кнопок %d'., [TMyButton. GetCount] ); end; end.
Вариант решения получим таким же, как на рис. 35. Однако в данном варианте, нажимая неоднократно на клавишу Backspace, можно удалить все кнопки, построенные на форме.
Все переменные типа класс (например, ToDestroy) по сути, являются указателями, поэтому для проверки, существует ли тот или иной объект, применяется функция Assigned (обработчик Timer1Timer), которая проверяет, равна переменная значению "пустой указатель" (т.е. nil) или нет.