- •Windows -приложение
- •Среда программирования
- •Встроенный отладчик
- •Использование графики
- •Графические данные и палитра
- •Сохранение проекта
- •Структура приложения
- •Структура модуля
- •Простые типы
- •Символьные типы
- •Логические типы
- •Тип перечень
- •Составной оператор
- •Оператор if
- •Оператор цикла for
- •Оператор цикла while
- •Оператор цикла repeat
- •Пример приложения 6
- •Пример приложения 7
- •Статические массивы
- •Динамические массивы
- •Оператор with
- •Идентичность типов
- •Совместимость типов
- •Преобразование типов
- •Операторы обработки исключительных ситуаций
- •Рекурсия
- •Процедура exit
- •Директивы подпрограммы
- •Класс как объектный тип
- •Наследование
- •Операции is и as
- •Типы ссылки на класс
- •Типизированные файлы
- •Файлы без типа
- •Пример приложения 17
- •Компонент tmainmenii
- •Двунаправленные списки
- •Потоки данных
- •Пример приложения 22
- •Интерфейс drag and drop
- •Пример приложения 24
- •С файлами
- •Пример приложения 26
- •Программные потоки
- •Приоритеты потоков
- •Класс tthread
- •Проблемы синхронизации потоков
Типы ссылки на класс
Такой программный элемент, как тип обычной переменной, представляющий собой правила обращения с теми или иными переменными, существует только во время компиляции программы. Эти правила не могут измениться во время выполнения программы. Типы ссылки на класс, которые синтаксически объявляются как class of TMyClass, позволяют нарушить указанные правила. С помощью этих типов можно обращаться к объектным типам во время выполнения программы. Для некоторого заданного типа
127
объектный тип - это значение, которое можно записать в переменную типа ссылки на класс. Во время выполнения программы в разных ее участках переменной данного типа можно присваивать в качестве значения различные объектные типы и, таким образом, строить нужные объекты, вызывая или иной виртуальный конструктор.
СВОЙСТВА
Свойства представляют интерфейс с внутренними полями данных того или иного объекта. Внутренние поля обычно объявляются в разделе Private. Их имена, как правило, начинаются с буквы F, например поле FColor. Соответственно свойство, с помощью которого осуществляется доступ к заданному полю FColor, имеет имя Color. Свойства могут объявляться с различной степенью доступа. Если требуется, чтобы они отображались в окне инспектора объектов, их объявляют в разделе Published.
Свойства могут создаваться с помощью различных типов данных, а именно:
-
Simple - простые свойства;
-
Enumerated - перечисляемые;
-
Set - множества;
-
Object - объектного типа;
-
Array - индексированные свойства.
Разные типы свойств по-разному отображаются в инспекторе объектов и имеют свои собственные редакторы для изменения значений свойств. Далее рассмотрим синтаксис записи различных видов свойств.
СВОЙСТВА SIMPLE
Простые свойства включают числовые, символьные и строковые типы данных. Наиболее часто используются свойства типа integer и string. Например, свойства Width, Height имеют тип integer, свойство Caption имеет тип string. Объявим для демонстрации синтаксиса записи простых свойств следующий класс:
Туре
TSimple=class(TCustomControl) Private
PString: string; Published
Property StringProp:string read FString write FString; end;
Как следует из этого примера, свойства имеют тип поля, в котором хранится значение данного свойства. Кроме того, для свойства необходимо указать
способ чтения информации из соответствующего поля и способ записи данных в поле. Если отсутствует или способ чтения, или способ записи данных, то соответствующее свойство имеет доступ или только по чтению, или только по записи.
СВОЙСТВА ENUMERATED
Свойства enumerated определяются типом перечень и логическим типом Boolean. Обычно все возможные значения данного типа свойств некоторым способом помечаются, например, как в объявлениях ниже:
туре
TEnumProp=(epZero, epOne, epTwo, epThree); TEnum=class(TCustomControl) Private
FEnumProp: TEnumProp; Published
Property EnumProp:TEnumProp read FEnumProp write FEnumProp; end;.
СВОЙСТВА SET
Элементы свойства Set (множество) в инспекторе объектов заключаются в квадратные скобки. Для развертывания в инспекторе объектов элементов базового множества, включающего в себя все возможные варианты, имеется знак "+", расположенный слева от наименования свойства. Включить или не включить тот или иной элемент из базового множества в рабочее множество выбирают путем указания true или false. Рабочее множество определяет значение данного свойства. Рассмотрим пример объявления свойства Set. При этом используется тип TEnumProp, объявленный выше.
TSetProp=set of TEnumProp TSetClass=class(TCustomControl) Private
FSetProp:TSetProp; Published
Property SetProp:TsetProp read FSetProp write FSetProp; End;.
СВОЙСТВА OBJECT
Свойства Object в инспекторе объектов помечаются или знаком "+" или кнопкой с многоточием. Например, свойство Font имеет объектный тип. Дня того чтобы привести пример объявления свойства объектного типа, несводимо предварительно сформировать объект какого-либо класса и вы-
. 129
брать предка для этого класса. На практике в большинстве случаев в качестве предка выбирается встроенный класс TPersistent. Объявим класс, который будет определять далее в примере тип свойства.
Туре
T0bjectDop=class{TPersistent) Private
FMyInt:integer; Public
Property MyProp:integer read FMyInt write FMyInt; End;.
В данном случае нужно решить одну проблему. Объявленный выше класс (TObjectDop) должен войти как составная часть в класс, который должен будет содержать свойство заданного объектного типа TObjectDop. Соответственно потребуется задавать этому свойству значение, а это означает, что потребуется объект типа TObjectDop, для которого необходимо обеспечить выделение памяти и освобождение памяти. Стандартных методов выделения и освобождения памяти в данном случае недостаточно. Для выделения памяти создадим конструктор Create, а для высвобождения памяти - деструктор Destroy.
туре
TObjectProp=class(TCustomControl) Private
FObjectProp: TObjectDop; Public
Constructor Create(AOwner:TComponent);override; Destructor Destroy;override;
Published
Property ObjectProp: TObjectDop
read FObjectProp write FObjectProp; End;.
Конструктор и деструктор объявлены с командой Override. Это означает, что в данном классе переопределяются имеющиеся по линии наследования стандартные виртуальные конструктор Create и деструктор Destroy. Определим эти методы.
Constructor TObjectProp.Create(AOwner:TComponent); begin
Inherited Create(AOwner);
FObjectProp:= TObjectProp.Create;
end;
Destructor TObjectProp.Destroy; Begin
FObjectProp.Free;
130
Inherited Destroy; End;.
Необходимо обратить внимание на то, в какой последовательности вызываются стандартные методы с помощью процедуры inherited.
СВОЙСТВА ARRAY
Свойства Array позволяют создать очень похожие на массивы индексированные свойства, которые отличаются от обычных массивов по двум основным аспектам:
-
свойства типа Array могут индексироваться строковым значением;
-
свойства типа Array могут получать доступ только к одному элементу за одно чтение.
В окне Object Inspector эти свойства помечаются кнопкой с многоточием (…). Для изменения значений свойств данного вида вызывается специальный редактор. Ниже приводится пример объявления индексированного свойства.
Туре
Str7=string[7];
TMas=array [1..3] of str7; TMasClass=class(TCustomControl)
Private
FMasProp: TMas;
Function GetMasInt(pIndex:integer):string;
Function GetMasStr(pIndex:string):integer; Public
Constructor Create(AOwner:TComponent);override;
Property MasPropInt[Index:integer]:string read GetMasInt;
Property MasPropStr[Index:string]:4integer read GetMasStr; end;.
В данном случае переопределяется конструктор create, который необходим не для выделения памяти, а для задания значений элементам индексированного свойства. Поэтому необходимо будет исключить вызов стандартного конструктора с помощью команды inherited. Далее определим конструктор и две функции для чтения значений элементов индексированного свойства, которое в данном случае имеет доступ к соответствующему полю только по чтению.
Constructor TMasClass.Create(AOwner:TComponent); begin
FMasProp[1]:='one';
FMasProp[2]:='two';
FMasProp[3]:='three'; End;
TMasClass.GetMasInt{pIndex:integer):string;
131
begin
result: = 'unknown' ;
if pIndex in [1..3] then result:= FMasProp[pIndex];
end;
Function TMasClass.GetMasStr(pIndex:string):integer;
var x:integer; begin
result:=-l; for x:=l to 3 do
if Uppercase(BMasProp[x])=Uppercase(pIndex) then begin
result:=x;
exit;
end;
end; .
Здесь первая функция может читать значение свойства при заданном индексе, а вторая может использоваться для поиска индекса по заданному значению.
ЗАДАНИЕ НАЧАЛЬНЫХ ЗНАЧЕНИЙ СВОЙСТВАМ
Когда создаются свойства, они первоначально получают нулевые или неопределенные (в зависимости от типа свойства) значения. Если необходимо задать свойствам какие-либо значения по умолчанию, то используются следующие шесть команд: Default, NoDefault, Default для свойств типа массив, Stored, index, Dispid. Например,
Property HumberProp:integer read Fnumber
write FNumber default 5; Property EnumProp: TEnumProp Read FenumProp
write FEnumProp default epOne;.
Для свойства типа массив команда Default указывается после точки с запятой без использования какого-либо значения. Массив, если он задан при объявлении класса, представляет собой начальные значения элементов индексированного свойства.
Property ArrayPropInt[Index: integer] :
string read GetArrayPropInt; Default;.
Команда NoDefault применяется обычно для унаследованных свойств при указании, что родительское значение не действует.
Команда stored используется для того, чтобы указать, сохранять ли текущее значение, например,
Property NumProl:integer read Fnuml write FNuml Stored true; Property NumPro2:integer read FNum2 write FNum2 Stored false; Property NumPro3:integer read Fnum3 write Fnum3 Stored Fun3;-
132
В первом случае указано, что необходимо сохранять текущее значение свойства, во втором - не сохранять, в третьем - сохранять, если функция Fun3 возвращает true.
Команда Index позволяет обеспечить гибкость при объявлении методов доступа Read и write к полям.
Рассмотрим это на примере. Пусть два свойства intProp1 и intProp2 для доступа к полям PIntProp1 и FIntProp2 используют одни и те же подпрограммы:
Property IntProp1:integer Index 1 read GetIntProp
write SetIntProp;
Property IntProp2:integer Index 2 read GetIntProp
write SetIntProp;.
Для указания того, что свойство IntProp1 работает с полем FIntProp1, a intProp2 - с полем FintProp2, используется команда index. В данном случае число после слова Index указывает, какое поле следует использовать, например, подпрограммы GetIntProp и SetIntProp можно определить так:
Function <имя класса>.GetIntProp (pIndex:integer):integer; begin
case pIndex of
1:result:=FIntProp1; 2:result:=FIntProp2; end; end; Procedure <иия класса>.SetIntProp (pIndex:integer; pValue:integer);
begin
case pIndex of
1: FIntProp1:= pValue;
2: FIntProp2:= pValue;
end; end;.
Команда Dispid применяется при работе с объектами OLE.
ПРИМЕР ПРИЛОЖЕНИЯ 16
Пусть требуется построить на форме три геометрические фигуры: круг, квадрат, эллипс. Необходимо обеспечить возможность изменения цвета каждой фигуры. Пусть будет по два цветовых варианта каждой фигуры (рис. 36).
Для решения задачи построим четыре класса. Первый класс назовем TDrawArea. Включим в него следующие действия: инициализация всех по-
133
read
GetBrushColor;
procedure
DrawFig;virtual;abstract;
procedure
Drawlt; end;
TSquare
= class(TDrawArea)
лей,
рисование фигуры. Остальные три класса
определяют конкретную реa
лизацию рисования круга, квадрата и
эллипса.
Программа приводится ниже.
unit PRIM16;
interface
uses Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls, ExtCtrls; type
TForm1 = class(TForm) Panel1: TPanel; btnRedCircle: TButton; btnBlueCircle: TButton; btnBlackSquare: TButton; btnWhiteSquare: TButton; btnYellowEllipse: TButton; btnGreenEllipse: TButton; Button1: TButton;
procedure AllBtnClick(Sender: TObject); procedure Button1Click(Sender: TObject); end;
ArrayColor=array[1..6] of TColor; TDrawArea = class(TPersistent)
private
xl(yl,x2,y2:integer;
FArrayBrushColor: ArrayColor;
FPenColor: TColor;
function GetBrushColor(pindex:integer): TColor; public
constructor InitColor;
constructor InitXY(NewXl,NewYl,NewX2,NewY2rinteger);
property PPenColor: TColor read FpenColor
write FpenColor Default clBlack;
property PBrushColor[pindex:integer]: TColor
procedure DrawFig;override; end;
TEllipse=class{TDrawArea)
Procedure DrawFig;override; end;
TCircle = class(TDrawArea)
procedure DrawFig;override; end; var Form1: T Form1;
MySquare:TSquare; MyEllipse:TEllipse; MyCirde: TCircle ; implementation
{$R *.DFM}
Constructor TDrawArea
Begin InitColor
FArrayBrushColor[1] :=clRed;
FArrayBrushColor[2] :=clBlue;
FArrayBrushColor[3] :=clBlack;
FArrayBrushColor[4] :=clWhite;
FArrayBrushColor[5] :=clYellow;
FArrayBrushColor[6] :=clGreen;
End;
Constructor TDrawArea.InitXY(HewXl,NewYl,NewX2,NewY2:
integer);begin
xl:=NewXl;
yl:=NewYl;
x2:=NewX2;
y2:=NewY2; end; Function TDrawArea.GetBrushColor(pIndex:integer):TColor;
begin
result:=-1;
if pIndex in [1..6] then result:=FArrayBrushColor[pIndex]; end;
procedure TDrawArea.Drawlt; begin
DrawFig; end;
procedure TSquare.DrawFig; begin
Form1.Canvas.Rectangle(xl,yl,x2,y2);
134
135
procedure TCircle.DrawFig;
begin
Form1.Canvas.Ellipse(xl-x2,yl-x2,xl+x2,yl+x2);
end;
procedure TEllipse.DrawFig; begin
Form1.Canvas.Ellipse(xl,yl,x2,y2);
end;
procedure TForm1.AllBtnClick(Sender: TObject);
begin
if (Sender as Tbutton=btnRedCircle) or
(Sender as Tbutton=btnBlueCircle) then begin
if not assigned(MyCircle) then begin MyCircle:=TCircle.Create;
MyCircle.InitColor;
MyCircle.InitXY(50,50,40,0); Form1.Canvas.Pen.Color:=MyCircle.PPenColor;
end;
if Sender as Tbutton = btnRedCircle then begin Form1.Canvas.Brush.Color:=MyCircle.PBrushColor[1]; MyCircle.Drawlt;
end;
if Sender as Tbutton = btnBlueCircle then begin Form1.Canvas.Brush.Color:=MyCircle.PBrushColor[2]; MyCircle.Drawlt;
end;
end;
if (Sender as Tbutton=btnBlackSquare) or
(Sender as Tbutton=btnWhiteSquare) then begin
if not assigned(MySquare) then begin MySquare:=TSquare.Create;
MySquare.InitColor;
MySquare.InitXY(110,10,190,90}; Form1.Canvas.Pen.Color:=MySquare.PPenColor;
end;
if Sender as Tbutton = btnBlackSquare then begin Form1.Canvas.Brush.Color:=MySquare.PBrushColor[3]; MySquare.Drawlt;
end;
if Sender as Tbutton = btnWhiteSquare then begin Form1.Canvas.Brush.Color:=MySquare.PBrushColor[4]; MySquare.Drawlt;
end;
end;
if (Sender as Tbutton=btnYellowEllipse) or
(Sender as Tbutton=btnGreenEllipse} than begin
if not assigned(MyEllipse) then begin
136
MyEllipse:=TEllipse.Create; MyEllipse.InitColor; MyEllipse.InitXY(220,10,280,90); Form1.Canvas.Pen.Color:=MyEllipse.PPenColor; end;
if Sender as Tbutton = btnYellowEllipse then begin Form1.Canvas.Brush.Color:=MyEllipse.PBrushColor[5]; MyEllipse.Drawlt; end;
if Sender as Tbutton = btnGreenEllipse then begin Form1.Canvas.Brush.Color:=MyEllipse.PBrushColor[6]; MyEllipse.Drawlt; end; end; end;
procedure TForm1.Button1Click(Sender: TObject); begin
if assigned(MyCircle) then MyCircle.Free; if assigned(MySquare) then MySquare.Free; if assigned(MyEllipse) then MyEllipse.Free; close; end; end.
В данном примере показано, как работать со свойствами типа Array. Следует обратить внимание, что каждый из трех используемых объектов должен строиться один раз. Для проверки существования объекта используется функция Assigned. При выходе из программы (Button1ciick.) все объекты разрушаются. В данной программе показано, как использовать один обработчик событий (AllBtnClick) для всех шести кнопок построения геометрических фигур. Используемые конструкторы InitColor и InitXY созданы не для построения объектов, а для инициализации данных, поэтому в их описании отсутствует вызов inherited.
■
ФАЙЛОВЫЕ ТИПЫ
Тип файл представляет собой последовательность элементов одного типа, обычно расположенных на внешнем устройстве, например на жестком Диске. Можно выделить три основных типа файлов на физическом устройстве:
1. Данные в файле располагаются в виде строк разной длины. Каждая строка заканчивается двумя символами #13 #10 (#13 - возврат каретки,#10
137
перевод строки). Эти символы отделяют строки друг от друга. Максимальный размер строки ограничен, чаще всего он равен 128 символам, можно увеличить этот размер до максимального, равного 255 символам. При поиске нужной информации в таком файле необходимо найти #13 #10, прочитать найденную строку и проверить, есть ли заданная информация в этой строке, затем опять следует искать #13 и #10 и т.д. Таким образом, поиск может осуществляться, последовательно читая этот файл только сначала, т.е. файл рассматривается как один массив строк, разделенных символами #13 #10. Если нужно вставить информацию в середину такого файла, то это можно сделать путем переписывания старого файла в новый со вставкой новых строк и последующим удалением старого файла. Операционная система допускает дописывание информации в конец файла. В соответствии со свойствами такой файл называют файлом последовательного доступа, или текстовым.
-
Данные представляются в виде записей одинаковой длины. Никаких разделителей между записями нет. Операционная система может располо жить головку считывания-записи информации на любой байт (установить файловый указатель). Так как все записи одинаковой длины, файловый ука затель операционной системой может быть установлен на начало любой за писи, таким образом получается, что все записи будто бы пронумерованы. Первая запись нумеруется числом 0. Если длина записи равна 80 байт, то установку файлового указателя на начало второй записи (запись с номером 1) операционная система выполняет на отметке 80 байт. Эту запись можно прочитать или перезаписать. Такие файлы являются типизированными - файлами прямого доступа (в отличие от текстовых файлов).
-
Файлы рассматриваются как последовательность байт. Эти файлы яв ляются файлами прямого доступа с длиной записи, чаще всего, равной 1 байту. С помощью такого подхода можно прочитать любой файл, но трудно интерпретировать информацию.
При работе с файлами автоматически проверяются ошибки ввода или вывода, если включена (по умолчанию) директива {$I+}. В этом случае при возникновении ошибки программа завершается. Если директива проверки файловых ошибок отключена {$I-}, то программа продолжает выполняться и можно проверить результат работы с файлом с помощью стандартной функции IOResult.
ТЕКСТОВЫЕ ФАЙЛЫ
Этот тип файлов объявляется с помощью файловой переменной, типа TextFile:Var т : TextFile;.
Перед тем как начинать работу с файлом, необходимо файловую переменную связать с конкретным файлом. Это осуществляется с помощью сле-
138
дующей процедуры: AssignFile((<ф.п.>, Name) ; , где <ф.п.>-файловая переменная, Name - строка типа String, содержащая имя файла, например AssignFile(T, ‘a.txt') ; .Строка Name может содержать не только имя файла, но и путь к файлу.
По окончании работы с файлом его нужно закрыть процедурой Close-File (<ф. п. >). Это требование обязательно должно соблюдаться для файлов, в которые записывалась информация. С помощью файлов можно вводить информацию в память, хранить информацию, передавать ее от одних программных единиц к другим. Для чтения и записи информации используются встроенные стандартные процедуры.
Перед чтением информации необходимо явно указать, что файл будет использоваться только для чтения с помощью процедуры Reset (<ф.п.>) . Эта процедура устанавливает файловый указатель на начало файла. Чтение информации осуществляется с помощью следующих процедур:
ReadLn(<ф.п.>, <список>); Read(<ф.п.>, <список>);.
Первая процедура позволяет полностью читать строку вместе с символами #13 #10. Вторая процедура читает элемент или несколько элементов строки, но в пределах одной строки. Например, Read(T, a,b,c) ; ReadLn(T, a,b,c);.
Если а, Ь, с - строковые переменные, то первый оператор заполнит данными только одну переменную а (Ь и с останутся пустыми).
Текстовые файлы имеют одну особенность: несмотря на то, что файл состоит из строк, он может содержать, наряду со строковыми данными, и другие типы данных, например числовые. Прочитать из текстового файла можно данные следующих типов: целого, вещественного, символьного, строкового. Дополнительно к перечисленным типам записывать можно еще логический тип данных.
При чтении чисел автоматически происходит перекодирование информации в двоичный код. Если элементы не строковые, то в переделах одной строки файла может находиться несколько элементов, обязательно отделенных друг от друга разделителями. В качестве разделителей используются следующие символы: пробел, символ табуляции, возврат каретки (#13). При подготовке информации необходимо соблюдать эти требования - записывать в явном виде разделители. Процедуры Read и ReadLn читают информацию последовательно по одному символу (в том числе читаются #13, #10 и #26 - конец файла), причем, если чтение происходит не в строковые переменные, то значимыми для Read являются все виды разделителей, а для ReadLn - только #13 и #10. Например, при чтении информации в строковые переменные String или PChar заносятся очередные символы до #13 и #10.
139
Для подготовки текстовых файлов можно использовать не только программную среду Delphi, но и любые простые редакторы.
При записи информации необходимо для файла задать явно режим записи. Для текстовых файлов режим записи задается одновременно с режимом создания нового файла. При этом используется процедура Rewrite (<ф. п. >). Если файл, указанный в соответствующей процедуре AssignFile, существует, то он уничтожается и создается новый. Запись информации в файл осуществляется процедурамиWrite(<ф.п. >,<список>) или WriteLn(<ф. п. >,< список >). Write записывает очередной элемент строки, WriteLn записывает строку вместе с #13 и #10. В отличие от процедур чтения, в списке можно указывать не только переменные, но и выражения. Если элементы числовые, то в списке необходимо явно предусматривать разделители между этими элементами. Например, writeLn(T, 'число а=' ,а,' ' , 'число b=' ,b) ;.